CVE-2026-9919
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifgpu/command_buffer/service/gl_utils.cc |
modified |
Files Changed
gpu/command_buffer/service/gl_utils.cc
Patch
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,
Original Bug Report
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.ccgpu/command_buffer/service/gles2_cmd_decoder.ccgpu/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). BecauseClearCompressedTextureLevel3Dignores driver errors and unconditionally returnstrue, 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.
- An attacker creates a malicious webpage hosting a WebGL2 context, targeting an Android device.
- The attacker enables a compressed texture extension (e.g.,
WEBGL_compressed_texture_astc). - 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. - The attacker binds the texture and triggers a draw call (e.g.,
gl.drawArrays). - 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. - The texture array is populated with either out-of-bounds CPU heap memory or uninitialized VRAM.
- 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.