diff --git a/NEWS b/NEWS index 9336ed394c35..25a37ba072bf 100644 --- a/NEWS +++ b/NEWS @@ -108,6 +108,8 @@ PHP NEWS file fails after it was already truncated. (Ilia Alshanetsky) - Standard: + . Fixed __unserialize() and __wakeup() being called for objects with a missing + closing brace. (Ilia Alshanetsky) . Fixed bug GH-24399 (usleep() and time_nanosleep() ignore queued APCs on Windows). (Nicolas Grekas) . Fixed chown() and lchown() failing to resolve user names in ZTS builds diff --git a/ext/date/tests/unserialize-test.phpt b/ext/date/tests/unserialize-test.phpt index aaff75db6b77..6096d7b7288e 100644 --- a/ext/date/tests/unserialize-test.phpt +++ b/ext/date/tests/unserialize-test.phpt @@ -31,7 +31,6 @@ bool(false) ossfuzz-55599.txt: %s: unserialize(): Error at offset 26 of 26 bytes in %sunserialize-test.php on line %d -Error: Invalid serialization data for DateTime object bool(false) @@ -42,6 +41,4 @@ bool(false) ossfuzz-56931.txt: %s: unserialize(): Error at offset 39 of 39 bytes in %sunserialize-test.php on line %d - -Deprecated: Creation of dynamic property DateInterval::$ is deprecated in %s on line %d bool(false) diff --git a/ext/standard/tests/serialize/__unserialize_malformed.phpt b/ext/standard/tests/serialize/__unserialize_malformed.phpt new file mode 100644 index 000000000000..6d43767e20f5 --- /dev/null +++ b/ext/standard/tests/serialize/__unserialize_malformed.phpt @@ -0,0 +1,38 @@ +--TEST-- +__unserialize() and destructors are not called for incomplete objects +--FILE-- + 'O:4:"Test":0:{', + 'nonempty' => 'O:4:"Test":1:{i:0;i:42;', + 'nested' => 'a:1:{i:0;O:4:"Test":0:{', +] as $name => $data) { + echo "$name: "; + var_dump(@unserialize($data)); +} + +echo "valid: "; +var_dump(unserialize('O:4:"Test":1:{i:0;i:42;}')); +?> +--EXPECT-- +empty: bool(false) +nonempty: bool(false) +nested: bool(false) +valid: __unserialize: array(1) { + [0]=> + int(42) +} +object(Test)#1 (0) { +} +__destruct diff --git a/ext/standard/tests/serialize/__unserialize_malformed_nested_destructor.phpt b/ext/standard/tests/serialize/__unserialize_malformed_nested_destructor.phpt new file mode 100644 index 000000000000..94ae62bb0970 --- /dev/null +++ b/ext/standard/tests/serialize/__unserialize_malformed_nested_destructor.phpt @@ -0,0 +1,27 @@ +--TEST-- +Destructor of a nested object in a failed __unserialize() payload cannot reach freed slots +--FILE-- + 'O:1:"T":2:{i:0;s:5:"hello";i:1;O:1:"D":0:{}', + 'malformed element' => 'O:1:"T":3:{i:0;s:5:"hello";i:1;O:1:"D":0:{}i:2;X', +] as $name => $data) { + echo "$name: "; + var_dump(@unserialize($data)); +} +?> +--EXPECT-- +missing brace: bool(false) +bool(false) +malformed element: bool(false) +bool(false) diff --git a/ext/standard/tests/serialize/__wakeup_malformed.phpt b/ext/standard/tests/serialize/__wakeup_malformed.phpt new file mode 100644 index 000000000000..b8cceead72a8 --- /dev/null +++ b/ext/standard/tests/serialize/__wakeup_malformed.phpt @@ -0,0 +1,38 @@ +--TEST-- +__wakeup() and destructors are not called for incomplete objects +--FILE-- +value\n"; + } + + public function __destruct() { + echo "__destruct\n"; + } +} + +foreach ([ + 'empty' => 'O:4:"Test":0:{', + 'nonempty' => 'O:4:"Test":1:{s:5:"value";i:42;', + 'nested' => 'a:1:{i:0;O:4:"Test":0:{', +] as $name => $data) { + echo "$name: "; + var_dump(@unserialize($data)); +} + +echo "valid: "; +var_dump(unserialize('O:4:"Test":1:{s:5:"value";i:42;}')); +?> +--EXPECT-- +empty: bool(false) +nonempty: bool(false) +nested: bool(false) +valid: __wakeup: 42 +object(Test)#1 (1) { + ["value"]=> + int(42) +} +__destruct diff --git a/ext/standard/var_unserializer.re b/ext/standard/var_unserializer.re index 43968dd40c9e..80f22e7c9647 100644 --- a/ext/standard/var_unserializer.re +++ b/ext/standard/var_unserializer.re @@ -854,10 +854,11 @@ static inline int object_common(UNSERIALIZE_PARAMETER, zend_long elements, bool if (UNEXPECTED(!array_finit_size_mixed(&ary, elements))) { return 0; } - if (!process_nested_array_data(UNSERIALIZE_PASSTHRU, Z_ARRVAL(ary), elements)) { + if (!process_nested_array_data(UNSERIALIZE_PASSTHRU, Z_ARRVAL(ary), elements) + || !finish_nested_data(UNSERIALIZE_PASSTHRU)) { ZVAL_DEREF(rval); GC_ADD_FLAGS(Z_OBJ_P(rval), IS_OBJ_DESTRUCTOR_CALLED); - zval_ptr_dtor(&ary); + ZVAL_COPY_VALUE(var_tmp_var(var_hash), &ary); return 0; } @@ -870,7 +871,7 @@ static inline int object_common(UNSERIALIZE_PARAMETER, zend_long elements, bool tmp++; ZVAL_COPY_VALUE(tmp, &ary); - return finish_nested_data(UNSERIALIZE_PASSTHRU); + return 1; } has_wakeup = Z_OBJCE_P(rval) != PHP_IC_ENTRY @@ -884,7 +885,8 @@ static inline int object_common(UNSERIALIZE_PARAMETER, zend_long elements, bool if (UNEXPECTED(zend_hash_fextend(ht, zend_hash_num_elements(ht) + elements, HT_IS_PACKED(ht)) == FAILURE)) { return 0; } - if (!process_nested_object_data(UNSERIALIZE_PASSTHRU, ht, elements, Z_OBJ_P(rval))) { + if (!process_nested_object_data(UNSERIALIZE_PASSTHRU, ht, elements, Z_OBJ_P(rval)) + || !finish_nested_data(UNSERIALIZE_PASSTHRU)) { if (has_wakeup) { ZVAL_DEREF(rval); GC_ADD_FLAGS(Z_OBJ_P(rval), IS_OBJ_DESTRUCTOR_CALLED); @@ -900,7 +902,7 @@ static inline int object_common(UNSERIALIZE_PARAMETER, zend_long elements, bool Z_EXTRA_P(wakeup_var) = VAR_WAKEUP_FLAG; } - return finish_nested_data(UNSERIALIZE_PASSTHRU); + return 1; } #ifdef PHP_WIN32 # pragma optimize("", on)