[php-src] PHP-8.5: Fix GH-17626: JIT corrupts opline handler when blacklisting root trace

From: Date: Wed, 07 Oct 2026 20:26:40 +0000
Subject: [php-src] PHP-8.5: Fix GH-17626: JIT corrupts opline handler when blacklisting root trace
Groups: php.cvs 
Request: Send a blank email to php-cvs+get-142074@lists.php.net to get a copy of this message
Author: RV7PR (RV7PR) Committer: ndossche (ndossche) Date: 2026-10-07T22:25:33+02:00 Commit: https://github.com/php/php-src/commit/7050d57e0eb28865a6263ea24e49c8e31ce18513 Raw diff: https://github.com/php/php-src/commit/7050d57e0eb28865a6263ea24e49c8e31ce18513.diff Fix GH-17626: JIT corrupts opline handler when blacklisting root trace When a trace exits through a ZEND_JIT_EXIT_INVALIDATE guard while ZEND_JIT_TRACE_NUM has already reached opcache.jit_max_root_traces, zend_jit_trace_exit() blacklists the root trace and restores the original VM handler. It wrote that handler to the exit opline (the INIT_* opline whose callee guard failed) instead of the root trace's start opline. The opcodes live in SHM, so every worker keeps executing the foreign handler until restart. For a root trace that starts at the entry of a function with typed parameters the copied handler is ZEND_RECV, which then runs on an INIT_FCALL / INIT_STATIC_METHOD_CALL opline and raises "Too few arguments to function X(), N passed ... and exactly N expected" from a frame that has all its arguments. Other root oplines lead to crashes instead. Introduced by 350af54 (GH-14475). Closes GH-23571. Changed paths: A ext/opcache/tests/jit/gh17626.inc A ext/opcache/tests/jit/gh17626.phpt A ext/opcache/tests/jit/gh17626_002.inc A ext/opcache/tests/jit/gh17626_002.phpt M NEWS M ext/opcache/jit/zend_jit_trace.c Diff: diff --git a/NEWS b/NEWS index 64c94bbbebd2..b2941f680ebe 100644 --- a/NEWS +++ b/NEWS @@ -9,6 +9,9 @@ PHP NEWS - Opcache: . Fixed bug GH-20890 (Segfault in zval_undefined_cv with non-simple property hook with minimal tracing JIT). (ndossche) + . Fixed bug GH-17626 (JIT corrupts an opline handler when blacklisting a + root trace at the opcache.jit_max_root_traces limit, causing spurious + "Too few arguments" errors and crashes). (RV7PR) - SOAP: . Fixed use of uninitialized func in do_request() on OOM bailout. diff --git a/ext/opcache/jit/zend_jit_trace.c b/ext/opcache/jit/zend_jit_trace.c index 5aa62d2e0878..41136bcd8051 100644 --- a/ext/opcache/jit/zend_jit_trace.c +++ b/ext/opcache/jit/zend_jit_trace.c @@ -8788,7 +8788,7 @@ int ZEND_FASTCALL zend_jit_trace_exit(uint32_t exit_num, zend_jit_registers_buf SHM_UNPROTECT(); zend_jit_unprotect(); - ((zend_op*)opline)->handler = + ((zend_op*)(t->opline))->handler = ZEND_OP_TRACE_INFO(t->opline, jit_extension->offset)->orig_handler; ZEND_OP_TRACE_INFO(t->opline, jit_extension->offset)->trace_flags &= ~ZEND_JIT_TRACE_JITED; diff --git a/ext/opcache/tests/jit/gh17626.inc b/ext/opcache/tests/jit/gh17626.inc new file mode 100644 index 000000000000..307a47ad2943 --- /dev/null +++ b/ext/opcache/tests/jit/gh17626.inc @@ -0,0 +1,2 @@ +<?php +function gh17626_callee(string $s) { return strtoupper($s); } diff --git a/ext/opcache/tests/jit/gh17626.phpt b/ext/opcache/tests/jit/gh17626.phpt new file mode 100644 index 000000000000..dbd917935c5f --- /dev/null +++ b/ext/opcache/tests/jit/gh17626.phpt @@ -0,0 +1,41 @@ +--TEST-- +GH-17626: Opline handler corrupted when a root trace is blacklisted at the max_root_traces limit (fails with --repeat 2) +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.file_update_protection=0 +opcache.revalidate_freq=0 +opcache.jit=tracing +opcache.jit_buffer_size=16M +opcache.jit_hot_func=2 +opcache.jit_hot_loop=255 +opcache.jit_hot_return=255 +opcache.jit_hot_side_exit=255 +opcache.jit_max_root_traces=2 +--EXTENSIONS-- +opcache +--FILE-- +<?php +namespace GH17626; + +// In --repeat 2 the callee is recompiled after the first run, so the function +// guard in the trace compiled for caller() fails with ZEND_JIT_EXIT_INVALIDATE. + +require __DIR__ . '/gh17626.inc'; + +function caller(string $s) { + return gh17626_callee($s) . $s; +} + +caller('a'); +caller('a'); +caller('a'); +echo caller('a'), "\n"; +echo caller('b'), "\n"; + +touch(__DIR__ . '/gh17626.inc'); +opcache_invalidate(__DIR__ . '/gh17626.inc', true); +?> +--EXPECT-- +Aa +Bb diff --git a/ext/opcache/tests/jit/gh17626_002.inc b/ext/opcache/tests/jit/gh17626_002.inc new file mode 100644 index 000000000000..8a12c7ae9fb6 --- /dev/null +++ b/ext/opcache/tests/jit/gh17626_002.inc @@ -0,0 +1,2 @@ +<?php +class GH17626GrandParent {} diff --git a/ext/opcache/tests/jit/gh17626_002.phpt b/ext/opcache/tests/jit/gh17626_002.phpt new file mode 100644 index 000000000000..a0f4792f5404 --- /dev/null +++ b/ext/opcache/tests/jit/gh17626_002.phpt @@ -0,0 +1,47 @@ +--TEST-- +GH-17626: Opline handler corrupted when a root trace is blacklisted at the max_root_traces limit +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.file_update_protection=0 +opcache.jit=tracing +opcache.jit_buffer_size=16M +opcache.jit_hot_func=2 +opcache.jit_hot_loop=255 +opcache.jit_hot_return=255 +opcache.jit_hot_side_exit=255 +opcache.jit_max_root_traces=2 +--EXTENSIONS-- +opcache +--FILE-- +<?php +require __DIR__ . '/gh17626_002.inc'; + +class ParentA extends GH17626GrandParent { public static function m() { return 'A'; } } +class ParentB extends GH17626GrandParent { public static function m() { return 'B'; } } + +trait T { + public function run(string $s) { + return parent::m() . $s; + } +} + +class A extends ParentA { use T; } +class B extends ParentB { use T; } + +$a = new A; +$b = new B; + +$a->run('x'); +$a->run('x'); +$a->run('x'); +echo $a->run('x'), "\n"; +echo $b->run('y'), "\n"; +echo $b->run('y'), "\n"; +echo $a->run('x'), "\n"; +?> +--EXPECT-- +Ax +By +By +Ax

« previous php.cvs (#142074) next »