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
Tracker541926503
Fix commiteffbf0fc9555 (v8/v8) +49/-6
CISA KEVNot listed
Creditedun3xploitable && GF
Disclosed2026-08-18

Files Changed

  • src/maglev/maglev-graph-builder.cc
  • test/mjsunit/maglev/regress-541926503.js
From effbf0fc9555ec832149d9884396b9c2f75a46e1 Mon Sep 17 00:00:00 2001
From: Victor Gomes <[email protected]>
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 <[email protected]>
Reviewed-by: Darius Mercadier <[email protected]>
Commit-Queue: Victor Gomes <[email protected]>
Commit-Queue: Darius Mercadier <[email protected]>
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 [email protected]

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