High watch CVSS 8.8 chrome Type Confusion 🔧 Commit mapped

Overview

High
Severity
8.8
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactType confusion in V8
DescriptionType confusion in V8
ComponentV8
Sub-componentSnapshot
Bug ClassType Confusion
Action tierwatch — corruption primitive but gated (local) — not default-reachable
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.
Tracker554236352
Fix commitfbf1d9e1fdc7 (v8/v8) +15/-2
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
Creditedywatanabee
Reported2026-08-29 (per vendor advisory)
Fix landed2026-09-02 (public git)
Disclosed2026-09-08
Patch gap6 days — fix public in source before the advisory

Background

Snapshot deserialization
the process by which V8 reconstructs heap objects from a serialized byte stream, such as a code cache produced with --cache=after-execute.
Reference descriptor
transient deserializer state (next_reference_is_weak_, next_reference_is_indirect_pointer_, next_reference_is_protected_pointer) recording how the next pointer slot should be written (strong, weak, indirect, or protected).
`RootArrayConstants`
a snapshot bytecode that writes a slot by referencing a well-known object from the root table by index, avoiding a full encoded object.
`WriteHeapPointer`
the deserializer helper that writes a heap pointer into a slot honoring a HeapObjectReferenceType descriptor and consuming (resetting) the pending reference state.

Root Cause Analysis

When the deserializer encountered a RootArrayConstants bytecode, Deserializer<IsolateT>::ReadRootArrayConstants unconditionally wrote the root object as a STRONG reference via slot_accessor.Write(heap_object, HeapObjectReferenceType::STRONG, ...), ignoring any pending reference descriptor set by a preceding weak/indirect/protected marker in the stream. Because the pending state was neither consulted nor reset, a slot that the serializer intended to hold a weak (or indirect/protected) reference was materialized as a strong direct pointer, and the stale descriptor could then bleed into the next slot write. This produced a type/tagging confusion where a slot’s actual bit representation no longer matched what surrounding code expected, violating the invariant that every deserialized slot is written exactly according to its declared reference kind.

The fix routes the write through WriteHeapPointer(slot_accessor, heap_object, GetAndResetNextReferenceDescriptor(), SKIP_WRITE_BARRIER), which applies the correct reference type and atomically consumes and clears the pending descriptor. Added DCHECKs in ReadRepeatedRoot further assert that no descriptor is left dangling at repeat-root boundaries.

Key insight
The single mistake was ReadRootArrayConstants hardcoding a STRONG write instead of honoring and resetting the pending reference descriptor; the fix makes it write through WriteHeapPointer with GetAndResetNextReferenceDescriptor() so the slot’s reference kind is correct and the transient state cannot leak.

Attack Path

  1. Craft a poisoned code cache Produce or influence a serialized snapshot (e.g. via --cache=after-execute) in which a RootArrayConstants slot is preceded by a weak/indirect/protected reference marker.
  2. Trigger deserialization Cause V8 to deserialize the cache, reaching ReadRootArrayConstants, which writes the root object as STRONG and leaves the pending descriptor set.
  3. Induce tag confusion The mismatched or leaked descriptor causes a slot to hold a pointer whose reference kind differs from what the engine assumes, or corrupts the following slot write.
  4. Confuse object type Subsequent code dereferences the mis-tagged slot as a different type than its true contents, yielding a type confusion primitive in the heap.
  5. Escalate Leverage the type confusion to read/write out of bounds or forge object fields, working toward controlled memory corruption in the renderer.

Impact Assessment

An attacker gains a type-confusion primitive in the V8 heap within the renderer process, potentially leading to memory corruption and remote code execution in that sandboxed context. Exploitation requires the victim to deserialize an attacker-influenced snapshot or code cache, so the practical precondition is control over the cached bytecode consumed during startup or execution.

Files Changed

  • src/snapshot/deserializer.cc
  • test/mjsunit/regress/regress-554236352.js

Audit Directions

  • Unconsumed reference descriptors
    Audit every deserializer bytecode handler that writes a slot to confirm it consumes and resets the pending reference descriptor via GetAndResetNextReferenceDescriptor rather than hardcoding a HeapObjectReferenceType.
  • Hardcoded reference kinds
    Search for direct slot_accessor.Write(..., HeapObjectReferenceType::STRONG, ...) calls that ignore weak/indirect/protected markers and could mis-tag slots.
  • State leakage across handlers
    Review paths where transient deserializer flags can persist between consecutive slot writes, and add DCHECKs at handler boundaries to assert they start clean.
From fbf1d9e1fdc72b2dca3a482b2658866fa4724815 Mon Sep 17 00:00:00 2001
From: Igor Sheludko <ishell@chromium.org>
Date: Wed, 02 Sep 2026 11:37:44 +0200
Subject: [PATCH] [snapshot] Reset weak reference descriptor in ReadRootArrayConstants

This CL updates ReadRootArrayConstants to write through WriteHeapPointer
with GetAndResetNextReferenceDescriptor(), ensuring the reference
descriptor is properly consumed and reset.

TAG=agy
CONV=6a397150-d53d-4615-b1b8-ce8c94c1a9dd

Fixed: 554236352
Change-Id: Idab7c9703b4d1210f8fd77564016bdc501543411
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/8346067
Commit-Queue: Igor Sheludko <ishell@chromium.org>
Reviewed-by: Leszek Swirski <leszeks@chromium.org>
Cr-Commit-Position: refs/heads/main@{#109637}
---
diff --git a/src/snapshot/deserializer.cc b/src/snapshot/deserializer.cc
index fee3fa2..9db502d 100644
--- a/src/snapshot/deserializer.cc
+++ b/src/snapshot/deserializer.cc
@@ -943,6 +943,9 @@
 template <typename SlotAccessor>
 int Deserializer<IsolateT>::ReadRepeatedRoot(SlotAccessor slot_accessor,
                                              int repeat_count) {
+  DCHECK(!next_reference_is_weak_);
+  DCHECK(!next_reference_is_indirect_pointer_);
+  DCHECK(!next_reference_is_protected_pointer);
   CHECK_LE(2, repeat_count);
 
   uint8_t id = source_.Get();
@@ -1602,8 +1605,9 @@
     PrintF("%*sRootArrayConstants [%u] : %s\n", depth_, "",
            static_cast<int>(root_index), RootsTable::name(root_index));
   }
-  return slot_accessor.Write(heap_object, HeapObjectReferenceType::STRONG, 0,
-                             SKIP_WRITE_BARRIER);
+  return WriteHeapPointer(slot_accessor, heap_object,
+                          GetAndResetNextReferenceDescriptor(),
+                          SKIP_WRITE_BARRIER);
 }
 
 template <typename IsolateT>
diff --git a/test/mjsunit/regress/regress-554236352.js b/test/mjsunit/regress/regress-554236352.js
new file mode 100644
index 0000000..63faeff
--- /dev/null
+++ b/test/mjsunit/regress/regress-554236352.js
@@ -0,0 +1,9 @@
+// Copyright 2026 the V8 project authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Flags: --cache=after-execute
+
+var R = Array.of(() => { 'use strict'; return eval('1'); }, "hello_world");
+assertEquals("hello_world", R[1]);
+assertEquals(1, R[0]());
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/test/mjsunit/regress/regress-554236352.js b/test/mjsunit/regress/regress-554236352.js
new file mode 100644
index 0000000..63faeff
--- /dev/null
+++ b/test/mjsunit/regress/regress-554236352.js
@@ -0,0 +1,9 @@
+// Copyright 2026 the V8 project authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Flags: --cache=after-execute
+
+var R = Array.of(() => { 'use strict'; return eval('1'); }, "hello_world");
+assertEquals("hello_world", R[1]);
+assertEquals(1, R[0]());
Loading diff…

Original Bug Report

The reporter's bug is still restricted on the tracker. Chrome de-restricts security bugs ~30–90 days after the fix ships; a later run will backfill it here.