From 4d719b8094f7f3dbc78e26ab76b22a23ac654652 Mon Sep 17 00:00:00 2001 From: lazerg Date: Mon, 26 Oct 2026 13:57:46 +0500 Subject: [PATCH 1/2] Fix GH-24729: UAF in ReflectionClass::setStaticPropertyValue() --- NEWS | 5 +++++ ext/reflection/php_reflection.c | 4 +++- ext/reflection/tests/gh24729.phpt | 19 +++++++++++++++++++ 3 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 ext/reflection/tests/gh24729.phpt diff --git a/NEWS b/NEWS index 5ea9ecbafb0b..046b49c3483c 100644 --- a/NEWS +++ b/NEWS @@ -139,6 +139,11 @@ PHP NEWS . Fixed a crash in readline_info() when completion_append_character contains a non-ASCII byte. (Weilin Du) +- Reflection: + . Fixed bug GH-24729 (Use-after-free in + ReflectionClass::setStaticPropertyValue() when the old value's destructor + unbinds the property). (Lazizbek Ergashev) + - SAPI: . Fixed infinite loop in sapi_header_op() for headers of 4 GiB or more. (Martin Mirchev) diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c index 2c94a6e80d18..6d2fa9f7abbe 100644 --- a/ext/reflection/php_reflection.c +++ b/ext/reflection/php_reflection.c @@ -4452,8 +4452,10 @@ ZEND_METHOD(ReflectionClass, setStaticPropertyValue) return; } - zval_ptr_dtor(variable_ptr); + zval garbage; + ZVAL_COPY_VALUE(&garbage, variable_ptr); ZVAL_COPY(variable_ptr, value); + zval_ptr_dtor(&garbage); } /* }}} */ diff --git a/ext/reflection/tests/gh24729.phpt b/ext/reflection/tests/gh24729.phpt new file mode 100644 index 000000000000..7613608921f4 --- /dev/null +++ b/ext/reflection/tests/gh24729.phpt @@ -0,0 +1,19 @@ +--TEST-- +GH-24729 (Use-after-free in ReflectionClass::setStaticPropertyValue() when the old value's destructor unbinds the property) +--FILE-- +setStaticPropertyValue('x', str_repeat('A', 64)); +var_dump(A::$x); +?> +--EXPECT-- +int(1) From 112c8b81654dd1e89ae67d939ef0b3aebd264224 Mon Sep 17 00:00:00 2001 From: lazerg Date: Mon, 26 Oct 2026 14:11:25 +0500 Subject: [PATCH 2/2] Assign through the static slot so type coercion can't leave a stale pointer --- ext/reflection/php_reflection.c | 15 ++------------- ext/reflection/tests/gh24729.phpt | 15 +++++++++++++++ 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c index 6d2fa9f7abbe..ae696e762b8c 100644 --- a/ext/reflection/php_reflection.c +++ b/ext/reflection/php_reflection.c @@ -4439,23 +4439,12 @@ ZEND_METHOD(ReflectionClass, setStaticPropertyValue) RETURN_THROWS(); } - if (Z_ISREF_P(variable_ptr)) { - zend_reference *ref = Z_REF_P(variable_ptr); - variable_ptr = Z_REFVAL_P(variable_ptr); - - if (!zend_verify_ref_assignable_zval(ref, value, 0)) { - return; - } - } - if (ZEND_TYPE_IS_SET(prop_info->type) && !zend_verify_property_type(prop_info, value, 0)) { return; } - zval garbage; - ZVAL_COPY_VALUE(&garbage, variable_ptr); - ZVAL_COPY(variable_ptr, value); - zval_ptr_dtor(&garbage); + Z_TRY_ADDREF_P(value); + zend_assign_to_variable(variable_ptr, value, IS_TMP_VAR, 0); } /* }}} */ diff --git a/ext/reflection/tests/gh24729.phpt b/ext/reflection/tests/gh24729.phpt index 7613608921f4..beacb476328b 100644 --- a/ext/reflection/tests/gh24729.phpt +++ b/ext/reflection/tests/gh24729.phpt @@ -14,6 +14,21 @@ $y = new Old; A::$x = &$y; (new ReflectionClass('A'))->setStaticPropertyValue('x', str_repeat('A', 64)); var_dump(A::$x); + +class B { public static string $x = ''; } +class S { + public function __toString(): string { + unset($GLOBALS['w']); + $z = 'z'; + B::$x = &$z; + return str_repeat('B', 8); + } +} +$w = 'old'; +B::$x = &$w; +(new ReflectionClass('B'))->setStaticPropertyValue('x', new S); +var_dump(B::$x); ?> --EXPECT-- int(1) +string(8) "BBBBBBBB"