Commit 2cf7ad09bdd for php

commit 2cf7ad09bdd44e495479e0b51d0161096ad8bc0a
Author: David Carlier <devnexen@gmail.com>
Date:   Fri Oct 9 13:21:09 2026 +0100

    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

diff --git a/NEWS b/NEWS
index 8561f853ec9..ab7a4aed1ac 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 de1a7159338..64e0c5f041e 100644
Binary files a/Zend/zend_vm_execute.h and b/Zend/zend_vm_execute.h differ
diff --git a/Zend/zend_vm_gen.php b/Zend/zend_vm_gen.php
index 6e6349bee0a..e08d9ebd32b 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 00000000000..96119454bf6
--- /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)