High watch CVSS 4.2 chrome Integer Overflow 📄 Reporter bug report 🔧 Commit mapped

Overview

High
Severity
4.2
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInteger overflow in GPU
DescriptionInteger overflow in GPU
ComponentGPU
Bug ClassInteger Overflow
Action tierwatch — corruption primitive but gated (uncommon) — not default-reachable
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.
Tracker497639714
Fix commit39eb06c88d28 (chromium/src) +5/-4
CWECWE-472
CVSS vectorCVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:L/I:L/A:N
CISA KEVNot listed
CreditedGoogle
Reported2026-03-30 (per vendor advisory)
Fix landed2026-04-02 (public git)
Disclosed2026-05-05
Patch gap33 days — fix public in source before the advisory

Background

`base::CheckedNumeric<T>`
a wrapper that performs arithmetic with overflow detection, so a sum that would overflow is flagged rather than silently wrapping; it only protects operations done inside the wrapper.
`GLsizei`
a signed 32-bit integer type used for GL counts; signed overflow wraps to a negative value, which can slip past unsigned/upper-bound checks.
`GetVerticesNeededForDraw`
the transform-feedback helper that computes how many vertices a draw will produce by combining vertices_drawn_ and pending_vertices_drawn, used to validate against buffer bounds.
`RequestBuffersAccess`
the buffer-manager routine that validates access for count bound buffers before a draw; its parameters (count, func_name, message_tag) appear in the hunk but its name comes from the commit message.

Root Cause Analysis

In GetVerticesNeededForDraw, the vertex total was computed as base::CheckedNumeric<GLsizei> checked_vertices = vertices_drawn_ + pending_vertices_drawn;. Because both operands are plain GLsizei, the addition vertices_drawn_ + pending_vertices_drawn is evaluated in ordinary signed integer arithmetic before the result is stored into the CheckedNumeric; an overflow therefore wraps to a negative value and is simply assigned into the checked wrapper, which never sees the overflow. The resulting (wrongly small or negative) vertex count then bypasses downstream buffer-bounds checks, which is the integer-overflow condition the commit describes. The invariant violated is that overflow-prone arithmetic must happen inside CheckedNumeric, not be fed to it as an already-computed value.

The fix seeds the wrapper with checked_vertices = vertices_drawn_; and then does checked_vertices += pending_vertices_drawn;, so the addition occurs within CheckedNumeric and overflow is detected. A second, defense-in-depth change in RequestBuffersAccess upgrades two DCHECKs to CHECKs and adds CHECK_GE(count, 0);, ensuring (in release builds too) that a negative count cannot reach the per-buffer validation loop.

Key insight
The core mistake was doing the vertex addition in native GLsizei arithmetic and only then assigning into base::CheckedNumeric, so the overflow had already wrapped before any check; the fix moves the addition inside the checked type via +=, and RequestBuffersAccess adds a CHECK_GE(count, 0) to reject negative counts.

Attack Path

  1. Drive transform feedback Attacker-controlled GPU commands (compromised or malicious renderer issuing GL draw/transform-feedback calls) set vertices_drawn_ and a draw whose pending_vertices_drawn pushes the sum past GLsizei range.
  2. Overflow outside the check vertices_drawn_ + pending_vertices_drawn wraps to a negative/small value in plain arithmetic and is assigned into checked_vertices, so CheckedNumeric reports no overflow.
  3. Bypass bounds validation The understated vertex count passes the buffer-size validation that was meant to reject the draw, as stated by the commit (“wrap to negative values and bypass buffer bounds checks”).
  4. Out-of-bounds access The subsequent draw/transform-feedback write proceeds against a buffer smaller than the real vertex requirement, an out-of-bounds access in the GPU process (inferred as to the precise sink; the diff shows the bypass, not the exact OOB location).

Impact Assessment

An attacker controlling GL/transform-feedback commands from a renderer can overflow the vertex count so it bypasses buffer-bounds checks, leading to an out-of-bounds buffer access in the GPU process, with the precondition of driving a transform-feedback draw whose vertex total exceeds GLsizei. The advisory rates this high (“Integer overflow in GPU”) and does not report in-the-wild exploitation; the commit also labels this a “phase 1 quick fix,” implying further hardening may follow. Memory corruption or code execution beyond the bounds-check bypass is (inferred) — the diff demonstrates the overflow and the added guards, not a concrete corruption primitive.

Changed Functions

FunctionChangeNotes
switch
gpu/command_buffer/service/transform_feedback_manager.cc
modified

Files Changed

  • gpu/command_buffer/service/buffer_manager.cc
  • gpu/command_buffer/service/transform_feedback_manager.cc

Audit Directions

  • Arithmetic assigned into `CheckedNumeric`
    Grep for CheckedNumeric<...> x = a + b (or * , <<) where the operation is performed on raw operands before assignment; the overflow must occur inside the wrapper (use +=/*= or wrap an operand first).
  • `DCHECK` on attacker-reachable preconditions
    Audit GPU/command-buffer validation for DCHECKs on counts, sizes, and signs that vanish in release builds; convert to CHECK/CHECK_GE where the value is renderer-controlled.
  • Signed GL counts feeding bounds checks
    Review paths where signed GLsizei/GLint counts are summed or multiplied and then compared against buffer sizes, since negative-wrap results can slip past upper-bound comparisons.
From 39eb06c88d286abf6714065181f84c2be0a7be18 Mon Sep 17 00:00:00 2001
From: Francisco Ochoa <[email protected]>
Date: Thu, 02 Apr 2026 04:43:30 -0700
Subject: [PATCH] Fix potential integer overflow and add size check.

Perform vertex addition in GetVerticesNeededForDraw within
base::CheckedNumeric to catch overflows that wrap to negative values and
bypass buffer bounds checks.

Add size(count variable) check in RequestBuffersAccess.

See (internal-only): go/code-terracotta-review-explainer

This is a phase 1 quick fix.

Bug: 497639714
Change-Id: Iecb9fe274109013e7d7383e409609d13ea2c7003
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7718624
Reviewed-by: Stephen Nusko <[email protected]>
Commit-Queue: Francisco Ochoa <[email protected]>
Reviewed-by: Arthur Sonzogni <[email protected]>
Reviewed-by: Colin Blundell <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1609104}
---

diff --git a/gpu/command_buffer/service/buffer_manager.cc b/gpu/command_buffer/service/buffer_manager.cc
index 49fd7c7f..109ef77 100644
--- a/gpu/command_buffer/service/buffer_manager.cc
+++ b/gpu/command_buffer/service/buffer_manager.cc
@@ -889,8 +889,9 @@
     GLsizei count,
     const char* func_name,
     const char* message_tag) {
-  DCHECK(error_state);
-  DCHECK(bindings);
+  CHECK(error_state);
+  CHECK(bindings);
+  CHECK_GE(count, 0);
 
   for (size_t ii = 0; ii < variable_sizes.size(); ++ii) {
     if (variable_sizes[ii] == 0)
diff --git a/gpu/command_buffer/service/transform_feedback_manager.cc b/gpu/command_buffer/service/transform_feedback_manager.cc
index 3c24101c..0c9dd74 100644
--- a/gpu/command_buffer/service/transform_feedback_manager.cc
+++ b/gpu/command_buffer/service/transform_feedback_manager.cc
@@ -109,8 +109,8 @@
   // Transform feedback only outputs complete primitives, so we need to round
   // down to the nearest complete primitive before multiplying by the number of
   // instances.
-  base::CheckedNumeric<GLsizei> checked_vertices =
-      vertices_drawn_ + pending_vertices_drawn;
+  base::CheckedNumeric<GLsizei> checked_vertices = vertices_drawn_;
+  checked_vertices += pending_vertices_drawn;
   base::CheckedNumeric<GLsizei> checked_count = count;
   base::CheckedNumeric<GLsizei> checked_primcount = primcount;
   switch (mode) {
Loading diff…

Original Bug Report

reported by [email protected]

Potential out-of-bounds write in GPU memory via Transform Feedback integer overflow

Project Fortify, an experimental security project, has identified the following potential security issue.

Overview: A combination of an accumulation logic bug and an unchecked signed integer overflow in the validating command decoder allows an attacker to bypass Transform Feedback buffer bounds checks. By using multi-draw commands, a compromised renderer can force the required buffer size calculation to wrap to a negative value. This bypasses the buffer size validation, leading to a potential out-of-bounds write in the GPU process.

Affected files:

  • gpu/command_buffer/service/transform_feedback_manager.cc
  • gpu/command_buffer/service/gles2_cmd_decoder.cc
  • gpu/command_buffer/service/buffer_manager.cc

Estimated timestamp from git blame: 2019-01-18

Vulnerability Details

A potential security vulnerability exists in the Chromium validating command decoder’s handling of Transform Feedback (TF) during multi-draw operations. The issue stems from two interacting flaws: a logic bug that repeatedly accumulates previously drawn vertices, and an improper initialization of base::CheckedNumeric that fails to detect a resulting integer overflow.

1. The Logic Flaw in CheckTransformFeedback

In gpu/command_buffer/service/gles2_cmd_decoder.cc, the function GLES2DecoderImpl::CheckTransformFeedback validates that the bound TF buffer is large enough for a multi-draw command. It loops over the drawcount and repeatedly calls GetVerticesNeededForDraw:

for (GLsizei draw_id = 0; draw_id < drawcount; ++draw_id) {
  // ...
  bool valid = state_.bound_transform_feedback->GetVerticesNeededForDraw(
      mode, count, primcount, *vertices_drawn, vertices_drawn);
  // ...
}

Here, *vertices_drawn acts as an accumulator. It is passed as the pending_vertices_drawn input argument and receives the updated total via the vertices_out pointer.

2. The Integer Overflow in GetVerticesNeededForDraw

Inside gpu/command_buffer/service/transform_feedback_manager.cc, TransformFeedback::GetVerticesNeededForDraw calculates the total vertices using the following expression:

base::CheckedNumeric<GLsizei> checked_vertices =
    vertices_drawn_ + pending_vertices_drawn;

Because vertices_drawn_ (the class member tracking vertices drawn in previous commands) is added to pending_vertices_drawn (the loop’s accumulator) on every iteration, vertices_drawn_ is incorrectly accumulated drawcount times.

Crucially, both vertices_drawn_ and pending_vertices_drawn are of type GLsizei (a 32-bit signed integer). The addition is performed using standard C++ signed arithmetic before the result is passed to the base::CheckedNumeric constructor. Chromium compiles with -fno-strict-overflow, defining signed overflow to wrap. If an attacker provides a massive drawcount, the sum $N \times \text{vertices_drawn_}$ will exceed INT_MAX and wrap to a large negative number. Because this wrapped negative number is a valid 32-bit integer, the base::CheckedNumeric constructor accepts it without flagging an invalid state.

3. Buffer Bounds Check Bypass

The wrapped negative vertex count is returned to CheckTransformFeedback and subsequently passed to BufferManager::RequestBuffersAccess (gpu/command_buffer/service/buffer_manager.cc):

if (!base::CheckMul(variable_sizes[ii], count)
         .AssignIfValid(&required_size) ||
    size < required_size) {
  // error...
}

Since count is negative, base::CheckMul correctly produces a negative required_size (as GLsizeiptr is a signed 64-bit type). The comparison size < required_size (where size is the positive actual buffer size and required_size is negative) evaluates to false. This completely bypasses the transform feedback buffer size check.

Suggested Exploit Path

Note: Our tooling agent cannot run code yet, so these are theoretical steps based on code analysis.

  1. Initial Setup: From a compromised renderer, create an OpenGL ES 3 / WebGL 2 context using the validating command decoder.
  2. Bind Transform Feedback: Bind a small buffer for Transform Feedback and call glBeginTransformFeedback(GL_POINTS).
  3. Populate vertices_drawn_: Issue a standard glDrawArrays call to draw a moderate number of vertices (e.g., $V = 10,000$). This sets the TransformFeedback object’s internal vertices_drawn_ state to $V$.
  4. Trigger the Overflow: Issue a WEBGL_multi_draw command (e.g., glMultiDrawArraysWEBGL) with a massive drawcount (e.g., $N = 300,000$).
  5. The Bypass: The validation loop in CheckTransformFeedback repeatedly adds $V$ to the accumulator $N$ times. The sum $10,000 \times 300,000$ overflows the 32-bit signed integer, wrapping to a negative value. The CheckedNumeric initialization fails to detect this. The negative count is passed to RequestBuffersAccess, where the negative required_size causes the bounds check (size < required_size) to evaluate to false.
  6. Out-of-Bounds Write: The decoder dispatches the massive multi-draw command to the native OpenGL driver. The hardware Transform Feedback unit sequentially writes the varying outputs into the bound buffer, quickly writing past the end of the allocation and corrupting adjacent GPU memory.
  7. Persistent State Corruption: After dispatch, OnVerticesDrawn is called with the negative accumulator, permanently corrupting vertices_drawn_. Subsequent standard draw calls will now also bypass buffer bounds checks.

Suggested Fix

  1. Fix the accumulation logic: In GLES2DecoderImpl::CheckTransformFeedback, do not repeatedly add vertices_drawn_ inside the loop. The TransformFeedback::GetVerticesNeededForDraw method should only calculate the vertices needed for the current draw parameters, and the caller should accumulate them. vertices_drawn_ should only be added once, outside the loop.
  2. Fix the CheckedNumeric initialization: In TransformFeedback::GetVerticesNeededForDraw, perform the addition using base::CheckedNumeric methods to catch overflows:
    base::CheckedNumeric<GLsizei> checked_vertices = vertices_drawn_;
    checked_vertices += pending_vertices_drawn;
    
  3. Validate count: In BufferManager::RequestBuffersAccess, ensure that count >= 0 before proceeding with calculations.

Evaluated with Chrome root at commit: 876d480da1f794d87813cfa2e6ff4fcf9771e939


Results so far have been promising, but there can be wrong deductions. If this proves to be a false positive, please close as WAI; 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