Medium CVSS 6.5 webkit Type Confusion 🔧 Commit mapped

Overview

Medium
Severity
6.5
CVSS
No
Exploited ITW
Fixed
Fix Status
DescriptionProcessing maliciously crafted web content may lead to an unexpected process crash
ComponentJSC DFG
Bug ClassType Confusion
Tracker288814
Fix commitac09d743b182 (WebKit/WebKit) +130/-142
CWECWE-20 (Improper input validation)
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:H
CISA KEVNot listed
CreditedJiming Wang and Jikai Ren
Disclosed2025-05-12

Background

DFG/FTL abstract interpreter (AI)
A dataflow analysis in JSC’s optimizing JIT that proves facts (types, structures) about values so later phases can drop checks and specialize code; its proofs must soundly match what the backend actually produces.
NewArrayWithSpread / Spread
DFG nodes that construct an array from spread elements (e.g. […x]); Spread produces the intermediate immutable butterfly and NewArrayWithSpread assembles the final array.
JSImmutableButterfly / CopyOnWrite array
A shared, immutable backing store (butterfly) used by copy-on-write arrays (CopyOnWriteArrayWithContiguous) so array literals can share storage until mutated.
HavingABadTime watchpoint
A JSGlobalObject watchpoint that fires when array prototype/indexed behavior is tampered with; while valid, the JIT may assume original, well-behaved array structures.
PhantomNewArrayBuffer / PhantomCreateRest
Phantom (materialization-elided) DFG nodes representing an array-literal buffer or a rest-parameter array that a Spread consumes without allocating the intermediate object.
Structure
JSC’s descriptor of an object’s shape and indexing type; JITted code specializes on structures, so a wrong structure assumption yields type confusion or out-of-bounds indexed access.

Root Cause Analysis

The bug is in JSC’s DFG abstract interpreter, specifically the AbstractInterpreter handling of the NewArrayWithSpread node in DFGAbstractInterpreterInlines.h. When compiling under the HavingABadTime watchpoint (i.e., array prototypes are unmodified) and the node has a single spread child whose bit is set, the FTL lowering (compileNewArrayWithSpread in FTLLowerDFGToB3.cpp) produces a CopyOnWrite contiguous array in essentially all sub-cases: a PhantomSpread of a PhantomNewArrayBuffer, a PhantomSpread of a PhantomCreateRest, or a directly-lowered immutable butterfly. The abstract interpreter, however, only proved the result was a CopyOnWrite original array (originalArrayStructureForIndexingType(CopyOnWriteArrayWithContiguous)) for the narrow case where the spread wrapped a PhantomNewArrayBuffer with a contiguous indexing type; for every other single-child spread shape it fell through to a broader/less-precise structure set (originalArrayStructureForIndexingType(ArrayWithContiguous) plus the CopyOnWrite variant, or the non-original ArrayWithContiguous during allocation). This is an inconsistency between what the FTL backend actually allocates and what the abstract value model claims: the AI over- or mis-approximated the result structure relative to the concrete allocation, violating the invariant that the abstract interpreter’s proven structure must match (soundly and precisely) the structure the backend emits. Downstream optimizations that trust the AI’s structure set (for example CSE/structure checks and array-access lowering such as the .at() access in the test) can then operate on an array whose real structure differs from the assumed one.

The fix refactors compileNewArrayWithSpread so both the PhantomNewArrayBuffer and PhantomCreateRest cases go through shared helpers (createContiguousImmutableButterflyFromPhantomNewArrayBuffer / …FromPhantomCreateRest) that always yield a contiguous CopyOnWrite immutable butterfly, and correspondingly simplifies the AbstractInterpreter so that for the single-set-bit spread case it uniformly sets the node’s structure to originalArrayStructureForIndexingType(CopyOnWriteArrayWithContiguous), and otherwise to arrayStructureForIndexingTypeDuringAllocation(ArrayWithContiguous). This realigns the abstract structure with what is actually allocated, closing the mismatch. It is an inference (not shown verbatim in the diff) that the concrete downstream misuse is a type-confusion/OOB via an array-access node trusting the wrong structure; the diff shows the AI/lowering mismatch and a regression test that exercises spread + element write + out-of-range .at().

Key insight
The abstract interpreter under-specified the result structure of single-child NewArrayWithSpread (only recognizing the PhantomNewArrayBuffer-contiguous case as CopyOnWrite) while FTL always allocates a CopyOnWrite contiguous array, so the AI’s model diverged from the emitted allocation; the fix unifies both the lowering and the AI so the proven structure always matches what is built.

Attack Path

  1. Construct a single-child spread array literal Use a construct like […[1,2,3,4,5]] (PhantomNewArrayBuffer spread) or spreading rest arguments (PhantomCreateRest) so the DFG builds a NewArrayWithSpread node with one child whose bit vector bit 0 is set.
  2. Force FTL compilation under the good-time watchpoint Warm the enclosing function so it tiers up to FTL while the HavingABadTime watchpoint is still valid (the test uses –thresholdForFTLOptimizeAfterWarmUp=1000 and a 1000-iteration warmup with an inner counting loop).
  3. Trigger the AI/allocation structure mismatch In the spread shape not covered by the narrow AI case, the abstract interpreter models a structure set that does not match the CopyOnWrite contiguous array the FTL backend actually allocates.
  4. Feed the array to a structure-trusting operation Mutate and index the array (v10[2]=3 then v10.at(3745) in the test, plus a class extending a returned function to shape the graph) so an optimized array-access relies on the AI’s incorrect structure assumption.
  5. Provoke the fault The access proceeds under a wrong structure/indexing assumption, producing an out-of-bounds or type-confused access that crashes the WebContent process (the demonstrated outcome).

Impact Assessment

The patch corrects a mismatch between the abstract interpreter’s proven result structure for single-child NewArrayWithSpread and the CopyOnWrite contiguous array the FTL backend actually allocates. A structure/indexing-type misprediction that downstream array accesses trust is a classic route to type confusion or out-of-bounds indexed access, which in JSC is a strong primitive that has historically been escalated toward arbitrary read/write and RCE; however, the diff and regression test only demonstrate a crash, and any escalation beyond that is inference, not established by the commit. The bug is confined to the sandboxed WebContent (renderer) process. The advisory characterizes it as an unexpected process crash, consistent with a controlled fault, while the underlying AI-imprecision class is the concerning part.

Changed Functions

FunctionChangeNotes
compileNewArrayWithSpread
Source/JavaScriptCore/ftl/FTLLowerDFGToB3.cpp
modified For the single-set-bit spread case, replaces inline per-shape logic with calls to the new helpers so both PhantomNewArrayBuffer and PhantomCreateRest paths uniformly build a contiguous CopyOnWrite immutable butterfly and allocate a CopyOnWriteArrayWithContiguous JSArray; unifies the fast/slow allocation and phi.
createContiguousImmutableButterflyFromPhantomNewArrayBuffer
Source/JavaScriptCore/ftl/FTLLowerDFGToB3.cpp
added Factored-out helper (from compileSpread) that returns the frozen contiguous immutable butterfly, or allocates a CopyOnWriteArrayWithContiguous immutable butterfly and copies the buffer's JSValues in when the source indexing type is not already contiguous.
createContiguousImmutableButterflyFromPhantomCreateRest
Source/JavaScriptCore/ftl/FTLLowerDFGToB3.cpp
added Factored-out helper (from compileSpread) that builds a contiguous CopyOnWrite immutable butterfly from rest arguments, computing length/start from the inline call frame and copying values in a loop; note it now uses immutableButterflyStructures[...] indexing for the structure.
compileSpread
Source/JavaScriptCore/ftl/FTLLowerDFGToB3.cpp
modified Rewritten to delegate to the two new helpers for the PhantomNewArrayBuffer and PhantomCreateRest branches instead of inlining the allocation/copy code, so Spread and NewArrayWithSpread share the same butterfly-construction logic.
AbstractInterpreter::executeEffects (NewArrayWithSpread case)
Source/JavaScriptCore/dfg/DFGAbstractInterpreterInlines.h
modified Simplifies the abstract value: for the single-set-bit spread case it now uniformly proves originalArrayStructureForIndexingType(CopyOnWriteArrayWithContiguous), and otherwise arrayStructureForIndexingTypeDuringAllocation(ArrayWithContiguous), removing the over-broad structure set that disagreed with what FTL allocates.

Files Changed

  • JSTests/stress/dfg-ai-should-reduce-new-array-with-spread-structures.js
  • Source/JavaScriptCore/dfg/DFGAbstractInterpreterInlines.h
  • Source/JavaScriptCore/ftl/FTLLowerDFGToB3.cpp

Audit Directions

  • Re-audit NewArrayWithSpread AI vs. FTL allocation parity
    In DFGAbstractInterpreterInlines.h and FTLLowerDFGToB3.cpp, diff every branch of compileNewArrayWithSpread/compileSpread against the structure setForNode() calls; confirm originalArrayStructureForIndexingType(CopyOnWriteArrayWithContiguous) vs arrayStructureForIndexingTypeDuringAllocation(ArrayWithContiguous) is chosen consistently for each spread shape (PhantomSpread-of-PhantomNewArrayBuffer, PhantomSpread-of-PhantomCreateRest, direct immutable butterfly).
  • Sweep other array-constructing nodes for the same AI/backend divergence
    Grep DFGAbstractInterpreterInlines.h for setForNode paired with originalArrayStructureForIndexingType / arrayStructureForIndexingTypeDuringAllocation on NewArray, NewArrayBuffer, NewArrayWithSize, Spread, and CreateRest, and verify each matches the concrete structure used in the corresponding FTL/DFG lowering, especially where a hasContiguous()/indexingType() special case narrows only one branch.
  • Check the refactored helpers for behavior changes, not just extraction
    Compare createContiguousImmutableButterflyFromPhantomCreateRest’s immutableButterflyStructures[arrayIndexFromIndexingType(CopyOnWriteArrayWithContiguous) - NumberOfIndexingShapes] against the original immutableButterflyStructure(CopyOnWriteArrayWithContiguous) call to ensure the structure selection is equivalent, and audit callers of these helpers (compileSpread and compileNewArrayWithSpread) for length/MAX_STORAGE_VECTOR_LENGTH and indexing-type assumptions.
  • Look for HavingABadTime-gated structure assumptions elsewhere
    Grep for isWatchingHavingABadTimeWatchpoint together with structure/indexing-type selection across DFG/FTL; any place that proves an ‘original array’ structure in the AI under this watchpoint but branches its concrete allocation on indexingType()/hasContiguous() is a candidate for the same mismatch.

Original Bug Report

The reporter's bug is still restricted on the tracker.