Overview

High
Severity
8.8
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactType confusion in V8
DescriptionType confusion in V8
ComponentV8
Sub-componentMaglev
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: none -- diff precisely matches the claim: NodeTypeFromAccessInfo now gated on stable_field_map, populated only when field is heapobject, map present, and is_stable(); regression test shows the type-confusion.
A second, independent AI reviewer (not a human) re-derived this analysis from the fixing commit and the vendor advisory, looking for errors.
Tracker541926503
Fix commiteffbf0fc9555 (v8/v8) +49/-6
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
Creditedun3xploitable && GF
Reported2026-08-03 (per vendor advisory)
Fix landed2026-08-03 (public git)
Disclosed2026-08-18
Patch gap15 days — fix public in source before the advisory

Background

Maglev
V8’s mid-tier optimizing JIT compiler, which builds a graph from bytecode and attaches static type information (NodeType) to loaded values.
`PropertyAccessInfo`
a compiler record describing how to access an object property, including its field_representation(), field_type(), and an optional field_map() naming the class map of the stored value.
Field map stability
a map is stable while no instance has migrated; a class field type derived from an unstable map can be silently widened to Any by the GC without deoptimizing code that depends on it.
Stability dependency
a compilation dependency that forces deoptimization if a relied-upon map loses stability, which is the only thing that makes a map-derived field type safe to trust in optimized code.

Root Cause Analysis

When lowering a property load, NodeTypeFromAccessInfo in src/maglev/maglev-graph-builder.cc derived the loaded value’s NodeType by calling StaticTypeForMap on access_info.field_map() whenever that field map had a value. This trusted the recorded class map even when the map was unstable, violating the invariant that a descriptor-array class map is only a valid type source when it is stable and a stability dependency is installed. For an unstable map, the GC can widen the field’s type to Any and, because the last instance holding that map can migrate away and let the map die, no dependent optimized code is invalidated. The optimized load therefore continues to assume a stale, too-narrow NodeType, so a value of any other type stored into the field is treated as the old class type, producing type confusion.

The fix computes a stable_field_map that is populated only when the field representation is a heap object, a field map exists, and field_map().value().is_stable() is true, and passes it into NodeTypeFromAccessInfo, which now infers a map-based type solely from stable_field_map.

Key insight
The single mistake was inferring a static field type from access_info.field_map() unconditionally instead of only from a map proven stable and backed by a stability dependency; the fix threads a stable_field_map (set only under those conditions) so a map-derived NodeType is used only when the GC cannot silently widen the field type out from under the optimized code.

Attack Path

  1. Teach a narrow class field type Store DataView instances into Holder.x so that Holder’s descriptor records the shared DataView map as the class field type for x, letting Maglev infer that narrow NodeType.
  2. Make the recorded map unstable Generalize an unrelated property (a.p = 1.5) so the map backing the field type becomes unstable while the recorded field_map() still points at it.
  3. Optimize the load Warm and optimize read, which lowers getInt32.call(holder.x, ...) and embeds the stale narrow NodeType from the unstable field map with no stability dependency to guard it.
  4. Kill the map without deopt Migrate the last instance (b.p = 2.5) and run gc() so the class map dies and the GC widens the field type to Any, yet the optimized read is never invalidated.
  5. Trigger the confusion Store an ordinary object into holder.x and call read; the optimized code still assumes the old DataView type, so a mismatched object is used as the confused type, which the fixed build correctly surfaces as a TypeError.

Impact Assessment

An attacker running script in the renderer can induce a type confusion where an arbitrary heap object is treated by optimized Maglev code as a different, narrower class type, a primitive commonly leveraged into memory corruption and renderer RCE. The preconditions are only reachable JavaScript: a property that learns a class field type, generalization of an unrelated field to make the recorded map unstable, and a GC after the last holding instance migrates — all achievable within a single sandboxed page.

Files Changed

  • test/mjsunit/maglev/regress-541926503.js
  • src/maglev/maglev-graph-builder.cc

Audit Directions

  • Map-derived static types
    Audit every place that turns a field_map(), descriptor class map, or recorded map into a static NodeType/type check and confirm it requires stability plus an installed stability dependency.
  • GC-driven field-type widening
    Look for optimized code that caches field types without a dependency that deoptimizes when the GC widens the field type to Any after a map dies or an instance migrates.
  • Unstable-map assumptions
    Review compiler paths that consult access_info maps or field types and verify they distinguish stable from unstable maps before trusting the value in optimized code.
From effbf0fc9555ec832149d9884396b9c2f75a46e1 Mon Sep 17 00:00:00 2001
From: Victor Gomes <victorgomes@chromium.org>
Date: Mon, 03 Aug 2026 09:57:06 +0200
Subject: [PATCH] [maglev] Only infer a field type from a stable field map

The class map recorded in a descriptor array is only a valid source of
type information when it is stable and a stability dependency is
installed: for an unstable map the GC widens the field type to Any
without deoptimizing dependent code.

Fixed: 541926503
Change-Id: I14c738f44613fdf403e188c7092fda2a59a0515c
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/8182709
Auto-Submit: Victor Gomes <victorgomes@chromium.org>
Reviewed-by: Darius Mercadier <dmercadier@chromium.org>
Commit-Queue: Victor Gomes <victorgomes@chromium.org>
Commit-Queue: Darius Mercadier <dmercadier@chromium.org>
Cr-Commit-Position: refs/heads/main@{#108990}
---
diff --git a/src/maglev/maglev-graph-builder.cc b/src/maglev/maglev-graph-builder.cc
index 9a81b79..6cf54d1 100644
--- a/src/maglev/maglev-graph-builder.cc
+++ b/src/maglev/maglev-graph-builder.cc
@@ -113,9 +113,9 @@
   }
 };
 
-NodeType NodeTypeFromAccessInfo(
-    compiler::JSHeapBroker* broker,
-    const compiler::PropertyAccessInfo& access_info) {
+NodeType NodeTypeFromAccessInfo(compiler::JSHeapBroker* broker,
+                                const compiler::PropertyAccessInfo& access_info,
+                                compiler::OptionalMapRef stable_field_map) {
   if (access_info.field_representation().IsSmi()) {
     return NodeType::kSmi;
   }
@@ -124,8 +124,8 @@
     return NodeType::kHeapNumber;
   }
 
-  if (access_info.field_map().has_value()) {
-    return StaticTypeForMap(access_info.field_map().value(), broker);
+  if (stable_field_map.has_value()) {
+    return StaticTypeForMap(stable_field_map.value(), broker);
   }
 
   const auto type = access_info.field_type();
@@ -4466,13 +4466,14 @@
         {heap_number}, static_cast<int>(offsetof(HeapNumber, value_)));
   }
 
-  const auto node_type = NodeTypeFromAccessInfo(this->broker(), access_info);
   compiler::OptionalMapRef stable_field_map;
   if (access_info.field_representation().IsHeapObject() &&
       access_info.field_map().has_value() &&
       access_info.field_map().value().is_stable()) {
     stable_field_map = access_info.field_map();
   }
+  const auto node_type =
+      NodeTypeFromAccessInfo(this->broker(), access_info, stable_field_map);
   ValueNode* value;
   GET_VALUE_OR_ABORT(
       value, BuildLoadTaggedField(load_source, field_index.offset(), node_type,
diff --git a/test/mjsunit/maglev/regress-541926503.js b/test/mjsunit/maglev/regress-541926503.js
new file mode 100644
index 0000000..11ec072
--- /dev/null
+++ b/test/mjsunit/maglev/regress-541926503.js
@@ -0,0 +1,42 @@
+// 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: --allow-natives-syntax --expose-gc --maglev
+
+const getInt32 = DataView.prototype.getInt32;
+
+function read(holder) {
+  return getInt32.call(holder.x, 0, true);
+}
+
+function Holder(x) {
+  this.x = x;
+}
+
+const a = new DataView(new ArrayBuffer(16));
+const b = new DataView(new ArrayBuffer(16));
+a.p = 1;
+b.p = 2;
+
+// `Holder.x` learns the DataView map of `a` and `b` as its class field type.
+const holder = new Holder(a);
+holder.x = b;
+
+// Generalizing `p` to double makes that map unstable.
+a.p = 1.5;
+
+%PrepareFunctionForOptimization(read);
+read(holder);
+read(holder);
+%OptimizeMaglevOnNextCall(read);
+read(holder);
+
+// The last instance migrates away, so the class map dies and the GC widens the
+// field type to Any without invalidating the optimized code.
+b.p = 2.5;
+gc();
+gc();
+
+holder.x = {a: 1, b: 2, c: 3, d: 4, e: 5, f: 6, g: 7, h: 8, i: 9};
+assertThrows(() => read(holder), TypeError);
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/test/mjsunit/maglev/regress-541926503.js b/test/mjsunit/maglev/regress-541926503.js
new file mode 100644
index 0000000..11ec072
--- /dev/null
+++ b/test/mjsunit/maglev/regress-541926503.js
@@ -0,0 +1,42 @@
+// 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: --allow-natives-syntax --expose-gc --maglev
+
+const getInt32 = DataView.prototype.getInt32;
+
+function read(holder) {
+  return getInt32.call(holder.x, 0, true);
+}
+
+function Holder(x) {
+  this.x = x;
+}
+
+const a = new DataView(new ArrayBuffer(16));
+const b = new DataView(new ArrayBuffer(16));
+a.p = 1;
+b.p = 2;
+
+// `Holder.x` learns the DataView map of `a` and `b` as its class field type.
+const holder = new Holder(a);
+holder.x = b;
+
+// Generalizing `p` to double makes that map unstable.
+a.p = 1.5;
+
+%PrepareFunctionForOptimization(read);
+read(holder);
+read(holder);
+%OptimizeMaglevOnNextCall(read);
+read(holder);
+
+// The last instance migrates away, so the class map dies and the GC widens the
+// field type to Any without invalidating the optimized code.
+b.p = 2.5;
+gc();
+gc();
+
+holder.x = {a: 1, b: 2, c: 3, d: 4, e: 5, f: 6, g: 7, h: 8, i: 9};
+assertThrows(() => read(holder), TypeError);
Loading diff…

Original Bug Report

reported by po...@gmail.com

Turbolev: stale DataView field type after GC can lead to arbitrary memory r/w

Steps to reproduce the problem

Please run with attached poc.js:

./out.gn/x64.release/d8 \
  --allow-natives-syntax \
  --turbolev \
  ./poc.js

release build was compiled with the following args.gn

is_debug=false
is_asan=true
is_clang=true

target_cpu="x64"
v8_target_cpu="x64"

v8_enable_sandbox=true
v8_enable_memory_corruption_api=true
v8_symbol_level=2

Problem Description

VULNERABILITY DETAILS

Turbolev: stale DataView field type after GC can lead to controllable memory corruption.

NodeTypeFromAccessInfo assigns a precise type whenever a property access has a class field Map:

if (access_info.field_map().has_value()) {
  return StaticTypeForMap(access_info.field_map().value(), broker);
}

However, BuildLoadField only retains that Map and installs a stability dependency when it is stable:

const auto node_type = NodeTypeFromAccessInfo(this->broker(), access_info);
compiler::OptionalMapRef stable_field_map;
if (access_info.field_representation().IsHeapObject() &&
    access_info.field_map().has_value() &&
    access_info.field_map().value().is_stable()) {
  stable_field_map = access_info.field_map();
}
...
if (stable_field_map.has_value()) {
  broker()->dependencies()->DependOnStableMap(stable_field_map.value());
} else {
  known_info->IntersectType(node_type);
}

This contradicts the access-info contract in src/compiler/access-info.cc, which states that the field type may be inferred from the class Map only when the Map is stable and a stability dependency is added at the use.

The PoC creates two DataViews with the same Map M and stores them in a mutable holder field, causing the field descriptor to learn FieldType::Class(M). Changing an added property from Smi to double makes M unstable. TurboLev then compiles the field load with NodeType::kJSDataView, but no dependency keeps the unstable Map valid.

After the last object migrates away from M, a natural major GC clears the dead weak class Map in MarkCompactCollector::SpecialClearMapSlot:

if (map->is_stable() && FieldType::kFieldTypesCanBeClearedOnGC) {
  location.store(FieldType::None());
} else {
  location.store(FieldType::Any());
}

The unstable field type is widened to Any without invalidating the optimized code. The holder can then contain an ordinary object while the compiled load still has the stale kJSDataView type. BuildCheckInstanceType accepts this known type and omits the runtime JS_DATA_VIEW_TYPE check.

TurboLev consequently reads the ordinary object’s in-object fields as the DataView byte length and data pointer. The optimized DataView.prototype.getInt32 and setInt32 calls then provide controlled 4-byte memory reads and writes.

IMPACT

The attached PoC first writes the JavaScript-controlled value 0x41414141 to a selected mapped address and reads the same four bytes back. It throws before continuing if the readback is not exactly 0x41414141.

It then performs a second 4-byte store at the selected relative address 0x41414141. Independent executions with different address randomization produced:

Received signal 11 SEGV_ACCERR 7ea441414141
Received signal 11 SEGV_ACCERR 7a8b41414141
Received signal 11 SEGV_ACCERR 7ea841414141

VERSION

Tested commit: 1d17afafffbb434e04b2ee4bec7ca09989626341 (2026-07-31), Linux x86-64.

BISECT

First bad commit: ea85967a2149d4f648c60cb26d48ba7b75ecdd79 (2026-06-15), [maglev] Enrich LoadTaggedField with NodeType metadata.

Summary

Turbolev: stale DataView field type after GC can lead to arbitrary memory r/w

Custom Questions

Type of crash:

controllable crash on renderer

Crash state:

Received signal 11 SEGV_ACCERR 7e9f41414141

==== C stack trace ===============================

./v8/v8/out/Release/d8(__interceptor_backtrace+0x4a)[0x63874af446ea]
./v8/v8/out/Release/d8(+0x6b449c0)[0x6387507a99c0]
/lib/x86_64-linux-gnu/libc.so.6(+0x45330)[0x7005cf445330]
[0x6387b04c1934]
[end of stack trace]

Additional Data

Category: Security
Chrome Channel: Not sure
Regression: N/A \

View on issue tracker