CVE-2026-14412
Overview
Files Changed
src/libANGLE/Framebuffer.cppsrc/libANGLE/renderer/d3d/FramebufferD3D.cppsrc/tests/gl_tests/WebGLCompatibilityTest.cpp
Patch
From b29ddb229672bf3890bd570878efd73c3060bc00 Mon Sep 17 00:00:00 2001 From: Geoff Lang <[email protected]> Date: Mon, 25 May 2026 12:30:39 -0400 Subject: [PATCH] Validate unique framebuffer attachments for hardened contexts. This behaviour was already done for WebGL and some backends. Fixed: chromium:513920834 Change-Id: Ifedf64a9b9be2265fa0f42af4f05586dd45611cd Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/7870307 Reviewed-by: Shahbaz Youssefi <[email protected]> Commit-Queue: Shahbaz Youssefi <[email protected]> --- diff --git a/src/libANGLE/Framebuffer.cpp b/src/libANGLE/Framebuffer.cpp index 03d8d09..9168b54 100644 --- a/src/libANGLE/Framebuffer.cpp +++ b/src/libANGLE/Framebuffer.cpp @@ -1604,7 +1604,7 @@ // The WebGL conformance tests implicitly define that all framebuffer // attachments must be unique. For example, the same level of a texture can // not be attached to two different color attachments. - if (state.getExtensions().webglCompatibilityANGLE) + if (context->isWebGL() || context->isHardenedContext()) { if (!mState.colorAttachmentsAreUniqueImages()) { diff --git a/src/libANGLE/renderer/d3d/FramebufferD3D.cpp b/src/libANGLE/renderer/d3d/FramebufferD3D.cpp index 83622df..a00a277 100644 --- a/src/libANGLE/renderer/d3d/FramebufferD3D.cpp +++ b/src/libANGLE/renderer/d3d/FramebufferD3D.cpp @@ -263,9 +263,9 @@ } // D3D11 does not allow for overlapping RenderTargetViews. - // If WebGL compatibility is enabled, this has already been checked at a higher level. - ASSERT(!context->isWebGL() || mState.colorAttachmentsAreUniqueImages()); - if (!context->isWebGL()) + // If WebGL compatibility (or hardened context) is enabled, this has already been checked at a + // higher level. + if (!context->isWebGL() && !context->isHardenedContext()) { if (!mState.colorAttachmentsAreUniqueImages()) { @@ -274,6 +274,7 @@ gl::err::kFramebufferIncompleteUnsupportedNonUniqueAttachments); } } + ASSERT(mState.colorAttachmentsAreUniqueImages()); // D3D requires all render targets to have the same dimensions. if (!mState.attachmentsHaveSameDimensions()) diff --git a/src/tests/gl_tests/WebGLCompatibilityTest.cpp b/src/tests/gl_tests/WebGLCompatibilityTest.cpp index 4c253a5..653e8ea 100644 --- a/src/tests/gl_tests/WebGLCompatibilityTest.cpp +++ b/src/tests/gl_tests/WebGLCompatibilityTest.cpp @@ -4557,6 +4557,36 @@ drawBuffersFeedbackLoop(program, {{GL_COLOR_ATTACHMENT0, GL_NONE}}, GL_INVALID_OPERATION); } +// WebGL requires that all framebuffer attachments are unique +TEST_P(WebGL2CompatibilityTest, UniqueFramebufferAttachments) +{ + GLTexture tex0; + FillTexture2D(tex0, 8, 8, GLColor::red, 0, GL_RGBA, GL_RGBA, 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_COLOR_ATTACHMENT1, GL_TEXTURE_2D, tex0, 0); + EXPECT_GLENUM_EQ(GL_FRAMEBUFFER_UNSUPPORTED, glCheckFramebufferStatus(GL_FRAMEBUFFER)); +} + +// Hardened contexts also require that all framebuffer color attachments are unique +TEST_P(HardenedContextTest, UniqueFramebufferAttachments) +{ + ANGLE_SKIP_TEST_IF(getClientMajorVersion() < 3); + + GLTexture tex0; + FillTexture2D(tex0, 8, 8, GLColor::red, 0, GL_RGBA, GL_RGBA, 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_COLOR_ATTACHMENT1, GL_TEXTURE_2D, tex0, 0); + EXPECT_GLENUM_EQ(GL_FRAMEBUFFER_UNSUPPORTED, glCheckFramebufferStatus(GL_FRAMEBUFFER)); +} + // This tests that texture base level for immutable textures is clamped to the valid range, unlike // for non-immutable textures, for purposes of validation. Related to WebGL test // conformance2/textures/misc/immutable-tex-render-feedback.html
Regression Test / PoC
diff --git a/src/tests/gl_tests/WebGLCompatibilityTest.cpp b/src/tests/gl_tests/WebGLCompatibilityTest.cpp
index 4c253a5..653e8ea 100644
--- a/src/tests/gl_tests/WebGLCompatibilityTest.cpp
+++ b/src/tests/gl_tests/WebGLCompatibilityTest.cpp
@@ -4557,6 +4557,36 @@
drawBuffersFeedbackLoop(program, {{GL_COLOR_ATTACHMENT0, GL_NONE}}, GL_INVALID_OPERATION);
}
+// WebGL requires that all framebuffer attachments are unique
+TEST_P(WebGL2CompatibilityTest, UniqueFramebufferAttachments)
+{
+ GLTexture tex0;
+ FillTexture2D(tex0, 8, 8, GLColor::red, 0, GL_RGBA, GL_RGBA, 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_COLOR_ATTACHMENT1, GL_TEXTURE_2D, tex0, 0);
+ EXPECT_GLENUM_EQ(GL_FRAMEBUFFER_UNSUPPORTED, glCheckFramebufferStatus(GL_FRAMEBUFFER));
+}
+
+// Hardened contexts also require that all framebuffer color attachments are unique
+TEST_P(HardenedContextTest, UniqueFramebufferAttachments)
+{
+ ANGLE_SKIP_TEST_IF(getClientMajorVersion() < 3);
+
+ GLTexture tex0;
+ FillTexture2D(tex0, 8, 8, GLColor::red, 0, GL_RGBA, GL_RGBA, 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_COLOR_ATTACHMENT1, GL_TEXTURE_2D, tex0, 0);
+ EXPECT_GLENUM_EQ(GL_FRAMEBUFFER_UNSUPPORTED, glCheckFramebufferStatus(GL_FRAMEBUFFER));
+}
+
// This tests that texture base level for immutable textures is clamped to the valid range, unlike
// for non-immutable textures, for purposes of validation. Related to WebGL test
// conformance2/textures/misc/immutable-tex-render-feedback.html
Original Bug Report
ANGLE Vulkan: Potential driver undefined behavior due to non-unique color attachments
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’s Vulkan backend fails to ensure color attachments are unique images in non-WebGL contexts, violating Vulkan Valid Usage requirements. This can lead to driver-level undefined behavior in the GPU process when multiple attachments alias the same memory. This is especially critical on Android where the GPU process is unsandboxed.
Affected files:
third_party/angle/src/libANGLE/renderer/vulkan/FramebufferVk.cppthird_party/angle/src/libANGLE/Framebuffer.cppthird_party/angle/src/libANGLE/renderer/vulkan/vk_cache_utils.cppthird_party/angle/src/libANGLE/renderer/vulkan/TextureVk.cppthird_party/angle/src/libANGLE/renderer/vulkan/vk_helpers.cpp
Estimated timestamp from git blame: Unknown (Google3 checkout)
Summary
A potential security vulnerability exists in ANGLE’s Vulkan backend where it fails to enforce that color attachments in a framebuffer are unique images for non-WebGL contexts (e.g., OpenGL ES 3.0). This omission leads to a violation of Vulkan Valid Usage (VUID-VkRenderPassCreateInfo-pAttachments-00836), which forbids multiple attachments from aliasing the same memory in a single subpass unless the VK_ATTACHMENT_DESCRIPTION_MAY_ALIAS_BIT is set. ANGLE currently never sets this bit and does not prevent the aliasing, resulting in driver-level undefined behavior.
Root Cause Analysis
In ANGLE’s frontend (third_party/angle/src/libANGLE/Framebuffer.cpp), the check for unique color attachment images is guarded by the webglCompatibilityANGLE extension:
if (state.getExtensions().webglCompatibilityANGLE)
{
if (!mState.colorAttachmentsAreUniqueImages())
{
return FramebufferStatus::Incomplete(GL_FRAMEBUFFER_UNSUPPORTED, ...);
}
}
When a renderer requests a non-WebGL GLES context (which is allowed via IPC), this check is bypassed. While some backends like D3D11 duplicate this check in their own checkStatus implementation, the Vulkan backend (third_party/angle/src/libANGLE/renderer/vulkan/FramebufferVk.cpp) does not.
Furthermore, when creating the Vulkan render pass in third_party/angle/src/libANGLE/renderer/vulkan/vk_cache_utils.cpp, the VkAttachmentDescription2 structures are initialized without the VK_ATTACHMENT_DESCRIPTION_MAY_ALIAS_BIT flag, which is required if multiple attachments alias the same image memory.
Potential Trigger Path
Note: These steps are suggested based on code analysis; a functional proof-of-concept has not yet been executed.
- A compromised renderer process requests a non-WebGL context (e.g.,
CONTEXT_TYPE_OPENGLES3) from the GPU process. - The renderer creates a single texture and attaches it to both
GL_COLOR_ATTACHMENT0andGL_COLOR_ATTACHMENT1on a new Framebuffer Object (FBO). - The renderer calls
glCheckFramebufferStatus, which returnsGL_FRAMEBUFFER_COMPLETEdue to the frontend bypass and missing backend check. - The renderer enables both attachments and issues a draw command (e.g.,
glDrawArrays). - ANGLE’s Vulkan backend creates a
VkFramebuffercontaining duplicateVkImageViewhandles and aVkRenderPasswhere the attachments alias the same memory without theMAY_ALIASbit. - The graphics driver executes the command, violating Vulkan valid usage and potentially entering an exploitable undefined state.
Security Impact
Triggering driver-level undefined behavior represents a path for memory corruption within the GPU process. On platforms like Android, where the GPU process is unsandboxed, this could potentially be leveraged by an attacker to escape the renderer sandbox and gain full control over the GPU process.
Suggested Fix
The uniqueness check should be mirrored in the Vulkan backend’s status check to ensure color attachments are unique images for all context types. This can be achieved by adding a check to FramebufferVk::checkStatus in third_party/angle/src/libANGLE/renderer/vulkan/FramebufferVk.cpp similar to the one in the D3D backend.
Evaluated with Chrome root at commit: 1a8d40fc44df2088d5945c0bf53584038aa1614a
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.