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
Sub-componentHeap
Bug ClassUAF
Tracker567538973
Fix commit55dcff77d1bd (v8/v8) +61/-9
CISA KEVNot listed
CreditedXinyang Ge (Anthropic), assisted by Claude
Disclosed2026-10-06

Changed Functions

FunctionChangeNotes
for
test/mjsunit/regress/regress-567538973.js
modified

Files Changed

  • src/objects/js-objects.cc
  • test/mjsunit/regress/regress-567538973.js
From 55dcff77d1bd944c6d026f7b02e33d49e9f069c6 Mon Sep 17 00:00:00 2001
From: Omer Katz <[email protected]>
Date: Wed, 30 Sep 2026 17:31:34 +0200
Subject: [PATCH] [heap] Fix GetHash for JSWeakRef weak handling in Scavenger

For JSReceiver objects, we get the hash through a properties object.
We could have a JSReceiver unregister token that's dead, but its
properties object is still live. In that case, getting the hash for the
token will try to check the properties object map, but the map can be a
forwarding pointer.

The forwarding pointer wasn't previously resolved since scavenger never
visited the token object.

Fix by resolving forwarding pointer if we observe them during weakness
clearing.

Bug: 567538973
Change-Id: I9c9ef717a2bacb958f297fd2e75ea0736c05f316
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/8494173
Reviewed-by: Dominik Inführ <[email protected]>
Commit-Queue: Omer Katz <[email protected]>
Cr-Commit-Position: refs/heads/main@{#110209}
---
diff --git a/src/objects/js-objects.cc b/src/objects/js-objects.cc
index 56c97b9..3d31d0a 100644
--- a/src/objects/js-objects.cc
+++ b/src/objects/js-objects.cc
@@ -55,6 +55,7 @@
 #include "src/objects/js-shadow-realm.h"
 #include "src/objects/js-shared-array.h"
 #include "src/objects/js-struct.h"
+#include "src/objects/map-word.h"
 #include "src/objects/object-conversions-inl.h"
 #include "src/objects/property-details.h"
 #ifdef V8_TEMPORAL_SUPPORT
@@ -867,23 +868,31 @@
     return Smi::ToInt(properties);
   }
 
-  if (IsPropertyArray(properties)) {
-    return Cast<PropertyArray>(properties)->Hash();
+  Tagged<HeapObject> properties_object = Cast<HeapObject>(properties);
+  if (MapWord properties_object_map = properties_object->map_word(kRelaxedLoad);
+      properties_object_map.IsForwardingAddress()) {
+    properties_object =
+        properties_object_map.ToForwardingAddress(properties_object);
+    DCHECK(!properties_object->map_word(kRelaxedLoad).IsForwardingAddress());
   }
 
-  if (IsPropertyDictionary(properties)) {
-    return Cast<PropertyDictionary>(properties)->Hash();
+  if (IsPropertyArray(properties_object)) {
+    return Cast<PropertyArray>(properties_object)->Hash();
   }
 
-  if (IsGlobalDictionary(properties)) {
-    return Cast<GlobalDictionary>(properties)->Hash();
+  if (IsPropertyDictionary(properties_object)) {
+    return Cast<PropertyDictionary>(properties_object)->Hash();
+  }
+
+  if (IsGlobalDictionary(properties_object)) {
+    return Cast<GlobalDictionary>(properties_object)->Hash();
   }
 
 #ifdef DEBUG
   ReadOnlyRoots roots = GetReadOnlyRoots();
-  DCHECK(properties == roots.empty_fixed_array() ||
-         properties == roots.empty_property_dictionary() ||
-         properties == roots.empty_swiss_property_dictionary());
+  DCHECK(properties_object == roots.empty_fixed_array() ||
+         properties_object == roots.empty_property_dictionary() ||
+         properties_object == roots.empty_swiss_property_dictionary());
 #endif
 
   return PropertyArray::kNoHashSentinel;
diff --git a/test/mjsunit/regress/regress-567538973.js b/test/mjsunit/regress/regress-567538973.js
new file mode 100644
index 0000000..3b7a353
--- /dev/null
+++ b/test/mjsunit/regress/regress-567538973.js
@@ -0,0 +1,43 @@
+// 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
+
+const reg = new FinalizationRegistry(() => {});
+const keep = {};
+let sink = null;
+
+function mk(i) {
+  const o = {};
+  o.a0 = i;
+  o.a1 = i;
+  o.a2 = i;
+  o.a3 = i;
+  o.a4 = i;
+  o.a5 = i;
+  o.a6 = i;
+  o.a7 = i;
+  return o;
+}
+
+function f(i) {
+  const o = mk(i);
+  reg.register(keep, i, o);
+  const x = o.a5;
+  const arr = new Array(4000);
+  const y = o.a6;
+  sink = arr;
+  return x + y;
+}
+
+%PrepareFunctionForOptimization(mk);
+%PrepareFunctionForOptimization(f);
+for (let i = 0; i < 10; i++) {
+  f(i);
+}
+%OptimizeFunctionOnNextCall(f);
+let s = 0;
+for (let i = 0; i < 2000; i++) {
+  s += f(i);
+}
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/test/mjsunit/regress/regress-567538973.js b/test/mjsunit/regress/regress-567538973.js
new file mode 100644
index 0000000..3b7a353
--- /dev/null
+++ b/test/mjsunit/regress/regress-567538973.js
@@ -0,0 +1,43 @@
+// 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
+
+const reg = new FinalizationRegistry(() => {});
+const keep = {};
+let sink = null;
+
+function mk(i) {
+  const o = {};
+  o.a0 = i;
+  o.a1 = i;
+  o.a2 = i;
+  o.a3 = i;
+  o.a4 = i;
+  o.a5 = i;
+  o.a6 = i;
+  o.a7 = i;
+  return o;
+}
+
+function f(i) {
+  const o = mk(i);
+  reg.register(keep, i, o);
+  const x = o.a5;
+  const arr = new Array(4000);
+  const y = o.a6;
+  sink = arr;
+  return x + y;
+}
+
+%PrepareFunctionForOptimization(mk);
+%PrepareFunctionForOptimization(f);
+for (let i = 0; i < 10; i++) {
+  f(i);
+}
+%OptimizeFunctionOnNextCall(f);
+let s = 0;
+for (let i = 0; i < 2000; i++) {
+  s += f(i);
+}
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.