CVE-2026-14402
Overview
Files Changed
src/libANGLE/renderer/d3d/d3d11/Blit11.cpp
Patch
From 2642c51bbee9e1851e8c245466cfae2be6fe00dc Mon Sep 17 00:00:00 2001
From: Geoff Lang <geofflang@chromium.org>
Date: Fri, 15 May 2026 09:52:50 -0400
Subject: [PATCH] D3D11: Avoid partial init state in Blit11.
If a resource allocation fails, Blit11 would be left in a partially
initialized state. Allocate to new stack variables and assign them to
members at the end.
Fixed: chromium:513051340
Change-Id: I197715bad3db9c6eda9d8fd2cb7593450ba5e413
Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/7850285
Commit-Queue: Shahbaz Youssefi <syoussefi@chromium.org>
Auto-Submit: Geoff Lang <geofflang@chromium.org>
Reviewed-by: Shahbaz Youssefi <syoussefi@chromium.org>
---
diff --git a/src/libANGLE/renderer/d3d/d3d11/Blit11.cpp b/src/libANGLE/renderer/d3d/d3d11/Blit11.cpp
index 98bf6b2..b0f12f9 100644
--- a/src/libANGLE/renderer/d3d/d3d11/Blit11.cpp
+++ b/src/libANGLE/renderer/d3d/d3d11/Blit11.cpp
@@ -1774,8 +1774,9 @@
Context11 *context11 = GetImplAs<Context11>(context);
- ANGLE_TRY(mRenderer->allocateTexture(context11, textureDesc, format, &mResolvedDepth));
- mResolvedDepth.setInternalName("Blit11::mResolvedDepth");
+ TextureHelper11 newResolvedDepth;
+ ANGLE_TRY(mRenderer->allocateTexture(context11, textureDesc, format, &newResolvedDepth));
+ newResolvedDepth.setInternalName("Blit11::mResolvedDepth");
D3D11_DEPTH_STENCIL_VIEW_DESC dsvDesc;
dsvDesc.Flags = 0;
@@ -1783,14 +1784,20 @@
dsvDesc.Texture2D.MipSlice = 0;
dsvDesc.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2D;
- ANGLE_TRY(mRenderer->allocateResource(context11, dsvDesc, mResolvedDepth.get(),
- &mResolvedDepthDSView));
- mResolvedDepthDSView.setInternalName("Blit11::mResolvedDepthDSView");
+ d3d11::DepthStencilView newResolvedDepthDSView;
+ ANGLE_TRY(mRenderer->allocateResource(context11, dsvDesc, newResolvedDepth.get(),
+ &newResolvedDepthDSView));
+ newResolvedDepthDSView.setInternalName("Blit11::mResolvedDepthDSView");
// Possibly D3D11 bug or undefined behaviour: Clear the DSV so that our first render
// works as expected. Otherwise the results of the first use seem to be incorrect.
ID3D11DeviceContext *deviceContext = mRenderer->getDeviceContext();
- deviceContext->ClearDepthStencilView(mResolvedDepthDSView.get(), D3D11_CLEAR_DEPTH, 1.0f, 0);
+ deviceContext->ClearDepthStencilView(newResolvedDepthDSView.get(), D3D11_CLEAR_DEPTH, 1.0f, 0);
+
+ // Only assign after the allocations have fully succeeded to avoid a partially initialized
+ // state.
+ mResolvedDepth = std::move(newResolvedDepth);
+ mResolvedDepthDSView = std::move(newResolvedDepthDSView);
return angle::Result::Continue;
}
@@ -1805,11 +1812,6 @@
return angle::Result::Continue;
}
- if (mResolvedDepthStencil.valid())
- {
- releaseResolveDepthStencilResources();
- }
-
const auto &formatSet = d3d11::Format::Get(GL_RG32F, mRenderer->getRenderer11DeviceCaps());
D3D11_TEXTURE2D_DESC textureDesc;
@@ -1827,13 +1829,24 @@
Context11 *context11 = GetImplAs<Context11>(context);
+ TextureHelper11 newResolvedDepthStencil;
ANGLE_TRY(
- mRenderer->allocateTexture(context11, textureDesc, formatSet, &mResolvedDepthStencil));
- mResolvedDepthStencil.setInternalName("Blit11::mResolvedDepthStencil");
+ mRenderer->allocateTexture(context11, textureDesc, formatSet, &newResolvedDepthStencil));
+ newResolvedDepthStencil.setInternalName("Blit11::mResolvedDepthStencil");
- ANGLE_TRY(mRenderer->allocateResourceNoDesc(context11, mResolvedDepthStencil.get(),
- &mResolvedDepthStencilRTView));
- mResolvedDepthStencilRTView.setInternalName("Blit11::mResolvedDepthStencilRTView");
+ d3d11::RenderTargetView newResolvedDepthStencilRTView;
+ ANGLE_TRY(mRenderer->allocateResourceNoDesc(context11, newResolvedDepthStencil.get(),
+ &newResolvedDepthStencilRTView));
+ newResolvedDepthStencilRTView.setInternalName("Blit11::mResolvedDepthStencilRTView");
+
+ // Only assign after the allocations have fully succeeded to avoid a partially initialized
+ // state.
+ if (mResolvedDepthStencil.valid())
+ {
+ releaseResolveDepthStencilResources();
+ }
+ mResolvedDepthStencil = std::move(newResolvedDepthStencil);
+ mResolvedDepthStencilRTView = std::move(newResolvedDepthStencilRTView);
return angle::Result::Continue;
}
Original Bug Report
Potential GPU memory disclosure in ANGLE D3D11 due to Blit11 cache desynchronization
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 Chrome Security team. Please see https://chromium.googlesource.com/chromium/src/+/main/docs/security/ai-generated-security-bugs-faq.md for more information.
Overview: A logic flaw in ANGLE’s D3D11 backend allows for the desynchronization of cached depth resolve resources when a resource allocation fails. This can lead to a WebGL context receiving an uninitialized texture containing stale GPU memory instead of the results of a resolve operation.
Affected files:
third_party/angle/src/libANGLE/renderer/d3d/d3d11/Blit11.cppthird_party/angle/src/libANGLE/renderer/d3d/d3d11/ResourceManager11.cppthird_party/angle/src/libANGLE/renderer/d3d/d3d11/Renderer11.cpp
Estimated timestamp from git blame: 2017-05-18
Background
In the ANGLE D3D11 renderer, Blit11 manages internal resources for blit and resolve operations. To optimize performance, it maintains a renderer-scoped cache for depth-only resolve operations, consisting of a texture (mResolvedDepth) and a DepthStencilView (mResolvedDepthDSView). Because Blit11 is owned by the shared Renderer11, these cached resources are shared across all WebGL contexts (and therefore across different origins) using the same D3D11 device.
The Vulnerability
The function Blit11::initResolveDepthOnly manages these cached resources in a fallible two-step process without sufficient state rollback. When the requested dimensions change, it reallocates the texture and then the view:
// third_party/angle/src/libANGLE/renderer/d3d/d3d11/Blit11.cpp
// Step 1: Reallocate texture. If successful, mResolvedDepth is updated.
ANGLE_TRY(mRenderer->allocateTexture(context11, textureDesc, format, &mResolvedDepth));
// Step 2: Reallocate DSV. If this fails, mResolvedDepthDSView is NOT updated.
ANGLE_TRY(mRenderer->allocateResource(context11, dsvDesc, mResolvedDepth.get(),
&mResolvedDepthDSView));
// Step 3: Clear the texture. This is only reached if Step 2 succeeds.
deviceContext->ClearDepthStencilView(mResolvedDepthDSView.get(), D3D11_CLEAR_DEPTH, 1.0f, 0);
If Step 1 succeeds but Step 2 fails (e.g., due to E_OUTOFMEMORY), the function returns an error, but the Blit11 object is left in an inconsistent state: mResolvedDepth points to the newly allocated texture, while mResolvedDepthDSView still points to the view for the previous texture size.
Because production builds typically do not zero-initialize newly allocated textures (unless specific debug flags are set), the new texture contains uninitialized GPU memory.
Potential Exploitation Path
- Attacker Context A triggers a multisample depth resolve for size $X$. The cache is populated correctly.
- Attacker Context A triggers a resolve for size $Y$ while the GPU is under memory pressure. Step 1 succeeds (updating
mResolvedDepthto size $Y$), but Step 2 fails.mResolvedDepthDSViewstill points to a view of size $X$. - Attacker Context B (or the same attacker) triggers a resolve for size $Y$.
initResolveDepthOnlysees a cache hit (sincemResolvedDepthis size $Y$) and returnsContinueearly. Blit11::resolveDepthproceeds to use the stalemResolvedDepthDSView. The GPU resolve operation writes into the old texture (size $X$) because that is what the stale view targets.- The function returns
mResolvedDepth(size $Y$), which remains uninitialized and contains stale GPU memory from the heap. - The attacker reads back this texture using standard WebGL commands (e.g.,
glReadPixels), potentially disclosing sensitive data from other origins or processes.
Suggested Fix
The initResolveDepthOnly function should ensure that dependent views are reset before attempting to reallocate the underlying texture, or implement a proper rollback mechanism. A similar hardening was previously applied to the sibling function initResolveDepthStencil, but appears to have been omitted here.
if (mResolvedDepth.valid())
{
mResolvedDepthDSView.reset();
mResolvedDepth.reset();
}
Evaluated with Chrome root at commit: b3153093eb3c78c3e88ccf562bcbc20437a04b0e
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.