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 <[email protected]> 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 <[email protected]> Reviewed-by: Darius Mercadier <[email protected]> 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