Overview

High
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInappropriate implementation in ANGLE
DescriptionInappropriate implementation in ANGLE
ComponentANGLE
Bug ClassLogic Error
Tracker517543052
Fix commitafb8744a126e (angle/angle) +7/-0
CISA KEVNot listed
CreditedGoogle
Disclosed2026-07-29

Files Changed

  • src/compiler/translator/tree_ops/msl/EnsureLoopForwardProgress.cpp
  • src/tests/gl_tests/GLSLOutputTest.cpp
From afb8744a126e877471234d1b42113d3bd7310ab3 Mon Sep 17 00:00:00 2001
From: Shahbaz Youssefi <syoussefi@chromium.org>
Date: Tue, 23 Jun 2026 10:51:44 -0400
Subject: [PATCH] MSL/AST: Handle non-unary/binary expr in loop-forward-progress

Bug: chromium:517543052
Change-Id: Idf3bb42ea56e9fd62cad2bd914f9221ff32226e0
Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/7977471
Reviewed-by: Yuxin Hu <yuxinhu@google.com>
Commit-Queue: Shahbaz Youssefi <syoussefi@chromium.org>
Auto-Submit: Shahbaz Youssefi <syoussefi@chromium.org>
---

diff --git a/src/compiler/translator/tree_ops/msl/EnsureLoopForwardProgress.cpp b/src/compiler/translator/tree_ops/msl/EnsureLoopForwardProgress.cpp
index 7997879..93d603f 100644
--- a/src/compiler/translator/tree_ops/msl/EnsureLoopForwardProgress.cpp
+++ b/src/compiler/translator/tree_ops/msl/EnsureLoopForwardProgress.cpp
@@ -158,6 +158,10 @@
                 return nullptr;
         }
     }
+    else
+    {
+        return nullptr;
+    }
     return variable;
 }
 
diff --git a/src/tests/gl_tests/GLSLOutputTest.cpp b/src/tests/gl_tests/GLSLOutputTest.cpp
index 29f0967..2cee833 100644
--- a/src/tests/gl_tests/GLSLOutputTest.cpp
+++ b/src/tests/gl_tests/GLSLOutputTest.cpp
@@ -596,6 +596,7 @@
 precision highp int;
 uniform int a;
 uniform uint b;
+int f() { return 0; }
 void main() {
 
 )";
@@ -617,6 +618,8 @@
         "for (int i = 0; i < 10; i++) { for (int j = 0; i = 0, j < 10; j++) { } }",
         "for (int i = 0; i < 10; i++) { for (int j = 0; j < 10; i = 0, j++) { } }",
         "for (int i = 0; i < 10; i++) { for (int j = 0; j < 10; i--, j++) { } }",
+        "for (int i = 0; i < 10; f()) { }",
+        "for (int i = 0; i < 10; a == 0 ? i++ : i = 0) { }",
     };
 
     for (const char *test : kTests)
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/src/tests/gl_tests/GLSLOutputTest.cpp b/src/tests/gl_tests/GLSLOutputTest.cpp
index 29f0967..2cee833 100644
--- a/src/tests/gl_tests/GLSLOutputTest.cpp
+++ b/src/tests/gl_tests/GLSLOutputTest.cpp
@@ -596,6 +596,7 @@
 precision highp int;
 uniform int a;
 uniform uint b;
+int f() { return 0; }
 void main() {
 
 )";
@@ -617,6 +618,8 @@
         "for (int i = 0; i < 10; i++) { for (int j = 0; i = 0, j < 10; j++) { } }",
         "for (int i = 0; i < 10; i++) { for (int j = 0; j < 10; i = 0, j++) { } }",
         "for (int i = 0; i < 10; i++) { for (int j = 0; j < 10; i--, j++) { } }",
+        "for (int i = 0; i < 10; f()) { }",
+        "for (int i = 0; i < 10; a == 0 ? i++ : i = 0) { }",
     };
 
     for (const char *test : kTests)
Loading diff…

Original Bug Report

reported by vm...@google.com

Potential ANGLE MSL loop forward-progress bypass via complex update expressions

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 logic flaw in ANGLE’s loop forward-progress mitigation pass for the Metal (MSL) backend can allow infinite loops to bypass validation. Due to a missing terminal else block in computeFiniteLoopVariable(), complex update expressions are incorrectly classified as finite loop increments. This prevents the insertion of volatile progress markers, potentially enabling the Metal compiler to optimize away the loop and leave subsequent array accesses unclamped.

Affected files:

  • third_party/angle/src/compiler/translator/tree_ops/msl/EnsureLoopForwardProgress.cpp

Estimated timestamp from git blame: 2025-05-22

Summary of the Potential Bug

In ANGLE’s Metal backend, infinite loops without side effects must be protected against dead-loop elimination. Under standard C++ forward-progress guarantees, the Metal compiler (built on LLVM) can optimize away infinite loops that have no observable side effects. When this occurs, any post-loop variables (like the loop counter) may be assigned LLVM poison values. If these poisoned values are later used to index into arrays, the compiler can optimize out safety clamping checks, leading to unclamped out-of-bounds (OOB) memory access within the GPU process.

To prevent this, ANGLE implements the EnsureLoopForwardProgress AST pass, which inserts a volatile memory access (loopForwardProgress()) into loops it cannot prove to be finite. However, a logic flaw in EnsureLoopForwardProgress.cpp incorrectly assumes certain complex loop update expressions are finite.

Root Cause Analysis

In third_party/angle/src/compiler/translator/tree_ops/msl/EnsureLoopForwardProgress.cpp, the helper function computeFiniteLoopVariable(TIntermLoop *loop) evaluates the loop’s update expression to determine if it is a valid, finite increment or decrement (lines 106–161):

    if (TIntermUnary *unary = expr->getAsUnaryNode())
    {
        // ... (Validates unary increments/decrements)
    }
    else if (TIntermBinary *binExpr = expr->getAsBinaryNode())
    {
        // ... (Validates binary additions/subtractions of constants)
    }
    return variable;

If the loop’s update expression is neither a unary node (TIntermUnary) nor a binary node (TIntermBinary)—such as a ternary operator (TIntermTernary) or a user-defined function call (TIntermAggregate)—it falls through both conditional blocks. Due to the missing terminal else block, the function falls through and executes the default return variable; at line 161.

Consequently, the AST traverser is falsely informed that the loop variable represents a structured, finite iteration step. The loop is treated as finite, and the loopForwardProgress() safety marker is not inserted.

Potential Step-by-Step Scenario to Trigger the Vulnerability

Because our automated tooling does not currently have the capability to execute code natively, these are suggested and potential steps that an attacker might follow to demonstrate this vulnerability:

  1. Deliver Shader: An attacker supplies a WebGL 2.0 (GLSL ES 3.00) shader containing an infinite loop where the increment expression is a ternary operator or a user function call (e.g., for (int i = 0; i < 10; (i++, true) ? i : i)).
  2. Bypass Volatile Insertion: When translating this shader to MSL, ANGLE’s EnsureLoopForwardProgress pass incorrectly classifies the loop as finite and skips inserting the volatile marker.
  3. Metal Optimization: The translated MSL source is compiled by the Metal compiler. The LLVM optimizer identifies the infinite loop as having no side effects and deletes the entire loop block.
  4. Poison Propagation: The deleted loop results in the post-loop counter index becoming a poison value.
  5. OOB GPU Access: Subsequent array indexing operations utilizing the loop counter (e.g., buffer[clamp(poison, 0, N)]) evaluate to poison, allowing the compiler to optimize out the safety clamp checks and generate raw, unchecked offset calculations. This results in potential unclamped out-of-bounds read/write access to GPU memory.

Suggested Fix

To remediate this issue, add a terminal else block to return nullptr if the loop’s update expression does not match any of the recognized finite patterns:

    if (TIntermUnary *unary = expr->getAsUnaryNode())
    {
        // ...
    }
    else if (TIntermBinary *binExpr = expr->getAsBinaryNode())
    {
        // ...
    }
    else
    {
        return nullptr;
    }
    return variable;

Evaluated with Chrome root at commit: 5133b93d189b383c37805b1cf3a9d2dbfe8d7379


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