High watch ~ plausible 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 Skia
DescriptionUse after free in Skia
ComponentSkia
Sub-componentGraphite
Bug ClassUAF
Action tierwatch — corruption primitive but gated (uncommon) — not default-reachable — factors set by independent review by AI
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.
Independent review by AIPlausible
Reviewer notes: Citations accurate; diff/pre-fix prove only the asymmetric missing reset on the mapping-failure path. The actual buffer freeing (onFailedBuffer) and dangling-pointer dereference (command submission) are not in the shown material, so the UAF itself is not provable here.
A second, independent AI reviewer (not a human) re-derived this analysis from the fixing commit and the vendor advisory, looking for errors.
Tracker514063977
Fix commitd93793dfc1fe (skia) +2/-0
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-05-17 (per vendor advisory)
Fix landed2026-05-19 (public git)
Disclosed2026-05-27
Patch gap8 days — fix public in source before the advisory

Background

`DrawList::snapDrawPass`
converts recorded Graphite draws into an immutable DrawPass, writing uniform/vertex data through a DrawBufferManager (src/gpu/graphite/DrawList.cpp:90).
`DrawBufferManager` / `hasMappingFailed()`
the GPU buffer allocator whose hasMappingFailed() reports that a buffer allocation or mapping failed during the snap (src/gpu/graphite/DrawList.cpp:223).
`DrawList::reset`
clears the draw list’s accumulated per-snap state and delegates to DrawListBase::reset (src/gpu/graphite/DrawList.cpp:247).

Root Cause Analysis

Verify before building a PoC

This analysis is consistent with the fixing commit but not fully provable from it: part of the vulnerable path lives in code the fix commit does not touch. Confirm the trigger and the sink against the source before relying on it for exploit development.
What the commit does not establish: Citations accurate; diff/pre-fix prove only the asymmetric missing reset on the mapping-failure path. The actual buffer freeing (onFailedBuffer) and dangling-pointer dereference (command submission) are not in the shown material, so the UAF itself is not provable here.

During snapDrawPass, each sort key calls uniformTracker.writeUniforms(fUniformDataCache, bufferMgr, key.uniformIndex()) and then renderStep.writeVertices(...) (src/gpu/graphite/DrawList.cpp:161-162, 221), after which the loop checks if (bufferMgr->hasMappingFailed()) (DrawList.cpp:223). On success the function finishes and calls this->reset(LoadOp::kLoad) at the end (DrawList.cpp:242), clearing the accumulated state before the DrawList is reused. On the failure branch, however, the pre-fix code logged and immediately return nullptr; (DrawList.cpp:224-225) without calling reset(), so the draw list retained its populated caches even though the buffers written during the aborted snap had been dropped. The same shape exists in the experimental DrawListLayer::snapDrawPass, whose recordDraw lambda returns false on hasMappingFailed() (src/gpu/graphite/DrawListLayer.cpp:492-494) while the success path resets at the end (DrawListLayer.cpp:538). The invariant — a failed snap must leave the DrawList as clean as a successful one so a reused list holds no references to freed buffers — was violated only on the error path.

The fix adds this->reset(LoadOp::kLoad); immediately before return nullptr; (diff DrawList.cpp @@ -222) and before return false; (diff DrawListLayer.cpp @@ -491), restoring that invariant.

Key insight
The successful path reset the draw list but the mapping-failure early return did not, leaving stale cached buffer references for the next reuse; the fix calls reset(LoadOp::kLoad) on the failure path too.

Attack Path

  1. Populate caches Issue Graphite draws so the DrawList accumulates uniform data and buffer references (src/gpu/graphite/DrawList.cpp:161-162).
  2. Induce a mapping failure Cause bufferMgr->hasMappingFailed() to become true during a snap, e.g. under GPU memory pressure (DrawList.cpp:223). (inferred: the memory-pressure trigger is the reporter’s claim)
  3. Abort without cleanup snapDrawPass returns nullptr while skipping reset(), leaving the caches referencing dropped buffers (DrawList.cpp:224-225).
  4. Reuse the list A later snap on the same DrawList records the stale buffer references into a command list. (inferred: subsequent dereference in command submission is not shown in the material)

Impact Assessment

Pre-fix, a snap that failed on buffer mapping left the Graphite DrawList holding references tied to the aborted attempt, which the advisory describes as leading to a use-after-free in the GPU process on reuse. The advisory rates this high severity with no in-the-wild exploitation; the escalation to code execution and the unsandboxed-GPU-process framing are reporter claims not shown in the code (inferred).

Files Changed

  • src/gpu/graphite/DrawList.cpp
  • src/gpu/graphite/DrawListLayer.cpp

Audit Directions

  • Asymmetric cleanup on error paths
    Compare success and early-return paths of any function that mutates reusable state; the error path must release or reset the same resources.
  • Reusable accumulators after partial failure
    When an object is reused across operations, verify a failed operation cannot leave dangling references in its caches.
  • Buffer-mapping failure handling
    Audit every hasMappingFailed()/allocation-failure branch to confirm it both bails out and restores a safe object state.
From d93793dfc1fec512d8bd130b8c4738ed48071f9d Mon Sep 17 00:00:00 2001
From: Thomas Smith <thomsmit@google.com>
Date: Mon, 18 May 2026 13:04:35 -0400
Subject: [PATCH] [graphite] ensure drawlist resources are cleared on failure

Bug: b/514063977
Change-Id: I03ccdf8224f6d6413c4632adf1d75216d8093e81
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/1238257
Commit-Queue: Thomas Smith <thomsmit@google.com>
Reviewed-by: Kaylee Lubick <kjlubick@google.com>
Reviewed-by: Michael Ludwig <michaelludwig@google.com>
---

diff --git a/src/gpu/graphite/DrawList.cpp b/src/gpu/graphite/DrawList.cpp
index 3d3d016..5112ccd 100644
--- a/src/gpu/graphite/DrawList.cpp
+++ b/src/gpu/graphite/DrawList.cpp
@@ -222,6 +222,7 @@
 
         if (bufferMgr->hasMappingFailed()) {
             SKGPU_LOG_W("Failed to write necessary vertex/instance data for DrawPass, dropping!");
+            this->reset(LoadOp::kLoad);
             return nullptr;
         }
 
diff --git a/src/gpu/graphite/DrawListLayer.cpp b/src/gpu/graphite/DrawListLayer.cpp
index ca983b6..6d1c692 100644
--- a/src/gpu/graphite/DrawListLayer.cpp
+++ b/src/gpu/graphite/DrawListLayer.cpp
@@ -491,6 +491,7 @@
 
         if (bufferMgr->hasMappingFailed()) {
             SKGPU_LOG_W("Failed to write necessary vertex/instance data for DrawPass, dropping!");
+            this->reset(LoadOp::kLoad);
             return false;
         }
 
Loading diff…

Original Bug Report

reported by vm...@google.com

Potential GPU Process UAF in Skia Graphite via Stale Cache Pointers after Allocation 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 state management flaw in Skia’s Graphite engine allows stale raw pointers to persist in the UniformDataCache following a buffer allocation failure. If a DrawContext is reused after a failed “snap” attempt, these dangling pointers are recorded into command lists and dereferenced in the GPU process, leading to a potential Use-After-Free.

Affected files:

  • third_party/skia/src/gpu/graphite/DrawList.cpp
  • third_party/skia/src/gpu/graphite/DrawListLayer.cpp
  • third_party/skia/src/gpu/graphite/DrawListBase.h
  • third_party/skia/src/gpu/graphite/BufferManager.cpp
  • third_party/skia/src/gpu/graphite/PipelineData.h
  • third_party/skia/src/gpu/graphite/ResourceTypes.h
  • third_party/skia/src/gpu/graphite/vk/VulkanCommandBuffer.cpp

Estimated timestamp from git blame: 2025-10-03

Summary

A potential Use-After-Free (UAF) vulnerability exists in Skia’s Graphite rendering engine due to improper handling of error paths during DrawPass generation. When a transient buffer allocation or mapping failure occurs, raw pointers to freed buffers are retained in a persistent cache and subsequently dereferenced in the GPU process during command submission.

Root Cause Analysis

In skgpu::graphite::DrawList::snapDrawPass (and the experimental DrawListLayer::snapDrawPass), the engine converts recorded draw commands into an immutable DrawPass. This process involves transitioning uniform data from CPU memory to GPU-backed buffers using a UniformTracker helper.

During this transition, the UniformTracker allocates space via the DrawBufferManager. If an allocation or mapping failure occurs (typically under high GPU memory pressure), DrawBufferManager::onFailedBuffer() is invoked. This function clears fUsedBuffers, dropping the only owning sk_sp<Buffer> references for all buffers allocated during the current snap attempt, causing them to be destroyed or recycled.

However, snapDrawPass detects the failure and early-returns nullptr without invoking this->reset(). This is problematic because the DrawList’s fUniformDataCache is a persistent member that is only cleared during reset(). Consequently, the cache remains populated with UniformDataCache::Entry objects that store stale raw const Buffer* pointers to the now-freed buffers.

On backends using the Uniform Buffer Object (UBO) path (such as Vulkan, where storage buffers are currently disabled by default in Graphite), the UniformTracker::writeUniforms logic checks if (!uniformData.fBufferBinding.fBuffer || ...) to decide whether to upload data. Because the stale pointer is non-null, the system incorrectly assumes the data is already successfully uploaded and skips the re-upload. It then records the stale buffer pointer into the DrawPass command list.

Potential Exploitation Path

While this analysis is based on manual code review and has not been verified with a functional proof-of-concept, an attacker controlling a renderer process could potentially trigger this issue through the following steps:

  1. Issue a series of draw calls to a Graphite-backed surface to populate the UniformDataCache.
  2. Induce GPU memory pressure such that a subsequent buffer mapping request fails during the snapDrawPass operation.
  3. Ensure the snapDrawPass fails and returns nullptr. At this point, the cache contains dangling pointers.
  4. Release memory pressure and issue further draws (or trigger another flush) using the same DrawContext.
  5. The subsequent successful snapDrawPass will include commands referencing the dangling buffer pointers.
  6. During command submission in the GPU process (e.g., in VulkanCommandBuffer::bindUniformBuffers), the stale pointer is dereferenced to retrieve descriptor sets or bind the buffer, resulting in a Use-After-Free.

Impact

This results in a UAF in the GPU process. On platforms such as Android, the GPU process is typically unsandboxed, making this a high-severity risk as it could lead to memory corruption or potential code execution outside of the renderer’s sandbox.

Suggested Fix

Ensure that DrawList::reset() (or a specific cache-clearing mechanism) is invoked in all error paths within snapDrawPass where a partial upload may have occurred before a failure. This ensures that a failed snap attempt does not leave the DrawList in an inconsistent and dangerous state.

Evaluated with Chrome root at commit: 1a8d40fc44df2088d5945c0bf53584038aa1614a


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.

View on issue tracker