diff --git a/Zend/tests/generators/errors/serialize_unserialize_error.phpt b/Zend/tests/generators/errors/serialize_unserialize_error.phpt index aa962eb99a466..d4534cb4bc29f 100644 --- a/Zend/tests/generators/errors/serialize_unserialize_error.phpt +++ b/Zend/tests/generators/errors/serialize_unserialize_error.phpt @@ -32,11 +32,11 @@ Stack trace: #0 %s(%d): serialize(Object(Generator)) #1 {main} +Exception: Unserialization of 'Generator' is not allowed in %s:%d +Stack trace: +#0 %s(%d): unserialize('O:9:"Generator"...') +#1 {main} -Warning: Erroneous data format for unserializing 'Generator' in %sserialize_unserialize_error.php on line %d - -Notice: unserialize(): Error at offset 19 of 20 bytes in %sserialize_unserialize_error.php on line %d -bool(false) Exception: Unserialization of 'Generator' is not allowed in %s:%d Stack trace: #0 %s(%d): unserialize('C:9:"Generator"...') diff --git a/Zend/zend_closures.c b/Zend/zend_closures.c index f765e573359c9..13eb86e09bde2 100644 --- a/Zend/zend_closures.c +++ b/Zend/zend_closures.c @@ -640,8 +640,6 @@ void zend_register_closure_ce(void) /* {{{ */ { zend_ce_closure = register_class_Closure(); zend_ce_closure->create_object = zend_closure_new; - zend_ce_closure->serialize = zend_class_serialize_deny; - zend_ce_closure->unserialize = zend_class_unserialize_deny; memcpy(&closure_handlers, &std_object_handlers, sizeof(zend_object_handlers)); closure_handlers.free_obj = zend_closure_free_storage; diff --git a/Zend/zend_closures.stub.php b/Zend/zend_closures.stub.php index 3d451e58b69b2..daa92492b1884 100644 --- a/Zend/zend_closures.stub.php +++ b/Zend/zend_closures.stub.php @@ -2,7 +2,10 @@ /** @generate-class-entries */ -/** @strict-properties */ +/** + * @strict-properties + * @not-serializable + */ final class Closure { private function __construct() {} diff --git a/Zend/zend_closures_arginfo.h b/Zend/zend_closures_arginfo.h index 888e4994a08c6..cb5a0126fc1e5 100644 --- a/Zend/zend_closures_arginfo.h +++ b/Zend/zend_closures_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: 7c4df531cdb30ac4206f43f0d40098666466b9a6 */ + * Stub hash: e3b480674671a698814db282c5ea34d438fe519d */ ZEND_BEGIN_ARG_INFO_EX(arginfo_class_Closure___construct, 0, 0, 0) ZEND_END_ARG_INFO() @@ -47,7 +47,7 @@ static zend_class_entry *register_class_Closure(void) INIT_CLASS_ENTRY(ce, "Closure", class_Closure_methods); class_entry = zend_register_internal_class_ex(&ce, NULL); - class_entry->ce_flags |= ZEND_ACC_FINAL|ZEND_ACC_NO_DYNAMIC_PROPERTIES; + class_entry->ce_flags |= ZEND_ACC_FINAL|ZEND_ACC_NO_DYNAMIC_PROPERTIES|ZEND_ACC_NOT_SERIALIZABLE; return class_entry; } diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 8b3bcf7fe1cef..15687b99526de 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -7662,8 +7662,7 @@ void zend_compile_class_decl(znode *result, zend_ast *ast, bool toplevel) /* {{{ if (UNEXPECTED((decl->flags & ZEND_ACC_ANON_CLASS))) { /* Serialization is not supported for anonymous classes */ - ce->serialize = zend_class_serialize_deny; - ce->unserialize = zend_class_unserialize_deny; + ce->ce_flags |= ZEND_ACC_NOT_SERIALIZABLE; } if (extends_ast) { diff --git a/Zend/zend_compile.h b/Zend/zend_compile.h index c78d2be54ae50..d9eba0c7397f6 100644 --- a/Zend/zend_compile.h +++ b/Zend/zend_compile.h @@ -238,7 +238,7 @@ typedef struct _zend_oparray_context { /* or IS_CONSTANT_VISITED_MARK | | | */ #define ZEND_CLASS_CONST_IS_CASE (1 << 6) /* | | | X */ /* | | | */ -/* Class Flags (unused: 29...) | | | */ +/* Class Flags (unused: 30...) | | | */ /* =========== | | | */ /* | | | */ /* Special class types | | | */ @@ -301,6 +301,9 @@ typedef struct _zend_oparray_context { /* loaded from file cache to process memory | | | */ #define ZEND_ACC_FILE_CACHED (1 << 27) /* X | | | */ /* | | | */ +/* Class cannot be serialized or unserialized | | | */ +#define ZEND_ACC_NOT_SERIALIZABLE (1 << 29) /* X | | | */ +/* | | | */ /* Function Flags (unused: 27-30) | | | */ /* ============== | | | */ /* | | | */ diff --git a/Zend/zend_fibers.c b/Zend/zend_fibers.c index aa052c7a66198..9e1cc90b6e0f9 100644 --- a/Zend/zend_fibers.c +++ b/Zend/zend_fibers.c @@ -868,8 +868,6 @@ void zend_register_fiber_ce(void) { zend_ce_fiber = register_class_Fiber(); zend_ce_fiber->create_object = zend_fiber_object_create; - zend_ce_fiber->serialize = zend_class_serialize_deny; - zend_ce_fiber->unserialize = zend_class_unserialize_deny; zend_fiber_handlers = std_object_handlers; zend_fiber_handlers.dtor_obj = zend_fiber_object_destroy; diff --git a/Zend/zend_fibers.stub.php b/Zend/zend_fibers.stub.php index cf18193056f1e..4d38c26a9d26c 100644 --- a/Zend/zend_fibers.stub.php +++ b/Zend/zend_fibers.stub.php @@ -2,7 +2,10 @@ /** @generate-class-entries */ -/** @strict-properties */ +/** + * @strict-properties + * @not-serializable + */ final class Fiber { public function __construct(callable $callback) {} diff --git a/Zend/zend_fibers_arginfo.h b/Zend/zend_fibers_arginfo.h index 51b751a4f20ea..3d12b20c9f726 100644 --- a/Zend/zend_fibers_arginfo.h +++ b/Zend/zend_fibers_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: 7a3a7030f97d2c1e787499ef25341607841a607c */ + * Stub hash: e82bbc8e81fe98873a9a5697a4b38e63a24379da */ ZEND_BEGIN_ARG_INFO_EX(arginfo_class_Fiber___construct, 0, 0, 1) ZEND_ARG_TYPE_INFO(0, callback, IS_CALLABLE, 0) @@ -79,7 +79,7 @@ static zend_class_entry *register_class_Fiber(void) INIT_CLASS_ENTRY(ce, "Fiber", class_Fiber_methods); class_entry = zend_register_internal_class_ex(&ce, NULL); - class_entry->ce_flags |= ZEND_ACC_FINAL|ZEND_ACC_NO_DYNAMIC_PROPERTIES; + class_entry->ce_flags |= ZEND_ACC_FINAL|ZEND_ACC_NO_DYNAMIC_PROPERTIES|ZEND_ACC_NOT_SERIALIZABLE; return class_entry; } diff --git a/Zend/zend_generators.c b/Zend/zend_generators.c index 6059d51145180..f8028bc94bc49 100644 --- a/Zend/zend_generators.c +++ b/Zend/zend_generators.c @@ -1117,8 +1117,6 @@ void zend_register_generator_ce(void) /* {{{ */ { zend_ce_generator = register_class_Generator(zend_ce_iterator); zend_ce_generator->create_object = zend_generator_create; - zend_ce_generator->serialize = zend_class_serialize_deny; - zend_ce_generator->unserialize = zend_class_unserialize_deny; /* get_iterator has to be assigned *after* implementing the interface */ zend_ce_generator->get_iterator = zend_generator_get_iterator; diff --git a/Zend/zend_generators.stub.php b/Zend/zend_generators.stub.php index 538596213a022..14df357150095 100644 --- a/Zend/zend_generators.stub.php +++ b/Zend/zend_generators.stub.php @@ -2,7 +2,10 @@ /** @generate-class-entries */ -/** @strict-properties */ +/** + * @strict-properties + * @not-serializable + */ final class Generator implements Iterator { public function rewind(): void {} diff --git a/Zend/zend_generators_arginfo.h b/Zend/zend_generators_arginfo.h index 17a82b1f4b925..26870a5ec952e 100644 --- a/Zend/zend_generators_arginfo.h +++ b/Zend/zend_generators_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: 06d4e8126db48fe8633ecd40b93904a0f9c59263 */ + * Stub hash: 0af5e8985dd4645bf23490b8cec312f8fd1fee2e */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Generator_rewind, 0, 0, IS_VOID, 0) ZEND_END_ARG_INFO() @@ -58,7 +58,7 @@ static zend_class_entry *register_class_Generator(zend_class_entry *class_entry_ INIT_CLASS_ENTRY(ce, "Generator", class_Generator_methods); class_entry = zend_register_internal_class_ex(&ce, NULL); - class_entry->ce_flags |= ZEND_ACC_FINAL|ZEND_ACC_NO_DYNAMIC_PROPERTIES; + class_entry->ce_flags |= ZEND_ACC_FINAL|ZEND_ACC_NO_DYNAMIC_PROPERTIES|ZEND_ACC_NOT_SERIALIZABLE; zend_class_implements(class_entry, 1, class_entry_Iterator); return class_entry; diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index 467a1f25de8ea..4169c61df3c4a 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -1625,7 +1625,7 @@ ZEND_API void zend_do_inheritance_ex(zend_class_entry *ce, zend_class_entry *par ce->ce_flags |= ZEND_ACC_EXPLICIT_ABSTRACT_CLASS; } } - ce->ce_flags |= parent_ce->ce_flags & (ZEND_HAS_STATIC_IN_METHODS | ZEND_ACC_HAS_TYPE_HINTS | ZEND_ACC_USE_GUARDS); + ce->ce_flags |= parent_ce->ce_flags & (ZEND_HAS_STATIC_IN_METHODS | ZEND_ACC_HAS_TYPE_HINTS | ZEND_ACC_USE_GUARDS | ZEND_ACC_NOT_SERIALIZABLE); } /* }}} */ diff --git a/build/gen_stub.php b/build/gen_stub.php index d1c6ee65d9f05..dbb7cccf5ae1e 100755 --- a/build/gen_stub.php +++ b/build/gen_stub.php @@ -1195,6 +1195,8 @@ class ClassInfo { public $isDeprecated; /** @var bool */ public $isStrictProperties; + /** @var bool */ + public $isNotSerializable; /** @var Name[] */ public $extends; /** @var Name[] */ @@ -1217,6 +1219,7 @@ public function __construct( ?string $alias, bool $isDeprecated, bool $isStrictProperties, + bool $isNotSerializable, array $extends, array $implements, array $propertyInfos, @@ -1228,6 +1231,7 @@ public function __construct( $this->alias = $alias; $this->isDeprecated = $isDeprecated; $this->isStrictProperties = $isStrictProperties; + $this->isNotSerializable = $isNotSerializable; $this->extends = $extends; $this->implements = $implements; $this->propertyInfos = $propertyInfos; @@ -1318,6 +1322,10 @@ private function getFlagsAsString(): string $flags[] = "ZEND_ACC_NO_DYNAMIC_PROPERTIES"; } + if ($this->isNotSerializable) { + $flags[] = "ZEND_ACC_NOT_SERIALIZABLE"; + } + return implode("|", $flags); } } @@ -1625,6 +1633,7 @@ function parseClass(Name $name, Stmt\ClassLike $class, array $properties, array $alias = null; $isDeprecated = false; $isStrictProperties = false; + $isNotSerializable = false; if ($comment) { $tags = parseDocComment($comment); @@ -1635,6 +1644,8 @@ function parseClass(Name $name, Stmt\ClassLike $class, array $properties, array $isDeprecated = true; } else if ($tag->name === 'strict-properties') { $isStrictProperties = true; + } else if ($tag->name === 'not-serializable') { + $isNotSerializable = true; } } } @@ -1658,6 +1669,7 @@ function parseClass(Name $name, Stmt\ClassLike $class, array $properties, array $alias, $isDeprecated, $isStrictProperties, + $isNotSerializable, $extends, $implements, $properties, diff --git a/ext/spl/spl_directory.c b/ext/spl/spl_directory.c index d668b4cc42ecb..cf3dab4af7e9b 100644 --- a/ext/spl/spl_directory.c +++ b/ext/spl/spl_directory.c @@ -2744,8 +2744,6 @@ PHP_MINIT_FUNCTION(spl_directory) spl_filesystem_object_handlers.cast_object = spl_filesystem_object_cast; spl_filesystem_object_handlers.dtor_obj = spl_filesystem_object_destroy_object; spl_filesystem_object_handlers.free_obj = spl_filesystem_object_free_storage; - spl_ce_SplFileInfo->serialize = zend_class_serialize_deny; - spl_ce_SplFileInfo->unserialize = zend_class_unserialize_deny; spl_ce_DirectoryIterator = register_class_DirectoryIterator(spl_ce_SplFileInfo, spl_ce_SeekableIterator); spl_ce_DirectoryIterator->create_object = spl_filesystem_object_new; diff --git a/ext/spl/spl_directory.stub.php b/ext/spl/spl_directory.stub.php index ef3cc9f68a4e2..f0fb44f13680e 100644 --- a/ext/spl/spl_directory.stub.php +++ b/ext/spl/spl_directory.stub.php @@ -2,6 +2,7 @@ /** @generate-class-entries */ +/** @not-serializable */ class SplFileInfo implements Stringable { public function __construct(string $filename) {} diff --git a/ext/spl/spl_directory_arginfo.h b/ext/spl/spl_directory_arginfo.h index 7a7261d4ffdf5..8dc420d010fe2 100644 --- a/ext/spl/spl_directory_arginfo.h +++ b/ext/spl/spl_directory_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: fcfc6e8120dff87ab81d9b5491f27e695b3790f4 */ + * Stub hash: f4bfc0a1b26c2c3d8a6bf8ac6369a10c5e015c61 */ ZEND_BEGIN_ARG_INFO_EX(arginfo_class_SplFileInfo___construct, 0, 0, 1) ZEND_ARG_TYPE_INFO(0, filename, IS_STRING, 0) @@ -496,6 +496,7 @@ static zend_class_entry *register_class_SplFileInfo(zend_class_entry *class_entr INIT_CLASS_ENTRY(ce, "SplFileInfo", class_SplFileInfo_methods); class_entry = zend_register_internal_class_ex(&ce, NULL); + class_entry->ce_flags |= ZEND_ACC_NOT_SERIALIZABLE; zend_class_implements(class_entry, 1, class_entry_Stringable); return class_entry; diff --git a/ext/standard/tests/serialize/bug67072.phpt b/ext/standard/tests/serialize/bug67072.phpt index df0593180d56c..c5f891480b8e6 100644 --- a/ext/standard/tests/serialize/bug67072.phpt +++ b/ext/standard/tests/serialize/bug67072.phpt @@ -2,11 +2,12 @@ Bug #67072 Echoing unserialized "SplFileObject" crash --FILE-- ===DONE== --EXPECTF-- -Warning: Erroneous data format for unserializing 'SplFileObject' in %sbug67072.php on line %d - -Notice: unserialize(): Error at offset 24 of 64 bytes in %sbug67072.php on line %d -===DONE== +Fatal error: Uncaught Exception: Unserialization of 'SplFileObject' is not allowed in %s:%d +Stack trace: +#0 %s(%d): unserialize('O:13:"SplFileOb...') +#1 {main} + thrown in %s on line %d diff --git a/ext/standard/tests/serialize/bug81111.phpt b/ext/standard/tests/serialize/bug81111.phpt new file mode 100644 index 0000000000000..dad07e96e5fd2 --- /dev/null +++ b/ext/standard/tests/serialize/bug81111.phpt @@ -0,0 +1,53 @@ +--TEST-- +Bug #81111: Serialization is unexpectedly allowed on anonymous classes with __serialize() +--FILE-- +getMessage(), "\n"; +} + +$anon = new class () { + public function __serialize() { return []; } + public function __unserialize($value) { } +}; + +try { + serialize($anon); +} catch (Exception $e) { + echo $e->getMessage(), "\n"; +} + +try { + unserialize("O:13:\"MySplFileInfo\":0:{}"); +} catch (Exception $e) { + echo $e->getMessage(), "\n"; +} +try { + unserialize("C:13:\"MySplFileInfo\":0:{}"); +} catch (Exception $e) { + echo $e->getMessage(), "\n"; +} + +$name = $anon::class; +try { + unserialize("O:" . strlen($name) . ":\"" . $name . "\":0:{}"); +} catch (Exception $e) { + echo $e->getMessage(), "\n"; +} + +?> +--EXPECTF-- +Serialization of 'MySplFileInfo' is not allowed +Serialization of 'class@anonymous' is not allowed +Unserialization of 'MySplFileInfo' is not allowed +Unserialization of 'MySplFileInfo' is not allowed + +Notice: unserialize(): Error at offset 0 of %d bytes in %s on line %d diff --git a/ext/standard/var.c b/ext/standard/var.c index 2c5b4b0cf6e35..8f8403ff37557 100644 --- a/ext/standard/var.c +++ b/ext/standard/var.c @@ -27,6 +27,7 @@ #include "basic_functions.h" #include "php_incomplete_class.h" #include "zend_enum.h" +#include "zend_exceptions.h" /* }}} */ struct php_serialize_data { @@ -1058,6 +1059,12 @@ static void php_var_serialize_intern(smart_str *buf, zval *struc, php_serialize_ bool incomplete_class; uint32_t count; + if (ce->ce_flags & ZEND_ACC_NOT_SERIALIZABLE) { + zend_throw_exception_ex(NULL, 0, "Serialization of '%s' is not allowed", + ZSTR_VAL(ce->name)); + return; + } + if (ce->ce_flags & ZEND_ACC_ENUM) { PHP_CLASS_ATTRIBUTES; diff --git a/ext/standard/var_unserializer.re b/ext/standard/var_unserializer.re index 7b53c96ff116b..f1ae49130cf12 100644 --- a/ext/standard/var_unserializer.re +++ b/ext/standard/var_unserializer.re @@ -18,6 +18,7 @@ #include "ext/standard/php_var.h" #include "php_incomplete_class.h" #include "zend_portability.h" +#include "zend_exceptions.h" /* {{{ reference-handling for unserializer: var_* */ #define VAR_ENTRIES_MAX 1018 /* 1024 - offsetof(php_unserialize_data, entries) / sizeof(void*) */ @@ -1267,6 +1268,13 @@ object ":" uiv ":" ["] { *p = YYCURSOR; + if (ce->ce_flags & ZEND_ACC_NOT_SERIALIZABLE) { + zend_throw_exception_ex(NULL, 0, "Unserialization of '%s' is not allowed", + ZSTR_VAL(ce->name)); + zend_string_release_ex(class_name, 0); + return 0; + } + if (custom_object) { int ret;