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-componentIC
Bug ClassUAF
Tracker566091009
Fix commit243b68bea40f (v8/v8) +88/-12
CISA KEVNot listed
Creditedzenneth (randomguy6407)
Disclosed2026-10-20

Changed Functions

FunctionChangeNotes
if
src/ic/ic.cc
modified
set
test/mjsunit/regress/regress-566091009.js
modified

Files Changed

  • src/ic/ic.cc
  • test/mjsunit/regress/regress-566091009.js
From 243b68bea40f3165551ee9fe0cc51826b81d7efa Mon Sep 17 00:00:00 2001
From: Igor Sheludko <ishell@chromium.org>
Date: Fri, 09 Oct 2026 11:52:18 +0200
Subject: [PATCH] [ic] Check access check and array prototype chain in element handlers

Ensure that element handler computation in KeyedLoadIC::LoadElementHandler
and KeyedStoreIC::StoreElementHandler returns slow handlers for receiver
maps that require access checks or belong to the Array prototype chain.

Fixed: 566091009

TAG=agy
CONV=be615bab-5e7a-4aa3-a0cc-c6325a9e2477

Change-Id: Idff0ae62aa44fd67c69dbf1940720b61203de5b9
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/8547996
Commit-Queue: Igor Sheludko <ishell@chromium.org>
Auto-Submit: Igor Sheludko <ishell@chromium.org>
Reviewed-by: Toon Verwaest <verwaest@chromium.org>
Commit-Queue: Toon Verwaest <verwaest@chromium.org>
Cr-Commit-Position: refs/heads/main@{#110514}
---
diff --git a/src/ic/ic.cc b/src/ic/ic.cc
index 75f487b..857564c 100644
--- a/src/ic/ic.cc
+++ b/src/ic/ic.cc
@@ -1761,17 +1761,6 @@
 Handle<Object> KeyedLoadIC::LoadElementHandler(
     DirectHandle<Map> receiver_map, KeyedAccessLoadMode new_load_mode,
     MaybeDirectHandle<Map> maybe_transition_target) {
-  // Has a getter interceptor, or is any has and has a query interceptor.
-  if (receiver_map->has_indexed_interceptor() &&
-      (receiver_map->GetIndexedInterceptor()->has_getter() ||
-       (IsAnyHas() && receiver_map->GetIndexedInterceptor()->has_query())) &&
-      !receiver_map->GetIndexedInterceptor()->non_masking()) {
-    // TODO(jgruber): Update counter name.
-    TRACE_HANDLER_STATS(isolate(), KeyedLoadIC_LoadIndexedInterceptorStub);
-    return IsAnyHas() ? BUILTIN_CODE(isolate(), HasIndexedInterceptorIC)
-                      : BUILTIN_CODE(isolate(), LoadIndexedInterceptorIC);
-  }
-
   InstanceType instance_type = receiver_map->instance_type();
   if (instance_type < FIRST_NONSTRING_TYPE) {
     TRACE_HANDLER_STATS(isolate(), KeyedLoadIC_LoadIndexedStringDH);
@@ -1785,6 +1774,23 @@
   if (instance_type == JS_PROXY_TYPE) {
     return LoadHandler::LoadProxy(isolate());
   }
+  if (receiver_map->is_access_check_needed()) {
+    TRACE_HANDLER_STATS(isolate(), KeyedLoadIC_SlowStub);
+    return LoadHandler::LoadSlow(isolate());
+  }
+
+  // Has a getter interceptor, or is any has and has a query interceptor.
+  if (receiver_map->has_indexed_interceptor() &&
+      (receiver_map->GetIndexedInterceptor()->has_getter() ||
+       (IsAnyHas() && receiver_map->GetIndexedInterceptor()->has_query())) &&
+      !receiver_map->GetIndexedInterceptor()->non_masking()) {
+    DCHECK(!receiver_map->is_access_check_needed());
+    // TODO(jgruber): Update counter name.
+    TRACE_HANDLER_STATS(isolate(), KeyedLoadIC_LoadIndexedInterceptorStub);
+    return IsAnyHas() ? BUILTIN_CODE(isolate(), HasIndexedInterceptorIC)
+                      : BUILTIN_CODE(isolate(), LoadIndexedInterceptorIC);
+  }
+
 #if V8_ENABLE_WEBASSEMBLY
   if (InstanceTypeChecker::IsWasmObject(instance_type)) {
     // TODO(jgruber): Update counter name.
@@ -2871,7 +2877,8 @@
 Handle<Object> KeyedStoreIC::StoreElementHandler(
     DirectHandle<Map> receiver_map, KeyedAccessStoreMode store_mode,
     MaybeDirectHandle<UnionOf<Smi, Cell>> prev_validity_cell) {
-  if (!IsJSObjectMap(*receiver_map)) {
+  if (!IsJSObjectMap(*receiver_map) || receiver_map->is_access_check_needed() ||
+      receiver_map->IsMapInArrayPrototypeChain(isolate())) {
     // DefineKeyedOwnIC, which is used to define computed fields in instances,
     // should handled by the slow stub below instead of the proxy stub.
     if (IsJSProxyMap(*receiver_map) && !IsDefineKeyedOwnIC()) {
diff --git a/test/mjsunit/regress/regress-566091009.js b/test/mjsunit/regress/regress-566091009.js
new file mode 100644
index 0000000..aaf8c21
--- /dev/null
+++ b/test/mjsunit/regress/regress-566091009.js
@@ -0,0 +1,69 @@
+// 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: --no-lazy-feedback-allocation --allow-natives-syntax
+
+(function TestAccessCheck() {
+  const target = d8.test.createAccessCheckedObject(true);
+  target[0] = "secret";
+  d8.test.setAccessPolicy(target, false);
+
+  const namedObj = { a: 1 };
+
+  // 1. KeyedLoadIC: transition from named property access to element access.
+  function read(obj, key) {
+    return obj[key];
+  }
+  read(namedObj, "a");
+  assertThrows(() => read(target, "a"), TypeError);
+  read([1.1], 0);
+  assertThrows(() => read(target, 0), TypeError);
+
+  // 2. KeyedHasIC: transition from named property access to element access.
+  function has(obj, key) {
+    return key in obj;
+  }
+  has(namedObj, "a");
+  assertThrows(() => has(target, "a"), TypeError);
+  has([1.1], 0);
+  assertThrows(() => has(target, 0), TypeError);
+
+  // 3. KeyedStoreIC: transition from named property access to element access.
+  function write(obj, key, val) {
+    obj[key] = val;
+  }
+  write(namedObj, "a", 2);
+  assertThrows(() => write(target, "a", 2), TypeError);
+  write([1.1], 0, 2.2);
+  assertThrows(() => write(target, 0, "overwritten"), TypeError);
+
+  d8.test.setAccessPolicy(target, true);
+  assertEquals("secret", target[0]);
+})();
+
+(function TestNoElementsProtector() {
+  function readHole(a) {
+    return a[0];
+  }
+  const holey = [, 1];
+  %PrepareFunctionForOptimization(readHole);
+  assertEquals(undefined, readHole(holey));
+  %OptimizeFunctionOnNextCall(readHole);
+  assertEquals(undefined, readHole(holey));
+
+  Object.defineProperty(Object.prototype, "prop", {
+    set(_) {},
+    configurable: true,
+  });
+  function write(o, k, v) {
+    o[k] = v;
+  }
+  write({ prop: 1 }, "prop", 1);
+  write(Array.prototype, "prop", 1);
+  write([], 0, 1);
+  write(Array.prototype, 0, "boom");
+
+  assertEquals("boom", holey[0]);
+  assertEquals("boom", readHole(holey));
+})();
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/test/mjsunit/regress/regress-566091009.js b/test/mjsunit/regress/regress-566091009.js
new file mode 100644
index 0000000..aaf8c21
--- /dev/null
+++ b/test/mjsunit/regress/regress-566091009.js
@@ -0,0 +1,69 @@
+// 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: --no-lazy-feedback-allocation --allow-natives-syntax
+
+(function TestAccessCheck() {
+  const target = d8.test.createAccessCheckedObject(true);
+  target[0] = "secret";
+  d8.test.setAccessPolicy(target, false);
+
+  const namedObj = { a: 1 };
+
+  // 1. KeyedLoadIC: transition from named property access to element access.
+  function read(obj, key) {
+    return obj[key];
+  }
+  read(namedObj, "a");
+  assertThrows(() => read(target, "a"), TypeError);
+  read([1.1], 0);
+  assertThrows(() => read(target, 0), TypeError);
+
+  // 2. KeyedHasIC: transition from named property access to element access.
+  function has(obj, key) {
+    return key in obj;
+  }
+  has(namedObj, "a");
+  assertThrows(() => has(target, "a"), TypeError);
+  has([1.1], 0);
+  assertThrows(() => has(target, 0), TypeError);
+
+  // 3. KeyedStoreIC: transition from named property access to element access.
+  function write(obj, key, val) {
+    obj[key] = val;
+  }
+  write(namedObj, "a", 2);
+  assertThrows(() => write(target, "a", 2), TypeError);
+  write([1.1], 0, 2.2);
+  assertThrows(() => write(target, 0, "overwritten"), TypeError);
+
+  d8.test.setAccessPolicy(target, true);
+  assertEquals("secret", target[0]);
+})();
+
+(function TestNoElementsProtector() {
+  function readHole(a) {
+    return a[0];
+  }
+  const holey = [, 1];
+  %PrepareFunctionForOptimization(readHole);
+  assertEquals(undefined, readHole(holey));
+  %OptimizeFunctionOnNextCall(readHole);
+  assertEquals(undefined, readHole(holey));
+
+  Object.defineProperty(Object.prototype, "prop", {
+    set(_) {},
+    configurable: true,
+  });
+  function write(o, k, v) {
+    o[k] = v;
+  }
+  write({ prop: 1 }, "prop", 1);
+  write(Array.prototype, "prop", 1);
+  write([], 0, 1);
+  write(Array.prototype, 0, "boom");
+
+  assertEquals("boom", holey[0]);
+  assertEquals("boom", readHole(holey));
+})();
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.