Overview

High
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactOut of bounds read in WebGL
DescriptionOut of bounds read in WebGL
ComponentWebGL
Bug ClassOOB
Tracker500114058
Fix commit35873c178b68 (chromium/src) +2/-5
CISA KEVNot listed
CreditedGoogle
Disclosed2026-05-27

Changed Functions

FunctionChangeNotes
if
gpu/command_buffer/service/gl_utils.cc
modified

Files Changed

  • gpu/command_buffer/service/gl_utils.cc
From 35873c178b689d732069c41f6c4d9d79d8214af8 Mon Sep 17 00:00:00 2001
From: Vasiliy Telezhnikov <[email protected]>
Date: Wed, 06 May 2026 07:22:57 -0700
Subject: [PATCH] Fix GetCompressedTexSizeInBytes depth handling

It was handled only for ES2 formats.

Bug: 500114058
Change-Id: I987973c53e474a7ef9699cdd6eca71b9c04a3574
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7816322
Reviewed-by: Geoff Lang <[email protected]>
Commit-Queue: Vasiliy Telezhnikov <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1626161}
---

diff --git a/gpu/command_buffer/service/gl_utils.cc b/gpu/command_buffer/service/gl_utils.cc
index c2772ae7..0067150 100644
--- a/gpu/command_buffer/service/gl_utils.cc
+++ b/gpu/command_buffer/service/gl_utils.cc
@@ -599,7 +599,6 @@
           base::CheckDiv(base::CheckAdd(height, kEACAndETC2BlockSize - 1),
                          kEACAndETC2BlockSize);
       bytes_required *= 8;
-      bytes_required *= depth;
       break;
     case GL_COMPRESSED_RG11_EAC:
     case GL_COMPRESSED_SIGNED_RG11_EAC:
@@ -612,7 +611,6 @@
           base::CheckDiv(base::CheckAdd(height, kEACAndETC2BlockSize - 1),
                          kEACAndETC2BlockSize);
       bytes_required *= 16;
-      bytes_required *= depth;
       break;
     case GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM_EXT:
     case GL_COMPRESSED_RGBA_BPTC_UNORM_EXT:
@@ -623,7 +621,6 @@
       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:
@@ -632,7 +629,6 @@
       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:
@@ -641,7 +637,6 @@
       bytes_required *= base::CheckDiv(
           base::CheckAdd(height, kRGTCBlockHeight - 1), kRGTCBlockHeight);
       bytes_required *= 16;
-      bytes_required *= depth;
       break;
     default:
       if (function_name && error_state) {
@@ -651,6 +646,8 @@
       return false;
   }
 
+  bytes_required *= depth;
+
   if (!bytes_required.IsValid()) {
     if (function_name && error_state) {
       ERRORSTATE_SET_GL_ERROR(error_state, GL_INVALID_VALUE, function_name,
Loading diff…

Original Bug Report

reported by [email protected]

Potential Heap OOB Read / Uninitialized VRAM Leak in WebGL Compressed Texture Arrays

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 without the security team.

Overview: The GetCompressedTexSizeInBytes function fails to multiply by the depth parameter for several compressed formats (ASTC, S3TC, PVRTC). This causes undersized buffer allocations during the lazy-clearing of GL_TEXTURE_2D_ARRAY, leading to a potential heap out-of-bounds read or an uninitialized VRAM leak in the GPU process.

Affected files:

  • gpu/command_buffer/service/gl_utils.cc
  • gpu/command_buffer/service/gles2_cmd_decoder.cc
  • gpu/command_buffer/service/texture_manager.cc

Estimated timestamp from git blame: 2026-03-24

Description

There is a logic error in gpu/command_buffer/service/gl_utils.cc where GetCompressedTexSizeInBytes calculates the memory required for compressed texture data. For formats like ETC2 and BPTC, it correctly multiplies the size by the depth parameter (representing slices in a 3D or 2D Array texture). However, for several other formats, it ignores depth and returns a size sufficient for only a single 2D slice. Affected formats include:

  • ASTC (all variants)
  • S3TC/DXT (DXT1, DXT3, DXT5)
  • ATC
  • ETC1
  • PVRTC

This flaw introduces two distinct security vulnerabilities in the Validating Command Decoder (which is enabled by default on Android):

Vector 1: Lazy Clear Undersized Allocation When gl.texStorage3D is used with GL_TEXTURE_2D_ARRAY, the texture is marked as uncleared. When the texture is subsequently used in a draw call, GLES2DecoderImpl::ClearCompressedTextureLevel3D is triggered to perform a lazy clear. It uses GetCompressedTexSizeInBytes to allocate a zero-initialized CPU buffer (base::HeapArray). Because the size is undersized (missing the depth multiplier), the buffer is too small. It then calls the driver’s glCompressedTexSubImage3D with the full depth but the undersized buffer.

Depending on the GL driver’s implementation, this results in:

  • Heap Out-of-Bounds Read: The driver reads past the end of the undersized CPU heap allocation, uploading sensitive cross-origin GPU process memory into the WebGL texture.
  • Uninitialized VRAM Leak: The driver rejects the undersized buffer (GL_INVALID_VALUE). Because ClearCompressedTextureLevel3D ignores driver errors and unconditionally returns true, Chrome incorrectly marks the texture as cleared, allowing the attacker to read uninitialized cross-process VRAM.

Vector 2: Validation Bypass GLES2DecoderImpl::ValidateCompressedTexFuncData also uses GetCompressedTexSizeInBytes to validate the imageSize provided by user data during compressedTexImage3D. An attacker can provide an undersized imageSize (corresponding to 1 slice) for a multi-slice texture. The validation will pass, and the driver may read out-of-bounds from the shared memory transfer buffer.

Suggested Attacker Steps

Note: These steps are based on static analysis; our tooling agent cannot execute code to verify them.

  1. An attacker creates a malicious webpage hosting a WebGL2 context, targeting an Android device.
  2. The attacker enables a compressed texture extension (e.g., WEBGL_compressed_texture_astc).
  3. The attacker calls gl.texStorage3D(gl.TEXTURE_2D_ARRAY, 1, gl.COMPRESSED_RGBA_ASTC_4x4_KHR, width, height, 256). Chrome allocates the VRAM and marks the texture as uncleared.
  4. The attacker binds the texture and triggers a draw call (e.g., gl.drawArrays).
  5. Chrome intercepts the draw call and calls ClearCompressedTextureLevel3D. It allocates an undersized buffer (only large enough for 1 slice, instead of 256) and passes it to the driver.
  6. The texture array is populated with either out-of-bounds CPU heap memory or uninitialized VRAM.
  7. The attacker’s WebGL shader samples the texture and uses gl.readPixels() to extract the leaked cross-origin data.

Suggested Fix

Update GetCompressedTexSizeInBytes in gpu/command_buffer/service/gl_utils.cc to correctly multiply the calculated bytes_required by the depth parameter for the affected formats (S3TC, ASTC, ATC, ETC1, and PVRTC), bringing them in line with the logic used for ETC2 and BPTC.

Evaluated with Chrome root at commit: f200f57a19490707ff8bc7aa5de3cbc443a3afad


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