High patch-now ~ plausible CVSS 9.6 chrome Integer Overflow 🔧 Commit mapped 🧬 A size or count is computed in a type too narrow to hold it

Overview

High
Severity
9.6
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInteger overflow in V8
DescriptionInteger overflow in V8
ComponentV8
Sub-componentFeedback-vector
Bug ClassInteger Overflow
Action tierpatch-now — corruption primitive, web-reachable in default config (plausible) — upheld by independent review by AI
Computed by PatchGap's deterministic exploitability ruler from the fixing commit. patch-now = RCE-class & attacker-reachable in a default config, or exploited in the wild; watch = real security signal below that bar; noise = no security relevance.
Independent review by AIPlausible
Reviewer notes: Diff adds bounds CHECKs at SizeFor only; it does not show how an oversized length arises or any undersized-allocation caller. FeedbackVectorVerify is a heap verifier, not an allocation path. Calling the wrap 'exploitable' and the OOB read/write are stated as fact without diff support.
A second, independent AI reviewer (not a human) re-derived this analysis from the fixing commit and the vendor advisory, looking for errors.
Tracker556715288
Fix commit714b5478d3c4 (v8/v8) +27/-0
CWECWE-190 (Integer overflow)
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:H
CISA KEVNot listed
CreditedJihyeon Jeong (Compsec Lab, Seoul National University / Research Intern)
Reported2026-09-03 · bounty $1,000 (per vendor advisory)
Fix landed2026-09-07 (public git)
Disclosed2026-09-15
Patch gap8 days — fix public in source before the advisory

Background

`FeedbackVector`
a V8 heap object holding the inline-cache and profiling feedback slots for a function, laid out as a fixed header followed by a variable-length tail of tagged slots.
`FeedbackVector::SizeFor`
a constexpr helper that computes a vector’s total byte size from its slot length as OFFSET_OF_DATA_START(FeedbackVector) + length * kTaggedSize.
`kTaggedSize`
the byte width of a single tagged pointer slot, the multiplier applied to length when sizing the tail.
Integer overflow
an arithmetic result that exceeds the range of its signed 32-bit type, wrapping to a small or negative value and thereby understating the memory a caller should allocate.

Root Cause Analysis

Verify before building a PoC

This analysis is consistent with the fixing commit but not fully provable from it: part of the vulnerable path lives in code the fix commit does not touch. Confirm the trigger and the sink against the source before relying on it for exploit development.
What the commit does not establish: Diff adds bounds CHECKs at SizeFor only; it does not show how an oversized length arises or any undersized-allocation caller. FeedbackVectorVerify is a heap verifier, not an allocation path. Calling the wrap 'exploitable' and the OOB read/write are stated as fact without diff support.

FeedbackVector::SizeFor in src/objects/feedback-vector.h computed the object size as OFFSET_OF_DATA_START(FeedbackVector) + length * kTaggedSize using signed 32-bit int arithmetic with no bound on length. Because the function never checked that length was non-negative or within a representable maximum, a sufficiently large length made length * kTaggedSize overflow, so SizeFor returned a truncated (or negative) size that no longer described the actual number of slots. Callers using that undersized value to allocate the vector would then read and write the variable-length tail — as FeedbackVectorVerify does over raw_feedback_slots()[i] for i < len — past the end of the allocation.

The fix defines FeedbackVector::kMaxLength as (kMaxInt - kHeaderSize) / kTaggedSize, the largest length for which the size expression cannot overflow, and adds CHECK_GE(length, 0) and CHECK_LE(length, kMaxLength) to SizeFor, plus a matching CHECK_LE(len, kMaxLength) in FeedbackVectorVerify. These checks turn an exploitable wrap into a safe abort before any undersized allocation can occur.

Key insight
The core mistake was performing the size multiplication length * kTaggedSize in bounded signed int arithmetic without ever validating length, allowing overflow to produce a size smaller than the data it must hold; the fix bounds length to kMaxLength and asserts it in SizeFor so the product can never wrap.

Attack Path

  1. Reach vector allocation with attacker-influenced length Drive script execution that causes V8 to create or grow a FeedbackVector whose slot length is derived from an attacker-controllable count.
  2. Push length past the overflow threshold Supply a length large enough that length * kTaggedSize exceeds kMaxInt, so FeedbackVector::SizeFor returns a wrapped, undersized (possibly negative) byte size.
  3. Trigger an undersized allocation The truncated SizeFor result is used to allocate a FeedbackVector far smaller than the declared length implies.
  4. Access the oversized tail Slot iteration over raw_feedback_slots()[i] for indices up to the declared length reads and writes beyond the small allocation, corrupting adjacent heap objects.
  5. Escalate the corruption Groom the heap so the out-of-bounds slots overlap sensitive objects, converting the overflow into a controlled read/write primitive within the renderer.

Impact Assessment

An attacker who runs script in the V8 renderer process gains an out-of-bounds heap read/write stemming from an undersized FeedbackVector allocation, a primitive typically escalated toward memory corruption and renderer-process compromise. The precondition is the ability to steer a FeedbackVector length close to or past the overflow boundary; the multiplication’s wrap does the rest with no further checks in the vulnerable code. Exploitation is contained to the sandboxed renderer absent a further sandbox escape.

Changed Functions

FunctionChangeNotes
for
src/diagnostics/objects-debug.cc
modified
TEST_F
test/unittests/objects/feedback-vector-unittest.cc
modified

Files Changed

  • src/diagnostics/objects-debug.cc
  • test/unittests/objects/feedback-vector-unittest.cc
  • src/objects/feedback-vector.h

Audit Directions

  • Unbounded size arithmetic
    Audit other SizeFor/OffsetOfElementAt helpers that compute header + length * elementSize in signed int without a kMaxLength bound, since any of them can wrap on an attacker-influenced length.
  • Length checks split from allocation
    Look for object types where the length is validated at allocation but not inside the shared size helper (or vice versa), leaving a path that reaches the multiplication with an unchecked count.
  • Verifier gaps
    Compare each object’s Verify routine against its length invariants; a missing CHECK_LE(len, kMaxLength) like the one added here indicates a class whose tail can be iterated past its true bounds.
From 714b5478d3c4ff055570eebe0ee9194e518ad21e Mon Sep 17 00:00:00 2001
From: Leszek Swirski <leszeks@chromium.org>
Date: Mon, 07 Sep 2026 14:19:53 +0200
Subject: [PATCH] [feedback-vector] Check length in FeedbackVector::SizeFor

Add FeedbackVector::kMaxLength and CHECK that the vector length
is non-negative and does not exceed kMaxLength in SizeFor().
Also add a length check in FeedbackVectorVerify().

TAG=agy
CONV=5e1a1d9b-ed48-4b56-91fb-9d3ead86c7d2

Fixed: 556715288
Change-Id: Ib88573d9bef7c364663538aa3801a5ded7eef182
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/8366705
Reviewed-by: Patrick Thier <pthier@chromium.org>
Commit-Queue: Patrick Thier <pthier@chromium.org>
Auto-Submit: Leszek Swirski <leszeks@chromium.org>
Cr-Commit-Position: refs/heads/main@{#109703}
---
diff --git a/src/diagnostics/objects-debug.cc b/src/diagnostics/objects-debug.cc
index 9ef6e1b..a28dd8c 100644
--- a/src/diagnostics/objects-debug.cc
+++ b/src/diagnostics/objects-debug.cc
@@ -1019,6 +1019,7 @@
   CHECK(IsFeedbackCell(parent_feedback_cell()));
   // Variable-length maybe-weak tail.
   const uint32_t len = length().value();
+  CHECK_LE(len, static_cast<uint32_t>(kMaxLength));
   for (uint32_t i = 0; i < len; ++i) {
     Tagged<MaybeObject> value = raw_feedback_slots()[i].Relaxed_Load();
     Object::VerifyMaybeObjectPointer(isolate, value);
diff --git a/src/objects/feedback-vector.h b/src/objects/feedback-vector.h
index 1f2971d..4f67694 100644
--- a/src/objects/feedback-vector.h
+++ b/src/objects/feedback-vector.h
@@ -513,6 +513,7 @@
 
   static const int kHeaderSize;
   static const int kRawFeedbackSlotsOffset;
+  static const int kMaxLength;
 
   static constexpr int SizeFor(int length);
   static constexpr int OffsetOfElementAt(int index);
@@ -565,8 +566,12 @@
     OFFSET_OF_DATA_START(FeedbackVector);
 inline constexpr int FeedbackVector::kRawFeedbackSlotsOffset =
     OFFSET_OF_DATA_START(FeedbackVector);
+inline constexpr int FeedbackVector::kMaxLength =
+    (kMaxInt - kHeaderSize) / kTaggedSize;
 
 constexpr int FeedbackVector::SizeFor(int length) {
+  CHECK_GE(length, 0);
+  CHECK_LE(length, kMaxLength);
   return OFFSET_OF_DATA_START(FeedbackVector) + length * kTaggedSize;
 }
 constexpr int FeedbackVector::OffsetOfElementAt(int index) {
diff --git a/test/unittests/objects/feedback-vector-unittest.cc b/test/unittests/objects/feedback-vector-unittest.cc
index a5acfac..84081da 100644
--- a/test/unittests/objects/feedback-vector-unittest.cc
+++ b/test/unittests/objects/feedback-vector-unittest.cc
@@ -783,5 +783,26 @@
   CHECK_EQ(InlineCacheState::MONOMORPHIC, nexus.ic_state());
 }
 
+TEST_F(FeedbackVectorTest, MaxLengthAndSizeFor) {
+  static_assert(FeedbackVector::SizeFor(0) == FeedbackVector::kHeaderSize);
+  static_assert(FeedbackVector::SizeFor(10) ==
+                FeedbackVector::kHeaderSize + 10 * kTaggedSize);
+  static_assert(FeedbackVector::SizeFor(FeedbackVector::kMaxLength) ==
+                FeedbackVector::kHeaderSize +
+                    FeedbackVector::kMaxLength * kTaggedSize);
+
+  EXPECT_EQ(FeedbackVector::kHeaderSize, FeedbackVector::SizeFor(0));
+  EXPECT_EQ(FeedbackVector::kHeaderSize + 10 * kTaggedSize,
+            FeedbackVector::SizeFor(10));
+  EXPECT_EQ(
+      FeedbackVector::kHeaderSize + FeedbackVector::kMaxLength * kTaggedSize,
+      FeedbackVector::SizeFor(FeedbackVector::kMaxLength));
+
+  ASSERT_DEATH_IF_SUPPORTED({ FeedbackVector::SizeFor(-1); }, ".*");
+  ASSERT_DEATH_IF_SUPPORTED(
+      { FeedbackVector::SizeFor(FeedbackVector::kMaxLength + 1); }, ".*");
+  ASSERT_DEATH_IF_SUPPORTED({ FeedbackVector::SizeFor(1 << 30); }, ".*");
+}
+
 }  // namespace internal
 }  // namespace v8
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/test/unittests/objects/feedback-vector-unittest.cc b/test/unittests/objects/feedback-vector-unittest.cc
index a5acfac..84081da 100644
--- a/test/unittests/objects/feedback-vector-unittest.cc
+++ b/test/unittests/objects/feedback-vector-unittest.cc
@@ -783,5 +783,26 @@
   CHECK_EQ(InlineCacheState::MONOMORPHIC, nexus.ic_state());
 }
 
+TEST_F(FeedbackVectorTest, MaxLengthAndSizeFor) {
+  static_assert(FeedbackVector::SizeFor(0) == FeedbackVector::kHeaderSize);
+  static_assert(FeedbackVector::SizeFor(10) ==
+                FeedbackVector::kHeaderSize + 10 * kTaggedSize);
+  static_assert(FeedbackVector::SizeFor(FeedbackVector::kMaxLength) ==
+                FeedbackVector::kHeaderSize +
+                    FeedbackVector::kMaxLength * kTaggedSize);
+
+  EXPECT_EQ(FeedbackVector::kHeaderSize, FeedbackVector::SizeFor(0));
+  EXPECT_EQ(FeedbackVector::kHeaderSize + 10 * kTaggedSize,
+            FeedbackVector::SizeFor(10));
+  EXPECT_EQ(
+      FeedbackVector::kHeaderSize + FeedbackVector::kMaxLength * kTaggedSize,
+      FeedbackVector::SizeFor(FeedbackVector::kMaxLength));
+
+  ASSERT_DEATH_IF_SUPPORTED({ FeedbackVector::SizeFor(-1); }, ".*");
+  ASSERT_DEATH_IF_SUPPORTED(
+      { FeedbackVector::SizeFor(FeedbackVector::kMaxLength + 1); }, ".*");
+  ASSERT_DEATH_IF_SUPPORTED({ FeedbackVector::SizeFor(1 << 30); }, ".*");
+}
+
 }  // namespace internal
 }  // namespace v8
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.