diff --git a/Zend/tests/gh10169.phpt b/Zend/tests/gh10169.phpt new file mode 100644 index 0000000000000..c64c2224ef129 --- /dev/null +++ b/Zend/tests/gh10169.phpt @@ -0,0 +1,37 @@ +--TEST-- +GH-10169: Fix use-after-free when releasing object during property assignment +--FILE-- +prop = new B(); +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} + +$a = new A(); +$a->prop = ''; +try { + $a->prop = new B(); +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} + +?> +--EXPECT-- +Object was released while assigning property A::$prop +Object was released while assigning property A::$prop diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c index b051937a5bc08..469b96371cf4d 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -894,6 +894,12 @@ ZEND_API ZEND_COLD void ZEND_FASTCALL zend_readonly_property_indirect_modificati ZSTR_VAL(info->ce->name), zend_get_unmangled_property_name(info->name)); } +ZEND_API ZEND_COLD void ZEND_FASTCALL zend_object_released_while_assigning_to_property_error(const zend_property_info *info) +{ + zend_throw_error(NULL, "Object was released while assigning property %s::$%s", + ZSTR_VAL(info->ce->name), zend_get_unmangled_property_name(info->name)); +} + static const zend_class_entry *resolve_single_class_type(zend_string *name, const zend_class_entry *self_ce) { if (zend_string_equals_literal_ci(name, "self")) { return self_ce; diff --git a/Zend/zend_execute.h b/Zend/zend_execute.h index a1e29f5cd0a0b..1f7158d1de7b0 100644 --- a/Zend/zend_execute.h +++ b/Zend/zend_execute.h @@ -82,6 +82,8 @@ ZEND_API ZEND_COLD void zend_wrong_string_offset_error(void); ZEND_API ZEND_COLD void ZEND_FASTCALL zend_readonly_property_modification_error(const zend_property_info *info); ZEND_API ZEND_COLD void ZEND_FASTCALL zend_readonly_property_indirect_modification_error(const zend_property_info *info); +ZEND_API ZEND_COLD void ZEND_FASTCALL zend_object_released_while_assigning_to_property_error(const zend_property_info *info); + ZEND_API bool zend_verify_scalar_type_hint(uint32_t type_mask, zval *arg, bool strict, bool is_internal_arg); ZEND_API ZEND_COLD void zend_verify_arg_error( const zend_function *zf, const zend_arg_info *arg_info, uint32_t arg_num, zval *value); diff --git a/Zend/zend_object_handlers.c b/Zend/zend_object_handlers.c index ab550497d5c3d..37e566b0a74df 100644 --- a/Zend/zend_object_handlers.c +++ b/Zend/zend_object_handlers.c @@ -818,7 +818,17 @@ ZEND_API zval *zend_std_write_property(zend_object *zobj, zend_string *name, zva } ZVAL_COPY_VALUE(&tmp, value); - if (UNEXPECTED(!zend_verify_property_type(prop_info, &tmp, property_uses_strict_types()))) { + // Increase refcount to prevent object from being released in __toString() + GC_ADDREF(zobj); + bool type_matched = zend_verify_property_type(prop_info, &tmp, property_uses_strict_types()); + if (UNEXPECTED(GC_DELREF(zobj) == 0)) { + zend_object_released_while_assigning_to_property_error(prop_info); + zend_objects_store_del(zobj); + zval_ptr_dtor(&tmp); + variable_ptr = &EG(error_zval); + goto exit; + } + if (UNEXPECTED(!type_matched)) { Z_TRY_DELREF_P(value); variable_ptr = &EG(error_zval); goto exit; @@ -889,7 +899,17 @@ ZEND_API zval *zend_std_write_property(zend_object *zobj, zend_string *name, zva } ZVAL_COPY_VALUE(&tmp, value); - if (UNEXPECTED(!zend_verify_property_type(prop_info, &tmp, property_uses_strict_types()))) { + // Increase refcount to prevent object from being released in __toString() + GC_ADDREF(zobj); + bool type_matched = zend_verify_property_type(prop_info, &tmp, property_uses_strict_types()); + if (UNEXPECTED(GC_DELREF(zobj) == 0)) { + zend_object_released_while_assigning_to_property_error(prop_info); + zend_objects_store_del(zobj); + zval_ptr_dtor(&tmp); + variable_ptr = &EG(error_zval); + goto exit; + } + if (UNEXPECTED(!type_matched)) { zval_ptr_dtor(value); goto exit; }