CVE-2026-78905
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
ClipDistanceTestES3src/tests/gl_tests/ClipDistanceTest.cpp |
modified |
Files Changed
src/compiler/translator/ParseContext.cppsrc/compiler/translator/ParseContext.hsrc/tests/angle_end2end_tests_expectations.txtsrc/tests/gl_tests/ClipDistanceTest.cpp
Patch
From ab5bee732a58f6c61b93b6eea830ef6296325d98 Mon Sep 17 00:00:00 2001 From: Shahbaz Youssefi <[email protected]> Date: Mon, 20 Jul 2026 15:39:51 -0400 Subject: [PATCH] Translator: Disallow gl_Clip/CullDistance whole-array use w/o... ... an explicit declaration. The alternative would be to record all whole-array uses of these built-ins and at the end of the shader validate that the implicitly-derived size matches the whole array use. Such usage is banned instead in this change. Bug: chromium:517245017 Change-Id: I842b2073c5132e1b1a74fd5316c4bb99c8740b2b Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/8122943 Commit-Queue: Shahbaz Youssefi <[email protected]> Reviewed-by: Alexey Knyazev <[email protected]> Reviewed-by: Geoff Lang <[email protected]> --- diff --git a/src/compiler/translator/ParseContext.cpp b/src/compiler/translator/ParseContext.cpp index 623f1cb..7773434 100644 --- a/src/compiler/translator/ParseContext.cpp +++ b/src/compiler/translator/ParseContext.cpp @@ -3269,7 +3269,7 @@ for (size_t i = 0; i < fnCandidate->getParamCount(); ++i) { TQualifier qual = fnCandidate->getParam(i)->getType().getQualifier(); - TIntermTyped *argument = (*(fnCall->getSequence()))[i]->getAsTyped(); + TIntermTyped *argument = (*fnCall->getSequence())[i]->getAsTyped(); bool argumentIsRead = (IsQualifierUnspecified(qual) || qual == EvqParamIn || qual == EvqParamInOut || qual == EvqParamConst); if (argumentIsRead) @@ -3299,6 +3299,45 @@ } } +void TParseContext::checkClipCullDistanceWholeArrayUse(const TSourceLoc &location, + TQualifier qualifier, + const char *message) +{ + switch (qualifier) + { + case EvqClipDistance: + if (mClipDistanceInfo.size == 0) + { + error(location, message, "gl_ClipDistance"); + return; + } + break; + case EvqCullDistance: + if (mCullDistanceInfo.size == 0) + { + error(location, message, "gl_CullDistance"); + return; + } + break; + default: + break; + } +} + +void TParseContext::functionCallClipCullDistanceCheck(const TFunction *fnCandidate, + TIntermAggregate *fnCall) +{ + // If clip/cull distance is not redeclared, they can't be passed to a function because their + // size is unknown. Per EXT_clip_cull_distance, only indexing with constants can implicitly + // size the built-ins, passing to a function shouldn't try to size them. + for (size_t i = 0; i < fnCandidate->getParamCount(); ++i) + { + TIntermTyped *argument = (*fnCall->getSequence())[i]->getAsTyped(); + checkClipCullDistanceWholeArrayUse(argument->getLine(), argument->getQualifier(), + "Cannot pass to function unless it is explicitly sized"); + } +} + void TParseContext::checkInvariantVariableQualifier(bool invariant, const TQualifier qualifier, const TSourceLoc &invariantLocation) @@ -9038,6 +9077,16 @@ error(loc, "array size mismatch", GetOperatorString(op)); return false; } + + // If either side is gl_Clip/CullDistance but the built-in is not sized, that's not allowed. + // Per EXT_clip_cull_distance, only indexing with constants can implicitly size the + // built-ins, using them in whole-array assignment shouldn't try to size them. + checkClipCullDistanceWholeArrayUse( + loc, left->getType().getQualifier(), + "Cannot use as left-hand side of assignment unless it is explicitly sized"); + checkClipCullDistanceWholeArrayUse( + loc, right->getType().getQualifier(), + "Cannot use as right-hand side of assignment unless it is explicitly sized"); } // Check ops which require integer / ivec parameters @@ -10012,6 +10061,7 @@ callNode->setLine(loc); checkImageMemoryAccessForUserDefinedFunctions(fnCandidate, callNode); functionCallRValueLValueErrorCheck(fnCandidate, callNode); + functionCallClipCullDistanceCheck(fnCandidate, callNode); mCallGraph[mCurrentFunction].insert(fnCandidate); mIRBuilder.callFunction(mFunctionToId.at(fnCandidate)); diff --git a/src/compiler/translator/ParseContext.h b/src/compiler/translator/ParseContext.h index d086955..956bcf3 100644 --- a/src/compiler/translator/ParseContext.h +++ b/src/compiler/translator/ParseContext.h @@ -216,6 +216,10 @@ bool checkWorkGroupSizeIsNotSpecified(const TSourceLoc &location, const TLayoutQualifier &layoutQualifier); void functionCallRValueLValueErrorCheck(const TFunction *fnCandidate, TIntermAggregate *fnCall); + void checkClipCullDistanceWholeArrayUse(const TSourceLoc &location, + TQualifier qualifier, + const char *message); + void functionCallClipCullDistanceCheck(const TFunction *fnCandidate, TIntermAggregate *fnCall); void checkInvariantVariableQualifier(bool invariant, const TQualifier qualifier, const TSourceLoc &invariantLocation); diff --git a/src/tests/angle_end2end_tests_expectations.txt b/src/tests/angle_end2end_tests_expectations.txt index ac1f736..417e268 100644 --- a/src/tests/angle_end2end_tests_expectations.txt +++ b/src/tests/angle_end2end_tests_expectations.txt @@ -211,6 +211,7 @@ 463961767 WIN INTEL OPENGL : EGLSurfacelessContextTest.Switcheroo/* = SKIP 494350632 WIN INTEL OPENGL : MipmapTestES3.MismatchingLevelFormats/* = SKIP 355607623 WIN INTEL OPENGL : WebGL2GLSLTest.BasicInfiniteLoop/* = SKIP +537235696 WIN INTEL OPENGL : ClipDistance*.ThreeClipDistancesRedeclaredAndPassedToFunction/* = SKIP // Some Intel Vulkan drivers report incorrect VkPhysicalDeviceLimits::maxSamplerLodBias on Windows. 498165510 WIN INTEL VULKAN : QCOMTextureLodBiasTest.BiasClamping/* = SKIP @@ -288,6 +289,7 @@ 379758210 LINUX INTEL OPENGL : GLSLTest_ES3.SwizzledToBoolCoercion/* = SKIP 437259106 LINUX INTEL : EGLDisplayTest.ContextLeakAfterTerminate/* = SKIP 448675904 LINUX INTEL OPENGL : GLSLTestLoops.ForContinueInSwitchComplex/* = SKIP +537235696 LINUX INTEL OPENGL : ClipDistance*.ThreeClipDistancesRedeclaredAndPassedToFunction/* = SKIP //Angle has bugs for sampler validation 401546698 : TextureCubeTest.CubeMapBug2/* = SKIP @@ -343,6 +345,7 @@ 536046166 NVIDIA VULKAN : FramebufferTest_ES31_MSAA.MultisampleStencilSampling/*EmulatedPrerotation* = SKIP 42265709 NVIDIA VULKAN : GLSLTest_ES31.TessellationControlShaderMatrixCopyBug/* = SKIP 42265709 NVIDIA VULKAN : GLSLTest_ES31.TessellationControlShaderMatrixMultiplicationBug/* = SKIP +537235696 NVIDIA OPENGL : ClipDistance*.ThreeClipDistancesRedeclaredAndPassedToFunction/* = SKIP // Intel Vulkan @@ -460,6 +463,7 @@ 515709506 MAC METAL : DrawBaseVertexBaseInstanceTest_ES3.BaseInstanceSmallDivisorClientMemory/* = SKIP 524008572 MAC METAL : VertexAttributeTestES3.LargeAttribPointerOffsetNoCrash/* = SKIP 534815900 MAC METAL : TextureCubeTestES3.RedefinedCubemapLevelsOnlyFaceZeroCompatible/* = SKIP +536936861 MAC METAL : ClipDistance*.ThreeClipDistancesRedeclaredAndPassedToFunction/* = SKIP // The workaround is not intended to be enabled in this configuration so // skip it as the failure is likely a driver bug. diff --git a/src/tests/gl_tests/ClipDistanceTest.cpp b/src/tests/gl_tests/ClipDistanceTest.cpp index 46c5623..069d16d 100644 --- a/src/tests/gl_tests/ClipDistanceTest.cpp +++ b/src/tests/gl_tests/ClipDistanceTest.cpp @@ -28,8 +28,16 @@ setConfigDepthBits(24); setExtensionsEnabled(false); } + + // Given the vertex and fragment shader, writes position to |a_position| and three planes in + // |u_plane[i]|. The vertex shader should evaluate |gl_ClipDistance[i]| as + // |dot(position, u_plane[i])|. The fragment shader should output red. + void threeClipDistancesRedeclared(const char *vs, const char *fs); }; +class ClipDistanceTestES3 : public ClipDistanceAPPLETest +{}; + // Query max clip distances and enable, disable states of clip distances TEST_P(ClipDistanceAPPLETest, StateQuery) { @@ -218,11 +226,15 @@ attribute vec2 a_position; +void write(out float distance) +{ + distance = dot(gl_Position, u_plane); +} + void main() { gl_Position = vec4(a_position, 0.0, 1.0); - - gl_ClipDistance[0] = dot(gl_Position, u_plane); + write(gl_ClipDistance[0]); })"; ANGLE_GL_PROGRAM(programRed, kVS, essl1_shaders::fs::Red()); @@ -647,36 +659,9 @@ } } -// Redeclare gl_ClipDistance in shader with explicit size, also use it in a global function -// outside main() -TEST_P(ClipDistanceAPPLETest, ThreeClipDistancesRedeclared) +void ClipDistanceAPPLETest::threeClipDistancesRedeclared(const char *vs, const char *fs)
Regression Test / PoC
diff --git a/src/tests/angle_end2end_tests_expectations.txt b/src/tests/angle_end2end_tests_expectations.txt
index ac1f736..417e268 100644
--- a/src/tests/angle_end2end_tests_expectations.txt
+++ b/src/tests/angle_end2end_tests_expectations.txt
@@ -211,6 +211,7 @@
463961767 WIN INTEL OPENGL : EGLSurfacelessContextTest.Switcheroo/* = SKIP
494350632 WIN INTEL OPENGL : MipmapTestES3.MismatchingLevelFormats/* = SKIP
355607623 WIN INTEL OPENGL : WebGL2GLSLTest.BasicInfiniteLoop/* = SKIP
+537235696 WIN INTEL OPENGL : ClipDistance*.ThreeClipDistancesRedeclaredAndPassedToFunction/* = SKIP
// Some Intel Vulkan drivers report incorrect VkPhysicalDeviceLimits::maxSamplerLodBias on Windows.
498165510 WIN INTEL VULKAN : QCOMTextureLodBiasTest.BiasClamping/* = SKIP
@@ -288,6 +289,7 @@
379758210 LINUX INTEL OPENGL : GLSLTest_ES3.SwizzledToBoolCoercion/* = SKIP
437259106 LINUX INTEL : EGLDisplayTest.ContextLeakAfterTerminate/* = SKIP
448675904 LINUX INTEL OPENGL : GLSLTestLoops.ForContinueInSwitchComplex/* = SKIP
+537235696 LINUX INTEL OPENGL : ClipDistance*.ThreeClipDistancesRedeclaredAndPassedToFunction/* = SKIP
//Angle has bugs for sampler validation
401546698 : TextureCubeTest.CubeMapBug2/* = SKIP
@@ -343,6 +345,7 @@
536046166 NVIDIA VULKAN : FramebufferTest_ES31_MSAA.MultisampleStencilSampling/*EmulatedPrerotation* = SKIP
42265709 NVIDIA VULKAN : GLSLTest_ES31.TessellationControlShaderMatrixCopyBug/* = SKIP
42265709 NVIDIA VULKAN : GLSLTest_ES31.TessellationControlShaderMatrixMultiplicationBug/* = SKIP
+537235696 NVIDIA OPENGL : ClipDistance*.ThreeClipDistancesRedeclaredAndPassedToFunction/* = SKIP
// Intel Vulkan
@@ -460,6 +463,7 @@
515709506 MAC METAL : DrawBaseVertexBaseInstanceTest_ES3.BaseInstanceSmallDivisorClientMemory/* = SKIP
524008572 MAC METAL : VertexAttributeTestES3.LargeAttribPointerOffsetNoCrash/* = SKIP
534815900 MAC METAL : TextureCubeTestES3.RedefinedCubemapLevelsOnlyFaceZeroCompatible/* = SKIP
+536936861 MAC METAL : ClipDistance*.ThreeClipDistancesRedeclaredAndPassedToFunction/* = SKIP
// The workaround is not intended to be enabled in this configuration so
// skip it as the failure is likely a driver bug.
diff --git a/src/tests/gl_tests/ClipDistanceTest.cpp b/src/tests/gl_tests/ClipDistanceTest.cpp
index 46c5623..069d16d 100644
--- a/src/tests/gl_tests/ClipDistanceTest.cpp
+++ b/src/tests/gl_tests/ClipDistanceTest.cpp
@@ -28,8 +28,16 @@
setConfigDepthBits(24);
setExtensionsEnabled(false);
}
+
+ // Given the vertex and fragment shader, writes position to |a_position| and three planes in
+ // |u_plane[i]|. The vertex shader should evaluate |gl_ClipDistance[i]| as
+ // |dot(position, u_plane[i])|. The fragment shader should output red.
+ void threeClipDistancesRedeclared(const char *vs, const char *fs);
};
+class ClipDistanceTestES3 : public ClipDistanceAPPLETest
+{};
+
// Query max clip distances and enable, disable states of clip distances
TEST_P(ClipDistanceAPPLETest, StateQuery)
{
@@ -218,11 +226,15 @@
attribute vec2 a_position;
+void write(out float distance)
+{
+ distance = dot(gl_Position, u_plane);
+}
+
void main()
{
gl_Position = vec4(a_position, 0.0, 1.0);
-
- gl_ClipDistance[0] = dot(gl_Position, u_plane);
+ write(gl_ClipDistance[0]);
})";
ANGLE_GL_PROGRAM(programRed, kVS, essl1_shaders::fs::Red());
@@ -647,36 +659,9 @@
}
}
-// Redeclare gl_ClipDistance in shader with explicit size, also use it in a global function
-// outside main()
-TEST_P(ClipDistanceAPPLETest, ThreeClipDistancesRedeclared)
+void ClipDistanceAPPLETest::threeClipDistancesRedeclared(const char *vs, const char *fs)
{
- ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_APPLE_clip_distance"));
-
- constexpr char kVS[] = R"(
-#extension GL_APPLE_clip_distance : require
-
-varying highp float gl_ClipDistance[3];
-
-void computeClipDistances(in vec4 position, in vec4 plane[3])
-{
- gl_ClipDistance[0] = dot(position, plane[0]);
- gl_ClipDistance[1] = dot(position, plane[1]);
- gl_ClipDistance[2] = dot(position, plane[2]);
-}
-
-uniform vec4 u_plane[3];
-
-attribute vec2 a_position;
-
-void main()
-{
- gl_Position = vec4(a_position, 0.0, 1.0);
-
- computeClipDistances(gl_Position, u_plane);
-})";
-
- ANGLE_GL_PROGRAM(programRed, kVS, essl1_shaders::fs::Red());
+ ANGLE_GL_PROGRAM(programRed, vs, fs);
glUseProgram(programRed);
ASSERT_GL_NO_ERROR();
@@ -741,6 +726,104 @@
}
}
+// Redeclare gl_ClipDistance in shader with explicit size, also use it in a global function
+// outside main()
+TEST_P(ClipDistanceAPPLETest, ThreeClipDistancesRedeclared)
+{
+ ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_APPLE_clip_distance"));
+
+ constexpr char kVS[] = R"(
+#extension GL_APPLE_clip_distance : require
+
+varying highp float gl_ClipDistance[3];
+
+void computeClipDistances(in vec4 position, in vec4 plane[3])
+{
+ gl_ClipDistance[0] = dot(position, plane[0]);
+ gl_ClipDistance[1] = dot(position, plane[1]);
+ gl_ClipDistance[2] = dot(position, plane[2]);
+}
+
+uniform vec4 u_plane[3];
+
+attribute vec2 a_position;
+
+void main()
+{
+ gl_Position = vec4(a_position, 0.0, 1.0);
+
+ computeClipDistances(gl_Position, u_plane);
+})";
+
+ threeClipDistancesRedeclared(kVS, essl1_shaders::fs::Red());
+}
+
+// Redeclare gl_ClipDistance in shader with explicit size, also pass it to a function.
+TEST_P(ClipDistanceAPPLETest, ThreeClipDistancesRedeclaredAndPassedToFunction)
+{
+ ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_APPLE_clip_distance"));
+
+ constexpr char kVS[] = R"(
+#extension GL_APPLE_clip_distance : require
+
+varying highp float gl_ClipDistance[3];
+
+void computeClipDistances(out float distance[3], in vec4 position, in vec4 plane[3])
+{
+ distance[0] = dot(position, plane[0]);
+ distance[1] = dot(position, plane[1]);
+ distance[2] = dot(position, plane[2]);
+}
+
+uniform vec4 u_plane[3];
+
+attribute vec2 a_position;
+
+void main()
+{
+ gl_Position = vec4(a_position, 0.0, 1.0);
+
+ computeClipDistances(gl_ClipDistance, gl_Position, u_plane);
+})";
+
+ threeClipDistancesRedeclared(kVS, essl1_shaders::fs::Red());
+}
+
+// Redeclare gl_ClipDistance in shader with explicit size, also pass it to a function.
+TEST_P(ClipDistanceTestES3, ThreeClipDistancesRedeclaredAndPassedToFunction)
+{
+ const bool hasExt = IsGLExtensionEnabled("GL_EXT_clip_cull_distance");
+ const bool hasAngle = IsGLExtensionEnabled("GL_ANGLE_clip_cull_distance");
+ ANGLE_SKIP_TEST_IF(!hasExt && !hasAngle);
+
+ std::stringstream vs;
+ vs << R"(#version 300 es
+#extension )"
+ << (hasExt ? "GL_EXT_clip_cull_distance" : "GL_ANGLE_clip_cull_distance") << R"( : require
+
+varying highp float gl_ClipDistance[3];
+
+void computeClipDistances(out float distance[3], in vec4 position, in vec4 plane[3])
+{
+ distance[0] = dot(position, plane[0]);
+ distance[1] = dot(position, plane[1]);
+ distance[2] = dot(position, plane[2]);
+}
+
+uniform vec4 u_plane[3];
+
+in vec2 a_position;
+
+void main()
+{
+ gl_Position = vec4(a_position, 0.0, 1.0);
+
+ computeClipDistances(gl_ClipDistance, gl_Position, u_plane);
+})";
+
+ threeClipDistancesRedeclared(vs.str().c_str(), essl3_shaders::fs::Red());
+}
+
using ClipCullDistanceTestParams = std::tuple<angle::PlatformParameters, bool>;
std::string PrintToStringParamName(const ::testing::TestParamInfo<ClipCullDistanceTestParams> &info)
@@ -2918,6 +3001,9 @@
ANGLE_INSTANTIATE_TEST_ES2_AND_ES3(ClipDistanceAPPLETest);
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(ClipDistanceTestES3);
+ANGLE_INSTANTIATE_TEST_ES3(ClipDistanceTestES3);
+
GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(ClipCullDistanceTest);
ANGLE_INSTANTIATE_TEST_COMBINE_1(ClipCullDistanceTest,
PrintToStringParamName,
diff --git a/src/tests/gl_tests/GLSLValidationTest.cpp b/src/tests/gl_tests/GLSLValidationTest.cpp
index dc5e920..c7197e5 100644
--- a/src/tests/gl_tests/GLSLValidationTest.cpp
+++ b/src/tests/gl_tests/GLSLValidationTest.cpp
@@ -7332,6 +7332,276 @@
}
}
+// Shader passes gl_ClipDistance to function without explicitly sizing it
+TEST_P(GLSLValidationClipDistanceTest_ES3, UnsizedClipDistancePassedToFunction)
+{
+ const bool hasExt = IsGLExtensionEnabled("GL_EXT_clip_cull_distance");
+ const bool hasAngle = IsGLExtensionEnabled("GL_ANGLE_clip_cull_distance");
+ ANGLE_SKIP_TEST_IF(!hasExt && !hasAngle);
+
+ constexpr char kVS[] =
+ R"(in vec4 aPosition;
+void f(float d[8]) {}
+void main()
+{
+ gl_Position = aPosition;
+ gl_ClipDistance[0] = 1.0;
+ f(gl_ClipDistance);
+}
+)";
+ constexpr char kExpect[] =
+ "'gl_ClipDistance' : Cannot pass to function unless it is explicitly sized";
+
+ if (hasAngle)
+ {
+ validateErrorWithExt(GL_VERTEX_SHADER, "GL_ANGLE_clip_cull_distance", kVS, kExpect);
+ }
+
+ if (hasExt)
+ {
+ validateErrorWithExt(GL_VERTEX_SHADER, "GL_EXT_clip_cull_distance", kVS, kExpect);
+ }
+}
+
+// Shader passes gl_CullDistance to function without explicitly sizing it
+TEST_P(GLSLValidationClipDistanceTest_ES3, UnsizedCullDistancePassedToFunction)
+{
+ const bool hasExt = IsGLExtensionEnabled("GL_EXT_clip_cull_distance");
+ const bool hasAngle = IsGLExtensionEnabled("GL_ANGLE_clip_cull_distance");
+ ANGLE_SKIP_TEST_IF(!hasExt && !hasAngle);
+
+ constexpr char kVS[] =
+ R"(in vec4 aPosition;
+void f(float d[8]) {}
+void main()
+{
+ gl_Position = aPosition;
+ gl_CullDistance[0] = 1.0;
+ f(gl_CullDistance);
+}
+)";
+ constexpr char kExpect[] =
+ "'gl_CullDistance' : Cannot pass to function unless it is explicitly sized";
+
+ if (hasAngle)
+ {
+ GLint maxCullDistances = 0;
+ glGetIntegerv(GL_MAX_CULL_DISTANCES_EXT, &maxCullDistances);
+ if (maxCullDistances > 0)
+ {
+ validateErrorWithExt(GL_VERTEX_SHADER, "GL_ANGLE_clip_cull_distance", kVS, kExpect);
+ }
+ }
+
+ if (hasExt)
+ {
... (truncated)
Original Bug Report
Type mismatch in ANGLE SPIR-V generation via whole-array gl_ClipDistance references
Project Fortify, 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: ANGLE fails to re-promote parent AST node types after shrinking the built-in gl_ClipDistance or gl_CullDistance arrays, creating an AST type desynchronization. In production builds of Chrome where AST and SPIR-V validations are disabled, this results in malformed SPIR-V instructions being passed to the underlying platform Vulkan driver. Depending on the Vulkan compiler’s robustness, this could potentially lead to out-of-bounds compiler memory accesses within the GPU process.
Affected files:
third_party/angle/src/compiler/translator/SizeClipCullDistance.cppthird_party/angle/src/compiler/translator/ParseContext.cppthird_party/angle/src/compiler/translator/spirv/OutputSPIRV.cpp
Estimated timestamp from git blame: 2025-10-01
Description
A potential vulnerability has been identified in ANGLE’s handling of the built-in gl_ClipDistance and gl_CullDistance arrays. The compiler pre-declares gl_ClipDistance with the maximum supported size (typically float[8]). During parsing, if the array is only indexed up to a smaller boundary (e.g., gl_ClipDistance[0] = 1.0;), the SizeClipCullDistance compiler pass resizes the array variable to the actual used size (e.g., float[3]).
While the pass replaces the variable associated with the underlying TIntermSymbol nodes, the parent nodes in the AST (such as TIntermBinary representing variable initializations or TIntermAggregate representing function calls) retain their original cached types (float[8]) determined during parsing. This leads to a type desynchronization within the AST.
In release builds of Chrome, where AST and SPIR-V validations are compiled out, this malformed AST is translated directly into SPIR-V. The mismatch manifests as an invalid OpStore instruction where a value of the resized type (float[3]) is stored into a pointer of the unresized type (float[8]):
OpStore %ptr_float_8 %val_float_3
This violates Section 3.42.8 of the SPIR-V Specification, which mandates that the type of the Object operand must match the type pointed to by the Pointer operand. This malformed SPIR-V is then passed directly to the platform’s Vulkan driver via vkCreateShaderModule in the GPU process.
Potential Impact
Passing structurally malformed SPIR-V to a Vulkan driver compiler can result in undefined behavior, including out-of-bounds memory writes or memory corruption during shader compilation. On platforms like Android, where the GPU process is currently unsandboxed, memory corruption in the Vulkan compiler could potentially facilitate a direct sandbox escape.
Potential Steps to Reproduce
Note: The following steps are theoretical and have not been executed on a live environment as this analysis was conducted via static code review.
- Initialize a WebGL2 context and request the
GL_ANGLE_clip_cull_distanceorGL_EXT_clip_cull_distanceextension on a platform using ANGLE’s Vulkan backend. - Query the maximum allowed clip distances (
N, typically 8). - Compile and attempt to link a vertex shader that references the whole
gl_ClipDistancearray (either via assignment to another array of sizeN, or by passing it to a function expecting an array of sizeN), while only writing to a single index:#version 300 es #extension GL_ANGLE_clip_cull_distance : require void f(highp float a[8]) { gl_Position = vec4(a[7]); } void main() { gl_ClipDistance[0] = 1.0; f(gl_ClipDistance); } - Trigger program linkage to force the SPIR-V binary generation and subsequent submission to
vkCreateShaderModule.
Suggested Fix
When SizeClipCullDistance modifies the array bounds of the built-in variables, the AST must be traversed to update and re-promote the types of any parent nodes that reference the resized symbol. Alternatively, the compiler should ensure that any parent TIntermExpression caching type definitions (such as TIntermBinary and TIntermAggregate nodes) are forced to re-derive their types when a child variable is replaced, preventing type desynchronization prior to SPIR-V code generation.
Evaluated with Chrome root at commit: b1520ef4a76878853a31f0943b565e42060edec8
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.