Overview

High
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactImproper validation of array index in ANGLE
DescriptionImproper validation of array index in ANGLE
ComponentANGLE
Bug ClassLogic Error
Tracker540019091
Fix commite422fa19258e (angle/angle) +148/-61
CISA KEVNot listed
CreditedGoogle
Disclosed2026-09-08

Background

ANGLE
Chrome’s shader translator/validation layer that compiles WebGL ESSL/GLSL into the platform’s native graphics API.
`EOpComma`
the AST operator node for GLSL’s comma operator, whose value and qualifier come from its right-hand operand (asBinary->getRight()).
Qualifier (`getQualifier`)
a TIntermTyped node’s storage/usage classification (e.g. EvqFragData, EvqFragmentOut, EvqPerVertexIn) that ANGLE uses to gate indexing restrictions.
`TParseContext`
ANGLE’s parse-time context in ParseContext.cpp that validates expression[index] accesses and reports errors for illegal or out-of-range indices.

Root Cause Analysis

When validating an expression[index] access, the index-handling code in ParseContext.cpp gated every restriction directly on baseExpression->getQualifier(), baseExpression->isArray(), baseExpression->isMatrix(), and related type queries. The invariant it assumed was that baseExpression is the actual indexed object, but a comma expression such as (x, gl_SomeBuiltIn) produces an EOpComma binary node whose own qualifier is the generic result qualifier rather than EvqFragData/EvqFragmentOut/etc. of its right-hand side.

As a result, wrapping a restricted built-in in a comma operator caused all the qualifier-gated checks — the “index must be constant” rules for fragment outputs and gl_FragData, and the out-of-range checkIndexLessThan bound clamping — to be silently skipped.

The fix adds RemoveCommaLeftHandSize, which walks down the right-hand operand of any chained EOpComma nodes to obtain the truly indexed expression, and then performs every qualifier/array/matrix/vector check against that effectivelyIndexedExpression instead of the raw baseExpression. This restores the invariant that (x, gl_SomeBuiltIn)[index] is subject to the identical restrictions as gl_SomeBuiltIn[index].

Key insight
The single mistake was treating the comma operator’s result node as the indexed object, so a restricted built-in hidden behind a comma escaped validation; the fix normalizes the base by stripping comma left-hand sides down to the real indexed expression before any qualifier or bounds check.

Attack Path

  1. Craft a comma-wrapped index In a WebGL shader, write an indexed access whose base is a comma expression, e.g. (0, gl_FragData)[i] or (x, someIoBlock)[i], so the base AST node is an EOpComma rather than the built-in itself.
  2. Bypass the constant-index gate Because getQualifier() on the comma node is not EvqFragData/EvqFragmentOut/EvqLastFragData, the “array indexes must be constant” errors never fire, allowing a dynamic index.
  3. Bypass the out-of-range clamp For the same reason isArray()/isMatrix()/isVector() checks against the comma node skip checkIndexLessThan, so index is not clamped to the object’s true size.
  4. Emit unvalidated translated shader ANGLE passes the shader through to the backend translator without the safety rewrite, leaving an unbounded or restriction-violating index in the generated native shader.

Impact Assessment

An attacker supplying a crafted WebGL shader gains the ability to defeat ANGLE’s index-validation for restricted built-ins and to skip bounds clamping on array/matrix/vector accesses, producing native shaders that read or write outside intended element ranges. This occurs in the GPU process (or, depending on the sandbox model, the process hosting ANGLE) at shader-compile time and only requires that the victim load attacker-controlled WebGL content. It is a translator logic error; the actual memory-safety consequence depends on how the downstream driver handles the resulting out-of-range access.

Files Changed

  • src/compiler/translator/ParseContext.cpp

Audit Directions

  • Comma-operator normalization
    Audit every site that inspects getQualifier(), isArray(), or type shape of an indexed or operand TIntermTyped for whether an EOpComma wrapper could hide the real node; such checks should first strip comma left-hand sides.
  • Qualifier-gated validation
    Review other restrictions keyed on specific qualifiers (EvqPerVertexIn, EvqPatchIn/EvqPatchOut, shader I/O blocks, EvqSecondaryFragDataEXT) to confirm they cannot be evaded by ternary, comma, or other value-forwarding expressions.
  • Base-expression identity assumptions
    Look for translator code that assumes a syntactic baseExpression is the semantic object being indexed and add explicit normalization before bounds and constant-index enforcement.
From e422fa19258e4dfa2b56cd7a9185cae7a36cd502 Mon Sep 17 00:00:00 2001
From: Shahbaz Youssefi <syoussefi@chromium.org>
Date: Fri, 31 Jul 2026 00:21:39 -0400
Subject: [PATCH] Translator: Fix index-checks vs comma

In `expression[index]`, when checking if certain limitations apply to
the `expression` and `index`, skip all comma operator left-hand sides in
`expression` before checking the qualifier.  If for example
`gl_SomeBuiltIn[index]` has a restriction, so does
`(x, gl_SomeBuiltIn)[index]`.

Bug: chromium:540019091
Change-Id: Ib01937521eba275220f7e157686606cda53331f2
Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/8178173
Reviewed-by: Yuxin Hu <yuxinhu@google.com>
Commit-Queue: Shahbaz Youssefi <syoussefi@chromium.org>
---

diff --git a/src/compiler/translator/ParseContext.cpp b/src/compiler/translator/ParseContext.cpp
index 9abfb33..0b17aaf 100644
--- a/src/compiler/translator/ParseContext.cpp
+++ b/src/compiler/translator/ParseContext.cpp
@@ -529,25 +529,21 @@
     return components;
 }
 
-bool IsWholeArrayFragDataUsed(TIntermTyped *node)
+TIntermTyped *RemoveCommaLeftHandSize(TIntermTyped *node)
 {
-    if (node->getQualifier() == EvqFragData)
+    TIntermTyped *current = node;
+    while (true)
     {
-        return true;
+        TIntermBinary *asBinary = current->getAsBinaryNode();
+        if (asBinary == nullptr || asBinary->getOp() != EOpComma)
+        {
+            break;
+        }
+
+        current = asBinary->getRight();
     }
 
-    TIntermBinary *asBinary = node->getAsBinaryNode();
-    if (asBinary != nullptr && asBinary->getOp() == EOpComma)
-    {
-        return IsWholeArrayFragDataUsed(asBinary->getRight());
-    }
-
-    // Either this is not ESSL 100 (where gl_FragData may be used), or gl_FragData is not used as a
-    // whole array.
-    //
-    // Note: ESSL 100 does not allow arrays in ternary operator, so there is no need to check for
-    // TIntermTernary here for a whole-array use of gl_FragData.
-    return false;
+    return current;
 }
 }  // namespace
 
@@ -3395,7 +3391,10 @@
     for (size_t i = 0; i < fnCandidate->getParamCount(); ++i)
     {
         TIntermTyped *argument = (*fnCall->getSequence())[i]->getAsTyped();
-        if (IsWholeArrayFragDataUsed(argument))
+        // Note: ESSL 100 does not allow arrays in ternary operator, so there is no need to check
+        // for TIntermTernary here for a whole-array use of gl_FragData, only descending into
+        // EOpComma nodes is sufficient.
+        if (RemoveCommaLeftHandSize(argument)->getQualifier() == EvqFragData)
         {
             // The whole array is passed to the function.  For validation purposes, assume all
             // indices are accessed in the function.
@@ -7307,7 +7306,8 @@
         return CreateZeroNode(TType(EbtFloat, EbpHigh, EvqConst));
     }
 
-    switch (baseExpression->getQualifier())
+    TIntermTyped *effectivelyIndexedExpression = RemoveCommaLeftHandSize(baseExpression);
+    switch (effectivelyIndexedExpression->getQualifier())
     {
         case EvqPerVertexIn:
             if (mGeometryShaderInputPrimitiveType == EptUndefined &&
@@ -7341,9 +7341,9 @@
     // effects - like array length() method on a non-constant array.
     if (indexExpression->getQualifier() != EvqConst || indexConstantUnion == nullptr)
     {
-        if (baseExpression->isInterfaceBlock())
+        if (effectivelyIndexedExpression->isInterfaceBlock())
         {
-            switch (baseExpression->getQualifier())
+            switch (effectivelyIndexedExpression->getQualifier())
             {
                 case EvqPerVertexIn:
                     break;
@@ -7364,9 +7364,9 @@
                     break;
                 default:
                     // It's ok for shader I/O blocks to be dynamically indexed
-                    if (!IsShaderIoBlock(baseExpression->getQualifier()) &&
-                        baseExpression->getQualifier() != EvqPatchIn &&
-                        baseExpression->getQualifier() != EvqPatchOut)
+                    if (!IsShaderIoBlock(effectivelyIndexedExpression->getQualifier()) &&
+                        effectivelyIndexedExpression->getQualifier() != EvqPatchIn &&
+                        effectivelyIndexedExpression->getQualifier() != EvqPatchOut)
                     {
                         // We can reach here only in error cases.
                         ASSERT(mDiagnostics->numErrors() > 0);
@@ -7374,29 +7374,30 @@
                     break;
             }
         }
-        else if (baseExpression->getQualifier() == EvqFragmentOut ||
-                 baseExpression->getQualifier() == EvqFragmentInOut)
+        else if (effectivelyIndexedExpression->getQualifier() == EvqFragmentOut ||
+                 effectivelyIndexedExpression->getQualifier() == EvqFragmentInOut)
         {
             error(location,
                   "array indexes for fragment outputs must be constant integral expressions", "[");
         }
-        else if (baseExpression->getQualifier() == EvqLastFragData)
+        else if (effectivelyIndexedExpression->getQualifier() == EvqLastFragData)
         {
             error(location,
                   "array indexes for gl_LastFragData must be constant integral expressions", "[");
         }
-        else if (mShaderSpec == SH_WEBGL2_SPEC && baseExpression->getQualifier() == EvqFragData)
+        else if (mShaderSpec == SH_WEBGL2_SPEC &&
+                 effectivelyIndexedExpression->getQualifier() == EvqFragData)
         {
             error(location, "array index for gl_FragData must be constant zero", "[");
         }
         else if (mShaderSpec == SH_WEBGL2_SPEC &&
-                 baseExpression->getQualifier() == EvqSecondaryFragDataEXT)
+                 effectivelyIndexedExpression->getQualifier() == EvqSecondaryFragDataEXT)
         {
             error(location, "array index for gl_SecondaryFragDataEXT must be constant zero", "[");
         }
-        else if (baseExpression->isArray())
+        else if (effectivelyIndexedExpression->isArray())
         {
-            TBasicType elementType = baseExpression->getType().getBasicType();
+            TBasicType elementType = effectivelyIndexedExpression->getType().getBasicType();
 
             // Note: In Section 12.30 of the ESSL 3.00 spec on p143-144:
             //
@@ -7473,9 +7474,10 @@
             safeIndex = 0;
         }
 
-        if (!baseExpression->getType().isUnsizedArray())
+        if (!effectivelyIndexedExpression->getType().isUnsizedArray())
         {
-            if (baseExpression->isArray() && baseExpression->getQualifier() == EvqFragData)
+            if (effectivelyIndexedExpression->isArray() &&
+                effectivelyIndexedExpression->getQualifier() == EvqFragData)
             {
                 mMaxFragDataArrayIndexUsed = std::max(mMaxFragDataArrayIndexUsed, index);
                 if (index > 0 && !isExtensionEnabled(TExtension::EXT_draw_buffers))
@@ -7490,24 +7492,27 @@
             // Only do generic out-of-range check if similar error hasn't already been reported.
             if (safeIndex < 0)
             {
-                if (baseExpression->isArray())
+                if (effectivelyIndexedExpression->isArray())
                 {
-                    safeIndex = checkIndexLessThan(outOfRangeIndexIsError, location, index,
-                                                   baseExpression->getOutermostArraySize(),
-                                                   "array index out of range");
+                    safeIndex =
+                        checkIndexLessThan(outOfRangeIndexIsError, location, index,
+                                           effectivelyIndexedExpression->getOutermostArraySize(),
+                                           "array index out of range");
                 }
-                else if (baseExpression->isMatrix())
+                else if (effectivelyIndexedExpression->isMatrix())
                 {
-                    safeIndex = checkIndexLessThan(outOfRangeIndexIsError, location, index,
-                                                   baseExpression->getType().getCols(),
-                                                   "matrix field selection out of range");
+                    safeIndex =
+                        checkIndexLessThan(outOfRangeIndexIsError, location, index,
+                                           effectivelyIndexedExpression->getType().getCols(),
+                                           "matrix field selection out of range");
                 }
                 else
                 {
-                    ASSERT(baseExpression->isVector());
-                    safeIndex = checkIndexLessThan(outOfRangeIndexIsError, location, index,
-                                                   baseExpression->getType().getNominalSize(),
-                                                   "vector field selection out of range");
+                    ASSERT(effectivelyIndexedExpression->isVector());
+                    safeIndex =
+                        checkIndexLessThan(outOfRangeIndexIsError, location, index,
+                                           effectivelyIndexedExpression->getType().getNominalSize(),
+                                           "vector field selection out of range");
                 }
             }
 
@@ -7527,7 +7532,8 @@
                 new TIntermBinary(EOpIndexDirect, baseExpression, indexExpression);
             node->setLine(location);
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 b7240c7..cdc585f 100644
--- a/src/tests/gl_tests/GLSLValidationTest.cpp
+++ b/src/tests/gl_tests/GLSLValidationTest.cpp
@@ -1180,12 +1180,12 @@
 TEST_P(GLSLValidationTest_ES3, EmptyArrayConstructor)
 {
     constexpr char kFS[] = R"(#version 300 es
-         precision mediump float;
-         out vec4 my_FragColor;
-         uniform float u;
-         const float[] f = float[]();
-         void main() {
-             my_FragColor = vec4(0.0);
+        precision mediump float;
+        out vec4 my_FragColor;
+        uniform float u;
+        const float[] f = float[]();
+        void main() {
+            my_FragColor = vec4(0.0);
       })";
 
     validateError(GL_FRAGMENT_SHADER, kFS,
@@ -1197,13 +1197,33 @@
 TEST_P(GLSLValidationTest_ES3, DynamicallyIndexedFragmentOutput)
 {
     constexpr char kFS[] = R"(#version 300 es
-         precision mediump float;
-         uniform int a;
-         out vec4[2] my_FragData;
-         void main()
-         {
+        precision mediump float;
+        uniform int a;
+        out vec4[2] my_FragData;
+        void main()
+        {
              my_FragData[true ? 0 : a] = vec4(0.0);
-         }
+        }
+    )";
+
+    validateError(
+        GL_FRAGMENT_SHADER, kFS,
+        " '[' : array indexes for fragment outputs must be constant integral expressions");
+}
+
+// Test that indexing fragment outputs with a non-constant expression is forbidden, even if ANGLE
+// is able to constant fold the index expression. ESSL 3.00 section 4.3.6.
+TEST_P(GLSLValidationTest_ES3, DynamicallyIndexedFragmentOutput2)
+{
+    constexpr char kFS[] = R"(#version 300 es
+        precision mediump float;
+        uniform int a;
+        out vec4[2] my_FragData;
+        void main()
+        {
+            float unused;
+            (unused, my_FragData)[true ? 0 : a];
+        }
     )";
 
     validateError(
@@ -1268,7 +1288,8 @@
         out vec4 my_FragColor;
         void main()
         {
-            my_FragColor = texture(s[true ? 0 : a], vec2(0));
+            float unused;
+            my_FragColor = texture((unused, s)[true ? 0 : a], vec2(0));
         })";
 
     validateError(GL_FRAGMENT_SHADER, kFS,
@@ -1286,7 +1307,8 @@
         out vec4 my_FragColor;
         void main()
         {
-            my_FragColor = imageLoad(image[true ? 0 : a], ivec2(0));
+            float unused;
+            my_FragColor = imageLoad((unused, unused, image)[true ? 0 : a], ivec2(0));
     })";
 
     validateError(GL_FRAGMENT_SHADER, kFS,
@@ -5035,6 +5057,29 @@
                   "GL_MAX_DUAL_SOURCE_DRAW_BUFFERS_EXT when gl_SecondaryFragDataEXT is used");
 }
 
+// Shader that writes to SecondaryFragData and FragData at an index >= than
+// gl_MaxDualSourceDrawBuffersEXT.  FragData is the result of a comma operator.
+TEST_P(GLSLValidationTest, BlendFuncExtendedDataArrayAndSecondaryDataWithComma)
+{
+    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_blend_func_extended"));
+    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_draw_buffers"));
+
+    constexpr char kFS[] = R"(#extension GL_EXT_draw_buffers : require
+#extension GL_EXT_blend_func_extended : require
+precision mediump float;
+void main() {
+    float f;
+    vec4 value = (f = 0.,
+                  gl_SecondaryFragDataEXT[0] = vec4(1.0),
+                  f += 0.5,
+                  gl_FragData)[gl_MaxDualSourceDrawBuffersEXT];
+    gl_FragData[0] = vec4(value.xyz, f);
+})";
+    validateError(GL_FRAGMENT_SHADER, kFS,
+                  "array index for gl_FragData must be less than "
+                  "GL_MAX_DUAL_SOURCE_DRAW_BUFFERS_EXT when gl_SecondaryFragDataEXT is used");
+}
+
 // Shader that writes to SecondaryFragData and passes FragData to a function.
 TEST_P(GLSLValidationTest, BlendFuncExtendedPassFragDataToFunction)
 {
@@ -6997,10 +7042,10 @@
     ANGLE_SKIP_TEST_IF(maxCombinedClipAndCullDistances > 11);
 
     constexpr char kFS[] = R"(out highp vec4 fragColor;
-
 void main()
 {
-    fragColor = vec4(gl_ClipDistance[4], gl_CullDistance[5], 0, 1);
+    mediump float unused;
+    fragColor = vec4((unused, gl_ClipDistance)[4], (unused, unused, gl_CullDistance)[5], 0, 1);
 })";
     constexpr char kExpect[] =
         "The sum of 'gl_ClipDistance' and 'gl_CullDistance' size is greater than "
@@ -7250,6 +7295,41 @@
     }
 }
 
+// Shader redeclares gl_ClipDistance, but after it's been referenced with a non-constant index.
+TEST_P(GLSLValidationClipDistanceTest_ES3, RedeclareClipDistanceAfterReferenceNonConstantIndex2)
+{
+    const bool hasExt   = IsGLExtensionEnabled("GL_EXT_clip_cull_distance");
+    const bool hasAngle = IsGLExtensionEnabled("GL_ANGLE_clip_cull_distance");
+    ANGLE_SKIP_TEST_IF(!hasExt && !hasAngle);
+
+    constexpr char kVS[] =
+        R"(in vec4 aPosition;
+void main()
+{
+    gl_Position = aPosition;
+    for (int i = 0; i < 2; ++i)
+    {
+        float unused;
+        float f = (unused, gl_ClipDistance)[i];
+    }
+}
+out highp float gl_ClipDistance[3];
+)";
+    constexpr char kExpect[] =
+        "'gl_ClipDistance' : redeclaration of gl_ClipDistance after it is referenced is not "
+        "allowed";
+
+    if (hasAngle)
+    {
+        validateErrorWithExt(GL_VERTEX_SHADER, "GL_ANGLE_clip_cull_distance", kVS, kExpect);
+    }
+
+    if (hasExt)
+    {
+        validateErrorWithExt(GL_VERTEX_SHADER, "GL_EXT_clip_cull_distance", kVS, kExpect);
+    }
+}
+
 // Shader redeclares gl_CullDistance, but after it's been referenced with a non-constant index.
 TEST_P(GLSLValidationClipDistanceTest_ES3, RedeclareCullDistanceAfterReferenceNonConstantIndex)
 {
Loading diff…

Original Bug Report

reported by vm...@google.com

Potential gl_FragData indexing bypass via comma operator leading to OOB 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. Please see https://chromium.googlesource.com/chromium/src/+/main/docs/security/ai-generated-security-bugs-faq.md for more information.

Overview: A potential vulnerability in ANGLE allows bypassing array-bounds checking and index-clamping for gl_FragData using a comma operator. This results in the generation of malformed SPIR-V containing an out-of-bounds OpAccessChain. This issue could potentially lead to driver-level memory corruption in the GPU process.

Affected files:

  • third_party/angle/src/compiler/translator/ParseContext.cpp
  • third_party/angle/src/compiler/translator/tree_ops/spirv/EmulateFragColorData.cpp
  • third_party/angle/src/compiler/translator/IntermNode.cpp
  • third_party/angle/src/compiler/translator/spirv/OutputSPIRV.cpp

Estimated timestamp from git blame: 2026-04-27

Potential out-of-bounds gl_FragData indexing bypass via comma operator

Root Cause Analysis

In third_party/angle/src/compiler/translator/ParseContext.cpp, TParseContext::addIndexExpression performs bounds tracking and safety-clamping for gl_FragData only when the immediate baseExpression has the qualifier EvqFragData:

// third_party/angle/src/compiler/translator/ParseContext.cpp
if (!baseExpression->getType().isUnsizedArray())
{
    if (baseExpression->isArray() && baseExpression->getQualifier() == EvqFragData)
    {
        mMaxFragDataArrayIndexUsed = std::max(mMaxFragDataArrayIndexUsed, index);
        if (index > 0 && !isExtensionEnabled(TExtension::EXT_draw_buffers))
        {
            outOfRangeError(...);
            safeIndex = 0;
        }
    }

When gl_FragData is wrapped inside a sequence/comma operator—such as (sideEffect, gl_FragData)[7]—the qualifier of the resulting EOpComma node is evaluated as EvqTemporary (via GetCommaQualifier in IntermNode.cpp:1846). This causes the compiler to completely skip the tracking and clamping logic shown above.

Because the generic bounds check at ParseContext.cpp:7481 checks against the comma node’s promoted outermost array size (which copies the type of its right operand gl_FragData, e.g., vec4[8]), a constant index such as 7 is successfully validated and retained.

Subsequently, during translation inside the Vulkan/SPIR-V backend, EmulateFragColorData (tree_ops/spirv/EmulateFragColorData.cpp:76-99) shrinks the output array to mResources.MaxDualSourceDrawBuffers (which is 1 when gl_SecondaryFragDataEXT is statically used). However, because TIntermBinary::replaceChildNode is used to replace the symbol without re-promoting parent node types, the parent nodes still assume a size of 8. Consequently, the code generator emits a direct OpAccessChain with the constant index 7 into an Output array declared with size 1.

This produces malformed SPIR-V that violates Vulkan’s Valid Usage requirements. Since Vulkan’s robustBufferAccess does not cover the Output storage class, this can trigger driver-defined behavior or memory corruption in the GPU process.


Potential Steps to Reproduce

Note: These are suggested/potential steps to reproduce. Our tooling agent does not currently have the capability to execute code or verify the exploitability of this issue on a live target.

  1. Initialize a standard WebGL1 context on a page:
    const gl = canvas.getContext('webgl');
    
  2. Enable the required extensions (both stable and non-privileged):
    gl.getExtension('WEBGL_draw_buffers');
    gl.getExtension('WEBGL_blend_func_extended');
    
  3. Submit and compile a fragment shader containing a comma-wrapped access into gl_FragData combined with dual-source blending, for example:
    #extension GL_EXT_blend_func_extended : require
    precision mediump float;
    void main() {
        gl_SecondaryFragDataEXT[0] = vec4(0.5);
        vec4 c = (gl_SecondaryFragDataEXT[0] = vec4(0.5), gl_FragData)[7];
        gl_FragData[0] = c;
    }
    
  4. Link the program and execute a draw call. This triggers the generation of malformed SPIR-V inside the GPU process, potentially resulting in memory corruption within the platform graphics driver.

Suggested Fix

To remediate this issue, update TParseContext::addIndexExpression in ParseContext.cpp to recursively traverse through any sequence/comma operator nodes on the baseExpression to locate the underlying operand. This ensures that indexing constraints on gl_FragData (or similar built-ins) are correctly applied even when wrapped in a comma operator, matching the pattern already used in IsWholeArrayFragDataUsed:

TIntermTyped *realBase = baseExpression;
while (realBase && realBase->getAsBinaryNode() && realBase->getAsBinaryNode()->getOp() == EOpComma)
{
    realBase = realBase->getAsBinaryNode()->getRight();
}

if (realBase && realBase->isArray() && realBase->getQualifier() == EvqFragData)
{
    // ... perform tracking and safety-clamping on the underlying gl_FragData ...
}

Evaluated with Chrome root at commit: 94d9235ebe3b7276e5284f0dc5d55577ff949908


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