CVE-2026-10919
Overview
Files Changed
src/libANGLE/renderer/vulkan/FramebufferVk.cppsrc/tests/gl_tests/TextureTest.cpp
Patch
From c0d3d631ba995e9b604c7755ed1bd57b1036f85e Mon Sep 17 00:00:00 2001 From: Shahbaz Youssefi <[email protected]> Date: Wed, 08 Apr 2026 10:46:15 -0400 Subject: [PATCH] Vulkan: Fix clean up of FSR image Bug: chromium:498872764 Change-Id: Id708656effead53182ca2820b5827fc0cef6b055 Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/7736774 Reviewed-by: Charlie Lao <[email protected]> Commit-Queue: Shahbaz Youssefi <[email protected]> --- diff --git a/src/libANGLE/renderer/vulkan/FramebufferVk.cpp b/src/libANGLE/renderer/vulkan/FramebufferVk.cpp index 9b8968f..4577593 100644 --- a/src/libANGLE/renderer/vulkan/FramebufferVk.cpp +++ b/src/libANGLE/renderer/vulkan/FramebufferVk.cpp @@ -472,6 +472,8 @@ if (mFragmentShadingRateImage.valid()) { + contextVk->finalizeImageLayout(&mFragmentShadingRateImage, {}); + vk::Renderer *renderer = contextVk->getRenderer(); mFragmentShadingRateImageView.release(renderer, mFragmentShadingRateImage.getResourceUse()); mFragmentShadingRateImage.releaseImage(renderer); diff --git a/src/tests/gl_tests/TextureTest.cpp b/src/tests/gl_tests/TextureTest.cpp index 11cb22f..3699645 100644 --- a/src/tests/gl_tests/TextureTest.cpp +++ b/src/tests/gl_tests/TextureTest.cpp @@ -14982,6 +14982,49 @@ EXPECT_PIXEL_COLOR_EQ(0, 0, angle::GLColor::blue); } +// Test destruction of foveated framebuffer while render pass is open. Regression test for a bug +// where the foveation image owned by the framebuffer wasn't properly handled upon framebuffer +// destruction. +TEST_P(Texture2DTestES3Foveation, DestroyFramebufferWhileRenderPassIsOpen) +{ + ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_QCOM_framebuffer_foveated")); + + // Switch to foveated framebuffer + GLTexture color; + glBindTexture(GL_TEXTURE_2D, color); + glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, 64, 64); + + GLFramebuffer fbo; + glBindFramebuffer(GL_FRAMEBUFFER, fbo); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, color, 0); + ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER); + + // Just need 1 focal point + GLuint providedFeatures = 0; + glFramebufferFoveationConfigQCOM(fbo, 1, 1, GL_FOVEATION_ENABLE_BIT_QCOM, &providedFeatures); + ASSERT_NE(providedFeatures & GL_FOVEATION_ENABLE_BIT_QCOM, 0u); + // Set foveation parameters + glFramebufferFoveationParametersQCOM(fbo, 0, 0, 0.0f, 0.0f, 8.0f, 8.0f, 0.0f); + EXPECT_GL_NO_ERROR(); + + ANGLE_GL_PROGRAM(drawGreen, essl1_shaders::vs::Simple(), essl1_shaders::fs::Green()); + glUseProgram(drawGreen); + + // Start a render pass with the foveated framebuffer + drawQuad(drawGreen, essl1_shaders::PositionAttrib(), 0.5f); + EXPECT_GL_NO_ERROR(); + + // Switch to another framebuffer + glBindFramebuffer(GL_FRAMEBUFFER, 0); + + // Delete the original framebuffer while the render pass is still open. + fbo.reset(); + + // Start a new render pass on the default framebuffer. + drawQuad(drawGreen, essl1_shaders::PositionAttrib(), 0.5f); + EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green); +} + // QCOM framebuffer foveated rendering with MSAA framebuffer TEST_P(Texture2DTestES3Foveation, DrawWithMsaaFramebuffer) {
Regression Test / PoC
diff --git a/src/tests/gl_tests/TextureTest.cpp b/src/tests/gl_tests/TextureTest.cpp
index 11cb22f..3699645 100644
--- a/src/tests/gl_tests/TextureTest.cpp
+++ b/src/tests/gl_tests/TextureTest.cpp
@@ -14982,6 +14982,49 @@
EXPECT_PIXEL_COLOR_EQ(0, 0, angle::GLColor::blue);
}
+// Test destruction of foveated framebuffer while render pass is open. Regression test for a bug
+// where the foveation image owned by the framebuffer wasn't properly handled upon framebuffer
+// destruction.
+TEST_P(Texture2DTestES3Foveation, DestroyFramebufferWhileRenderPassIsOpen)
+{
+ ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_QCOM_framebuffer_foveated"));
+
+ // Switch to foveated framebuffer
+ GLTexture color;
+ glBindTexture(GL_TEXTURE_2D, color);
+ glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, 64, 64);
+
+ GLFramebuffer fbo;
+ glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+ glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, color, 0);
+ ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
+
+ // Just need 1 focal point
+ GLuint providedFeatures = 0;
+ glFramebufferFoveationConfigQCOM(fbo, 1, 1, GL_FOVEATION_ENABLE_BIT_QCOM, &providedFeatures);
+ ASSERT_NE(providedFeatures & GL_FOVEATION_ENABLE_BIT_QCOM, 0u);
+ // Set foveation parameters
+ glFramebufferFoveationParametersQCOM(fbo, 0, 0, 0.0f, 0.0f, 8.0f, 8.0f, 0.0f);
+ EXPECT_GL_NO_ERROR();
+
+ ANGLE_GL_PROGRAM(drawGreen, essl1_shaders::vs::Simple(), essl1_shaders::fs::Green());
+ glUseProgram(drawGreen);
+
+ // Start a render pass with the foveated framebuffer
+ drawQuad(drawGreen, essl1_shaders::PositionAttrib(), 0.5f);
+ EXPECT_GL_NO_ERROR();
+
+ // Switch to another framebuffer
+ glBindFramebuffer(GL_FRAMEBUFFER, 0);
+
+ // Delete the original framebuffer while the render pass is still open.
+ fbo.reset();
+
+ // Start a new render pass on the default framebuffer.
+ drawQuad(drawGreen, essl1_shaders::PositionAttrib(), 0.5f);
+ EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
+}
+
// QCOM framebuffer foveated rendering with MSAA framebuffer
TEST_P(Texture2DTestES3Foveation, DrawWithMsaaFramebuffer)
{
Original Bug Report
Potential UAF in ANGLE Vulkan via dangling FragmentShadingRateImage pointer
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 without the security team.
Overview: A potential Use-After-Free (UAF) vulnerability exists in ANGLE’s Vulkan backend due to deferred render pass closure. A bare pointer to an inline ImageHelper is cached during a render pass and dereferenced later, leading to a UAF if the parent Framebuffer is synchronously deleted. Exploitation provides a highly reliable arbitrary 32-bit decrement primitive in the GPU process.
Affected files:
third_party/angle/src/libANGLE/renderer/vulkan/FramebufferVk.cppthird_party/angle/src/libANGLE/renderer/vulkan/vk_helpers.cppthird_party/angle/src/libANGLE/renderer/vulkan/FramebufferVk.hthird_party/angle/src/libANGLE/renderer/vulkan/vk_helpers.h
Estimated timestamp from git blame: 2025-09-08
Background
In ANGLE’s Vulkan backend, FramebufferVk contains an inline embedded vk::ImageHelper member named mFragmentShadingRateImage. When a render pass starts using a framebuffer with a fragment shading rate attachment, the address of this inline member (&mFragmentShadingRateImage) is stored as a bare ImageHelper* in RenderPassCommandBufferHelper::mFragmentShadingRateAtachment.mImage.
The Vulnerability
A Use-After-Free condition occurs because gl::Framebuffer objects can be deleted synchronously while their associated render passes are deferred and left active in the background for performance reasons.
Here are the potential steps an attacker might follow to exploit this:
- Foveation Initialization: A compromised renderer creates a texture with
GL_TEXTURE_FOVEATED_FEATURE_BITS_QCOM(reachable via the passthrough command buffer on supported hardware like Adreno) and attaches it to FBO X. - Pointer Caching: The attacker binds FBO X and issues a draw call.
FramebufferVk::startNewRenderPassis called, which passes the address of the inlinemFragmentShadingRateImagetoRenderPassCommandBufferHelper::fragmentShadingRateImageRead. This stores the bare pointer inmImage. - Deferred Render Pass Closure: The attacker binds a different framebuffer (FBO Y). ANGLE processes the binding change but, for performance optimization, does not immediately flush or close the existing render pass. The
RenderPassCommandBufferHelperremains alive in the background. - Synchronous Deletion: The attacker deletes FBO X via
glDeleteFramebuffers. Because FBO X is unbound, the deletion executes synchronously. The underlyingFramebufferVkobject is freed viaSafeDelete(mImpl), and becausemFragmentShadingRateImageis inline, its backing memory is freed as well. - Heap Spraying: The attacker sprays the heap to reclaim the freed
FramebufferVkchunk. They specifically craft themLastNonShaderReadOnlyEventmember of the sprayedImageHelper. This member is aRefCountedEventcontaining a bare pointerRefCounted<EventAndStage>* mHandle. The attacker setsmHandleto an arbitrary target memory address. - UAF Trigger: The attacker issues a draw call on FBO Y, forcing the deferred render pass to flush.
RenderPassCommandBufferHelper::endRenderPassretrieves the danglingmImagepointer and passes it toupdateImageLayoutAndBarrier. - Arbitrary Decrement Primitive: The layout transition logic executes
mLastNonShaderReadOnlyEvent.release(context), which callsmHandle->getAndReleaseRef() == 1. BecausemRefCountis the first member of theRefCounted<T>struct at offset +0, this executes a 32-bit decrement (mRefCount--) exactly at the attacker’s chosenmHandleaddress. This provides a powerful, highly reliable primitive to corrupt lengths/sizes and achieve RCE in the GPU process.
(Note: These are suggested steps based on source code analysis. Our tooling agent does not yet have the ability to run code to produce a working Proof of Concept).
Suggested Fix
The vulnerability fundamentally stems from caching a bare pointer to an object with a strictly shorter lifetime. Potential fixes include:
- Refactoring
mFragmentShadingRateImageout of the inlineFramebufferVkmemory and managing it via reference counting orstd::shared_ptrso it survives as long as theRenderPassCommandBufferHelperneeds it. - Having
FramebufferVk::destroyexplicitly walk the active command buffers and clear out any cached attachments originating from itself. - Adopting
raw_ptr<T>(MiraclePtr) forRenderPassAttachment::mImage, which would mitigate the UAF into a controlled crash.
Evaluated with Chrome root at commit: ff3d2b74fa39431785bd60e51463b08fcc71ee33
Results so far have been promising, but there can be wrong deductions. If this proves to be a false positive, please close as WAI; 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.