fix(tracing): reset jit_trace_num on PHP 8.5 sandbox bailout#3973
fix(tracing): reset jit_trace_num on PHP 8.5 sandbox bailout#3973Leiyks wants to merge 2 commits into
Conversation
The #3964 regression test tests/opcache/jit_trace_num_bailout.phpt crashes with an ASAN SEGV only on PHP 8.5 (bad JIT side-exit reading a stale trace stack map). PHP 8.5's IR-based tracing JIT made zend_jit_trace_exit() less tolerant of a stale EG(jit_trace_num) after a caught sandbox bailout, so restoring the captured (possibly already-stale) trace id re-arms the crash. zend_call_function() only ever save/restores EG(jit_trace_num), and opcache exposes no symbol to reset JIT_G(tracing) from another extension, so on 8.5 we reset jit_trace_num to 0 on the bailout-catch path -- the 'not in a trace' invariant the resumed interpreter expects. <=8.4 behaviour is unchanged.
|
Benchmarks [ tracer ]Benchmark execution time: 2026-06-10 14:08:59 Comparing candidate commit 44fa3af in PR branch Found 0 performance improvements and 2 performance regressions! Performance is the same for 192 metrics, 0 unstable metrics.
|
Description
Draft / hypothesis fix — CI is the experiment. Fixes the PHP 8.5-only ASAN SEGV in
tests/opcache/jit_trace_num_bailout.phpt(the regression test added by #3964). The crash reproduces across multiple branches/PRs, always on theASAN Opcache tests: [8.5]job, never on 8.0–8.4.Root cause (high confidence on category, medium on exact state)
EG(jit_trace_num)across a caught sandbox bailout, mirroring whatzend_call_function()does (Zend/zend_execute_API.c— it only ever save/restoresEG(jit_trace_num)for user functions). This is correct and sufficient on 8.0–8.4.zend_jit_trace_exit()less tolerant of a stale trace id: it removed theGCC_GLOBAL_REGSguard around the exception/opline correction (php-srcext/opcache/jit/zend_jit_trace.c~8773-8779), so after a caught bailout the resumed caller reaches the side-exit deopt path much more readily and dereferences&zend_jit_traces[EG(jit_trace_num)].jit_trace_numcan itself be stale (the hook may have been entered while already inside a trace), so simply restoring it re-arms the crash on 8.5.ASAN evidence
Valid FP + a null variable-register dereferenced at
+0x78= a side-exit restoring the wrong variable stack map, i.e. stale trace metadata. No core dump was captured by CI (artifacts/core_dumps/is empty), so the crashing JIT frame can't be symbolized from CI data alone.Fix
On PHP 8.5+ only, reset
EG(jit_trace_num)to0(the "not currently in a trace" invariant the resumed interpreter expects) on the bailout-catch path instead of restoring the captured value. opcache exposes no symbol to resetJIT_G(tracing)from another extension, so thisEG-level reset is the reachable equivalent.zai_sandbox_engine_state_restore()is only ever called fromzai_sandbox_bailout(), so 8.0–8.4 behaviour is untouched.Verification
ASAN Opcache tests: [8.5]job is the verifier. If green, hypothesis confirmed.debug-zts-asanPHP 8.5 gdb reproduction is being run in parallel to confirm the exact faulting access (restored stack map +EG(jit_trace_num)) and validate "zero vs restore".cc the author of #3964 — this builds directly on that fix and may warrant a more principled upstream/opcache-level reset of the trace-recording state.