[php-src] PHP-8.6: standard: Create incomplete objects again from the `C` format (#24020)

From: Date: Wed, 30 Sep 2026 20:23:04 +0000
Subject: [php-src] PHP-8.6: standard: Create incomplete objects again from the `C` format (#24020)
Groups: php.cvs 
Request: Send a blank email to php-cvs+get-141820@lists.php.net to get a copy of this message
Author: Nicolas Grekas (nicolas-grekas) Committer: GitHub (web-flow) Pusher: kocsismate Date: 2026-09-30T22:23:01+02:00 Commit: https://github.com/php/php-src/commit/4bf700e63e080ad16f0c14363c9a876111592b7a Raw diff: https://github.com/php/php-src/commit/4bf700e63e080ad16f0c14363c9a876111592b7a.diff standard: Create incomplete objects again from the C format (#24020) Since GH-22046 was fixed, unserialize() fails as a whole when a C payload names a class that is missing or not in allowed_classes, because the __PHP_Incomplete_Class substituted for it has no unserializer either. The incomplete class has no internal state, so create an empty one as before and keep failing only for real classes that are not Serializable. Changed paths: A ext/standard/tests/serialize/incomplete_class_c_format.phpt A ext/standard/tests/serialize/incomplete_class_c_format_allowed_classes.phpt M ext/standard/tests/serialize/serialization_objects_009.phpt M ext/standard/var_unserializer.re Diff: diff --git a/ext/standard/tests/serialize/incomplete_class_c_format.phpt b/ext/standard/tests/serialize/incomplete_class_c_format.phpt new file mode 100644 index 000000000000..258e76921c4a --- /dev/null +++ b/ext/standard/tests/serialize/incomplete_class_c_format.phpt @@ -0,0 +1,36 @@ +--TEST-- +unserialize() turns C: objects of missing classes into incomplete objects +--FILE-- +<?php + +var_dump(unserialize('a:2:{i:0;C:7:"Missing":4:{abcd}i:1;s:4:"tail";}')); + +function callback(string $class): void +{ + echo __FUNCTION__, "($class)\n"; +} + +ini_set('unserialize_callback_func', 'callback'); + +var_dump(unserialize('C:7:"Missing":4:{abcd}')); +?> +--EXPECTF-- +Warning: Class __PHP_Incomplete_Class has no unserializer in %s on line %d +array(2) { + [0]=> + object(__PHP_Incomplete_Class)#%d (1) { + ["__PHP_Incomplete_Class_Name"]=> + string(7) "Missing" + } + [1]=> + string(4) "tail" +} +callback(Missing) + +Warning: unserialize(): Function callback() hasn't defined the class it was called for in %s on line %d + +Warning: Class __PHP_Incomplete_Class has no unserializer in %s on line %d +object(__PHP_Incomplete_Class)#%d (1) { + ["__PHP_Incomplete_Class_Name"]=> + string(7) "Missing" +} diff --git a/ext/standard/tests/serialize/incomplete_class_c_format_allowed_classes.phpt b/ext/standard/tests/serialize/incomplete_class_c_format_allowed_classes.phpt new file mode 100644 index 000000000000..54a68a935ef1 --- /dev/null +++ b/ext/standard/tests/serialize/incomplete_class_c_format_allowed_classes.phpt @@ -0,0 +1,63 @@ +--TEST-- +unserialize() with allowed_classes turns C: objects of classes that are not allowed into incomplete objects +--FILE-- +<?php + +final class Kept +{ + public $a = 1; +} + +final class Blocked implements Serializable +{ + public function serialize(): string + { + return ''; + } + + public function unserialize(string $data): void + { + echo __METHOD__, "\n"; + } + + public function __serialize(): array + { + return []; + } + + public function __unserialize(array $data): void + { + echo __METHOD__, "\n"; + } +} + +$payload = 'a:4:{i:0;O:4:"Kept":1:{s:1:"a";i:2;}i:1;C:7:"Blocked":4:{abcd}i:2;C:7:"Missing":4:{abcd}i:3;r:4;}'; + +var_dump(unserialize($payload, ['allowed_classes' => ['Kept']])); +?> +--EXPECTF-- +Warning: Class __PHP_Incomplete_Class has no unserializer in %s on line %d + +Warning: Class __PHP_Incomplete_Class has no unserializer in %s on line %d +array(4) { + [0]=> + object(Kept)#%d (1) { + ["a"]=> + int(2) + } + [1]=> + object(__PHP_Incomplete_Class)#%d (1) { + ["__PHP_Incomplete_Class_Name"]=> + string(7) "Blocked" + } + [2]=> + object(__PHP_Incomplete_Class)#%d (1) { + ["__PHP_Incomplete_Class_Name"]=> + string(7) "Missing" + } + [3]=> + object(__PHP_Incomplete_Class)#%d (1) { + ["__PHP_Incomplete_Class_Name"]=> + string(7) "Blocked" + } +} diff --git a/ext/standard/tests/serialize/serialization_objects_009.phpt b/ext/standard/tests/serialize/serialization_objects_009.phpt index 95b85ccd80f7..27da8e53ebb4 100644 --- a/ext/standard/tests/serialize/serialization_objects_009.phpt +++ b/ext/standard/tests/serialize/serialization_objects_009.phpt @@ -13,11 +13,12 @@ echo "Done"; --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) +object(__PHP_Incomplete_Class)#%d (1) { + ["__PHP_Incomplete_Class_Name"]=> + string(1) "C" +} bool(false) Done diff --git a/ext/standard/var_unserializer.re b/ext/standard/var_unserializer.re index 0138471aa63e..e10b067c68d8 100644 --- a/ext/standard/var_unserializer.re +++ b/ext/standard/var_unserializer.re @@ -769,7 +769,11 @@ 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)); - return 0; + /* __PHP_Incomplete_Class has no internal state, an empty instance is safe. */ + if (ce != PHP_IC_ENTRY) { + return 0; + } + object_init_ex(rval, ce); } else if (ce->unserialize(rval, ce, (const unsigned char*)*p, datalen, (zend_unserialize_data *)var_hash) != SUCCESS) { return 0; }

« previous php.cvs (#141820) next »