Medium CVSS 6.5 webkit UAF 🔧 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 ClassUAF
Tracker312781
Fix commit13bfbf94f49e (WebKit/WebKit) +34/-1
CWECWE-119, CWE-416 (Buffer bounds error, Use-after-free)
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:H
CISA KEVNot listed
CreditedUsing GLM From Z.AI, Tristan Madani (@TristanInSec) from Talence Security, stratan (@5tratan) of Almamater Technologies, Soyeon Park, Amy Burnett, Khai Tran, sherkito, Kota Toda, HexRabbit (@h3xr4bb1t) and NiNi (@terrynini38514) of DEVCORE Research Team, Brian Carpenter
Disclosed2026-06-29

Background

DFG constant folding
A JavaScriptCore optimizing-JIT phase that replaces operations whose inputs are provably constant with precomputed results, sometimes embedding object-derived pointers (like a typed array’s backing store) directly into machine code.
Resizable ArrayBuffer / growable SharedArrayBuffer
ArrayBuffer variants whose byte length can change at runtime via resize()/grow(), which may cause the engine to reallocate the underlying backing store to a new memory address.
Backing store (vector) pointer
The raw pointer to a typed array’s element storage; for non-resizable buffers it is stable, but for resizable/growable-shared buffers it is invalidated when the store is reallocated.
isResizableOrGrowableShared()
The JSArrayBufferView predicate the patch uses to detect views whose buffer may be reallocated, gating the folding decision.
WebAssembly.Memory.grow()
A JS API that increases a Wasm memory’s size and, when it cannot extend in place, reallocates the backing store—one of the operations that invalidates a folded pointer.

Root Cause Analysis

The DFG constant-folding phase (DFGConstantFoldingPhase.cpp) walks the IR and tries to strength-reduce/eliminate operations on JSArrayBufferViews whose identity is known at compile time. In the branch shown, once the phase has proven it is dealing with a concrete typed-array view it calls m_interpreter.execute(indexInBlock) and sets eliminated = true, folding the operation and, as part of that, baking view-derived state (notably the backing store / vector pointer of the typed array) into the compiled code as a constant. The invariant this relies on is that a typed array’s backing buffer, once observed, does not move for the lifetime of the folded code. That invariant holds for ordinary ArrayBuffers, but it is FALSE for views over resizable ArrayBuffers and growable SharedArrayBuffers: resize() and WebAssembly.Memory.grow() may reallocate the backing store, leaving any previously-folded vector pointer dangling.

The patch adds an early break: if (view->isResizableOrGrowableShared()) { break; } so the phase refuses to fold operations on such views, restoring the invariant by simply not embedding a pointer that can go stale. The added JSTests case demonstrates the path: it grooms the WebAssembly memory pool, creates a WebAssembly.Memory with a resizable buffer, builds a Float64Array over it, warms up trigger() writing view[0] until the DFG compiles it (folding the store against the current backing store), then calls memory.grow(1) to reallocate and finally trigger(1.1) writes through the now-stale folded pointer. The exact node type being folded (a GetByVal/PutByVal or a GetTypedArrayLength/StorePointer style operation) is not literally printed in the diff, so the precise folded value is an inference; what the diff establishes is that folding operations on resizable/growable-shared views was unsound and is now suppressed.

Key insight
Constant folding may only bake in state that is immutable for the life of the compiled code; typed arrays over resizable/growable-shared buffers break that assumption because resize()/grow() can relocate the backing store, so the fix is to refuse to fold on such views rather than to track their relocation.

Attack Path

  1. Allocate a resizable backing store From JS, create a WebAssembly.Memory with a small initial and larger maximum and obtain a resizable ArrayBuffer via memory.toResizableBuffer() (or a resizable ArrayBuffer directly), then build a typed array view (Float64Array) over it.
  2. Groom memory so growth relocates Pre-allocate many large WebAssembly.Memory objects (as the test does) so that when the target memory is grown its backing store cannot be extended in place and must be reallocated to a new address.
  3. Warm up the accessor to trigger DFG compilation Call a function that indexes the view (e.g. trigger(val){ view[0]=val; }) in a tight loop (~10000 iterations) so the DFG tiers it up and the constant-folding phase bakes the current backing-store pointer into the compiled code.
  4. Reallocate the backing store Call memory.grow(1) (or buffer.resize()), which reallocates the backing store to a new address and frees/repurposes the old one, invalidating the folded pointer.
  5. Re-enter the compiled code Call the compiled accessor again (trigger(1.1)); the JIT code dereferences the stale folded vector pointer, reading/writing memory that is no longer the live buffer, producing an out-of-bounds or use-after-free access and typically a crash.

Impact Assessment

The patch itself is classified LogicError / medium with the stated effect of an unexpected process crash, and the observed primitive is a stale pointer dereference to a freed or relocated buffer (a UAF/OOB read-or-write) from JIT-compiled code running in the WebContent (renderer) process. Because the operand values written through the stale pointer are attacker-controlled and the timing of reallocation is controllable via grooming, a determined attacker could plausibly escalate an OOB write from a controlled crash toward memory disclosure/corruption, but the diff and test only establish a reliable crash, so anything beyond that is inference. It is confined to the WebContent sandbox; no sandbox escape is implied by this commit.

Changed Functions

FunctionChangeNotes
ConstantFoldingPhase per-node folding loop (foldConstants)
Source/JavaScriptCore/dfg/DFGConstantFoldingPhase.cpp
modified Adds an early break when the involved typed-array view isResizableOrGrowableShared(), before m_interpreter.execute()/eliminated=true, so the phase no longer folds/embeds backing-store state for views whose buffer can be reallocated. Exact enclosing method name not shown in the diff (hunk context is 'private:').

Files Changed

  • JSTests/stress/resizable-array-constant-folding.js
  • Source/JavaScriptCore/dfg/DFGConstantFoldingPhase.cpp

Audit Directions

  • Same phase, other foldable typed-array operations
    In DFGConstantFoldingPhase.cpp, review every branch that calls m_interpreter.execute()/sets eliminated for typed-array or ArrayBuffer nodes and confirm each is now guarded by isResizableOrGrowableShared(); grep for ‘isResizableOrGrowableShared’, ‘vector()’, ‘butterfly’, and typed-array node cases.
  • Other JIT phases that embed backing-store pointers
    Audit DFGAbstractInterpreter, DFG/FTL strength reduction and lowering (e.g. GetByVal/PutByVal/GetIndexedPropertyStorage handling) for places that capture a view’s vector/length as a constant; look for CheckArray/GetIndexedPropertyStorage without a resizable-buffer guard.
  • Auto-length and length assumptions
    Search across JSC for code that caches typed-array length or storage across side-effecting calls and does not account for resizable buffers; grep for ‘isResizable’, ‘isGrowableShared’, ‘byteLength’, ’lengthTrackingAutoLength’ near cached-pointer or cached-length logic.
  • Watchpoint/invalidation coverage for resize
    Verify that resize()/grow() paths correctly invalidate any structure/watchpoint-based assumptions the JIT relies on; grep for the resize implementations (ArrayBuffer::resize, Wasm memory grow) and cross-reference with JIT watchpoint registration for array-buffer views.

Original Bug Report

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