High watch CVSS 8.3 chrome UAF 📄 Reporter bug report 🔧 Commit mapped

Overview

High
Severity
8.3
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in Graphite
DescriptionUse after free in Graphite
ComponentGraphite
Sub-componentTerracotta-Phase-1
Bug ClassUAF
Action tierwatch — corruption primitive but gated (uncommon) — not default-reachable
Computed by PatchGap's deterministic exploitability ruler from the fixing commit. patch-now = RCE-class & attacker-reachable in a default config, or exploited in the wild; watch = real security signal below that bar; noise = no security relevance.
Tracker496393742
Fix commit3f9969421ad5 (skia) +1/-1
CWECWE-416 (Use-after-free)
CVSS vectorCVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:C/C:H/I:H/A:H
CISA KEVNot listed
CreditedGoogle
Reported2026-03-26 (per vendor advisory)
Fix landed2026-03-27 (public git)
Disclosed2026-04-15
Patch gap19 days — fix public in source before the advisory

Background

Graphite
Skia’s next-generation GPU backend, whose Context object 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 Context is 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

The diff only reorders two member declarations inside the 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.
Key insight
The single mistake is a member-lifetime ordering error: a manager that must outlive the queue was declared so that it would be destroyed first, and the fix simply reorders the declarations so the QueueManager is torn down before the ClientMappedBufferManager it may still reference.

Attack Path

  1. Create and populate a Graphite `Context` An attacker-influenced rendering workload (e.g. via a compromised or malicious renderer driving GPU work) causes the Context to hold a QueueManager with in-flight submissions that reference client-mapped buffers.
  2. Trigger `Context` destruction Tearing down the Context begins destroying members in reverse declaration order, freeing fMappedBufferManager before fQueueManager.
  3. Dangling access during queue teardown fQueueManager’s destructor flushes or releases outstanding work that reaches into the already-destroyed ClientMappedBufferManager, producing the use-after-free (inferred; the speculative diff does not pinpoint the exact dereference).
  4. Exploitation Reclaiming the freed ClientMappedBufferManager allocation before the stale access could yield memory corruption and potentially controlled execution in the GPU process (inferred).

Impact Assessment

An attacker who can drive Graphite GPU work and force 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 coupling
    Audit 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 lifetimes
    Review 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 fixes
    Treat 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.
From 3f9969421ad5d0b544b2013ea1386864589511e5 Mon Sep 17 00:00:00 2001
From: Kalvin Lee <kdlee@chromium.org>
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 <michaelludwig@google.com>
Commit-Queue: Michael Ludwig <michaelludwig@google.com>
Reviewed-by: Thomas Smith <thomsmit@google.com>
---

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;
Loading diff…

Original Bug Report

reported by vm...@google.com

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.h
  • third_party/skia/src/gpu/graphite/Context.cpp
  • third_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:

  1. fMappedBufferManager is destroyed, leaving the raw pointer in the pending callback dangling.
  2. fQueueManager is destroyed. Its destructor (QueueManager::~QueueManager) calls checkForFinishedWork(SyncToCpu::kYes).
  3. checkForFinishedWork waits for the GPU to finish outstanding work by blocking and pumping Dawn WebGPU events (e.g., instance.ProcessEvents()).
  4. Once the work finishes, the QueueManager destroys the GpuWorkSubmission, which unconditionally executes the registered finish callback with a success status.
  5. The callback dereferences the dangling manager pointer, performing a UAF read (manager->ownerID()) and a UAF write (manager->insert()).
  6. manager->insert() calls emplace_front() on a std::forward_list located 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:

  1. Request an asynchronous pixel readback via WebGL/WebGPU to queue the vulnerable callback.
  2. Trigger a GPU context destruction (e.g., by forcing a channel teardown or simulating a context loss).
  3. 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 freed ClientMappedBufferManager.
  4. This predictable, same-thread reallocation window allows the attacker to reliably reclaim the freed memory chunk.
  5. 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

  1. Swap the declaration order in third_party/skia/include/gpu/graphite/Context.h so that fQueueManager is destroyed first:
std::unique_ptr<ClientMappedBufferManager> fMappedBufferManager;
std::unique_ptr<QueueManager> fQueueManager;
  1. Alternatively, implement a mechanism to explicitly cancel or fail pending callbacks during Context teardown before members are destroyed, similar to the abandonContext logic 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.

View on issue tracker