CVE-2026-17811
Overview
Files Changed
src/libANGLE/renderer/d3d/d3d11/TextureStorage11.cpp
Patch
From 1cadf7e26cb6f7817cc1606c932bc1e178f9b10a Mon Sep 17 00:00:00 2001 From: wangra <[email protected]> Date: Tue, 09 Jun 2026 10:03:35 -0400 Subject: [PATCH] D3D11: Fix Use-After-Free in TextureStorage11 onDestroy Ensures all associated images are disassociated during TextureStorage11 destruction, even if recovery fails. This prevents early loop termination from leaving sibling images with dangling pointers to the deleted storage. Bug: b/516954622 Change-Id: I2c87c9c0bc764245aefc20773d85f9c78eaa7a53 Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/7910783 Reviewed-by: Geoff Lang <[email protected]> Reviewed-by: Shahbaz Youssefi <[email protected]> Commit-Queue: Ran Wang <[email protected]> --- diff --git a/src/libANGLE/renderer/d3d/d3d11/TextureStorage11.cpp b/src/libANGLE/renderer/d3d/d3d11/TextureStorage11.cpp index d1eed0b..828454a 100644 --- a/src/libANGLE/renderer/d3d/d3d11/TextureStorage11.cpp +++ b/src/libANGLE/renderer/d3d/d3d11/TextureStorage11.cpp @@ -1088,6 +1088,7 @@ angle::Result TextureStorage11_2D::onDestroy(const gl::Context *context) { + angle::Result result = angle::Result::Continue; for (unsigned i = 0; i < gl::IMPLEMENTATION_MAX_TEXTURE_LEVELS; i++) { if (mAssociatedImages[i] != nullptr) @@ -1096,9 +1097,13 @@ // We must let the Images recover their data before we delete it from the // TextureStorage. - ANGLE_TRY(mAssociatedImages[i]->recoverFromAssociatedStorage(context)); + if (IsError(mAssociatedImages[i]->recoverFromAssociatedStorage(context))) + { + result = angle::Result::Stop; + } } } + ANGLE_TRY(result); if (mHasKeyedMutex) { @@ -2213,6 +2218,7 @@ angle::Result TextureStorage11_Cube::onDestroy(const gl::Context *context) { + angle::Result result = angle::Result::Continue; for (unsigned int level = 0; level < gl::IMPLEMENTATION_MAX_TEXTURE_LEVELS; level++) { for (unsigned int face = 0; face < gl::kCubeFaceCount; face++) @@ -2223,12 +2229,15 @@ // We must let the Images recover their data before we delete it from the // TextureStorage. - ANGLE_TRY(mAssociatedImages[face][level]->recoverFromAssociatedStorage(context)); + if (IsError(mAssociatedImages[face][level]->recoverFromAssociatedStorage(context))) + { + result = angle::Result::Stop; + } } } } - return angle::Result::Continue; + return result; } TextureStorage11_Cube::~TextureStorage11_Cube() {} @@ -2900,6 +2909,7 @@ angle::Result TextureStorage11_3D::onDestroy(const gl::Context *context) { + angle::Result result = angle::Result::Continue; for (unsigned i = 0; i < gl::IMPLEMENTATION_MAX_TEXTURE_LEVELS; i++) { if (mAssociatedImages[i] != nullptr) @@ -2908,11 +2918,14 @@ // We must let the Images recover their data before we delete it from the // TextureStorage. - ANGLE_TRY(mAssociatedImages[i]->recoverFromAssociatedStorage(context)); + if (IsError(mAssociatedImages[i]->recoverFromAssociatedStorage(context))) + { + result = angle::Result::Stop; + } } } - return angle::Result::Continue; + return result; } TextureStorage11_3D::~TextureStorage11_3D() {} @@ -3276,6 +3289,7 @@ angle::Result TextureStorage11_2DArray::onDestroy(const gl::Context *context) { + angle::Result result = angle::Result::Continue; for (auto iter : mAssociatedImages) { if (iter.second) @@ -3284,12 +3298,15 @@ // We must let the Images recover their data before we delete it from the // TextureStorage. - ANGLE_TRY(iter.second->recoverFromAssociatedStorage(context)); + if (IsError(iter.second->recoverFromAssociatedStorage(context))) + { + result = angle::Result::Stop; + } } } mAssociatedImages.clear(); - return angle::Result::Continue; + return result; } TextureStorage11_2DArray::~TextureStorage11_2DArray() {}
Original Bug Report
Potential UAF in ANGLE D3D11 due to early exit in TextureStorage11::onDestroy on recovery failure
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: A potential Use-After-Free (UAF) vulnerability exists in ANGLE’s D3D11 backend on Windows due to an early loop termination in the onDestroy method of TextureStorage11 subclasses. If recovering an associated image fails (e.g., during memory pressure), the loop terminates immediately, leaving subsequent sibling images with dangling raw pointers to the deleted storage. These dangling pointers are later dereferenced via a virtual function call during image cleanup, leading to potential arbitrary code execution inside the sandboxed GPU process.
Affected files:
third_party/angle/src/libANGLE/renderer/d3d/d3d11/TextureStorage11.cppthird_party/angle/src/libANGLE/renderer/d3d/d3d11/Image11.cppthird_party/angle/src/libANGLE/renderer/d3d/TextureD3D.cpp
Estimated timestamp from git blame: 2014-08-12
Description
A potential Use-After-Free (UAF) vulnerability has been identified in ANGLE’s D3D11 backend. When a TextureStorage11 subclass is destroyed via onDestroy (e.g., TextureStorage11_2D::onDestroy), it loops through and attempts to recover the data for all associated Image11 instances across different mipmap levels or faces:
// In third_party/angle/src/libANGLE/renderer/d3d/d3d11/TextureStorage11.cpp
angle::Result TextureStorage11_2D::onDestroy(const gl::Context *context) {
for (unsigned i = 0; i < gl::IMPLEMENTATION_MAX_TEXTURE_LEVELS; i++) {
if (mAssociatedImages[i] != nullptr) {
mAssociatedImages[i]->verifyAssociatedStorageValid(this);
ANGLE_TRY(mAssociatedImages[i]->recoverFromAssociatedStorage(context));
}
}
...
}
If recoverFromAssociatedStorage fails (for example, returning E_OUTOFMEMORY during staging texture creation in createStagingTexture), the ANGLE_TRY macro triggers an early exit from onDestroy and propagates the error code. Because the loop terminates immediately, any sibling Image11 instances at subsequent iterations are never visited.
However, the caller in TextureD3D::releaseTexStorage unconditionally deletes the storage object regardless of whether onDestroy succeeded:
// In third_party/angle/src/libANGLE/renderer/d3d/TextureD3D.cpp
auto err = mTexStorage->onDestroy(context);
SafeDelete(mTexStorage);
return err;
Consequently, the unvisited sibling Image11 objects still retain their raw mAssociatedStorage pointers (defined as TextureStorage11 *mAssociatedStorage; in Image11.h) pointing to the now-freed TextureStorage11 instance. When these sibling images are eventually cleaned up during context teardown, Image11::~Image11 calls disassociateStorage(), which attempts a pure-virtual call dispatch on the deleted storage instance:
// In third_party/angle/src/libANGLE/renderer/d3d/d3d11/Image11.cpp
void Image11::disassociateStorage() {
if (mRecoverFromStorage) {
mAssociatedStorage->disassociateImage(mAssociatedImageIndex, this); // Use-After-Free
...
Because ANGLE does not use base::raw_ptr (MiraclePtr) for backend D3D11 structures, this Use-After-Free is not mitigated by BackupRefPtr.
Potential Step-by-Step Trigger Sequence
Note: These are suggested/potential steps derived from static analysis, as our tooling does not currently have the capability to execute code.
- Establish a WebGL context utilizing the ANGLE D3D11 backend on Windows.
- Create a texture and define multiple mip levels (e.g., Level 0 and Level 1), which associates both corresponding
Image11objects with a singleTextureStorage11_2Dinstance. - Induce high GPU memory pressure (e.g., via a parallel WebGL context) to prepare the system for allocation failures.
- Redefine Level 0 of the texture with mismatched dimensions. This triggers
TextureD3D::releaseTexStorage, callingonDestroyon the underlyingTextureStorage11_2D. - During the loop in
onDestroy, processing Level 0 fails to allocate a staging texture insiderecoverFromAssociatedStoragedue toE_OUTOFMEMORYand returns an error. This causesonDestroyto exit early via theANGLE_TRYmacro. - The loop is aborted before processing Level 1.
TextureD3D::releaseTexStorageunconditionally deletes the storage instance, leaving the Level 1Image11holding a dangling raw pointermAssociatedStorageto the deleted storage. - Reclaim the freed storage memory via heap grooming/spraying with controlled data.
- Clean up the WebGL context. The destructor of the Level 1
Image11is called, invokingdisassociateStorage(), which triggers an indirect virtual method call via the controlled vtable pointer.
Suggested Fix
Modify onDestroy in all affected subclasses (such as TextureStorage11_2D, TextureStorage11_Cube, TextureStorage11_3D, and TextureStorage11_2DArray in TextureStorage11.cpp) to avoid terminating the loop early upon error. Instead, run the loop to completion to ensure all associated images are processed/disassociated, and return the accumulated error at the end. For example:
angle::Result TextureStorage11_2D::onDestroy(const gl::Context *context)
{
angle::Result result = angle::Result::Continue;
for (unsigned i = 0; i < gl::IMPLEMENTATION_MAX_TEXTURE_LEVELS; i++)
{
if (mAssociatedImages[i] != nullptr)
{
mAssociatedImages[i]->verifyAssociatedStorageValid(this);
angle::Result err = mAssociatedImages[i]->recoverFromAssociatedStorage(context);
if (err != angle::Result::Continue)
{
result = err;
}
}
}
if (mHasKeyedMutex)
{
mRenderer->getStateManager()->invalidateBoundViews();
}
return result;
}
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.