CVE-2026-43716
Overview
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.
Attack Path
- Expose wasm memory as a resizable buffer With useWasmMemoryToBufferAPIs, create a WebAssembly.Memory and obtain its resizable non-shared ArrayBuffer, associating buffer and memory.
- 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.
- Resize after GC Call the resizable buffer’s resize/grow; pre-patch it dereferences the now-dangling associated Wasm::Memory.
- Use-after-free / crash The resize operates on freed or inconsistent memory state in the WebContent process.
Impact Assessment
Changed Functions
| Function | Change | Notes |
|---|---|---|
ArrayBuffer::setAssociatedWasmMemory (removed) / resizeSource/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 / setAssociatedWasmMemoryWrapperSource/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) / declsSource/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.jsSource/JavaScriptCore/runtime/ArrayBuffer.cppSource/JavaScriptCore/runtime/ArrayBuffer.hSource/JavaScriptCore/runtime/JSArrayBuffer.cppSource/JavaScriptCore/runtime/JSArrayBuffer.hSource/JavaScriptCore/runtime/JSArrayBufferPrototype.cppSource/JavaScriptCore/wasm/WasmMemory.hSource/JavaScriptCore/wasm/js/JSWebAssemblyMemory.cpp
Audit Directions
- Untraced cross-object pointersgrep 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 pathsAudit 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-GCReview other resizable-buffer operations for correctness when the last non-GC reference is dropped and a collection intervenes.