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
Tracker513135862
Fix commitd9508fb37d47 (angle/angle) +23/-2
CISA KEVNot listed
CreditedGoogle
Disclosed2026-06-02

Files Changed

  • src/compiler/translator/ParseContext.cpp
  • src/tests/gl_tests/GLSLValidationTest.cpp
From d9508fb37d4725a6d6a00a83e392a19403a17f85 Mon Sep 17 00:00:00 2001
From: Shahbaz Youssefi <[email protected]>
Date: Mon, 25 May 2026 16:46:48 -0400
Subject: [PATCH] Translator: Disallow equality check on structs-with-samplers

The spec doesn't say it, but glslang and drivers don't allow equality
check on structs with samplers.  This was already checked for ESSL 100.

Spec clarification requested at
https://gitlab.khronos.org/opengl/API/-/issues/297

Bug: chromium:513135862
Change-Id: I0c6d2215bc3fd9cd7f3f28e59237ebf3b86f12d0
Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/7875104
Commit-Queue: Shahbaz Youssefi <[email protected]>
Reviewed-by: Geoff Lang <[email protected]>
---

diff --git a/src/compiler/translator/ParseContext.cpp b/src/compiler/translator/ParseContext.cpp
index 438fde8..b05aec1 100644
--- a/src/compiler/translator/ParseContext.cpp
+++ b/src/compiler/translator/ParseContext.cpp
@@ -8813,8 +8813,10 @@
             // Samplers as l-values are disallowed also in ESSL 3.00, see section 4.1.7,
             // we interpret the spec so that this extends to structs containing samplers,
             // similarly to ESSL 1.00 spec.
-            if ((mShaderVersion < 300 || op == EOpAssign || op == EOpInitialize) &&
-                left->getType().isStructureContainingSamplers())
+            //
+            // ESSL 3.00 doesn't disallow comparison between structs with samplers, but glslang, the
+            // reference implementation does, as do many drivers.
+            if (left->getType().isStructureContainingSamplers())
             {
                 error(loc, "undefined operation for structs containing samplers",
                       GetOperatorString(op));
diff --git a/src/tests/gl_tests/GLSLValidationTest.cpp b/src/tests/gl_tests/GLSLValidationTest.cpp
index da82bd8..70ccd19 100644
--- a/src/tests/gl_tests/GLSLValidationTest.cpp
+++ b/src/tests/gl_tests/GLSLValidationTest.cpp
@@ -334,6 +334,25 @@
                   "'==' : undefined operation for structs containing samplers");
 }
 
+// The ESSL 3.00 spec says that equality is supported for all types, but glslang does not accept
+// equality between structs with samplers.  glslang is the reference compiler, so ANGLE follows
+// suit with the same validation.
+TEST_P(GLSLValidationTest, CompareStructsContainingSamplersESSL300)
+{
+    constexpr char kFS[] = R"(#version 300 es
+precision mediump float;
+struct S { sampler2D s; };
+uniform S a;
+uniform S b;
+out vec4 c;
+void main() {
+  c = vec4(a == b ? 1.0 : 0.0);
+})";
+
+    validateError(GL_FRAGMENT_SHADER, kFS,
+                  "'==' : undefined operation for structs containing samplers");
+}
+
 // https://crbug.com/499176133
 TEST_P(GLSLValidationTest, LongIdentifierAtLimit_1024)
 {
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 da82bd8..70ccd19 100644
--- a/src/tests/gl_tests/GLSLValidationTest.cpp
+++ b/src/tests/gl_tests/GLSLValidationTest.cpp
@@ -334,6 +334,25 @@
                   "'==' : undefined operation for structs containing samplers");
 }
 
+// The ESSL 3.00 spec says that equality is supported for all types, but glslang does not accept
+// equality between structs with samplers.  glslang is the reference compiler, so ANGLE follows
+// suit with the same validation.
+TEST_P(GLSLValidationTest, CompareStructsContainingSamplersESSL300)
+{
+    constexpr char kFS[] = R"(#version 300 es
+precision mediump float;
+struct S { sampler2D s; };
+uniform S a;
+uniform S b;
+out vec4 c;
+void main() {
+  c = vec4(a == b ? 1.0 : 0.0);
+})";
+
+    validateError(GL_FRAGMENT_SHADER, kFS,
+                  "'==' : undefined operation for structs containing samplers");
+}
+
 // https://crbug.com/499176133
 TEST_P(GLSLValidationTest, LongIdentifierAtLimit_1024)
 {
Loading diff…

Original Bug Report

reported by [email protected]

ANGLE: Potential validation bypass for equality ops on sampler-only structs in ESSL 3.00+

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: ANGLE’s ESSL translator incorrectly allows equality comparisons on structures containing samplers when using ESSL 3.00 or higher. This leads to the generation of malformed SPIR-V modules after AST transformations fail to account for the invalid state. These malformed modules are passed to the native Vulkan driver, potentially allowing for exploitation of driver-level vulnerabilities.

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

Estimated timestamp from git blame: 2015-03-23

Summary

A potential vulnerability exists in the ANGLE ESSL translator where front-end validation fails to prevent equality operators (== and !=) on structures containing samplers in ESSL 3.00+. This oversight leads to a corrupted Abstract Syntax Tree (AST) after subsequent transformations, resulting in the emission of malformed SPIR-V modules to the underlying Vulkan driver.

Root Cause Analysis

1. Front-end Validation Bypass

In third_party/angle/src/compiler/translator/ParseContext.cpp, the function binaryOpCommonCheck handles validation for binary operators. For ESSL 3.00+, the check intended to prevent operations on structs containing samplers is bypassed for equality operators:

// ParseContext.cpp:8798
if ((mShaderVersion < 300 || op == EOpAssign || op == EOpInitialize) &&
    left->getType().isStructureContainingSamplers())
{
    error(loc, "undefined operation for structs containing samplers", ...);
    return false;
}

For #version 300 es and op == EOpEqual, this condition evaluates to false, allowing the comparison to proceed into the AST.

2. AST Transformation Failure

The RewriteStructSamplers pass (RewriteStructSamplers.cpp) extracts samplers from uniforms. If a uniform struct contains only samplers, the pass removes the uniform declaration entirely. However, because RewriteStructSamplersTraverser::visitBinary only handles indexing operators, it fails to rewrite or remove the EOpEqual node. This leaves the AST with orphaned TIntermSymbol nodes referencing deleted uniform declarations.

3. Malformed SPIR-V Generation

During SPIR-V emission in OutputSPIRV.cpp, the generator encounters these orphaned symbols. In release builds, internal UNREACHABLE() guards are no-ops. The generator proceeds to emit irregular SPIR-V:

  • It creates an OpVariable in StorageClassUniform using the original struct type (which may still contain sampled-image members).
  • It applies a BuiltIn decoration with an invalid sentinel value (BuiltInMax).
  • It emits OpIEqual instructions operating directly on sampled-image types, which violates the SPIR-V specification.

Security Impact

By providing a specifically crafted GLSL shader, a malicious web page can force ANGLE to emit malformed SPIR-V and pass it to the native Vulkan driver’s vkCreateShaderModule. On platforms where the GPU process is unsandboxed (notably Android), a vulnerability in the native driver triggered by this malformed input could lead to code execution in the context of the GPU process.

Potential Reproduction Steps

  1. Use a Chromium build with the ANGLE Vulkan backend.
  2. Compile and link a fragment shader containing an equality comparison on a struct with only samplers:
    #version 300 es
    precision mediump float;
    struct S { sampler2D s; };
    uniform S a; uniform S b;
    out vec4 color;
    void main() { color = vec4(a == b ? 1.0 : 0.0); }
    
  3. Observe (in a debugger or via driver logs) the emission of invalid SPIR-V to vkCreateShaderModule.

Suggested Fix

Update TParseContext::binaryOpCommonCheck in ParseContext.cpp to correctly enforce the restriction on structs containing samplers for all versions and comparison operators:

if (left->getType().isStructureContainingSamplers())
{
    error(loc, "undefined operation for structs containing samplers", GetOperatorString(op));
    return false;
}

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