Unsafe Recovery: Fault-Injection and Stateful Fuzzing for Cross-Request Memory Corruption Surviving longjmp Recovery in Persistent Interpreter Workers

A persistent interpreter worker serves many requests from one process, so when it recovers from a fatal error with a non-local jump (setjmp/longjmp, as in PHP's zend_bailout), any cleanup the jump skips leaves engine state behind for the next request to inherit. That leftover is reused across the request boundary as a use-after-free or an uninitialised read, which is the shape of the PHP2Shell vulnerabilities (CVE-2026-109618 and CVE-2026-109619); we verify the advisory against primary sources, including the upstream fix. The method keeps the carried-over state that persistent-mode fuzzers normally erase and turns its reuse into the detection signal, combining precise fatal-fault injection, carryover-preserving stateful fuzzing, and a cross-request poison-and-detect oracle, all validated in a faithful C model. A controlled 2x2 experiment shows both halves of the fix, a fallible allocator and an enumerated global reset, are load-bearing, and that neither alone closes every failure path. Because the whole method rests on the detector, we test its limits and add an allocation-generation oracle that tells two pointers to the same reused address apart by the generation each was created under, catching stale cross-request reads that a byte shadow and a stock sanitizer both miss; a matched baseline scores it against fresh-worker prevention and the default detector. We reimplement the per-request allocator in Rust, audit its C-to-Rust boundary against the FFI-safety literature, and build it into real php-src, where a ZendMM-shaped version renders WordPress at +5.1% on a single render and a median +52.5% at concurrent p95, one point in a wide run-to-run spread from +27% to +231%; a controlled ablation shows cheap allocator tunings do not close the gap. A six-runtime study places the enabling condition, non-local fatal recovery over state the runtime does not clean up, and finds PHP is the mainstream runtime that meets it, while CPython, Ruby, Node/V8 and Lua do not and show no use-after-free under AddressSanitizer in the injected-OOM campaign. The sharpest result is predictive: we name the discipline that protects the safe runtimes, remove it, and watch the bug class reappear in a real lua_State where the taxonomy said it would. Anchoring all of this is an honest negative: stock AddressSanitizer cannot see the real PHP bug, for a code-level reason we prove, and that reason is itself the case for a semantic software oracle. All reproduction is confined to an isolated local lab; no exploit is developed against any live system.

Authors

Publication Details

Journal
Zenodo (CERN European Organization for Nuclear Research)
Published
2026-11-09
DOI
https://doi.org/10.5281/zenodo.24073567
Primary Topic
Software Testing and Debugging Techniques
Type
preprint
Controls
|||
ALL TIME
JAN
FEB
MAR
APR
MAY
JUN
JUL
AUG
SEP
OCT
NOV
preprint

Unsafe Recovery: Fault-Injection and Stateful Fuzzing for Cross-Request Memory Corruption Surviving longjmp Recovery in Persistent Interpreter Workers

Piyush Shukla
Zenodo (CERN European Organization for Nuclear Research)
Software Testing and Debugging Techniques
preprint

Unsafe Recovery: Fault-Injection and Stateful Fuzzing for Cross-Request Memory Corruption Surviving longjmp Recovery in Persistent Interpreter Workers

Piyush Shukla
preprint en

Abstract

A persistent interpreter worker serves many requests from one process, so when it recovers from a fatal error with a non-local jump (setjmp/longjmp, as in PHP's zend_bailout), any cleanup the jump skips leaves engine state behind for the next request to inherit. That leftover is reused across the request boundary as a use-after-free or an uninitialised read, which is the shape of the PHP2Shell vulnerabilities (CVE-2026-109618 and CVE-2026-109619); we verify the advisory against primary sources, including the upstream fix. The method keeps the carried-over state that persistent-mode fuzzers normally erase and turns its reuse into the detection signal, combining precise fatal-fault injection, carryover-preserving stateful fuzzing, and a cross-request poison-and-detect oracle, all validated in a faithful C model. A controlled 2x2 experiment shows both halves of the fix, a fallible allocator and an enumerated global reset, are load-bearing, and that neither alone closes every failure path. Because the whole method rests on the detector, we test its limits and add an allocation-generation oracle that tells two pointers to the same reused address apart by the generation each was created under, catching stale cross-request reads that a byte shadow and a stock sanitizer both miss; a matched baseline scores it against fresh-worker prevention and the default detector. We reimplement the per-request allocator in Rust, audit its C-to-Rust boundary against the FFI-safety literature, and build it into real php-src, where a ZendMM-shaped version renders WordPress at +5.1% on a single render and a median +52.5% at concurrent p95, one point in a wide run-to-run spread from +27% to +231%; a controlled ablation shows cheap allocator tunings do not close the gap. A six-runtime study places the enabling condition, non-local fatal recovery over state the runtime does not clean up, and finds PHP is the mainstream runtime that meets it, while CPython, Ruby, Node/V8 and Lua do not and show no use-after-free under AddressSanitizer in the injected-OOM campaign. The sharpest result is predictive: we name the discipline that protects the safe runtimes, remove it, and watch the bug class reappear in a real lua_State where the taxonomy said it would. Anchoring all of this is an honest negative: stock AddressSanitizer cannot see the real PHP bug, for a code-level reason we prove, and that reason is itself the case for a semantic software oracle. All reproduction is confined to an isolated local lab; no exploit is developed against any live system.

Zenodo (CERN European Organization for Nuclear Research)
Software Testing and Debugging Techniques
AI Navigator

Ask Laika to Summarize, Analyze, and Connect papers live on the map.

Summarize Papers & Methodologies

Extract key findings, datasets, and comparative methods across publications.

Benchmark Rankings & Visual Analytics

Rank top research institutions, authors, funders, topics, and journals by Field-Weighted Citation Impact (FWCI) and paper volume with instant charts.

Connect Distant Disciplines

Bridge topological clusters on the map to find hidden collaborative intersections.

Laika access coming soon