← All issues

[25] [JSC] Don't advance bytecode when reifying inline frames at a checkpoint

Severity: Medium | Component: JSC DFG JIT | 4e802a1

Rated Medium because the diff fixes incorrect exception-handler dispatch metadata: the CallSiteIndex written into reified baseline frames during OSR exit was the bytecode AFTER the checkpoint, so a throw from the re-executed checkpoint instruction matched the wrong HandlerInfo try-range.

reifyInlinedCallFrames used bytecodeIndexForExit(...) which normalizes a checkpoint-bearing index to the bytecode position after the checkpoint — correct for resumption (kept in a separate sidestate), wrong for unwinding.

Source/JavaScriptCore/dfg/DFGOSRExitCompilerCommon.cpp

- BytecodeIndex exitIndex = baselineCodeBlock->bytecodeIndexForExit(codeOrigin->bytecodeIndex());
+ BytecodeIndex exitIndex(codeOrigin->bytecodeIndex().offset());

Off-by-one between the bytecode-index used for resumption and the bytecode-index used for exception-handler lookup when reifying inlined frames at a checkpoint.

Checkpoints overlay sub-bytecode resumption on the historical "one bytecode index per stack frame" assumption baked into baseline frame layout and HandlerInfo lookup. The resumption index lives in OSR sidestate while the unwinder uses the on-frame CallSiteIndex — anywhere these two indices are written from a shared "normalize for baseline" helper is structural risk. Reachable from an attacker mixing a DFG-compiled inlinee containing op_instanceof (or op_iterator_open/next, op_get_by_val_with_this), prototype/structure invalidation forcing throw on re-execution, and a try/catch around the call site — exception routed to the wrong catch block.

This vulnerability weakens the JS exception-handling boundary inside the renderer. JS state divergence between baseline and DFG semantics may surface values the original control flow would never produce.