From bd42b2f8c2cb3f061aa82878c493a98c2b846bec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1t=C3=A9=20Kocsis?= Date: Sun, 31 May 2026 23:30:05 +0200 Subject: [PATCH 1/4] General fix --- .../serialize/serialization_objects_009.phpt | 28 ++++++++++--------- ext/standard/var_unserializer.re | 2 +- ext/uri/tests/gh22046.phpt | 22 +++++++++++++++ 3 files changed, 38 insertions(+), 14 deletions(-) create mode 100644 ext/uri/tests/gh22046.phpt diff --git a/ext/standard/tests/serialize/serialization_objects_009.phpt b/ext/standard/tests/serialize/serialization_objects_009.phpt index 9485f3ef8068..09f6404e624f 100644 --- a/ext/standard/tests/serialize/serialization_objects_009.phpt +++ b/ext/standard/tests/serialize/serialization_objects_009.phpt @@ -3,22 +3,24 @@ Custom unserialization of classes with no custom unserializer. --FILE-- getMessage(), PHP_EOL; +} + eval('class C {}'); -$b = unserialize($ser); -var_dump($a, $b); +try { + unserialize($ser); +} catch (Throwable $e) { + echo $e::class, ": ", $e->getMessage(), PHP_EOL; +} echo "Done"; ?> ---EXPECTF-- -Warning: Class __PHP_Incomplete_Class has no unserializer in %sserialization_objects_009.php on line %d - -Warning: Class C has no unserializer in %sserialization_objects_009.php on line %d -object(__PHP_Incomplete_Class)#%d (1) { - ["__PHP_Incomplete_Class_Name"]=> - string(1) "C" -} -object(C)#%d (0) { -} +--EXPECT-- +Exception: Class __PHP_Incomplete_Class has no unserializer +Exception: Class C has no unserializer Done diff --git a/ext/standard/var_unserializer.re b/ext/standard/var_unserializer.re index d5019d94dc0c..da6ab7db3d99 100644 --- a/ext/standard/var_unserializer.re +++ b/ext/standard/var_unserializer.re @@ -769,7 +769,7 @@ static inline int object_custom(UNSERIALIZE_PARAMETER, zend_class_entry *ce) } if (ce->unserialize == NULL) { - zend_error(E_WARNING, "Class %s has no unserializer", ZSTR_VAL(ce->name)); + zend_throw_exception_ex(NULL, 0, "Class %s has no unserializer", ZSTR_VAL(ce->name)); object_init_ex(rval, ce); } else if (ce->unserialize(rval, ce, (const unsigned char*)*p, datalen, (zend_unserialize_data *)var_hash) != SUCCESS) { return 0; diff --git a/ext/uri/tests/gh22046.phpt b/ext/uri/tests/gh22046.phpt new file mode 100644 index 000000000000..9b832b8b65fa --- /dev/null +++ b/ext/uri/tests/gh22046.phpt @@ -0,0 +1,22 @@ +--TEST-- +GH-22046: The unserialize function with Uri\WhatWg\Url leads to NULL pointer dereference when object serialized back +--FILE-- +getMessage(), PHP_EOL; +} + +$payload = 'C:15:"Uri\Rfc3986\Uri":0:{}'; +try { + unserialize($payload); +} catch (Throwable $e) { + echo $e::class, ": ", $e->getMessage(), PHP_EOL; +} +?> +--EXPECT-- +Exception: Class Uri\WhatWg\Url has no unserializer +Exception: Class Uri\Rfc3986\Uri has no unserializer From 0e0f03556bce726869145516e598ebf6ca117531 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1t=C3=A9=20Kocsis?= Date: Mon, 1 Jun 2026 07:26:28 +0200 Subject: [PATCH 2/4] Fixes --- ext/standard/var_unserializer.re | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ext/standard/var_unserializer.re b/ext/standard/var_unserializer.re index da6ab7db3d99..2e886902cee6 100644 --- a/ext/standard/var_unserializer.re +++ b/ext/standard/var_unserializer.re @@ -769,8 +769,8 @@ static inline int object_custom(UNSERIALIZE_PARAMETER, zend_class_entry *ce) } if (ce->unserialize == NULL) { - zend_throw_exception_ex(NULL, 0, "Class %s has no unserializer", ZSTR_VAL(ce->name)); - object_init_ex(rval, ce); + zend_throw_exception_ex(NULL, 0, "Class %s has no unserializer", ZSTR_VAL(ce->name)); + return 0; } else if (ce->unserialize(rval, ce, (const unsigned char*)*p, datalen, (zend_unserialize_data *)var_hash) != SUCCESS) { return 0; } From a24d1796957727b96e9f03a449f164ab0b5603b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1t=C3=A9=20Kocsis?= Date: Thu, 11 Jun 2026 21:53:08 +0200 Subject: [PATCH 3/4] Trigger warning instead of exception --- .../serialize/serialization_objects_009.phpt | 29 +++++++++---------- ext/standard/var_unserializer.re | 2 +- ext/uri/tests/gh22046.phpt | 25 +++++++--------- 3 files changed, 25 insertions(+), 31 deletions(-) diff --git a/ext/standard/tests/serialize/serialization_objects_009.phpt b/ext/standard/tests/serialize/serialization_objects_009.phpt index 09f6404e624f..95b85ccd80f7 100644 --- a/ext/standard/tests/serialize/serialization_objects_009.phpt +++ b/ext/standard/tests/serialize/serialization_objects_009.phpt @@ -3,24 +3,21 @@ Custom unserialization of classes with no custom unserializer. --FILE-- getMessage(), PHP_EOL; -} - +$a = unserialize($ser); eval('class C {}'); +$b = unserialize($ser); -try { - unserialize($ser); -} catch (Throwable $e) { - echo $e::class, ": ", $e->getMessage(), PHP_EOL; -} - +var_dump($a, $b); echo "Done"; ?> ---EXPECT-- -Exception: Class __PHP_Incomplete_Class has no unserializer -Exception: Class C has no unserializer +--EXPECTF-- +Warning: Class __PHP_Incomplete_Class has no unserializer in %s on line %d + +Warning: unserialize(): Error at offset 11 of 18 bytes in %s on line %d + +Warning: Class C has no unserializer in %s on line %d + +Warning: unserialize(): Error at offset 11 of 18 bytes in %s on line %d +bool(false) +bool(false) Done diff --git a/ext/standard/var_unserializer.re b/ext/standard/var_unserializer.re index 2e886902cee6..484cb5aa8fc9 100644 --- a/ext/standard/var_unserializer.re +++ b/ext/standard/var_unserializer.re @@ -769,7 +769,7 @@ static inline int object_custom(UNSERIALIZE_PARAMETER, zend_class_entry *ce) } if (ce->unserialize == NULL) { - zend_throw_exception_ex(NULL, 0, "Class %s has no unserializer", ZSTR_VAL(ce->name)); + zend_error(E_WARNING, "Class %s has no unserializer", ZSTR_VAL(ce->name)); return 0; } else if (ce->unserialize(rval, ce, (const unsigned char*)*p, datalen, (zend_unserialize_data *)var_hash) != SUCCESS) { return 0; diff --git a/ext/uri/tests/gh22046.phpt b/ext/uri/tests/gh22046.phpt index 9b832b8b65fa..af297905aa30 100644 --- a/ext/uri/tests/gh22046.phpt +++ b/ext/uri/tests/gh22046.phpt @@ -1,22 +1,19 @@ --TEST-- -GH-22046: The unserialize function with Uri\WhatWg\Url leads to NULL pointer dereference when object serialized back +GH-22046: The unserialize function can lead to segfault when internal classes are serialized back with the unsupported C format --FILE-- getMessage(), PHP_EOL; -} +unserialize($payload); $payload = 'C:15:"Uri\Rfc3986\Uri":0:{}'; -try { - unserialize($payload); -} catch (Throwable $e) { - echo $e::class, ": ", $e->getMessage(), PHP_EOL; -} +unserialize($payload); ?> ---EXPECT-- -Exception: Class Uri\WhatWg\Url has no unserializer -Exception: Class Uri\Rfc3986\Uri has no unserializer +--EXPECTF-- +Warning: Class Uri\WhatWg\Url has no unserializer in %s on line %d + +Warning: unserialize(): Error at offset 25 of 26 bytes in %s on line %d + +Warning: Class Uri\Rfc3986\Uri has no unserializer in %s on line %d + +Warning: unserialize(): Error at offset 26 of 27 bytes in %s on line %d From ae2edbb573e5feb838abea67dcae1294a2f84c9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1t=C3=A9=20Kocsis?= Date: Fri, 12 Jun 2026 22:01:59 +0200 Subject: [PATCH 4/4] Update NEWS [skip ci] --- NEWS | 3 +++ 1 file changed, 3 insertions(+) diff --git a/NEWS b/NEWS index 4fbc7e89eb11..e48e74423a24 100644 --- a/NEWS +++ b/NEWS @@ -21,6 +21,9 @@ PHP NEWS . Deprecate specifying a nullable return type for __debugInfo(). (timwolla) . Fixed bug GH-22142 (Assertion failure in zendi_try_get_long() on IS_UNDEF). (David Carlier) + . Fixed bug GH-22046 (The unserialize function can lead to segfault when + non-Serializable internal classes are serialized back with the C format). + (kocsismate) - BCMath: . Added NUL-byte validation to BCMath functions. (jorgsowa)