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.
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.6-fpmDocker image, Debian Trixie base)JIT configuration matrix
PHP 8.5.4 with
opcache.jit=1205was stable. Regression introduced in 8.5.5, not resolved in 8.5.6.opcache config
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):
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):Faulting instruction at
4c9421:Both faulting instructions are
mov [rbp + reg*4], reg32— 32-bit indexed loads consistent with PHP literal/constant pool lookup (op_array->literalsis azval[]accessed by 32-bit indices fromzend_opoperands). Therbpregister 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:
opcache.jit_buffer_sizereduced, or specificopcache.jit_*tuningSwitching to
opcache.jit=1235is a viable workaround for now.