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 process crash
ComponentJSC Wasm
Bug ClassUAF
Tracker314235
Fix commite96472d9cab9 (WebKit/WebKit) +63/-7
CWECWE-125, CWE-787 (Out-of-bounds read, Out-of-bounds write)
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:H
CISA KEVNot listed
CreditedKwak Kiyong, Song nuri, Tristan Madani (@TristanInSec) from Talence Security
Disclosed2026-06-29

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.

Key insight
Retaining only the directly-named TypeDefinition — not the transitive closure of types it references — left dangling TypeIndex values in long-lived Globals/Tables once the defining module was collected.

Attack Path

  1. 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.
  2. 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.
  3. Drop the module Null out the instance/module reference so the only thing pinning $s is (pre-patch) nobody — m_typeDefinition retained just $f.
  4. Force collection Run gc() so the transitively-reachable $s TypeDefinition is freed while the global/table survives.
  5. 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

A controlled use-after-free of a Wasm TypeDefinition that outlives the module which defined its transitive dependencies. Triggering it needs GC timing, and the immediate observable is a crash in the WebContent process, but a reclaimed TypeDefinition slot filled with attacker-influenced data could turn a dangling TypeIndex into type confusion within the wasm type system. Confined to the WebContent sandbox; rated medium (CVSS 6.5).

Changed Functions

FunctionChangeNotes
Table::Table
Source/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.js
  • JSTests/wasm/gc/transitive-type-retention-table.js
  • Source/JavaScriptCore/wasm/WasmGlobal.h
  • Source/JavaScriptCore/wasm/WasmTable.cpp
  • Source/JavaScriptCore/wasm/WasmTable.h

Audit Directions

  • Other bare TypeIndex holders
    grep wasm/ for TypeInformation::getRef and RefPtr<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 walk
    Audit 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 containers
    Check tags/exceptions, JS-exposed wrappers (JSWebAssemblyGlobal/Table), and any cache that stores a Wasm::Type past module teardown for the same retention gap.

Original Bug Report

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