CVE-2026-6304
Overview
Background
- Graphite
- Skia’s next-generation GPU backend, whose
Contextobject owns the long-lived GPU service objects used to record and submit rendering work. - Member destruction order
- In C++, a class’s non-static data members are destroyed in the reverse of their declaration order, so the member declared last is destroyed first.
- `QueueManager`
- The Graphite component that owns and flushes outstanding GPU command submissions when the
Contextis torn down. - `ClientMappedBufferManager`
- The Graphite component that tracks client-visible mapped GPU buffers, which in-flight queue work may still reference during teardown.
Root Cause Analysis
Context class in include/gpu/graphite/Context.h: std::unique_ptr<QueueManager> fQueueManager and std::unique_ptr<ClientMappedBufferManager> fMappedBufferManager are swapped so that fMappedBufferManager is now declared before fQueueManager. Because members are destroyed in reverse declaration order, the pre-patch layout destroyed fMappedBufferManager first and then fQueueManager; if fQueueManager’s destruction (for example, draining or flushing outstanding submissions) touched the ClientMappedBufferManager, it would be operating on an object that was already freed, i.e. a use-after-free during Context teardown. After the patch fQueueManager is destroyed first, so any mapped-buffer references it holds are released while fMappedBufferManager is still alive, restoring the required lifetime ordering. The commit message explicitly labels this a speculative reorder pending further investigation in the bug, so the exact dereference is not demonstrated in the diff itself. No function body was modified—only the declaration order that governs the implicit ~Context destructor sequence.QueueManager is torn down before the ClientMappedBufferManager it may still reference.Attack Path
- Create and populate a Graphite `Context`
An attacker-influenced rendering workload (e.g. via a compromised or malicious renderer driving GPU work) causes the
Contextto hold aQueueManagerwith in-flight submissions that reference client-mapped buffers. - Trigger `Context` destruction
Tearing down the
Contextbegins destroying members in reverse declaration order, freeingfMappedBufferManagerbeforefQueueManager. - Dangling access during queue teardown
fQueueManager’s destructor flushes or releases outstanding work that reaches into the already-destroyedClientMappedBufferManager, producing the use-after-free (inferred; the speculative diff does not pinpoint the exact dereference). - Exploitation
Reclaiming the freed
ClientMappedBufferManagerallocation before the stale access could yield memory corruption and potentially controlled execution in the GPU process (inferred).
Impact Assessment
Context teardown gains a use-after-free in the process hosting the GPU backend, with the preconditions being control over rendering workloads and the ability to trigger destruction with outstanding queue state. The vendor advisory rates this high severity and describes it as a use after free in Graphite; no exploited-in-the-wild status is indicated. Any escalation to code execution or sandbox compromise is (inferred) and not demonstrated by this speculative reorder patch.Files Changed
include/gpu/graphite/Context.h
Audit Directions
- Destruction-order couplingAudit classes whose destructors (or members’ destructors) reach into sibling members; verify declaration order so any object that references another is declared after it and thus destroyed first.
- Owning-manager lifetimesReview Graphite/Skia components that flush or drain in-flight work on teardown (
QueueManager, resource and buffer managers) for assumptions that their collaborators are still alive during that flush. - Speculative/partial fixesTreat commits marked “speculative” as incomplete; re-audit the bug for the precise dereference and for sibling reorderings that may hide the same class of latent lifetime bug.
Patch
From 3f9969421ad5d0b544b2013ea1386864589511e5 Mon Sep 17 00:00:00 2001 From: Kalvin Lee <[email protected]> Date: Fri, 27 Mar 2026 16:37:30 +0900 Subject: [PATCH] Terracotta-Phase-1: Reorder member destruction This is a speculative patch. Please see the bug for details. Bug: b/496393742 Change-Id: Ib574a0086f92abda83715b36a0d1e7a99e9edd67 Reviewed-on: https://skia-review.googlesource.com/c/skia/+/1196676 Reviewed-by: Michael Ludwig <[email protected]> Commit-Queue: Michael Ludwig <[email protected]> Reviewed-by: Thomas Smith <[email protected]> --- diff --git a/include/gpu/graphite/Context.h b/include/gpu/graphite/Context.h index 84ec802..d81e3fe 100644 --- a/include/gpu/graphite/Context.h +++ b/include/gpu/graphite/Context.h @@ -406,8 +406,8 @@ sk_sp<SharedContext> fSharedContext; std::unique_ptr<ResourceProvider> fResourceProvider; - std::unique_ptr<QueueManager> fQueueManager; std::unique_ptr<ClientMappedBufferManager> fMappedBufferManager; + std::unique_ptr<QueueManager> fQueueManager; std::unique_ptr<const skcpu::ContextImpl> fCPUContext; PersistentPipelineStorage* fPersistentPipelineStorage;
Original Bug Report
Potential Use-After-Free in Skia Graphite Context via Member Destruction Order
Project Fortify, an experimental security project, has identified the following potential security issue.
Overview: A potential Use-After-Free vulnerability exists in Skia’s Graphite backend during Context destruction. Because fMappedBufferManager is destroyed before fQueueManager, pending asynchronous readback callbacks executed during QueueManager teardown access the freed manager. This could provide an attacker with a reliable heap overwrite primitive in the GPU process.
Affected files:
third_party/skia/include/gpu/graphite/Context.hthird_party/skia/src/gpu/graphite/Context.cppthird_party/skia/src/gpu/graphite/QueueManager.cpp
Estimated timestamp from git blame: 2023-11-27
Summary
A potential Use-After-Free (UAF) vulnerability exists in the skgpu::graphite::Context class within Skia’s Graphite backend. The root cause is a C++ member destruction order issue where fMappedBufferManager is destroyed before fQueueManager. Pending GPU callbacks are executed during fQueueManager’s destruction, leading them to access the already-freed fMappedBufferManager.
Root Cause Analysis
In third_party/skia/include/gpu/graphite/Context.h, the members are declared as follows:
std::unique_ptr<QueueManager> fQueueManager;
std::unique_ptr<ClientMappedBufferManager> fMappedBufferManager;
According to the C++ standard, class members are destroyed in the reverse order of their declaration. Therefore, when Context::~Context() executes, fMappedBufferManager is destroyed and its memory is freed before fQueueManager is destroyed.
When a client requests an asynchronous pixel readback (e.g., via Context::finalizeAsyncReadPixels), a callback is created and registered with the QueueManager. This callback stores a raw pointer to fMappedBufferManager.get().
During context teardown, the following sequence occurs:
fMappedBufferManageris destroyed, leaving the raw pointer in the pending callback dangling.fQueueManageris destroyed. Its destructor (QueueManager::~QueueManager) callscheckForFinishedWork(SyncToCpu::kYes).checkForFinishedWorkwaits for the GPU to finish outstanding work by blocking and pumping Dawn WebGPU events (e.g.,instance.ProcessEvents()).- Once the work finishes, the
QueueManagerdestroys theGpuWorkSubmission, which unconditionally executes the registered finish callback with a success status. - The callback dereferences the dangling manager pointer, performing a UAF read (
manager->ownerID()) and a UAF write (manager->insert()). manager->insert()callsemplace_front()on astd::forward_listlocated inside the freed object, writing a new heap node pointer directly into the freed memory.
Potential Attacker Steps
Note: These are theoretical steps, as our setup does not have the ability to run code or construct a working proof-of-concept.
An attacker with control over a compromised Renderer process could potentially exploit this to achieve a Renderer-to-GPU sandbox escape:
- Request an asynchronous pixel readback via WebGL/WebGPU to queue the vulnerable callback.
- Trigger a GPU context destruction (e.g., by forcing a channel teardown or simulating a context loss).
- During the
QueueManager’s synchronous wait loop, Dawn events are pumped. The attacker can queue Dawn IPC tasks immediately prior to destruction to allocate memory of the exact same size as the freedClientMappedBufferManager. - This predictable, same-thread reallocation window allows the attacker to reliably reclaim the freed memory chunk.
- When the callback fires, the
emplace_front()call writes a valid heap pointer into the attacker-controlled reclaimed memory, providing a powerful primitive to hijack control flow or corrupt further GPU process state.
MiraclePtr (BRP) does not mitigate this vulnerability because Skia utilizes un-rewritten raw pointers for these internal components.
Proposed Fix
- Swap the declaration order in
third_party/skia/include/gpu/graphite/Context.hso thatfQueueManageris destroyed first:
std::unique_ptr<ClientMappedBufferManager> fMappedBufferManager;
std::unique_ptr<QueueManager> fQueueManager;
- Alternatively, implement a mechanism to explicitly cancel or fail pending callbacks during
Contextteardown before members are destroyed, similar to theabandonContextlogic utilized in Skia’s Ganesh backend.
Evaluated with Chrome root at commit: 0eb4855bda702feaaa8b899336664f97e3df88b8
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. Please feel free to reach out to me if you have concerns or feedback.