High chrome Input Validation 📄 Reporter bug report 🔧 Commit mapped

Overview

High
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactImproper input validation in ANGLE
DescriptionImproper input validation in ANGLE
ComponentANGLE
Sub-componentMSL
Bug ClassInput Validation
Tracker563788518
Fix commit386bb0e11d39 (angle/angle) +52/-13
CISA KEVNot listed
Creditedhamayanhamayan
Disclosed2026-10-20

Files Changed

  • src/compiler/translator/ir/src/transform/msl/ensure_loop_forward_progress.rs
  • src/tests/gl_tests/GLSLOutputTest.cpp
  • src/compiler/translator/tree_ops/msl/EnsureLoopForwardProgress.cpp
From 386bb0e11d39ae46d1367c9e9e8b50f46b27ca90 Mon Sep 17 00:00:00 2001
From: Shahbaz Youssefi <syoussefi@chromium.org>
Date: Sun, 20 Sep 2026 16:33:45 -0400
Subject: [PATCH] MSL: Correctly identify uint >= 0 condition as an infinite loop

Bug: chromium:563788518
Change-Id: Idf857a8ed20b71b50739b8799017c88fb301f99a
Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/8427087
Commit-Queue: Shahbaz Youssefi <syoussefi@chromium.org>
Reviewed-by: Yuxin Hu <yuxinhu@google.com>
---
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 c4eb076..89c5e81 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
@@ -166,6 +166,18 @@
             return None;
         }
 
+        // If the type is uint and the condition is >=, it might be |>= 0| which is always true;
+        // don't consider that a finite loop.  Only |>= 0| and |>= variable_that_could_be_zero| may
+        // lead to infinite loop, but all uint with >= is considered infinite loop for simplicity.
+        if matches!(type_info, Type::Scalar(BasicType::Uint))
+            && matches!(
+                ir_meta.get_instruction(op_result).op,
+                OpCode::Binary(BinaryOpCode::GreaterThanEqual, ..)
+            )
+        {
+            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 55df115..1c773d4 100644
--- a/src/compiler/translator/tree_ops/msl/EnsureLoopForwardProgress.cpp
+++ b/src/compiler/translator/tree_ops/msl/EnsureLoopForwardProgress.cpp
@@ -93,7 +93,16 @@
         case EOpLessThan:
         case EOpGreaterThan:
         case EOpLessThanEqual:
+            break;
         case EOpGreaterThanEqual:
+            // If the type is uint and the condition is >=, it might be |>= 0| which is always true;
+            // don't consider that a finite loop.  Only |>= 0| and |>= variable_that_could_be_zero|
+            // may lead to infinite loop, but all uint with >= is considered infinite loop for
+            // simplicity.
+            if (variable->getType().getBasicType() == EbtUInt)
+            {
+                return nullptr;
+            }
             break;
         default:
             return nullptr;
diff --git a/src/tests/gl_tests/GLSLOutputTest.cpp b/src/tests/gl_tests/GLSLOutputTest.cpp
index 08f1705..eb22cd8 100644
--- a/src/tests/gl_tests/GLSLOutputTest.cpp
+++ b/src/tests/gl_tests/GLSLOutputTest.cpp
@@ -566,19 +566,30 @@
 
 )";
     const char kShaderSuffix[] = "}\n";
-    const char *kTests[]{"int i = 101; for (; i < 10; i++) { }",
-                         "int i = 101; for (; i < 10; i+=1) { }",
-                         "int i = 101; for (; i < 10; i-=1) { }",
-                         "for (int i = 0; i < 10; i++) { }",
-                         "for (int i = 0; i < a; i++) { }",
-                         "for (int i = 0; i < 100000/2; ++i) { }",
-                         "for (uint i = 0u; i < 10u; i++) { }",
-                         "for (uint i = 0u; i < b; i++) { }",
-                         "for (uint i = 0u; i < 100000u/2u; ++i) { }",
-                         "for (uint i = 0u; i < 4294967295u; ++i) { }",
-                         "for (uint i = 10u; i > 1u+3u ; --i) { }",
-                         "const int z = 7; for (int i = 0; i < z; i++) { }",
-                         "for (int i = 0; i < 10; i++) { for (int j = 0; j < 1000; ++j) { }}"};
+    const char *kTests[]{
+        "int i = 101; for (; i < 10; i++) { }",
+        "int i = 101; for (; i < 10; i+=1) { }",
+        "int i = 101; for (; i < 10; i-=1) { }",
+        "for (int i = 0; i < 10; i++) { }",
+        "for (int i = 0; i < a; i++) { }",
+        "for (int i = 0; i < 100000/2; ++i) { }",
+        "for (uint i = 0u; i < 10u; i++) { }",
+        "for (uint i = 0u; i < b; i++) { }",
+        "for (uint i = 0u; i < 100000u/2u; ++i) { }",
+        "for (uint i = 0u; i < 4294967295u; ++i) { }",
+        "for (uint i = 10u; i > 1u+3u ; --i) { }",
+        "const int z = 7; for (int i = 0; i < z; i++) { }",
+        "for (int i = 0; i < 10; i++) { for (int j = 0; j < 1000; ++j) { }}",
+        // Finite because |int| is implemented with |uint| such that wraparound has
+        // well-defined behavior, and eventually the values wrap around to make the
+        // condition false.
+        "for (int i = 0; i >= 0; ++i) { }",
+        "for (int i = 1; i >= 0; ++i) { }",
+        "for (int i = 1; i > 0; ++i) { }",
+        "for (int i = 0; i < 10; i -= 1) { }",
+        // Finite because of eventual wrap around to zero.
+        "for (uint i = 1u; i > 0u; ++i) { }",
+    };
 
     for (const char *test : kTests)
     {
@@ -622,6 +633,13 @@
         "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)) { }",
+        // Infinite loop because the condition is always true
+        "for (int i = 0; i == 0; i += 0) { }",
+        "for (uint i = 0u; i >= 0u; ++i) { }",
+        // Infinite loop because the |b| might be zero
+        "for (uint i = 10u; i >= b; ++i) { }",
+        // Infinite loop because the condition is always true as |i| never changes.
+        "for (uint i = 0u; i == 0u; i += 0u) { }",
     };
 
     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 08f1705..eb22cd8 100644
--- a/src/tests/gl_tests/GLSLOutputTest.cpp
+++ b/src/tests/gl_tests/GLSLOutputTest.cpp
@@ -566,19 +566,30 @@
 
 )";
     const char kShaderSuffix[] = "}\n";
-    const char *kTests[]{"int i = 101; for (; i < 10; i++) { }",
-                         "int i = 101; for (; i < 10; i+=1) { }",
-                         "int i = 101; for (; i < 10; i-=1) { }",
-                         "for (int i = 0; i < 10; i++) { }",
-                         "for (int i = 0; i < a; i++) { }",
-                         "for (int i = 0; i < 100000/2; ++i) { }",
-                         "for (uint i = 0u; i < 10u; i++) { }",
-                         "for (uint i = 0u; i < b; i++) { }",
-                         "for (uint i = 0u; i < 100000u/2u; ++i) { }",
-                         "for (uint i = 0u; i < 4294967295u; ++i) { }",
-                         "for (uint i = 10u; i > 1u+3u ; --i) { }",
-                         "const int z = 7; for (int i = 0; i < z; i++) { }",
-                         "for (int i = 0; i < 10; i++) { for (int j = 0; j < 1000; ++j) { }}"};
+    const char *kTests[]{
+        "int i = 101; for (; i < 10; i++) { }",
+        "int i = 101; for (; i < 10; i+=1) { }",
+        "int i = 101; for (; i < 10; i-=1) { }",
+        "for (int i = 0; i < 10; i++) { }",
+        "for (int i = 0; i < a; i++) { }",
+        "for (int i = 0; i < 100000/2; ++i) { }",
+        "for (uint i = 0u; i < 10u; i++) { }",
+        "for (uint i = 0u; i < b; i++) { }",
+        "for (uint i = 0u; i < 100000u/2u; ++i) { }",
+        "for (uint i = 0u; i < 4294967295u; ++i) { }",
+        "for (uint i = 10u; i > 1u+3u ; --i) { }",
+        "const int z = 7; for (int i = 0; i < z; i++) { }",
+        "for (int i = 0; i < 10; i++) { for (int j = 0; j < 1000; ++j) { }}",
+        // Finite because |int| is implemented with |uint| such that wraparound has
+        // well-defined behavior, and eventually the values wrap around to make the
+        // condition false.
+        "for (int i = 0; i >= 0; ++i) { }",
+        "for (int i = 1; i >= 0; ++i) { }",
+        "for (int i = 1; i > 0; ++i) { }",
+        "for (int i = 0; i < 10; i -= 1) { }",
+        // Finite because of eventual wrap around to zero.
+        "for (uint i = 1u; i > 0u; ++i) { }",
+    };
 
     for (const char *test : kTests)
     {
@@ -622,6 +633,13 @@
         "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)) { }",
+        // Infinite loop because the condition is always true
+        "for (int i = 0; i == 0; i += 0) { }",
+        "for (uint i = 0u; i >= 0u; ++i) { }",
+        // Infinite loop because the |b| might be zero
+        "for (uint i = 10u; i >= b; ++i) { }",
+        // Infinite loop because the condition is always true as |i| never changes.
+        "for (uint i = 0u; i == 0u; i += 0u) { }",
     };
 
     for (const char *test : kTests)
Loading diff…

Original Bug Report

reported by bz...@gmail.com

GPU Memory OOB Read in ANGLE (Metal/MSL) on Chrome for macOS via Incorrect Finiteness Analysis


Report description

GPU Memory OOB Read in ANGLE (Metal/MSL) on Chrome for macOS via Incorrect Finiteness Analysis


Bug location

Where do you want to report your vulnerability?

Chrome VRP – Report security issues affecting the Chrome browser. See program rules


The problem

Please describe the technical details of the vulnerability

In Google Chrome on macOS using the ANGLE Metal/MSL backend, the finite-loop analysis incorrectly classifies a loop whose termination has not actually been proven as “finite.” As a result, the protection intended to prevent the Metal optimizer from removing infinite loops (ANGLE_loopForwardProgress()) is not inserted, causing the array-index clamp that WebGL is supposed to guarantee (ANGLE_int_clamp) to be eliminated.

This allows an attacker to perform out-of-bounds reads of GPU memory from an ordinary web page. The attacker can read not only beyond the bounds of their own program’s uniform array, but also GPU memory allocated by another WebGL context or by another tab (i.e., another origin). Specifically, this issue exploits the fact that the following loop is incorrectly classified as “finite”:

for (uint i = 0u; i >= 0u; ++i) { }

Reproduction Steps

The issue can be reproduced by opening read.html.

I reproduced the vulnerability using Google Chrome for macOS, Version 153.0.8010.53 (Official Build) (arm64).

When the vulnerability is successfully reproduced, the following four lines are displayed:

Output Meaning
idx=7 -> [32, 32, 32, 32] table[7] = 0.125 × 255 = 31.9. This is the correct in-bounds value and serves as a control.
idx=8 -> [191, 191, 191, 191] This corresponds to neighbor, which is declared immediately after the array: 0.75 × 255. If the clamp were still present, the access would be clamped to table[7] and return 32. Therefore, the fact that 191 is returned demonstrates that the clamp has been removed and that the load is performed using the raw 32-bit index. If 191 is displayed, the vulnerability has been successfully reproduced.
idx=9 -> [0, 0, 0, 0] This reads from offset 144 onward in the 160-byte uniform buffer, including fields such as idx itself. Because the values are small, they are quantized to 0.
idx=10 -> [0, 0, 0, 0] This reads outside the uniform buffer at offset 160.

Impact analysis

Simply by opening a malicious attacker-controlled website, the attacker can read GPU memory belonging to the victim, including GPU memory allocated by other WebGL contexts or other tabs (i.e., other origins).


The cause

What version of Chrome have you found the security issue in?

153.0.8010.53 (Official Build) (arm64)

No, it is not related to a crash.

Choose the type of vulnerability

Information Leak

How would you like to be publicly acknowledged for your report?

hamayanhamayan

View on issue tracker