CVE-2026-11061
Overview
Files Changed
src/compiler/translator/Symbol.cppsrc/compiler/translator/Types.cppsrc/tests/gl_tests/GLSLValidationTest.cpp
Patch
From 268683ad32ba2c93c64518d2dc7a8d8058061ac3 Mon Sep 17 00:00:00 2001 From: Shahbaz Youssefi <[email protected]> Date: Tue, 07 Apr 2026 17:07:23 -0400 Subject: [PATCH] Translator: Fix struct name mangling collision Bug: chromium:499031961 Change-Id: I13a459386b40bcbaaf9333bd726b0a912f89827f Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/7736235 Reviewed-by: Geoff Lang <[email protected]> Commit-Queue: Shahbaz Youssefi <[email protected]> --- diff --git a/src/compiler/translator/Symbol.cpp b/src/compiler/translator/Symbol.cpp index 9c88538..2271e2c 100644 --- a/src/compiler/translator/Symbol.cpp +++ b/src/compiler/translator/Symbol.cpp @@ -32,8 +32,6 @@ constexpr const ImmutableString kImageAtomicExchangeName("imageAtomicExchange"); constexpr const ImmutableString kAtomicCounterName("atomicCounter"); -static const char kFunctionMangledNameSeparator = '('; - } // anonymous namespace TSymbol::TSymbol(TSymbolTable *symbolTable, @@ -230,6 +228,8 @@ ImmutableString TFunction::buildMangledName() const { + constexpr char kFunctionMangledNameSeparator = '('; + ImmutableString name = this->name(); std::string newName(name.data(), name.length()); newName += kFunctionMangledNameSeparator; diff --git a/src/compiler/translator/Types.cpp b/src/compiler/translator/Types.cpp index c0882da..a3564df 100644 --- a/src/compiler/translator/Types.cpp +++ b/src/compiler/translator/Types.cpp @@ -463,6 +463,8 @@ } else { + constexpr char kStructMangledNameSeparator = ':'; + ASSERT(type == EbtStruct || type == EbtInterfaceBlock); switch (type) { @@ -472,12 +474,14 @@ { mangledName += mStructure->name().data(); } + mangledName += kStructMangledNameSeparator; mangledName += mStructure->mangledFieldList(); mangledName += '}'; break; case EbtInterfaceBlock: mangledName += "{i"; mangledName += mInterfaceBlock->name().data(); + mangledName += kStructMangledNameSeparator; mangledName += mInterfaceBlock->mangledFieldList(); mangledName += '}'; break; diff --git a/src/tests/gl_tests/GLSLValidationTest.cpp b/src/tests/gl_tests/GLSLValidationTest.cpp index 438df0d..4b40128 100644 --- a/src/tests/gl_tests/GLSLValidationTest.cpp +++ b/src/tests/gl_tests/GLSLValidationTest.cpp @@ -2291,6 +2291,36 @@ "l-value required (can't modify an input \"f\")"); } +// Test no mangling collision in structs +TEST_P(GLSLValidationTest, ManglingCollisionInStruct) +{ + constexpr char kFS[] = R"(precision mediump float; +struct A00B { vec4 y; }; +struct A { float x; vec4 y; }; + +void foo(A00B p); +void foo(A p) {} + +void main() { + A00B v = A00B(vec4(0)); + foo(v); +})"; + validateError(GL_FRAGMENT_SHADER, kFS, "Function foo() called by main() is undefined"); +} + +// Test no mangling collision in function parameters +TEST_P(GLSLValidationTest, ManglingCollisionInFunctionParams) +{ + constexpr char kFS[] = R"(precision mediump float; +void fooA00B(vec4 y); +void foo(float x, vec4 y) {} + +void main() { + fooA00B(vec4(0)); +})"; + validateError(GL_FRAGMENT_SHADER, kFS, "Function fooA00B() called by main() is undefined"); +} + // Test that infinite loop with while(true) is rejected TEST_P(WebGL2GLSLValidationTest, InfiniteLoopWhileTrue) {
Regression Test / PoC
diff --git a/src/tests/gl_tests/GLSLValidationTest.cpp b/src/tests/gl_tests/GLSLValidationTest.cpp
index 438df0d..4b40128 100644
--- a/src/tests/gl_tests/GLSLValidationTest.cpp
+++ b/src/tests/gl_tests/GLSLValidationTest.cpp
@@ -2291,6 +2291,36 @@
"l-value required (can't modify an input \"f\")");
}
+// Test no mangling collision in structs
+TEST_P(GLSLValidationTest, ManglingCollisionInStruct)
+{
+ constexpr char kFS[] = R"(precision mediump float;
+struct A00B { vec4 y; };
+struct A { float x; vec4 y; };
+
+void foo(A00B p);
+void foo(A p) {}
+
+void main() {
+ A00B v = A00B(vec4(0));
+ foo(v);
+})";
+ validateError(GL_FRAGMENT_SHADER, kFS, "Function foo() called by main() is undefined");
+}
+
+// Test no mangling collision in function parameters
+TEST_P(GLSLValidationTest, ManglingCollisionInFunctionParams)
+{
+ constexpr char kFS[] = R"(precision mediump float;
+void fooA00B(vec4 y);
+void foo(float x, vec4 y) {}
+
+void main() {
+ fooA00B(vec4(0));
+})";
+ validateError(GL_FRAGMENT_SHADER, kFS, "Function fooA00B() called by main() is undefined");
+}
+
// Test that infinite loop with while(true) is rejected
TEST_P(WebGL2GLSLValidationTest, InfiniteLoopWhileTrue)
{
Original Bug Report
ANGLE struct mangled-name collision enables invalid SPIR-V generation
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 translator lacks a separator between a struct’s name and its mangled field list, allowing distinct structs to produce identical mangled names. This type confusion mutates function prototypes, leading to the generation of type-mismatched SPIR-V code that bypasses validation and is passed to the GPU driver.
Affected files:
third_party/angle/src/compiler/translator/Types.cppthird_party/angle/src/compiler/translator/SymbolTable.cppthird_party/angle/src/compiler/translator/ParseContext.cppthird_party/angle/src/compiler/translator/Symbol.cppthird_party/angle/src/compiler/translator/spirv/OutputSPIRV.cpp
Estimated timestamp from git blame: 2018-03-13
Summary
A potential vulnerability in ANGLE’s GLSL translator allows an attacker to cause a mangled-name collision between structurally distinct structs. This can be abused to trigger type confusion in the compiler’s symbol table, resulting in ANGLE generating invalid SPIR-V instructions. Because ANGLE disables SPIR-V validation in release builds, the malformed instructions are passed directly to the underlying GPU driver. This could potentially lead to driver-level memory corruption and a GPU process sandbox escape.
Note: These steps are based on static code analysis; a working Proof of Concept has not yet been executed by our tooling.
Root Cause Analysis
In third_party/angle/src/compiler/translator/Types.cpp, TType::buildMangledName() constructs a mangled name for a struct by concatenating "{s", the struct name, and the mangledFieldList() without any delimiter. Since the mangled field list uses standard alphanumeric characters, two distinct structs can produce the exact same mangled string.
For example:
struct A00B { vec4 y; }(Name:A00B, Field:vec4mangles to30B) -> Mangled Name:0{sA00B30B}struct A { float x; vec4 y; }(Name:A, Fields:floatmangles to00B,vec4to30B) -> Mangled Name:0{sA00B30B}
Potential Exploitation Steps
An attacker could exploit this type confusion by supplying a malicious WebGL shader:
- The shader defines the two colliding structs, a function prototype
void foo(A00B p);, and a function definitionvoid foo(A p) { ... }. - During parsing, ANGLE computes identical mangled names for both the prototype and the definition (e.g.,
foo(0{sA00B30B}). - In
ParseContext.cpp, the definition is mapped to the existing prototype.TSymbolTable::setFunctionParameterNamesFromDefinitionis called, which invokesfirstDeclaration->shareParameters(*function). TFunction::shareParametersperforms a shallow copy of the definition’s parameter pointers (mParameters) onto the prototype. The prototype is now mutated and internally expects an argument of typestruct Ainstead ofstruct A00B.- The attacker calls
foo(v)insidemain()with an argumentvof typestruct A00B. The parser matches this call to the mutated prototype because the mangled names match. - During SPIR-V translation (
spirv/OutputSPIRV.cpp),createFunctionCallallocates a temporary variable for the expected parameter type (struct A*). - It then emits an
OpStoreinstruction storing the caller’s argument object (struct A00B) into the temporary variable (struct A*). - This violates the SPIR-V specification, which strictly requires the object type to match the pointer’s pointed-to type.
- In Release builds of Chromium,
spirv::Validate()is skipped (it relies onASSERTand aconstexprflag that disables it outside of debug builds). The malformed SPIR-V is handed off to the Vulkan driver. - GPU driver compilers generally trust the validation phase of the front-end toolchain. Interpreting an
A00Bobject as anAobject in memory can lead to out-of-bounds memory accesses or memory corruption within the highly privileged GPU process.
Suggested Fix
Update TType::buildMangledName() in src/compiler/translator/Types.cpp to include a unique delimiter (such as - or ;) between the struct name and the field list.
For example:
mangledName += mStructure->name().data();
mangledName += '-'; // Add delimiter
mangledName += mStructure->mangledFieldList();
This ensures that structurally distinct structs will always have unique mangled names.
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.