← 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.

🔒

Detailed look at how a missing GC finalization dispatch can leave stale internal references behind, and the conditions under which that becomes more than a bookkeeping bug.

Subscribe to read more

🔒

Multiple reusable audit patterns identified for GC-subspace dispatch symmetry and weak-reference cleanup, with concrete starting points in the JSC heap and Wasm GC layers.

Subscribe to read more