Overview

Medium
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactRace in V8
DescriptionRace in V8
ComponentV8
Bug ClassRace
Tracker487768771
Fix commit02cd73dfc58d (v8/v8) +26/-15
CISA KEVNot listed
CreditedQYmag1c
Disclosed2026-04-07

Changed Functions

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

Files Changed

  • src/objects/elements.cc
  • src/objects/elements.h
  • src/objects/js-objects.cc
From 02cd73dfc58dbe73054382c85f9f126af9ce597e Mon Sep 17 00:00:00 2001
From: Marja Hölttä <marja@chromium.org>
Date: Mon, 02 Mar 2026 20:05:25 +0100
Subject: [PATCH] [RAB/GSAB] Object.values: Handle a TA grown by a background thread gracefully

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

diff --git a/src/objects/elements.cc b/src/objects/elements.cc
index 3be69a6..351a76c 100644
--- a/src/objects/elements.cc
+++ b/src/objects/elements.cc
@@ -1281,16 +1281,20 @@
   Maybe<bool> CollectValuesOrEntries(Isolate* isolate,
                                      DirectHandle<JSObject> object,
                                      DirectHandle<FixedArray> values_or_entries,
-                                     bool get_entries, uint32_t* nof_items,
+                                     uint32_t max_nof_items, bool get_entries,
+                                     uint32_t* nof_items,
                                      PropertyFilter filter) override {
-    return Subclass::CollectValuesOrEntriesImpl(
-        isolate, object, values_or_entries, get_entries, nof_items, filter);
+    auto to_return = Subclass::CollectValuesOrEntriesImpl(
+        isolate, object, values_or_entries, max_nof_items, get_entries,
+        nof_items, filter);
+    CHECK_LE(*nof_items, max_nof_items);
+    return to_return;
   }
 
   static Maybe<bool> CollectValuesOrEntriesImpl(
       Isolate* isolate, DirectHandle<JSObject> object,
-      DirectHandle<FixedArray> values_or_entries, bool get_entries,
-      uint32_t* nof_items, PropertyFilter filter) {
+      DirectHandle<FixedArray> values_or_entries, uint32_t max_nof_items,
+      bool get_entries, uint32_t* nof_items, PropertyFilter filter) {
     DCHECK_EQ(*nof_items, 0);
     KeyAccumulator accumulator(isolate, KeyCollectionMode::kOwnOnly,
                                ALL_PROPERTIES);
@@ -2881,8 +2885,8 @@
 
   static Maybe<bool> CollectValuesOrEntriesImpl(
       Isolate* isolate, DirectHandle<JSObject> object,
-      DirectHandle<FixedArray> values_or_entries, bool get_entries,
-      uint32_t* nof_items, PropertyFilter filter) {
+      DirectHandle<FixedArray> values_or_entries, uint32_t max_nof_items,
+      bool get_entries, uint32_t* nof_items, PropertyFilter filter) {
     uint32_t count = 0;
     if (get_entries) {
       // Collecting entries needs to allocate, so this code must be handlified.
@@ -3377,8 +3381,8 @@
 
   static Maybe<bool> CollectValuesOrEntriesImpl(
       Isolate* isolate, DirectHandle<JSObject> object,
-      DirectHandle<FixedArray> values_or_entries, bool get_entries,
-      uint32_t* nof_items, PropertyFilter filter) {
+      DirectHandle<FixedArray> values_or_entries, uint32_t max_nof_items,
+      bool get_entries, uint32_t* nof_items, PropertyFilter filter) {
     DirectHandle<FixedDoubleArray> elements(
         Cast<FixedDoubleArray>(object->elements()), isolate);
     uint32_t count = 0;
@@ -3717,12 +3721,17 @@
 
   static Maybe<bool> CollectValuesOrEntriesImpl(
       Isolate* isolate, DirectHandle<JSObject> object,
-      DirectHandle<FixedArray> values_or_entries, bool get_entries,
-      uint32_t* nof_items, PropertyFilter filter) {
+      DirectHandle<FixedArray> values_or_entries, uint32_t max_nof_items,
+      bool get_entries, uint32_t* nof_items, PropertyFilter filter) {
     uint32_t count = 0;
     if ((filter & ONLY_CONFIGURABLE) == 0) {
       DirectHandle<FixedArrayBase> elements(object->elements(), isolate);
       size_t length = AccessorClass::GetCapacityImpl(*object, *elements);
+      // The TypedArray might have been grown by a background thread. Handle it
+      // gracefully.
+      if (length > max_nof_items) {
+        length = max_nof_items;
+      }
       for (size_t index = 0; index < length; ++index) {
         DirectHandle<Object> value = AccessorClass::GetInternalImpl(
             isolate, object, InternalIndex(index));
diff --git a/src/objects/elements.h b/src/objects/elements.h
index f763961..c11363e 100644
--- a/src/objects/elements.h
+++ b/src/objects/elements.h
@@ -92,8 +92,9 @@
 
   virtual Maybe<bool> CollectValuesOrEntries(
       Isolate* isolate, DirectHandle<JSObject> object,
-      DirectHandle<FixedArray> values_or_entries, bool get_entries,
-      uint32_t* nof_items, PropertyFilter filter = ALL_PROPERTIES) = 0;
+      DirectHandle<FixedArray> values_or_entries, uint32_t max_nof_items,
+      bool get_entries, uint32_t* nof_items,
+      PropertyFilter filter = ALL_PROPERTIES) = 0;
 
   virtual MaybeHandle<FixedArray> PrependElementIndices(
       Isolate* isolate, DirectHandle<JSObject> object,
diff --git a/src/objects/js-objects.cc b/src/objects/js-objects.cc
index aa69662..8f1ac7e 100644
--- a/src/objects/js-objects.cc
+++ b/src/objects/js-objects.cc
@@ -2257,8 +2257,9 @@
 
   if (object->elements() != ReadOnlyRoots(isolate).empty_fixed_array()) {
     MAYBE_RETURN(object->GetElementsAccessor()->CollectValuesOrEntries(
-                     isolate, object, values_or_entries, get_entries, &count,
-                     ENUMERABLE_STRINGS),
+                     isolate, object, values_or_entries,
+                     static_cast<uint32_t>(number_of_own_elements), get_entries,
+                     &count, ENUMERABLE_STRINGS),
                  Nothing<bool>());
   }
 
Loading diff…

Original Bug Report

reported by qy...@gmail.com

Debug check failed: IsInBounds(index).

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

Root Cause

The root cause is inconsistent length snapshots within one logical operation, plus missing write-side capacity bounds.

  • Allocation uses old length (snapshot A)
  • Writing re-reads a new length (snapshot B, possibly larger)
  • When snapshot B > snapshot A, values_or_entries->set(count++, ...) writes out of bounds

This is a classic shared-memory TOCTOU problem under GSAB + length-tracking TypedArray concurrent growth.

Trigger Path and Call Chain

Key call chain:

  1. Object.values(ta) / Object.entries(ta) enters the fast path.
  2. FastGetOwnValuesOrEntries allocates the result array using one length snapshot:
    • src/objects/js-objects.cc:2240
    • src/objects/js-objects.cc:2249
  3. Then it enters TypedArray element collection:
    • src/objects/js-objects.cc:2254
    • src/objects/elements.cc:3719
  4. TypedElementsAccessor::CollectValuesOrEntriesImpl reads the current length again and writes in a loop:
    • Length read: src/objects/elements.cc:3726
    • Write site: src/objects/elements.cc:3733

In the PoC, a worker concurrently executes sab.grow(...), making step-2 allocation length smaller than step-4 write length, which causes 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

Summary

Debug check failed: IsInBounds(index).

Custom Questions

Type of crash:

tab

Crash state:

# Fatal error in ../../src/objects/fixed-array-inl.h, line 160
# Debug check failed: IsInBounds(index).
#
#
#
#FailureMessage Object: 0x7ffcc5f29308
==== C stack trace ===============================

    /home/qy/new2/v8/out/x64.debug/libv8_libbase.so(v8::base::debug::StackTrace::StackTrace()+0x29) [0x7d3f31b090e9]
    /home/qy/new2/v8/out/x64.debug/libv8_libplatform.so(+0x4e29d) [0x7d3f31a6a29d]
    /home/qy/new2/v8/out/x64.debug/libv8_libbase.so(V8_Fatal(char const*, int, char const*, ...)+0x205) [0x7d3f31add2f5]
    /home/qy/new2/v8/out/x64.debug/libv8_libbase.so(+0x53b8c) [0x7d3f31adcb8c]
    /home/qy/new2/v8/out/x64.debug/libv8_libbase.so(V8_Dcheck(char const*, int, char const*)+0x4d) [0x7d3f31add3ed]
    /home/qy/new2/v8/out/x64.debug/libv8.so(v8::internal::TaggedArrayBase<v8::internal::FixedArray, v8::internal::TaggedArrayShape, v8::internal::HeapObjectLayout>::set(unsigned int, v8::internal::Tagged<v8::internal::Object>, v8::internal::WriteBarrierMode)+0x81) [0x7d3f2bbb9fe1]
    /home/qy/new2/v8/out/x64.debug/libv8.so(+0xa852a36) [0x7d3f2cc52a36]
    /home/qy/new2/v8/out/x64.debug/libv8.so(+0xa8508cb) [0x7d3f2cc508cb]
    /home/qy/new2/v8/out/x64.debug/libv8.so(v8::internal::FastGetOwnValuesOrEntries(v8::internal::Isolate*, v8::internal::DirectHandle<v8::internal::JSReceiver>, bool, v8::internal::Handle<v8::internal::FixedArray>*)+0x5f1) [0x7d3f2cdcc7f1]
    /home/qy/new2/v8/out/x64.debug/libv8.so(v8::internal::GetOwnValuesOrEntries(v8::internal::Isolate*, v8::internal::DirectHandle<v8::internal::JSReceiver>, v8::internal::PropertyFilter, bool, bool)+0x8b) [0x7d3f2cdcd89b]
    /home/qy/new2/v8/out/x64.debug/libv8.so(v8::internal::JSReceiver::GetOwnValues(v8::internal::Isolate*, v8::internal::DirectHandle<v8::internal::JSReceiver>, v8::internal::PropertyFilter, bool)+0x4a) [0x7d3f2cdce0ba]
    /home/qy/new2/v8/out/x64.debug/libv8.so(+0xaf3b29d) [0x7d3f2d33b29d]
    /home/qy/new2/v8/out/x64.debug/libv8.so(v8::internal::Runtime_ObjectValues(int, unsigned long*, v8::internal::Isolate*)+0x151) [0x7d3f2d33af21]
    /home/qy/new2/v8/out/x64.debug/libv8.so(+0x8c289bd) [0x7d3f2b0289bd]
Trace/breakpoint trap (core dumped)

Reporter credit:

QYmag1c

Additional Data

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

View on issue tracker