CVE-2026-43712
Overview
Background
- Wasm GC typed references
- WebAssembly GC lets values be typed references like (ref null $t) that point at struct/array/func TypeDefinitions identified by a TypeIndex.
- TypeDefinition / TypeIndex
- A TypeDefinition is the interned description of a wasm type; a Wasm::Type stores a TypeIndex into that table, which must stay valid as long as any value of that type exists.
- Transitive type retention
- A type can reference other types (a func type refers to its parameter struct), so keeping a type valid requires retaining the whole reachable graph, not just the named definition.
- WebAssemblyGCTypeDependencies
- The helper introduced by the fix that holds strong references to a TypeDefinition and every type transitively reachable from it.
Root Cause Analysis
A WebAssembly Global or Table whose element type is a GC reference (e.g. (ref null $f)) stores that type as a Wasm::Type carrying a TypeIndex. To keep the TypeIndex valid after the defining module is gone, WasmGlobal and WasmTable retained the type with a single RefPtr<const Wasm::TypeDefinition> m_typeDefinition = TypeInformation::getRef(type.index). That pins only the directly-named TypeDefinition. But a TypeDefinition can transitively reference other types — in the regression tests, function type $f references struct type $s — and those transitively-reachable definitions were not retained by anyone once the module and the strong references it held were collected. The Global/Table then still held a Wasm::Type whose TypeIndex pointed at a freed $s definition. A later operation on the global or table (reading or writing its value, or table.get/set/grow) resolves that dangling TypeIndex — a use-after-free / type-system confusion surfacing as an unexpected process crash.
The fix replaces the lone RefPtr<const TypeDefinition> with std::optional<WebAssemblyGCTypeDependencies> m_typeDependencies, populated via m_typeDependencies.emplace(Ref { *typeDefinition }) in each Global/Table constructor, and WebAssemblyGCTypeDependencies retains the definition together with all transitively reachable types.
The restored invariant is that a Global or Table that names a GC type keeps alive the entire transitive closure of types its TypeIndex values can reach, not merely the top-level definition.
Attack Path
- Define nested GC types Build a wasm module with a rec group where a function type $f takes/returns a struct type $s, i.e. $f transitively references $s.
- Store the type in a long-lived container
Create an exported global
(mut (ref null $f))or a table with element type(ref null $f)so a WasmGlobal/WasmTable holds a Wasm::Type with $s’s TypeIndex reachable only transitively. - Drop the module Null out the instance/module reference so the only thing pinning $s is (pre-patch) nobody — m_typeDefinition retained just $f.
- Force collection Run gc() so the transitively-reachable $s TypeDefinition is freed while the global/table survives.
- Touch the survivor Read/write the global value or call table.get/set/grow; resolving the now-dangling TypeIndex dereferences freed memory — crash or type confusion.
Impact Assessment
Changed Functions
| Function | Change | Notes |
|---|---|---|
Table::TableSource/JavaScriptCore/wasm/WasmTable.cpp |
modified | Drops the single m_wasmTypeDefinition RefPtr and instead emplaces a WebAssemblyGCTypeDependencies (m_typeDependencies) that retains the type and all transitively reachable definitions. |
Global::Global (both constructors)Source/JavaScriptCore/wasm/WasmGlobal.h |
modified | Same change for globals: replaces m_typeDefinition with a transitive m_typeDependencies so a GC element type keeps its whole type closure alive. |
m_typeDependencies member (Global/Table)Source/JavaScriptCore/wasm/WasmGlobal.h |
modified | Field type changed from RefPtr<const TypeDefinition> to std::optional<WebAssemblyGCTypeDependencies>; mirrored in WasmTable.h. |
Files Changed
JSTests/wasm/gc/transitive-type-retention-global.jsJSTests/wasm/gc/transitive-type-retention-table.jsSource/JavaScriptCore/wasm/WasmGlobal.hSource/JavaScriptCore/wasm/WasmTable.cppSource/JavaScriptCore/wasm/WasmTable.h
Audit Directions
- Other bare TypeIndex holdersgrep wasm/ for
TypeInformation::getRefandRefPtr<const TypeDefinition>/RefPtr<const Wasm::TypeDefinition>to find other long-lived objects that pin only the direct definition rather than a WebAssemblyGCTypeDependencies. - Completeness of transitive walkAudit WebAssemblyGCTypeDependencies to confirm it follows rec-group members, struct/array field types, and func param/result types — any missed edge re-opens the same dangling-TypeIndex class.
- Other GC-typed containersCheck tags/exceptions, JS-exposed wrappers (JSWebAssemblyGlobal/Table), and any cache that stores a Wasm::Type past module teardown for the same retention gap.