[php-src] master: JIT: Fix run_time_cache offset stored without map_ptr dereference

Ilia Alshanetsky <[email protected]>
Newsgroups gmane.comp.php.cvs.general
Message-ID <[email protected]>
Author: Ilia Alshanetsky (iliaal)
Date: 2026-06-26T13:55:58-04:00

Commit: https://github.com/php/php-src/commit/a0ddc2aa577de0dc8018d6b068dddae9cb0f1ab2
Raw diff: https://github.com/php/php-src/commit/a0ddc2aa577de0dc8018d6b068dddae9cb0f1ab2.diff

JIT: Fix run_time_cache offset stored without map_ptr dereference

zend_jit_do_fcall() loaded the callee run_time_cache through the closure
direct-pointer shortcut whenever the call frame was flagged as a closure
call. That flag is set for every ZEND_INIT_DYNAMIC_CALL, but a dynamic
call may resolve to a non-closure function whose run_time_cache is a
zend_map_ptr offset. The raw offset was then stored into
EX(run_time_cache) without resolving it through CG(map_ptr_base), and a
later cache lookup dereferenced a bogus address.

Restrict the shortcut to statically-known closures. Unknown dynamic
calls fall through to the general path, which resolves both offsets and
direct pointers.

Fixes GH-22443
Closes GH-22459

Changed paths:
  A  ext/opcache/tests/jit/gh22443.phpt
  M  NEWS
  M  ext/opcache/jit/zend_jit_ir.c


Diff:

diff --git a/NEWS b/NEWS
index ef8d40d00f82..4022869c0b06 100644
--- a/NEWS
+++ b/NEWS
@@ -12,6 +12,8 @@ PHP                                                                        NEWS
   . Fixed bug GH-22158 (Tracing JIT dispatches the observer begin handler
     through the wrong run_time_cache slot on megamorphic calls). (ptondereau,
     iliaal)
+  . Fixed bug GH-22443 (Tracing JIT SIGSEGV on megamorphic dynamic calls from
+    an undereferenced run_time_cache map_ptr offset). (iliaal)
 
 - Intl:
   . Fixed Locale::lookup() and locale_lookup() to return NULL instead of the
diff --git a/ext/opcache/jit/zend_jit_ir.c b/ext/opcache/jit/zend_jit_ir.c
index 5e8a71cd48ce..f8503a131734 100644
--- a/ext/opcache/jit/zend_jit_ir.c
+++ b/ext/opcache/jit/zend_jit_ir.c
@@ -10221,10 +10221,7 @@ static int zend_jit_do_fcall(zend_jit_ctx *jit, const zend_op *opline, const zen
 			 && ZEND_MAP_PTR_IS_OFFSET(func->op_array.run_time_cache)) {
 				run_time_cache = ir_LOAD_A(ir_ADD_OFFSET(ir_LOAD_A(jit_CG(map_ptr_base)),
 					(uintptr_t)ZEND_MAP_PTR(func->op_array.run_time_cache)));
-			} else if ((func && (func->op_array.fn_flags & ZEND_ACC_CLOSURE)) ||
-					(JIT_G(current_frame) &&
-					 JIT_G(current_frame)->call &&
-					 TRACE_FRAME_IS_CLOSURE_CALL(JIT_G(current_frame)->call))) {
+			} else if (func && (func->op_array.fn_flags & ZEND_ACC_CLOSURE)) {
 				/* Closures always use direct pointers */
 				ir_ref local_func_ref = func_ref ? func_ref : ir_LOAD_A(jit_CALL(rx, func));
 
diff --git a/ext/opcache/tests/jit/gh22443.phpt b/ext/opcache/tests/jit/gh22443.phpt
new file mode 100644
index 000000000000..869329b79b5c
--- /dev/null
+++ b/ext/opcache/tests/jit/gh22443.phpt
@@ -0,0 +1,57 @@
+--TEST--
+GH-22443 (SIGSEGV in tracing JIT: run_time_cache offset stored without map_ptr dereference)
+--EXTENSIONS--
+opcache
+--INI--
+opcache.enable=1
+opcache.enable_cli=1
+opcache.jit=tracing
+opcache.jit_buffer_size=64M
+opcache.jit_max_polymorphic_calls=0
+--FILE--
+<?php
+final class Svc {
+    public function m0(...$a): int { return (int) array_sum($a) + 0; }
+    public function m1(...$a): int { return (int) array_sum($a) + 1; }
+    public function m2(...$a): int { return (int) array_sum($a) + 2; }
+    public function m3(...$a): int { return (int) array_sum($a) + 3; }
+    public function m4(...$a): int { return (int) array_sum($a) + 4; }
+    public function m5(...$a): int { return (int) array_sum($a) + 5; }
+    public function m6(...$a): int { return (int) array_sum($a) + 6; }
+    public function m7(...$a): int { return (int) array_sum($a) + 7; }
+    public function coldMethod(...$a): int { return $this->helper((int) array_sum($a)); }
+    private function helper(int $x): int { return $x + 1; }
+}
+
+function makeListener($listener): Closure {
+    return function ($event, $payload) use ($listener) {
+        return $listener(...array_values($payload));
+    };
+}
+
+function invokeListeners(array $listeners, string $event, array $payload): int {
+    $r = 0;
+    foreach ($listeners as $l) {
+        $r += $l($event, $payload);
+    }
+    return $r;
+}
+
+$svc = new Svc();
+$warm = [];
+for ($k = 0; $k < 8; $k++) {
+    $warm[] = makeListener([$svc, 'm' . $k]);
+}
+$cold = [makeListener([$svc, 'coldMethod'])];
+
+$s = 0;
+for ($i = 0; $i < 4000000; $i++) {
+    $s += invokeListeners($warm, 'e', [$i & 7, ($i >> 2) & 7]);
+}
+for ($j = 0; $j < 5; $j++) {
+    $s += invokeListeners($cold, 'e', [$j, $j + 1]);
+}
+echo "done\n";
+?>
+--EXPECT--
+done
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.