Critical chrome Logic Error 📄 Reporter bug report 🔧 Commit mapped

Overview

Critical
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
Tracker522556145
Fix commitd2c3236e24be (angle/angle) +95/-13
CISA KEVNot listed
CreditedGoogle
Disclosed2026-07-29

Files Changed

  • src/compiler/translator/ParseContext.cpp
  • src/compiler/translator/ParseContext.h
  • src/tests/gl_tests/GLSLValidationTest.cpp
From d2c3236e24be3e35b80d87de7a1148cc26237f01 Mon Sep 17 00:00:00 2001
From: Geoff Lang <geofflang@chromium.org>
Date: Mon, 15 Jun 2026 12:16:38 -0400
Subject: [PATCH] Translator: Disallow constructing too-large variables.

Validate at construction-time that a too-large variable is invalid. This
covers the case of temporary variables instead of just variable
declarations.

Add a cache of validated TTypes. The previous logic which checked if an
error had already been generated was fragile. It would return that the
type was valid immediately after any error, if there were two variables
that were too large, the second one would be accepted and instatiated.

Fixed: chromium:522556145
Fixed: chromium:522811428
Change-Id: Ie0f6d4b940cd06bc9426bdeaf3371b3c9cbf756c
Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/7939998
Commit-Queue: Shahbaz Youssefi <syoussefi@chromium.org>
Reviewed-by: Shahbaz Youssefi <syoussefi@chromium.org>
---

diff --git a/src/compiler/translator/ParseContext.cpp b/src/compiler/translator/ParseContext.cpp
index 7f5b6a6..51a90ba 100644
--- a/src/compiler/translator/ParseContext.cpp
+++ b/src/compiler/translator/ParseContext.cpp
@@ -1855,16 +1855,19 @@
     // The size check does not take std430 into account as it is intended for WebGL shaders.  For
     // the same reason, other shader stages than vertex/fragment are ignored as defer-sized
     // variables e.g. in geometry shaders are not handled.
-    //
-    // Additionally, if the shader has already failed compilation, do not validate the type sizes.
-    // For example, if previously an error is generated due to too-deep struct nesting the
-    // calculation here could overflow the stack if performed.
-    if (!mCompileOptions.rejectWebglShadersWithLargeVariables || numErrors() > 0 ||
+    if (!mCompileOptions.rejectWebglShadersWithLargeVariables ||
         (mShaderType != GL_VERTEX_SHADER && mShaderType != GL_FRAGMENT_SHADER))
     {
         return true;
     }
 
+    // Check the cache of validated types first.
+    auto preValidatedIter = mValidatedVariableTypeSizes.find(*type);
+    if (preValidatedIter != mValidatedVariableTypeSizes.end())
+    {
+        return preValidatedIter->second;
+    }
+
     // Note: the only allowed interface block in webgl shaders is UBOs in std140 mode, so the size
     // is unconditionally calculated with std140 rules if the variable is an interface block.
     // Uniform variables are treated the same way as UBOs, as they are often packed the same way
@@ -1875,6 +1878,7 @@
     if (variableSize > kWebGLMaxVariableSizeInBytes)
     {
         error(line, "Size of declared variable exceeds implementation-defined limit", identifier);
+        mValidatedVariableTypeSizes[*type] = false;
         return false;
     }
 
@@ -1921,6 +1925,7 @@
                 error(line,
                       "Size of declared private variable exceeds implementation-defined limit",
                       identifier);
+                mValidatedVariableTypeSizes[*type] = false;
                 return false;
             }
             mTotalPrivateVariablesSize += variableSize;
@@ -1928,6 +1933,8 @@
         default:
             break;
     }
+
+    mValidatedVariableTypeSizes[*type] = true;
     return true;
 }
 
@@ -6415,7 +6422,9 @@
 //
 TIntermTyped *TParseContext::addConstructor(TFunctionLookup *fnCall, const TSourceLoc &line)
 {
-    TType type                 = fnCall->constructorType();
+    TType type = fnCall->constructorType();
+    checkVariableSize(line, ImmutableString(""), &type);
+
     TIntermSequence &arguments = fnCall->arguments();
     if (type.isUnsizedArray())
     {
diff --git a/src/compiler/translator/ParseContext.h b/src/compiler/translator/ParseContext.h
index 653a5ac..9052005 100644
--- a/src/compiler/translator/ParseContext.h
+++ b/src/compiler/translator/ParseContext.h
@@ -915,6 +915,8 @@
     // Keeps track of the total size of shader-private variables, if validating that this size
     // should not exceed a sensible threshold.
     angle::base::CheckedNumeric<size_t> mTotalPrivateVariablesSize;
+    // Tracks if a type has been validated as safe in checkVariableSize.
+    TMap<TType, bool> mValidatedVariableTypeSizes;
 
     // Track state related to control flow, used for various validation:
     //
diff --git a/src/tests/gl_tests/GLSLValidationTest.cpp b/src/tests/gl_tests/GLSLValidationTest.cpp
index 8f9b2c0..2fe9f45 100644
--- a/src/tests/gl_tests/GLSLValidationTest.cpp
+++ b/src/tests/gl_tests/GLSLValidationTest.cpp
@@ -2986,12 +2986,8 @@
                   "Size of declared private variable exceeds implementation-defined limit");
 }
 
-// Test using a large constant that is declared inline, without using variable space that would
-// exceed the implementation-defined limit.  Because of the variable limit, the shader would have to
-// either inline an extremely large constant, which would practically take forever to construct and
-// parse, or use near-limit private variables.  In the latter case, the constant array constructor
-// does not cause any 32-bit overflows, so the shader succeeds compilation just fine.  If the large
-// constant is indexed, it can get constant folded, but at that point the constant is small.
+// Test using a large constant that is declared inline. Construction of such a large object, even if
+// it may be constant folded is not allowed.
 TEST_P(WebGL2GLSLValidationTest, InlineLargeConstant)
 {
     const int N1 = 256;
@@ -3040,7 +3036,82 @@
        << "const S2 sB = S2(b);\n"
        << "void main(){ " << s2.str() << "[0].b[0].a[0]; }\n";
 
-    validateSuccess(GL_FRAGMENT_SHADER, fs.str().c_str());
+    validateError(GL_FRAGMENT_SHADER, fs.str().c_str(),
+                  "'' : Size of declared variable exceeds implementation-defined limit");
+}
+
+// Validate that too-large structures cannot be instantiated as temporaries.
+TEST_P(WebGL2GLSLValidationTest, LargeStructConstructorOnly)
+{
+    const int N1 = 1024;
+    const int N2 = 1024;
+    const int N3 = 64;
+
+    std::ostringstream fs;
+    fs << "#version 300 es\n";
+    fs << "precision highp float;\n";
+    fs << "struct S1 { mat4 m[" << N1 << "]; };\n";
+    fs << "struct S2 {\n";
+    for (int i = 0; i < N2; i++)
+    {
+        fs << "    S1 m" << i << ";\n";
+    }
+    fs << "};\n";
+    fs << "struct S3 {\n";
+    for (int i = 0; i < N3; i++)
+    {
+        fs << "    S2 m" << i << ";\n";
+    }
+    fs << "};\n";
+    fs << "out vec4 color;\n";
+    fs << "void main() {\n";
+    fs << "    S1 s1;\n";
+    fs << "    color = S3(";
+    for (int i = 0; i < N3; i++)
+    {
+        fs << "S2(";
+        for (int j = 0; j < N2; j++)
+        {
+            fs << "s1";
+            if (j != N2 - 1)
+            {
+                fs << ",";
+            }
+        }
+        fs << ")";
+        if (i != N3 - 1)
+        {
+            fs << ",";
+        }
+    }
+    fs << ").m0.m0.m[0][0];\n";
+    fs << "}\n";
+
+    validateError(GL_FRAGMENT_SHADER, fs.str().c_str(),
+                  "'' : Size of declared private variable exceeds implementation-defined limit");
+}
+
+// Validate that too-large structures cannot be used as function return types
+TEST_P(WebGL2GLSLValidationTest, LargeVariableFunctionReturnType)
+{
+    constexpr char kFS[] = R"(#version 300 es
+precision highp float;
+
+struct largestruct {
+    mat4 m[1000000000u];
+};
+
+largestruct func() {
+    largestruct s;
+    return s;
+}
+
+void main() {
+    func();
+})";
+
+    validateError(GL_FRAGMENT_SHADER, kFS,
+                  "'s' : Size of declared variable exceeds implementation-defined limit");
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 8f9b2c0..2fe9f45 100644
--- a/src/tests/gl_tests/GLSLValidationTest.cpp
+++ b/src/tests/gl_tests/GLSLValidationTest.cpp
@@ -2986,12 +2986,8 @@
                   "Size of declared private variable exceeds implementation-defined limit");
 }
 
-// Test using a large constant that is declared inline, without using variable space that would
-// exceed the implementation-defined limit.  Because of the variable limit, the shader would have to
-// either inline an extremely large constant, which would practically take forever to construct and
-// parse, or use near-limit private variables.  In the latter case, the constant array constructor
-// does not cause any 32-bit overflows, so the shader succeeds compilation just fine.  If the large
-// constant is indexed, it can get constant folded, but at that point the constant is small.
+// Test using a large constant that is declared inline. Construction of such a large object, even if
+// it may be constant folded is not allowed.
 TEST_P(WebGL2GLSLValidationTest, InlineLargeConstant)
 {
     const int N1 = 256;
@@ -3040,7 +3036,82 @@
        << "const S2 sB = S2(b);\n"
        << "void main(){ " << s2.str() << "[0].b[0].a[0]; }\n";
 
-    validateSuccess(GL_FRAGMENT_SHADER, fs.str().c_str());
+    validateError(GL_FRAGMENT_SHADER, fs.str().c_str(),
+                  "'' : Size of declared variable exceeds implementation-defined limit");
+}
+
+// Validate that too-large structures cannot be instantiated as temporaries.
+TEST_P(WebGL2GLSLValidationTest, LargeStructConstructorOnly)
+{
+    const int N1 = 1024;
+    const int N2 = 1024;
+    const int N3 = 64;
+
+    std::ostringstream fs;
+    fs << "#version 300 es\n";
+    fs << "precision highp float;\n";
+    fs << "struct S1 { mat4 m[" << N1 << "]; };\n";
+    fs << "struct S2 {\n";
+    for (int i = 0; i < N2; i++)
+    {
+        fs << "    S1 m" << i << ";\n";
+    }
+    fs << "};\n";
+    fs << "struct S3 {\n";
+    for (int i = 0; i < N3; i++)
+    {
+        fs << "    S2 m" << i << ";\n";
+    }
+    fs << "};\n";
+    fs << "out vec4 color;\n";
+    fs << "void main() {\n";
+    fs << "    S1 s1;\n";
+    fs << "    color = S3(";
+    for (int i = 0; i < N3; i++)
+    {
+        fs << "S2(";
+        for (int j = 0; j < N2; j++)
+        {
+            fs << "s1";
+            if (j != N2 - 1)
+            {
+                fs << ",";
+            }
+        }
+        fs << ")";
+        if (i != N3 - 1)
+        {
+            fs << ",";
+        }
+    }
+    fs << ").m0.m0.m[0][0];\n";
+    fs << "}\n";
+
+    validateError(GL_FRAGMENT_SHADER, fs.str().c_str(),
+                  "'' : Size of declared private variable exceeds implementation-defined limit");
+}
+
+// Validate that too-large structures cannot be used as function return types
+TEST_P(WebGL2GLSLValidationTest, LargeVariableFunctionReturnType)
+{
+    constexpr char kFS[] = R"(#version 300 es
+precision highp float;
+
+struct largestruct {
+    mat4 m[1000000000u];
+};
+
+largestruct func() {
+    largestruct s;
+    return s;
+}
+
+void main() {
+    func();
+})";
+
+    validateError(GL_FRAGMENT_SHADER, kFS,
+                  "'s' : Size of declared variable exceeds implementation-defined limit");
 }
 
 // Test that too large color outputs are rejected
Loading diff…

Original Bug Report

reported by rj...@google.com

Potential GPU Process RCE via missing size validation on temporary constructor objects in ANGLE

Flapjack, 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 GLSL compiler enforces a 2GB limit on variable sizes to prevent integer overflows in native graphics drivers. However, TParseContext::addConstructor fails to check the size of anonymous temporary objects created via inline constructors. An attacker can instantiate nested structures exceeding 2GB inline, bypassing validation and potentially causing overflows and memory corruption in the native GPU driver.

Affected files:

  • third_party/angle/src/compiler/translator/ParseContext.cpp

Estimated timestamp from git blame: Unknown (Google3 checkout)

Description

ANGLE’s GLSL compiler implements security mitigations to prevent unprivileged WebGL shaders from triggering integer overflows in underlying native graphics drivers. Specifically, the function TParseContext::checkVariableSize limits individual variables to 2GB (kWebGLMaxVariableSizeInBytes) and private/temporary variables to 64KB (kWebGLMaxPrivateVariableSizeInBytes).

While explicitly declared variables are properly validated in TParseContext::declareVariable, this size validation is completely missing when a massive object is instantiated as an anonymous temporary expression. In third_party/angle/src/compiler/translator/ParseContext.cpp, the TParseContext::addConstructor function creates the AST node for a constructor call but fails to invoke checkVariableSize on the constructed type. Furthermore, because it never enters checkVariableSize, it bypasses the accumulator mTotalPrivateVariablesSize, evading the 16MB kWebGLMaxTotalPrivateVariableSizeInBytes limit.

Impact

By generating GLSL that contains a massive anonymous temporary object, ANGLE translates and passes the shader to the native graphics driver (e.g., Adreno, Mali, Metal). Native driver compilers frequently use 32-bit signed or unsigned integers for layout and offset calculations. Parsing a >2GB or >4GB temporary object will predictably trigger integer overflows, potentially resulting in undersized heap allocations or out-of-bounds stack accesses during compilation or runtime execution.

Memory corruption in the GPU process can lead to Remote Code Execution. On platforms where the GPU process is unsandboxed (e.g., Android), this translates to an immediate sandbox escape.

Potential Steps to Reproduce

Note: These are theoretical steps, as our tooling cannot currently execute code to produce a working PoC.

  1. Initialize a WebGL 2.0 context.
  2. Compile a GLSL shader that defines nested structures to construct a 4GB type without exceeding the kWebGLMaxStructNesting (4) or kMaxFieldCount (16383) limits.
#version 300 es
precision highp float;

struct S1 { mat4 m[1024]; };  // 64KB, depth 1
struct S2 { S1 f0; S1 f1; /* ... 1024 fields ... */ S1 f1023; }; // 64MB, depth 2
struct S3 { S2 g0; S2 g1; /* ... 64 fields ... */ S2 g63; }; // 4GB, depth 3

out vec4 color;
void main() {
    S1 s1;
    // The constructor for S3 bypasses checkVariableSize because it is an inline temporary.
    color = S3(S2(s1, s1 /* ... */), S2(s1, s1 /* ... */) /* ... */).g0.f0.m[0][0];
}
  1. Pass this shader to gl.compileShader. ANGLE will parse the massive S3(...) temporary and create the AST without throwing a size error.
  2. The translated code is passed to the native driver, where the 4GB allocation likely overflows 32-bit offset/size calculations.

Suggested Fix

In third_party/angle/src/compiler/translator/ParseContext.cpp, update TParseContext::addConstructor to call checkVariableSize on the constructor’s return type before creating the AST node.

TIntermTyped *TParseContext::addConstructor(TFunctionLookup *fnCall, const TSourceLoc &line)
{
    TType type = fnCall->constructorType();
    
    if (!checkVariableSize(line, ImmutableString(""), &type))
    {
        return CreateZeroNode(type);
    }
    
    // ... proceed with checking arguments

Evaluated with Chrome root at commit: 2155cb00003ec35716a76ed3246eae995f87b7ff


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