← All issues

[3] JSC PutByVal IC IndexingHeader Corruption via Non-String Primitive Key Transition

Severity: High | Component: JSC Inline Cache | 4731033

Rated High because the observable effect is corruption of IndexingHeader metadata for typed array views reachable from web content via a straightforward trigger (resizable ArrayBuffer + primitive key assignment), and the projected primitive — OOB access on the ArrayBuffer backing store — is consistent with the zeroed bounds metadata and the commit message's description of "incorrect" subsequent indexed accesses, though the exact behavior of downstream bounds checks with zeroed metadata is unverified.

The patch adds a new putByValNonStringPrimitiveKeyTransitionOutOfLineHandlerImpl template function in InlineCacheCompiler.cpp that calls operationReallocateButterflyAndTransition to properly handle butterfly reallocation for objects with an IndexingHeader. The DEFINE_CONSTANT_KEY_PUTBYVAL_HANDLERS macro is updated so that the TransitionReallocatingOutOfLineHandler entry for non-string primitive keys (undefined, null, true, false) dispatches to this new handler instead of the inline allocation path that zero-fills the butterfly without preserving the existing IndexingHeader.

Source/JavaScriptCore/bytecode/InlineCacheCompiler.cpp

+template<NonStringPrimitiveKeyType keyType>
+static MacroAssemblerCodeRef<JITThunkPtrTag> putByValNonStringPrimitiveKeyTransitionOutOfLineHandlerImpl(VM& vm)
+{
+ CCallHelpers jit;
+ ...
+ fallThrough.append(emitNonStringPrimitiveKeyCheck<keyType>(jit, propertyJSR));
+ fallThrough.append(InlineCacheCompiler::emitDataICCheckStructure(jit, baseJSR.payloadGPR(), scratch1GPR));
+ ...
+ jit.setupArguments<decltype(operationReallocateButterflyAndTransition)>(CCallHelpers::TrustedImmPtr(&vm), baseJSR.payloadGPR(), GPRInfo::handlerGPR, valueJSR);
+ jit.callOperation<OperationPtrTag>(operationReallocateButterflyAndTransition);
+ ...
+}
MacroAssemblerCodeRef<JITThunkPtrTag> putByValWith##KeyName##KeyTransitionReallocatingOutOfLineHandler(VM& vm) \
- { return putByValNonStringPrimitiveKeyTransitionHandlerImpl<true, false, NonStringPrimitiveKeyType::keyType>(vm); }
+ { return putByValNonStringPrimitiveKeyTransitionOutOfLineHandlerImpl<NonStringPrimitiveKeyType::keyType>(vm); }

JSTests/stress/resizable-typed-array-non-string-primitive-key-transition.js

+for (let i = 0; i < 1000; i++) {
+ const buf = new ArrayBuffer(7, { maxByteLength: 3378 });
+ const ta = new Int8Array(buf);
+ ta[undefined] = 42;
+ ta[0];
+}

IC transition handler dispatches to an inline butterfly allocator that destroys the IndexingHeader required by ArrayBuffer views.

JSC's inline cache (IC) system generates specialized machine-code stubs for property operations, keyed on object structure. When a property store causes a structure transition that requires butterfly reallocation, the IC must allocate a new butterfly and copy data from the old one. The butterfly is JSC's out-of-line property and indexed-element storage — a contiguous memory region attached to a JSObject. For objects with an IndexingHeader — such as typed array views backed by ArrayBuffers — the butterfly's header region stores metadata (like byte length for resizable typed arrays) that subsequent indexed element accesses depend on for bounds checking.

operationReallocateButterflyAndTransition is the runtime function that properly handles butterfly reallocation including IndexingHeader preservation. Commit 310410@main added PutByVal IC support for non-string primitive keys (undefined, null, true, false), which are converted to string property names (e.g., "undefined") and stored as named properties, potentially triggering structure transitions that require butterfly reallocation.

The non-string primitive key PutByVal IC (added in 310410@main) had a TransitionReallocatingOutOfLineHandler that incorrectly dispatched to the inline allocation handler (putByValNonStringPrimitiveKeyTransitionHandlerImpl<true, false, keyType>). This handler zero-fills the newly allocated butterfly without copying the existing IndexingHeader from the old butterfly (as strongly inferred from the fix pattern — the old handler's body is not shown in the diff). For objects with an IndexingHeader — specifically ArrayBuffer views like typed arrays — this IndexingHeader contains critical metadata (e.g., byte length for resizable typed arrays). After the IC fires and the butterfly is reallocated with a zeroed IndexingHeader, subsequent indexed accesses to the typed array operate against corrupted metadata.

The string-key handlers already had the correct split: putByValTransitionHandlerImpl for inline allocation (no IndexingHeader) and putByValTransitionOutOfLineHandlerImpl which calls operationReallocateButterflyAndTransition to handle IndexingHeader preservation. The non-string primitive key handlers duplicated the inline allocation path for both cases, missing the critical out-of-line variant — a classic "incomplete pattern replication" bug when a new feature mirrors an existing one.

🔒

Detailed analysis of how zeroed IndexingHeader metadata interacts with typed array bounds checking, and whether the corruption yields a usable memory access primitive

Subscribe to read more

🔒

Multiple audit patterns identified for IC handler completeness and butterfly reallocation correctness, with concrete search targets

Subscribe to read more