From 846b6479537a112d1ded725e6484e46462048b35 Mon Sep 17 00:00:00 2001 From: Nikita Popov Date: Fri, 3 Jan 2020 15:35:10 +0100 Subject: [PATCH] Throw Error when referencing uninit typed prop in __sleep Previously this generated a notice, but would likely generate an Error when unserializing. Now we treat it with the same distinction as direct property accesses, i.e. referencing an unset/undefined normal property stays a notice, while a typed property becomes an Error exception. This fixed bug #79002. Closes GH-5050. --- NEWS | 2 + .../sleep_uninitialized_typed_prop.phpt | 60 +++++++++++++++++++ ext/standard/var.c | 48 ++++++++++++--- 3 files changed, 101 insertions(+), 9 deletions(-) create mode 100644 ext/standard/tests/serialize/sleep_uninitialized_typed_prop.phpt diff --git a/NEWS b/NEWS index 0a4ccbd28b..9a28926ff6 100644 --- a/NEWS +++ b/NEWS @@ -16,6 +16,8 @@ PHP NEWS (Dmitry) . Fixed bug #79008 (General performance regression with PHP 7.4 on Windows). (cmb) + . Fixed bug #79002 (Serializing uninitialized typed properties with __sleep + makes unserialize throw). (Nikita) - CURL: . Fixed bug #79033 (Curl timeout error with specific url and post). (cmb) diff --git a/ext/standard/tests/serialize/sleep_uninitialized_typed_prop.phpt b/ext/standard/tests/serialize/sleep_uninitialized_typed_prop.phpt new file mode 100644 index 0000000000..3d78e11f28 --- /dev/null +++ b/ext/standard/tests/serialize/sleep_uninitialized_typed_prop.phpt @@ -0,0 +1,60 @@ +--TEST-- +Referencing an uninitialized typed property in __sleep() should result in Error +--FILE-- +$name = $val; + } +} + +$t = new Test; +try { + serialize($t); +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} + +$t->x = 1; +try { + serialize($t); +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} + +$t->y = 2; +try { + serialize($t); +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} + +$t->z = 3; +try { + var_dump(unserialize(serialize($t))); +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} + +?> +--EXPECT-- +Typed property Test::$x must not be accessed before initialization (in __sleep) +Typed property Test::$y must not be accessed before initialization (in __sleep) +Typed property Test::$z must not be accessed before initialization (in __sleep) +object(Test)#3 (3) { + ["x"]=> + int(1) + ["y":protected]=> + int(2) + ["z":"Test":private]=> + int(3) +} diff --git a/ext/standard/var.c b/ext/standard/var.c index b611b121c9..c816dd05f9 100644 --- a/ext/standard/var.c +++ b/ext/standard/var.c @@ -772,7 +772,7 @@ static int php_var_serialize_call_magic_serialize(zval *retval, zval *obj) /* {{ /* }}} */ static int php_var_serialize_try_add_sleep_prop( - HashTable *ht, HashTable *props, zend_string *name, zend_string *error_name) /* {{{ */ + HashTable *ht, HashTable *props, zend_string *name, zend_string *error_name, zval *struc) /* {{{ */ { zval *val = zend_hash_find(props, name); if (val == NULL) { @@ -782,6 +782,12 @@ static int php_var_serialize_try_add_sleep_prop( if (Z_TYPE_P(val) == IS_INDIRECT) { val = Z_INDIRECT_P(val); if (Z_TYPE_P(val) == IS_UNDEF) { + zend_property_info *info = zend_get_typed_property_info_for_slot(Z_OBJ_P(struc), val); + if (info) { + zend_throw_error(NULL, + "Typed property %s::$%s must not be accessed before initialization (in __sleep)", + ZSTR_VAL(Z_OBJCE_P(struc)->name), ZSTR_VAL(error_name)); + } return FAILURE; } } @@ -797,14 +803,17 @@ static int php_var_serialize_try_add_sleep_prop( } /* }}} */ -static void php_var_serialize_get_sleep_props( +static int php_var_serialize_get_sleep_props( HashTable *ht, zval *struc, HashTable *sleep_retval) /* {{{ */ { zend_class_entry *ce = Z_OBJCE_P(struc); HashTable *props = zend_get_properties_for(struc, ZEND_PROP_PURPOSE_SERIALIZE); zval *name_val; + int retval = SUCCESS; zend_hash_init(ht, zend_hash_num_elements(sleep_retval), NULL, ZVAL_PTR_DTOR, 0); + /* TODO: Rewrite this by fetching the property info instead of trying out different + * name manglings? */ ZEND_HASH_FOREACH_VAL(sleep_retval, name_val) { zend_string *name, *tmp_name, *priv_name, *prot_name; @@ -815,36 +824,56 @@ static void php_var_serialize_get_sleep_props( } name = zval_get_tmp_string(name_val, &tmp_name); - if (php_var_serialize_try_add_sleep_prop(ht, props, name, name) == SUCCESS) { + if (php_var_serialize_try_add_sleep_prop(ht, props, name, name, struc) == SUCCESS) { zend_tmp_string_release(tmp_name); continue; } + if (EG(exception)) { + zend_tmp_string_release(tmp_name); + retval = FAILURE; + break; + } + priv_name = zend_mangle_property_name( ZSTR_VAL(ce->name), ZSTR_LEN(ce->name), ZSTR_VAL(name), ZSTR_LEN(name), ce->type & ZEND_INTERNAL_CLASS); - if (php_var_serialize_try_add_sleep_prop(ht, props, priv_name, name) == SUCCESS) { + if (php_var_serialize_try_add_sleep_prop(ht, props, priv_name, name, struc) == SUCCESS) { zend_tmp_string_release(tmp_name); zend_string_release(priv_name); continue; } zend_string_release(priv_name); + if (EG(exception)) { + zend_tmp_string_release(tmp_name); + retval = FAILURE; + break; + } + prot_name = zend_mangle_property_name( "*", 1, ZSTR_VAL(name), ZSTR_LEN(name), ce->type & ZEND_INTERNAL_CLASS); - if (php_var_serialize_try_add_sleep_prop(ht, props, prot_name, name) == SUCCESS) { + if (php_var_serialize_try_add_sleep_prop(ht, props, prot_name, name, struc) == SUCCESS) { zend_tmp_string_release(tmp_name); zend_string_release(prot_name); continue; } zend_string_release(prot_name); + if (EG(exception)) { + zend_tmp_string_release(tmp_name); + retval = FAILURE; + break; + } + php_error_docref(NULL, E_NOTICE, "\"%s\" returned as member variable from __sleep() but does not exist", ZSTR_VAL(name)); zend_hash_add(ht, name, &EG(uninitialized_zval)); zend_tmp_string_release(tmp_name); } ZEND_HASH_FOREACH_END(); + zend_release_properties(props); + return retval; } /* }}} */ @@ -900,10 +929,11 @@ static void php_var_serialize_nested_data(smart_str *buf, zval *struc, HashTable static void php_var_serialize_class(smart_str *buf, zval *struc, zval *retval_ptr, php_serialize_data_t var_hash) /* {{{ */ { HashTable props; - php_var_serialize_get_sleep_props(&props, struc, HASH_OF(retval_ptr)); - php_var_serialize_class_name(buf, struc); - php_var_serialize_nested_data( - buf, struc, &props, zend_hash_num_elements(&props), /* incomplete_class */ 0, var_hash); + if (php_var_serialize_get_sleep_props(&props, struc, HASH_OF(retval_ptr)) == SUCCESS) { + php_var_serialize_class_name(buf, struc); + php_var_serialize_nested_data( + buf, struc, &props, zend_hash_num_elements(&props), /* incomplete_class */ 0, var_hash); + } zend_hash_destroy(&props); } /* }}} */ -- 2.40.0