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 <[email protected]> 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 <[email protected]> Reviewed-by: Shahbaz Youssefi <[email protected]> --- 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