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 Safari crash
ComponentJSC Runtime
Bug ClassUAF
Tracker313473
Fix commita012babd4f16 (WebKit/WebKit) +129/-47
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
CreditedMaher Azzouzi, Tuan and Duc from Calif.io, OpenAI Codex Security - Amy Burnett, Evan Lambert
Disclosed2026-06-29

Background

Resizable buffer over wasm memory
toResizableBuffer/useWasmMemoryToBufferAPIs exposes growable wasm memory as a resizable ArrayBuffer whose resize is routed to the memory.
GC tracing / write barrier
References between GC objects must be stored on GC-visible cells with a write barrier so the collector keeps the referent alive; a raw pointer on a non-GC object is invisible to GC.
Native ArrayBuffer vs JSArrayBuffer
ArrayBuffer is the native backing object; JSArrayBuffer is its GC-visible JS wrapper cell.
HostResizeArrayBuffer
The abstract operation resizing a buffer; for wasm-backed buffers it must delegate growth to the wasm memory.

Root Cause Analysis

When the useWasmMemoryToBufferAPIs feature exposes a WebAssembly.Memory as a resizable non-shared ArrayBuffer, the buffer and the wasm memory must stay associated so a JS-side resize is routed to the memory’s grow (which reallocates the backing store).

Pre-patch, the native ArrayBuffer held that association itself via m_associatedWasmMemory (set through ArrayBuffer::setAssociatedWasmMemory), and ArrayBuffer::resize, on a wasm-backed buffer, fetched RefPtrWasm::Memory from it and called memory->grow. Storing the buffer->memory link on the native ArrayBuffer (which is not a GC object and is not traced) meant the association was not GC-visible: after a garbage collection the link could go stale / the memory be reclaimed or left in an inconsistent state, and a later resize (the added test resizes a resizable buffer after GC) would operate on a dangling or wrong memory – a use-after-free / crash.

The fix moves the association onto the GC-visible JS wrapper: JSArrayBuffer gains m_associatedWasmMemoryWrapper with associatedWasmMemoryWrapper()/setAssociatedWasmMemoryWrapper(vm, wrapper) using a write barrier (.set(vm, this, wrapper)), so the JSWebAssemblyMemory is properly kept alive and traced by the collector; the native ArrayBuffer::setAssociatedWasmMemory is removed, and ArrayBuffer::resize now RELEASE_ASSERTs !isWasmMemory() (native resize must never touch a wasm memory – growth goes through the JS wrapper/memory path instead).

The restored invariant is that the buffer<->wasm-memory association is owned by a GC-traced wrapper so it cannot dangle across GC, and native ArrayBuffer resize is never applied to wasm memory.

Key insight
The ArrayBuffer<->WebAssembly.Memory association was stored on the non-GC native buffer and thus invisible to the collector, so it could dangle across GC; moving it to a write-barriered JS wrapper (and forbidding native resize of wasm memory) makes the link GC-safe.

Attack Path

  1. Expose wasm memory as a resizable buffer With useWasmMemoryToBufferAPIs, create a WebAssembly.Memory and obtain its resizable non-shared ArrayBuffer, associating buffer and memory.
  2. Drop strong references and GC Remove other references to the wasm memory / wrapper and force garbage collection, so the untraced native association can go stale.
  3. Resize after GC Call the resizable buffer’s resize/grow; pre-patch it dereferences the now-dangling associated Wasm::Memory.
  4. Use-after-free / crash The resize operates on freed or inconsistent memory state in the WebContent process.

Impact Assessment

A lifetime/GC bug: the buffer->wasm-memory association lived on an untraced native object, so after GC a resizable-buffer resize could touch a dangling memory – a use-after-free reachable from JS in the WebContent process. The attacker controls GC timing and the resize, making it a groomable primitive though the observed effect is a crash. Confined to WebContent; rated medium.

Changed Functions

FunctionChangeNotes
ArrayBuffer::setAssociatedWasmMemory (removed) / resize
Source/JavaScriptCore/runtime/ArrayBuffer.cpp
modified Removes the native buffer's untraced wasm-memory pointer and adds RELEASE_ASSERT(!isWasmMemory()) in resize so native resize is never used for wasm memory.
JSArrayBuffer::associatedWasmMemoryWrapper / setAssociatedWasmMemoryWrapper
Source/JavaScriptCore/runtime/JSArrayBuffer.cpp
added Stores the buffer<->memory association on the GC-visible JS wrapper via a write-barriered m_associatedWasmMemoryWrapper.set(vm, this, wrapper), so the JSWebAssemblyMemory is kept alive and traced.
m_associatedWasmMemoryWrapper (member) / decls
Source/JavaScriptCore/runtime/JSArrayBuffer.h
modified Adds the traced wrapper member and accessors; JSWebAssemblyMemory/WasmMemory headers wired accordingly.

Files Changed

  • JSTests/wasm/stress/wasm-resizable-buffer-resize-after-gc.js
  • Source/JavaScriptCore/runtime/ArrayBuffer.cpp
  • Source/JavaScriptCore/runtime/ArrayBuffer.h
  • Source/JavaScriptCore/runtime/JSArrayBuffer.cpp
  • Source/JavaScriptCore/runtime/JSArrayBuffer.h
  • Source/JavaScriptCore/runtime/JSArrayBufferPrototype.cpp
  • Source/JavaScriptCore/wasm/WasmMemory.h
  • Source/JavaScriptCore/wasm/js/JSWebAssemblyMemory.cpp

Audit Directions

  • Untraced cross-object pointers
    grep JSC runtime for raw/RefPtr members on native (non-GC) objects that point at GC cells (wrappers, memories) and should instead live on the wrapper with a write barrier.
  • Wasm memory <-> buffer paths
    Audit resize/grow/detach/transfer for resizable and shared buffers backed by wasm memory to confirm associations survive GC and native fast paths are gated by isWasmMemory().
  • resize-after-GC
    Review other resizable-buffer operations for correctness when the last non-GC reference is dropped and a collection intervenes.

Original Bug Report

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