Critical chrome Integer Overflow 📄 Reporter bug report 🔧 Commit mapped

Overview

Critical
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInteger overflow in ANGLE
DescriptionInteger overflow in ANGLE
ComponentANGLE
Bug ClassInteger Overflow
Tracker498400132
Fix commitd8ffa7447da0 (angle/angle) +121/-6
CISA KEVNot listed
CreditedGoogle
Disclosed2026-05-12

Files Changed

  • src/compiler/translator/ParseContext.cpp
  • src/compiler/translator/ParseContext.h
  • src/tests/gl_tests/GLSLValidationTest.cpp
From d8ffa7447da0342d3ba77c152c3343ba633ff9f5 Mon Sep 17 00:00:00 2001
From: Shahbaz Youssefi <syoussefi@chromium.org>
Date: Mon, 20 Apr 2026 11:37:22 -0400
Subject: [PATCH] Translator: Avoid setting initializer for too-large variable

Test credit kbr@chromium.org / gemini.

Fixed: chromium:498400132
Change-Id: I4b5379de712c22fcfcdbabbaf9c3f92bae209922
Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/7778457
Auto-Submit: Shahbaz Youssefi <syoussefi@chromium.org>
Reviewed-by: Kenneth Russell <kbr@chromium.org>
Reviewed-by: Geoff Lang <geofflang@chromium.org>
Commit-Queue: Shahbaz Youssefi <syoussefi@chromium.org>
---

diff --git a/src/compiler/translator/ParseContext.cpp b/src/compiler/translator/ParseContext.cpp
index 62e78a5..5e41089 100644
--- a/src/compiler/translator/ParseContext.cpp
+++ b/src/compiler/translator/ParseContext.cpp
@@ -1708,7 +1708,7 @@
     }
 }
 
-void TParseContext::checkVariableSize(const TSourceLoc &line,
+bool TParseContext::checkVariableSize(const TSourceLoc &line,
                                       const ImmutableString &identifier,
                                       const TType *type)
 {
@@ -1726,7 +1726,7 @@
     if (!mCompileOptions.rejectWebglShadersWithLargeVariables || numErrors() > 0 ||
         (mShaderType != GL_VERTEX_SHADER && mShaderType != GL_FRAGMENT_SHADER))
     {
-        return;
+        return true;
     }
 
     // Note: the only allowed interface block in webgl shaders is UBOs in std140 mode, so the size
@@ -1739,7 +1739,7 @@
     if (variableSize > kWebGLMaxVariableSizeInBytes)
     {
         error(line, "Size of declared variable exceeds implementation-defined limit", identifier);
-        return;
+        return false;
     }
 
     switch (type->getQualifier())
@@ -1785,13 +1785,14 @@
                 error(line,
                       "Size of declared private variable exceeds implementation-defined limit",
                       identifier);
-                return;
+                return false;
             }
             mTotalPrivateVariablesSize += variableSize;
             break;
         default:
             break;
     }
+    return true;
 }
 
 void TParseContext::checkVaryingLocations(const TSourceLoc &line, const TVariable *variable)
@@ -2147,7 +2148,10 @@
         return false;
     }
 
-    checkVariableSize(line, identifier, type);
+    if (!checkVariableSize(line, identifier, type))
+    {
+        return false;
+    }
     checkVariableLocations(line, *variable);
 
     // Declare the variable in IR
diff --git a/src/compiler/translator/ParseContext.h b/src/compiler/translator/ParseContext.h
index e89684b..1f83d31 100644
--- a/src/compiler/translator/ParseContext.h
+++ b/src/compiler/translator/ParseContext.h
@@ -753,7 +753,7 @@
     bool parseTessControlShaderOutputLayoutQualifier(const TTypeQualifier &typeQualifier);
     bool parseTessEvaluationShaderInputLayoutQualifier(const TTypeQualifier &typeQualifier);
 
-    void checkVariableSize(const TSourceLoc &line,
+    bool checkVariableSize(const TSourceLoc &line,
                            const ImmutableString &identifier,
                            const TType *type);
     void checkVaryingLocations(const TSourceLoc &line, const TVariable *variable);
diff --git a/src/tests/gl_tests/GLSLValidationTest.cpp b/src/tests/gl_tests/GLSLValidationTest.cpp
index 8e64731..7afe0d0 100644
--- a/src/tests/gl_tests/GLSLValidationTest.cpp
+++ b/src/tests/gl_tests/GLSLValidationTest.cpp
@@ -11,6 +11,8 @@
 #include "test_utils/CompilerTest.h"
 #include "test_utils/angle_test_configs.h"
 
+#include <sstream>
+
 using namespace angle;
 
 namespace
@@ -2752,6 +2754,115 @@
                   "'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)
+{
+    const int N1 = 256;
+    const int N2 = 256;
+    const int N3 = 65537;
+
+    std::ostringstream aInit;
+    const char *delim = "";
+    for (int i = 0; i < N1; i++)
+    {
+        aInit << delim << "1.5";
+        delim = ",";
+    }
+
+    std::ostringstream bInit;
+    delim = "";
+    for (int i = 0; i < N2; i++)
+    {
+        bInit << delim << "sA";
+        delim = ",";
+    }
+
+    std::ostringstream cInit;
+    delim = "";
+    for (int i = 0; i < N3; i++)
+    {
+        cInit << delim << "sB";
+        delim = ",";
+    }
+
+    // Set up a shader with large arrays, overflowing 32-bit math.
+    //
+    // S: 256*sizeof(float) = 1024 bytes
+    // S2: 256*sizeof(S) = 256KB
+    // c: 65537*sizeof(S2) >= 4*4GB
+    std::ostringstream fs;
+    fs << "#version 300 es\n"
+       << "precision highp float;\n"
+       << "struct S { float a[" << N1 << "]; };\n"
+       << "struct S2 { S b[" << N2 << "]; };\n"
+       << "const float a[" << N1 << "] = float[" << N1 << "](" << aInit.str() << ");\n"
+       << "const S sA = S(a);\n"
+       << "const S b[" << N2 << "] = S[" << N2 << "](" << bInit.str() << ");\n"
+       << "const S2 sB = S2(b);\n"
+       << "const S2 c[" << N3 << "] = S2[" << N3 << "](" << cInit.str() << ");\n"
+       << "void main(){}\n";
+
+    validateError(GL_FRAGMENT_SHADER, fs.str().c_str(),
+                  "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_P(WebGL2GLSLValidationTest, InlineLargeConstant)
+{
+    const int N1 = 256;
+    const int N2 = 32;
+    const int N3 = 65536 * 8 + 1;
+
+    std::ostringstream aInit;
+    const char *delim = "";
+    for (int i = 0; i < N1; i++)
+    {
+        aInit << delim << "1.5";
+        delim = ",";
+    }
+
+    std::ostringstream bInit;
+    delim = "";
+    for (int i = 0; i < N2; i++)
+    {
+        bInit << delim << "sA";
+        delim = ",";
+    }
+
+    std::ostringstream s2;
+    s2 << "S2[" << N3 << "](";
+    delim = "";
+    for (int i = 0; i < N3; i++)
+    {
+        s2 << delim << "sB";
+        delim = ",";
+    }
+    s2 << ")";
+
+    // Set up a shader with large arrays, overflowing 32-bit math.
+    //
+    // S: 256*sizeof(float) = 1024 bytes
+    // S2: 32*sizeof(S) = 32KB
+    // constant: (65536 * 8 + 1)*sizeof(S2) >= 4*4GB
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 8e64731..7afe0d0 100644
--- a/src/tests/gl_tests/GLSLValidationTest.cpp
+++ b/src/tests/gl_tests/GLSLValidationTest.cpp
@@ -11,6 +11,8 @@
 #include "test_utils/CompilerTest.h"
 #include "test_utils/angle_test_configs.h"
 
+#include <sstream>
+
 using namespace angle;
 
 namespace
@@ -2752,6 +2754,115 @@
                   "'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)
+{
+    const int N1 = 256;
+    const int N2 = 256;
+    const int N3 = 65537;
+
+    std::ostringstream aInit;
+    const char *delim = "";
+    for (int i = 0; i < N1; i++)
+    {
+        aInit << delim << "1.5";
+        delim = ",";
+    }
+
+    std::ostringstream bInit;
+    delim = "";
+    for (int i = 0; i < N2; i++)
+    {
+        bInit << delim << "sA";
+        delim = ",";
+    }
+
+    std::ostringstream cInit;
+    delim = "";
+    for (int i = 0; i < N3; i++)
+    {
+        cInit << delim << "sB";
+        delim = ",";
+    }
+
+    // Set up a shader with large arrays, overflowing 32-bit math.
+    //
+    // S: 256*sizeof(float) = 1024 bytes
+    // S2: 256*sizeof(S) = 256KB
+    // c: 65537*sizeof(S2) >= 4*4GB
+    std::ostringstream fs;
+    fs << "#version 300 es\n"
+       << "precision highp float;\n"
+       << "struct S { float a[" << N1 << "]; };\n"
+       << "struct S2 { S b[" << N2 << "]; };\n"
+       << "const float a[" << N1 << "] = float[" << N1 << "](" << aInit.str() << ");\n"
+       << "const S sA = S(a);\n"
+       << "const S b[" << N2 << "] = S[" << N2 << "](" << bInit.str() << ");\n"
+       << "const S2 sB = S2(b);\n"
+       << "const S2 c[" << N3 << "] = S2[" << N3 << "](" << cInit.str() << ");\n"
+       << "void main(){}\n";
+
+    validateError(GL_FRAGMENT_SHADER, fs.str().c_str(),
+                  "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_P(WebGL2GLSLValidationTest, InlineLargeConstant)
+{
+    const int N1 = 256;
+    const int N2 = 32;
+    const int N3 = 65536 * 8 + 1;
+
+    std::ostringstream aInit;
+    const char *delim = "";
+    for (int i = 0; i < N1; i++)
+    {
+        aInit << delim << "1.5";
+        delim = ",";
+    }
+
+    std::ostringstream bInit;
+    delim = "";
+    for (int i = 0; i < N2; i++)
+    {
+        bInit << delim << "sA";
+        delim = ",";
+    }
+
+    std::ostringstream s2;
+    s2 << "S2[" << N3 << "](";
+    delim = "";
+    for (int i = 0; i < N3; i++)
+    {
+        s2 << delim << "sB";
+        delim = ",";
+    }
+    s2 << ")";
+
+    // Set up a shader with large arrays, overflowing 32-bit math.
+    //
+    // S: 256*sizeof(float) = 1024 bytes
+    // S2: 32*sizeof(S) = 32KB
+    // constant: (65536 * 8 + 1)*sizeof(S2) >= 4*4GB
+    std::ostringstream fs;
+    fs << "#version 300 es\n"
+       << "precision highp float;\n"
+       << "struct S { float a[" << N1 << "]; };\n"
+       << "struct S2 { S b[" << N2 << "]; };\n"
+       << "const float a[" << N1 << "] = float[" << N1 << "](" << aInit.str() << ");\n"
+       << "const S sA = S(a);\n"
+       << "const S b[" << N2 << "] = S[" << N2 << "](" << bInit.str() << ");\n"
+       << "const S2 sB = S2(b);\n"
+       << "void main(){ " << s2.str() << "[0].b[0].a[0]; }\n";
+
+    validateSuccess(GL_FRAGMENT_SHADER, fs.str().c_str());
+}
+
 // Test that too large color outputs are rejected
 TEST_P(WebGL2GLSLValidationTest, LargeColorOutput)
 {
Loading diff…

Original Bug Report

reported by vm...@google.com

32-bit size_t overflow in ANGLE's TIntermAggregate::getConstantValue leads to heap OOB write

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: A 32-bit integer overflow can occur in ANGLE’s TIntermAggregate::getConstantValue when multiplying elementSize by getOutermostArraySize(). This results in an undersized memory allocation followed by a massive out-of-bounds heap write during constant array initialization. The issue is reachable via WebGL2 and impacts 32-bit platforms such as Android ARMv7.

Affected files:

  • third_party/angle/src/compiler/translator/IntermNode.cpp
  • third_party/angle/src/compiler/translator/ParseContext.cpp
  • third_party/angle/src/common/PoolAlloc.cpp
  • third_party/angle/src/compiler/translator/Types.cpp

Estimated timestamp from git blame: 2026-01-21

Description

A potential integer overflow vulnerability exists in the ANGLE compiler’s TIntermAggregate::getConstantValue function. When processing constant array constructors, the code calculates the required size for a new TConstantUnion array by multiplying the element size by the number of elements. On 32-bit systems, this multiplication can overflow the 32-bit size_t, leading to an undersized memory allocation. The subsequent loop, which initializes the array using memcpy, continues for the full number of elements, resulting in a linear out-of-bounds heap write.

Technical Details

In third_party/angle/src/compiler/translator/IntermNode.cpp around line 878, the array-constructor path performs the following calculation:

size_t elementSize = mArguments.front()->getAsTyped()->getType().getObjectSize();
constArray         = new TConstantUnion[elementSize * getOutermostArraySize()];

On 32-bit targets, if elementSize * getOutermostArraySize() exceeds UINT_MAX, the product wraps around. This causes the allocation of an undersized buffer. The following loop then performs getOutermostArraySize() iterations of memcpy, writing far past the end of the allocated buffer.

        size_t elementOffset = 0u;
        for (TIntermNode *constructorArg : mArguments)
        {
            const TConstantUnion *elementConstArray =
                constructorArg->getAsTyped()->getConstantValue();
            ASSERT(elementConstArray);
            size_t elementSizeBytes = sizeof(TConstantUnion) * elementSize;
            memcpy(static_cast<void *>(&constArray[elementOffset]),
                   static_cast<const void *>(elementConstArray), elementSizeBytes);
            elementOffset += elementSize;
        }

Amplification and Mitigations Bypassed

  1. Constant Amplification: Through the use of nested structures and large constant arrays, an attacker can geometrically increase the elementSize without exceeding parser depth limits. For example, a struct containing an array of structs can multiply the size at each level of nesting.
  2. Size Check Bypass: While ANGLE includes a checkVariableSize function (in ParseContext.cpp), it does not prevent this overflow. The function issues an error but returns void, and the caller declareVariable ignores the error state, returning true and allowing the process to continue to executeInitializer. Furthermore, if an error has already been recorded (e.g., from a preliminary large constant declaration or syntax error), checkVariableSize short-circuits due to a numErrors() > 0 check, skipping the size validation entirely for subsequent variables.
  3. Allocator Behavior: The allocation uses PoolAllocator, which for large allocations falls back to new (std::nothrow) uint8_t[] (in PoolAlloc.cpp:320). This typically results in a standalone heap chunk (PartitionAlloc in Chrome). The OOB write will corrupt adjacent heap objects.

Potential Exploitation Steps

While our tooling agent cannot execute code to provide a working proof-of-concept, the following steps suggest how an attacker might trigger the vulnerability:

  1. Craft a WebGL2 shader (ESSL 3.00) targeted at a 32-bit platform (e.g., Android ARMv7).
  2. Define nested structures to amplify the object size to a large value, e.g., 0x10000 (65,536).
    #version 300 es
    precision highp float;
    struct S  { float a[256]; };       
    struct S2 { S     b[256]; };       
    
  3. Declare a massive constant array of these structures, where the outermost array size is 0x10001 (65,537).
    const float A[256] = float[256](... 256 float literals ...);
    const S  sA     = S(A);
    const S  B[256] = S[256](sA, sA, ..., sA);
    const S2 sB     = S2(B);
    // elementSize = 0x10000 (65536)
    // arraySize = 0x10001 (65537)
    // product = 0x100010000 -> wraps to 0x10000 on 32-bit
    const S2 D[65537] = S2[65537](sB, sB, ..., sB);
    void main(){}
    
  4. Calling gl.compileShader() with this source will trigger the heap OOB write during the constant folding of variable D.
  5. The loop performs a massive linear out-of-bounds write of the attacker-controlled constant data. Although this will eventually hit an unmapped page and crash, an attacker can use concurrent Web Workers to spray objects in the GPU process heap and exploit the race window to achieve remote code execution before the crash occurs.

Suggested Fix

The multiplication in TIntermAggregate::getConstantValue should use safe math operations (e.g., using a checked math class) to prevent integer overflow. If an overflow is detected, the function should handle the error gracefully without allocating undersized memory. Additionally, checkVariableSize in ParseContext.cpp should perhaps halt the compilation or prevent executeInitializer from being called if the variable exceeds maximum size limits.

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