Overview

Critical
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in Skia
DescriptionUse after free in Skia
ComponentSkia
Bug ClassUAF
Tracker520514458
Fix commit6a4be3addd0a (skia) +14/-8
CISA KEVNot listed
CreditedGoogle
Disclosed2026-07-29

Files Changed

  • src/gpu/graphite/dawn/DawnGraphicsPipeline.cpp
From 6a4be3addd0a6e271e68a6a1bb873877e24813e9 Mon Sep 17 00:00:00 2001
From: Michael Ludwig <michaelludwig@google.com>
Date: Fri, 12 Jun 2026 11:24:39 -0400
Subject: [PATCH] [graphite] Use stable collection for static bindings

Since the layouts are passed by pointer in the `nextInChain` field,
their addresses need to stay valid until the BindGroupLayout is created.
With vector, if it ever grew, that would not remain the case. Since
there are usually only 0 to 1 immutable samplers, this likely never
happened (and also why it uses a built-in storage for 1).

Also removes the include for vector and uses TArray (we had been mixing
both throughout the file).

Bug: 520514458
Fixed: 520514458
Change-Id: I44c9566c68ab0e6c6d659ea77a423ddc50d53c76
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/1264157
Reviewed-by: Thomas Smith <thomsmit@google.com>
Auto-Submit: Michael Ludwig <michaelludwig@google.com>
Commit-Queue: Michael Ludwig <michaelludwig@google.com>
---

diff --git a/src/gpu/graphite/dawn/DawnGraphicsPipeline.cpp b/src/gpu/graphite/dawn/DawnGraphicsPipeline.cpp
index d5f6f61..2d771ef 100644
--- a/src/gpu/graphite/dawn/DawnGraphicsPipeline.cpp
+++ b/src/gpu/graphite/dawn/DawnGraphicsPipeline.cpp
@@ -10,7 +10,8 @@
 #include "include/gpu/graphite/TextureInfo.h"
 #include "include/gpu/graphite/dawn/DawnGraphiteTypes.h"
 #include "include/private/SkLog.h"
-#include "include/private/SkTemplates.h"
+#include "include/private/SkTArray.h"
+#include "src/base/SkTBlockList.h"
 #include "src/core/SkTraceEvent.h"
 #include "src/gpu/SkSLToBackend.h"
 #include "src/gpu/Swizzle.h"
@@ -34,7 +35,8 @@
 #include "src/sksl/ir/SkSLProgram.h"
 
 #include <atomic>
-#include <vector>
+
+using namespace skia_private;
 
 namespace skgpu::graphite {
 
@@ -152,7 +154,7 @@
 
 size_t create_vertex_attributes(SkSpan<const Attribute> attrs,
                                 int shaderLocationOffset,
-                                std::vector<wgpu::VertexAttribute>* out) {
+                                TArray<wgpu::VertexAttribute>* out) {
     SkASSERT(out && out->empty());
     out->resize(attrs.size());
     size_t vertexAttributeOffset = 0;
@@ -499,12 +501,16 @@
                 !(samplerDescArrPtr && samplerDescArrPtr->at(0).isImmutable())) {
                 groupLayouts[1] = sharedContext->getSingleTextureSamplerBindGroupLayout();
             } else {
-                std::vector<wgpu::BindGroupLayoutEntry> entries(numTexturesAndSamplers);
+                TArray<wgpu::BindGroupLayoutEntry> entries;
+                entries.reset(numTexturesAndSamplers);
+
 #if !defined(__EMSCRIPTEN__)
                 // Static sampler layouts are passed into Dawn by address and therefore must stay
                 // alive until the BindGroupLayoutDescriptor is created. So, store them outside of
-                // the loop that iterates over each BindGroupLayoutEntry.
-                skia_private::TArray<wgpu::StaticSamplerBindingLayout> staticSamplerLayouts;
+                // the loop that iterates over each BindGroupLayoutEntry. Use a TBlockList so that
+                // any append StaticSamplerBindingLayouts have a stable address in case we have to
+                // grow the collection.
+                SkTBlockList<wgpu::StaticSamplerBindingLayout, 1> staticSamplerLayouts;
 
                 // Note that the number of samplers is equivalent to numTexturesAndSamplers / 2. So,
                 // a sampler's index within any container that only pertains to sampler information
@@ -598,7 +604,7 @@
     // Vertex state
     std::array<wgpu::VertexBufferLayout, kNumVertexBuffers> vertexBufferLayouts;
     // Static data buffer layout
-    std::vector<wgpu::VertexAttribute> staticDataAttributes;
+    TArray<wgpu::VertexAttribute> staticDataAttributes;
     {
         auto arrayStride = create_vertex_attributes(step->staticAttributes(),
                                                     0,
@@ -622,7 +628,7 @@
     }
 
     // Append data buffer layout
-    std::vector<wgpu::VertexAttribute> appendDataAttributes;
+    TArray<wgpu::VertexAttribute> appendDataAttributes;
     {
         // Note: the shaderLocationOffset in this function call needs to be the staticAttributeSize
         auto arrayStride = create_vertex_attributes(step->appendAttributes(),
Loading diff…

Original Bug Report

reported by vm...@google.com

Potential Heap Use-After-Free in DawnGraphicsPipeline due to staticSamplerLayouts reallocation

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 potential heap use-after-free vulnerability exists in Skia’s Graphite Dawn backend (DawnGraphicsPipeline.cpp) due to the reallocation of a TArray holding static sampler layouts while pointers to its elements are still held in a descriptor chain. When the layout is subsequently processed during bind group layout creation, the deallocated memory is traversed, potentially leading to a GPU process crash or code execution.

Affected files:

  • third_party/skia/src/gpu/graphite/dawn/DawnGraphicsPipeline.cpp

Estimated timestamp from git blame: 2024-10-03

Description

A potential heap use-after-free (UAF) vulnerability has been identified in DawnGraphicsPipeline::Make() within Skia’s Graphite Dawn backend (third_party/skia/src/gpu/graphite/dawn/DawnGraphicsPipeline.cpp).

The issue stems from the lifecycle management of skia_private::TArray<wgpu::StaticSamplerBindingLayout> staticSamplerLayouts during the creation of a bind group layout.

Detailed Analysis

In DawnGraphicsPipeline::Make(), a local vector is declared to hold static sampler layouts:

skia_private::TArray<wgpu::StaticSamplerBindingLayout> staticSamplerLayouts;

As the loop iterates over textures and samplers, if a sampler is immutable, a layout is added to the array and its address is saved into the stack-allocated entries array:

wgpu::StaticSamplerBindingLayout& immutableSamplerBinding =
        staticSamplerLayouts.emplace_back();
...
entries[i].nextInChain = &immutableSamplerBinding;

Because staticSamplerLayouts is default-constructed with a capacity of 0 and is not pre-reserved, appending elements can trigger a vector reallocation. When TArray::emplace_back() exhausts its capacity, it reallocates the underlying buffer via growAndConstructAtEnd() -> installDataAndUpdateCapacity(), move-constructs elements to the new buffer, and frees the old buffer via sk_free().

If a reallocation occurs, any pointers already stored in entries[*].nextInChain pointing to the previous array storage become dangling.

Later, Dawn processes these entries inside device.CreateBindGroupLayout(&groupLayoutDesc). In release builds, DeviceBase::CreateBindGroupLayout calls ConvertAndExpandBGLEntries(), which invokes the generated Unpack<BindGroupLayoutEntry>() function. This function traverses the linked list starting at the dangling nextInChain pointer.

If an attacker is able to reclaim and populate the freed heap memory before the unpack traversal, they could control the structure’s sType and sampler pointer. When StaticSamplerBindingInfo::From() is called, it constructs a Ref<SamplerBase>{layout.sampler}, which increments and decrements the reference count at an attacker-controlled offset and potentially triggers a virtual method call on destruction, leading to arbitrary code execution within the GPU process.

Triggering Preconditions

On 64-bit systems, the first allocation of staticSamplerLayouts is rounded up to a minimum capacity of 8 elements (256 bytes, fitting PartitionAlloc’s 256-byte bucket). Thus, a pipeline containing 9 or more immutable (Vulkan YCbCr) samplers is required to trigger a reallocation. Under normal operation, immutable samplers are typically produced only for textures backed by a YCbCr descriptor (such as Android AHardwareBuffer SharedImages).

Potential Steps to Reproduce (Theoretical)

Note: These are potential steps based on source code analysis; our tools do not currently have the capability to run code or verify this dynamically.

  1. From a compromised renderer, serialize OOP-R paint commands that reference 9 or more distinct, unique AHardwareBuffer-backed YCbCr images in a single paint shader/filter tree.
  2. Ensure the resulting pipeline description is unique to prevent hitting the pipeline cache.
  3. Once the raster command is executed, the GPU process calls DawnGraphicsPipeline::Make(). On the 9th immutable sampler, staticSamplerLayouts reallocates, leaving dangling pointers in entries[0..7].nextInChain.
  4. Concurrently, spray the PartitionAlloc 256-byte bucket to reclaim the freed block and overwrite its contents with forged layout descriptions.
  5. When device.CreateBindGroupLayout() parses the entries, it traverses the forged chained layouts, performing refcount increments and virtual calls.

Suggested Fix

To resolve this issue, reserve sufficient capacity in staticSamplerLayouts prior to entering the loop. Since the maximum possible number of static samplers is bounded by numTexturesAndSamplers / 2, pre-allocating or reserving this capacity prevents any subsequent reallocation:

skia_private::TArray<wgpu::StaticSamplerBindingLayout> staticSamplerLayouts;
staticSamplerLayouts.reserve(numTexturesAndSamplers / 2);

Evaluated with Chrome root at commit: e9507a33bb4148ee071aaaf8a7e9ad68770359bf


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