High chrome Type Confusion 📄 Reporter bug report 🔧 Commit mapped

Overview

High
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactType Confusion in V8
DescriptionType Confusion in V8
ComponentV8
Bug ClassType Confusion
Tracker381696874
Fix commita4d354fa54ba (v8/v8) +16/-11
CISA KEVNot listed
CreditedSeunghyun Lee (@0x10n)
Disclosed2024-12-10

Changed Functions

FunctionChangeNotes
if
src/wasm/canonical-types.h
modified

Files Changed

  • src/wasm/canonical-types.h
From a4d354fa54ba3e6a0f061c6b959be408ead5db95 Mon Sep 17 00:00:00 2001
From: Clemens Backes <[email protected]>
Date: Mon, 02 Dec 2024 15:42:19 +0100
Subject: [PATCH] [wasm] Fix equality of canonical value types

We were incorrectly assuming that the value kind implies indexedness.
Fix this, and also compare generic (non-indexes) ref types.

[email protected]

Fixed: 381696874
Change-Id: Ie74ea8cb20d14f1b8d7d6a09701bc45cf91a913e
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/6063041
Reviewed-by: Jakob Kummerow <[email protected]>
Commit-Queue: Clemens Backes <[email protected]>
Cr-Commit-Position: refs/heads/main@{#97503}
---

diff --git a/src/wasm/canonical-types.h b/src/wasm/canonical-types.h
index 2f5de53..a730f23 100644
--- a/src/wasm/canonical-types.h
+++ b/src/wasm/canonical-types.h
@@ -262,15 +262,15 @@
 
     bool EqualTypeIndex(CanonicalTypeIndex index1,
                         CanonicalTypeIndex index2) const {
-      if (recgroup1.Contains(index1)) {
-        // Compare relative supertypes in the recgroups.
-        if (!recgroup2.Contains(index2)) return false;
-        uint32_t rel_supertype1 = index1.index - recgroup1.first.index;
-        uint32_t rel_supertype2 = index2.index - recgroup2.first.index;
-        if (rel_supertype1 != rel_supertype2) return false;
-      } else {
-        if (recgroup2.Contains(index2)) return false;
-        if (index1 != index2) return false;
+      const bool relative_index = recgroup1.Contains(index1);
+      if (relative_index != recgroup2.Contains(index2)) return false;
+      if (relative_index) {
+        // Compare relative type indexes within the respective recgroups.
+        uint32_t rel_type1 = index1.index - recgroup1.first.index;
+        uint32_t rel_type2 = index2.index - recgroup2.first.index;
+        if (rel_type1 != rel_type2) return false;
+      } else if (index1 != index2) {
+        return false;
       }
       return true;
     }
@@ -303,8 +303,13 @@
     bool EqualValueType(CanonicalValueType type1,
                         CanonicalValueType type2) const {
       if (type1.kind() != type2.kind()) return false;
-      if (type1.has_index() &&
-          !EqualTypeIndex(type1.ref_index(), type2.ref_index())) {
+      const bool indexed = type1.has_index();
+      if (indexed != type2.has_index()) return false;
+      if (indexed) return EqualTypeIndex(type1.ref_index(), type2.ref_index());
+      const bool is_ref = type1.is_object_reference();
+      DCHECK_EQ(is_ref, type2.is_object_reference());
+      if (is_ref &&
+          type1.heap_representation() != type2.heap_representation()) {
         return false;
       }
       return true;
Loading diff…

Original Bug Report

reported by [email protected]

Arbitrary Wasm type confusion due to improper fix of b/380397544

VULNERABILITY DETAILS

A bit unfortunate that even after a series of patches canonicalization is still broken, we really need a proactive approach rather than reactive bug discovery & patches.

Summary

Arbitrary Wasm type confusion due to improper fix of b/380397544 (which attempts to fix a broken patch for b/379009132, which in turn attempts to fix a broken patch for b/371565065 + b/354408144). HeapType checks are missing for generic Wasm heap types at CanonicalEquality::EqualValueType(), allowing type confusion between arbitrary Wasm types.

Details

I’ve pointed out in b/380397544 that after https://crrev.com/c/6035175 v8 does not distinguish between (relative) recursion group based index vs. (absolute) canonical index, which leads to different recursion group to be mistakenly canonicalized into the same index and thus lead to arbitrary type confusion between Wasm types. https://crrev.com/c/6048961 attempts to fix this by checking for relative types on type index comparison. Equality checks for CanonicalValueTypes go through the following code at CanonicalEquality::EqualValueType():

    bool EqualValueType(CanonicalValueType type1,
                        CanonicalValueType type2) const {
      if (type1.kind() != type2.kind()) return false;
      if (type1.has_index() &&
          !EqualTypeIndex(type1.ref_index(), type2.ref_index())) {
        return false;
      }
      return true;
    }

We see that if !type1.has_index(), that is, if the left-hand side of the equality comparison has a generic heap type, then the comparison always returns true no matter what the heap type of type2 is. This results in the equality comparator of Canonical(Singleton)Group to consider different reference types to be the same under the aformentioned case and thus may lead to arbitrary Wasm type confusion again.

However, this issue is not immediately evident as we use std::unordered_set<Canonical(Singleton)Group> to find pre-existing canonicalization results. This uses CanonicalHashing as a hashing function for the hashmap. Thus, to trigger this issue we must find two different recursion group that is considered equal by CanonicalEquality, but which at the same time also has a hash collision when hashed via CanonicalHashing.

CanonicalHashing uses base::Hasher which is based on MurmurHash64A and thus returns a 64bit hash value. Birthday attack allows us to find a collision in ~50% chance with 2^32 samples which is very feasible (in minutes, if not seconds). By precomputing offline the hash values for struct types that either has ref null any or ref null none as its fields and iterating this selection for >32 fields we can easily create >2^32 different inputs that are all considerered equal by CanonicalEquality, but which has random-ish hash values in which we are likely to find at least a single duplicate hash value. By using such precomputed colliding struct types we can canonicalize two different struct types into the same canonical index and cause arbitrary Wasm type confusion.

The attached PoC/exploit has a precomputed hash-colliding struct type that uses either ref null any or ref null none for its 40 field types. It tries two colliding pairs, one that works before https://crrev.com/c/6055121 and one that works after that as the patch affects hashing results.

Bisect

Bug introduced by https://crrev.com/c/6048961 in M133 that attempts to fix b/380397544 by accounting for relative type indices. Note that the commit is already backported to M132 and M131.

VERSION

Chrome Version: 133.0.6862.0 ~ latest / M131 head / M132 head
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.6871.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