High watch CVSS 9.6 chrome Input Validation 📄 Reporter bug report 🔧 Commit mapped

Overview

High
Severity
9.6
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactImproper input validation in Dawn
DescriptionImproper input validation in Dawn
ComponentDawn
Sub-componentTranslator
Bug ClassInput Validation
Action tierwatch — ub-class only — no demonstrated corruption path
Computed by PatchGap's deterministic exploitability ruler from the fixing commit. patch-now = RCE-class & attacker-reachable in a default config, or exploited in the wild; watch = real security signal below that bar; noise = no security relevance.
Tracker525683797
Fix commitbaeac58b753d (angle/angle) +26/-0
CWECWE-20 (Improper input validation)
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:H
CISA KEVNot listed
CreditedGoogle
Reported2026-06-19 (per vendor advisory)
Fix landed2026-07-14 (public git)
Disclosed2026-08-25
Patch gap42 days — fix public in source before the advisory

Background

`TType`
The ANGLE translator’s representation of a GLSL type, including its base type, array dimensions, backing mStructure, and (for uniform/interface members) its mInterfaceBlock.
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 via TType::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.

Key insight
The single mistake is an incomplete comparator: 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

  1. 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 TType size to be computed and cached.
  2. Declare a colliding large UBO The shader then declares a second interface block containing an oversized array (e.g. int rr[~1U]) whose TType differs only by mInterfaceBlock, so it hashes/orders to the same cache key as the small block.
  3. 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)
  4. 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

An attacker who can supply a crafted shader (e.g. via WebGL/WebGPU from a web page in the renderer/GPU context) can bypass the interface-block size-limit validation in ANGLE’s translator, so an oversized uniform block reaches the backend as if it were within limits. The vendor advisory rates this high severity and does not report in-the-wild exploitation; NVD scores it CVSS 9.6. Any further memory corruption or escalation beyond the validation bypass is not demonstrated by the patch. (inferred) The precondition is the ability to compile a shader containing two interface blocks whose types collide under the pre-patch comparator.

Files Changed

  • src/compiler/translator/Types.h
  • src/tests/gl_tests/GLSLValidationTest.cpp

Audit Directions

  • Incomplete comparators as cache keys
    Audit 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 results
    Review 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 handling
    Search the translator for other places (layout, offset, size computation) where mInterfaceBlock vs mStructure are treated interchangeably or where block identity is dropped from a comparison.
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)
 {
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 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)
 {
Loading diff…

Original Bug Report

reported by vm...@google.com

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:

  1. 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.
  2. The attacker creates a compute shader module (device.createShaderModule) declaring a large storage buffer of up to 4 GiB (just under Tint’s 0xFFFFFFFF limit):
    @group(0) @binding(0) var<storage, read> x : array<mat4x4f, 67108863>; // ~4 GiB
    @compute @workgroup_size(1) fn main() { _ = x[0]; }
    
  3. Tint accepts the type (since storage buffers are exempt from the kMaxArrayElementCount limit) and generates GLSL ES 3.10 code containing a layout(std430) buffer with the massive array size.
  4. Dawn attempts to compile this shader using the statically linked ANGLE GLES context via glCompileShader.
  5. In ANGLE, the checkVariableSize function returns true early without checking the size of the block because mShaderType is GL_COMPUTE_SHADER.
  6. ANGLE’s GL backend forwards the translated GLSL directly to the native Android GPU vendor’s shader compiler via glCompileShader inside 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.

View on issue tracker