diff --git a/NEWS b/NEWS index e027d6c139ee..a6557f8a25ec 100644 --- a/NEWS +++ b/NEWS @@ -81,6 +81,10 @@ PHP NEWS . Fixed a heap over-read in the interactive shell prompt when cli.prompt is set to an empty string. (Ilia Alshanetsky) +- Reflection: + . Fixed bug GH-24147 (ReflectionProperty class and name properties can be + modified through compound assignment or by reference). (lazerg) + - Session: . Fixed session_start() continuing after a failed create_sid() when session.use_strict_mode rejects the supplied ID. (Ilia Alshanetsky) diff --git a/UPGRADING b/UPGRADING index 46cf4cafab12..c08a175fdbb3 100644 --- a/UPGRADING +++ b/UPGRADING @@ -27,6 +27,11 @@ PHP 8.7 UPGRADE NOTES not present in the result set. It previously reported the condition through PDO::ATTR_ERRMODE and returned false. +- Reflection: + . Modifying the $name or $class property of a Reflection object through + compound assignment, increment/decrement or by reference now throws a + ReflectionException, like a plain assignment already did. + - Standard: . The number of filters that can be chained in a php://filter URL is limited to 16 by default. Set the stream context option max_filter_count to change diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c index 0c1a6ba721ce..e5157c575349 100644 --- a/ext/reflection/php_reflection.c +++ b/ext/reflection/php_reflection.c @@ -7936,11 +7936,16 @@ ZEND_METHOD(ReflectionFiber, getCallable) RETURN_COPY(&fiber->fci.function_name); } +static bool reflection_is_read_only_property(zend_object *object, zend_string *name) +{ + return zend_hash_exists(&object->ce->properties_info, name) + && (zend_string_equals(name, ZSTR_KNOWN(ZEND_STR_NAME)) || zend_string_equals(name, ZSTR_KNOWN(ZEND_STR_CLASS))); +} + /* {{{ _reflection_write_property */ static zval *_reflection_write_property(zend_object *object, zend_string *name, zval *value, void **cache_slot) { - if (zend_hash_exists(&object->ce->properties_info, name) - && (zend_string_equals(name, ZSTR_KNOWN(ZEND_STR_NAME)) || zend_string_equals(name, ZSTR_KNOWN(ZEND_STR_CLASS)))) + if (reflection_is_read_only_property(object, name)) { zend_throw_exception_ex(reflection_exception_ptr, 0, "Cannot set read-only property %pS::$%pS", object->ce->name, name); @@ -7951,6 +7956,28 @@ static zval *_reflection_write_property(zend_object *object, zend_string *name, } /* }}} */ +static zval *_reflection_read_property(zend_object *object, zend_string *name, int type, void **cache_slot, zval *rv) +{ + /* Don't cache the offset of read-only properties: a by-reference fetch + * sharing the cache slot (FETCH_OBJ_FUNC_ARG) would skip get_property_ptr_ptr. */ + if (reflection_is_read_only_property(object, name)) { + cache_slot = NULL; + } + + return zend_std_read_property(object, name, type, cache_slot, rv); +} + +static zval *_reflection_get_property_ptr_ptr(zend_object *object, zend_string *name, int type, void **cache_slot) +{ + if (reflection_is_read_only_property(object, name)) { + zend_throw_exception_ex(reflection_exception_ptr, 0, + "Cannot set read-only property %s::$%s", ZSTR_VAL(object->ce->name), ZSTR_VAL(name)); + return &EG(error_zval); + } + + return zend_std_get_property_ptr_ptr(object, name, type, cache_slot); +} + ZEND_METHOD(ReflectionConstant, __construct) { zend_string *name; @@ -8171,7 +8198,9 @@ PHP_MINIT_FUNCTION(reflection) /* {{{ */ reflection_object_handlers.offset = offsetof(reflection_object, zo); reflection_object_handlers.free_obj = reflection_free_objects_storage; reflection_object_handlers.clone_obj = NULL; + reflection_object_handlers.read_property = _reflection_read_property; reflection_object_handlers.write_property = _reflection_write_property; + reflection_object_handlers.get_property_ptr_ptr = _reflection_get_property_ptr_ptr; reflection_object_handlers.get_gc = reflection_get_gc; reflection_exception_ptr = register_class_ReflectionException(zend_ce_exception); diff --git a/ext/reflection/tests/gh24147.phpt b/ext/reflection/tests/gh24147.phpt new file mode 100644 index 000000000000..c69b9c904189 --- /dev/null +++ b/ext/reflection/tests/gh24147.phpt @@ -0,0 +1,51 @@ +--TEST-- +GH-24147 (ReflectionProperty class and name properties can be changed) +--FILE-- +class .= 'Z'; +} catch (ReflectionException $e) { + echo $e->getMessage(), "\n"; +} + +try { + $prop->name++; +} catch (ReflectionException $e) { + echo $e->getMessage(), "\n"; +} + +try { + $ref = &$prop->name; + $ref = 'y'; +} catch (ReflectionException $e) { + echo $e->getMessage(), "\n"; +} + +foreach (['byVal', 'byRef'] as $fn) { + try { + $fn($prop->class); + } catch (ReflectionException $e) { + echo $e->getMessage(), "\n"; + } +} + +var_dump($prop->class, $prop->name); + +?> +--EXPECT-- +Cannot set read-only property ReflectionProperty::$class +Cannot set read-only property ReflectionProperty::$name +Cannot set read-only property ReflectionProperty::$name +Cannot set read-only property ReflectionProperty::$class +string(1) "B" +string(1) "x"