Medium chrome Logic Error 📄 Reporter bug report 🔧 Commit mapped

Overview

Medium
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInsufficient validation of untrusted input in ANGLE
DescriptionInsufficient validation of untrusted input in ANGLE
ComponentANGLE
Bug ClassLogic Error
Tracker499124128
Fix commitcb03599b340f (angle/angle) +23/-3
CISA KEVNot listed
CreditedGoogle
Disclosed2026-06-02

Files Changed

  • src/compiler/translator/ParseContext.cpp
  • src/tests/gl_tests/GLSLValidationTest.cpp
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)
 {
Loading diff…

Regression Test / PoC

shipped with the fix
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)
 {
Loading diff…

Original Bug Report

reported by vm...@google.com

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.cpp
  • third_party/angle/src/compiler/translator/glsl/OutputGLSLBase.cpp
  • third_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:

  1. Bypass of Validation Check: When parsing an interface block, TParseContext::addInterfaceBlock() directly instantiates a new TVariable and calls declareIRVariable(). It does not call declareVariable(), which is the standard function that invokes checkVariableSize().
  2. Flawed Size Calculation: Even if checkVariableSize() were invoked, its helper function CalculateVariableSize() ignores EbtInterfaceBlock. While it correctly calculates the size of EbtStruct by iterating over its fields, it falls through to a default case for EbtInterfaceBlock, severely undercalculating its size.
  3. 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.

  1. Initialize a WebGL 2 context on a system using the ANGLE-on-GL backend (e.g., ChromeOS or Linux with --use-angle=gl).
  2. 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); }
    
  3. Call gl.shaderSource() and gl.compileShader().
  4. 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

  1. Modify TParseContext::addInterfaceBlock() to explicitly call checkVariableSize() for the newly created interface block instance variable before adding it to the AST.
  2. Update CalculateVariableSize() in ParseContext.cpp to properly handle EbtInterfaceBlock types. It should iterate over the fields of the interface block and sum their sizes, identical to the logic already in place for EbtStruct.
  3. 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.

View on issue tracker