[2] [JSC YARR] Fix integer overflow in AssemblerBuffer leading to heap overflow
Severity: High | Component: JSC AssemblerBuffer / YARR | 7663d81
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.