Unsafe Recovery: Fault-Injection and Stateful Fuzzing for Cross-Request Memory Corruption Surviving longjmp Recovery in Persistent Interpreter Workers
Year: 2026
Type: preprint
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.
Author: Shukla, Piyush
Language: English
Subfield: Software
Field: Computer Science
Domain: Physical Sciences
Open Access status: green
OpenAlex ID: W7225654624
Cites:
Cited by:
Related to:
