CVE-2026-10987
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifsrc/baseline/baseline-batch-compiler.cc |
modified | |
ifsrc/baseline/baseline-compiler.cc |
modified |
Files Changed
src/baseline/baseline-batch-compiler.ccsrc/baseline/baseline-compiler.ccsrc/baseline/baseline-compiler.h
Patch
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();
Original Bug Report
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.ccv8/src/baseline/baseline-compiler.ccv8/src/codegen/assembler.ccv8/src/codegen/arm64/assembler-arm64.ccv8/src/baseline/baseline.ccv8/src/codegen/compiler.ccv8/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
- A potential attacker defines a JavaScript function containing a massive number of bytecodes (e.g., millions of simple logical operations) designed to produce a
BytecodeArrayexceeding 306.8 MiB. - The attacker triggers Sparkplug compilation of this function (e.g., by repeatedly calling it until it tiers up).
- The estimation logic overflows, bypassing the initial 512 MiB check and initializing the compiler with a 128-byte buffer.
- 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.