High chrome UAF 🔧 Commit mapped

Overview

High
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in V8
DescriptionUse after free in V8
ComponentV8
Bug ClassUAF
Tracker550360762
Fix commitdd7e9b5ef6d7 (v8/v8) +56/-2
CISA KEVNot listed
CreditedJihyeon Jeong (Compsec Lab, Seoul National University / Research Intern)
Disclosed2026-09-08

Changed Functions

FunctionChangeNotes
for
test/mjsunit/maglev/regress/regress-550360762.js
modified

Files Changed

  • src/maglev/maglev-graph-builder.cc
  • src/maglev/maglev-graph-builder.h
  • test/mjsunit/maglev/regress/regress-550360762.js
From dd7e9b5ef6d7b32fc5752b88ec84352b4903b821 Mon Sep 17 00:00:00 2001
From: Marco Vitale <mrcvtl@chromium.org>
Date: Mon, 31 Aug 2026 14:03:34 +0000
Subject: [PATCH] [maglev] Invalidate for-in receiver map check on inlined map changes

When inlining a call inside an active for-in loop, operations in the
inlinee may invalidate object maps. Previously, BuildEagerInlineCall
copied the inlinee's final receiver_needs_map_check into the caller.
If the inlinee iterated another object or validated hasOwnProperty,
receiver_needs_map_check was reset to false, erroneously clearing
 the caller's requirement to re-check its own receiver map.

Track whether map mutations occurred during inlining with
may_have_changed_maps. In BuildEagerInlineCall, invalidate the caller's
receiver map cache if any map change was observed in the inlinee.

Fixed: 550360762
Change-Id: I7adcf8f479a162d82fb13b1b2f607ad54d8996ae
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/8322427
Commit-Queue: Marco Vitale <mrcvtl@chromium.org>
Reviewed-by: Darius Mercadier <dmercadier@chromium.org>
Cr-Commit-Position: refs/heads/main@{#109603}
---

diff --git a/src/maglev/maglev-graph-builder.cc b/src/maglev/maglev-graph-builder.cc
index 9e0916a..77acb55 100644
--- a/src/maglev/maglev-graph-builder.cc
+++ b/src/maglev/maglev-graph-builder.cc
@@ -7808,8 +7808,10 @@
   // Propagate frame information back to the caller.
   current_interpreter_frame_.set_known_node_aspects(
       inner_graph_builder.current_interpreter_frame_.known_node_aspects());
-  current_for_in_state.receiver_needs_map_check =
-      inner_graph_builder.current_for_in_state.receiver_needs_map_check;
+  if (inner_graph_builder.may_have_changed_maps()) {
+    may_have_changed_maps_ = true;
+    current_for_in_state.receiver_needs_map_check = true;
+  }
 
   // Resume execution using the final block of the inner builder.
   inner_graph_builder.reducer_.FlushNodesToBlock();
diff --git a/src/maglev/maglev-graph-builder.h b/src/maglev/maglev-graph-builder.h
index fc531f9..b7daff4 100644
--- a/src/maglev/maglev-graph-builder.h
+++ b/src/maglev/maglev-graph-builder.h
@@ -1986,6 +1986,9 @@
 
   // When set, inline only small functions.
   bool only_inline_small_ = false;
+
+  bool may_have_changed_maps() const { return may_have_changed_maps_; }
+  bool may_have_changed_maps_ = false;
 };
 
 template <bool is_possible_map_change>
@@ -1996,6 +1999,7 @@
   // TODO(leszeks): Track this on merge states / known node aspects, rather
   // than on the graph, so that it can survive control flow.
   if constexpr (is_possible_map_change) {
+    may_have_changed_maps_ = true;
     current_for_in_state.receiver_needs_map_check = true;
   }
 }
diff --git a/test/mjsunit/maglev/regress/regress-550360762.js b/test/mjsunit/maglev/regress/regress-550360762.js
new file mode 100644
index 0000000..7e8a663
--- /dev/null
+++ b/test/mjsunit/maglev/regress/regress-550360762.js
@@ -0,0 +1,48 @@
+// 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: --maglev --maglev-inlining --allow-natives-syntax
+
+const probe = {a: 1};
+
+function Receiver(v) {
+  this.p0 = 10;
+  this.p1 = 11;
+  this.p2 = 12;
+  this.p3 = 13;
+  this.x = v;
+  this.y = 99;
+  for (let i = 0; i < 4; ++i) Object.defineProperty(this, 'p' + i, {enumerable: false});
+}
+
+function erasePressureThenRevalidate(victim, other) {
+  delete victim.x;
+  let key;
+  OUT: {
+    for (key in other) break OUT;
+    throw 0;
+  }
+  return other.hasOwnProperty(key);
+}
+
+function vulnerable(victim, other) {
+  for (let key in victim) {
+    erasePressureThenRevalidate(victim, other);
+    return victim[key];
+  }
+  return -1;
+}
+
+%PrepareFunctionForOptimization(Receiver);
+%PrepareFunctionForOptimization(erasePressureThenRevalidate);
+%PrepareFunctionForOptimization(vulnerable);
+
+for (let i = 0; i < 2; ++i) {
+  vulnerable(new Receiver(0), probe);
+}
+%OptimizeMaglevOnNextCall(vulnerable);
+vulnerable(new Receiver(0), probe);
+
+const victim = new Receiver(1337);
+assertEquals(undefined, vulnerable(victim, probe));
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/test/mjsunit/maglev/regress/regress-550360762.js b/test/mjsunit/maglev/regress/regress-550360762.js
new file mode 100644
index 0000000..7e8a663
--- /dev/null
+++ b/test/mjsunit/maglev/regress/regress-550360762.js
@@ -0,0 +1,48 @@
+// 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: --maglev --maglev-inlining --allow-natives-syntax
+
+const probe = {a: 1};
+
+function Receiver(v) {
+  this.p0 = 10;
+  this.p1 = 11;
+  this.p2 = 12;
+  this.p3 = 13;
+  this.x = v;
+  this.y = 99;
+  for (let i = 0; i < 4; ++i) Object.defineProperty(this, 'p' + i, {enumerable: false});
+}
+
+function erasePressureThenRevalidate(victim, other) {
+  delete victim.x;
+  let key;
+  OUT: {
+    for (key in other) break OUT;
+    throw 0;
+  }
+  return other.hasOwnProperty(key);
+}
+
+function vulnerable(victim, other) {
+  for (let key in victim) {
+    erasePressureThenRevalidate(victim, other);
+    return victim[key];
+  }
+  return -1;
+}
+
+%PrepareFunctionForOptimization(Receiver);
+%PrepareFunctionForOptimization(erasePressureThenRevalidate);
+%PrepareFunctionForOptimization(vulnerable);
+
+for (let i = 0; i < 2; ++i) {
+  vulnerable(new Receiver(0), probe);
+}
+%OptimizeMaglevOnNextCall(vulnerable);
+vulnerable(new Receiver(0), probe);
+
+const victim = new Receiver(1337);
+assertEquals(undefined, vulnerable(victim, probe));
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.