From 9512c51844d64ed9a16b8de1e6329c3b671f05d6 Mon Sep 17 00:00:00 2001 From: David Carlier Date: Sat, 3 Oct 2026 23:51:50 +0100 Subject: [PATCH 1/3] Zend: user opcode DISPATCH runs on a stale frame in the TAILCALL VM. Fix #24081 ZEND_VM_DISPATCH() ran the CALL variant of the handler and returned its next opline to execute_ex(), whose execute_data still pointed at the entry frame. It now tail calls the TAILCALL variant of the handler. --- Zend/zend_vm_execute.h | 6 ++++-- Zend/zend_vm_gen.php | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index dd284391a2a1..e9d66ab066fd 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -336,7 +336,7 @@ static zend_vm_opcode_handler_func_t const * zend_opcode_handler_funcs; static const zend_op call_interrupt_op; #endif -#if (ZEND_VM_KIND != ZEND_VM_KIND_HYBRID && ZEND_VM_KIND != ZEND_VM_KIND_TAILCALL) || !ZEND_VM_SPEC +#if ZEND_VM_KIND != ZEND_VM_KIND_HYBRID || !ZEND_VM_SPEC static zend_vm_opcode_handler_t zend_vm_get_opcode_handler(uint8_t opcode, const zend_op* op); #endif @@ -53712,6 +53712,7 @@ static ZEND_COLD zend_never_inline ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_F # undef ZEND_VM_INTERRUPT # undef ZEND_VM_ENTER_EX # undef ZEND_VM_LEAVE +# undef ZEND_VM_DISPATCH # define ZEND_VM_TAIL_CALL(call) ZEND_MUSTTAIL return call # define ZEND_VM_CONTINUE() ZEND_VM_TAIL_CALL(opline->handler(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU)) @@ -53725,6 +53726,7 @@ static ZEND_COLD zend_never_inline ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_F # define ZEND_VM_INTERRUPT() ZEND_VM_TAIL_CALL(zend_interrupt_TAILCALL(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU)) # define ZEND_VM_ENTER_EX() ZEND_VM_INTERRUPT_CHECK(); ZEND_VM_CONTINUE() # define ZEND_VM_LEAVE() ZEND_VM_CONTINUE() +# define ZEND_VM_DISPATCH(opcode, opline) ZEND_VM_TAIL_CALL(zend_vm_get_opcode_handler(opcode, opline)(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU)) static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV zend_interrupt_helper_SPEC_TAILCALL(ZEND_OPCODE_HANDLER_ARGS); static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV zend_interrupt(ZEND_OPCODE_HANDLER_ARGS); @@ -123417,7 +123419,7 @@ static uint32_t ZEND_FASTCALL zend_vm_get_opcode_handler_idx(uint32_t spec, cons return (spec & SPEC_START_MASK) + offset; } -#if (ZEND_VM_KIND != ZEND_VM_KIND_HYBRID && ZEND_VM_KIND != ZEND_VM_KIND_TAILCALL) || !ZEND_VM_SPEC +#if ZEND_VM_KIND != ZEND_VM_KIND_HYBRID || !ZEND_VM_SPEC static zend_vm_opcode_handler_t zend_vm_get_opcode_handler(uint8_t opcode, const zend_op* op) { return zend_opcode_handlers[zend_vm_get_opcode_handler_idx(zend_spec_handlers[opcode], op)]; diff --git a/Zend/zend_vm_gen.php b/Zend/zend_vm_gen.php index 674d1ed5b673..57b718fcf76a 100755 --- a/Zend/zend_vm_gen.php +++ b/Zend/zend_vm_gen.php @@ -1917,7 +1917,7 @@ function gen_executor($f, $skl, $spec, $kind, $executor_name, $initializer_name) out($f,"static const zend_op call_interrupt_op;\n"); out($f,"#endif\n\n"); } - out($f,"#if (ZEND_VM_KIND != ZEND_VM_KIND_HYBRID && ZEND_VM_KIND != ZEND_VM_KIND_TAILCALL) || !ZEND_VM_SPEC\n"); + out($f,"#if ZEND_VM_KIND != ZEND_VM_KIND_HYBRID || !ZEND_VM_SPEC\n"); out($f,"static zend_vm_opcode_handler_t zend_vm_get_opcode_handler(uint8_t opcode, const zend_op* op);\n"); out($f,"#endif\n\n"); if ($kind == ZEND_VM_KIND_HYBRID) { @@ -2146,6 +2146,7 @@ function gen_executor($f, $skl, $spec, $kind, $executor_name, $initializer_name) out($f,"# undef ZEND_VM_INTERRUPT\n"); out($f,"# undef ZEND_VM_ENTER_EX\n"); out($f,"# undef ZEND_VM_LEAVE\n"); + out($f,"# undef ZEND_VM_DISPATCH\n"); out($f,"\n"); out($f,"# define ZEND_VM_TAIL_CALL(call) ZEND_MUSTTAIL return call\n"); out($f,"# define ZEND_VM_CONTINUE() ZEND_VM_TAIL_CALL(opline->handler(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU))\n"); @@ -2159,6 +2160,7 @@ function gen_executor($f, $skl, $spec, $kind, $executor_name, $initializer_name) out($f,"# define ZEND_VM_INTERRUPT() ZEND_VM_TAIL_CALL(zend_interrupt_TAILCALL(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU))\n"); out($f,"# define ZEND_VM_ENTER_EX() ZEND_VM_INTERRUPT_CHECK(); ZEND_VM_CONTINUE()\n"); out($f,"# define ZEND_VM_LEAVE() ZEND_VM_CONTINUE()\n"); + out($f,"# define ZEND_VM_DISPATCH(opcode, opline) ZEND_VM_TAIL_CALL(zend_vm_get_opcode_handler(opcode, opline)(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU))\n"); out($f,"\n"); out($f,"static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV zend_interrupt_helper".($spec?"_SPEC":"")."_TAILCALL(ZEND_OPCODE_HANDLER_ARGS);\n"); out($f,"static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV zend_interrupt(ZEND_OPCODE_HANDLER_ARGS);\n"); @@ -3042,7 +3044,7 @@ function gen_vm($def, $skel) { out($f, "\treturn (spec & SPEC_START_MASK) + offset;\n"); } out($f, "}\n\n"); - out($f, "#if (ZEND_VM_KIND != ZEND_VM_KIND_HYBRID && ZEND_VM_KIND != ZEND_VM_KIND_TAILCALL) || !ZEND_VM_SPEC\n"); + out($f, "#if ZEND_VM_KIND != ZEND_VM_KIND_HYBRID || !ZEND_VM_SPEC\n"); out($f, "static zend_vm_opcode_handler_t zend_vm_get_opcode_handler(uint8_t opcode, const zend_op* op)\n"); out($f, "{\n"); if (!ZEND_VM_SPEC) { From 5952ca90b0652e59a98c24464e46ee802606def0 Mon Sep 17 00:00:00 2001 From: David Carlier Date: Sun, 4 Oct 2026 00:22:52 +0100 Subject: [PATCH 2/3] add test --- ext/zend_test/tests/gh24081.phpt | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 ext/zend_test/tests/gh24081.phpt diff --git a/ext/zend_test/tests/gh24081.phpt b/ext/zend_test/tests/gh24081.phpt new file mode 100644 index 000000000000..a1dd1f4821c7 --- /dev/null +++ b/ext/zend_test/tests/gh24081.phpt @@ -0,0 +1,23 @@ +--TEST-- +GH-24081 (User opcode handlers resume execution against a stale frame under ZEND_VM_KIND_TAILCALL) +--EXTENSIONS-- +zend_test +--INI-- +zend_test.observer.enabled=1 +zend_test.observer.show_opcode_in_user_handler=ZEND_ADD +--FILE-- + +--EXPECTF-- + + + + + +int(4) From 94810863c25e0064b0058782ccf279ff0d8347d5 Mon Sep 17 00:00:00 2001 From: David Carlier Date: Sun, 4 Oct 2026 01:06:32 +0100 Subject: [PATCH 3/3] fix test attempt --- ext/zend_test/tests/gh24081.phpt | 1 + 1 file changed, 1 insertion(+) diff --git a/ext/zend_test/tests/gh24081.phpt b/ext/zend_test/tests/gh24081.phpt index a1dd1f4821c7..96119454bf69 100644 --- a/ext/zend_test/tests/gh24081.phpt +++ b/ext/zend_test/tests/gh24081.phpt @@ -3,6 +3,7 @@ GH-24081 (User opcode handlers resume execution against a stale frame under ZEND --EXTENSIONS-- zend_test --INI-- +opcache.jit=disable zend_test.observer.enabled=1 zend_test.observer.show_opcode_in_user_handler=ZEND_ADD --FILE--