Medium chrome Logic Error 📄 Reporter bug report 🔧 Commit mapped

Overview

Medium
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactImproper input validation in ANGLE
DescriptionImproper input validation in ANGLE
ComponentANGLE
Bug ClassLogic Error
Tracker520504922
Fix commitbc129145afe5 (angle/angle) +10/-1
CISA KEVNot listed
CreditedGoogle
Disclosed2026-08-25

Files Changed

  • src/compiler/translator/ir/src/transform/msl/ensure_loop_forward_progress.rs
  • src/compiler/translator/tree_ops/msl/EnsureLoopForwardProgress.cpp
  • src/tests/gl_tests/GLSLOutputTest.cpp
From bc129145afe520b62f11dae1a80d821ebfd6f273 Mon Sep 17 00:00:00 2001
From: Shahbaz Youssefi <syoussefi@chromium.org>
Date: Thu, 16 Jul 2026 14:43:21 -0400
Subject: [PATCH] MSL: Don't take non-scalars as loop vars in loop-forward-progress

Bug: chromium:520504922
Change-Id: I0974c02eab4aacacd01013ea5bff0d7b0b6d4153
Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/8108624
Reviewed-by: Geoff Lang <geofflang@chromium.org>
Commit-Queue: Shahbaz Youssefi <syoussefi@chromium.org>
---

diff --git a/src/compiler/translator/ir/src/transform/msl/ensure_loop_forward_progress.rs b/src/compiler/translator/ir/src/transform/msl/ensure_loop_forward_progress.rs
index 4bbf37c..c4eb076 100644
--- a/src/compiler/translator/ir/src/transform/msl/ensure_loop_forward_progress.rs
+++ b/src/compiler/translator/ir/src/transform/msl/ensure_loop_forward_progress.rs
@@ -91,6 +91,7 @@
 //
 // Where:
 //
+// * `variable` can only be a scalar integer
 // * `op` can only be one of > >= < <= == or !=
 // * `constant` can be a non-zero constant, or a read-only value such as shader inputs and uniforms.
 // * `expression` can only be one of:
@@ -159,6 +160,12 @@
         ir_meta.get_instruction(loop_variable_value).op
         && loop_variable.id.is_variable()
     {
+        // Loop variable must be scalar integer
+        let type_info = ir_meta.get_type(ir_meta.get_pointee_type(loop_variable.type_id));
+        if !matches!(type_info, Type::Scalar(BasicType::Int) | Type::Scalar(BasicType::Uint)) {
+            return None;
+        }
+
         loop_variable.id.get_variable()
     } else {
         return None;
diff --git a/src/compiler/translator/tree_ops/msl/EnsureLoopForwardProgress.cpp b/src/compiler/translator/tree_ops/msl/EnsureLoopForwardProgress.cpp
index 93d603f..55df115 100644
--- a/src/compiler/translator/tree_ops/msl/EnsureLoopForwardProgress.cpp
+++ b/src/compiler/translator/tree_ops/msl/EnsureLoopForwardProgress.cpp
@@ -81,7 +81,7 @@
     {
         return nullptr;
     }
-    if (!IsInteger(variable->getType().getBasicType()) ||
+    if (!IsInteger(variable->getType().getBasicType()) || !variable->getType().isScalar() ||
         variable->getType().getQualifier() != EvqTemporary)
     {
         return nullptr;
diff --git a/src/tests/gl_tests/GLSLOutputTest.cpp b/src/tests/gl_tests/GLSLOutputTest.cpp
index 3cb431a..08f1705 100644
--- a/src/tests/gl_tests/GLSLOutputTest.cpp
+++ b/src/tests/gl_tests/GLSLOutputTest.cpp
@@ -620,6 +620,8 @@
         "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 (ivec2 i = ivec2(0); i != ivec2(10, 20); i++) { }",
+        "for (ivec2 i = ivec2(0); i != ivec2(10, 10); i += ivec2(1, 2)) { }",
     };
 
     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 3cb431a..08f1705 100644
--- a/src/tests/gl_tests/GLSLOutputTest.cpp
+++ b/src/tests/gl_tests/GLSLOutputTest.cpp
@@ -620,6 +620,8 @@
         "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 (ivec2 i = ivec2(0); i != ivec2(10, 20); i++) { }",
+        "for (ivec2 i = ivec2(0); i != ivec2(10, 10); i += ivec2(1, 2)) { }",
     };
 
     for (const char *test : kTests)
Loading diff…

Original Bug Report

reported by vm...@google.com

Potential loop forward progress bypass in ANGLE MSL translator via vector loop variables

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: In ANGLE’s MSL translator, the computeFiniteLoopVariable analysis incorrectly classifies loops with vector-typed index variables (such as uvec2 or ivec2) as finite. This occurs because the logic checks only if the variable’s basic type is integer, failing to verify that the variable is a scalar. Consequently, side-effect-free infinite loops can bypass the forward-progress injection mitigation, potentially allowing the Metal compiler optimizer to delete the loop and execute downstream code with undefined state.

Affected files:

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

Estimated timestamp from git blame: 2025-05-22

Root Cause

In third_party/angle/src/compiler/translator/tree_ops/msl/EnsureLoopForwardProgress.cpp, computeFiniteLoopVariable() is responsible for identifying loops that are guaranteed to terminate (i.e. finite). If a loop is determined to be finite, the EnsureLoopForwardProgress AST pass skips injecting the ANGLE_loopForwardProgress() protection marker.

The function identifies loop variables using the following type validation:

    if (!IsInteger(variable->getType().getBasicType()) ||
        variable->getType().getQualifier() != EvqTemporary)
    {
        return nullptr;
    }

IsInteger() is defined in BaseTypes.h as:

inline bool IsInteger(TBasicType type)
{
    return type == EbtInt || type == EbtUInt;
}

This check only evaluates the variable’s basic element type (e.g., EbtUInt or EbtInt), but fails to verify if the variable is a scalar. As a result, vector loop variables such as uvec2 or ivec2 are incorrectly accepted as finite.

Impact and Potential Exploitation Scenario

By bypassing this check, an attacker could write a WebGL2 shader with a side-effect-free infinite loop on a vector index that is classified as finite. The compiler then skips inserting the forward-progress marker. When compiled by Apple’s Metal compiler (which assumes side-effect-free loops always terminate under standard C++ forward-progress guarantees), the optimizer may delete the loop entirely. This potentially results in the execution of downstream code with undefined or poisoned GPU state.

Scenario 1: Unsigned Wrap-Around Bypass

A WebGL2 shader could contain:

for (uvec2 i = uvec2(0u); i != C; i++) {}

If C is set to uvec2(3u, 7u), the vector index i increments as (0,0), (1,1), (2,2), etc., and never matches (3,7) even with unsigned integer wrap-around. The loop is infinite but is incorrectly classified as finite.

Scenario 2: Component-wise Multi-dimensional Stride Bypass

In EnsureLoopForwardProgress.cpp:123, binary update expressions are evaluated:

        const TConstantUnion *value = binExpr->getRight()->getConstantValue();
        ...
        switch (value->getType())
        {
            case EbtInt:
                if (value->getIConst() == -1 || value->getIConst() == 1)

For a loop like for(ivec2 i=ivec2(0); i!=ivec2(10,10); i+=ivec2(1,-1)), getConstantValue() returns a pointer to the element array. The code checks value->getIConst(), which only reads the first element (1), completely ignoring the second component (-1). This loop is also incorrectly classified as finite.

Suggested Potential Steps to Reproduce

(Please note these are suggested/potential steps; our tooling agent does not yet have the ability to execute code and verify runtime behavior.)

  1. On macOS Chrome (which uses the default ANGLE Metal backend), initialize a WebGL2 context.
  2. Compile and link a fragment shader containing a loop with a vector-typed loop variable:
    #version 300 es
    precision highp int; precision highp float;
    uniform uvec2 C; out vec4 c;
    void main(){ for(uvec2 i=uvec2(0u); i!=C; i++){} c=vec4(1.0); }
    
  3. Set the uniform C to uvec2(3u, 7u) and execute a draw call.
  4. Verify that ANGLE’s EnsureLoopForwardProgress pass skips the forward-progress marker insertion.

Suggested Fix

Reject any non-scalar variables in computeFiniteLoopVariable by enforcing that the variable must be a scalar:

    if (!IsInteger(variable->getType().getBasicType()) ||
        !variable->getType().isScalar() ||
        variable->getType().getQualifier() != EvqTemporary)
    {
        return nullptr;
    }

Evaluated with Chrome root at commit: e9507a33bb4148ee071aaaf8a7e9ad68770359bf


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