Chrome · WebGL
CVE-2026-78948
OOB in WebGL
Overview
High
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Files Changed
include/GLSLANG/ShaderLang.hinclude/platform/autogen/FeaturesGL_autogen.hinclude/platform/gl_features.jsonsrc/compiler/translator/ParseContext.cpp
Patch
From 318948133839dcf445f6b39854538f776e3302b3 Mon Sep 17 00:00:00 2001
From: Ken Russell <kbr@chromium.org>
Date: Fri, 17 Jul 2026 15:24:36 -0700
Subject: [PATCH] Reland "GL: Limit declared vertex output varying components"
This reverts commit 3cf1a6c74785246ed0c29f58816632e05d7d6616.
Add a workaround which, when enabled, tracks the number of output
components from the vertex shader and fails the compile if there's an
attempt to exceed a specific limit (1024).
Relative to the original commit, apply a much higher compile time
limit which is unlikely to be reached by any test or real-world
application, and apply it to all context types, not just WebGL's.
Apply this workaround on Imagination's PowerVR OpenGL ES drivers to
work around a bug in the driver's shader compiler.
Co-authored with Gemini.
Test: angle_end2end_tests \
--gtest_filter=GLSLValidationTest_ES3_LimitOutputVaryings.\
TooManyDeclaredVertexOutputComponents
TAG=agy
CONV=8ed05bff-eb25-4de9-92b8-d097e4c0abae
Bug: chromium:529991907
Change-Id: I79d0bd117d6e0d6412011d361183484f088243e6
Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/8114814
Commit-Queue: Kenneth Russell <kbr@chromium.org>
Reviewed-by: Shahbaz Youssefi <syoussefi@chromium.org>
---
diff --git a/include/GLSLANG/ShaderLang.h b/include/GLSLANG/ShaderLang.h
index a725c38..dde82d2 100644
--- a/include/GLSLANG/ShaderLang.h
+++ b/include/GLSLANG/ShaderLang.h
@@ -26,7 +26,7 @@
// Version number for shader translation API.
// It is incremented every time the API changes.
-#define ANGLE_SH_VERSION 417
+#define ANGLE_SH_VERSION 418
enum ShShaderSpec
{
@@ -179,8 +179,8 @@
// If requested, validates the AST after every transformation. Useful for debugging.
uint64_t validateAST : 1;
- // placeholder bit for removed validateLoopIndexing option.
- uint64_t unused3 : 1;
+ // Limit the number of output varyings allowed in vertex shaders to work around driver bugs.
+ uint64_t limitOutputVaryingsTo256 : 1;
// Emits #line directives in HLSL.
uint64_t lineDirectives : 1;
diff --git a/include/platform/autogen/FeaturesGL_autogen.h b/include/platform/autogen/FeaturesGL_autogen.h
index 9f60d6b..2b4a23e 100644
--- a/include/platform/autogen/FeaturesGL_autogen.h
+++ b/include/platform/autogen/FeaturesGL_autogen.h
@@ -722,6 +722,12 @@
&members,
};
+ FeatureInfo limitOutputVaryingsTo256AtCompileTime = {
+ "limitOutputVaryingsTo256AtCompileTime",
+ FeatureCategory::OpenGLWorkarounds,
+ &members,
+ };
+
};
inline FeaturesGL::FeaturesGL() = default;
diff --git a/include/platform/gl_features.json b/include/platform/gl_features.json
index 127cccf..789e621 100644
--- a/include/platform/gl_features.json
+++ b/include/platform/gl_features.json
@@ -942,6 +942,14 @@
"Split full-image level 0 PBO uploads via TexSubImage2D into two calls to work around driver bugs."
],
"issue": "http://crbug.com/496807874"
+ },
+ {
+ "name": "limit_output_varyings_to_256_at_compile_time",
+ "category": "Workarounds",
+ "description": [
+ "Limit the number of declared varying components at compile time to work around a PowerVR driver bug."
+ ],
+ "issue": "http://crbug.com/529991907"
}
]
}
diff --git a/src/compiler/translator/ParseContext.cpp b/src/compiler/translator/ParseContext.cpp
index ddcf9bc..7745cbe 100644
--- a/src/compiler/translator/ParseContext.cpp
+++ b/src/compiler/translator/ParseContext.cpp
@@ -498,6 +498,31 @@
structDeclaration->appendDeclarator(structDeclarator);
return structDeclaration;
}
+
+unsigned int GetTypeComponentCount(const TType &type)
+{
+ unsigned int components = 0;
+ if (type.getBasicType() == EbtInterfaceBlock)
+ {
+ for (const TField *field : type.getInterfaceBlock()->fields())
+ {
+ components += GetTypeComponentCount(*field->type());
+ }
+ }
+ else if (type.getStruct())
+ {
+ for (const TField *field : type.getStruct()->fields())
+ {
+ components += GetTypeComponentCount(*field->type());
+ }
+ }
+ else
+ {
+ components = static_cast<unsigned int>(type.getNominalSize()) * type.getSecondarySize();
+ }
+ components *= type.getArraySizeProduct();
+ return components;
+}
} // namespace
// This tracks each binding point's current default offset for inheritance of subsequent
@@ -577,6 +602,7 @@
mNumViews(-1),
mMaxUniformBlocks(GetMaxUniformBlocksForShaderType(mShaderType, options, resources)),
mNumUniformBlocks(0),
+ mNumOutputVaryingComponents(0),
mDeclaringFunction(false),
mDeclaringMain(false),
mMainFunction(nullptr),
@@ -2343,6 +2369,7 @@
error(line, "redefinition", identifier);
return false;
}
+ addAndCheckOutputVaryings(**variable, line);
if (!checkIsNonVoid(line, identifier, type->getBasicType()))
{
@@ -7035,6 +7062,7 @@
error(field->line(), "redefinition of an interface block member name",
field->name());
}
+ addAndCheckOutputVaryings(*fieldVariable, field->line());
// Don't declare variables for fields of nameless interface blocks in the IR, just
// remember to implicitly index the instance variable when referenced.
@@ -7064,6 +7092,7 @@
{
error(instanceLine, "redefinition of an interface block instance name", instanceName);
}
+ addAndCheckOutputVaryings(*instanceVariable, instanceLine);
}
TIntermSymbol *blockSymbol = new TIntermSymbol(instanceVariable);
@@ -10768,6 +10797,44 @@
return numErrors() == 0;
}
+void TParseContext::addAndCheckOutputVaryings(const TVariable &variable, const TSourceLoc &line)
+{
+ if (mShaderType != GL_VERTEX_SHADER)
+ {
+ return;
+ }
+
+ if (!mCompileOptions.limitOutputVaryingsTo256)
+ {
+ return;
+ }
+
+ if (variable.symbolType() == SymbolType::BuiltIn)
+ {
+ return;
+ }
+
+ if (!IsVaryingOut(variable.getType().getQualifier()))
+ {
+ return;
+ }
+
+ angle::CheckedNumeric<unsigned int> checkedNum = mNumOutputVaryingComponents;
+ checkedNum += GetTypeComponentCount(variable.getType());
+ 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)
+ {
+ error(line, "Too many declared shader output varying components for this device",
+ variable.name());
Loading diff…
Regression Test / PoC
shipped with the fix
diff --git a/src/tests/gl_tests/GLSLValidationTest.cpp b/src/tests/gl_tests/GLSLValidationTest.cpp
index 014ab69..28a44aa 100644
--- a/src/tests/gl_tests/GLSLValidationTest.cpp
+++ b/src/tests/gl_tests/GLSLValidationTest.cpp
@@ -9469,6 +9469,39 @@
validateError(GL_FRAGMENT_SHADER, kFS, "version");
}
+class GLSLValidationTest_ES3_LimitOutputVaryings : public GLSLValidationTest_ES3
+{};
+
+// 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.
+TEST_P(GLSLValidationTest_ES3_LimitOutputVaryings, TooManyDeclaredVertexOutputComponents)
+{
+ ANGLE_SKIP_TEST_IF(
+ !getEGLWindow()->isFeatureEnabled(Feature::LimitOutputVaryingsTo256AtCompileTime));
+
+ constexpr int kMaxVectors = 1024 / 4;
+
+ std::stringstream vsValid;
+ vsValid << "#version 300 es\n";
+ for (int i = 0; i < kMaxVectors; ++i)
+ {
+ vsValid << "out highp vec4 v" << i << ";\n";
+ }
+ vsValid << "void main() { gl_Position = vec4(0.0); }\n";
+ validateSuccess(GL_VERTEX_SHADER, vsValid.str().c_str());
+
+ std::stringstream vsInvalid;
+ vsInvalid << "#version 300 es\n";
+ for (int i = 0; i < kMaxVectors + 1; ++i)
+ {
+ vsInvalid << "out highp vec4 v" << i << ";\n";
+ }
+ vsInvalid << "void main() { gl_Position = vec4(0.0); }\n";
+ validateError(GL_VERTEX_SHADER, vsInvalid.str().c_str(),
+ "Too many declared shader output varying components for this device");
+}
+
} // namespace
ANGLE_INSTANTIATE_TEST_ES2_AND_ES3(GLSLValidationTest);
@@ -9491,6 +9524,11 @@
GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(WebGL2GLSLValidationTest);
ANGLE_INSTANTIATE_TEST_ES3(WebGL2GLSLValidationTest);
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(GLSLValidationTest_ES3_LimitOutputVaryings);
+ANGLE_INSTANTIATE_TEST(GLSLValidationTest_ES3_LimitOutputVaryings,
+ ES3_OPENGL().enable(Feature::LimitOutputVaryingsTo256AtCompileTime),
+ ES3_OPENGLES().enable(Feature::LimitOutputVaryingsTo256AtCompileTime));
+
ANGLE_INSTANTIATE_TEST_ES2_AND(WebGLGLSLValidationExtensionDisableTest,
ES2_OPENGL().enable(Feature::AllowExtensionDisableAfterNonPpTokens));
Loading diff…
Original Bug Report
The reporter's bug is still restricted on the tracker. Chrome de-restricts security bugs ~30–90 days after the fix ships; a later run will backfill it here.
References
On This Page