[2] [JSC YARR] Fix integer overflow in AssemblerBuffer leading to heap overflow
Await a cross-realm promise in JSC and the next thenable's resolver landed in the wrong realm — the fast path skipped the spec's realm fix-up.
Rated High because the diff widens code-buffer length arithmetic that previously wrapped 32-bit under attacker-shaped regex pressure; the downstream putIntegralUnchecked performs an unchecked write past the real allocation, yielding a linear write-anything-the-JIT-emits primitive into adjacent heap.
Widens m_index, m_capacity, and the arithmetic in isAvailable(), putIntegral(), and grow() from unsigned to a width that does not wrap under YARR-driven code emission. Adds TooManyCaptures and FrameTooLarge YARR errors — the commit message labels these "not actually critical" (DoS and frame-bookkeeping overflows in pattern compilation).
Source/JavaScriptCore/assembler/AssemblerBuffer.h
32-bit overflow in JIT code buffer length arithmetic: wrapped nextIndex bypasses the capacity check, then putIntegralUnchecked writes the emitted machine code past the real allocation.
Patch Details
Buffer width is widened; the isAvailable / putIntegral / grow arithmetic is recomputed in the wider type. YARR's pattern compiler gains TooManyCaptures and FrameTooLarge early bailouts so pathological patterns fail before reaching the emitter.
Background
AssemblerBuffer is JSC's emission buffer used by every JIT backend — YARR, baseline, DFG, FTL, and Wasm BBQ/OMG — to accumulate machine code before linking. The contract is: isAvailable(n) is the precondition for putIntegralUnchecked<T>, which performs WTF::unalignedStore<T>(m_storageBuffer + m_index, value) and bumps m_index. grow() is called by putIntegral when isAvailable reports insufficient room. The buffer was sized in unsigned despite YARR being capable of driving emission size past 2^32 with adversarial patterns.
Analysis
The three pre-fix arithmetic sites all evaluated in 32-bit:
isAvailable(space): return m_index + space <= m_capacity; // unsigned wrap
putIntegral(value): nextIndex = m_index + sizeof(IntegralType); // unsigned wrap, "nextIndex > capacity" guard bypassed
grow(extra): newCapacity = m_capacity + m_capacity/2 + extra; // unsigned wrap, undersize allocation
A YARR pattern driving emission toward 2^32 bytes wraps these expressions. After wrap, isAvailable returns true, putIntegral's nextIndex > capacity test is bypassed, and grow() resizes to a value smaller than the data actually written. putIntegralUnchecked then performs an unaligned store of the encoded machine code past the live allocation.
The write is linear and the payload is the JIT's emitted bytes — the pattern compiler controls immediates, displacements, and opcodes, all of which become attacker-influenced bits in the overflow. Heap layout adjacency dictates which structure is hit first; the JIT memory pool sits beside the rest of JSC's heap, so the write reaches metadata that affects the next code linking pass or the next JS-visible allocation.
The two YARR-side changes are non-critical bookkeeping fixes: TooManyCaptures triggers when the parsed capture count would overflow the per-term backtrack frame layout, and FrameTooLarge triggers when currentCallFrameSize would wrap during stack-slot accounting. Pre-fix these produced inconsistent backtrack frame layouts or compiler hang/crash.
This weakens the JIT memory invariant that the emitter never writes past the live buffer. The exploit primitive is heap write of attacker-controlled bytes into whatever follows the assembler data buffer in the JSC allocator.
Audit directions
- Every
unsignedlength/offset arithmetic site on a buffer whose size is attacker-influenced. GrepAssemblerBuffer,LinkBuffer, andMacroAssemblerfor survivingunsignedarithmetic on positions or capacities. - Other JIT clients with attacker-driven code-size expansion. Wasm BBQ/OMG, FTL B3 lowering with deeply nested loops, and DFG with extreme constant folding can all emit very large buffers — confirm none of them rely on
unsignedaccounting for limits. - YARR pattern-compiler accumulators (
currentCallFrameSize, capture-count, alternative-count) for similar wrap-on-overflow patterns now thatTooManyCaptures/FrameTooLargeexist but other accumulators may not.