CVE-2026-110152
Overview
Background
- ANGLE
- Chrome’s GL abstraction layer that translates WebGL/OpenGL ES calls into the host platform’s native graphics API. Its shader translator parses and validates GLSL ES before handing it to the underlying driver.
- `ParseContext`
- the ANGLE shader-translator class (
src/compiler/translator/ParseContext.cpp) that enforces semantic limits at GLSL compile time, before the shader reaches the device driver. - Vertex output varyings vs. components
- a varying is a vec4-sized interpolant passed from the vertex to the fragment stage. Each varying holds up to 4 components (scalars). “256 varyings” and “256 components” differ by a factor of 4: 256 varyings = 1024 components.
- `limitOutputVaryingsTo256` workaround
- a driver-bug workaround flag (named
LimitOutputVaryingsTo256AtCompileTime) that is supposed to clamp a shader’s vertex output size so it stays below a threshold the native driver can safely handle. The flag name is retained for compatibility with references in Chromium’s code. - `mNumOutputVaryingComponents`
- the running count, in components, of declared vertex shader outputs, computed with a
CheckedNumericand compared against the workaround cap.
Root Cause Analysis
The workaround is intended to cap a shader’s vertex output at 256 components, because the targeted native GL driver has a bug that is triggered once output size crosses that boundary (an undersized driver-side buffer that overflows). In ParseContext.cpp, however, the compile-time validation compared the component count against the wrong limit: if (mNumOutputVaryingComponents > 1024). The comment confirms the intent — the author conflated “256 vec4 varyings” with a component cap and wrote 256 * 4 = 1024. As a result, any shader declaring between 257 and 1024 output components passed ANGLE’s validation and was forwarded to the driver, where it still tripped the driver’s underlying buffer-overflow bug. The validation that was meant to be the safety gate was effectively 4x too permissive.
The patch changes the single comparison to if (mNumOutputVaryingComponents > 256) and rewrites the comment to describe a component cap rather than a varying cap. The accompanying GLSLValidationTest regression test is updated to use kMaxVectors = 256 / 4 and to assert that shaders up to 256 components compile while anything above is rejected at compile time. The deeper memory-corruption sink — the actual overflow — lives in the native driver and is not present in this diff; what the diff substantiates is that ANGLE’s compile-time guard was set at the wrong threshold, allowing corrupting shaders through.
Attack Path
- Web attacker serves a page that obtains a WebGL/WebGL2 context, reaching ANGLE’s shader translator in the GPU/renderer process (inferred: standard WebGL reachability).
- Craft shader authors a vertex shader declaring vertex output varyings whose total component count falls in the 257–1024 range (e.g. ~257 components), staying under the old 1024 cap.
- Pass the guard
ParseContextcomputesmNumOutputVaryingComponentsand, under the old> 1024check, accepts the shader as valid on a device whereLimitOutputVaryingsTo256AtCompileTimeis active. - Reach the driver ANGLE forwards the validated shader to the native GL driver, which hits its known overflow at >256 output components.
- Primitive a buffer overflow inside the native driver (advisory class OOB), corrupting driver/GPU-process memory; exploitability depends on the specific driver and is not characterized by this diff.
Impact Assessment
LimitOutputVaryingsTo256 workaround is enabled (a specific vendor’s driver bug), and the corruption occurs in driver memory outside Chrome’s direct control, so the exact write primitive and its controllability are not demonstrated by the diff. The fix is purely tightening the validation threshold; it does not alter the driver, so the real-world severity tracks the driver’s overflow behavior.Files Changed
src/compiler/translator/ParseContext.cppsrc/tests/gl_tests/GLSLValidationTest.cpp
Audit Directions
- Unit confusion in GL limitsaudit other ANGLE translator caps that mix “varyings/vectors” (vec4 units) with “components” (scalar units); a 4x off-by-factor is easy to repeat anywhere a limit is expressed in one unit but compared in another (uniforms, inputs, transform-feedback varyings).
- Workaround thresholds vs. driver triggersreview every entry in ANGLE/Chromium’s driver-bug workaround set where a numeric threshold is meant to mirror a driver’s internal limit; confirm the compile-time cap actually matches the value that triggers the native bug, not a derived or rounded value.
- Validation-before-forward invariantslook for other places where ANGLE validates a resource size and then forwards to the driver; a too-loose bound here is a pattern that converts driver bugs into web-reachable corruption.
Patch
From e0e5a9bc99a7027268515fd6463768d7c6236293 Mon Sep 17 00:00:00 2001 From: Ken Russell <[email protected]> Date: Fri, 02 Oct 2026 16:01:02 -0700 Subject: [PATCH] Change semantic of limitOutputVaryingsTo256 workaround. Change this to limit the number of vertex output components to 256 rather than the number of vertex output varyings, which is 4 times larger and which can still trigger the underlying driver bug. Avoid changing the name of the workaround because it's already referenced in Chromium's code. Revise the unit test for the workaround. Fixed: chromium:567736859 Change-Id: I5ca01e92ce29b2a61263d1fe0b6b275d52edf75a Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/8503841 Commit-Queue: Shahbaz Youssefi <[email protected]> Auto-Submit: Kenneth Russell <[email protected]> Reviewed-by: Shahbaz Youssefi <[email protected]> --- diff --git a/src/compiler/translator/ParseContext.cpp b/src/compiler/translator/ParseContext.cpp index 622e90d..b58d4ba 100644 --- a/src/compiler/translator/ParseContext.cpp +++ b/src/compiler/translator/ParseContext.cpp @@ -11347,11 +11347,10 @@ mNumOutputVaryingComponents = checkedNum.ValueOrDefault(std::numeric_limits<unsigned int>::max()); - // The cap to 256 vec4s = 1024 components seems somewhat arbitrary, but this is intended as a - // workaround for a specific driver bug, and this limit being much - // higher than the device limits (mResources.MaxVertexOutputVectors * - // 4), it avoids regressing both tests and applications. - if (mNumOutputVaryingComponents > 1024) + // The cap to 256 components seems somewhat arbitrary, but this is intended as a workaround for + // a specific driver bug, and this limit being much higher than the device limits + // (mResources.MaxVertexOutputVectors * 4), it avoids regressing both tests and applications. + if (mNumOutputVaryingComponents > 256) { error(line, "Too many declared shader output varying components for this device", variable.name()); diff --git a/src/tests/gl_tests/GLSLValidationTest.cpp b/src/tests/gl_tests/GLSLValidationTest.cpp index 63edd37..f2718dc 100644 --- a/src/tests/gl_tests/GLSLValidationTest.cpp +++ b/src/tests/gl_tests/GLSLValidationTest.cpp @@ -10220,14 +10220,14 @@ {}; // Regression test for crbug.com/529991907. -// Verify that compiling a shader with up to 1024 output varying components -// succeeds, and exceeding 1024 components is rejected at compile time. +// Verify that compiling a shader with up to 256 output varying components +// succeeds, and exceeding 256 components is rejected at compile time. TEST_P(GLSLValidationTest_ES3_LimitOutputVaryings, TooManyDeclaredVertexOutputComponents) { ANGLE_SKIP_TEST_IF( !getEGLWindow()->isFeatureEnabled(Feature::LimitOutputVaryingsTo256AtCompileTime)); - constexpr int kMaxVectors = 1024 / 4; + constexpr int kMaxVectors = 256 / 4; std::stringstream vsValid; vsValid << "#version 300 es\n";
Regression Test / PoC
diff --git a/src/tests/gl_tests/GLSLValidationTest.cpp b/src/tests/gl_tests/GLSLValidationTest.cpp
index 63edd37..f2718dc 100644
--- a/src/tests/gl_tests/GLSLValidationTest.cpp
+++ b/src/tests/gl_tests/GLSLValidationTest.cpp
@@ -10220,14 +10220,14 @@
{};
// Regression test for crbug.com/529991907.
-// Verify that compiling a shader with up to 1024 output varying components
-// succeeds, and exceeding 1024 components is rejected at compile time.
+// Verify that compiling a shader with up to 256 output varying components
+// succeeds, and exceeding 256 components is rejected at compile time.
TEST_P(GLSLValidationTest_ES3_LimitOutputVaryings, TooManyDeclaredVertexOutputComponents)
{
ANGLE_SKIP_TEST_IF(
!getEGLWindow()->isFeatureEnabled(Feature::LimitOutputVaryingsTo256AtCompileTime));
- constexpr int kMaxVectors = 1024 / 4;
+ constexpr int kMaxVectors = 256 / 4;
std::stringstream vsValid;
vsValid << "#version 300 es\n";