From 919a5616838c44fd55549b7cea090d5dcb23c65a Mon Sep 17 00:00:00 2001 From: Alexander Danilov Date: Fri, 30 Oct 2026 21:19:33 +0200 Subject: [PATCH] Fix use-after-free when assigning to a typed property releases the object --- NEWS | 2 + ...ped_properties_assign_object_released.phpt | 172 ++++++++++++++++++ Zend/zend_execute.c | 22 ++- Zend/zend_vm_def.h | 4 +- Zend/zend_vm_execute.h | 80 ++++---- ext/opcache/jit/zend_jit_helpers.c | 29 ++- ext/opcache/jit/zend_jit_ir.c | 6 +- ext/opcache/tests/jit/assign_obj_007.phpt | 42 +++++ 8 files changed, 303 insertions(+), 54 deletions(-) create mode 100644 Zend/tests/type_declarations/typed_properties_assign_object_released.phpt create mode 100644 ext/opcache/tests/jit/assign_obj_007.phpt diff --git a/NEWS b/NEWS index 57d6e4694d07..47c082bba5cd 100644 --- a/NEWS +++ b/NEWS @@ -36,6 +36,8 @@ PHP NEWS (Marti2203). . Fixed bug GH-24989 (Use-after-free in GC when an exception is thrown). (David Carlier) + . Fixed use-after-free when assigning to a typed property releases the + object while the value is coerced. (Alexander Danilov) - CLI: . Fixed bug GH-24697 (PHP_HTTP_PARSER_STRICT build option doesn't work). diff --git a/Zend/tests/type_declarations/typed_properties_assign_object_released.phpt b/Zend/tests/type_declarations/typed_properties_assign_object_released.phpt new file mode 100644 index 000000000000..95d03ed22489 --- /dev/null +++ b/Zend/tests/type_declarations/typed_properties_assign_object_released.phpt @@ -0,0 +1,172 @@ +--TEST-- +Assigning to a typed property must not use the object after coercion user code released it +--FILE-- +s = $v; + return $this->s; + } +} + +class Releaser { + public function __toString(): string { + unset($GLOBALS['a'][0]); + return "5"; + } +} + +function attempt(Closure $fn) { + try { + $fn(); + echo "assigned\n"; + } catch (Error $e) { + echo $e->getMessage(), "\n"; + } +} + +function assign_tmp(array &$a, bool $release) { $a[0]->s = $release ? new Releaser : "x"; } +function assign_cv(array &$a, $v) { $a[0]->s = $v; } +function assign_int(array &$a, $v) { $a[0]->i = $v; } + +$a = [new Test]; +assign_tmp($a, false); +assign_cv($a, "x"); +assign_int($a, 1); + +echo "--- __toString()\n"; +for ($k = 0; $k < 3; $k++) { + $a = [new Test]; + attempt(function () use (&$a) { assign_tmp($a, true); }); + $a = [new Test]; + $v = new Releaser; + attempt(function () use (&$a, $v) { assign_cv($a, $v); }); +} + +echo "--- error handler\n"; +set_error_handler(function () { + unset($GLOBALS['a'][0]); + return true; +}); +for ($k = 0; $k < 3; $k++) { + $a = [new Test]; + attempt(function () use (&$a) { assign_int($a, "1.5"); }); +} + +echo "--- class type: the object is not coerced\n"; +class Foo {} +class Holder { + public Foo $foo; + public string|Foo $u = ""; +} +$a = [new Holder]; +for ($k = 0; $k < 3; $k++) { + $a[0]->foo = new Foo; + var_dump($a[0]->foo instanceof Foo); +} + +echo "--- union with string: the object is coerced\n"; +function assign_union(array &$a, $v) { $a[0]->u = $v; } +$a = [new Holder]; +assign_union($a, "x"); +for ($k = 0; $k < 3; $k++) { + $a = [new Holder]; + attempt(function () use (&$a) { assign_union($a, new Releaser); }); +} + +echo "--- \$this keeps the object alive\n"; +for ($k = 0; $k < 3; $k++) { + $a = [new Test]; + var_dump($a[0]->set(new Releaser)); +} + +echo "--- static property\n"; +class Stringy { + public function __toString(): string { return "str"; } +} +class Sta { + public static string $s = "a"; + public static int $i = 0; + public static ?Foo $foo = null; +} +for ($k = 0; $k < 3; $k++) { + Sta::$s = 12; + var_dump(Sta::$s); + Sta::$s = new Stringy; + var_dump(Sta::$s); + Sta::$i = "5"; + var_dump(Sta::$i); + Sta::$i = "1.5"; + var_dump(Sta::$i); + Sta::$foo = new Foo; + var_dump(Sta::$foo instanceof Foo); + attempt(function () { Sta::$i = "x"; }); + attempt(function () { Sta::$foo = new stdClass; }); + var_dump(Sta::$i); +} + +echo "--- object stays alive\n"; +$a = [new Test]; +$a[0]->s = 12; +var_dump($a[0]->s); + +?> +--EXPECT-- +--- __toString() +Object was released while assigning to property Test::$s +Object was released while assigning to property Test::$s +Object was released while assigning to property Test::$s +Object was released while assigning to property Test::$s +Object was released while assigning to property Test::$s +Object was released while assigning to property Test::$s +--- error handler +Object was released while assigning to property Test::$i +Object was released while assigning to property Test::$i +Object was released while assigning to property Test::$i +--- class type: the object is not coerced +bool(true) +bool(true) +bool(true) +--- union with string: the object is coerced +Object was released while assigning to property Holder::$u +Object was released while assigning to property Holder::$u +Object was released while assigning to property Holder::$u +--- $this keeps the object alive +string(1) "5" +string(1) "5" +string(1) "5" +--- static property +string(2) "12" +string(3) "str" +int(5) +int(1) +bool(true) +Cannot assign string to property Sta::$i of type int +Cannot assign stdClass to property Sta::$foo of type ?Foo +int(1) +string(2) "12" +string(3) "str" +int(5) +int(1) +bool(true) +Cannot assign string to property Sta::$i of type int +Cannot assign stdClass to property Sta::$foo of type ?Foo +int(1) +string(2) "12" +string(3) "str" +int(5) +int(1) +bool(true) +Cannot assign string to property Sta::$i of type int +Cannot assign stdClass to property Sta::$foo of type ?Foo +int(1) +--- object stays alive +string(2) "12" diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c index 45c8b8fe2823..acf14a40c861 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -1057,7 +1057,7 @@ ZEND_API bool zend_never_inline zend_verify_property_type(const zend_property_in return i_zend_verify_property_type(info, property, strict); } -static zend_never_inline zval* zend_assign_to_typed_prop(zend_property_info *info, zval *property_val, zval *value, zend_refcounted **garbage_ptr EXECUTE_DATA_DC) +static zend_never_inline zval* zend_assign_to_typed_prop(zend_object *zobj, zend_property_info *info, zval *property_val, zval *value, zend_refcounted **garbage_ptr EXECUTE_DATA_DC) { zval tmp; @@ -1075,9 +1075,23 @@ static zend_never_inline zval* zend_assign_to_typed_prop(zend_property_info *inf ZVAL_DEREF(value); ZVAL_COPY(&tmp, value); - if (UNEXPECTED(!i_zend_verify_property_type(info, &tmp, EX_USES_STRICT_TYPES()))) { - zval_ptr_dtor(&tmp); - return &EG(uninitialized_zval); + if (UNEXPECTED(!ZEND_TYPE_CONTAINS_CODE(info->type, Z_TYPE(tmp)))) { + bool protect_object = zobj + && (Z_TYPE(tmp) != IS_OBJECT || (ZEND_TYPE_FULL_MASK(info->type) & MAY_BE_STRING)); + if (protect_object) { + GC_ADDREF(zobj); + } + bool type_matched = i_zend_verify_property_type(info, &tmp, EX_USES_STRICT_TYPES()); + if (protect_object && UNEXPECTED(GC_DELREF(zobj) == 0)) { + zend_object_released_while_assigning_to_property_error(info); + zend_objects_store_del(zobj); + zval_ptr_dtor(&tmp); + return &EG(uninitialized_zval); + } + if (UNEXPECTED(!type_matched)) { + zval_ptr_dtor(&tmp); + return &EG(uninitialized_zval); + } } Z_PROP_FLAG_P(property_val) &= ~IS_PROP_REINITABLE; diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index 53f33e0ce5a0..e04938f2647d 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -2461,7 +2461,7 @@ ZEND_VM_C_LABEL(assign_obj_simple): property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(zobj, prop_info, property_val, value, &garbage EXECUTE_DATA_CC); ZEND_VM_C_GOTO(free_and_exit_assign_obj); } else { ZEND_VM_C_LABEL(fast_assign_obj): @@ -2595,7 +2595,7 @@ ZEND_VM_HANDLER(25, ZEND_ASSIGN_STATIC_PROP, ANY, ANY, CACHE_SLOT, SPEC(OP_DATA= value = GET_OP_DATA_ZVAL_PTR(BP_VAR_R); if (ZEND_TYPE_IS_SET(prop_info->type)) { - value = zend_assign_to_typed_prop(prop_info, prop, value, &garbage EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(NULL, prop_info, prop, value, &garbage EXECUTE_DATA_CC); FREE_OP_DATA(); } else { value = zend_assign_to_variable_ex(prop, value, OP_DATA_TYPE, EX_USES_STRICT_TYPES(), &garbage); diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index 258567bb34ed..aa4a2a86e05e 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -952,7 +952,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_STATIC_PROP_SPEC_OP_DAT value = RT_CONSTANT((opline+1), (opline+1)->op1); if (ZEND_TYPE_IS_SET(prop_info->type)) { - value = zend_assign_to_typed_prop(prop_info, prop, value, &garbage EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(NULL, prop_info, prop, value, &garbage EXECUTE_DATA_CC); } else { value = zend_assign_to_variable_ex(prop, value, IS_CONST, EX_USES_STRICT_TYPES(), &garbage); @@ -988,7 +988,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_STATIC_PROP_SPEC_OP_DAT value = _get_zval_ptr_tmp((opline+1)->op1.var EXECUTE_DATA_CC); if (ZEND_TYPE_IS_SET(prop_info->type)) { - value = zend_assign_to_typed_prop(prop_info, prop, value, &garbage EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(NULL, prop_info, prop, value, &garbage EXECUTE_DATA_CC); zval_ptr_dtor_nogc(EX_VAR((opline+1)->op1.var)); } else { value = zend_assign_to_variable_ex(prop, value, IS_TMP_VAR, EX_USES_STRICT_TYPES(), &garbage); @@ -1024,7 +1024,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_STATIC_PROP_SPEC_OP_DAT value = _get_zval_ptr_var((opline+1)->op1.var EXECUTE_DATA_CC); if (ZEND_TYPE_IS_SET(prop_info->type)) { - value = zend_assign_to_typed_prop(prop_info, prop, value, &garbage EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(NULL, prop_info, prop, value, &garbage EXECUTE_DATA_CC); zval_ptr_dtor_nogc(EX_VAR((opline+1)->op1.var)); } else { value = zend_assign_to_variable_ex(prop, value, IS_VAR, EX_USES_STRICT_TYPES(), &garbage); @@ -1060,7 +1060,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_STATIC_PROP_SPEC_OP_DAT value = _get_zval_ptr_cv_BP_VAR_R((opline+1)->op1.var EXECUTE_DATA_CC); if (ZEND_TYPE_IS_SET(prop_info->type)) { - value = zend_assign_to_typed_prop(prop_info, prop, value, &garbage EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(NULL, prop_info, prop, value, &garbage EXECUTE_DATA_CC); } else { value = zend_assign_to_variable_ex(prop, value, IS_CV, EX_USES_STRICT_TYPES(), &garbage); @@ -24112,7 +24112,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_VAR_CONST_OP_D property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(zobj, prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -24266,7 +24266,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_VAR_CONST_OP_D property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(zobj, prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -24420,7 +24420,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_VAR_CONST_OP_D property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(zobj, prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -24574,7 +24574,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_VAR_CONST_OP_D property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(zobj, prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -27120,7 +27120,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_VAR_TMPVAR_OP_ property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(zobj, prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -27274,7 +27274,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_VAR_TMPVAR_OP_ property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(zobj, prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -27428,7 +27428,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_VAR_TMPVAR_OP_ property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(zobj, prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -27582,7 +27582,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_VAR_TMPVAR_OP_ property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(zobj, prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -31480,7 +31480,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_VAR_CV_OP_DATA property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(zobj, prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -31634,7 +31634,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_VAR_CV_OP_DATA property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(zobj, prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -31788,7 +31788,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_VAR_CV_OP_DATA property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(zobj, prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -31942,7 +31942,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_VAR_CV_OP_DATA property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(zobj, prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -34230,7 +34230,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_UNUSED_CONST_O property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(zobj, prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -34384,7 +34384,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_UNUSED_CONST_O property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(zobj, prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -34538,7 +34538,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_UNUSED_CONST_O property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(zobj, prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -34692,7 +34692,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_UNUSED_CONST_O property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(zobj, prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -36415,7 +36415,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_UNUSED_TMPVAR_ property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(zobj, prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -36569,7 +36569,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_UNUSED_TMPVAR_ property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(zobj, prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -36723,7 +36723,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_UNUSED_TMPVAR_ property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(zobj, prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -36877,7 +36877,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_UNUSED_TMPVAR_ property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(zobj, prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -39070,7 +39070,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_UNUSED_CV_OP_D property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(zobj, prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -39224,7 +39224,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_UNUSED_CV_OP_D property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(zobj, prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -39378,7 +39378,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_UNUSED_CV_OP_D property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(zobj, prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -39532,7 +39532,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_UNUSED_CV_OP_D property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(zobj, prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -43478,7 +43478,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_CV_CONST_OP_DA property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(zobj, prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -43632,7 +43632,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_CV_CONST_OP_DA property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(zobj, prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -43786,7 +43786,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_CV_CONST_OP_DA property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(zobj, prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -43940,7 +43940,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_CV_CONST_OP_DA property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(zobj, prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -47433,7 +47433,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_CV_TMPVAR_OP_D property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(zobj, prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -47587,7 +47587,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_CV_TMPVAR_OP_D property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(zobj, prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -47741,7 +47741,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_CV_TMPVAR_OP_D property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(zobj, prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -47895,7 +47895,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_CV_TMPVAR_OP_D property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(zobj, prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -52941,7 +52941,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_CV_CV_OP_DATA_ property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(zobj, prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -53095,7 +53095,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_CV_CV_OP_DATA_ property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(zobj, prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -53249,7 +53249,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_CV_CV_OP_DATA_ property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(zobj, prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: @@ -53403,7 +53403,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_ASSIGN_OBJ_SPEC_CV_CV_OP_DATA_ property_val = OBJ_PROP(zobj, prop_offset); if (Z_TYPE_P(property_val) != IS_UNDEF) { if (prop_info != NULL) { - value = zend_assign_to_typed_prop(prop_info, property_val, value, &garbage EXECUTE_DATA_CC); + value = zend_assign_to_typed_prop(zobj, prop_info, property_val, value, &garbage EXECUTE_DATA_CC); goto free_and_exit_assign_obj; } else { fast_assign_obj: diff --git a/ext/opcache/jit/zend_jit_helpers.c b/ext/opcache/jit/zend_jit_helpers.c index 9504b5bf79ec..38d048fc7f31 100644 --- a/ext/opcache/jit/zend_jit_helpers.c +++ b/ext/opcache/jit/zend_jit_helpers.c @@ -2651,7 +2651,7 @@ static zend_always_inline bool verify_readonly_and_avis(zval *property_val, zend return true; } -static void ZEND_FASTCALL zend_jit_assign_to_typed_prop(zval *property_val, zend_property_info *info, zval *value, zval *result) +static void ZEND_FASTCALL zend_jit_assign_to_typed_prop(zend_object *zobj, zval *property_val, zend_property_info *info, zval *value, zval *result) { zend_execute_data *execute_data = EG(current_execute_data); zend_refcounted *garbage = NULL; @@ -2674,12 +2674,29 @@ static void ZEND_FASTCALL zend_jit_assign_to_typed_prop(zval *property_val, zend ZVAL_DEREF(value); ZVAL_COPY(&tmp, value); - if (UNEXPECTED(!zend_verify_property_type(info, &tmp, EX_USES_STRICT_TYPES()))) { - zval_ptr_dtor(&tmp); - if (result) { - ZVAL_NULL(result); + if (UNEXPECTED(!ZEND_TYPE_CONTAINS_CODE(info->type, Z_TYPE(tmp)))) { + bool protect_object = Z_TYPE(tmp) != IS_OBJECT + || (ZEND_TYPE_FULL_MASK(info->type) & MAY_BE_STRING); + if (protect_object) { + GC_ADDREF(zobj); + } + bool type_matched = zend_verify_property_type(info, &tmp, EX_USES_STRICT_TYPES()); + if (protect_object && UNEXPECTED(GC_DELREF(zobj) == 0)) { + zend_object_released_while_assigning_to_property_error(info); + zend_objects_store_del(zobj); + zval_ptr_dtor(&tmp); + if (result) { + ZVAL_NULL(result); + } + return; + } + if (UNEXPECTED(!type_matched)) { + zval_ptr_dtor(&tmp); + if (result) { + ZVAL_NULL(result); + } + return; } - return; } Z_PROP_FLAG_P(property_val) &= ~IS_PROP_REINITABLE; diff --git a/ext/opcache/jit/zend_jit_ir.c b/ext/opcache/jit/zend_jit_ir.c index b54c2cddf1ae..78af12f66f14 100644 --- a/ext/opcache/jit/zend_jit_ir.c +++ b/ext/opcache/jit/zend_jit_ir.c @@ -14925,7 +14925,8 @@ static int zend_jit_assign_obj(zend_jit_ctx *jit, } // JIT: value = zend_assign_to_typed_prop(prop_info, property_val, value EXECUTE_DATA_CC); jit_SET_EX_OPLINE(jit, opline); - ir_CALL_4(IR_VOID, ir_CONST_FC_FUNC(zend_jit_assign_to_typed_prop), + ir_CALL_5(IR_VOID, ir_CONST_FC_FUNC(zend_jit_assign_to_typed_prop), + obj_ref, prop_ref, prop_info_ref, arg3, @@ -14996,7 +14997,8 @@ static int zend_jit_assign_obj(zend_jit_ctx *jit, } else { arg4 = jit_ZVAL_ADDR(jit, res_addr); } - ir_CALL_4(IR_VOID, ir_CONST_FC_FUNC(zend_jit_assign_to_typed_prop), + ir_CALL_5(IR_VOID, ir_CONST_FC_FUNC(zend_jit_assign_to_typed_prop), + obj_ref, prop_ref, ref, arg3, diff --git a/ext/opcache/tests/jit/assign_obj_007.phpt b/ext/opcache/tests/jit/assign_obj_007.phpt new file mode 100644 index 000000000000..8db7c0c4fc46 --- /dev/null +++ b/ext/opcache/tests/jit/assign_obj_007.phpt @@ -0,0 +1,42 @@ +--TEST-- +JIT: assigning to a typed property must not use the object after coercion user code released it +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.jit_buffer_size=64M +opcache.jit=function +--EXTENSIONS-- +opcache +--FILE-- +s = $v; +} + +for ($i = 0; $i < 3; $i++) { + $a = [new Test]; + try { + assign($a, new Releaser); + echo "assigned\n"; + } catch (Error $e) { + echo $e->getMessage(), "\n"; + } +} + +?> +--EXPECT-- +Object was released while assigning to property Test::$s +Object was released while assigning to property Test::$s +Object was released while assigning to property Test::$s