[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 ; <-- 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 <...> ; 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 ; <-- 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->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->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>