← All issues

[10] WebAssemblyGCStructure skipped during end-of-GC weak-reference clearing

Severity: Medium | Component: JSC Heap finalisation | d55bf0c

Rated Medium because the diff restores end-of-full-GC weak-reference clearing for the Wasm-GC structure subspace; any surviving WebAssemblyGCStructure could retain stale internal pointers to freed cells, and re-reading those pointers after slot reuse would yield a UAF/type-confusion on a Wasm GC type descriptor.

Structure::finalizeUnconditionally must be called at GC end phase. Structure and BrandedStructure are done correctly, but WebAssemblyGCStructure is not.

Source/JavaScriptCore/heap/Heap.cpp

void Heap::finalizeUnconditionalFinalizers()
{
if (collectionScope == CollectionScope::Full) {
finalizeMarkedUnconditionalFinalizers<Structure>(structureSpace, collectionScope);
finalizeMarkedUnconditionalFinalizers<BrandedStructure>(brandedStructureSpace, collectionScope);
+#if ENABLE(WEBASSEMBLY)
+ finalizeMarkedUnconditionalFinalizers<WebAssemblyGCStructure>(webAssemblyGCStructureSpace, collectionScope);
+#endif
}

A single missing dispatch site is added: inside the CollectionScope::Full branch, finalizeMarkedUnconditionalFinalizers<WebAssemblyGCStructure>(webAssemblyGCStructureSpace, collectionScope) is now called alongside the existing Structure and BrandedStructure dispatches, gated by #if ENABLE(WEBASSEMBLY).

Missing dispatch of weak-reference finalization to a parallel allocator subspace, leaving stale internal pointers inside surviving Structure-like objects after full GC.

JSC organises GC-managed cells into per-type subspaces (structureSpace, brandedStructureSpace, webAssemblyGCStructureSpace); each is iterated separately during collection. finalizeUnconditionally is a JSC GC hook called at the end of a collection on cells that survived marking, used to clear weak references whose targets did NOT survive — without it, a surviving object can still hold pointers to just-freed cells. Structure is the JSC type descriptor that records property layout, prototype, and transition links; many of these links are weak. WebAssemblyGCStructure is the Structure subclass that backs Wasm GC types (structs/arrays with RTT and supertype chains) introduced by the WebAssembly GC proposal. Full GC (CollectionScope::Full) scans the entire heap, so weak slots inside Structures must be cleared then.

The full-GC end-phase dispatcher iterated Structure::finalizeUnconditionally over Structure and BrandedStructure instances but never invoked the same finalizer on the parallel WebAssemblyGCStructure subspace. Structure::finalizeUnconditionally walks weak internal references (transition tables, cached prototype/parent references, similar weak slots) and clears those that point to cells which did not survive marking. Because the Wasm GC subspace was skipped, weak references inside surviving WebAssemblyGCStructure instances that pointed to cells freed during the same full collection were left untouched.

After the full GC sweep frees a referenced Wasm GC cell, the slot in the un-finalised WebAssemblyGCStructure still appears non-null and is treated as live by any subsequent code that reads it (transition lookup, RTT/supertype walk, prototype query) — even though the underlying memory has been returned to the allocator and may be reused for a different cell type. An attacker who arranges for such a stale reference to be re-read after the freed slot is reclaimed could obtain a use-after-free or type confusion against a Wasm GC type-descriptor pointer, which would be a foothold toward a renderer-side R/W primitive.

This vulnerability weakened memory-safety guarantees inside the WebContent process when WebAssembly GC content is present. The fix restores the invariant that every Structure-class subspace participates in the end-of-full-GC weak-reference cleanup. This is a classic "parallel subspace forgotten in a finalization dispatcher" bug: whenever a Structure-like type is split into its own MarkedSpace subspace, every per-subspace iteration site (mark, sweep, finalize, weak-reference clearing, snapshotting) becomes a checklist item. The fact that Structure and BrandedStructure were both correctly dispatched but WebAssemblyGCStructure was not strongly suggests the original Wasm-GC integration patch added the subspace and the marking hooks but not the end-phase finalizer dispatch.