[5] [JSC] Move DataView null vector check in IC outside of register save/restore
Rated High because the diff fixes an IC handler that pops registers without a matching push, jumping to the polymorphic continuation with a corrupted register file and stack — downstream JIT code then executes against attacker-influenceable register state.
In emitIntrinsicGetter, a pre-push null-vector check shared a JumpList with a post-push out-of-bounds check; the shared link site pop'd registers for both, so the pre-push failure path executed restoreReusedRegistersByPopping without a matching push.
Source/JavaScriptCore/bytecode/InlineCacheCompiler.cpp
Patch Details
The pre-push failAndIgnore jumps are appended directly to m_failAndIgnore (no pop). A new postPushFailAndIgnore list collects post-push failures that correctly route through restoreReusedRegistersByPopping.
Misbalanced register-save/restore on a JIT IC failure path: a pre-push failure jump was routed through a post-push pop, desynchronizing the stack.
Background
JSC caches property operations (like get_by_id for byteLength) as small machine-code stubs; on check failure they fall through to m_failAndIgnore. IntrinsicGetterAccessCase specializes built-in getters such as DataView.prototype.byteLength. ScratchRegisterAllocator::preserveReusedRegistersByPushing / restoreReusedRegistersByPopping push reused registers onto the native stack when the stub needs more scratches than free; every exit path between them must either both push and pop, or neither. loadDataViewByteLength emits inline code to read the underlying buffer length and emits an outOfBounds jump for detached/exceeded views.
Analysis
The pre-push null-vector check produced a jump that ended up linked through code that popped reused registers without a matching push, leaving SP offset by the push width and reused callee/scratch registers holding the wrong values.
The path then jumps to m_failAndIgnore (the polymorphic continuation / slow-path entry) with SP offset and reused registers holding the wrong values; the diff demonstrates the invariant violation but does not by itself establish an exploitable primitive in downstream code. The regression test grooms a four-way polymorphic site to force IC stub generation, then calls ArrayBuffer.prototype.transfer() to drive the detached/null-vector branch — exactly the pre-push failure edge.
This vulnerability weakens the IC invariant that every IC handler returns to the polymorphic continuation with the abstract machine state intact; once that invariant is violated, the attacker can plausibly aim for type/state confusion in downstream JIT-generated code reachable from web JS.
Audit directions
- A single
JumpListcollecting failure edges from both pre- and post-preserveReusedRegistersByPushingregions. Audit every use ofpreserveReusedRegistersByPushinginInlineCacheCompiler.cpp,Source/JavaScriptCore/jit/, anddfg/DFGSpeculativeJIT*.cppforJumpListdeclarations above the push call that are appended to from both sides. - Stub epilogues that conditionally call
restoreReusedRegistersByPoppingonly whenallocator.didReuseRegisters()is true. Verify every jump linked to that label was emitted between the matching push and pop. loadDataViewByteLength/loadTypedArrayByteLengthcallers. Trace where their returned failure-edgeJumpLists are linked.- Detachable/resizable buffer state guards. Any null-vector or detached-state guard inside
InlineCacheCompilerandDFGSpeculativeJITis a candidate for being placed on the wrong side of a register-save boundary.