CVE-2026-11066
Overview
Files Changed
src/compiler/translator/ParseContext.cppsrc/tests/gl_tests/GLSLValidationTest.cpp
Patch
From cb03599b340f2e3ae87f072030d7c50483bb5b56 Mon Sep 17 00:00:00 2001
From: Shahbaz Youssefi <syoussefi@chromium.org>
Date: Tue, 07 Apr 2026 16:25:02 -0400
Subject: [PATCH] Translator: Add missing check for UBO size in webgl
Bug: chromium:499124128
Change-Id: Icdb1c28decb341cc53ceb6a88e419bcfbbcffd75
Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/7737681
Commit-Queue: Shahbaz Youssefi <syoussefi@chromium.org>
Reviewed-by: Geoff Lang <geofflang@chromium.org>
---
diff --git a/src/compiler/translator/ParseContext.cpp b/src/compiler/translator/ParseContext.cpp
index bb15b50..418f9cf 100644
--- a/src/compiler/translator/ParseContext.cpp
+++ b/src/compiler/translator/ParseContext.cpp
@@ -398,11 +398,13 @@
return elementSize * type->getArraySizeProduct();
}
- if (type->getBasicType() == EbtStruct)
+ if (type->getBasicType() == EbtStruct || type->getBasicType() == EbtInterfaceBlock)
{
- const TStructure *structure = type->getStruct();
+ const TFieldList &fields = type->getBasicType() == EbtStruct
+ ? type->getStruct()->fields()
+ : type->getInterfaceBlock()->fields();
angle::base::CheckedNumeric<size_t> totalSize = 0;
- for (const TField *field : structure->fields())
+ for (const TField *field : fields)
{
const TType *fieldType = field->type();
totalSize += CalculateVariableSize(fieldType, isStd140);
@@ -6761,6 +6763,7 @@
new TVariable(&symbolTable, instanceName, interfaceBlockType,
instanceName.empty() ? SymbolType::Empty : instanceSymbolType);
+ checkVariableSize(nameLine, blockName, interfaceBlockType);
checkVariableLocations(nameLine, instanceVariable);
declareIRVariable(instanceVariable, sized);
diff --git a/src/tests/gl_tests/GLSLValidationTest.cpp b/src/tests/gl_tests/GLSLValidationTest.cpp
index 4b40128..0003b95 100644
--- a/src/tests/gl_tests/GLSLValidationTest.cpp
+++ b/src/tests/gl_tests/GLSLValidationTest.cpp
@@ -2735,6 +2735,23 @@
"'rr' : Size of declared variable exceeds implementation-defined limit");
}
+// Test that too large array in UBO, where cast to signed int would produce negative sizes, does not
+// crash.
+TEST_P(WebGL2GLSLValidationTest, LargeArrayUintMaxSizeInUBO)
+{
+ constexpr char kFS[] = R"(#version 300 es
+uniform Block
+{
+ int rr[~1U];
+};
+out int o;
+void main() {
+ o = rr[1];
+})";
+ validateError(GL_FRAGMENT_SHADER, kFS,
+ "'Block' : Size of declared variable exceeds implementation-defined limit");
+}
+
// Test that too large color outputs are rejected
TEST_P(WebGL2GLSLValidationTest, LargeColorOutput)
{
Regression Test / PoC
diff --git a/src/tests/gl_tests/GLSLValidationTest.cpp b/src/tests/gl_tests/GLSLValidationTest.cpp
index 4b40128..0003b95 100644
--- a/src/tests/gl_tests/GLSLValidationTest.cpp
+++ b/src/tests/gl_tests/GLSLValidationTest.cpp
@@ -2735,6 +2735,23 @@
"'rr' : Size of declared variable exceeds implementation-defined limit");
}
+// Test that too large array in UBO, where cast to signed int would produce negative sizes, does not
+// crash.
+TEST_P(WebGL2GLSLValidationTest, LargeArrayUintMaxSizeInUBO)
+{
+ constexpr char kFS[] = R"(#version 300 es
+uniform Block
+{
+ int rr[~1U];
+};
+out int o;
+void main() {
+ o = rr[1];
+})";
+ validateError(GL_FRAGMENT_SHADER, kFS,
+ "'Block' : Size of declared variable exceeds implementation-defined limit");
+}
+
// Test that too large color outputs are rejected
TEST_P(WebGL2GLSLValidationTest, LargeColorOutput)
{
Original Bug Report
GPU Process RCE via oversized Interface Block declaration in ANGLE
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: ANGLE’s GLSL parser fails to validate the size of interface blocks (UBOs/SSBOs), allowing arbitrarily large arrays to bypass the 2GB compiler limit check. When ANGLE-on-GL passes these unvalidated oversized declarations to the native OpenGL driver during shader compilation, it can trigger 32-bit integer overflows in the driver’s memory layout calculations. This driver-level overflow could lead to out-of-bounds memory access and potential Remote Code Execution in the GPU process.
Affected files:
third_party/angle/src/compiler/translator/ParseContext.cppthird_party/angle/src/compiler/translator/glsl/OutputGLSLBase.cppthird_party/angle/src/libANGLE/renderer/gl/ShaderGL.cpp
Estimated timestamp from git blame: 2026-01-21
Summary
In ANGLE’s GLSL translator, the parsing logic for interface blocks (uniform Block { ... } and buffer Block { ... }) entirely bypasses the checkVariableSize() security mitigation. This mitigation was specifically designed to prevent integer overflows in backends and native graphic drivers by restricting shader variables to a 2GB limit. By bypassing this check, malicious shaders can declare interface blocks large enough to trigger 32-bit integer overflows when the native OpenGL driver processes the memory layout, potentially leading to heap corruption and Remote Code Execution (RCE) in the GPU process.
Technical Details
The vulnerability stems from multiple oversights in third_party/angle/src/compiler/translator/ParseContext.cpp:
- Bypass of Validation Check: When parsing an interface block,
TParseContext::addInterfaceBlock()directly instantiates a newTVariableand callsdeclareIRVariable(). It does not calldeclareVariable(), which is the standard function that invokescheckVariableSize(). - Flawed Size Calculation: Even if
checkVariableSize()were invoked, its helper functionCalculateVariableSize()ignoresEbtInterfaceBlock. While it correctly calculates the size ofEbtStructby iterating over its fields, it falls through to a default case forEbtInterfaceBlock, severely undercalculating its size. - Unbounded Array Parsing: The array sizes within interface blocks are processed by
checkIsValidArraySize(), which checks that the size is a non-negative 32-bit integer but fails to enforce any upper memory boundary.
On the ANGLE-on-GL backend (used by default on ChromeOS and available on Linux/Android), unused interface blocks are intentionally not pruned from the AST (exempted in PruneNoOps). Thus, OutputGLSLBase::declareInterfaceBlock emits the full, oversized declaration into the translated GLSL string.
When this translated GLSL is passed to the native OpenGL driver via glCompileShader(), the native driver attempts to calculate layout sizes and offsets for the interface block members. A massive array (e.g., mat4 m[67108864];) exactly wraps a 32-bit unsigned integer boundary (67108864 * 64 bytes = 4,294,967,296 bytes = 0 modulo 2^32). This causes malformed internal driver states, such as 0-byte allocations or overlapping offsets, leading to out-of-bounds reads/writes in the GPU process.
Note: ANGLE has a link-time size validation (ValidateInterfaceBlocks), but this only executes during glLinkProgram(). The native driver is exposed to the malformed shader during the earlier glCompileShader() call, rendering the link-time validation ineffective against driver-level compiler exploitation.
Potential Reproduction Steps
Note: These are suggested steps to trigger the vulnerability. Our tooling agent does not have the ability to run code to confirm the resulting driver crash/exploit locally.
- Initialize a WebGL 2 context on a system using the ANGLE-on-GL backend (e.g., ChromeOS or Linux with
--use-angle=gl). - Create a fragment shader containing a massive UBO or SSBO declaration designed to overflow a 32-bit layout calculation, for example:
#version 300 es precision highp float; uniform Huge { mat4 m[67108864]; } b; out vec4 c; void main() { c = vec4(1.0); } - Call
gl.shaderSource()andgl.compileShader(). - Observe that the massive block declaration passes ANGLE’s parsing and reaches the native graphics driver, potentially causing a crash or enabling heap corruption in the GPU process.
Suggested Fix
- Modify
TParseContext::addInterfaceBlock()to explicitly callcheckVariableSize()for the newly created interface block instance variable before adding it to the AST. - Update
CalculateVariableSize()inParseContext.cppto properly handleEbtInterfaceBlocktypes. It should iterate over the fields of the interface block and sum their sizes, identical to the logic already in place forEbtStruct. - Consider adding a maximum bound check directly inside
checkIsValidArraySize()as an additional defense-in-depth measure.
Evaluated with Chrome root at commit: ff3d2b74fa39431785bd60e51463b08fcc71ee33
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.