Overview

High
Severity
8.8
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInteger overflow in V8
DescriptionInteger overflow in V8
ComponentV8
Sub-componentBaseline
Bug ClassInteger Overflow
Action tierwatch — ub-class only — no demonstrated corruption path — revised 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 (tier revised from patch-now)
Reviewer notes: Unchecked int multiply is confirmed. The diff shows no undersized buffer being overrun; the sink is only a fatal OOM abort, so the corruption primitive is unsupported. The batch-compiler overflow only affects a compilation budget counter.
A second, independent AI reviewer (not a human) re-derived this analysis from the fixing commit and the vendor advisory, looking for errors.
Tracker515431687
Fix commitebf970d8a364 (v8/v8) +22/-9
CWECWE-472
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H
CISA KEVNot listed
CreditedGoogle
Reported2026-05-21 (per vendor advisory)
Fix landed2026-05-22 (public git)
Disclosed2026-06-02
Patch gap11 days — fix public in source before the advisory

Background

Baseline / Sparkplug compiler
V8’s fast, non-optimizing baseline tier that generates machine code straight from bytecode; BaselineCompiler::AllocateBuffer sizes the AssemblerBuffer it emits into.
`EstimateInstructionSize`
Estimates output code size as bytecode->length() * kAverageBytecodeToInstructionRatio, used both to size the assembler buffer and to accumulate a batch-compilation budget.
`Assembler::kMaximalBufferSize`
The upper bound on assembler buffer size; AllocateBuffer RoundUps the estimate to 4 KB and bails to FatalProcessOutOfMemory if it exceeds this cap.
`base::CheckedNumeric<int>`
A wrapper that tracks overflow through arithmetic and exposes it via AssignIfValid / ValueOrDefault, instead of silently wrapping like a raw int.

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: Unchecked int multiply is confirmed. The diff shows no undersized buffer being overrun; the sink is only a fatal OOM abort, so the corruption primitive is unsupported. The batch-compiler overflow only affects a compilation budget counter.

EstimateInstructionSize computed bytecode->length() * kAverageBytecodeToInstructionRatio in plain int, so for a sufficiently large BytecodeArray the multiplication overflowed and could produce a small or negative value. In AllocateBuffer that value was passed to RoundUp(estimated_size, 4 * KB) and then checked against Assembler::kMaximalBufferSize; an overflowed-small estimated_size passes the rounded_size > kMaximalBufferSize guard, so the code allocates an AssemblerBuffer far too small for the actual code the baseline compiler will emit. Baseline code generation then writes past the undersized buffer, a heap buffer overflow (inferred as the ultimate sink; the diff shows the size miscalculation and the now-added cap, not the write).

The fix makes EstimateInstructionSize return base::CheckedNumeric<int>, and AllocateBuffer now rejects the estimate with FatalProcessOutOfMemory when AssignIfValid fails (overflow) or when the raw value already exceeds kMaximalBufferSize, before any RoundUp. The batch compiler path is likewise hardened: estimated_instruction_size_ += estimated_size is replaced with CheckedNumeric accumulation clamped via ValueOrDefault(kMaxInt), so the running budget cannot overflow either.

Key insight
The core mistake was estimating code-buffer size with unchecked int multiplication, letting a huge bytecode array overflow the estimate to a small/negative value that slipped past the kMaximalBufferSize cap and produced an undersized assembler buffer; the fix routes the estimate through base::CheckedNumeric<int> and fatally aborts on overflow before allocation.

Attack Path

  1. Build a huge bytecode array Get JavaScript compiled to a BytecodeArray whose length() is large enough that length() * kAverageBytecodeToInstructionRatio overflows a 32-bit int.
  2. Trigger baseline compilation Warm the function so Sparkplug/baseline compiles it, calling AllocateBuffer → EstimateInstructionSize.
  3. Overflow the estimate past the cap The unchecked multiply wraps to a small/negative estimated_size, so RoundUp and the rounded_size > kMaximalBufferSize check are satisfied and an undersized AssemblerBuffer is allocated.
  4. Overflow the buffer during codegen Baseline code generation emits the real (large) amount of machine code into the too-small buffer, writing out of bounds on the heap (inferred; the diff adds the overflow detection rather than showing the write).

Impact Assessment

An attacker running script in the renderer can, with a very large function/bytecode array, cause the baseline compiler to allocate an undersized code buffer and overflow it during code generation, corrupting the V8/renderer heap; the diff demonstrates the size miscalculation and its guard, while the resulting write primitive and any RCE are (inferred). The precondition is only attacker JavaScript large enough to overflow the int estimate plus normal baseline-tier warm-up. The advisory classifies this as an integer overflow in V8 and does not state exploited-in-the-wild.

Changed Functions

FunctionChangeNotes
if
src/baseline/baseline-batch-compiler.cc
modified
if
src/baseline/baseline-compiler.cc
modified

Files Changed

  • src/baseline/baseline-batch-compiler.cc
  • src/baseline/baseline-compiler.cc
  • src/baseline/baseline-compiler.h

Audit Directions

  • Size/length estimates in `int`
    Audit code-size, buffer-size, and capacity estimates derived from BytecodeArray/object lengths times a constant; any raw int multiply feeding an allocation should use base::CheckedNumeric.
  • Guards defeated by prior overflow
    Look for > kMaximalBufferSize / cap checks that run after an unchecked arithmetic step, where a wrapped small/negative value passes the check.
  • Accumulated budgets
    Review running counters like estimated_instruction_size_ that sum per-item estimates over a batch; ensure summation is overflow-checked and clamped.
From ebf970d8a36498ea34eb9bf9fdfe8574c6bd8cf3 Mon Sep 17 00:00:00 2001
From: Leszek Swirski <[email protected]>
Date: Fri, 22 May 2026 14:46:48 +0200
Subject: [PATCH] [baseline] Fix integer overflow in instruction size estimation

Use base::CheckedNumeric<int> for EstimateInstructionSize to prevent
integer overflow when calculating estimated instruction size for very
large bytecode arrays.

Also update baseline batch compiler to handle CheckedNumeric and prevent
overflow of estimated_instruction_size_.

Bug: 515431687
Change-Id: Id2208f5d95d81bd99adcde656ebcabcee9f38cce
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/7866066
Auto-Submit: Leszek Swirski <[email protected]>
Commit-Queue: Patrick Thier <[email protected]>
Commit-Queue: Leszek Swirski <[email protected]>
Reviewed-by: Patrick Thier <[email protected]>
Cr-Commit-Position: refs/heads/main@{#107527}
---

diff --git a/src/baseline/baseline-batch-compiler.cc b/src/baseline/baseline-batch-compiler.cc
index ca0dbc9..11c2f01 100644
--- a/src/baseline/baseline-batch-compiler.cc
+++ b/src/baseline/baseline-batch-compiler.cc
@@ -7,6 +7,7 @@
 #include <algorithm>
 
 #include "src/base/fpu.h"
+#include "src/base/numerics/checked_math.h"
 #include "src/baseline/baseline-compiler.h"
 #include "src/codegen/compiler.h"
 #include "src/execution/isolate.h"
@@ -342,20 +343,23 @@
   if (shared->is_sparkplug_compiling()) return false;
   if (!CanCompileWithBaseline(isolate_, shared)) return false;
 
-  int estimated_size;
+  base::CheckedNumeric<int> estimated_size;
   {
     DisallowHeapAllocation no_gc;
     estimated_size = BaselineCompiler::EstimateInstructionSize(
         shared->GetBytecodeArray(isolate_));
   }
-  estimated_instruction_size_ += estimated_size;
+  int raw_estimated_size = estimated_size.ValueOrDefault(kMaxInt);
+  base::CheckedNumeric<int> new_size = estimated_instruction_size_;
+  new_size += raw_estimated_size;
+  estimated_instruction_size_ = new_size.ValueOrDefault(kMaxInt);
   if (v8_flags.trace_baseline_batch_compilation) {
     CodeTracer::Scope trace_scope(isolate_->GetCodeTracer());
     PrintF(trace_scope.file(), "[Baseline batch compilation] Enqueued SFI %s",
            shared->DebugNameCStr().get());
     PrintF(trace_scope.file(),
-           " with estimated size %d (current budget: %d/%d)\n", estimated_size,
-           estimated_instruction_size_,
+           " with estimated size %d (current budget: %d/%d)\n",
+           raw_estimated_size, estimated_instruction_size_,
            v8_flags.baseline_batch_compilation_threshold.value());
   }
   if (estimated_instruction_size_ >=
diff --git a/src/baseline/baseline-compiler.cc b/src/baseline/baseline-compiler.cc
index bdf8087..7ba2329 100644
--- a/src/baseline/baseline-compiler.cc
+++ b/src/baseline/baseline-compiler.cc
@@ -271,12 +271,17 @@
 #endif
 std::unique_ptr<AssemblerBuffer> AllocateBuffer(
     DirectHandle<BytecodeArray> bytecodes) {
-  int estimated_size;
+  base::CheckedNumeric<int> estimated_size;
   {
     DisallowHeapAllocation no_gc;
     estimated_size = BaselineCompiler::EstimateInstructionSize(*bytecodes);
   }
-  int rounded_size = RoundUp(estimated_size, 4 * KB);
+  int raw_estimated_size;
+  if (!estimated_size.AssignIfValid(&raw_estimated_size) ||
+      raw_estimated_size > Assembler::kMaximalBufferSize) {
+    V8::FatalProcessOutOfMemory(nullptr, "BaselineCompiler::AllocateBuffer");
+  }
+  int rounded_size = RoundUp(raw_estimated_size, 4 * KB);
   if (rounded_size > Assembler::kMaximalBufferSize) {
     V8::FatalProcessOutOfMemory(nullptr, "BaselineCompiler::AllocateBuffer");
   }
@@ -367,8 +372,10 @@
   return code_builder.TryBuild();
 }
 
-int BaselineCompiler::EstimateInstructionSize(Tagged<BytecodeArray> bytecode) {
-  return bytecode->length() * kAverageBytecodeToInstructionRatio;
+base::CheckedNumeric<int> BaselineCompiler::EstimateInstructionSize(
+    Tagged<BytecodeArray> bytecode) {
+  return base::CheckedNumeric<int>(bytecode->length()) *
+         kAverageBytecodeToInstructionRatio;
 }
 
 interpreter::Register BaselineCompiler::RegisterOperand(int operand_index) {
diff --git a/src/baseline/baseline-compiler.h b/src/baseline/baseline-compiler.h
index 14b3ead..23df4ef 100644
--- a/src/baseline/baseline-compiler.h
+++ b/src/baseline/baseline-compiler.h
@@ -6,6 +6,7 @@
 #define V8_BASELINE_BASELINE_COMPILER_H_
 
 #include "src/base/logging.h"
+#include "src/base/numerics/checked_math.h"
 #include "src/base/pointer-with-payload.h"
 #include "src/base/threaded-list.h"
 #include "src/base/vlq.h"
@@ -55,7 +56,8 @@
 
   void GenerateCode();
   MaybeHandle<Code> Build();
-  static int EstimateInstructionSize(Tagged<BytecodeArray> bytecode);
+  static base::CheckedNumeric<int> EstimateInstructionSize(
+      Tagged<BytecodeArray> bytecode);
 
  private:
   void Prologue();
Loading diff…

Original Bug Report

reported by [email protected]

Integer overflow in BaselineCompiler instruction size estimation

Project Fortify, an experimental security project, has identified the following potential security issue. If you’re a feature owner CC-ed on this bug, please do your best to review these reports. Please see https://chromium.googlesource.com/chromium/src/+/main/docs/security/ai-generated-security-bugs-faq.md for more information.

Overview: A potential signed integer overflow in V8’s Sparkplug (baseline) compiler during instruction size estimation could allow very large functions to bypass an initial size check. This leads to the allocation of a minimal buffer that subsequently grows until it hits a strictly enforced 512MB limit, resulting in a controlled process termination.

Affected files:

  • v8/src/codegen/x64/assembler-x64.cc
  • v8/src/baseline/baseline-compiler.cc
  • v8/src/codegen/assembler.cc
  • v8/src/codegen/arm64/assembler-arm64.cc
  • v8/src/baseline/baseline.cc
  • v8/src/codegen/compiler.cc
  • v8/src/execution/tiering-manager.cc

Estimated timestamp from git blame: 2021-04-21

Potential Vulnerability Summary

A potential signed integer overflow exists in the V8 baseline compiler’s initial buffer allocation logic. For functions with extremely large bytecode arrays, the estimated machine code size can overflow a 32-bit signed integer, bypassing a capacity check and leading to an inefficient initial allocation. However, current safeguards in the assembler’s buffer growth logic prevent this from escalating into a memory corruption vulnerability.

Technical Analysis

When V8’s Sparkplug compiler prepares to compile a function, it estimates the required instruction buffer size using BaselineCompiler::EstimateInstructionSize. On most architectures, this estimate is calculated by multiplying the BytecodeArray length by 7.

For functions with very large bytecode arrays (approximately 306.8 MiB or larger), the result of this multiplication can overflow a 32-bit signed integer, wrapping around to a negative value. In baseline::AllocateBuffer, this overflowed value is checked against Assembler::kMaximalBufferSize (512 MiB). Because the value is negative, the check is bypassed, and a call to NewAssemblerBuffer is made.

Inside the buffer allocation logic, the negative size is processed by std::max(kMinimalBufferSize, size), resulting in the allocation of a minimal 128-byte buffer. As the compiler begins to emit instructions into this buffer, it quickly triggers a call to Assembler::GrowBuffer.

In current V8 versions, architecture-specific GrowBuffer implementations utilize the ComputeNewBufferSize helper. This helper employs 64-bit integer arithmetic (int64_t) for size calculations and performs a strict check against the 512 MiB kMaximalBufferSize before any reallocation occurs. If the compiler attempts to emit more code than the limit allows, V8 triggers a controlled FatalProcessOutOfMemory (OOM) crash.

Potential Attack Steps

  1. A potential attacker defines a JavaScript function containing a massive number of bytecodes (e.g., millions of simple logical operations) designed to produce a BytecodeArray exceeding 306.8 MiB.
  2. The attacker triggers Sparkplug compilation of this function (e.g., by repeatedly calling it until it tiers up).
  3. The estimation logic overflows, bypassing the initial 512 MiB check and initializing the compiler with a 128-byte buffer.
  4. As the compiler attempts to grow the buffer incrementally to accommodate the JIT code, it eventually reaches the 512 MiB cap.

Impact and Fix Recommendation

The current impact is limited to a Denial-of-Service (DoS) via a process crash when extremely large functions are encountered. The reported sandbox bypass and linear heap overflow are mitigated by the robust size checks in ComputeNewBufferSize and the 512 MiB limit on the code buffer size.

To resolve the underlying logic flaw, we recommend using size_t or base::CheckedNumeric for all instruction size estimations and buffer allocation checks in v8/src/baseline/baseline-compiler.cc to prevent integer overflows during the estimation phase.

Note: These are potential steps based on code analysis; our current tooling does not allow for a live proof-of-concept execution.

Evaluated with Chrome root at commit: 29093e11cf509e3593f6229e4b1b075cca356049


Results so far have been promising, but there can be wrong deductions. Feel free to adjust as follows:

  • If you are familiar with the severity guidelines, you may adjust the severity.
  • If this is a false positive, and there’s no work to be done, please close as WAI.
  • If there is work to do here but not a vulnerability, please change the issue type to Task/Bug/FR.

Data from false positives will be used to improve accuracy over time. And please feel free to reach out to me directly if you have concerns or feedback on the project.

View on issue tracker