CVE-2026-79019
Overview
Background
- ANGLE HLSL translator
- The ANGLE backend that translates validated GLSL ES shaders into HLSL for the Direct3D driver, generating identifier names for every shader variable.
- `DecorateVariableIfNeeded`
- The utility that produces the emitted HLSL name for a shader variable, deciding whether to append the variable’s unique symbol id to disambiguate names.
- `TQualifier` (`EvqTemporary` / `EvqGlobal` / `EvqConst`)
- Storage/qualifier classes for shader variables: local/temporary, global (file-scope), and constant respectively.
- Unique symbol id / `kFirstUserDefinedSymbolId`
- A per-variable id (user-defined ids start at 3000) that ANGLE appends to local names so same-named variables in different scopes do not alias.
Root Cause Analysis
In src/compiler/translator/hlsl/UtilsHLSL.cpp, DecorateVariableIfNeeded appended the unique symbol id (str(variable.uniqueId().get())) only when a user-defined variable had qualifier EvqTemporary. Global (EvqGlobal) and constant (EvqConst) user-defined variables were emitted with an undecorated name, so a global variable declared with a name like _a3003 could collide in the generated HLSL with a local variable a that had been decorated to the suffixed name a3003. This violated the invariant that every distinct source variable maps to a distinct HLSL identifier, causing name aliasing between a global and a local in the translated program.
The fix broadens the suffixing condition to (qualifier == EvqTemporary || qualifier == EvqGlobal || qualifier == EvqConst), so globals and constants are also decorated with their unique id and can no longer collide with locals. The regression test constructs shaders whose global is named _a<id> for ids 3000–3009 and confirms the rendered result is correct after the fix.
EvqTemporary user-defined variables with a unique id while leaving globals and constants undecorated, allowing an attacker-chosen global name to alias a local’s generated HLSL name; the fix decorates EvqGlobal and EvqConst as well so all user-defined names stay distinct.Attack Path
- Craft a colliding shader
An attacker supplies a GLSL ES shader with a global variable named to match the decorated form of a local (e.g. global
_a3003alongside a localathat receives suffix3003). - Translate to HLSL
DecorateVariableIfNeededdecorates the local but not the global, so both map to the same HLSL identifier, producing a program where the two variables alias. - Corrupt generated code
The aliasing yields incorrect or malformed HLSL where distinct storage is conflated, which the advisory identifies as leading to an out-of-bounds write in ANGLE
(inferred mechanism from the collision; the specific OOB sink is not shown in the diff). - Out-of-bounds write (inferred)
Per the advisory the resulting condition is an out-of-bounds write in the GPU process; the diff itself demonstrates only the name collision, so the write primitive and its location are
(inferred).
Impact Assessment
(inferred).Files Changed
src/compiler/translator/hlsl/UtilsHLSL.cppsrc/tests/gl_tests/GLSLTest.cpp
Audit Directions
- Incomplete qualifier enumerationLook for name-generation or decoration logic in ANGLE translators that special-cases only a subset of
TQualifiervalues, where an omitted qualifier can produce colliding or undecorated identifiers. - Uniqueness assumptions in codegenAudit any backend (HLSL, MSL, GLSL output) that relies on unique-id suffixing for disambiguation to confirm the suffix is applied to every user-defined symbol class, not just temporaries.
- Attacker-controllable identifier namesReview paths where user shader source can influence emitted identifier strings, since a chosen name matching an internal decoration scheme can force aliasing.
Patch
From 630cd843c4d352b46775f51d42f8da48e178c046 Mon Sep 17 00:00:00 2001 From: Shahbaz Youssefi <[email protected]> Date: Mon, 06 Jul 2026 15:48:32 -0400 Subject: [PATCH] HLSL: Fix name collision between globals and locals Bug: chromium:523266585 Change-Id: Icb973380fb3ccec63dfafa6f31bf6b1c66e5e359 Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/8047141 Commit-Queue: Shahbaz Youssefi <[email protected]> Reviewed-by: Geoff Lang <[email protected]> --- diff --git a/src/compiler/translator/hlsl/UtilsHLSL.cpp b/src/compiler/translator/hlsl/UtilsHLSL.cpp index 53912e5..96ca73f 100644 --- a/src/compiler/translator/hlsl/UtilsHLSL.cpp +++ b/src/compiler/translator/hlsl/UtilsHLSL.cpp @@ -841,6 +841,7 @@ TString DecorateVariableIfNeeded(const TVariable &variable) { + const TQualifier qualifier = variable.getType().getQualifier(); if (variable.symbolType() == SymbolType::AngleInternal || variable.symbolType() == SymbolType::BuiltIn || variable.symbolType() == SymbolType::Empty) { @@ -854,7 +855,7 @@ // For user defined variables, combine variable name with unique id // so variables of the same name in different scopes do not get overwritten. else if (variable.symbolType() == SymbolType::UserDefined && - variable.getType().getQualifier() == EvqTemporary) + (qualifier == EvqTemporary || qualifier == EvqGlobal || qualifier == EvqConst)) { return Decorate(variable.name()) + str(variable.uniqueId().get()); } diff --git a/src/tests/gl_tests/GLSLTest.cpp b/src/tests/gl_tests/GLSLTest.cpp index f80bd3b..ec1978a 100644 --- a/src/tests/gl_tests/GLSLTest.cpp +++ b/src/tests/gl_tests/GLSLTest.cpp @@ -24506,6 +24506,36 @@ EXPECT_PIXEL_COLOR_NEAR(0, 0, GLColor(51, 153, 0, 255), 1); ASSERT_GL_NO_ERROR(); } + +// Regression test for a bug in the HLSL generator where the global variable names could collide +// with local variable names. In particular, the local variables were suffixed with the symbol id, +// starting from 3000 (kFirstUserDefinedSymbolId) but the global variables weren't. +TEST_P(GLSLTest_ES3, HLSLGlobalNameCollisionWithLocalVar) +{ + // At the time this regression test was written, the ID of the local variable was 3003. Try a + // few IDs starting at kFirstUserDefinedSymbolId so the test is not sensitive to small + // variations in the ID. + for (uint32_t id = 3000; id < 3010; ++id) + { + std::ostringstream fs; + fs << R"(precision highp float; +float _a)" << id + << R"( = 0.5; +void main() +{ + float a = 0.2; + a = _a)" << id + << R"(; + gl_FragColor = vec4(a); +})"; + std::cout << fs.str() << "\n"; + + ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), fs.str().c_str()); + drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f); + EXPECT_PIXEL_COLOR_NEAR(0, 0, GLColor(127, 127, 127, 127), 1); + ASSERT_GL_NO_ERROR(); + } +} } // anonymous namespace ANGLE_INSTANTIATE_TEST_ES2_AND_ES3_AND_ES31_AND_ES32(
Regression Test / PoC
diff --git a/src/tests/gl_tests/GLSLTest.cpp b/src/tests/gl_tests/GLSLTest.cpp
index f80bd3b..ec1978a 100644
--- a/src/tests/gl_tests/GLSLTest.cpp
+++ b/src/tests/gl_tests/GLSLTest.cpp
@@ -24506,6 +24506,36 @@
EXPECT_PIXEL_COLOR_NEAR(0, 0, GLColor(51, 153, 0, 255), 1);
ASSERT_GL_NO_ERROR();
}
+
+// Regression test for a bug in the HLSL generator where the global variable names could collide
+// with local variable names. In particular, the local variables were suffixed with the symbol id,
+// starting from 3000 (kFirstUserDefinedSymbolId) but the global variables weren't.
+TEST_P(GLSLTest_ES3, HLSLGlobalNameCollisionWithLocalVar)
+{
+ // At the time this regression test was written, the ID of the local variable was 3003. Try a
+ // few IDs starting at kFirstUserDefinedSymbolId so the test is not sensitive to small
+ // variations in the ID.
+ for (uint32_t id = 3000; id < 3010; ++id)
+ {
+ std::ostringstream fs;
+ fs << R"(precision highp float;
+float _a)" << id
+ << R"( = 0.5;
+void main()
+{
+ float a = 0.2;
+ a = _a)" << id
+ << R"(;
+ gl_FragColor = vec4(a);
+})";
+ std::cout << fs.str() << "\n";
+
+ ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), fs.str().c_str());
+ drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
+ EXPECT_PIXEL_COLOR_NEAR(0, 0, GLColor(127, 127, 127, 127), 1);
+ ASSERT_GL_NO_ERROR();
+ }
+}
} // anonymous namespace
ANGLE_INSTANTIATE_TEST_ES2_AND_ES3_AND_ES31_AND_ES32(
Original Bug Report
Potential OOB access in WebGL via HLSL local variable shadowing
Flapjack, 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 deterministic identifier collision in ANGLE’s HLSL translator allows local variables to shadow global variables in the generated HLSL code. This shadowing causes AST-based array bounds checks to be applied to the wrong variable, potentially leading to out-of-bounds reads and writes on the GPU stack.
Affected files:
third_party/angle/src/compiler/translator/hlsl/UtilsHLSL.cpp
Estimated timestamp from git blame: 2019-02-04
Background
When ANGLE translates GLSL to HLSL for the Direct3D backend, it decorates identifiers to avoid name collisions. This logic is implemented in DecorateVariableIfNeeded within third_party/angle/src/compiler/translator/hlsl/UtilsHLSL.cpp.
Global variables are decorated by prepending an underscore _. For example, a global variable named a3005 becomes _a3005.
Local variables (which receive the EvqTemporary qualifier) are decorated by prepending _ and appending the variable’s internal sequential uniqueId. For example, a local variable named a that is assigned a uniqueId of 3005 also becomes _a3005.
Vulnerability Details
Because TSymbolTable resets its mUniqueIdCounter to kFirstUserDefinedSymbolId (3000) at the start of every shader compilation, unique IDs are assigned deterministically. An attacker can control a local variable’s ID by declaring a specific number of dummy variables prior to it.
This predictable assignment allows a local variable to intentionally collide with the decorated name of a global variable. When the HLSL is generated, standard scoping rules apply, and the local declaration within the function shadows the global declaration.
This shadowing bypasses ANGLE’s array bounds checking pass, ClampIndirectIndices. The pass inserts a clamp() operation based on the size of the array in the Abstract Syntax Tree (AST). If the AST correctly points to a large global array, a large clamp bound is generated. However, the underlying HLSL compiler will execute this clamped access against the shadowed, smaller local array, resulting in out-of-bounds memory access.
Potential Attacker Steps
An attacker could potentially trigger this by supplying the following WebGL shader:
- Declare a large global array:
float a3005[100];. - Declare dummy variables to increment the internal symbol ID counter to exactly 3004.
- Inside a function, declare a small local array:
float a[1];. This variable receivesuniqueId3005. - Access the global array using an attacker-controlled index:
a3005[index];.
During compilation:
ClampIndirectIndicesanalyzes the AST, sees the access to the globala3005of size 100, and emits:clamp(index, 0, 99).OutputHLSLgenerates the global variable:static float _a3005[100];.OutputHLSLgenerates the local variable, creating a name collision:float _a3005[1];.OutputHLSLgenerates the array access:_a3005[clamp(index, 0, 99)].
When the HLSL executes, _a3005 resolves to the local array of size 1. An attacker supplying an index of 50 will read/write memory far past the end of the local array on the GPU’s stack or registers.
(Note: These are potential steps based on code analysis; our tooling agent does not yet have the ability to run code to confirm a live exploit.)
Impact
Out-of-bounds reads and writes on the GPU can lead to arbitrary code execution within the GPU process. On platforms where HLSL is used (primarily Windows), this constitutes a sandbox escape from the highly restricted WebGL renderer process into the GPU process.
Suggested Fix
The decoration scheme for temporary variables should use a separator that cannot be part of a user-defined GLSL identifier. Since WebGL shaders are restricted from using double underscores __ or starting identifiers with certain characters, using a safer decoration pattern would prevent these collisions.
For example, in third_party/angle/src/compiler/translator/hlsl/UtilsHLSL.cpp:
return Decorate(variable.name()) + "__" + str(variable.uniqueId().get());
Evaluated with Chrome root at commit: 2155cb00003ec35716a76ed3246eae995f87b7ff
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.