[php-src] Issue #22084: Segfault in PHP-FPM with tracing JIT (opcache.jit=1205) due to stale base pointer in rbp (Regression in 8.5.5)

[email protected] (martinvenus) Mon, 18 May 2026 17:03:56 +0000
Newsgroups php.bugs
Message-ID <[email protected]>
Issue: https://github.com/php/php-src/issues/22084
Author: martinvenus

<html><head></head><body><h3>Description</h3>
<p>After upgrading from PHP 8.5.4 to 8.5.5, php-fpm workers started
segfaulting deterministically under normal web traffic. Issue persists
on 8.5.6. Only tracing JIT (<code>opcache.jit=1205</code>) is affected; function
JIT (<code>1235</code>) and disabled JIT both work fine.</p>
<h3>Environment</h3>
<ul>
<li>PHP 8.5.5 and 8.5.6 (official <code>php:8.5.6-fpm</code> Docker image,
Debian Trixie base)</li>
<li>x86_64</li>
<li>Application: Nette Framework + Dibi 5.1.1 + ext/mysqli, MariaDB 11.8</li>
</ul>
<h3>JIT configuration matrix</h3>

opcache.jit | Result
-- | --
1205 (tracing) | Workers segfault within seconds of receiving traffic
1235 (function) | Stable
off | Stable


<p>PHP 8.5.4 with <code>opcache.jit=1205</code> was stable. Regression introduced in
8.5.5, not resolved in 8.5.6.</p>
<h3>opcache config</h3>
<pre><code class="language-ini">opcache.enable=1
opcache.memory_consumption=256
opcache.jit=1205
opcache.jit_buffer_size=128M
</code></pre>
<h3>Crash signature (host dmesg)</h3>
<p>Repeated segfaults at a small, stable set of offsets in the php-fpm
binary (binary base changes due to ASLR; offsets are consistent):</p>
<pre><code>php-fpm[294862]: segfault at 7e3e1bc12bc0 ip ...4c9466 ... error 4 in php-fpm[4c9466,...+801000]
php-fpm[515992]: segfault at 702b7efcdf8c ip ...4bc8a6 ... error 4 in php-fpm[4bc8a6,...+801000]
php-fpm[521652]: segfault at 7b4425b40ca0 ip ...4bc17b ... error 4 in php-fpm[4bc17b,...+801000]
php-fpm[529400]: segfault at 72f91dc4c1c0 ip ...4c9421 ... error 4 in php-fpm[4c9421,...+801000]
</code></pre>
<p><code>error 4</code> = user-mode read fault on unmapped page. Fault addresses are
high (7e…, 7b…, 72…), consistent with reads through a stale pointer.</p>
<h3>Disassembly at crash offsets</h3>
<p>Official Docker image is stripped, so all faulting offsets fall under
the preceding exported symbol <code>opcache_preloading@@Base</code>. The actual
crashing code is unexported, almost certainly JIT-generated VM handler
inlines.</p>
<p>Faulting instruction at <code>4c9466</code> (inside a tight loop):</p>
<pre><code>4c9460: mov    (%rax),%esi             ; load 32-bit index from source
4c9462: add    $0x4,%rax               ; advance source pointer
4c9466: mov    0x0(%rbp,%rsi,4),%esi   ; &lt;-- FAULT: rbp[rsi*4]
</code></pre>
<p>Faulting instruction at <code>4c9421</code>:</p>
<pre><code>4c940d: movslq -0x2c(%rdx),%rdi
4c9411: cmp    %r8d,0x74(%r15)         ; bounds check vs. value at r15+0x74
4c9415: jb     4c94b7 &lt;...&gt;            ; branch if below
4c941b: mov    0x1c(%rdx),%eax         ; load constant index (op_array slot?)
4c941e: mov    0x10(%rdx),%esi
4c9421: mov    0x0(%rbp,%rax,4),%eax   ; &lt;-- FAULT: rbp[rax*4]
</code></pre>
<p>Both faulting instructions are <code>mov [rbp + reg*4], reg32</code> — 32-bit
indexed loads consistent with PHP literal/constant pool lookup
(<code>op_array-&gt;literals</code> is a <code>zval[]</code> accessed by 32-bit indices from
<code>zend_op</code> operands). The <code>rbp</code> register holds what appears to be a
stale base pointer pointing into freed memory.</p>
<h3>Hypothesis</h3>
<p>JIT-compiled tracing code appears to hold a base pointer (possibly
<code>op_array-&gt;literals</code>) across a point where the underlying memory is
freed or moved, then dereferences it via a stable 32-bit index from
the opcode stream. Maintainers will know better — this is only a
guess based on the instruction pattern.</p>
<p>Possibly related: GH-21395 (UAF in JIT, fixed in 8.5.5). The fix may
be incomplete, or it may have introduced an adjacent regression.
Tracing-only failure (function JIT works) strongly suggests a
tracing-specific bug, e.g. in trace recording / side-exit assumptions
about value lifetimes.</p>
<h3>Reproduction</h3>
<p>Plain web traffic against the application is enough; workers crash
within seconds. Happy to provide:</p>
<ul>
<li>The specific application code paths (Nette controller, Dibi queries)</li>
<li>Larger dmesg samples</li>
<li>strace / ltrace output</li>
<li>A core dump (can be arranged if you can guide on getting useful
symbols from the official php-fpm image)</li>
<li>Test with <code>opcache.jit_buffer_size</code> reduced, or specific
<code>opcache.jit_*</code> tuning</li>
</ul>
<p>Switching to <code>opcache.jit=1235</code> is a viable workaround for now.</p></body></html>