CVE-2026-113117
Overview
Files Changed
src/compiler/translator/ir/src/transform/msl/ensure_loop_forward_progress.rssrc/tests/gl_tests/GLSLOutputTest.cppsrc/compiler/translator/tree_ops/msl/EnsureLoopForwardProgress.cpp
Patch
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)
Regression Test / PoC
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)
Original Bug Report
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)
Is the security issue related to a crash?
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