Overview

High
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in Skia
DescriptionUse after free in Skia
ComponentSkia
Bug ClassUAF
Tracker514063977
Fix commitd93793dfc1fe (skia) +2/-0
CISA KEVNot listed
CreditedGoogle
Disclosed2026-05-27

Files Changed

  • src/gpu/graphite/DrawList.cpp
  • src/gpu/graphite/DrawListLayer.cpp
From d93793dfc1fec512d8bd130b8c4738ed48071f9d Mon Sep 17 00:00:00 2001
From: Thomas Smith <[email protected]>
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 <[email protected]>
Reviewed-by: Kaylee Lubick <[email protected]>
Reviewed-by: Michael Ludwig <[email protected]>
---

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 [email protected]

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