[1] TypedArray TOCTOU heap overflow in toSorted/toReversed/with
Severity: High | Component: JSC TypedArray prototype methods | 4c82252
Rated High because the observable effect is a heap buffer overflow from a TOCTOU race with attacker-controlled overflow size and content, and the regression test demonstrates the race is winnable within thousands of iterations with growable SharedArrayBuffer — a feature reachable from ordinary web content.
The TypedArray.prototype.toSorted, toReversed, and with methods create a new TypedArray and copy from the original. The copying reads the TypedArray's length and then separately acquires a typedSpan(). If the TypedArray is backed by a growable SharedArrayBuffer, the span may have a different length than the copy because the buffer grew in parallel. The fix snapshots the span upfront.
Source/JavaScriptCore/runtime/JSGenericTypedArrayViewPrototypeFunctions.h
// toReversed:
- size_t length = thisObject->length();
+ // Snapshot the span at this time, as SABs may grow (but never shrink) in parallel.
+ auto originalSpan = const_cast<const ViewClass*>(thisObject)->typedSpan();
+ size_t length = originalSpan.size();
...
- auto from = const_cast<const ViewClass*>(thisObject)->typedSpan();
- ASSERT(from.size() == length);
auto to = result->typedSpan();
ASSERT(to.size() == length);
- WTF::copyElements(to, from);
+ WTF::copyElements(to, originalSpan);
// toSorted:
- size_t length = thisObject->length();
+ auto originalSpan = const_cast<const ViewClass*>(thisObject)->typedSpan();
+ size_t length = originalSpan.size();
...
- auto from = const_cast<const ViewClass*>(thisObject)->typedSpan();
- ASSERT(from.size() == length);
- WTF::copyElements(to, from);
+ WTF::copyElements(to, originalSpan);
// with:
- size_t updatedLength = thisObject->length();
- if (thisLength != updatedLength) [[unlikely]] {
+ auto maybeUpdatedSpan = const_cast<const ViewClass*>(thisObject)->typedSpan();
+ if (thisLength != maybeUpdatedSpan.size()) [[unlikely]] {
...
- auto from = const_cast<const ViewClass*>(thisObject)->typedSpan();
- WTF::copyElements(to, from);
+ WTF::copyElements(to, maybeUpdatedSpan);
JSTests/stress/growable-sharedarraybuffer-parallel-grow-during-prototype-methods.js
+ $.agent.start(`
+ $.agent.receiveBroadcast((sab, idx) => {
+ for (let i = 0; i < 50000; i++) {
+ try {
+ if (sab.byteLength < ${maxBytes}) {
+ sab.grow(sab.byteLength + 64);
+ }
+ } catch (e) {}
+ }
+ });
+ `);
+ for (let i = 0; i < ITERATIONS_PER_ROUND; i++) {
+ ta.with(0, 0x41414141);
+ ta.toReversed();
+ ta.toSorted();
+ }
Patch Details
The fix modifies all three methods — genericTypedArrayViewProtoFuncToReversed, genericTypedArrayViewProtoFuncToSorted, and genericTypedArrayViewProtoFuncWith — to capture thisObject->typedSpan() once upfront and derive the length from that snapshot. Previously, each method read thisObject->length() first to allocate the result buffer, then called thisObject->typedSpan() later to obtain the source data for copying. The fix ensures length and data pointer are consistent by binding them to a single snapshot.
Before: After:
length = thisObject->length() originalSpan = thisObject->typedSpan()
| length = originalSpan.size()
v |
result = allocate(length) v
| result = allocate(length)
v ← SAB grows here |
from = thisObject->typedSpan() v
from.size() > length! WTF::copyElements(to, originalSpan)
v (originalSpan.size() == length, always)
WTF::copyElements(to, from)
→ OOB WRITE past result
TOCTOU race between length read and data span acquisition on a concurrently growable SharedArrayBuffer-backed TypedArray.
Background
SharedArrayBuffer with the maxByteLength option creates a growable shared memory region. Multiple threads (main thread + Web Workers) can access the same SAB concurrently. The grow() method increases the SAB's byteLength up to maxByteLength and is observable from all threads immediately — there is no synchronization barrier; the new length is visible as soon as the grow completes.
TypedArray.prototype.toReversed(), toSorted(), and with() are copy-producing methods introduced in ES2023. They allocate a new TypedArray, copy data from the source, and return the new array without modifying the original. typedSpan() returns a std::span whose size reflects the TypedArray's current length, which for resizable/growable-SAB-backed views can change between calls as the underlying buffer grows from another thread.
Analysis
The root cause is a TOCTOU race condition between reading the TypedArray's length and reading its data span. In toReversed and toSorted, the code first called thisObject->length() to determine the allocation size for the result buffer, then later called thisObject->typedSpan() to get the source data for copying. When the TypedArray is backed by a growable SharedArrayBuffer, a concurrent worker thread can call sab.grow() between these two reads. Since growable SABs can increase in size at any time from another thread, typedSpan() could return a span whose .size() exceeds the length used to allocate the destination buffer. WTF::copyElements then copies from the larger source span into the smaller destination span, writing past the end of the destination buffer (if WTF::copyElements copies based on source span size rather than destination span size, as the fix pattern strongly implies).
The with method had a slightly more complex variant: it re-read thisObject->length() to detect resize, but then called typedSpan() again — introducing another TOCTOU window between the length comparison and the span acquisition.
Aaa Aaaaaaaaaa Aaaa Aaaaaaaaaaaa Aaa Aaaaa Aaaaaaaa a Aaaaaa Aaaaaaaa a Aaaaaaaaa Aa Aaa Aaaaaa Aaa Aaa Aaaaa Aaaaaaaaaaaaaaaaaaaaaaaa a Aaaa Aa a Aaaaa Aaaa Aa Aaaaaa Aaaaaaaaaaa Aa Aaa Aaaa Aaaaaaa Aaaaaaaaaaa Aaaaaaaaaaaaa Aaaaaaaaaaaaaaaaaa Aaa Aaaaaaaaaaaaaaa Aaa Aaaaaa Aaaaaaaaaaa Aaa Aaaaaaaaaaaa Aaaaa Aaaaaa Aa Aa Aaaaaaa Aaaaaaaaaaaaaaaaaa Aaaaaaa Aaaa Aaa Aaaaaaaa Aaaaaaaa Aaaaa Aaaaaaa Aaa Aaaa Aaaaaaaa Aaaaaa Aaaa Aaa Aaaa Aaaaa Aaa Aaaaaa Aaaaaa Aa Aaaaaaaaa Aaa Aaa Aaa Aaaaaaaaa Aaaaaaa Aaa Aaaaaaaaaaaaaa Aaaaaa Aaaa a Aaaa Aaaaa Aa Aaa Aaa Aaaaaaaa Aaaaaaa Aaaaaaaaaaa Aaa Aaaaaaaaaaaa
Aaaa Aa Aaaaaaaaaaa Aaa a Aaaaaaaaaa Aaaa Aaaaaa Aaaaaaaaa Aaa Aaaaaaaa Aaaaaaaa Aaaaaaaa Aaaa Aaa Aaa Aaa Aaaaaa Aaaaaaaaa Aaa Aaaaaaaa Aaaaaaa Aaa Aaa Aaaaaaaaaa Aaaaaaa Aaaaaaa Aaa Aaaa Aa Aaaaaaaaaaaaa Aaa Aaa Aaaa Aaaaaaaaaaaa Aa Aa Aaaaaaaa Aaaaaa Aaaaaaaaa Aa Aaaaaaaaaaa Aaa Aaaaaaaaa Aaaaaaaaa Aa a Aaaaaa Aaa Aaaaa Aaaaaaaa Aa Aaa Aaaaaa Aaaaaaaa Aaaaaaaaaa a Aaa Aaaaaaaa Aaaaaaaa Aaaa Aaa Aaaaaaaa Aaaa Aaaaaaaaaaa Aa Aaa Aaaaaa Aaaaaaaaa Aaaa Aaaa Aaa Aaaaa Aaa Aaa Aaaaaaaa Aaaaaaa Aaaaaaa Aa Aaa Aaaaaa Aaaaaaaaaaaa Aaaa Aaaaa Aaaaaaa Aa Aaaaaaaaa Aaaaa Aaaaaaaaa Aaaaaaaa Aaa Aaaaaaaaaa Aaaaaaaa Aaaa Aaaaaaaa Aa Aaaaaaa Aa Aaa Aaaaaaaaaa Aaaaaaaa
Aaaa Aaaaaaaaaaaaa Aaaaaaa Aaaaaa Aaaaaa Aaaaaa Aaa Aaaaaaaa Aaaaaaaa Aaa Aaaaaaaaaaaaaaaaa Aaaaaaaaaaa Aaaaa Aaaaaaa Aaaa Aaaaaaaaaa Aa Aaaaaaaaaa Aaaaa Aaaaaaa Aaaaaaaaaaaa Aaaaaaa Aaaaaa Aaa Aaaa Aaa Aaaaaa Aaa Aaaaaaaa Aaaa Aaaaaaaaaaa Aa Aaaaaaaa Aaaa Aaa Aaaaaaa Aaaaaaa a Aaaaaaaa Aaa Aaaaa Aaaaaaa Aa Aaa Aaaaa Aaaa Aaa Aaaaa Aaaaaaaaa Aaaaaa Aaaaaaaaaaaa Aaaaaaa Aaaaaa Aaaaaaaaaa Aaaaaaaa Aaaa Aaaaaa Aa Aaa Aaaaaaaaaa Aaaaaaaa Aaaa Aaaaa Aaaaa Aa Aaaaaaaaa Aaaaa Aaaaaaaaa Aaaaaaaa Aaa Aaaaaaaa Aaaaaaaaaaa
Aaaa Aa a Aaaaaaaa Aaaaaaa Aa Aaaaaa Aaaa Aaaaaaaaaa Aa Aaa Aaaaaaaaaaaaaaaaaa Aaaaaaaaaaa Aaaaaaaa Aaaaaaa Aaa Aaaaaaaaaa Aaaaaaaaa Aaaa Aaaaa Aaaaaa Aaa Aaaa Aa Aaaaaaaa Aaaaa Aa Aaaaaaaaaa Aaaa Aaa Aaaaaaa Aaaaaa Aa a Aaaaaaaa Aaaa Aaa Aaa Aaaaaaa a Aaaaaaaa Aaa Aaaa Aaaa Aaa Aaaaaa Aaaaaa Aaaa Aa a Aa Aaaaaa Aaa Aaaa Aa Aaaaaaa Aaaaaaaaaaaaaa Aa Aaaaa Aaaaaaaaaa Aaaaaa Aaaa Aaaaaaaa Aaaa Aaaaaaaaaaa Aaa Aaaaaaaa Aaa Aaaaaaa Aaaaaaaaaaaaa Aaaaaaa Aaa Aaaaaaaaaaa Aaaaa Aaa Aaaaaaaaaaaa Aaaaaa Aa Aa Aaaaaa Aaaaaa Aaaaaa a Aaaaaa Aaaaaaaa Aaaaaaaaaa
Aaaaaaaaa Aaaa Aaaaaaaaaaaaaa Aaaaaaa a Aaaaaaaaaaaa Aaaaaaa Aaaaaaaaaaaaaaaaaaa Aaaaaa Aaaaa Aa Aaaaaa Aaaa Aaaa Aaa Aaaaaaa Aaaaaaaaaaaaa Aaaaaaaa Aaaaaaaaaa Aaaaaa Aaaaaaaaaaa a Aaa Aaaaaaaa Aaaa Aaa Aaa Aaaaaaa Aaa Aaaa Aaaaaaaaa Aaaaaa Aaaa Aaaaaaaa Aaaaaaa Aa Aaa Aaaaa Aaa Aaaa Aaaa Aaaaaaaaa Aaa Aaa Aaaaa Aaaaaaaaa Aaa Aaaaaaaaaaaa Aaaaaaaaa Aa Aaa Aaaaa Aaa Aaaaaaaaaa Aaaaa
🔒Explores the heap corruption primitive this race yields, including attacker control over overflow size and content
Subscribe to read more
Audit directions
a Aaaaaaaa Aaaaaaa Aaaaaaaaaa Aaaaaa Aaaa Aaa Aaaa Aaaaaa Aaaa Aaaaaa Aa Aaaaaaaa Aaaaaaaaaaaaaaaaaaaa Aaa Aaaaaaaaaa Aaaaaaaaa Aaaaaa Aaaa Aaa Aaaaa Aaaaaaaaaa Aa Aaaaaaaaaaaaaaa Aaa Aaaaaaaa Aaaaaaaaaa Aa Aaaaaaaaaaa Aaaaa Aa Aaaa Aaaaaa Aaaa Aaa Aaaaa Aaaaaaaaaaaaa Aa Aaaaaaaa Aaaaaa Aa Aaaaaaaaaa Aa Aaaaaaaaaa Aaaaaaa Aaaaa Aaa Aaaaaaa Aa Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa a Aaaa Aaa Aaaaaaaaa Aa Aaaaaaaaaaaaaaaaaaaaaa Aaaaaaaa Aa Aaaaaaaaaaaaaaaaaaaaaaaaa Aaaa Aaaaaaaaaaa Aaaaaaaaaaa Aaaaaaa Aaaa Aaaaaaaa Aaaaaaaaa Aaaaaa Aaaaaaa Aaaaaaaaaaaaa Aaaaaa Aaa Aaaaaaaaaa Aaa Aaaaaaaaaaa
a Aaaaaaaaaaaaa Aaaaaaaaaaaaa Aa Aaaa Aaaaaaaaaa Aaaaaa Aaaaaaaaa Aaaaaa Aaaaaaaaaaaa Aaaaa Aaa Aaaa Aaaa Aaaaa a Aaaaaaaaaaa Aaaaa Aaaaaaaaa a Aaaaaaaaaaaa Aaaa Aaaaaaaa Aaa Aaaaaa Aa Aaaaa Aaaa Aaaaaaaaaaaaaaaaa Aaa Aaaaaaaa Aaaaaaaa Aaa Aaa Aaaaa Aa Aaa Aaaa Aaaaaaa Aaaaaa Aaaaaaa Aaaaaaaaaaaaaaa Aaaaa Aaaaaaaa Aaaaa Aaaaaaaaaaaaaaaaaaaaaaaaaaa Aaaaaaaaaa Aaaa Aaaaaaaa Aaa Aaaaaaaaaaaaaaaaa Aaaaa Aaaa Aaaaaa Aaaaaaaaa Aaaaaaaa Aaaaa Aaaa Aaaa Aaa Aaaaaaaaaaa Aaaaa Aaaaaaaa Aa Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
a Aaaaaaa Aaa Aaaaaa Aaaaaaaa Aaaaaa Aaaaaa Aaaa Aaaaaaaaaaa Aa Aaaaaaaaaaaaaaaaaaaaaaaa Aaaaaaaa Aaaa Aaaa Aaaaaaa Aaa Aaaa Aaaaa Aaa Aaaaaaaaaa Aaa Aaaaaaaa Aaa Aaaa Aaaaaaaaaaaaaaaaaaaaaaaaa Aa a Aaaaaaaaaa Aaa Aaaa Aaaaaaaa Aa Aa Aaaaaaaaaaaa Aaaaa Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa Aaaaaa Aaaa Aaa Aaaaaaaaaaaaaaaaaa Aaaa Aaaa Aaaaaaaaa Aaaaaaaaaaaaa Aaaaaaa Aaaa Aaa Aaaaaa Aaaa Aaaa Aaa Aaa Aaaaaaaaaaa Aaaaaaaaaa Aaaaa Aaaaaaaaaa Aaa Aaa Aaaaaa Aaaaaaaaaa Aaaaaaaaa
🔒Multiple TOCTOU audit patterns identified across TypedArray methods, with concrete search targets for variant discovery
Subscribe to read more