High chrome UAF 🔧 Commit mapped

Overview

High
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in Turbolev
DescriptionUse after free in Turbolev
ComponentTurbolev
Sub-componentMaglev
Bug ClassUAF
Tracker567129324
Fix commitc713c92ce08f (v8/v8) +91/-2
CISA KEVNot listed
CreditedOpenAI Codex Security (amyb)
Disclosed2026-10-27

Changed Functions

FunctionChangeNotes
switch
src/maglev/maglev-graph-optimizer.cc
modified

Files Changed

  • src/maglev/maglev-graph-optimizer.cc
  • src/maglev/maglev-graph-optimizer.h
  • test/mjsunit/turbolev/regress-567129324.js
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.