CVE-2026-8534
Overview
Background
- `GetCompressedTexSizeInBytes`
- GPU-service routine (
gl::gles2) that computes the byte size required for a compressed texture upload fromwidth,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) / blockSizerounding. - `base::CheckAdd` / `base::CheckDiv`
- Checked-arithmetic helpers that track overflow in a
CheckedNumericresult rather than silently wrapping on nativeintoperations. - `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.
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
- Issue a compressed-texture call
Via WebGL/GLES, an attacker-controlled page supplies a compressed-texture upload with extreme
width/heightfor one of the affected formats (S3TC, ASTC, ETC2/EAC, BPTC, RGTC). - Reach the size computation
The call lands in
GetCompressedTexSizeInBytes, hitting the matchingcasethat computesbytes_requiredfrom the dimensions. - Integer overflow
The
(width + blockSize - 1)add and the chained multiplications overflow the native integer, sobytes_requiredwraps to a value smaller than the data actually described. - Undersized length propagates
The truncated
bytes_requiredis 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
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 mathGrep for
(x + N - 1) / Nandw * h * bpppatterns on externally influenced dimensions and confirm they usebase::CheckedNumericrather than nativeint. - GPU command-buffer size validatorsReview 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 hardeningVerify no remaining
bytes_required *= ...ordepthmultiplications in the same function/family still operate on an already-unchecked base, so one hardened term is not undermined by a later raw multiply.
Patch
From 701332cc0468ea4c6283a911e683a8ac7c46f1eb Mon Sep 17 00:00:00 2001 From: Yuki Shiino <[email protected]> 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 <[email protected]> Commit-Queue: Kai Ninomiya <[email protected]> Auto-Submit: Yuki Shiino <[email protected]> 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;