CVE-2026-8519
Overview
Files Changed
src/compiler/translator/ParseContext.cppsrc/compiler/translator/ParseContext.hsrc/tests/gl_tests/GLSLValidationTest.cpp
Patch
From d8ffa7447da0342d3ba77c152c3343ba633ff9f5 Mon Sep 17 00:00:00 2001 From: Shahbaz Youssefi <[email protected]> Date: Mon, 20 Apr 2026 11:37:22 -0400 Subject: [PATCH] Translator: Avoid setting initializer for too-large variable Test credit [email protected] / gemini. Fixed: chromium:498400132 Change-Id: I4b5379de712c22fcfcdbabbaf9c3f92bae209922 Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/7778457 Auto-Submit: Shahbaz Youssefi <[email protected]> Reviewed-by: Kenneth Russell <[email protected]> Reviewed-by: Geoff Lang <[email protected]> Commit-Queue: Shahbaz Youssefi <[email protected]> --- 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
Regression Test / PoC
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)
{
Original Bug Report
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.cppthird_party/angle/src/compiler/translator/ParseContext.cppthird_party/angle/src/common/PoolAlloc.cppthird_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
- Constant Amplification: Through the use of nested structures and large constant arrays, an attacker can geometrically increase the
elementSizewithout exceeding parser depth limits. For example, a struct containing an array of structs can multiply the size at each level of nesting. - Size Check Bypass: While ANGLE includes a
checkVariableSizefunction (inParseContext.cpp), it does not prevent this overflow. The function issues an error but returnsvoid, and the callerdeclareVariableignores the error state, returningtrueand allowing the process to continue toexecuteInitializer. Furthermore, if an error has already been recorded (e.g., from a preliminary large constant declaration or syntax error),checkVariableSizeshort-circuits due to anumErrors() > 0check, skipping the size validation entirely for subsequent variables. - Allocator Behavior: The allocation uses
PoolAllocator, which for large allocations falls back tonew (std::nothrow) uint8_t[](inPoolAlloc.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:
- Craft a WebGL2 shader (ESSL 3.00) targeted at a 32-bit platform (e.g., Android ARMv7).
- 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]; }; - 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(){} - Calling
gl.compileShader()with this source will trigger the heap OOB write during the constant folding of variableD. - 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.