CVE-2026-17651
Overview
Files Changed
src/libANGLE/Framebuffer.cppsrc/libANGLE/Framebuffer.hsrc/libANGLE/validationES.cppsrc/tests/angle_end2end_tests_expectations.txtsrc/tests/gl_tests/WebGLCompatibilityTest.cpp
Patch
From 8903f2cc0bcade7cc296cc7518e6c0a2bf4d3e1c Mon Sep 17 00:00:00 2001 From: Stephen White <[email protected]> Date: Tue, 09 Jun 2026 16:17:16 -0400 Subject: [PATCH] Modify validation of depth/stencil feedback loops Allow depth/stencil read-only feedback loops on non-WebGL contexts. Bug: angleproject:521861813 Bug: chromium:517307966 Change-Id: Ie6a3cc7460d59541941d30ff8a4d270fa26b9ead Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/7914428 Commit-Queue: Stephen White <[email protected]> Reviewed-by: Geoff Lang <[email protected]> Reviewed-by: Charlie Lao <[email protected]> Reviewed-by: Shahbaz Youssefi <[email protected]> --- diff --git a/src/libANGLE/Framebuffer.cpp b/src/libANGLE/Framebuffer.cpp index 035383f..bb18831 100644 --- a/src/libANGLE/Framebuffer.cpp +++ b/src/libANGLE/Framebuffer.cpp @@ -2380,7 +2380,8 @@ } } -bool Framebuffer::formsRenderingFeedbackLoopWith(const Context *context) const +bool Framebuffer::formsRenderingFeedbackLoopWith(const Context *context, + AllowedFeedbackLoop allowedFeedbackLoop) const { const State &glState = context->getState(); const ProgramExecutable *executable = glState.getLinkedProgramExecutable(context); @@ -2415,12 +2416,22 @@ if (AttachmentOverlapsWithTexture(mState.mDepthAttachment, texture, sampler)) { - return true; + if (allowedFeedbackLoop != AllowedFeedbackLoop::ReadOnlyDepthStencil || + (glState.isDepthWriteEnabled() && !texture->getState().isStencilMode())) + { + return true; + } } if (AttachmentOverlapsWithTexture(mState.mStencilAttachment, texture, sampler)) { - return true; + if (allowedFeedbackLoop != AllowedFeedbackLoop::ReadOnlyDepthStencil || + (glState.isStencilWriteEnabled( + glState.getDrawFramebuffer()->getStencilBitCount()) && + texture->getState().isStencilMode())) + { + return true; + } } if (pls != nullptr) diff --git a/src/libANGLE/Framebuffer.h b/src/libANGLE/Framebuffer.h index 555000a..74c5664 100644 --- a/src/libANGLE/Framebuffer.h +++ b/src/libANGLE/Framebuffer.h @@ -456,7 +456,14 @@ // Observer implementation void onSubjectStateChange(angle::SubjectIndex index, angle::SubjectMessage message) override; - bool formsRenderingFeedbackLoopWith(const Context *context) const; + enum class AllowedFeedbackLoop : bool + { + NoneAllowed, + ReadOnlyDepthStencil, + }; + + bool formsRenderingFeedbackLoopWith(const Context *context, + AllowedFeedbackLoop allowedFeedbackLoop) const; bool formsCopyingFeedbackLoopWith(TextureID destTextureId, const gl::ImageIndex &destImageIndex) const; diff --git a/src/libANGLE/validationES.cpp b/src/libANGLE/validationES.cpp index 73761f1..5ecde1f 100644 --- a/src/libANGLE/validationES.cpp +++ b/src/libANGLE/validationES.cpp @@ -4245,7 +4245,10 @@ if (ANGLE_UNLIKELY(context->isWebGL() || context->isHardenedContext())) { // UB: Detect rendering feedback loops for WebGL or hardened context. - if (framebuffer->formsRenderingFeedbackLoopWith(context)) + if (framebuffer->formsRenderingFeedbackLoopWith( + context, context->isWebGL() + ? Framebuffer::AllowedFeedbackLoop::NoneAllowed + : Framebuffer::AllowedFeedbackLoop::ReadOnlyDepthStencil)) { return kFeedbackLoop; } diff --git a/src/tests/angle_end2end_tests_expectations.txt b/src/tests/angle_end2end_tests_expectations.txt index 4cff1cb4..fc14680 100644 --- a/src/tests/angle_end2end_tests_expectations.txt +++ b/src/tests/angle_end2end_tests_expectations.txt @@ -103,6 +103,14 @@ 500721361 VULKAN SWIFTSHADER : VertexAttributeTestES3.storeStaticAttribWithLargeOffset/* = SKIP +// Causes a write-after-write hazard on Linux/Vk +524405499 VULKAN : HardenedContextTest.RenderingFeedbackLoopWithStencilOnlyStencil8/* = SKIP +524405499 VULKAN : HardenedContextTest.RenderingFeedbackLoopWithStencilOnlyANGLE/* = SKIP + +// Asserts on Win/D3D11 +524406308 WIN D3D11 : HardenedContextTest.RenderingFeedbackLoopWithStencilOnlyStencil8/* = SKIP +524406308 WIN D3D11 : HardenedContextTest.RenderingFeedbackLoopWithStencilOnlyANGLE/* = SKIP + // Windows 42262429 WIN NVIDIA D3D11 : BufferDataOverflowTest.VertexBufferIntegerOverflow/* = SKIP 40096654 WIN VULKAN : BufferDataOverflowTest.VertexBufferIntegerOverflow/* = SKIP diff --git a/src/tests/gl_tests/WebGLCompatibilityTest.cpp b/src/tests/gl_tests/WebGLCompatibilityTest.cpp index 04f9929..8c94044 100644 --- a/src/tests/gl_tests/WebGLCompatibilityTest.cpp +++ b/src/tests/gl_tests/WebGLCompatibilityTest.cpp @@ -4770,6 +4770,235 @@ EXPECT_GL_ERROR(GL_INVALID_OPERATION) << "Stencil test disabled should still fail"; } +// This test covers detection of rendering feedback loops between the FBO and a depth Texture. +// Read-only depth/stencil feedback loops are allowed in hardened contexts and +// should not generate an error. +TEST_P(HardenedContextTest, RenderingFeedbackLoopWithDepthStencil) +{ + constexpr char kVS[] = + R"(#version 300 es +in vec4 aPosition; +out vec2 texCoord; +void main() { + gl_Position = aPosition; + texCoord = (aPosition.xy * 0.5) + 0.5; +})"; + + constexpr char kFS[] = + R"(#version 300 es +precision mediump float; +uniform sampler2D tex; +in vec2 texCoord; +out vec4 oColor; +void main() { + oColor = texture(tex, texCoord); +})"; + + GLsizei width = 8; + GLsizei height = 8; + + ANGLE_GL_PROGRAM(program, kVS, kFS); + glUseProgram(program); + + glViewport(0, 0, width, height); + + GLint texLoc = glGetUniformLocation(program, "tex"); + glUniform1i(texLoc, 0); + + // Create textures and allocate storage + GLTexture tex0; + GLTexture tex1; + FillTexture2D(tex0, width, height, GLColor::black, 0, GL_RGBA, GL_RGBA, GL_UNSIGNED_BYTE); + FillTexture2D(tex1, width, height, 0x80, 0, GL_DEPTH_COMPONENT16, GL_DEPTH_COMPONENT, + GL_UNSIGNED_INT); + ASSERT_GL_NO_ERROR(); + + GLFramebuffer fbo; + glBindFramebuffer(GL_FRAMEBUFFER, fbo); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex0, 0); + + // Test rendering and sampling feedback loop for depth buffer + glBindTexture(GL_TEXTURE_2D, tex1); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, tex1, 0); + ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER); + + // The same image is used as depth buffer during rendering. + // This should be an error in hardened contexts. + glEnable(GL_DEPTH_TEST); + drawQuad(program, "aPosition", 0.5f, 1.0f, true); + EXPECT_GL_ERROR(GL_INVALID_OPERATION) << "Same image as depth buffer should fail"; + + // The same image is used as depth buffer. But depth mask is false. + glDepthMask(GL_FALSE); + drawQuad(program, "aPosition", 0.5f, 1.0f, true); + EXPECT_GL_NO_ERROR(); + + // The same image is used as depth buffer. But depth test is not enabled during rendering. + glDepthMask(GL_TRUE); + glDisable(GL_DEPTH_TEST); + drawQuad(program, "aPosition", 0.5f, 1.0f, true); + EXPECT_GL_NO_ERROR(); +} + +// This test covers detection of rendering feedback loops between the FBO and a stencil texture with +// GL_OES_texture_stencil8. Read-only stencil feedback loops are allowed in hardened contexts and +// should not generate an error. +TEST_P(HardenedContextTest, RenderingFeedbackLoopWithStencilOnlyStencil8) +{ + ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_OES_texture_stencil8")); + + constexpr char kVS[] = + R"(#version 300 es +in vec4 aPosition; +out vec2 texCoord;
Regression Test / PoC
diff --git a/src/tests/angle_end2end_tests_expectations.txt b/src/tests/angle_end2end_tests_expectations.txt
index 4cff1cb4..fc14680 100644
--- a/src/tests/angle_end2end_tests_expectations.txt
+++ b/src/tests/angle_end2end_tests_expectations.txt
@@ -103,6 +103,14 @@
500721361 VULKAN SWIFTSHADER : VertexAttributeTestES3.storeStaticAttribWithLargeOffset/* = SKIP
+// Causes a write-after-write hazard on Linux/Vk
+524405499 VULKAN : HardenedContextTest.RenderingFeedbackLoopWithStencilOnlyStencil8/* = SKIP
+524405499 VULKAN : HardenedContextTest.RenderingFeedbackLoopWithStencilOnlyANGLE/* = SKIP
+
+// Asserts on Win/D3D11
+524406308 WIN D3D11 : HardenedContextTest.RenderingFeedbackLoopWithStencilOnlyStencil8/* = SKIP
+524406308 WIN D3D11 : HardenedContextTest.RenderingFeedbackLoopWithStencilOnlyANGLE/* = SKIP
+
// Windows
42262429 WIN NVIDIA D3D11 : BufferDataOverflowTest.VertexBufferIntegerOverflow/* = SKIP
40096654 WIN VULKAN : BufferDataOverflowTest.VertexBufferIntegerOverflow/* = SKIP
diff --git a/src/tests/gl_tests/WebGLCompatibilityTest.cpp b/src/tests/gl_tests/WebGLCompatibilityTest.cpp
index 04f9929..8c94044 100644
--- a/src/tests/gl_tests/WebGLCompatibilityTest.cpp
+++ b/src/tests/gl_tests/WebGLCompatibilityTest.cpp
@@ -4770,6 +4770,235 @@
EXPECT_GL_ERROR(GL_INVALID_OPERATION) << "Stencil test disabled should still fail";
}
+// This test covers detection of rendering feedback loops between the FBO and a depth Texture.
+// Read-only depth/stencil feedback loops are allowed in hardened contexts and
+// should not generate an error.
+TEST_P(HardenedContextTest, RenderingFeedbackLoopWithDepthStencil)
+{
+ constexpr char kVS[] =
+ R"(#version 300 es
+in vec4 aPosition;
+out vec2 texCoord;
+void main() {
+ gl_Position = aPosition;
+ texCoord = (aPosition.xy * 0.5) + 0.5;
+})";
+
+ constexpr char kFS[] =
+ R"(#version 300 es
+precision mediump float;
+uniform sampler2D tex;
+in vec2 texCoord;
+out vec4 oColor;
+void main() {
+ oColor = texture(tex, texCoord);
+})";
+
+ GLsizei width = 8;
+ GLsizei height = 8;
+
+ ANGLE_GL_PROGRAM(program, kVS, kFS);
+ glUseProgram(program);
+
+ glViewport(0, 0, width, height);
+
+ GLint texLoc = glGetUniformLocation(program, "tex");
+ glUniform1i(texLoc, 0);
+
+ // Create textures and allocate storage
+ GLTexture tex0;
+ GLTexture tex1;
+ FillTexture2D(tex0, width, height, GLColor::black, 0, GL_RGBA, GL_RGBA, GL_UNSIGNED_BYTE);
+ FillTexture2D(tex1, width, height, 0x80, 0, GL_DEPTH_COMPONENT16, GL_DEPTH_COMPONENT,
+ GL_UNSIGNED_INT);
+ ASSERT_GL_NO_ERROR();
+
+ GLFramebuffer fbo;
+ glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+ glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex0, 0);
+
+ // Test rendering and sampling feedback loop for depth buffer
+ glBindTexture(GL_TEXTURE_2D, tex1);
+ glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, tex1, 0);
+ ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
+
+ // The same image is used as depth buffer during rendering.
+ // This should be an error in hardened contexts.
+ glEnable(GL_DEPTH_TEST);
+ drawQuad(program, "aPosition", 0.5f, 1.0f, true);
+ EXPECT_GL_ERROR(GL_INVALID_OPERATION) << "Same image as depth buffer should fail";
+
+ // The same image is used as depth buffer. But depth mask is false.
+ glDepthMask(GL_FALSE);
+ drawQuad(program, "aPosition", 0.5f, 1.0f, true);
+ EXPECT_GL_NO_ERROR();
+
+ // The same image is used as depth buffer. But depth test is not enabled during rendering.
+ glDepthMask(GL_TRUE);
+ glDisable(GL_DEPTH_TEST);
+ drawQuad(program, "aPosition", 0.5f, 1.0f, true);
+ EXPECT_GL_NO_ERROR();
+}
+
+// This test covers detection of rendering feedback loops between the FBO and a stencil texture with
+// GL_OES_texture_stencil8. Read-only stencil feedback loops are allowed in hardened contexts and
+// should not generate an error.
+TEST_P(HardenedContextTest, RenderingFeedbackLoopWithStencilOnlyStencil8)
+{
+ ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_OES_texture_stencil8"));
+
+ constexpr char kVS[] =
+ R"(#version 300 es
+in vec4 aPosition;
+out vec2 texCoord;
+void main() {
+ gl_Position = aPosition;
+ texCoord = (aPosition.xy * 0.5) + 0.5;
+})";
+
+ constexpr char kFS[] =
+ R"(#version 300 es
+precision mediump float;
+precision mediump usampler2D;
+uniform usampler2D tex;
+in vec2 texCoord;
+out vec4 oColor;
+void main() {
+ oColor = vec4(texture(tex, texCoord)) / 256.0;
+})";
+
+ GLsizei width = 8;
+ GLsizei height = 8;
+
+ ANGLE_GL_PROGRAM(program, kVS, kFS);
+ glUseProgram(program);
+
+ glViewport(0, 0, width, height);
+
+ GLint texLoc = glGetUniformLocation(program, "tex");
+ glUniform1i(texLoc, 0);
+
+ // Create textures and allocate storage
+ GLTexture tex0;
+ GLTexture tex1;
+ FillTexture2D(tex0, width, height, GLColor::black, 0, GL_RGBA, GL_RGBA, GL_UNSIGNED_BYTE);
+ FillTexture2D(tex1, width, height, 0x40, 0, GL_STENCIL_INDEX8, GL_STENCIL_INDEX,
+ GL_UNSIGNED_BYTE);
+ ASSERT_GL_NO_ERROR();
+
+ GLFramebuffer fbo;
+ glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+ glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex0, 0);
+ glFramebufferTexture2D(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, tex1, 0);
+ ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
+
+ glBindTexture(GL_TEXTURE_2D, tex1);
+
+ // The same image is used as stencil buffer during rendering.
+ // Ensure that the stencil func and op are not no-ops, so that there can
+ // be stencil writes.
+ glEnable(GL_STENCIL_TEST);
+ glStencilFunc(GL_GREATER, 0x0000, 0xFFFFFFFF);
+ glStencilOp(GL_KEEP, GL_REPLACE, GL_REPLACE);
+ drawQuad(program, "aPosition", 0.5f, 1.0f, true);
+ EXPECT_GL_ERROR(GL_INVALID_OPERATION) << "Same image as stencil buffer should fail";
+
+ // The same image is used as stencil buffer. But stencil mask is zero.
+ glStencilMask(0x0);
+ drawQuad(program, "aPosition", 0.5f, 1.0f, true);
+ EXPECT_GL_NO_ERROR();
+
+ // The same image is used as stencil buffer. But stencil test is not enabled during rendering.
+ glStencilMask(0xffff);
+ glDisable(GL_STENCIL_TEST);
+ drawQuad(program, "aPosition", 0.5f, 1.0f, true);
+ EXPECT_GL_NO_ERROR();
+}
+
+// This test covers detection of rendering feedback loops between the FBO and a stencil texture with
+// GL_ANGLE_stencil_texturing. Read-only stencil feedback loops are allowed in hardened contexts and
+// should not generate an error.
+TEST_P(HardenedContextTest, RenderingFeedbackLoopWithStencilOnlyANGLE)
+{
+ ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_ANGLE_stencil_texturing"));
+
+ constexpr char kVS[] =
+ R"(#version 300 es
+in vec4 aPosition;
+out vec2 texCoord;
+void main() {
+ gl_Position = aPosition;
+ texCoord = (aPosition.xy * 0.5) + 0.5;
+})";
+
+ constexpr char kFS[] =
+ R"(#version 300 es
+precision mediump float;
+precision mediump usampler2D;
+uniform usampler2D tex;
+in vec2 texCoord;
+out vec4 oColor;
+void main() {
+ oColor = vec4(texture(tex, texCoord)) / 256.0;
+})";
+
+ GLsizei width = 8;
+ GLsizei height = 8;
+
+ ANGLE_GL_PROGRAM(program, kVS, kFS);
+ glUseProgram(program);
+
+ glViewport(0, 0, width, height);
+
+ GLint texLoc = glGetUniformLocation(program, "tex");
+ glUniform1i(texLoc, 0);
+
+ // Create textures and allocate storage
+ GLTexture tex0;
+ GLTexture tex1;
+ FillTexture2D(tex0, width, height, GLColor::black, 0, GL_RGBA, GL_RGBA, GL_UNSIGNED_BYTE);
+ FillTexture2D(tex1, width, height, 0x40, 0, GL_DEPTH24_STENCIL8, GL_DEPTH_STENCIL,
+ GL_UNSIGNED_INT_24_8);
+ ASSERT_GL_NO_ERROR();
+
+ GLFramebuffer fbo;
+ glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+ glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex0, 0);
+ glFramebufferTexture2D(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, tex1, 0);
+ ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
+
+ glBindTexture(GL_TEXTURE_2D, tex1);
+ glTexParameteri(GL_TEXTURE_2D, GL_DEPTH_STENCIL_TEXTURE_MODE, GL_STENCIL_INDEX);
+
+ // The same image is used as stencil buffer during rendering.
+ // Ensure that the stencil func and op are not no-ops, so that there can
+ // be stencil writes.
+ glEnable(GL_STENCIL_TEST);
+ glStencilFunc(GL_GREATER, 0x0000, 0xFFFFFFFF);
+ glStencilOp(GL_KEEP, GL_REPLACE, GL_REPLACE);
+ drawQuad(program, "aPosition", 0.5f, 1.0f, true);
+ EXPECT_GL_ERROR(GL_INVALID_OPERATION) << "Same image as stencil buffer should fail";
+
+ // The same image is used as stencil buffer. But stencil mask is zero.
+ glStencilMask(0x0);
+ drawQuad(program, "aPosition", 0.5f, 1.0f, true);
+ EXPECT_GL_NO_ERROR();
+
+ // The same image is used as stencil buffer. But stencil test is not enabled during rendering.
+ glStencilMask(0xffff);
+ glDisable(GL_STENCIL_TEST);
+ drawQuad(program, "aPosition", 0.5f, 1.0f, true);
+ EXPECT_GL_NO_ERROR();
+
+ // The same image is used as stencil buffer. But only the depth component of the texture is
+ // being read.
+ glStencilMask(0xffff);
+ glEnable(GL_STENCIL_TEST);
+ glTexParameteri(GL_TEXTURE_2D, GL_DEPTH_STENCIL_TEXTURE_MODE, GL_DEPTH_COMPONENT);
+ drawQuad(program, "aPosition", 0.5f, 1.0f, true);
+ EXPECT_GL_NO_ERROR();
+}
+
// The source and the target for CopyTexSubImage3D are the same 3D texture.
// But the level of the 3D texture != the level of the read attachment.
TEST_P(WebGL2CompatibilityTest, NoTextureCopyingFeedbackLoopBetween3DLevels)
Original Bug Report
Potential Android GPU Driver Memory Corruption via Dawn OpenGLES Unhardened ANGLE Context
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: Dawn’s OpenGLES backend creates its EGL context without requesting a hardened ANGLE context, which disables ANGLE’s large-variable size validation checks. This allows Tint-generated storage buffer blocks up to 4GB to pass validation and reach the native Android GLES vendor shader compiler, potentially triggering integer-overflow or memory corruption vulnerabilities.
Affected files:
third_party/dawn/src/dawn/native/opengl/ContextEGL.cppthird_party/dawn/src/tint/lang/wgsl/resolver/resolver.cc
Estimated timestamp from git blame: 2026-01-13
Root Cause Analysis
In third_party/dawn/src/dawn/native/opengl/ContextEGL.cpp, Dawn’s OpenGLES backend creates its EGL context via egl.CreateContext without specifying either the EGL_CONTEXT_HARDENED_ANGLE or EGL_CONTEXT_WEBGL_COMPATIBILITY_ANGLE attributes:
if (egl.HasExt(EGLExt::CreateContext)) {
switch (backend) {
case wgpu::BackendType::OpenGLES:
AddAttrib(EGL_CONTEXT_MAJOR_VERSION, 3);
AddAttrib(EGL_CONTEXT_MINOR_VERSION, 1);
break;
...
}
}
Unlike Chrome’s passthrough command-buffer path (which enforces context hardening), Dawn’s directly created EGL context bypasses this mitigation. As a result, in ANGLE’s context initialization (third_party/angle/src/libANGLE/Context.cpp), mHardenedContext remains false:
bool GetHardenedContext(const egl::AttributeMap &attribs) {
return (attribs.get(EGL_CONTEXT_HARDENED_ANGLE, EGL_FALSE) == EGL_TRUE);
}
When ANGLE compiles a shader, options.rejectWebglShadersWithLargeVariables is only set to true if context->isWebGL() || context->isHardenedContext() (third_party/angle/src/libANGLE/Shader.cpp). Because both are false on Dawn’s OpenGLES context, ANGLE’s variable size validation in ParseContext.cpp (checkVariableSize) is bypassed:
if (!mCompileOptions.rejectWebglShadersWithLargeVariables || numErrors() > 0 ||
(mShaderType != GL_VERTEX_SHADER && mShaderType != GL_FRAGMENT_SHADER)) {
return true; // Unconditionally bypassed for Dawn's OpenGLES context
}
Attacker-Controlled Shader Sizes (Tint 4GB Limit)
In third_party/dawn/src/tint/lang/wgsl/resolver/resolver.cc, Tint’s WGSL resolver explicitly exempts storage address space arrays from the standard 65,536-element cap, validating only that the total block byte size is under 4GB:
if (auto* arr = ty->As<sem::Array>()) {
if (address_space != core::AddressSpace::kStorage) {
...
if (count.has_value() && count.value() >= internal_limits::kMaxArrayElementCount) { ... }
}
}
At shader translation time, the GLSL writer emits the literal element count inside an std430 buffer block, creating a structure that can be up to 4GB (e.g., layout(binding=0, std430) buffer f_block { mat4 inner[67108863]; } v; which translates to 4,294,967,232 bytes).
Suggested / Potential Attack Steps
Note: These are potential steps, as our tooling agent has not executed this code to verify a functional proof-of-concept.
- A web page requests a WebGPU adapter with
featureLevel: "compatibility"on Android (where Vulkan is blocklisted or unavailable), which falls back to OpenGLES. - The attacker’s page submits a shader containing an exceptionally large array (e.g., ~4GB) in the
<storage>address space. - Tint verifies that the array is under 4GB and successfully translates the WGSL shader into a GLSL block with a massive literal dimension.
- Because
rejectWebglShadersWithLargeVariablesisfalse, the 4GB SSBO structure bypasses ANGLE’s variable-size validation. - During
glCompileShader, ANGLE’s GL backend (ShaderGL::postTranslate) forwards the translated GLSL containing the oversized block to the native Android GLES GPU vendor driver prior to link-time checks. - Delivered to a vendor driver compiler in the unsandboxed GPU process on Android, block size calculations exceeding
INT32_MAXbytes can overflow signed 32-bit integers, potentially causing memory corruption or heap buffer overflow in the driver.
Proposed Fix
Dawn’s OpenGLES backend should query and pass EGL_CONTEXT_HARDENED_ANGLE during context creation inside ContextEGL.cpp when the display extension is supported, or otherwise clamp maximum shader interface variable sizes to prevent them from approaching driver integer overflow limits on non-hardened compatibility contexts.
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.