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)

Ouverte
#22,084 7 commentaires 9 réactions 1 personne assignée Voir sur GitHub

@arnaud-lb y travaille déjà.

Depuis le 26/8/2026.

Bug Category: JIT SAPI: fpm
Langage dominant
C
Étoiles
40.4k
Forks
8.2k
Merge moyen
2 j 13 h
PR mergées (30 j)
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.

Guide de contribution

Ouvrir le guide de contribution

Par où commencer

  1. Lisez l'issue en entier, puis le guide de contribution du projet.
  2. Signalez en commentaire que vous la prenez — cela évite que deux personnes fassent le même travail.
  3. Forkez le dépôt et travaillez sur une branche.
  4. Ouvrez une pull request qui référence le numéro de l'issue.

Évaluation

Cette issue n'a pas encore été évaluée.

Recevez les nouvelles issues par e-mail

Un résumé court des issues GitHub adaptées aux débutants.