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 range analysis
Bug ClassUAF
Tracker570203091
Fix commit49b69c460a6f (v8/v8) +93/-0
CISA KEVNot listed
CreditedHoyong Jin
Disclosed2026-10-20

Changed Functions

FunctionChangeNotes
for
test/mjsunit/regress/regress-570203091-1.js
modified
if
test/mjsunit/regress/regress-570203091-1.js
modified
for
test/mjsunit/regress/regress-570203091-2.js
modified
if
test/mjsunit/regress/regress-570203091-2.js
modified

Files Changed

  • test/mjsunit/regress/regress-570203091-1.js
  • test/mjsunit/regress/regress-570203091-2.js
  • src/maglev/maglev-range-analysis.h
From 49b69c460a6fc5061f5ad1cf1661677295f78762 Mon Sep 17 00:00:00 2001
From: Marja Hölttä <marja@chromium.org>
Date: Wed, 14 Oct 2026 11:07:17 +0200
Subject: [PATCH] [maglev range analysis] Fix phi ranges in resumable loops

Resumable loops might not have a live forward predecessor, but it might
still be possible to enter them via a resume point.

Previously, the phi ranges in such resumable loops were left
uninitialized and the range analysis went wrong.

This CL initializes the phi ranges for blocks which didn't have them
initialized through a live predecessor. If a block has a live
predecessor, this should be a no-op.

Fixed: 570203091
Change-Id: I89842d78c6b7e70dafd18894e275a5706c7722ee
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/8559780
Reviewed-by: Darius Mercadier <dmercadier@chromium.org>
Commit-Queue: Marja Hölttä <marja@chromium.org>
Cr-Commit-Position: refs/heads/main@{#110690}
---
diff --git a/src/maglev/maglev-range-analysis.h b/src/maglev/maglev-range-analysis.h
index 5174495..57d415f 100644
--- a/src/maglev/maglev-range-analysis.h
+++ b/src/maglev/maglev-range-analysis.h
@@ -67,6 +67,18 @@
     return *range;
   }
 
+  void InitializeRangeIfMissing(BasicBlock* block, ValueNode* node,
+                                Range range) {
+    DCHECK(ranges_initialized_.Contains(block->id()));
+    RangeMap& map = ranges_[block->id()];
+    if (!map.find(node)) {
+      map = map.insert(zone(), node, range);
+      TRACE_RANGE("Initializing in Block b" << block->id() << ": "
+                                            << PrintNodeBrief(node) << ": "
+                                            << range);
+    }
+  }
+
   void UnionUpdate(BasicBlock* block, ValueNode* node, Range range) {
     DCHECK(ranges_initialized_.Contains(block->id()));
     RangeMap& map = ranges_[block->id()];
@@ -210,6 +222,15 @@
   BlockProcessResult PreProcessBasicBlock(BasicBlock* block) {
     current_block_ = block;
     ranges_.EnsureMapExistsFor(block);
+    if (block->has_state() && block->state()->is_resumable_loop() &&
+        block->predecessor_count() == 1) {
+      // Resumable loop headers might not have a live forward predecessor: that
+      // part of the code might be dead even though the loop is still reachable
+      // via a resume point. Make sure we initialize phis in such case. If the
+      // loop has a live predecessor, ProcessPhis is called when processing that
+      // live predecessor, and this code is a no-op.
+      InitializeLoopPhisIfMissing(block);
+    }
     return BlockProcessResult::kContinue;
   }
   BlockProcessResult PostProcessBasicBlock(BasicBlock* block) {
@@ -543,6 +564,14 @@
     }
   }
 
+  void InitializeLoopPhisIfMissing(BasicBlock* block) {
+    if (!block->has_phi()) return;
+    for (Phi* phi : *block->phis()) {
+      ranges_.InitializeRangeIfMissing(
+          block, phi, phi->is_int32() ? Range::Int32() : Range::All());
+    }
+  }
+
   // Returns true if the loop reach a fixpoint.
   bool ProcessLoopPhisBackedge(BasicBlock* block, BasicBlock* backedge_pred) {
     if (!block->has_phi()) return true;
diff --git a/test/mjsunit/regress/regress-570203091-1.js b/test/mjsunit/regress/regress-570203091-1.js
new file mode 100644
index 0000000..2ac6c40
--- /dev/null
+++ b/test/mjsunit/regress/regress-570203091-1.js
@@ -0,0 +1,29 @@
+// 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: --turbolev --allow-natives-syntax
+
+let sink = 0;
+function* gen() {
+  try { unknown_global; } catch {}
+  let x = 0;
+  for (;;) {
+    if (x === 0) {
+      sink = 1;
+    } else {
+      sink = 2;
+    }
+    const is_zero = x === 0 ? true : false;
+    x = (yield is_zero).length;
+  }
+}
+
+%PrepareFunctionForOptimization(gen);
+const g = gen();
+g.next();     // x = 0
+g.next("a");  // x = 1
+g.next("");   // x = 0
+
+%OptimizeFunctionOnNextCall(gen);
+assertEquals(false, g.next("hello").value);
diff --git a/test/mjsunit/regress/regress-570203091-2.js b/test/mjsunit/regress/regress-570203091-2.js
new file mode 100644
index 0000000..f990372
--- /dev/null
+++ b/test/mjsunit/regress/regress-570203091-2.js
@@ -0,0 +1,35 @@
+// 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: --turbolev --allow-natives-syntax
+
+let sink = 0;
+const arr = [1.1, 2.2];
+const victim = [3.3];
+
+function* gen() {
+  try { unknown_global; } catch {}
+  let x = 0;
+  for (;;) {
+    if (x === 0) {
+      sink = 1;
+    } else {
+      sink = 2;
+    }
+    if (arr.length <= 0) return;
+    arr[x] = 1.5;
+    x = (yield).length;
+  }
+}
+
+%PrepareFunctionForOptimization(gen);
+const g = gen();
+g.next();     // x = 0
+g.next("a");  // x = 1
+g.next("");   // x = 0
+
+%OptimizeFunctionOnNextCall(gen);
+g.next("abcdef");
+assertEquals(3.3, victim[0]);
+assertEquals(7, arr.length);
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/test/mjsunit/regress/regress-570203091-1.js b/test/mjsunit/regress/regress-570203091-1.js
new file mode 100644
index 0000000..2ac6c40
--- /dev/null
+++ b/test/mjsunit/regress/regress-570203091-1.js
@@ -0,0 +1,29 @@
+// 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: --turbolev --allow-natives-syntax
+
+let sink = 0;
+function* gen() {
+  try { unknown_global; } catch {}
+  let x = 0;
+  for (;;) {
+    if (x === 0) {
+      sink = 1;
+    } else {
+      sink = 2;
+    }
+    const is_zero = x === 0 ? true : false;
+    x = (yield is_zero).length;
+  }
+}
+
+%PrepareFunctionForOptimization(gen);
+const g = gen();
+g.next();     // x = 0
+g.next("a");  // x = 1
+g.next("");   // x = 0
+
+%OptimizeFunctionOnNextCall(gen);
+assertEquals(false, g.next("hello").value);
diff --git a/test/mjsunit/regress/regress-570203091-2.js b/test/mjsunit/regress/regress-570203091-2.js
new file mode 100644
index 0000000..f990372
--- /dev/null
+++ b/test/mjsunit/regress/regress-570203091-2.js
@@ -0,0 +1,35 @@
+// 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: --turbolev --allow-natives-syntax
+
+let sink = 0;
+const arr = [1.1, 2.2];
+const victim = [3.3];
+
+function* gen() {
+  try { unknown_global; } catch {}
+  let x = 0;
+  for (;;) {
+    if (x === 0) {
+      sink = 1;
+    } else {
+      sink = 2;
+    }
+    if (arr.length <= 0) return;
+    arr[x] = 1.5;
+    x = (yield).length;
+  }
+}
+
+%PrepareFunctionForOptimization(gen);
+const g = gen();
+g.next();     // x = 0
+g.next("a");  // x = 1
+g.next("");   // x = 0
+
+%OptimizeFunctionOnNextCall(gen);
+g.next("abcdef");
+assertEquals(3.3, victim[0]);
+assertEquals(7, arr.length);
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.