CVE-2026-79111
Overview
Background
- `TType`
- The ANGLE translator’s representation of a GLSL type, including its base type, array dimensions, backing
mStructure, and (for uniform/interface members) itsmInterfaceBlock. - Interface block (UBO)
- A named block of uniform variables (e.g. a Uniform Buffer Object) whose declared members and array sizes determine the storage layout the translator must validate against implementation limits.
- Type size cache
- A lookup keyed on
TType(ordered viaTType::operator<) that memoizes computed type sizes so repeated types are not re-measured; a key collision returns a previously cached size for a different type. - `TType::operator<`
- The strict-weak-ordering comparator used as the cache key’s ordering function; it must distinguish any two types that have different sizes.
Root Cause Analysis
TType::operator< compared array sizes and mStructure but never compared mInterfaceBlock, so two types that differed only in which interface block they belonged to were treated as equal keys in the type size cache. When a small UBO was declared and its size cached, a subsequently declared large UBO whose TType was otherwise identical (same base type, array shape, and null/shared mStructure) collided with the cached entry and was assigned the small UBO’s size. This caused the translator to skip the “size exceeds implementation-defined limit” validation for the large block, because the cached (small) size was under the limit.
The fix adds an if (mInterfaceBlock != right.mInterfaceBlock) return mInterfaceBlock < right.mInterfaceBlock; branch so interface-block identity becomes part of the ordering key, forcing distinct blocks to occupy distinct cache entries and be measured independently. The regression test LargeArrayUBOAfterSmallUBO confirms a large array (rr[~1U]) in a second UBO now correctly triggers the limit error after a small UBO is declared first.
TType::operator< omitted mInterfaceBlock, so its equivalence classes were coarser than actual type size, letting a large interface block alias a small one’s cached size; the fix restores the invariant that any two size-distinct types compare unequal by adding the missing mInterfaceBlock comparison.Attack Path
- Declare a small UBO
An attacker-supplied WebGL2/GLSL ES 3.00 shader first declares a uniform interface block whose size is comfortably under the implementation-defined limit, causing its
TTypesize to be computed and cached. - Declare a colliding large UBO
The shader then declares a second interface block containing an oversized array (e.g.
int rr[~1U]) whoseTTypediffers only bymInterfaceBlock, so it hashes/orders to the same cache key as the small block. - Skip the size-limit check
The translator retrieves the cached small size for the large block and does not emit
'Block' : Size of declared variable exceeds implementation-defined limit, so the oversized block passes validation. (inferred) - Downstream size miscomputation The translated shader / driver proceeds with an interface block whose real storage requirement exceeds validated limits, an input-validation failure in Dawn/ANGLE. Any resulting memory-safety consequence is not shown by the diff. (inferred)
Impact Assessment
Files Changed
src/compiler/translator/Types.hsrc/tests/gl_tests/GLSLValidationTest.cpp
Audit Directions
- Incomplete comparators as cache keysAudit every
operator<,operator==, and hash used to key size/layout caches (in ANGLE and elsewhere) and verify it compares every field that can affect the cached value; a field omitted from ordering silently merges distinct entries. - Validation gated by memoized resultsReview paths where a security-relevant limit check (size, bounds, capability) is skipped when a cached result is found, and confirm the cache key uniquely identifies all inputs to that check.
- Interface-block and struct identity handlingSearch the translator for other places (layout, offset, size computation) where
mInterfaceBlockvsmStructureare treated interchangeably or where block identity is dropped from a comparison.
Patch
From baeac58b753dbc08bb9e289842e0aa230b7f9c4b Mon Sep 17 00:00:00 2001
From: Shahbaz Youssefi <syoussefi@chromium.org>
Date: Mon, 13 Jul 2026 16:54:46 -0400
Subject: [PATCH] Translator: Fix type size cache for interface blocks
`TType::operator<` did not differentiate between interface blocks, so a
large UBO after a small UBO hit the cache and used the small UBO's size.
Bug: chromium:525315564
Bug: chromium:525683797
Change-Id: Ic58a260a217d6159e88c0d40840bb3eb232d3689
Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/8087299
Commit-Queue: Shahbaz Youssefi <syoussefi@chromium.org>
Reviewed-by: Geoff Lang <geofflang@chromium.org>
---
diff --git a/src/compiler/translator/Types.h b/src/compiler/translator/Types.h
index 2dbc6a0..b3e6950 100644
--- a/src/compiler/translator/Types.h
+++ b/src/compiler/translator/Types.h
@@ -308,7 +308,13 @@
return mArraySizes[i] < right.mArraySizes[i];
}
if (mStructure != right.mStructure)
+ {
return mStructure < right.mStructure;
+ }
+ if (mInterfaceBlock != right.mInterfaceBlock)
+ {
+ return mInterfaceBlock < right.mInterfaceBlock;
+ }
return false;
}
diff --git a/src/tests/gl_tests/GLSLValidationTest.cpp b/src/tests/gl_tests/GLSLValidationTest.cpp
index 4b8cf0d..bed9ed9 100644
--- a/src/tests/gl_tests/GLSLValidationTest.cpp
+++ b/src/tests/gl_tests/GLSLValidationTest.cpp
@@ -2979,6 +2979,26 @@
"'Block' : Size of declared variable exceeds implementation-defined limit");
}
+// Test that too large array in UBO fails after another UBO within limit is declared.
+TEST_P(WebGL2GLSLValidationTest, LargeArrayUBOAfterSmallUBO)
+{
+ constexpr char kFS[] = R"(#version 300 es
+uniform Small
+{
+ int i;
+};
+uniform Block
+{
+ int rr[~1U];
+};
+out int o;
+void main() {
+ o = rr[1] + i;
+})";
+ validateError(GL_FRAGMENT_SHADER, kFS,
+ "'Block' : Size of declared variable exceeds implementation-defined limit");
+}
+
// Regression test for a 32-bit overflow bug when setting initializer for a large constant.
TEST_P(WebGL2GLSLValidationTest, LargeConstantVariableWithInitializer)
{
Regression Test / PoC
diff --git a/src/tests/gl_tests/GLSLValidationTest.cpp b/src/tests/gl_tests/GLSLValidationTest.cpp
index 4b8cf0d..bed9ed9 100644
--- a/src/tests/gl_tests/GLSLValidationTest.cpp
+++ b/src/tests/gl_tests/GLSLValidationTest.cpp
@@ -2979,6 +2979,26 @@
"'Block' : Size of declared variable exceeds implementation-defined limit");
}
+// Test that too large array in UBO fails after another UBO within limit is declared.
+TEST_P(WebGL2GLSLValidationTest, LargeArrayUBOAfterSmallUBO)
+{
+ constexpr char kFS[] = R"(#version 300 es
+uniform Small
+{
+ int i;
+};
+uniform Block
+{
+ int rr[~1U];
+};
+out int o;
+void main() {
+ o = rr[1] + i;
+})";
+ validateError(GL_FRAGMENT_SHADER, kFS,
+ "'Block' : Size of declared variable exceeds implementation-defined limit");
+}
+
// Regression test for a 32-bit overflow bug when setting initializer for a large constant.
TEST_P(WebGL2GLSLValidationTest, LargeConstantVariableWithInitializer)
{
Original Bug Report
Potential bypass of ANGLE's checkVariableSize 2 GiB limit on compute shaders on hardened contexts
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. Please see https://chromium.googlesource.com/chromium/src/+/main/docs/security/ai-generated-security-bugs-faq.md for more information.
Overview: ANGLE’s parser-level guard checkVariableSize enforces a 2 GiB size limit on hardened contexts but early-returns for shader stages other than vertex and fragment. Consequently, WebGPU compute shaders using Dawn’s OpenGLES backend can potentially compile storage buffer blocks (SSBOs) up to 4 GiB. This delivers oversized layouts directly to the native GPU driver’s shader compiler in the unsandboxed GPU process.
Affected files:
third_party/angle/src/compiler/translator/ParseContext.cpp
Estimated timestamp from git blame: 2026-01-14
Description
ANGLE’s checkVariableSize is a parser-level security check designed to prevent driver crash bugs triggered by extremely large variables (such as integer overflows in driver shader compilers) by enforcing a 2 GiB limit (kWebGLMaxVariableSizeInBytes) on hardened contexts. However, this check contains an early-return optimization designed for WebGL that ignores shader stages other than vertex and fragment shaders:
// third_party/angle/src/compiler/translator/ParseContext.cpp:1858-1861
if (!mCompileOptions.rejectWebglShadersWithLargeVariables ||
(mShaderType != GL_VERTEX_SHADER && mShaderType != GL_FRAGMENT_SHADER))
{
return true; // GL_COMPUTE_SHADER bypasses the size check here
}
This early-return was written under the assumption that the hardening is only relevant for WebGL shaders. However, Dawn’s OpenGLES backend (specifically on Android devices running in compatibility mode) utilizes ANGLE as its GLES provider and explicitly requests a hardened ANGLE context:
// third_party/dawn/src/dawn/native/opengl/ContextEGL.cpp:199-201
if (egl.HasExt(EGLExt::ANGLECreateContextWebGLCompatibility)) {
AddAttrib(EGL_CONTEXT_HARDENED_ANGLE, EGL_TRUE);
}
When EGL_CONTEXT_HARDENED_ANGLE is set, ANGLE configures rejectWebglShadersWithLargeVariables = true. Because Dawn supports WebGPU compute shaders (GL_COMPUTE_SHADER), this is now a hardened client that compiles compute-stage interface blocks (SSBOs). Due to the stage filter in checkVariableSize, compute shaders compiling storage buffers bypass the 2 GiB check entirely.
Potential Attack Vector
Our static analysis suggests the following potential path to trigger the issue from web content:
- A web page running on an Android device where WebGPU compatibility mode is active (and falling back to the OpenGLES backend) requests a WebGPU adapter and device.
- The attacker creates a compute shader module (
device.createShaderModule) declaring a large storage buffer of up to 4 GiB (just under Tint’s0xFFFFFFFFlimit):@group(0) @binding(0) var<storage, read> x : array<mat4x4f, 67108863>; // ~4 GiB @compute @workgroup_size(1) fn main() { _ = x[0]; } - Tint accepts the type (since storage buffers are exempt from the
kMaxArrayElementCountlimit) and generates GLSL ES 3.10 code containing alayout(std430) bufferwith the massive array size. - Dawn attempts to compile this shader using the statically linked ANGLE GLES context via
glCompileShader. - In ANGLE, the
checkVariableSizefunction returnstrueearly without checking the size of the block becausemShaderTypeisGL_COMPUTE_SHADER. - ANGLE’s GL backend forwards the translated GLSL directly to the native Android GPU vendor’s shader compiler via
glCompileShaderinside the unsandboxed GPU process, bypassing the safety firewall.
Note: These steps are based on static code analysis; our tooling agent does not currently have the capability to run code or verify this dynamically on a target device.
Proposed Fix
Modify checkVariableSize to evaluate compute shaders on hardened contexts, and ensure that the size calculation properly handles the std430 layout rules typically used by storage buffer blocks (EvqBuffer / BLOCKLAYOUT_STD430).
Evaluated with Chrome root at commit: 75203b87cbf6681eb7c7dda8e1d0bf781538c76a
Results so far have been promising, but there can be wrong deductions. Feel free to adjust as follows:
- If you are familiar with the severity guidelines, you may adjust the severity.
- If this is a false positive, and there’s no work to be done, please close as WAI.
- If there is work to do here but not a vulnerability, please change the issue type to Task/Bug/FR.
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.