CVE-2026-9878
Overview
Files Changed
src/libANGLE/Program.cpp
Patch
From 648b530b4470442fdc10b392aa3008dc61e9a7fd Mon Sep 17 00:00:00 2001 From: Shahbaz Youssefi <[email protected]> Date: Wed, 08 Apr 2026 11:22:25 -0400 Subject: [PATCH] Vulkan: Wait for post-link tasks even if link fails The post-link tasks are automatically scheduled at the end of link, if it succeeds. So initially, the idea was that if the link fails, there are no post-link tasks. However, it was necessary to defer some tasks to the main thread (with access to Context), which are done in "getResult()" after waiting for the link tasks to finish. This creates a situation where post-link tasks are posted but link may still fail. This is a theoretical fix for when those functions in "getResult()" fail. This is practically never, as it requires OOM to happen at exactly that moment, and so is untestable without a fault-injection infrastructure. Bug: chromium:499054245 Change-Id: I3890026bbdf2d8987d5bc4fc1bebf2950592c5a7 Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/7736777 Commit-Queue: Shahbaz Youssefi <[email protected]> Reviewed-by: Geoff Lang <[email protected]> --- diff --git a/src/libANGLE/Program.cpp b/src/libANGLE/Program.cpp index ad1f558..add1e25 100644 --- a/src/libANGLE/Program.cpp +++ b/src/libANGLE/Program.cpp @@ -1256,6 +1256,7 @@ // The above means that it's ok for ANGLE to reset the executable here, but it *may* be // helpful to applications if it doesn't. We do reset it however, the info log should // already have enough debug information for the application. + waitForPostLinkTasks(context); mState.mExecutable->reset(); return; }
Original Bug Report
GPU Process UAF: Unsynchronized background tasks during ANGLE link 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 without the security team.
Overview: A Use-After-Free (UAF) vulnerability exists in ANGLE’s Vulkan backend due to improper cleanup of background pipeline warm-up tasks. If a shader link fails, the frontend clears its tracking list of background tasks without waiting for them to finish, causing the backend to skip synchronization during program destruction. An orphaned background task will later dereference the freed ProgramExecutableVk object, potentially allowing for arbitrary memory writes and RCE in the GPU process.
Affected files:
third_party/angle/src/libANGLE/renderer/vulkan/ProgramExecutableVk.cppthird_party/angle/src/libANGLE/ProgramExecutable.cppthird_party/angle/src/libANGLE/Program.cppthird_party/angle/src/libANGLE/renderer/vulkan/ProgramVk.cppthird_party/angle/src/common/WorkerThread.cpp
Estimated timestamp from git blame: 2025-04-19
Summary
A potential Use-After-Free (UAF) vulnerability in the ANGLE Vulkan backend allows for arbitrary memory writes and code execution in the GPU process. The issue stems from a failure to synchronize background ‘warm-up’ tasks when a shader program link fails. This results in worker threads holding raw pointers to a ProgramExecutableVk object that has been subsequently destroyed.
Technical Details
When a program link is initiated in ANGLE, the Vulkan backend schedules asynchronous WarmUpGraphicsTask objects to pre-populate the pipeline cache (ProgramExecutableVk.cpp:938). These tasks are designed to run in parallel on a worker thread pool and capture a raw this pointer to the ProgramExecutableVk instance (ProgramExecutableVk.cpp:446). The frontend schedules these tasks and stores waitable events in mState.mExecutable->mPostLinkSubTasks (Program.cpp:586).
If the link process fails after these tasks have been scheduled (for example, due to an Out-Of-Memory error or unsynchronized race condition in initializeDescriptorPools during LinkTaskVk::getResult()), the frontend’s Program::resolveLinkImpl observes the failure and cleans up by calling mState.mExecutable->reset() (Program.cpp:1259).
This reset() call explicitly clears the vectors tracking the background tasks:
// third_party/angle/src/libANGLE/ProgramExecutable.cpp
void ProgramExecutable::reset()
{
// ...
mPostLinkSubTasks.clear();
mPostLinkSubTaskWaitableEvents.clear();
}
Crucially, this clearing happens without blocking or waiting for the background thread pool to finish executing the tasks. The tasks are thus orphaned.
When a subsequent link or destruction of the program occurs, ANGLE attempts to prevent UAFs in ProgramExecutableVk::resetLayout() by calling waitForPostLinkTasksImpl(). However, this safety check is ineffective because it relies on the frontend’s task vector, which was just cleared:
// third_party/angle/src/libANGLE/renderer/vulkan/ProgramExecutableVk.cpp
void ProgramExecutableVk::waitForPostLinkTasksImpl(ContextVk *contextVk)
{
const std::vector<std::shared_ptr<rx::LinkSubTask>> &postLinkSubTasks =
mExecutable->getPostLinkSubTasks();
if (postLinkSubTasks.empty())
{
return; // Returns immediately because the vector was cleared by reset()
}
// ...
}
Consequently, the ProgramExecutableVk object is freed while a WarmUpGraphicsTask is still running or queued in the background worker pool.
Potential Exploitation Steps
(Note: These are suggested steps based on static analysis; a working Proof of Concept has not yet been executed.)
- The attacker triggers a shader link via WebGL/WebGPU, causing ANGLE to schedule
WarmUpGraphicsTaskbackground tasks. - The attacker induces a link failure on the main thread (e.g., via memory pressure or a race condition on
MetaDescriptorPool), causing the frontend to clear the post-link task vectors and orphan the background tasks. - The attacker deletes the shader program, freeing the
ProgramExecutableVkobject (~16KB allocation). - The attacker reclaims the freed 16KB memory chunk using another WebGL API, such as
glProgramBinary, allocating a controlled buffer of the exact same size. - The attacker populates this buffer with a forged
ProgramExecutableVkstructure, carefully controlling internal fields, such asmCompleteGraphicsPipelines(an array ofstd::unordered_mapobjects). - The orphaned
WarmUpGraphicsTaskeventually executes in the background. It callsmExecutableVk->warmUpGraphicsPipelineCache(...), dereferencing the attacker-controlled memory. - The task attempts to insert a pipeline into the forged
std::unordered_map. By manipulating the internal pointers of the map, the attacker achieves an arbitrary memory write primitive. - The attacker uses the arbitrary write to overwrite function pointers or virtual tables, leading to Remote Code Execution (RCE) in the GPU process.
Suggested Fix
In ProgramExecutable::reset(), wait for the post-link tasks to complete before clearing the mPostLinkSubTasks and mPostLinkSubTaskWaitableEvents vectors. Alternatively, ensure that ProgramExecutableVk::destroy() explicitly cancels or waits on the worker pool tasks regardless of the state of the frontend vectors.
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.