php / php/php-src

Segfault in PHP-FPM with tracing JIT (opcache.jit=1205) due to stale base pointer in rbp (Regression in 8.5.5)

Open
#22,084 7 comments 9 reactions 1 assignee View on GitHub

@arnaud-lb is already working on this.

Since Aug 26, 2026.

Bug Category: JIT SAPI: fpm
Dominant language
C
Stars
40.4k
Forks
8.1k
Avg merge
2d 13h
Merged PRs (30d)
96

Description

Description

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 (opcache.jit=1205) is affected; function JIT (1235) and disabled JIT both work fine.

Environment
  • PHP 8.5.5 and 8.5.6 (official php:8.5.6-fpm Docker image, Debian Trixie base)
  • x86_64
  • Application: Nette Framework + Dibi 5.1.1 + ext/mysqli, MariaDB 11.8
JIT configuration matrix
opcache.jit Result
1205 (tracing) Workers segfault within seconds of receiving traffic
1235 (function) Stable
off Stable

PHP 8.5.4 with opcache.jit=1205 was stable. Regression introduced in 8.5.5, not resolved in 8.5.6.

opcache config
opcache.enable=1
opcache.memory_consumption=256
opcache.jit=1205
opcache.jit_buffer_size=128M
Crash signature (host dmesg)

Repeated segfaults at a small, stable set of offsets in the php-fpm binary (binary base changes due to ASLR; offsets are consistent):

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]

error 4 = user-mode read fault on unmapped page. Fault addresses are high (7e…, 7b…, 72…), consistent with reads through a stale pointer.

Disassembly at crash offsets

Official Docker image is stripped, so all faulting offsets fall under the preceding exported symbol opcache_preloading@@Base. The actual crashing code is unexported, almost certainly JIT-generated VM handler inlines.

Faulting instruction at 4c9466 (inside a tight loop):

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]

Faulting instruction at 4c9421:

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]

Both faulting instructions are mov [rbp + reg*4], reg32 — 32-bit indexed loads consistent with PHP literal/constant pool lookup (op_array->literals is a zval[] accessed by 32-bit indices from zend_op operands). The rbp register holds what appears to be a stale base pointer pointing into freed memory.

Hypothesis

JIT-compiled tracing code appears to hold a base pointer (possibly op_array->literals) 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.

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.

Reproduction

Plain web traffic against the application is enough; workers crash within seconds. Happy to provide:

  • The specific application code paths (Nette controller, Dibi queries)
  • Larger dmesg samples
  • strace / ltrace output
  • A core dump (can be arranged if you can guide on getting useful symbols from the official php-fpm image)
  • Test with opcache.jit_buffer_size reduced, or specific opcache.jit_* tuning

Switching to opcache.jit=1235 is a viable workaround for now.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.