Overview

High
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactOut of bounds write in V8
DescriptionOut of bounds write in V8
ComponentV8
Bug ClassOOB
Tracker487746373
Fix commita9509c5e815d (v8/v8) +28/-20
CISA KEVNot listed
Creditedqymag1c
Disclosed2026-03-18

Changed Functions

FunctionChangeNotes
for
src/objects/elements.cc
modified
if
src/objects/elements.cc
modified

Files Changed

  • src/objects/elements.cc
From a9509c5e815de90dcbbf6b03af50fc069fd87b8f Mon Sep 17 00:00:00 2001
From: Marja Hölttä <marja@chromium.org>
Date: Tue, 03 Mar 2026 12:11:41 +0100
Subject: [PATCH] [RAB/GSAB] Object.keys: Handle a TA grown by a background thread gracefully

Fixed: 487746373
Change-Id: I24ddb75f34f4ea888e7679e9c250fcc2d2c23358
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/7623640
Commit-Queue: Marja Hölttä <marja@chromium.org>
Reviewed-by: Igor Sheludko <ishell@chromium.org>
Cr-Commit-Position: refs/heads/main@{#105573}
---

diff --git a/src/objects/elements.cc b/src/objects/elements.cc
index 351a76c..2a7a791 100644
--- a/src/objects/elements.cc
+++ b/src/objects/elements.cc
@@ -1387,12 +1387,17 @@
   static Handle<FixedArray> DirectCollectElementIndicesImpl(
       Isolate* isolate, DirectHandle<JSObject> object,
       DirectHandle<FixedArrayBase> backing_store, GetKeysConversion convert,
-      PropertyFilter filter, Handle<FixedArray> list, uint32_t* nof_indices,
-      uint32_t insertion_index = 0) {
+      PropertyFilter filter, Handle<FixedArray> list, uint32_t max_nof_indices,
+      uint32_t* nof_indices, uint32_t insertion_index = 0) {
     size_t length = Subclass::GetMaxIndex(*object, *backing_store);
     for (size_t i = 0; i < length; i++) {
       if (Subclass::HasElementImpl(isolate, *object, i, *backing_store,
                                    filter)) {
+        if (insertion_index >= max_nof_indices) {
+          // This might happen when the object is a TypedArray which was grown
+          // by a background thread.
+          break;
+        }
         if (convert == GetKeysConversion::kConvertToString) {
           // Avoid trashing the number to string cache with numbers that
           // are not likely to be needed.
@@ -1409,6 +1414,7 @@
       }
     }
     *nof_indices = insertion_index;
+    CHECK_LE(*nof_indices, max_nof_indices);
     return list;
   }
 
@@ -1425,15 +1431,15 @@
       DirectHandle<FixedArrayBase> backing_store, DirectHandle<FixedArray> keys,
       GetKeysConversion convert, PropertyFilter filter) {
     uint32_t nof_property_keys = keys->ulength().value();
-    size_t initial_list_length_szt =
+    size_t nof_elements_szt =
         Subclass::GetMaxNumberOfEntries(isolate, *object, *backing_store);
 
-    if (initial_list_length_szt > FixedArray::kMaxLength - nof_property_keys) {
+    if (nof_elements_szt > FixedArray::kMaxLength - nof_property_keys) {
       THROW_NEW_ERROR(isolate,
                       NewRangeError(MessageTemplate::kInvalidArrayLength));
     }
-    uint32_t initial_list_length =
-        static_cast<uint32_t>(initial_list_length_szt) + nof_property_keys;
+    uint32_t nof_elements = static_cast<uint32_t>(nof_elements_szt);
+    uint32_t initial_list_length = nof_elements + nof_property_keys;
 
     // Collect the element indices into a new list.
     DCHECK_LE(initial_list_length, std::numeric_limits<int>::max());
@@ -1450,9 +1456,9 @@
         // large-object space which doesn't free memory on shrinking the list.
         // Hence we try to estimate the final size for holey backing stores more
         // precisely here.
-        initial_list_length =
+        nof_elements =
             Subclass::NumberOfElementsImpl(isolate, *object, *backing_store);
-        initial_list_length += nof_property_keys;
+        initial_list_length = nof_elements + nof_property_keys;
       }
       DCHECK_LE(initial_list_length, std::numeric_limits<int>::max());
       combined_keys = isolate->factory()->NewFixedArray(initial_list_length);
@@ -1464,7 +1470,7 @@
     combined_keys = Subclass::DirectCollectElementIndicesImpl(
         isolate, object, backing_store,
         needs_sorting ? GetKeysConversion::kKeepNumbers : convert, filter,
-        combined_keys, &nof_indices);
+        combined_keys, nof_elements, &nof_indices);
 
     if (needs_sorting) {
       SortIndices(isolate, combined_keys, nof_indices);
@@ -1922,8 +1928,8 @@
   static Handle<FixedArray> DirectCollectElementIndicesImpl(
       Isolate* isolate, DirectHandle<JSObject> object,
       DirectHandle<FixedArrayBase> backing_store, GetKeysConversion convert,
-      PropertyFilter filter, Handle<FixedArray> list, uint32_t* nof_indices,
-      uint32_t insertion_index = 0) {
+      PropertyFilter filter, Handle<FixedArray> list, uint32_t max_nof_indices,
+      uint32_t* nof_indices, uint32_t insertion_index = 0) {
     if (filter & SKIP_STRINGS) return list;
 
     auto dictionary = Cast<NumberDictionary>(backing_store);
@@ -1935,6 +1941,7 @@
       insertion_index++;
     }
     *nof_indices = insertion_index;
+    CHECK_LE(*nof_indices, max_nof_indices);
     return list;
   }
 
@@ -5236,12 +5243,13 @@
       DirectHandle<JSObject> object, DirectHandle<FixedArrayBase> backing_store,
       KeyAccumulator* keys) {
     Isolate* isolate = keys->isolate();
+    uint32_t max_nof_indices = GetCapacityImpl(*object, *backing_store);
     uint32_t nof_indices = 0;
-    Handle<FixedArray> indices = isolate->factory()->NewFixedArray(
-        GetCapacityImpl(*object, *backing_store));
-    DirectCollectElementIndicesImpl(isolate, object, backing_store,
-                                    GetKeysConversion::kKeepNumbers,
-                                    ENUMERABLE_STRINGS, indices, &nof_indices);
+    Handle<FixedArray> indices =
+        isolate->factory()->NewFixedArray(max_nof_indices);
+    DirectCollectElementIndicesImpl(
+        isolate, object, backing_store, GetKeysConversion::kKeepNumbers,
+        ENUMERABLE_STRINGS, indices, max_nof_indices, &nof_indices);
     SortIndices(isolate, indices, nof_indices);
     for (uint32_t i = 0; i < nof_indices; i++) {
       RETURN_FAILURE_IF_NOT_SUCCESSFUL(keys->AddKey(indices->get(i)));
@@ -5252,8 +5260,8 @@
   static Handle<FixedArray> DirectCollectElementIndicesImpl(
       Isolate* isolate, DirectHandle<JSObject> object,
       DirectHandle<FixedArrayBase> backing_store, GetKeysConversion convert,
-      PropertyFilter filter, Handle<FixedArray> list, uint32_t* nof_indices,
-      uint32_t insertion_index = 0) {
+      PropertyFilter filter, Handle<FixedArray> list, uint32_t max_nof_indices,
+      uint32_t* nof_indices, uint32_t insertion_index = 0) {
     auto elements = Cast<SloppyArgumentsElements>(backing_store);
     uint32_t length = elements->ulength().value();
 
@@ -5272,8 +5280,8 @@
 
     DirectHandle<FixedArray> store(elements->arguments(), isolate);
     return ArgumentsAccessor::DirectCollectElementIndicesImpl(
-        isolate, object, store, convert, filter, list, nof_indices,
-        insertion_index);
+        isolate, object, store, convert, filter, list, max_nof_indices,
+        nof_indices, insertion_index);
   }
 
   static Maybe<bool> IncludesValueImpl(Isolate* isolate,
Loading diff…

Original Bug Report

reported by qy...@gmail.com

Concurrent GSAB Growth Causes OOB Write in Key Enumeration

Steps to reproduce the problem

run with: Download worker.js and poc.js and put them in the same directory. d8 poc.js

Problem Description

Foreword

Since this vulnerability and the previous report https://issues.chromium.org/issues/487768771 were introduced in the same commit, I suspect they will most likely be assigned to the same Google developer.

To prevent unnecessary issue merging, please allow me to clarify here that these two vulnerabilities are not caused by the same code. This issue is in the key enumeration path, not the values/entries path.

Thank you for reading.

Root Cause

TOCTOU length mismatch in the key-enumeration pipeline, compounded by missing tail-reservation enforcement, leading to OOB writes.

  • PrependElementIndicesImpl size estimation uses snapshot A
  • DirectCollectElementIndicesImpl iteration uses snapshot B
  • If B > A, list->set(insertion_index, ...) can exceed list bounds

Additional risk in this path:

  • nof_indices can consume space reserved for nof_property_keys
  • later CopyObjectToObjectElements(... nof_indices, nof_property_keys) can become OOB copy as well

Trigger Path and Call Chain

This issue is in the key enumeration path, not the values/entries path.

Key chain:

  1. Object.keys(ta) / for-in / JSON.stringify(ta) enters key collection.
  2. PrependElementIndicesImpl estimates size and allocates using one length snapshot:
    • src/objects/elements.cc:1426
    • src/objects/elements.cc:1438 / src/objects/elements.cc:1455
  3. DirectCollectElementIndicesImpl reads length again and writes:
    • Length read: src/objects/elements.cc:1389
    • Write sites: src/objects/elements.cc:1399 / src/objects/elements.cc:1403
  4. Concurrent sab.grow(...) makes second length exceed first allocation, causing OOB writes.

Additional Comments

Introduced by commit

commit  3160edf011b11347dd741c1c09a7fcb57bb479c4
[rab/gsab] ResizableArrayBuffer / GrowableSharedArrayBuffer part 1

Detailed list of changes:
https://docs.google.com/document/d/15i4-SZDzFDW7FfclIYuZEhFn-q-KpobCBy23x9zZZLc/edit?usp=sharing

Bug: v8:11111
Change-Id: I931003bd4552cf91d57de95af04a427a9e6d6ac9

I will provide poc_debug.js in the comments section. If you encounter issues with ASAN version verification, please use the poc_debug.js.

Summary

Concurrent GSAB Growth Causes OOB Write in Key Enumeration

Custom Questions

Type of crash:

tab

Crash state:

To differentiate this from the report at https://issues.chromium.org/issues/487768771, a pocjs script that can cause the release version to crash is constructed here. This out-of-bounds write might trigger a check, and running it multiple times can trigger crashes in different locations.

Received signal 11 SEGV_ACCERR 6b3b69241848

==== C stack trace ===============================

out/x64.asan/d8(__interceptor_backtrace+0x46)[0x5fe982ffeb36]
out/x64.asan/d8(+0x17b06e0)[0x5fe9834416e0]
/lib/x86_64-linux-gnu/libc.so.6(+0x45330)[0x707cbe445330]
out/x64.asan/d8(+0x278bd26)[0x5fe98441cd26]
out/x64.asan/d8(+0x278a5c6)[0x5fe98441b5c6]
out/x64.asan/d8(+0x2684995)[0x5fe984315995]
out/x64.asan/d8(+0x26a936f)[0x5fe98433a36f]
out/x64.asan/d8(+0x6610fb6)[0x5fe9882a1fb6]
[end of stack trace]
Segmentation fault

Reporter credit:

QYmag1c

Additional Data

Category: Security
Chrome Channel: Not sure
Regression: N/A \

View on issue tracker