CVE-2026-10914
Overview
Files Changed
src/libANGLE/renderer/d3d/d3d11/Buffer11.h
Patch
From cad8fd6da0f03b667943e12939e6ea4669b09868 Mon Sep 17 00:00:00 2001 From: Geoff Lang <[email protected]> Date: Wed, 15 Apr 2026 14:40:20 -0400 Subject: [PATCH] D3D11: Use uint64_t for buffer storage LRU count The 32-bit counter has a chance of wrapping around with intentional churn of the cache. Increase this to 64 bits which should be an infeasible number of allocations. Fixed: chromium:497574371 Change-Id: If8e50445515fe1a0231ca4c57644614a49e811c1 Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/7765257 Reviewed-by: Shahbaz Youssefi <[email protected]> --- diff --git a/src/libANGLE/renderer/d3d/d3d11/Buffer11.h b/src/libANGLE/renderer/d3d/d3d11/Buffer11.h index 620f96f..4055a42 100644 --- a/src/libANGLE/renderer/d3d/d3d11/Buffer11.h +++ b/src/libANGLE/renderer/d3d/d3d11/Buffer11.h @@ -152,7 +152,7 @@ BufferCacheEntry() : storage(nullptr), lruCount(0) {} BufferStorage *storage; - unsigned int lruCount; + uint64_t lruCount; }; struct StructuredBufferKey
Original Bug Report
Potential Use-After-Free in ANGLE Buffer11 due to LRU counter wraparound
Project Fortify, an experimental security project, has identified the following potential security issue.
Overview: A 32-bit LRU counter in ANGLE’s D3D11 Buffer11 cache can wrap around after 2^32 operations, causing newly created buffer storage objects to be incorrectly identified as the least recently used. This leads to the new buffer being synchronously deleted and immediately accessed, resulting in a Use-After-Free (UAF) and a reliable GPU process crash.
Affected files:
third_party/angle/src/libANGLE/renderer/d3d/d3d11/Buffer11.cppthird_party/angle/src/libANGLE/renderer/d3d/d3d11/Buffer11.h
Estimated timestamp from git blame: 2019-12-20
Description
There is a potential Use-After-Free (UAF) vulnerability in the ANGLE D3D11 backend’s Buffer11 implementation. The functions getStructuredBufferRangeSRV and getConstantBufferRangeStorage in Buffer11.cpp maintain caches of storage objects using a Least Recently Used (LRU) policy. To track usage, they rely on 32-bit unsigned integer counters (mMaxStructuredBufferLruCount and mMaxConstantBufferLruCount).
When a cache’s memory budget is exceeded, an eviction loop uses std::min_element to find and delete the entry with the smallest lruCount.
Because the counter is 32-bit, it can wrap around to 0 after 2^32 operations. When an attacker intentionally wraps this counter and then allocates a new buffer that triggers an eviction, the newly allocated buffer is given a very small lruCount (e.g., 1). The std::min_element logic incorrectly identifies this newly created buffer as the “least recently used” because its wrapped lruCount is smaller than the lruCount of older entries (which might be near 0xFFFFFFFF).
An assertion intended to prevent self-eviction (ASSERT(iter->second.storage != newStorage)) is compiled out in production Release builds. Consequently, SafeDelete is called on the newly created object, freeing it. The code then synchronously continues to use the dangling pointer (e.g., calling resizeStructuredBuffer or updateBufferStorage), resulting in a UAF.
Potential Steps to Trigger
Note: These are suggested steps based on static analysis, as our tooling cannot execute dynamic Proof-of-Concepts.
- Setup: An attacker hosts a malicious WebGL 2 page.
- Counter Wraparound: The JavaScript executes a tight loop of WebGL draw calls that bind structured or constant buffers. This is repeated exactly 2^32 times to silently overflow the 32-bit unsigned
lruCountback to0. - Eviction Trigger: The attacker then binds a new, previously unseen buffer range, requesting a size large enough to exceed the cache’s maximum allowed memory budget (
2 * getSize()). - Flawed Eviction:
Buffer11allocates the newStructuredBufferStorageobject and assigns it a wrapped, smalllruCount(e.g.,1). The cache eviction loop triggers andstd::min_elementselects this newly created object for deletion. - Synchronous UAF:
SafeDeletefrees the object. Immediately after, the code callsstructuredBufferStorage->resizeStructuredBuffer(...)on the dangling stack pointer.
Impact
Because the freed memory is accessed synchronously within the exact same C++ function block, an attacker cannot pause execution to interleave JavaScript and spray the GPU process heap. Therefore, this UAF cannot be trivially exploited for Remote Code Execution (RCE).
Instead, accessing the freed memory immediately triggers a crash. If PartitionAlloc’s BackupRefPtr (MiraclePtr) or quarantine is enabled, the poisoned memory will safely cause an access violation. Otherwise, it will cause a double-free or null pointer dereference when the inner smart pointers (rx::Resource11) attempt to release their inner COM objects. This results in a reliable Denial of Service (DoS) of the GPU process.
Suggested Fix
- Increase Counter Width: Change the types of
mMaxStructuredBufferLruCountandmMaxConstantBufferLruCountinBuffer11.hfromunsigned inttouint64_t. A 64-bit counter will realistically never overflow during a single browser session. - Robust Eviction Check: Replace the
ASSERTwith a hardifcheck to ensure the newly creatednewStorageis never considered for eviction, or use a proper LRU data structure (likebase::LRUCache) instead of manual counter tracking.
Evaluated with Chrome root at commit: a9cbf6e8b275fe4147435aa905f3b7f5a656f5f0
Results from 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.