Chrome · Turbolev › Maglev
CVE-2026-118350
UAF in Turbolev (Maglev)
Overview
High
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
switchsrc/maglev/maglev-graph-optimizer.cc |
modified |
Files Changed
src/maglev/maglev-graph-optimizer.ccsrc/maglev/maglev-graph-optimizer.htest/mjsunit/turbolev/regress-567129324.js
Patch
From c713c92ce08f558caec5b068172348a21e9a5d9d Mon Sep 17 00:00:00 2001
From: Marco Vitale <mrcvtl@chromium.org>
Date: Tue, 29 Sep 2026 15:50:53 +0000
Subject: [PATCH] [maglev] Validate alternative type checks before reusing conversions
MaglevGraphOptimizer could replace a conversion node with a cached
alternative that performed a weaker type check. For example, a
TruncateCheckedNumberOrOddballToInt32 assuming kNumber could be replaced
by a cached alternative assuming kNumberOrOddball. This dropped the
stricter deopt guard, allowing non-Numbers (such as Booleans) to reach
downstream operations that rely on the stronger type.
This CL introduces a check ensuring that an alternative can only be
reused if its guaranteed input type satisfies the requested input type.
Fixed: 567129324
Change-Id: I7909cce03bea404a9fc145e86ff5f9096a6a6964
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/8492751
Commit-Queue: Marco Vitale <mrcvtl@chromium.org>
Reviewed-by: Darius Mercadier <dmercadier@chromium.org>
Cr-Commit-Position: refs/heads/main@{#110191}
---
diff --git a/src/maglev/maglev-graph-optimizer.cc b/src/maglev/maglev-graph-optimizer.cc
index fbe6a71..a143543 100644
--- a/src/maglev/maglev-graph-optimizer.cc
+++ b/src/maglev/maglev-graph-optimizer.cc
@@ -688,6 +688,48 @@
}
}
+namespace {
+
+// Returns the type check that `alt` is guaranteed to have already performed
+// on its tagged input node.
+NodeType GuaranteedInputType(UseRepresentation repr, ValueNode* alt) {
+ switch (repr) {
+ case UseRepresentation::kInt32:
+ case UseRepresentation::kFloat64:
+ return NodeType::kNumber;
+ case UseRepresentation::kHoleyFloat64:
+ return NodeType::kNumberOrUndefined;
+ // Unlike Int32/Float64, a Truncate node doesn't have a single fixed check,
+ // it depends on its assumed_input_type.
+ case UseRepresentation::kTruncatedInt32:
+ if (auto* t = alt->TryCast<TruncateCheckedNumberOrOddballToInt32>()) {
+ return t->assumed_input_type();
+ }
+ if (auto* t = alt->TryCast<TruncateUnsafeNumberOrOddballToInt32>()) {
+ return t->assumed_input_type();
+ }
+ return NodeType::kNumberOrOddball;
+ case UseRepresentation::kTagged:
+ case UseRepresentation::kTaggedForNumberToString:
+ case UseRepresentation::kUint32:
+ case UseRepresentation::kNonTruncated:
+ UNREACHABLE();
+ }
+ UNREACHABLE();
+}
+
+} // namespace
+
+bool MaglevGraphOptimizer::CanReuseAlternative(
+ ValueNode* node, ValueNode* alt, UseRepresentation repr,
+ std::optional<NodeType> assumed_input_type) {
+ if (!assumed_input_type.has_value()) return true;
+ // We cannot replace a stricter check with a weaker one, as subsequent
+ // operations might rely on this node to deopt invalid inputs.
+ return reducer_.CheckType(node, *assumed_input_type) ||
+ NodeTypeIs(GuaranteedInputType(repr, alt), *assumed_input_type);
+}
+
MaybeReduceResult MaglevGraphOptimizer::GetUntaggedValueWithRepresentation(
ValueNode* node, UseRepresentation use_repr,
std::optional<NodeType> assumed_input_type) {
@@ -710,8 +752,11 @@
// Check if we already have a canonical conversion.
NodeInfo* node_info =
known_node_aspects().GetOrCreateInfoFor(broker(), node);
- auto& alternative = node_info->alternative();
- if (ValueNode* alt = alternative.get(use_repr)) return alt;
+ if (ValueNode* alternative = node_info->alternative().get(use_repr);
+ alternative &&
+ CanReuseAlternative(node, alternative, use_repr, assumed_input_type)) {
+ return alternative;
+ }
// If `node` is itself tagging an untagged value, convert that value
// directly instead of untagging the tagged result.
if (node->is_conversion()) {
diff --git a/src/maglev/maglev-graph-optimizer.h b/src/maglev/maglev-graph-optimizer.h
index f41212a..7e98d10 100644
--- a/src/maglev/maglev-graph-optimizer.h
+++ b/src/maglev/maglev-graph-optimizer.h
@@ -150,6 +150,12 @@
ValueNode* node, UseRepresentation repr,
std::optional<NodeType> assumed_input_type);
+ // Returns true if reusing the cached conversion 'alt' safely preserves all
+ // required type checks for 'node'.
+ bool CanReuseAlternative(ValueNode* node, ValueNode* alt,
+ UseRepresentation repr,
+ std::optional<NodeType> assumed_input_type);
+
// Records the untagged input of a tagging conversion as the matching
// untagged alternative of `tagged`, so a later untagging use can reuse it.
template <ValueRepresentation kRepresentation>
diff --git a/test/mjsunit/turbolev/regress-567129324.js b/test/mjsunit/turbolev/regress-567129324.js
new file mode 100644
index 0000000..1abeaea
--- /dev/null
+++ b/test/mjsunit/turbolev/regress-567129324.js
@@ -0,0 +1,38 @@
+// 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 --turbolev
+// Flags: --max-turbolev-eager-inlined-bytecode-size=0
+
+// The graph optimizer must not replace a TruncateChecked...ToInt32[Number]
+// with a cached truncation that only assumed NumberOrOddball: the Number check
+// narrows `x` (Smi|Boolean) to Smi, which the Smi-field store relies on.
+// `alt` must not be eagerly inlined, so that its truncation is only added to
+// the graph by the inliner, after `x | y` has been built.
+
+function alt(x) {
+ return Math.imul(x, 3);
+}
+
+const obj = { b: 123 };
+
+function f(flag, y, obj) {
+ const x = flag ? true : 123;
+ const a = alt(x);
+ const b = x | y;
+ obj.b = x;
+ return b + a;
+}
+
+%PrepareFunctionForOptimization(alt);
+%PrepareFunctionForOptimization(f);
+f(false, 0.125, obj);
+%OptimizeFunctionOnNextCall(f);
+assertEquals(123 + 369, f(false, 0.125, obj));
+assertOptimized(f);
+
+assertEquals(1 + 3, f(true, 0.125, obj));
+assertUnoptimized(f);
+assertEquals(true, obj.b);
+%HeapObjectVerify(obj);
Loading diff…
Regression Test / PoC
shipped with the fix
diff --git a/test/mjsunit/turbolev/regress-567129324.js b/test/mjsunit/turbolev/regress-567129324.js
new file mode 100644
index 0000000..1abeaea
--- /dev/null
+++ b/test/mjsunit/turbolev/regress-567129324.js
@@ -0,0 +1,38 @@
+// 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 --turbolev
+// Flags: --max-turbolev-eager-inlined-bytecode-size=0
+
+// The graph optimizer must not replace a TruncateChecked...ToInt32[Number]
+// with a cached truncation that only assumed NumberOrOddball: the Number check
+// narrows `x` (Smi|Boolean) to Smi, which the Smi-field store relies on.
+// `alt` must not be eagerly inlined, so that its truncation is only added to
+// the graph by the inliner, after `x | y` has been built.
+
+function alt(x) {
+ return Math.imul(x, 3);
+}
+
+const obj = { b: 123 };
+
+function f(flag, y, obj) {
+ const x = flag ? true : 123;
+ const a = alt(x);
+ const b = x | y;
+ obj.b = x;
+ return b + a;
+}
+
+%PrepareFunctionForOptimization(alt);
+%PrepareFunctionForOptimization(f);
+f(false, 0.125, obj);
+%OptimizeFunctionOnNextCall(f);
+assertEquals(123 + 369, f(false, 0.125, obj));
+assertOptimized(f);
+
+assertEquals(1 + 3, f(true, 0.125, obj));
+assertUnoptimized(f);
+assertEquals(true, obj.b);
+%HeapObjectVerify(obj);
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.
References
On This Page