Overview

Critical
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in Skia
DescriptionUse after free in Skia
ComponentSkia
Bug ClassUAF
Tracker513972075
Fix commit3471ebf5af0c (skia) +3/-0
CISA KEVNot listed
CreditedGoogle
Disclosed2026-05-27

Changed Functions

FunctionChangeNotes
if
src/gpu/ganesh/vk/GrVkPipelineStateBuilder.cpp
modified

Files Changed

  • src/gpu/ganesh/vk/GrVkPipelineStateBuilder.cpp
From 3471ebf5af0cc82c45df7e2c60ef40a4d5de27f5 Mon Sep 17 00:00:00 2001
From: Greg Daniel <[email protected]>
Date: Mon, 18 May 2026 14:53:08 +0000
Subject: [PATCH] Null out VkShaderModule handles when destroying them.

Bug: b/513972075
Change-Id: Ieeaf92c87343aa328479302d9cffeb36b5b0c8e4
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/1238159
Auto-Submit: Greg Daniel <[email protected]>
Reviewed-by: Robert Phillips <[email protected]>
---

diff --git a/src/gpu/ganesh/vk/GrVkPipelineStateBuilder.cpp b/src/gpu/ganesh/vk/GrVkPipelineStateBuilder.cpp
index 5012b5e..9f4bc32 100644
--- a/src/gpu/ganesh/vk/GrVkPipelineStateBuilder.cpp
+++ b/src/gpu/ganesh/vk/GrVkPipelineStateBuilder.cpp
@@ -151,6 +151,7 @@
             if (outShaderModules[i]) {
                 GR_VK_CALL(fGpu->vkInterface(),
                            DestroyShaderModule(fGpu->device(), outShaderModules[i], nullptr));
+                outShaderModules[i] = VK_NULL_HANDLE;
             }
         }
         return 0;
@@ -282,6 +283,7 @@
                 if (shaderModules[i]) {
                     GR_VK_CALL(fGpu->vkInterface(), DestroyShaderModule(fGpu->device(),
                                                                         shaderModules[i], nullptr));
+                    shaderModules[i] = VK_NULL_HANDLE;
                 }
             }
             return nullptr;
@@ -357,6 +359,7 @@
         if (shaderModules[i]) {
             GR_VK_CALL(fGpu->vkInterface(), DestroyShaderModule(fGpu->device(), shaderModules[i],
                                                                 nullptr));
+            shaderModules[i] = VK_NULL_HANDLE;
         }
     }
 
Loading diff…

Original Bug Report

reported by [email protected]

Potential double destruction of VkShaderModule in Skia Vulkan backend

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 double-free vulnerability exists in the Skia Ganesh Vulkan backend when creating pipeline states. Stale VkShaderModule handles are not nulled after destruction during a failed cache-load attempt, which can lead to a second destruction if a subsequent from-source retry also fails.

Affected files:

  • third_party/skia/src/gpu/ganesh/vk/GrVkPipelineStateBuilder.cpp
  • third_party/skia/src/gpu/ganesh/vk/GrVkUtil.cpp

Estimated timestamp from git blame: 2020-06-10

Summary

A potential double-free vulnerability has been identified in third_party/skia/src/gpu/ganesh/vk/GrVkPipelineStateBuilder.cpp. The vulnerability occurs when the system fails to nullify stack-allocated Vulkan shader module handles after they are destroyed during a failed shader loading attempt. If a subsequent retry also fails, the same handles are destroyed a second time, leading to driver-side heap corruption in the GPU process.

Root Cause Analysis

In GrVkPipelineStateBuilder::finalize, a stack-allocated array shaderModules is used to store VkShaderModule handles:

// third_party/skia/src/gpu/ganesh/vk/GrVkPipelineStateBuilder.cpp:187
VkShaderModule shaderModules[kGrShaderTypeCount] = { VK_NULL_HANDLE, VK_NULL_HANDLE };

The function first attempts to load shaders from the persistent cache via loadShadersFromCache. If this function partially succeeds (e.g., installs one stage) but then fails on a subsequent stage, it enters a cleanup loop to destroy any successfully created modules:

// third_party/skia/src/gpu/ganesh/vk/GrVkPipelineStateBuilder.cpp:149
if (!success) {
    for (int i = 0; i < kGrShaderTypeCount; ++i) {
        if (outShaderModules[i]) {
            GR_VK_CALL(fGpu->vkInterface(),
                       DestroyShaderModule(fGpu->device(), outShaderModules[i], nullptr));
        }
    }
    return 0;
}

Crucially, the handles in outShaderModules (which point to the caller’s shaderModules array) are destroyed but not reset to VK_NULL_HANDLE.

When loadShadersFromCache returns 0, finalize proceeds to a “retry from sources” path. If this retry path also fails—for instance, if createVkShaderModule returns false due to an SkSL compilation error or memory pressure before it can overwrite the stale handle—finalize executes its own final cleanup loop:

// third_party/skia/src/gpu/ganesh/vk/GrVkPipelineStateBuilder.cpp:280
if (!success) {
    for (int i = 0; i < kGrShaderTypeCount; ++i) {
        if (shaderModules[i]) {
            GR_VK_CALL(fGpu->vkInterface(), DestroyShaderModule(fGpu->device(),
                                                                shaderModules[i], nullptr));
        }
    }
    return nullptr;
}

Because the stale handles from the first attempt were never nulled, the second cleanup loop calls vkDestroyShaderModule on a handle that has already been freed. In many Vulkan implementations, vkCreateShaderModule does not modify the output handle on failure, ensuring the stale handle persists and is processed again.

Potential Attack Vector

An attacker in a compromised renderer process could potentially trigger this vulnerability via Out-of-Process Rasterization (OOP-R). By providing specially crafted draw calls or paint commands that result in complex SkSL, the attacker could attempt to induce a failure in the Vulkan shader compilation or module creation path (e.g., via forced memory pressure or specific compilation errors). If the first attempt (cache load) partially installs shaders and fails, and the second attempt (from source) also fails, the double-free will be triggered.

Impact

This issue leads to a driver-heap double-free in the GPU process. Since Vulkan driver handles are involved, this is not protected by MiraclePtr. On platforms like Android, the GPU process is typically unsandboxed and shares the browser’s UID, making this a high-severity path for privilege escalation or remote code execution.

Suggested Fix

The cleanup loops in both loadShadersFromCache and finalize should be modified to nullify the handles immediately after destruction:

if (shaderModules[i]) {
    GR_VK_CALL(fGpu->vkInterface(), DestroyShaderModule(fGpu->device(), shaderModules[i], nullptr));
    shaderModules[i] = VK_NULL_HANDLE;
}

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