CVE-2026-8552
Overview
Background
- 3D compressed textures
- GPU textures with width, height, and
depthdimensions stored in a compressed format; a full level spansdepth2D layers. - `GetCompressedTexSizeInBytes`
- A helper that computes the byte size required for a compressed texture region given width, height, depth, and format.
- `base::HeapArray<uint8_t>::Uninit`
- Allocates an uninitialized heap buffer of the requested byte count, used here to hold cleared/decompressed pixel data.
- GLES2 decoder
- The GPU-process command decoder (
gles2_cmd_decoder.cc) that services GL commands from the renderer, including clearing and decompressing compressed texture levels.
Root Cause Analysis
Two code paths that process 3D compressed textures sized their buffers using only width and height, ignoring the depth dimension. The clear path called GetCompressedTexSizeInBytes("ClearCompressedTextureLevel3D", width, height, 1, format, &bytes_required, ...) with a hardcoded depth of 1, so bytes_required covered only a single layer even when the texture had many depth layers. The decompression path allocated decompressed_data as output_pixel_size * width * height, again omitting depth. Because the subsequent clear/decompress operations write all depth layers into these under-sized buffers, writes past the allocation occur, a heap buffer overflow.
The fix passes the real depth into GetCompressedTexSizeInBytes and multiplies the decompression allocation by depth (output_pixel_size * width * height * depth), so each buffer is large enough to cover every specified layer.
depth fixed at 1 (or omitted) while the operation actually writes all depth layers; the fix threads the true depth into both the size computation and the allocation.Attack Path
- Specify a multi-layer 3D compressed texture
A renderer issues GL commands creating/updating a compressed 3D texture with
depth > 1. - Trigger clear or decompress The renderer drives a path that clears an uninitialized compressed 3D texture level or decompresses it, invoking the affected decoder code.
- Under-allocate the buffer (pre-patch)
The size/allocation uses depth
1(clear) or omitsdepth(decompress), so the heap buffer is sized for a single layer. - Overflow the heap
The operation writes all
depthlayers into the single-layer-sized buffer, writing beyond the allocation (heap buffer overflow). The attacker-controlled dimensions govern how far the overflow extends (specific corruption targets are not shown by the diff and are (inferred)).
Impact Assessment
depth > 1 can cause a heap buffer overflow in the GPU process by driving the clear or decompress path with under-sized buffers; the precondition is control over the texture’s depth and the ability to trigger clearing/decompression. The vendor advisory rates this high severity and describes it as a heap buffer overflow in GPU. Exploitation beyond the out-of-bounds write demonstrated by the under-allocation (e.g. controlled corruption or code execution) is not shown by the diff and is (inferred).Files Changed
gpu/command_buffer/service/gles2_cmd_decoder.cc
Audit Directions
- Dropped dimensions in size mathAudit texture/buffer size and allocation computations for 3D/array resources where
depth(or array layer count) is hardcoded to 1 or omitted while the write loop covers all layers. - Allocation vs. write-extent mismatchCompare each
HeapArray/buffer allocation against the full extent of the subsequent write loop, especially where one uses a helper and the other open-codes the size. - Attacker-controlled dimension multipliersReview GPU command handlers where renderer-supplied width/height/depth feed allocation sizes, checking for both missing factors and multiplication overflow.
Patch
From 397b3fab53f0e040db3f9ac9af5733c524fd772a Mon Sep 17 00:00:00 2001
From: Brandon Jones <bajones@chromium.org>
Date: Thu, 02 Apr 2026 13:04:33 -0700
Subject: [PATCH] Use depth when updating 3D compressed textures
The depth parameter was accidentally not considered in every case when
clearing or decompressing 3D compressed textures. This change ensures
that the memory allocated for clearing or decompressing is large enough
to cover all specified depth layers.
Bug: 498706958
Change-Id: Ic7cae42cf9adee07dda8754e2184a3ee988c1d09
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7727007
Reviewed-by: Kenneth Russell <kbr@chromium.org>
Reviewed-by: Kai Ninomiya <kainino@chromium.org>
Commit-Queue: Brandon Jones <bajones@chromium.org>
Commit-Queue: Kenneth Russell <kbr@chromium.org>
Auto-Submit: Brandon Jones <bajones@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1609454}
---
diff --git a/gpu/command_buffer/service/gles2_cmd_decoder.cc b/gpu/command_buffer/service/gles2_cmd_decoder.cc
index 4fb2911..d55fb20 100644
--- a/gpu/command_buffer/service/gles2_cmd_decoder.cc
+++ b/gpu/command_buffer/service/gles2_cmd_decoder.cc
@@ -12200,7 +12200,7 @@
GLsizei bytes_required = 0;
if (!GetCompressedTexSizeInBytes("ClearCompressedTextureLevel3D", width,
- height, 1, format, &bytes_required,
+ height, depth, format, &bytes_required,
error_state_.get())) {
return false;
}
@@ -12518,8 +12518,8 @@
auto* api = state.api();
uint32_t output_pixel_size = GLES2Util::ComputeImageGroupSize(
info.decompressed_format, info.decompressed_type);
- auto decompressed_data =
- base::HeapArray<uint8_t>::Uninit(output_pixel_size * width * height);
+ auto decompressed_data = base::HeapArray<uint8_t>::Uninit(
+ output_pixel_size * width * height * depth);
// If a PBO is bound, map it to decompress the data.
const void* input_data = data;