From 1cbea32f2edf02036809233a6239721d122b434a Mon Sep 17 00:00:00 2001 From: Ilia Alshanetsky Date: Thu, 9 Apr 2026 12:58:37 -0400 Subject: [PATCH] Fix GH-21687: array_walk creates references to readonly properties array_walk() wrapped object properties in IS_REFERENCE for the callback, bypassing readonly enforcement. For enum singletons this permanently corrupted the shared object (and opcache SHM), crashing on any subsequent var_dump(). For regular readonly properties it allowed the callback to silently modify values that should be immutable. Check ZEND_ACC_READONLY before creating the reference, matching the check that foreach by-reference already performs. Throw the same "Cannot acquire reference to readonly property" error. Closes GH-21687 --- Zend/tests/gh21687.phpt | 32 ++++++++++++++++++++++++++++++++ ext/standard/array.c | 15 ++++++++++++--- 2 files changed, 44 insertions(+), 3 deletions(-) create mode 100644 Zend/tests/gh21687.phpt diff --git a/Zend/tests/gh21687.phpt b/Zend/tests/gh21687.phpt new file mode 100644 index 0000000000000..602f406b61052 --- /dev/null +++ b/Zend/tests/gh21687.phpt @@ -0,0 +1,32 @@ +--TEST-- +GH-21687 (array_walk corrupts readonly and enum properties by wrapping them in IS_REFERENCE) +--FILE-- +getMessage() . "\n"; +} +var_dump($bar); + +class MyObj { + public function __construct(public readonly string $name = "test") {} +} +$obj = new MyObj(); +try { + array_walk($obj, function(&$v) { $v = "modified"; }); +} catch (\Error $e) { + echo $e->getMessage() . "\n"; +} +echo $obj->name . "\n"; +?> +--EXPECT-- +Cannot acquire reference to readonly property Foo::$name +enum(Foo::Bar) +Cannot acquire reference to readonly property MyObj::$name +test diff --git a/ext/standard/array.c b/ext/standard/array.c index 037f13a7a4647..6432506b3ed1f 100644 --- a/ext/standard/array.c +++ b/ext/standard/array.c @@ -1409,10 +1409,19 @@ static zend_result php_array_walk( /* Add type source for property references. */ if (Z_TYPE_P(zv) != IS_REFERENCE && Z_TYPE_P(array) == IS_OBJECT) { zend_property_info *prop_info = - zend_get_typed_property_info_for_slot(Z_OBJ_P(array), zv); + zend_get_property_info_for_slot(Z_OBJ_P(array), zv); if (prop_info) { - ZVAL_NEW_REF(zv, zv); - ZEND_REF_ADD_TYPE_SOURCE(Z_REF_P(zv), prop_info); + if (UNEXPECTED(prop_info->flags & ZEND_ACC_READONLY)) { + zend_throw_error(NULL, + "Cannot acquire reference to readonly property %s::$%s", + ZSTR_VAL(prop_info->ce->name), + zend_get_unmangled_property_name(prop_info->name)); + break; + } + if (ZEND_TYPE_IS_SET(prop_info->type)) { + ZVAL_NEW_REF(zv, zv); + ZEND_REF_ADD_TYPE_SOURCE(Z_REF_P(zv), prop_info); + } } } }