High watch CVSS 8.3 chrome Integer Overflow 🔧 Commit mapped

Overview

High
Severity
8.3
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInteger overflow in GPU
DescriptionInteger overflow in GPU
ComponentGPU
Bug ClassInteger Overflow
Action tierwatch — ub-class only — no demonstrated corruption path
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.
Tracker495314407
Fix commit701332cc0468 (chromium/src) +32/-16
CWECWE-472
CVSS vectorCVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:C/C:H/I:H/A:H
CISA KEVNot listed
CreditedGoogle
Reported2026-03-23 (per vendor advisory)
Fix landed2026-03-24 (public git)
Disclosed2026-05-12
Patch gap49 days — fix public in source before the advisory

Background

`GetCompressedTexSizeInBytes`
GPU-service routine (gl::gles2) that computes the byte size required for a compressed texture upload from width, height, depth, and the compression format.
Block-compressed formats
Formats (S3TC/DXT, ASTC, ETC2/EAC, BPTC, RGTC) whose size is computed as ceil(width/blockW) * ceil(height/blockH) * blockBytes, i.e. (dim + blockSize - 1) / blockSize rounding.
`base::CheckAdd` / `base::CheckDiv`
Checked-arithmetic helpers that track overflow in a CheckedNumeric result rather than silently wrapping on native int operations.
`bytes_required`
The accumulator variable holding the computed size; if it overflows, downstream buffer sizing/validation can be bypassed.

Root Cause Analysis

For each compressed-texture case, bytes_required was computed with raw integer arithmetic: (width + kBlockWidth - 1) / kBlockWidth and the analogous height term, then multiplied by the per-block byte size. When width or height is attacker-influenced and large enough, the width + kBlockWidth - 1 addition (and the subsequent multiplications) can overflow the native integer type, producing a bytes_required that is far smaller than the real required size. That undersized value then violates the invariant that bytes_required accurately bounds the compressed data, so any later allocation or bounds check keyed on it would be wrong.

The fix wraps the rounding additions and divisions in base::CheckAdd/base::CheckDiv so the computation carries overflow state in a CheckedNumeric, converting a silent wrap into a detectable/invalid result across every affected format case.

The patch changes only the arithmetic inside the size computation; it does not itself show the downstream consumer.

Key insight
The size-in-bytes computation for compressed textures used unchecked native integer arithmetic that could wrap on large width/height, and the fix replaces the additions and divisions with base::CheckAdd/base::CheckDiv so overflow is caught instead of silently producing an undersized length.

Attack Path

  1. Issue a compressed-texture call Via WebGL/GLES, an attacker-controlled page supplies a compressed-texture upload with extreme width/height for one of the affected formats (S3TC, ASTC, ETC2/EAC, BPTC, RGTC).
  2. Reach the size computation The call lands in GetCompressedTexSizeInBytes, hitting the matching case that computes bytes_required from the dimensions.
  3. Integer overflow The (width + blockSize - 1) add and the chained multiplications overflow the native integer, so bytes_required wraps to a value smaller than the data actually described.
  4. Undersized length propagates The truncated bytes_required is returned to callers that size or validate the upload buffer, where a smaller-than-actual length could lead to an out-of-bounds access (inferred — the sink is not in this diff).

Impact Assessment

Per the vendor advisory this is a high-severity integer overflow in GPU: a renderer/WebGL client supplying crafted compressed-texture dimensions can make GetCompressedTexSizeInBytes return an undersized byte count in the GPU process. Whether this yields a memory-safety primitive depends on the downstream consumer of bytes_required, which is not shown in the diff and is therefore inferred; the advisory does not report in-the-wild exploitation.

Files Changed

  • gpu/command_buffer/service/gl_utils.cc

Audit Directions

  • Rounding-up size math
    Grep for (x + N - 1) / N and w * h * bpp patterns on externally influenced dimensions and confirm they use base::CheckedNumeric rather than native int.
  • GPU command-buffer size validators
    Review other size/offset computations in gpu/command_buffer/service (texture, buffer, and pixel-pack paths) for unchecked multiplications before allocation or bounds checks.
  • Partial overflow hardening
    Verify no remaining bytes_required *= ... or depth multiplications in the same function/family still operate on an already-unchecked base, so one hardened term is not undermined by a later raw multiply.
From 701332cc0468ea4c6283a911e683a8ac7c46f1eb Mon Sep 17 00:00:00 2001
From: Yuki Shiino <yukishiino@chromium.org>
Date: Tue, 24 Mar 2026 14:28:14 -0700
Subject: [PATCH] Fix possible integer overflow in gl::gles2::GetCompressedTexSizeInBytes

When width or height is enough huge, it's possible to cause
integer overflow in gl::gles2::GetCompressedTexSizeInBytes.
Applies base::CheckAdd and base::CheckDiv.

Bug: 495314407
Change-Id: Iff5addc01dadde00e68d170556adaea99415d2c4
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7693951
Reviewed-by: Kai Ninomiya <kainino@chromium.org>
Commit-Queue: Kai Ninomiya <kainino@chromium.org>
Auto-Submit: Yuki Shiino <yukishiino@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1604392}
---

diff --git a/gpu/command_buffer/service/gl_utils.cc b/gpu/command_buffer/service/gl_utils.cc
index 71f362f..c2772ae7 100644
--- a/gpu/command_buffer/service/gl_utils.cc
+++ b/gpu/command_buffer/service/gl_utils.cc
@@ -505,8 +505,10 @@
     case GL_COMPRESSED_SRGB_S3TC_DXT1_EXT:
     case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT:
     case GL_ETC1_RGB8_OES:
-      bytes_required = (width + kS3TCBlockWidth - 1) / kS3TCBlockWidth;
-      bytes_required *= (height + kS3TCBlockHeight - 1) / kS3TCBlockHeight;
+      bytes_required = base::CheckDiv(
+          base::CheckAdd(width, kS3TCBlockWidth - 1), kS3TCBlockWidth);
+      bytes_required *= base::CheckDiv(
+          base::CheckAdd(height, kS3TCBlockHeight - 1), kS3TCBlockHeight);
       bytes_required *= kS3TCDXT1BlockSize;
       break;
     case GL_COMPRESSED_RGBA_ASTC_4x4_KHR:
@@ -546,8 +548,10 @@
       const int kBlockWidth = kASTCBlockArray[index].blockWidth;
       const int kBlockHeight = kASTCBlockArray[index].blockHeight;
 
-      bytes_required = (width + kBlockWidth - 1) / kBlockWidth;
-      bytes_required *= (height + kBlockHeight - 1) / kBlockHeight;
+      bytes_required =
+          base::CheckDiv(base::CheckAdd(width, kBlockWidth - 1), kBlockWidth);
+      bytes_required *= base::CheckDiv(base::CheckAdd(height, kBlockHeight - 1),
+                                       kBlockHeight);
 
       bytes_required *= kASTCBlockSize;
       break;
@@ -558,8 +562,10 @@
     case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT:
     case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT:
     case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT:
-      bytes_required = (width + kS3TCBlockWidth - 1) / kS3TCBlockWidth;
-      bytes_required *= (height + kS3TCBlockHeight - 1) / kS3TCBlockHeight;
+      bytes_required = base::CheckDiv(
+          base::CheckAdd(width, kS3TCBlockWidth - 1), kS3TCBlockWidth);
+      bytes_required *= base::CheckDiv(
+          base::CheckAdd(height, kS3TCBlockHeight - 1), kS3TCBlockHeight);
       bytes_required *= kS3TCDXT3AndDXT5BlockSize;
       break;
     case GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG:
@@ -587,9 +593,11 @@
     case GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2:
     case GL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2:
       bytes_required =
-          (width + kEACAndETC2BlockSize - 1) / kEACAndETC2BlockSize;
+          base::CheckDiv(base::CheckAdd(width, kEACAndETC2BlockSize - 1),
+                         kEACAndETC2BlockSize);
       bytes_required *=
-          (height + kEACAndETC2BlockSize - 1) / kEACAndETC2BlockSize;
+          base::CheckDiv(base::CheckAdd(height, kEACAndETC2BlockSize - 1),
+                         kEACAndETC2BlockSize);
       bytes_required *= 8;
       bytes_required *= depth;
       break;
@@ -598,9 +606,11 @@
     case GL_COMPRESSED_RGBA8_ETC2_EAC:
     case GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC:
       bytes_required =
-          (width + kEACAndETC2BlockSize - 1) / kEACAndETC2BlockSize;
+          base::CheckDiv(base::CheckAdd(width, kEACAndETC2BlockSize - 1),
+                         kEACAndETC2BlockSize);
       bytes_required *=
-          (height + kEACAndETC2BlockSize - 1) / kEACAndETC2BlockSize;
+          base::CheckDiv(base::CheckAdd(height, kEACAndETC2BlockSize - 1),
+                         kEACAndETC2BlockSize);
       bytes_required *= 16;
       bytes_required *= depth;
       break;
@@ -608,22 +618,28 @@
     case GL_COMPRESSED_RGBA_BPTC_UNORM_EXT:
     case GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT_EXT:
     case GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT_EXT:
-      bytes_required = (width + kBPTCBlockWidth - 1) / kBPTCBlockWidth;
-      bytes_required *= (height + kBPTCBlockHeight - 1) / kBPTCBlockHeight;
+      bytes_required = base::CheckDiv(
+          base::CheckAdd(width, kBPTCBlockWidth - 1), kBPTCBlockWidth);
+      bytes_required *= base::CheckDiv(
+          base::CheckAdd(height, kBPTCBlockHeight - 1), kBPTCBlockHeight);
       bytes_required *= 16;
       bytes_required *= depth;
       break;
     case GL_COMPRESSED_RED_RGTC1_EXT:
     case GL_COMPRESSED_SIGNED_RED_RGTC1_EXT:
-      bytes_required = (width + kRGTCBlockWidth - 1) / kRGTCBlockWidth;
-      bytes_required *= (height + kRGTCBlockHeight - 1) / kRGTCBlockHeight;
+      bytes_required = base::CheckDiv(
+          base::CheckAdd(width, kRGTCBlockWidth - 1), kRGTCBlockWidth);
+      bytes_required *= base::CheckDiv(
+          base::CheckAdd(height, kRGTCBlockHeight - 1), kRGTCBlockHeight);
       bytes_required *= 8;
       bytes_required *= depth;
       break;
     case GL_COMPRESSED_RED_GREEN_RGTC2_EXT:
     case GL_COMPRESSED_SIGNED_RED_GREEN_RGTC2_EXT:
-      bytes_required = (width + kRGTCBlockWidth - 1) / kRGTCBlockWidth;
-      bytes_required *= (height + kRGTCBlockHeight - 1) / kRGTCBlockHeight;
+      bytes_required = base::CheckDiv(
+          base::CheckAdd(width, kRGTCBlockWidth - 1), kRGTCBlockWidth);
+      bytes_required *= base::CheckDiv(
+          base::CheckAdd(height, kRGTCBlockHeight - 1), kRGTCBlockHeight);
       bytes_required *= 16;
       bytes_required *= depth;
       break;
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.