From 075b6b85f6b964108c76cea7c64c944bf1585954 Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc <365207+arnaud-lb@users.noreply.github.com> Date: Tue, 13 Jan 2026 12:32:52 +0100 Subject: [PATCH 1/5] Set default_object_handlers when registering internal enums Internal enums can be cloned and compared, unlike user enums, because we didn't set default_object_handlers when registering internal enums. Fix by setting default_object_handlers when registering internal enums. Fixes GH-20914 Closes GH-20915 --- Zend/tests/enum/comparison-internal.phpt | 54 +++++++++++++++++++ Zend/tests/enum/implements-internal.phpt | 14 +++++ Zend/tests/enum/instanceof-backed-enum.phpt | 6 +++ Zend/tests/enum/instanceof-unitenum.phpt | 4 ++ Zend/tests/enum/no-clone-internal.phpt | 16 ++++++ .../enum/no-dynamic-properties-internal.phpt | 18 +++++++ Zend/zend_enum.c | 2 + ext/zend_test/test.c | 2 + ext/zend_test/test.stub.php | 5 ++ ext/zend_test/test_arginfo.h | 16 +++++- 10 files changed, 136 insertions(+), 1 deletion(-) create mode 100644 Zend/tests/enum/comparison-internal.phpt create mode 100644 Zend/tests/enum/implements-internal.phpt create mode 100644 Zend/tests/enum/no-clone-internal.phpt create mode 100644 Zend/tests/enum/no-dynamic-properties-internal.phpt diff --git a/Zend/tests/enum/comparison-internal.phpt b/Zend/tests/enum/comparison-internal.phpt new file mode 100644 index 0000000000000..3d0a2f1e17221 --- /dev/null +++ b/Zend/tests/enum/comparison-internal.phpt @@ -0,0 +1,54 @@ +--TEST-- +Enum comparison (internal enum) +--EXTENSIONS-- +zend_test +--FILE-- + $foo); +var_dump($foo < $foo); +var_dump($foo >= $foo); +var_dump($foo <= $foo); + +var_dump($foo > $bar); +var_dump($foo < $bar); +var_dump($foo >= $bar); +var_dump($foo <= $bar); + +var_dump($foo > true); +var_dump($foo < true); +var_dump($foo >= true); +var_dump($foo <= true); + +?> +--EXPECT-- +bool(true) +bool(true) +bool(false) +bool(false) +bool(false) +bool(false) +bool(false) +bool(false) +bool(true) +bool(true) +bool(false) +bool(false) +bool(false) +bool(false) +bool(false) +bool(false) +bool(false) +bool(false) diff --git a/Zend/tests/enum/implements-internal.phpt b/Zend/tests/enum/implements-internal.phpt new file mode 100644 index 0000000000000..518602af97e19 --- /dev/null +++ b/Zend/tests/enum/implements-internal.phpt @@ -0,0 +1,14 @@ +--TEST-- +Enum implements (internal enum) +--EXTENSIONS-- +zend_test +--FILE-- + +--EXPECT-- +bool(false) +bool(true) diff --git a/Zend/tests/enum/instanceof-backed-enum.phpt b/Zend/tests/enum/instanceof-backed-enum.phpt index 4716835d1169c..7087cfec74b40 100644 --- a/Zend/tests/enum/instanceof-backed-enum.phpt +++ b/Zend/tests/enum/instanceof-backed-enum.phpt @@ -1,5 +1,7 @@ --TEST-- Auto implement BackedEnum interface +--EXTENSIONS-- +zend_test --FILE-- --EXPECT-- bool(false) bool(true) +bool(false) +bool(true) diff --git a/Zend/tests/enum/instanceof-unitenum.phpt b/Zend/tests/enum/instanceof-unitenum.phpt index 5523796325398..89b04b5046624 100644 --- a/Zend/tests/enum/instanceof-unitenum.phpt +++ b/Zend/tests/enum/instanceof-unitenum.phpt @@ -1,5 +1,7 @@ --TEST-- Auto implement UnitEnum interface +--EXTENSIONS-- +zend_test --FILE-- --EXPECT-- bool(true) bool(false) +bool(true) diff --git a/Zend/tests/enum/no-clone-internal.phpt b/Zend/tests/enum/no-clone-internal.phpt new file mode 100644 index 0000000000000..84b7ee2634d26 --- /dev/null +++ b/Zend/tests/enum/no-clone-internal.phpt @@ -0,0 +1,16 @@ +--TEST-- +Enum disallows cloning (internal enum) +--EXTENSIONS-- +zend_test +--FILE-- +getMessage() . "\n"; +} + +?> +--EXPECT-- +Trying to clone an uncloneable object of class ZendTestIntEnum diff --git a/Zend/tests/enum/no-dynamic-properties-internal.phpt b/Zend/tests/enum/no-dynamic-properties-internal.phpt new file mode 100644 index 0000000000000..8d821a5f62925 --- /dev/null +++ b/Zend/tests/enum/no-dynamic-properties-internal.phpt @@ -0,0 +1,18 @@ +--TEST-- +Enum case disallows dynamic properties (internal enum) +--EXTENSIONS-- +zend_test +--FILE-- +baz = 'Baz'; +} catch (\Error $e) { + echo $e->getMessage(); +} + +?> +--EXPECT-- +Cannot create dynamic property ZendTestUnitEnum::$baz diff --git a/Zend/zend_enum.c b/Zend/zend_enum.c index ccafca48fe9b8..464e7d801a89e 100644 --- a/Zend/zend_enum.c +++ b/Zend/zend_enum.c @@ -528,6 +528,8 @@ ZEND_API zend_class_entry *zend_register_internal_enum( zend_class_implements(ce, 1, zend_ce_backed_enum); } + ce->default_object_handlers = &zend_enum_object_handlers; + return ce; } diff --git a/ext/zend_test/test.c b/ext/zend_test/test.c index 59b2c79edf672..576bacd5e4a9a 100644 --- a/ext/zend_test/test.c +++ b/ext/zend_test/test.c @@ -74,6 +74,7 @@ static zend_class_entry *zend_test_ns2_ns_foo_class; static zend_class_entry *zend_test_unit_enum; static zend_class_entry *zend_test_string_enum; static zend_class_entry *zend_test_int_enum; +static zend_class_entry *zend_test_enum_with_interface; static zend_class_entry *zend_test_magic_call; static zend_object_handlers zend_test_class_handlers; @@ -1318,6 +1319,7 @@ PHP_MINIT_FUNCTION(zend_test) zend_test_unit_enum = register_class_ZendTestUnitEnum(); zend_test_string_enum = register_class_ZendTestStringEnum(); zend_test_int_enum = register_class_ZendTestIntEnum(); + zend_test_enum_with_interface = register_class_ZendTestEnumWithInterface(zend_test_interface); zend_test_magic_call = register_class__ZendTestMagicCall(); diff --git a/ext/zend_test/test.stub.php b/ext/zend_test/test.stub.php index 1be61b7a17028..9116245c30f44 100644 --- a/ext/zend_test/test.stub.php +++ b/ext/zend_test/test.stub.php @@ -201,6 +201,11 @@ enum ZendTestIntEnum: int { case Baz = -1; } + enum ZendTestEnumWithInterface implements _ZendTestInterface { + case Foo; + case Bar; + } + function zend_test_array_return(): array {} /** @genstubs-expose-comment-block diff --git a/ext/zend_test/test_arginfo.h b/ext/zend_test/test_arginfo.h index b38a354651816..039757207e696 100644 --- a/ext/zend_test/test_arginfo.h +++ b/ext/zend_test/test_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: 6f76138d313c37244148004e2691ee47534f87a4 */ + * Stub hash: bf65e1dd1eeeeec46687a76a7ea6554cd1971dfc */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_test_array_return, 0, 0, IS_ARRAY, 0) ZEND_END_ARG_INFO() @@ -1189,6 +1189,20 @@ static zend_class_entry *register_class_ZendTestIntEnum(void) } #endif +#if (PHP_VERSION_ID >= 80100) +static zend_class_entry *register_class_ZendTestEnumWithInterface(zend_class_entry *class_entry__ZendTestInterface) +{ + zend_class_entry *class_entry = zend_register_internal_enum("ZendTestEnumWithInterface", IS_UNDEF, NULL); + zend_class_implements(class_entry, 1, class_entry__ZendTestInterface); + + zend_enum_add_case_cstr(class_entry, "Foo", NULL); + + zend_enum_add_case_cstr(class_entry, "Bar", NULL); + + return class_entry; +} +#endif + static zend_class_entry *register_class_ZendTestNS_Foo(void) { zend_class_entry ce, *class_entry; From b273fc7aca49a97d7c047cbd5899b1995d12b5b9 Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Tue, 13 Jan 2026 12:34:03 +0100 Subject: [PATCH 2/5] NEWS --- NEWS | 1 + 1 file changed, 1 insertion(+) diff --git a/NEWS b/NEWS index 21f69b8895c3a..d1e88208d83e6 100644 --- a/NEWS +++ b/NEWS @@ -7,6 +7,7 @@ PHP NEWS function triggered by bailout in php_output_lock_error()). (timwolla) . Fix OSS-Fuzz #471533782 (Infinite loop in GC destructor fiber). (ilutov) . Fix OSS-Fuzz #472563272 (Borked block_pass JMP[N]Z optimization). (ilutov) + . Fixed bug GH-GH-20914 (Internal enums can be cloned and compared). (Arnaud) - MbString: . Fixed bug GH-20833 (mb_str_pad() divide by zero if padding string is From 4bfaf549f670060d692978b74eb9027564544d65 Mon Sep 17 00:00:00 2001 From: Ilija Tovilo Date: Sun, 11 Jan 2026 19:26:34 +0100 Subject: [PATCH 3/5] Fix leaked parent property default value Fixes OSS-Fuzz #474613951 Closes GH-20911 --- NEWS | 1 + Zend/tests/oss-fuzz-474613951.phpt | 17 +++++++++++++++++ Zend/zend_inheritance.c | 5 ++--- 3 files changed, 20 insertions(+), 3 deletions(-) create mode 100644 Zend/tests/oss-fuzz-474613951.phpt diff --git a/NEWS b/NEWS index d1e88208d83e6..5572f87df12d0 100644 --- a/NEWS +++ b/NEWS @@ -8,6 +8,7 @@ PHP NEWS . Fix OSS-Fuzz #471533782 (Infinite loop in GC destructor fiber). (ilutov) . Fix OSS-Fuzz #472563272 (Borked block_pass JMP[N]Z optimization). (ilutov) . Fixed bug GH-GH-20914 (Internal enums can be cloned and compared). (Arnaud) + . Fix OSS-Fuzz #474613951 (Leaked parent property default value). (ilutov) - MbString: . Fixed bug GH-20833 (mb_str_pad() divide by zero if padding string is diff --git a/Zend/tests/oss-fuzz-474613951.phpt b/Zend/tests/oss-fuzz-474613951.phpt new file mode 100644 index 0000000000000..552be2e0116a9 --- /dev/null +++ b/Zend/tests/oss-fuzz-474613951.phpt @@ -0,0 +1,17 @@ +--TEST-- +OSS-Fuzz #474613951: Leaked parent property default value +--FILE-- + $this->prop; } +} + +class B extends A { + public $prop { get => 42; } +} + +?> +===DONE=== +--EXPECT-- +===DONE=== diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index f7f757b438e27..56e5bdb9295a6 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -1495,10 +1495,9 @@ static void do_inherit_property(zend_property_info *parent_info, zend_string *ke } int parent_num = OBJ_PROP_TO_NUM(parent_info->offset); + /* Don't keep default properties in GC (they may be freed by opcache) */ + zval_ptr_dtor_nogc(&(ce->default_properties_table[parent_num])); if (child_info->offset != ZEND_VIRTUAL_PROPERTY_OFFSET) { - /* Don't keep default properties in GC (they may be freed by opcache) */ - zval_ptr_dtor_nogc(&(ce->default_properties_table[parent_num])); - if (use_child_prop) { ZVAL_UNDEF(&ce->default_properties_table[parent_num]); } else { From 10751821189a94185e6e0c8bde80d4fc1640073d Mon Sep 17 00:00:00 2001 From: Ilija Tovilo Date: Tue, 13 Jan 2026 13:15:31 +0100 Subject: [PATCH 4/5] Fix "fptr may be used uninitialized" warning Older versions of GCC seem to get confused about the call to zend_ast_call_get_args(ast), and assume it may mutate ast, even though it is local. --- Zend/zend_ast.c | 1 + 1 file changed, 1 insertion(+) diff --git a/Zend/zend_ast.c b/Zend/zend_ast.c index e0a68a73bdf27..cd8e6792d031f 100644 --- a/Zend/zend_ast.c +++ b/Zend/zend_ast.c @@ -1238,6 +1238,7 @@ static zend_result ZEND_FASTCALL zend_ast_evaluate_inner( break; } + EMPTY_SWITCH_DEFAULT_CASE() } zend_create_fake_closure(result, fptr, fptr->common.scope, called_scope, NULL); From ab1f34b3de9df8eb88bfd69080cd4763b5eefb54 Mon Sep 17 00:00:00 2001 From: Ilija Tovilo Date: Tue, 13 Jan 2026 13:34:47 +0100 Subject: [PATCH 5/5] Upgrade Circle CI build to Ubuntu 24.04 Closes GH-20923 --- .circleci/config.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index f5dddad093158..8c8f08f17543a 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -4,7 +4,7 @@ jobs: arm: resource_class: arm.medium docker: - - image: cimg/base:current-22.04 + - image: cimg/base:current-24.04 - image: mysql:8.3 environment: MYSQL_ALLOW_EMPTY_PASSWORD: true @@ -60,7 +60,7 @@ jobs: libreadline-dev \ libldap2-dev \ libsodium-dev \ - libargon2-0-dev \ + libargon2-dev \ libmm-dev \ libsnmp-dev \ snmpd \ @@ -78,7 +78,7 @@ jobs: libqdbm-dev \ libjpeg-dev \ libpng-dev \ - libfreetype6-dev + libfreetype-dev - run: name: ./configure command: |