Overview

High
Severity
8.8
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactType Confusion in V8
DescriptionType Confusion in V8
ComponentV8
Sub-componentWASM
Bug ClassType Confusion
Action tierpatch-now — corruption primitive, web-reachable in default config (confirmed) — upheld by independent review by AI
Computed by PatchGap's deterministic exploitability ruler from the fixing commit. patch-now = RCE-class & attacker-reachable in a default config, or exploited in the wild; watch = real security signal below that bar; noise = no security relevance.
Independent review by AIConfirmed
Reviewer notes: Mechanism matches diff. Impact states arbitrary read/write and struct.set misuse as fact; the diff only shows the missing mutability comparison, so downstream consequences are inferred.
A second, independent AI reviewer (not a human) re-derived this analysis from the fixing commit and the vendor advisory, looking for errors.
Tracker382291459
Fix commit3852cf8b5bce (v8/v8) +9/-4
CWECWE-843 (Type confusion)
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H
CISA KEVNot listed
CreditedSeunghyun Lee (@0x10n)
Reported2024-12-05 · bounty $55000 (per vendor advisory)
Fix landed2024-12-05 (public git)
Disclosed2024-12-18
Patch gap13 days — fix public in source before the advisory

Background

Wasm canonical types
V8 deduplicates structurally equivalent WebAssembly types into a global registry so that two modules declaring the “same” type share one CanonicalTypeIndex, letting the engine treat their values as interchangeable.
`CanonicalStructType`
the canonicalized representation of a Wasm struct, holding the list of its field value types (fields()) and a parallel list of per-field mutability flags (mutabilities()).
Field mutability
a boolean per struct field distinguishing an immutable field from a mutable one, which governs whether struct.set is permitted and is part of a field’s subtyping/identity semantics.
Type confusion
treating a value of one type as if it were an unrelated type, which in V8 lets an attacker forge object layouts and bypass memory-safety invariants.

Root Cause Analysis

When registering a Wasm struct type in the canonical type registry, V8’s CanonicalEquality::EqualStructType decided whether two struct types were identical so it could deduplicate them under a single canonical index. The vulnerable implementation compared only the two structs’ field value types via std::equal over type1.fields() and type2.fields(), and never compared their mutabilities().

As a result, two structs whose fields had identical types but differing mutability (for example one with an immutable field and one with a mutable field) were wrongly judged equal and collapsed to the same CanonicalTypeIndex, violating the invariant that a canonical type must fully capture a value’s observable type semantics including mutability. This let the type system treat an immutable-field object and a mutable-field object as the same type, enabling struct.set writes (or reference-subtyping decisions) that the immutable variant should have forbidden, i.e. a type confusion.

The fix adds a second std::equal comparison over type1.mutabilities() and type2.mutabilities(), so structs that differ only in mutability are no longer canonicalized together and the type identity again reflects the full field contract.

Key insight
The single mistake was that struct canonical-type equality checked field types but omitted per-field mutability, so structurally distinct types were merged; the fix restores correctness by additionally requiring the mutabilities() arrays to match.

Attack Path

  1. Declare colliding struct types Craft a Wasm module defining two struct types with identical field value types but different mutability on at least one field (immutable vs. mutable).
  2. Trigger canonicalization Instantiate the module so V8 registers the types through CanonicalEquality::EqualStructType, which erroneously assigns both structs the same CanonicalTypeIndex.
  3. Alias across the confusion Use the shared canonical index to pass or store an object of the immutable-field type where the mutable-field type is expected (or vice versa), which the verifier now accepts as the same type.
  4. Perform an illegal mutation Execute a struct.set (or exploit relaxed subtyping) that mutates a field the true type declared immutable, corrupting an invariant the engine relied on.
  5. Escalate to memory corruption Leverage the resulting type confusion to forge object contents or pointers, achieving out-of-bounds access and controlled read/write inside the renderer.

Impact Assessment

An attacker who can run attacker-controlled WebAssembly in a victim renderer gains a type confusion in V8 that can be turned into memory corruption and arbitrary read/write within the sandboxed renderer process. The only precondition is executing crafted Wasm (e.g., via a malicious web page), with no special permissions required. Combined with a sandbox escape this contributes to full remote code execution.

Files Changed

  • src/wasm/canonical-types.h

Audit Directions

  • Incomplete structural equality
    Review every canonical-type comparison (EqualArrayType, function/signature equality, recursive-group hashing) to confirm each checks the full set of identity-bearing attributes, not just the obvious ones.
  • Type-property parity with hashing
    Ensure any hash function used to bucket canonical types incorporates the same fields (including mutability) as the equality predicate, since a mismatch reintroduces collisions.
  • Mutability-sensitive invariants
    Hunt for other places that key subtyping, deduplication, or caching on field/element types while dropping mutability or nullability, especially in Wasm GC struct/array handling.
From 3852cf8b5bceed6a76e4537fc0aa191f9ed672a3 Mon Sep 17 00:00:00 2001
From: Clemens Backes <clemensb@chromium.org>
Date: Thu, 05 Dec 2024 19:41:22 +0100
Subject: [PATCH] [wasm] Fix comparison of canonical struct types

In addition to the field types we should also check the mutability.

R=jkummerow@chromium.org

Fixed: 382291459
Change-Id: I46c5d9ece184a49699dd1c1e44c3b6f08646334b
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/6074536
Reviewed-by: Jakob Kummerow <jkummerow@chromium.org>
Commit-Queue: Clemens Backes <clemensb@chromium.org>
Cr-Commit-Position: refs/heads/main@{#97587}
---

diff --git a/src/wasm/canonical-types.h b/src/wasm/canonical-types.h
index 7c8618f..bcb6913 100644
--- a/src/wasm/canonical-types.h
+++ b/src/wasm/canonical-types.h
@@ -325,10 +325,15 @@
 
     bool EqualStructType(const CanonicalStructType& type1,
                          const CanonicalStructType& type2) const {
-      return std::equal(
-          type1.fields().begin(), type1.fields().end(), type2.fields().begin(),
-          type2.fields().end(),
-          std::bind_front(&CanonicalEquality::EqualValueType, this));
+      return
+          // Compare fields, including a check that the size is the same.
+          std::equal(
+              type1.fields().begin(), type1.fields().end(),
+              type2.fields().begin(), type2.fields().end(),
+              std::bind_front(&CanonicalEquality::EqualValueType, this)) &&
+          // Compare mutabilities, skipping the check for the size.
+          std::equal(type1.mutabilities().begin(), type1.mutabilities().end(),
+                     type2.mutabilities().begin());
     }
 
     bool EqualArrayType(const CanonicalArrayType& type1,
Loading diff…

Original Bug Report

reported by se...@gmail.com

Arbitrary Wasm type confusion due to missing struct field mutability check on canonicalization

VULNERABILITY DETAILS

Summary

Arbitrary Wasm type confusion due to missing struct field mutability check on canonicalization. Mutability checks are missing for struct fields at CanonicalEquality::EqualStructType(), allowing type confusion between arbitrary Wasm types.

This is a variant of b/381696874.

Details

I’ve pointed out in b/381696874 that if we have a broken CanonicalEquality check that may be exploitable, we can use the birthday attack to cause a hash collision and trigger the bug. CanonicalEquality::EqualStructType() is missing mutability checks for its fields:

    bool EqualStructType(const CanonicalStructType& type1,
                         const CanonicalStructType& type2) const {
      return std::equal(
          type1.fields().begin(), type1.fields().end(), type2.fields().begin(),
          type2.fields().end(),
          std::bind_front(&CanonicalEquality::EqualValueType, this));
    }

This can be exploited by a casting chain of struct {const ref null none} -> struct {const ref null any} -> struct {mut ref null any} where the last cast is due to broken canonicalization, and the first cast is through legal subtype relationship. This allows us to overwrite ref null none with a value of ref null any type, resulting in arbitrary Wasm type confusion.

The attached PoC/exploit has a precomputed hash-colliding struct type that uses either const ref null any or mut ref null any for its 40 field types.

Bisect

Bug likely introduced by https://crrev.com/c/6049646 in M133 that attempts to fix b/379009132 by removing relative type indexs from canonical types. Note that the commit is already backported to M132 and M131.

VERSION

Chrome Version: 131.0.6778.108, 132.0.6834.32, 133.0.6848.0 ~ latest
Operating System: All

REPRODUCTION CASE

Attached as poc.js which exploits the hash collision + type confusion to obtain in-sandbox exploit primitives, and then crashes on arbitrary caged write attempt.

Also attached is yet another full exploit exp.html that pops calc on Windows x64 Chrome, tested against Canary 133.0.6877.0.

FOR CRASHES, PLEASE INCLUDE THE FOLLOWING ADDITIONAL INFORMATION

Type of crash: Renderer
Crash State: Crashes on arbitrary caged write attempt from JIT-compiled Wasm function (on d8, poc.js), arbitrary code execution (on Chrome, exp.html)

CREDIT INFORMATION

Reporter credit: Seunghyun Lee (@0x10n) of CMU CyLab

View on issue tracker