[php-src] master: Fix GH-24081: User opcode DISPATCH runs on a stale frame in the TAILCALL VM
| From: | David Carlier | Date: | Fri, 09 Oct 2026 12:26:44 +0000 |
| Subject: | [php-src] master: Fix GH-24081: User opcode DISPATCH runs on a stale frame in the TAILCALL VM | ||
| Groups: | php.cvs | ||
| Request: | Send a blank email to php-cvs+get-142112@lists.php.net to get a copy of this message | ||
Author: David Carlier (devnexen)
Date: 2026-10-09T13:21:09+01:00
Commit: https://github.com/php/php-src/commit/2cf7ad09bdd44e495479e0b51d0161096ad8bc0a
Raw diff: https://github.com/php/php-src/commit/2cf7ad09bdd44e495479e0b51d0161096ad8bc0a.diff
Fix GH-24081: User opcode DISPATCH runs on a stale frame in the TAILCALL VM
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.
Close GH-24109
Changed paths:
A ext/zend_test/tests/gh24081.phpt
M NEWS
M Zend/zend_vm_execute.h
M Zend/zend_vm_gen.php
Diff:
diff --git a/NEWS b/NEWS
index 8561f853ec96..ab7a4aed1ac2 100644
--- a/NEWS
+++ b/NEWS
@@ -10,6 +10,8 @@ PHP NEWS
. Fixed memory manager keeping a block reallocated to exactly the size of
the next smaller bin in its larger bin, which efree_size() then freed
into the wrong one. (Marc Bennewitz)
+ . Fixed bug GH-24081 (User opcode handlers resume execution against a stale
+ frame under ZEND_VM_KIND_TAILCALL). (devnexen)
- Opcache:
. Fixed bug GH-17626 (JIT corrupts an opline handler when blacklisting a
diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h
index de1a7159338e..64e0c5f041e1 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
@@ -56563,6 +56563,7 @@ static ZEND_COLD zend_never_inline ZEND_OPCODE_HANDLER_RET
ZEND_OPCODE_HANDLER_F
# undef ZEND_VM_RETURN
# undef ZEND_VM_DISPATCH_TO_HELPER
# undef ZEND_VM_INTERRUPT
+# 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))
@@ -56574,6 +56575,7 @@ static ZEND_COLD zend_never_inline ZEND_OPCODE_HANDLER_RET
ZEND_OPCODE_HANDLER_F
} while (0)
# define ZEND_VM_DISPATCH_TO_LEAVE_HELPER(helper) opline = &call_leave_op; SAVE_OPLINE();
ZEND_VM_CONTINUE()
# define ZEND_VM_INTERRUPT()
ZEND_VM_TAIL_CALL(zend_interrupt_TAILCALL(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU))
+# 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);
@@ -129420,7 +129422,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 6e6349bee0ad..e08d9ebd32b2 100755
--- a/Zend/zend_vm_gen.php
+++ b/Zend/zend_vm_gen.php
@@ -1922,7 +1922,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) {
@@ -2149,6 +2149,7 @@ function gen_executor($f, $skl, $spec, $kind, $executor_name,
$initializer_name)
out($f,"# undef ZEND_VM_RETURN\n");
out($f,"# undef ZEND_VM_DISPATCH_TO_HELPER\n");
out($f,"# undef ZEND_VM_INTERRUPT\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");
@@ -2160,6 +2161,7 @@ function gen_executor($f, $skl, $spec, $kind, $executor_name,
$initializer_name)
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");
out($f,"# define ZEND_VM_INTERRUPT()
ZEND_VM_TAIL_CALL(zend_interrupt_TAILCALL(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU))\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");
@@ -3043,7 +3045,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) {
diff --git a/ext/zend_test/tests/gh24081.phpt b/ext/zend_test/tests/gh24081.phpt
new file mode 100644
index 000000000000..96119454bf69
--- /dev/null
+++ b/ext/zend_test/tests/gh24081.phpt
@@ -0,0 +1,24 @@
+--TEST--
+GH-24081 (User opcode handlers resume execution against a stale frame under ZEND_VM_KIND_TAILCALL)
+--EXTENSIONS--
+zend_test
+--INI--
+opcache.jit=disable
+zend_test.observer.enabled=1
+zend_test.observer.show_opcode_in_user_handler=ZEND_ADD
+--FILE--
+<?php
+function f($a, $b) {
+ $r = $a + $b;
+ return strlen("x") + $r;
+}
+
+var_dump(f(1, 2));
+?>
+--EXPECTF--
+<!-- init '%s' -->
+<!-- init f() -->
+<!-- opcode: 'ZEND_ADD' in user handler -->
+<!-- opcode: 'ZEND_ADD' in user handler -->
+<!-- init var_dump() -->
+int(4)