diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index 0d11a17ce781..47021b8a04e1 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -56523,6 +56523,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_NULL_HANDLER( # define ZEND_VM_DISPATCH_TO_HELPER(call) \ do { \ opline = call; \ + if (UNEXPECTED(((uintptr_t)opline & ZEND_VM_ENTER_BIT))) { \ + return opline; \ + } \ ZEND_VM_TAIL_CALL(opline->handler(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU)); \ } while (0) # define ZEND_VM_DISPATCH_TO_LEAVE_HELPER(helper) opline = &call_leave_op; SAVE_OPLINE(); ZEND_VM_CONTINUE() diff --git a/Zend/zend_vm_gen.php b/Zend/zend_vm_gen.php index 38c20b24da2e..09c940882ec4 100755 --- a/Zend/zend_vm_gen.php +++ b/Zend/zend_vm_gen.php @@ -2136,6 +2136,9 @@ function gen_executor($f, $skl, $spec, $kind, $executor_name, $initializer_name) out($f,"# define ZEND_VM_DISPATCH_TO_HELPER(call) \\\n"); out($f," do { \\\n"); out($f," opline = call; \\\n"); + out($f," if (UNEXPECTED(((uintptr_t)opline & ZEND_VM_ENTER_BIT))) { \\\n"); + out($f," return opline; \\\n"); + out($f," } \\\n"); out($f," ZEND_VM_TAIL_CALL(opline->handler(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU)); \\\n"); out($f," } while (0)\n"); out($f,"# define ZEND_VM_DISPATCH_TO_LEAVE_HELPER(helper) opline = &call_leave_op; SAVE_OPLINE(); ZEND_VM_CONTINUE()\n"); diff --git a/ext/zend_test/object_handlers.c b/ext/zend_test/object_handlers.c index 15e362605f8e..9163c2f416a1 100644 --- a/ext/zend_test/object_handlers.c +++ b/ext/zend_test/object_handlers.c @@ -232,6 +232,44 @@ ZEND_METHOD(NumericCastableNoOperations, __construct) ZVAL_COPY(OBJ_PROP_NUM(Z_OBJ_P(ZEND_THIS), 0), n); } +static zend_class_entry *vm_interrupt_comparable_ce; +static zend_object_handlers vm_interrupt_comparable_object_handlers; + +static zend_object* vm_interrupt_comparable_object_create_ex(zend_class_entry* ce, zend_long l) { + zend_object *obj = zend_objects_new(ce); + object_properties_init(obj, ce); + obj->handlers = &vm_interrupt_comparable_object_handlers; + ZVAL_LONG(OBJ_PROP_NUM(obj, 0), l); + return obj; +} + +static zend_object *vm_interrupt_comparable_object_create(zend_class_entry *ce) +{ + return vm_interrupt_comparable_object_create_ex(ce, 0); +} + +static int vm_interrupt_comparable_compare(zval *op1, zval *op2) +{ + ZEND_COMPARE_OBJECTS_FALLBACK(op1, op2); + + zend_atomic_bool_store_ex(&EG(vm_interrupt), true); + + return ZEND_THREEWAY_COMPARE( + Z_LVAL_P(OBJ_PROP_NUM(Z_OBJ_P(op1), 0)), + Z_LVAL_P(OBJ_PROP_NUM(Z_OBJ_P(op2), 0))); +} + +ZEND_METHOD(VmInterruptComparable, __construct) +{ + zend_long l; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_LONG(l) + ZEND_PARSE_PARAMETERS_END(); + + ZVAL_LONG(OBJ_PROP_NUM(Z_OBJ_P(ZEND_THIS), 0), l); +} + static zend_class_entry *dimension_handlers_no_ArrayAccess_ce; static zend_object_handlers dimension_handlers_no_ArrayAccess_object_handlers; @@ -302,6 +340,11 @@ void zend_test_object_handlers_init(void) memcpy(&numeric_castable_no_operation_object_handlers, &std_object_handlers, sizeof(zend_object_handlers)); numeric_castable_no_operation_object_handlers.cast_object = numeric_castable_no_operation_cast_object; + vm_interrupt_comparable_ce = register_class_VmInterruptComparable(); + vm_interrupt_comparable_ce->create_object = vm_interrupt_comparable_object_create; + memcpy(&vm_interrupt_comparable_object_handlers, &std_object_handlers, sizeof(zend_object_handlers)); + vm_interrupt_comparable_object_handlers.compare = vm_interrupt_comparable_compare; + dimension_handlers_no_ArrayAccess_ce = register_class_DimensionHandlersNoArrayAccess(); dimension_handlers_no_ArrayAccess_ce->create_object = dimension_handlers_no_ArrayAccess_object_create; memcpy(&dimension_handlers_no_ArrayAccess_object_handlers, &std_object_handlers, sizeof(zend_object_handlers)); diff --git a/ext/zend_test/object_handlers.stub.php b/ext/zend_test/object_handlers.stub.php index a474908b1095..8c8e7f9bfe9b 100644 --- a/ext/zend_test/object_handlers.stub.php +++ b/ext/zend_test/object_handlers.stub.php @@ -23,6 +23,11 @@ final class NumericCastableNoOperations { public function __construct(int|float $val) {} } +final class VmInterruptComparable { + private int $val; + public function __construct(int $val) {} +} + class DimensionHandlersNoArrayAccess { public bool $read = false; public bool $write = false; diff --git a/ext/zend_test/object_handlers_arginfo.h b/ext/zend_test/object_handlers_arginfo.h index 370ad13894aa..b46028a6e011 100644 --- a/ext/zend_test/object_handlers_arginfo.h +++ b/ext/zend_test/object_handlers_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: 81be60f2c465ffe5c036739d072ab80d9c388907 */ + * Stub hash: 1a70ed60c5af38539b1222a979f97fddf7d1826e */ ZEND_BEGIN_ARG_INFO_EX(arginfo_class_DoOperationNoCast___construct, 0, 0, 1) ZEND_ARG_TYPE_INFO(0, val, IS_LONG, 0) @@ -15,10 +15,13 @@ ZEND_BEGIN_ARG_INFO_EX(arginfo_class_NumericCastableNoOperations___construct, 0, ZEND_ARG_TYPE_MASK(0, val, MAY_BE_LONG|MAY_BE_DOUBLE, NULL) ZEND_END_ARG_INFO() +#define arginfo_class_VmInterruptComparable___construct arginfo_class_DoOperationNoCast___construct + static ZEND_METHOD(DoOperationNoCast, __construct); static ZEND_METHOD(LongCastableNoOperations, __construct); static ZEND_METHOD(FloatCastableNoOperations, __construct); static ZEND_METHOD(NumericCastableNoOperations, __construct); +static ZEND_METHOD(VmInterruptComparable, __construct); static const zend_function_entry class_DoOperationNoCast_methods[] = { ZEND_ME(DoOperationNoCast, __construct, arginfo_class_DoOperationNoCast___construct, ZEND_ACC_PUBLIC) @@ -40,6 +43,11 @@ static const zend_function_entry class_NumericCastableNoOperations_methods[] = { ZEND_FE_END }; +static const zend_function_entry class_VmInterruptComparable_methods[] = { + ZEND_ME(VmInterruptComparable, __construct, arginfo_class_VmInterruptComparable___construct, ZEND_ACC_PUBLIC) + ZEND_FE_END +}; + static zend_class_entry *register_class_DoOperationNoCast(void) { zend_class_entry ce, *class_entry; @@ -104,6 +112,22 @@ static zend_class_entry *register_class_NumericCastableNoOperations(void) return class_entry; } +static zend_class_entry *register_class_VmInterruptComparable(void) +{ + zend_class_entry ce, *class_entry; + + INIT_CLASS_ENTRY(ce, "VmInterruptComparable", class_VmInterruptComparable_methods); + class_entry = zend_register_internal_class_with_flags(&ce, NULL, ZEND_ACC_FINAL); + + zval property_val_default_value; + ZVAL_UNDEF(&property_val_default_value); + zend_string *property_val_name = zend_string_init("val", sizeof("val") - 1, 1); + zend_declare_typed_property(class_entry, property_val_name, &property_val_default_value, ZEND_ACC_PRIVATE, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_LONG)); + zend_string_release(property_val_name); + + return class_entry; +} + static zend_class_entry *register_class_DimensionHandlersNoArrayAccess(void) { zend_class_entry ce, *class_entry; diff --git a/ext/zend_test/tests/observer_vm_interrupt_tailcall_helper.phpt b/ext/zend_test/tests/observer_vm_interrupt_tailcall_helper.phpt new file mode 100644 index 000000000000..d0178bdbf614 --- /dev/null +++ b/ext/zend_test/tests/observer_vm_interrupt_tailcall_helper.phpt @@ -0,0 +1,26 @@ +--TEST-- +Observer: VM interrupt during tailcall helper dispatch +--DESCRIPTION-- +This exercises a VM interrupt raised while an opcode handler dispatches to an +extra-argument helper. On the tailcall VM, the helper may return an opline +tagged with ZEND_VM_ENTER_BIT; treating that tagged value as a zend_op * before +tailcalling the next handler can crash. +--EXTENSIONS-- +zend_test +--INI-- +opcache.jit=0 +zend_test.observer.set_vm_interrupt_on_begin=1 +--FILE-- + +--EXPECT-- +stdClass