Overview

High
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
Tracker513001247
Fix commit77daa7a3be82 (angle/angle) +25/-0
CISA KEVNot listed
CreditedGoogle
Disclosed2026-05-27

Files Changed

  • src/compiler/translator/ParseContext.cpp
  • src/tests/gl_tests/GLSLValidationTest.cpp
From 77daa7a3be822e5446122a9500cf61c2b9eaf38b Mon Sep 17 00:00:00 2001
From: Shahbaz Youssefi <[email protected]>
Date: Fri, 15 May 2026 09:27:48 -0400
Subject: [PATCH] Translator: Disallow structs-with-sampelrs in constructors

Bug: chromium:513001247
Change-Id: I7bf0ed6d7a3a12588ca523348531679dcbc6ad89
Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/7852696
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 ef8d290..a256765 100644
--- a/src/compiler/translator/ParseContext.cpp
+++ b/src/compiler/translator/ParseContext.cpp
@@ -1272,6 +1272,12 @@
             error(line, reason.c_str(), "constructor");
             return false;
         }
+        else if (argTyped->getType().isStructureContainingSamplers())
+        {
+            std::string reason("cannot convert a variable with struct type containing samplers");
+            error(line, reason.c_str(), "constructor");
+            return false;
+        }
         else if (argTyped->getMemoryQualifier().writeonly)
         {
             error(line, "cannot convert a variable with writeonly", "constructor");
diff --git a/src/tests/gl_tests/GLSLValidationTest.cpp b/src/tests/gl_tests/GLSLValidationTest.cpp
index 3446dcb..c63e089 100644
--- a/src/tests/gl_tests/GLSLValidationTest.cpp
+++ b/src/tests/gl_tests/GLSLValidationTest.cpp
@@ -1128,6 +1128,25 @@
                   "'constructor' : cannot convert a variable with type sampler2D");
 }
 
+// Test that a struct-with-sampler can't be used in a constructor
+TEST_P(GLSLValidationTest_ES3, ConstructorWithStructWithSampler)
+{
+    constexpr char kFS[] = R"(#version 300 es
+        precision mediump float;
+        struct S {
+            sampler2D inStruct;
+        };
+        uniform S s;
+        out vec4 color;
+        void main()
+        {
+            color = texture(S[2](s, s)[0].inStruct, vec2(0));
+        })";
+
+    validateError(GL_FRAGMENT_SHADER, kFS,
+                  "'constructor' : cannot convert a variable with struct type containing samplers");
+}
+
 // Test that void can't be used in constructor argument list
 TEST_P(GLSLValidationTest, VoidInConstructorArguments)
 {
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 3446dcb..c63e089 100644
--- a/src/tests/gl_tests/GLSLValidationTest.cpp
+++ b/src/tests/gl_tests/GLSLValidationTest.cpp
@@ -1128,6 +1128,25 @@
                   "'constructor' : cannot convert a variable with type sampler2D");
 }
 
+// Test that a struct-with-sampler can't be used in a constructor
+TEST_P(GLSLValidationTest_ES3, ConstructorWithStructWithSampler)
+{
+    constexpr char kFS[] = R"(#version 300 es
+        precision mediump float;
+        struct S {
+            sampler2D inStruct;
+        };
+        uniform S s;
+        out vec4 color;
+        void main()
+        {
+            color = texture(S[2](s, s)[0].inStruct, vec2(0));
+        })";
+
+    validateError(GL_FRAGMENT_SHADER, kFS,
+                  "'constructor' : cannot convert a variable with struct type containing samplers");
+}
+
 // Test that void can't be used in constructor argument list
 TEST_P(GLSLValidationTest, VoidInConstructorArguments)
 {
Loading diff…

Original Bug Report

reported by [email protected]

Memory corruption in ANGLE via sampler-bearing struct array constructors

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 Chrome Security team. Please see https://chromium.googlesource.com/chromium/src/+/main/docs/security/ai-generated-security-bugs-faq.md for more information.

Overview: A logic error in ANGLE’s SPIR-V translator allows sampler-bearing structures to be used in array constructors, bypassing intended security checks. This results in the generation of malformed SPIR-V instructions, which can lead to type confusion and potential memory corruption within the GPU process.

Affected files:

  • third_party/angle/src/compiler/translator/ParseContext.cpp
  • third_party/angle/src/compiler/translator/tree_ops/RewriteStructSamplers.cpp
  • third_party/angle/src/compiler/translator/spirv/OutputSPIRV.cpp
  • third_party/angle/src/compiler/translator/Types.cpp
  • third_party/angle/src/compiler/translator/spirv/TranslatorSPIRV.cpp

Estimated timestamp from git blame: Unknown (Google3 checkout)

Summary

A potential vulnerability has been identified in ANGLE’s SPIR-V translator that allows for the creation of malformed SPIR-V. The issue stems from a failure to detect opaque types (like samplers) nested within structures during constructor validation, combined with a failure to update type information during AST transformations. This can lead to spec-invalid SPIR-V being passed to the underlying Vulkan driver, potentially resulting in memory corruption in the GPU process.

Root Cause Analysis

1. Incomplete Opaque Type Check in checkConstructorArguments

In third_party/angle/src/compiler/translator/ParseContext.cpp, the function checkConstructorArguments validates constructor parameters. It checks if an argument is an opaque type using IsOpaqueType(argTyped->getBasicType()). However, for a structure containing a sampler, the basic type is EbtStruct, which IsOpaqueType returns false for. This allows constructors for structures containing samplers (which are generally prohibited) to be successfully parsed into the AST, especially when used within an array constructor.

2. Stale Type Information in RewriteStructSamplers

The RewriteStructSamplers transformation (in third_party/angle/src/compiler/translator/tree_ops/RewriteStructSamplers.cpp) extracts samplers from uniforms and creates a “stripped” version of the structure. While it updates symbols and field selections, it does not override visitAggregate. Since EOpConstruct (constructor) nodes are a type of TIntermAggregate, they are not visited, and their mType field retains the original, sampler-bearing structure type. This creates an inconsistency where a constructor expecting a sampler-bearing struct is passed a stripped struct as an argument.

Technical Impact

During SPIR-V emission in OutputSPIRV.cpp, this AST inconsistency results in malformed instructions:

  • Mismatched OpCompositeConstruct: The instruction is emitted with a result type ID for the original sampler-bearing struct array, but with constituents that are the stripped structs. This violates SPIR-V type matching rules.
  • Invalid OpCompositeExtract: Operations like struct comparisons (==) on these arrays will attempt to extract fields based on the original struct layout. This leads to emitting OpCompositeExtract with incorrect indices, causing type confusion in the Vulkan driver (e.g., treating a float as a sampler descriptor).

Since SPIR-V validation is typically disabled in release builds, these malformed blobs are passed directly to vkCreateShaderModule. Driver behavior on such invalid input is undefined and has historically led to memory corruption within the shader compiler.

Potential Attack Vector

  1. A compromised renderer process provides a crafted GLSL fragment shader to the WebGL2 API.
  2. The shader defines a struct with a sampler and uses it in an array constructor: struct S { sampler2D s; float f; }; uniform S a; ... S[1](a) == S[1](a) ....
  3. ANGLE translates this into malformed SPIR-V and passes it to the GPU process.
  4. The Vulkan driver in the GPU process crashes or suffers memory corruption while compiling the malformed shader.

On platforms like Android, where the GPU process is unsandboxed, this could potentially allow for a significant elevation of privilege.

Suggested Fix

  1. Improve Validation: In TParseContext::checkConstructorArguments, replace the IsOpaqueType check with a recursive check (e.g., ContainsOpaque<IsOpaqueFunc>(argTyped->getType())) to ensure structures containing opaque types cannot be used as constructor arguments.
  2. Update Transformations: In RewriteStructSamplers.cpp, override visitAggregate to ensure that EOpConstruct nodes have their mType updated to the stripped structure type when their constituents are modified.

Evaluated with Chrome root at commit: b3153093eb3c78c3e88ccf562bcbc20437a04b0e


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