diff --git a/hphp/compiler/analysis/emitter.cpp b/hphp/compiler/analysis/emitter.cpp index cfca79ebb..78a0aa913 100644 --- a/hphp/compiler/analysis/emitter.cpp +++ b/hphp/compiler/analysis/emitter.cpp @@ -4305,6 +4305,9 @@ void EmitterVisitor::emitBuiltinDefaultArg(Emitter& e, Variant& v, not_reached(); } break; + case KindOfArray: + e.Array(v.getArrayData()); + break; default: not_reached(); } diff --git a/hphp/compiler/expression/scalar_expression.cpp b/hphp/compiler/expression/scalar_expression.cpp index 76f29ff20..b4eb5771a 100644 --- a/hphp/compiler/expression/scalar_expression.cpp +++ b/hphp/compiler/expression/scalar_expression.cpp @@ -437,7 +437,7 @@ int64_t ScalarExpression::getHash() const { Variant ScalarExpression::getVariant() const { if (!m_serializedValue.empty()) { Variant ret = unserialize_from_buffer( - m_serializedValue.data(), m_serializedValue.size()); + m_serializedValue.data(), m_serializedValue.size(), null_array); if (ret.isDouble()) { return m_dval; } diff --git a/hphp/idl/base.php b/hphp/idl/base.php index ab2ec2371..1365a4b23 100644 --- a/hphp/idl/base.php +++ b/hphp/idl/base.php @@ -415,11 +415,10 @@ function get_serialized_default($s) { if (preg_match('/^".*"$/', $s) || preg_match('/^[\-0-9.]+$/', $s) || preg_match('/^0x[0-9a-fA-F]+$/', $s) || - preg_match('/^(true|false|null)$/', $s) || - $s == 'Array()' - ) { + preg_match('/^(true|false|null)$/', $s)) { return serialize(eval("return $s;")); } + if ($s == "empty_array") return serialize(array()); if (preg_match('/^null_(string|array|object|variant)$/', $s)) { return serialize(null); } diff --git a/hphp/idl/string.idl.php b/hphp/idl/string.idl.php index 1fb988149..d97bff6c0 100644 --- a/hphp/idl/string.idl.php +++ b/hphp/idl/string.idl.php @@ -972,7 +972,7 @@ DefineFunction( array( 'name' => "extra", 'type' => StringVec, - 'value' => "Array()", + 'value' => "empty_array", 'desc' => "An array of extra ascii chars to be encoded.", ), ), diff --git a/hphp/idl/variable.idl.php b/hphp/idl/variable.idl.php index 1162bbe54..e8575b1ea 100644 --- a/hphp/idl/variable.idl.php +++ b/hphp/idl/variable.idl.php @@ -536,6 +536,12 @@ DefineFunction( 'type' => String, 'desc' => "The serialized string.\n\nIf the variable being unserialized is an object, after successfully reconstructing the object PHP will automatically attempt to call the __wakeup() member function (if it exists).\n\nunserialize_callback_func directive\n\nIt's possible to set a callback-function which will be called, if an undefined class should be instantiated during unserializing. (to prevent getting an incomplete object \"__PHP_Incomplete_Class\".) Use your php.ini, ini_set() or .htaccess to define 'unserialize_callback_func'. Everytime an undefined class should be instantiated, it'll be called. To disable this feature just empty this setting.", ), + array( + 'name' => "class_whitelist", + 'type' => StringVec, + 'desc' => "The array of the class names that are authorized to be unserialized. The array is default to be empty, which means no class is allowed. If the class name is a super class, all its subclasses are also allowed. Note that primitive types and Serializable classes are not subject to this whitelist. See http://fburl.com/SafeSerializable for the reason why we need this.", + 'value' => "empty_array" + ), ), )); diff --git a/hphp/runtime/base/builtin_functions.cpp b/hphp/runtime/base/builtin_functions.cpp index 9d5dcacfe..fb6614127 100644 --- a/hphp/runtime/base/builtin_functions.cpp +++ b/hphp/runtime/base/builtin_functions.cpp @@ -747,15 +747,18 @@ String f_serialize(CVarRef value) { } Variant unserialize_ex(const char* str, int len, - VariableUnserializer::Type type) { + VariableUnserializer::Type type, + CArrRef class_whitelist /* = null_array */) { if (str == nullptr || len <= 0) { return false; } - VariableUnserializer vu(str, len, type); + VariableUnserializer vu(str, len, type, false, class_whitelist); Variant v; try { v = vu.unserialize(); + } catch (FatalErrorException &e) { + throw; } catch (Exception &e) { raise_notice("Unable to unserialize: [%s]. %s.", str, e.getMessage().c_str()); @@ -764,8 +767,10 @@ Variant unserialize_ex(const char* str, int len, return v; } -Variant unserialize_ex(CStrRef str, VariableUnserializer::Type type) { - return unserialize_ex(str.data(), str.size(), type); +Variant unserialize_ex(CStrRef str, + VariableUnserializer::Type type, + CArrRef class_whitelist /* = null_array */) { + return unserialize_ex(str.data(), str.size(), type, class_whitelist); } String concat3(CStrRef s1, CStrRef s2, CStrRef s3) { diff --git a/hphp/runtime/base/builtin_functions.h b/hphp/runtime/base/builtin_functions.h index fb191084a..0aa8aa6b8 100644 --- a/hphp/runtime/base/builtin_functions.h +++ b/hphp/runtime/base/builtin_functions.h @@ -483,21 +483,28 @@ Object f_clone(CVarRef v); char const kUnserializableString[] = "\x01"; /** - * Serialize/unserialize a variant into/from a string. We need these two - * functions in runtime/base, as there are functions in runtime/base that depend on - * these two functions. + * Serialize/unserialize a variant into/from a string. We need these + * two functions in runtime/base, as there are functions in + * runtime/base that depend on these two functions. */ String f_serialize(CVarRef value); -Variant unserialize_ex(CStrRef str, VariableUnserializer::Type type); +Variant unserialize_ex(CStrRef str, + VariableUnserializer::Type type, + CArrRef class_whitelist = null_array); Variant unserialize_ex(const char* str, int len, - VariableUnserializer::Type type); + VariableUnserializer::Type type, + CArrRef class_whitelist = null_array); -inline Variant unserialize_from_buffer(const char* str, int len) { - return unserialize_ex(str, len, VariableUnserializer::Serialize); +inline Variant unserialize_from_buffer(const char* str, int len, + CArrRef class_whitelist = null_array) { + return unserialize_ex(str, len, + VariableUnserializer::Serialize, + class_whitelist); } -inline Variant unserialize_from_string(CStrRef str) { - return unserialize_from_buffer(str.data(), str.size()); +inline Variant unserialize_from_string(CStrRef str, + CArrRef class_whitelist = null_array) { + return unserialize_from_buffer(str.data(), str.size(), class_whitelist); } String resolve_include(CStrRef file, const char* currentDir, diff --git a/hphp/runtime/base/runtime_option.cpp b/hphp/runtime/base/runtime_option.cpp index c8a1877ce..ed4671269 100644 --- a/hphp/runtime/base/runtime_option.cpp +++ b/hphp/runtime/base/runtime_option.cpp @@ -220,6 +220,9 @@ bool RuntimeOption::WhitelistExec = false; bool RuntimeOption::WhitelistExecWarningOnly = false; std::vector RuntimeOption::AllowedExecCmds; +bool RuntimeOption::UnserializationWhitelistCheck = false; +bool RuntimeOption::UnserializationWhitelistCheckWarningOnly = true; + std::string RuntimeOption::TakeoverFilename; int RuntimeOption::AdminServerPort; int RuntimeOption::AdminThreadCount = 1; @@ -773,6 +776,11 @@ void RuntimeOption::Load(Hdf &config, StringVec *overwrites /* = NULL */, WhitelistExecWarningOnly = server["WhitelistExecWarningOnly"].getBool(); server["AllowedExecCmds"].get(AllowedExecCmds); + UnserializationWhitelistCheck = + server["UnserializationWhitelistCheck"].getBool(false); + UnserializationWhitelistCheckWarningOnly = + server["UnserializationWhitelistCheckWarningOnly"].getBool(true); + server["AllowedFiles"].get(AllowedFiles); server["ForbiddenFileExtensions"].get(ForbiddenFileExtensions); diff --git a/hphp/runtime/base/runtime_option.h b/hphp/runtime/base/runtime_option.h index c43233a89..7fc4f1c7f 100644 --- a/hphp/runtime/base/runtime_option.h +++ b/hphp/runtime/base/runtime_option.h @@ -219,6 +219,9 @@ public: static bool WhitelistExecWarningOnly; static std::vector AllowedExecCmds; + static bool UnserializationWhitelistCheck; + static bool UnserializationWhitelistCheckWarningOnly; + static std::string TakeoverFilename; static int AdminServerPort; static int AdminThreadCount; diff --git a/hphp/runtime/base/type_array.cpp b/hphp/runtime/base/type_array.cpp index 049c08ebc..f6a0ab03b 100644 --- a/hphp/runtime/base/type_array.cpp +++ b/hphp/runtime/base/type_array.cpp @@ -34,6 +34,7 @@ namespace HPHP { const Array null_array = Array(); +const Array empty_array = HphpArray::GetStaticEmptyArray(); void Array::setEvalScalar() const { Array* thisPtr = const_cast(this); diff --git a/hphp/runtime/base/type_variant.cpp b/hphp/runtime/base/type_variant.cpp index 73ae08a83..ce2502994 100644 --- a/hphp/runtime/base/type_variant.cpp +++ b/hphp/runtime/base/type_variant.cpp @@ -3089,6 +3089,18 @@ void Variant::unserialize(VariableUnserializer *uns, VM::Class* cls = VM::Unit::loadClass(clsName.get()); Object obj; + if (RuntimeOption::UnserializationWhitelistCheck && + !uns->isWhitelistedClass(clsName)) { + String err_msg = + "The object being unserialized with class name '%s' " + "is not in the given whitelist. " + "See http://fburl.com/SafeSerializable for more detail"; + if (RuntimeOption::UnserializationWhitelistCheckWarningOnly) { + raise_warning(err_msg, clsName.c_str()); + } else { + raise_error(err_msg, clsName.c_str()); + } + } if (cls) { obj = VM::Instance::newInstance(cls); if (UNLIKELY(cls == c_Pair::s_cls && size != 2)) { diff --git a/hphp/runtime/base/types.h b/hphp/runtime/base/types.h index dc3af03e7..59be67cd4 100644 --- a/hphp/runtime/base/types.h +++ b/hphp/runtime/base/types.h @@ -67,6 +67,7 @@ extern const VarNR NEGINF_varNR; extern const VarNR NAN_varNR; extern const String null_string; extern const Array null_array; +extern const Array empty_array; /* * All TypedValue-compatible types have their reference count field at diff --git a/hphp/runtime/base/variable_unserializer.cpp b/hphp/runtime/base/variable_unserializer.cpp index 9bd42b9a3..56463a1be 100644 --- a/hphp/runtime/base/variable_unserializer.cpp +++ b/hphp/runtime/base/variable_unserializer.cpp @@ -17,7 +17,8 @@ #include #include #include - +#include +#include namespace HPHP { /////////////////////////////////////////////////////////////////////////////// @@ -68,5 +69,20 @@ Variant &VariableUnserializer::addVar() { return m_vars.back(); } +bool VariableUnserializer::isWhitelistedClass(CStrRef cls_name) const { + if (m_type != Serialize || m_classWhiteList.isNull()) { + return true; + } + if (!m_classWhiteList.isNull() && !m_classWhiteList.empty()) { + for (ArrayIter iter(m_classWhiteList); iter; ++iter) { + CVarRef value(iter.secondRef()); + if (f_is_subclass_of(cls_name, value) || value.same(cls_name)) { + return true; + } + } + } + return false; +} + /////////////////////////////////////////////////////////////////////////////// } diff --git a/hphp/runtime/base/variable_unserializer.h b/hphp/runtime/base/variable_unserializer.h index e3f436616..e9491ef78 100644 --- a/hphp/runtime/base/variable_unserializer.h +++ b/hphp/runtime/base/variable_unserializer.h @@ -33,17 +33,27 @@ public: }; public: + /** + * Be aware that the default class_whitelist is null_array instead of + * empty_array here because we do not limit the unserialization of arbitrary + * class for hphp internal use + */ VariableUnserializer(const char *str, size_t len, Type type, - bool allowUnknownSerializableClass = false) + bool allowUnknownSerializableClass = false, + CArrRef class_whitelist = null_array) : m_type(type), m_buf(str), m_end(str + len), - m_unknownSerializable(allowUnknownSerializableClass) {} + m_unknownSerializable(allowUnknownSerializableClass), + m_classWhiteList(class_whitelist) {} VariableUnserializer(const char *str, const char *end, Type type, - bool allowUnknownSerializableClass = false) + bool allowUnknownSerializableClass = false, + CArrRef class_whitelist = null_array) : m_type(type), m_buf(str), m_end(end), - m_unknownSerializable(allowUnknownSerializableClass) {} + m_unknownSerializable(allowUnknownSerializableClass), + m_classWhiteList(class_whitelist) {} Type getType() const { return m_type;} bool allowUnknownSerializableClass() const { return m_unknownSerializable;} + bool isWhitelistedClass(CStrRef cls_name) const; Variant unserialize(); Variant unserializeKey(); @@ -123,6 +133,7 @@ public: smart::vector m_refs; smart::list m_vars; bool m_unknownSerializable; + CArrRef m_classWhiteList; // classes allowed to be unserialized void check() { if (m_buf >= m_end) { diff --git a/hphp/runtime/ext/ext_string.ext_hhvm.cpp b/hphp/runtime/ext/ext_string.ext_hhvm.cpp index 9e658d3d6..64c079573 100644 --- a/hphp/runtime/ext/ext_string.ext_hhvm.cpp +++ b/hphp/runtime/ext/ext_string.ext_hhvm.cpp @@ -2145,7 +2145,7 @@ TypedValue * fg1_fb_htmlspecialchars(TypedValue* rv, HPHP::VM::ActRec* ar, int64 tvCastToStringInPlace(args-0); } String defVal2 = "ISO-8859-1"; - Array defVal3 = Array(); + Array defVal3 = empty_array; fh_fb_htmlspecialchars((&rv->m_data), &args[-0].m_data, (count > 1) ? (int)(args[-1].m_data.num) : (int)(k_ENT_COMPAT), (count > 2) ? &args[-2].m_data : (Value*)(&defVal2), (count > 3) ? &args[-3].m_data : (Value*)(&defVal3)); if (rv->m_data.num == 0LL) rv->m_type = KindOfNull; return rv; @@ -2159,7 +2159,7 @@ TypedValue* fg_fb_htmlspecialchars(HPHP::VM::ActRec *ar) { if ((count <= 3 || (args-3)->m_type == KindOfArray) && (count <= 2 || IS_STRING_TYPE((args-2)->m_type)) && (count <= 1 || (args-1)->m_type == KindOfInt64) && IS_STRING_TYPE((args-0)->m_type)) { rv.m_type = KindOfString; String defVal2 = "ISO-8859-1"; - Array defVal3 = Array(); + Array defVal3 = empty_array; fh_fb_htmlspecialchars((&rv.m_data), &args[-0].m_data, (count > 1) ? (int)(args[-1].m_data.num) : (int)(k_ENT_COMPAT), (count > 2) ? &args[-2].m_data : (Value*)(&defVal2), (count > 3) ? &args[-3].m_data : (Value*)(&defVal3)); if (rv.m_data.num == 0LL) rv.m_type = KindOfNull; frame_free_locals_no_this_inl(ar, 4); diff --git a/hphp/runtime/ext/ext_string.h b/hphp/runtime/ext/ext_string.h index f71ba383a..eb10668be 100644 --- a/hphp/runtime/ext/ext_string.h +++ b/hphp/runtime/ext/ext_string.h @@ -90,7 +90,7 @@ String f_htmlspecialchars(CStrRef str, int quote_style = k_ENT_COMPAT, bool double_encode = true); String f_fb_htmlspecialchars(CStrRef str, int quote_style = k_ENT_COMPAT, CStrRef charset = "ISO-8859-1", - CArrRef extra = Array()); + CArrRef extra = empty_array); String f_quoted_printable_encode(CStrRef str); String f_quoted_printable_decode(CStrRef str); Variant f_convert_uudecode(CStrRef data); diff --git a/hphp/runtime/ext/ext_variable.cpp b/hphp/runtime/ext/ext_variable.cpp index 687096903..d140df9dd 100644 --- a/hphp/runtime/ext/ext_variable.cpp +++ b/hphp/runtime/ext/ext_variable.cpp @@ -182,8 +182,8 @@ void f_debug_zval_dump(CVarRef variable) { vs.serialize(variable, false); } -Variant f_unserialize(CStrRef str) { - return unserialize_from_string(str); +Variant f_unserialize(CStrRef str, CArrRef class_whitelist /* = empty_array */) { + return unserialize_from_string(str, class_whitelist); } /////////////////////////////////////////////////////////////////////////////// diff --git a/hphp/runtime/ext/ext_variable.ext_hhvm.cpp b/hphp/runtime/ext/ext_variable.ext_hhvm.cpp index 673921843..34bb69971 100644 --- a/hphp/runtime/ext/ext_variable.ext_hhvm.cpp +++ b/hphp/runtime/ext/ext_variable.ext_hhvm.cpp @@ -958,21 +958,33 @@ TypedValue* fg_debug_zval_dump(HPHP::VM::ActRec *ar) { /* -HPHP::Variant HPHP::f_unserialize(HPHP::String const&) -_ZN4HPHP13f_unserializeERKNS_6StringE +HPHP::Variant HPHP::f_unserialize(HPHP::String const&, HPHP::Array const&) +_ZN4HPHP13f_unserializeERKNS_6StringERKNS_5ArrayE (return value) => rax _rv => rdi str => rsi +class_whitelist => rdx */ -TypedValue* fh_unserialize(TypedValue* _rv, Value* str) asm("_ZN4HPHP13f_unserializeERKNS_6StringE"); +TypedValue* fh_unserialize(TypedValue* _rv, Value* str, Value* class_whitelist) asm("_ZN4HPHP13f_unserializeERKNS_6StringERKNS_5ArrayE"); TypedValue * fg1_unserialize(TypedValue* rv, HPHP::VM::ActRec* ar, int64_t count) __attribute__((noinline,cold)); TypedValue * fg1_unserialize(TypedValue* rv, HPHP::VM::ActRec* ar, int64_t count) { TypedValue* args UNUSED = ((TypedValue*)ar) - 1; - tvCastToStringInPlace(args-0); - fh_unserialize((rv), &args[-0].m_data); + switch (count) { + default: // count >= 2 + if ((args-1)->m_type != KindOfArray) { + tvCastToArrayInPlace(args-1); + } + case 1: + break; + } + if (!IS_STRING_TYPE((args-0)->m_type)) { + tvCastToStringInPlace(args-0); + } + Array defVal1 = empty_array; + fh_unserialize((rv), &args[-0].m_data, (count > 1) ? &args[-1].m_data : (Value*)(&defVal1)); if (rv->m_type == KindOfUninit) rv->m_type = KindOfNull; return rv; } @@ -981,25 +993,26 @@ TypedValue* fg_unserialize(HPHP::VM::ActRec *ar) { TypedValue rv; int64_t count = ar->numArgs(); TypedValue* args UNUSED = ((TypedValue*)ar) - 1; - if (count == 1LL) { - if (IS_STRING_TYPE((args-0)->m_type)) { - fh_unserialize((&(rv)), &args[-0].m_data); + if (count >= 1LL && count <= 2LL) { + if ((count <= 1 || (args-1)->m_type == KindOfArray) && IS_STRING_TYPE((args-0)->m_type)) { + Array defVal1 = empty_array; + fh_unserialize((&(rv)), &args[-0].m_data, (count > 1) ? &args[-1].m_data : (Value*)(&defVal1)); if (rv.m_type == KindOfUninit) rv.m_type = KindOfNull; - frame_free_locals_no_this_inl(ar, 1); + frame_free_locals_no_this_inl(ar, 2); memcpy(&ar->m_r, &rv, sizeof(TypedValue)); return &ar->m_r; } else { fg1_unserialize(&rv, ar, count); - frame_free_locals_no_this_inl(ar, 1); + frame_free_locals_no_this_inl(ar, 2); memcpy(&ar->m_r, &rv, sizeof(TypedValue)); return &ar->m_r; } } else { - throw_wrong_arguments_nr("unserialize", count, 1, 1, 1); + throw_wrong_arguments_nr("unserialize", count, 1, 2, 1); } rv.m_data.num = 0LL; rv.m_type = KindOfNull; - frame_free_locals_no_this_inl(ar, 1); + frame_free_locals_no_this_inl(ar, 2); memcpy(&ar->m_r, &rv, sizeof(TypedValue)); return &ar->m_r; return &ar->m_r; diff --git a/hphp/runtime/ext/ext_variable.ext_hhvm.h b/hphp/runtime/ext/ext_variable.ext_hhvm.h index 6bddffe3a..6c2406932 100644 --- a/hphp/runtime/ext/ext_variable.ext_hhvm.h +++ b/hphp/runtime/ext/ext_variable.ext_hhvm.h @@ -276,15 +276,16 @@ variable => rdi void fh_debug_zval_dump(TypedValue* variable) asm("_ZN4HPHP17f_debug_zval_dumpERKNS_7VariantE"); /* -HPHP::Variant HPHP::f_unserialize(HPHP::String const&) -_ZN4HPHP13f_unserializeERKNS_6StringE +HPHP::Variant HPHP::f_unserialize(HPHP::String const&, HPHP::Array const&) +_ZN4HPHP13f_unserializeERKNS_6StringERKNS_5ArrayE (return value) => rax _rv => rdi str => rsi +class_whitelist => rdx */ -TypedValue* fh_unserialize(TypedValue* _rv, Value* str) asm("_ZN4HPHP13f_unserializeERKNS_6StringE"); +TypedValue* fh_unserialize(TypedValue* _rv, Value* str, Value* class_whitelist) asm("_ZN4HPHP13f_unserializeERKNS_6StringERKNS_5ArrayE"); /* HPHP::Array HPHP::f_get_defined_vars() diff --git a/hphp/runtime/ext/ext_variable.h b/hphp/runtime/ext/ext_variable.h index dfd3f7a6d..8ce6ce595 100644 --- a/hphp/runtime/ext/ext_variable.h +++ b/hphp/runtime/ext/ext_variable.h @@ -61,7 +61,8 @@ void f_var_dump(CVarRef v); void f_var_dump(int _argc, CVarRef expression, CArrRef _argv = null_array); void f_debug_zval_dump(CVarRef variable); String f_serialize(CVarRef value); -Variant f_unserialize(CStrRef str); +Variant f_unserialize(CStrRef str, + CArrRef class_whitelist = empty_array); /////////////////////////////////////////////////////////////////////////////// // variable table diff --git a/hphp/system/class_map.cpp b/hphp/system/class_map.cpp index d26512c60..cc465b234 100644 --- a/hphp/system/class_map.cpp +++ b/hphp/system/class_map.cpp @@ -8596,7 +8596,7 @@ const char *g_class_map[] = { (const char *)0x14 /* KindOfString */, (const char *)0x2000, "str", "", (const char *)0x14 /* KindOfString */, "", (const char *)0, "", (const char *)0, NULL, (const char *)0x2000, "quote_style", "", (const char *)0xa /* KindOfInt64 */, "i:2;", (const char *)4, "ENT_COMPAT", (const char *)10, NULL, (const char *)0x2000, "charset", "", (const char *)0x14 /* KindOfString */, "s:10:\"ISO-8859-1\";", (const char *)18, "\"ISO-8859-1\"", (const char *)12, NULL, - (const char *)0x2000, "extra", "", (const char *)0x20 /* KindOfArray */, "a:0:{}", (const char *)6, "Array()", (const char *)7, NULL, + (const char *)0x2000, "extra", "", (const char *)0x20 /* KindOfArray */, "a:0:{}", (const char *)6, "empty_array", (const char *)11, NULL, NULL, NULL, NULL, @@ -9617,8 +9617,9 @@ const char *g_class_map[] = { NULL, NULL, (const char *)0x10006040, "unserialize", "", (const char*)0, (const char*)0, - "/**\n * ( excerpt from http://php.net/manual/en/function.unserialize.php )\n *\n *\n * @str string The serialized string.\n *\n * If the variable being unserialized is an object,\n * after successfully reconstructing the object PHP\n * will automatically attempt to call the __wakeup()\n * member function (if it exists).\n *\n * unserialize_callback_func directive\n *\n * It's possible to set a callback-function which will\n * be called, if an undefined class should be\n * instantiated during unserializing. (to prevent\n * getting an incomplete object\n * \"__PHP_Incomplete_Class\".) Use your php.ini,\n * ini_set() or .htaccess to define\n * 'unserialize_callback_func'. Everytime an undefined\n * class should be instantiated, it'll be called. To\n * disable this feature just empty this setting.\n *\n * @return mixed The converted value is returned, and can be a\n * boolean, integer, float, string, array or object.\n *\n * In case the passed string is not unserializeable,\n * FALSE is returned and E_NOTICE is issued.\n */", + "/**\n * ( excerpt from http://php.net/manual/en/function.unserialize.php )\n *\n *\n * @str string The serialized string.\n *\n * If the variable being unserialized is an object,\n * after successfully reconstructing the object PHP\n * will automatically attempt to call the __wakeup()\n * member function (if it exists).\n *\n * unserialize_callback_func directive\n *\n * It's possible to set a callback-function which will\n * be called, if an undefined class should be\n * instantiated during unserializing. (to prevent\n * getting an incomplete object\n * \"__PHP_Incomplete_Class\".) Use your php.ini,\n * ini_set() or .htaccess to define\n * 'unserialize_callback_func'. Everytime an undefined\n * class should be instantiated, it'll be called. To\n * disable this feature just empty this setting.\n * @class_whitelist\n * vector The array of the class names that are authorized to\n * be unserialized. The array is default to be empty,\n * which means no class is allowed. If the class name\n * is a super class, all its subclasses are also\n * allowed. Note that primitive types and Serializable\n * classes are not subject to this whitelist. See\n * http://fburl.com/SafeSerializable for the reason why\n * we need this.\n *\n * @return mixed The converted value is returned, and can be a\n * boolean, integer, float, string, array or object.\n *\n * In case the passed string is not unserializeable,\n * FALSE is returned and E_NOTICE is issued.\n */", (const char *)0xffffffff /* KindOfUnknown: $t: Variant */, (const char *)0x2000, "str", "", (const char *)0x14 /* KindOfString */, "", (const char *)0, "", (const char *)0, NULL, + (const char *)0x2000, "class_whitelist", "", (const char *)0x20 /* KindOfArray */, "a:0:{}", (const char *)6, "empty_array", (const char *)11, NULL, NULL, NULL, NULL, diff --git a/hphp/test/quick/serialize.php b/hphp/test/quick/serialize.php index 0ea568bc1..099b4a921 100644 --- a/hphp/test/quick/serialize.php +++ b/hphp/test/quick/serialize.php @@ -10,8 +10,88 @@ class B extends A { public $b = 0; } -$a = new B; -$b = serialize($a); -var_dump($b); -$c = unserialize($b); -var_dump($c); +class C { + public $a, $b, $c; + function __construct() { + $this->a = null; + $this->b = acos(1.01); + $this->c = log(0); + echo "C has a safe constructor.\n"; + } + function __destruct() { + echo "C has a safe destrcutor.\n"; + } + function __wakeup() { + echo "C wakes up safely.\n"; + } + function __sleep() { + echo "C sleeps safely.\n"; + return array('a', 'b', 'c'); + } +} + +class DangerousClass { + public $danger = "DangerousString"; + function __construct() { + echo "I have dangerous constructor.\n"; + } + function __destruct() { + echo "I have dangerous destructor.\n"; + } + function __wakeup() { + echo "I wake up dangerously.\n"; + } + function __sleep() { + echo "I sleep dangerously.\n"; + return array('danger'); + } +} + +class E { + public $dangerousClass; + function __construct() { + $this->dangerousClass = new DangerousClass; + } +} + +class F implements Serializable { + public function serialize() { + return "SerializedData"; + } + public function unserialize($serialized) { + echo $serialized; + return $this; + } +} + +class G extends DangerousClass { +} + +function test_serialization($obj, $class_whitelist) { + $str = serialize($obj); + var_dump($str); + $new_obj = unserialize($str, $class_whitelist); + var_dump($new_obj); + unset($obj); + unset($new_obj); + echo "========================\n"; +} + +function main(int $argc, array $argv) { + // null will be autmatically translated into empty array (see idl definition) + // So it should still not allow any class. + test_serialization(new A, null); + test_serialization(new B, array('A')); + test_serialization(new C, array('C')); + test_serialization(new DangerousClass, array()); + test_serialization(new E, array('E')); + test_serialization(new F, array()); + test_serialization(new G, array('G')); + test_serialization(array("Hello World<>$%", acos(1.01), log(0), 50), array()); + test_serialization( + array( new A, array(new B, array(new C, array(new E, array(new F))))), + array('abc' => 'A', 5 => 'C', 'E') + ); +} + +exit(main($argc, $argv)); diff --git a/hphp/test/quick/serialize.php.expect b/hphp/test/quick/serialize.php.expect deleted file mode 100644 index a88f9feee..000000000 Binary files a/hphp/test/quick/serialize.php.expect and /dev/null differ diff --git a/hphp/test/quick/serialize.php.expectf b/hphp/test/quick/serialize.php.expectf new file mode 100644 index 000000000..dc31c334f Binary files /dev/null and b/hphp/test/quick/serialize.php.expectf differ diff --git a/hphp/test/quick/serialize.php.opts b/hphp/test/quick/serialize.php.opts new file mode 100644 index 000000000..79da92bd9 --- /dev/null +++ b/hphp/test/quick/serialize.php.opts @@ -0,0 +1 @@ +-vServer.UnserializationWhitelistCheck=true -vServer.UnserializationWhitelistCheckWarningOnly=true