Commit a0ddc2aa577 for php.net
commit a0ddc2aa577de0dc8018d6b068dddae9cb0f1ab2
Author: Ilia Alshanetsky <ilia@ilia.ws>
Date: Thu Jun 25 17:24:38 2026 -0400
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
diff --git a/NEWS b/NEWS
index ef8d40d00f8..4022869c0b0 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 5e8a71cd48c..f8503a13173 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 00000000000..869329b79b5
--- /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