Overview

High
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in GPU
DescriptionUse after free in GPU
ComponentGPU
Bug ClassUAF
Tracker513044017
Fix commit7247631764e0 (chromium/src) +15/-20
CISA KEVNot listed
CreditedGoogle
Disclosed2026-08-06

Changed Functions

FunctionChangeNotes
if
components/viz/service/display_embedder/skia_output_surface_impl_on_gpu.cc
modified

Files Changed

  • components/viz/service/display_embedder/skia_output_surface_impl_on_gpu.cc
  • components/viz/service/display_embedder/skia_output_surface_impl_on_gpu.h
From 7247631764e0dc6dd1ec83912b0e05081b25bddc Mon Sep 17 00:00:00 2001
From: Vasiliy Telezhnikov <vasilyt@chromium.org>
Date: Fri, 17 Jul 2026 06:15:18 -0700
Subject: [PATCH] Fix release fence vkSemaphore destruction

There is no reason to handle fail case and success case differently, we
should always use VulkanFenceHelper.

Bug: 513044017
Change-Id: I00c0f359494fa81d129396a0ed9fa9070d831e59
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8102907
Reviewed-by: Saifuddin Hitawala <hitawala@chromium.org>
Commit-Queue: Vasiliy Telezhnikov <vasilyt@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1663840}
---

diff --git a/components/viz/service/display_embedder/skia_output_surface_impl_on_gpu.cc b/components/viz/service/display_embedder/skia_output_surface_impl_on_gpu.cc
index 14cb0d6d8..d8485e6 100644
--- a/components/viz/service/display_embedder/skia_output_surface_impl_on_gpu.cc
+++ b/components/viz/service/display_embedder/skia_output_surface_impl_on_gpu.cc
@@ -2335,13 +2335,20 @@
   semaphores.reserve(pending_release_fence_cbs_.size());
 
   while (!pending_release_fence_cbs_.empty()) {
+    gfx::GpuFenceHandle release_fence;
+
     auto& item = pending_release_fence_cbs_.front();
-    auto release_fence = CreateReleaseFenceForVulkan(item.first);
-    if (release_fence.is_null()) {
-      LOG(ERROR) << "Unable to create a release fence for Vulkan.";
-    } else {
-      semaphores.emplace_back(GrBackendSemaphores::GetVkSemaphore(item.first));
+    VkSemaphore semaphore = GrBackendSemaphores::GetVkSemaphore(item.first);
+
+    if (semaphore != VK_NULL_HANDLE) {
+      semaphores.emplace_back(semaphore);
+
+      release_fence = CreateReleaseFenceForVulkan(semaphore);
+      if (release_fence.is_null()) {
+        LOG(ERROR) << "Unable to create a release fence for Vulkan.";
+      }
     }
+
     std::move(item.second).Run(std::move(release_fence));
     pending_release_fence_cbs_.pop_front();
   }
@@ -2607,25 +2614,14 @@
 
 #if BUILDFLAG(ENABLE_VULKAN)
 gfx::GpuFenceHandle SkiaOutputSurfaceImplOnGpu::CreateReleaseFenceForVulkan(
-    const GrBackendSemaphore& semaphore) {
+    VkSemaphore semaphore) {
   DCHECK(is_using_vulkan());
 
-  if (GrBackendSemaphores::GetVkSemaphore(semaphore) == VK_NULL_HANDLE) {
-    return {};
-  }
-
   auto* implementation = vulkan_context_provider_->GetVulkanImplementation();
   VkDevice device =
       vulkan_context_provider_->GetDeviceQueue()->GetVulkanDevice();
 
-  auto handle = implementation->GetSemaphoreHandle(
-      device, GrBackendSemaphores::GetVkSemaphore(semaphore));
-  if (!handle.is_valid()) {
-    vkDestroySemaphore(device, GrBackendSemaphores::GetVkSemaphore(semaphore),
-                       /*pAllocator=*/nullptr);
-    LOG(ERROR) << "Failed to create a release fence for Vulkan.";
-    return {};
-  }
+  auto handle = implementation->GetSemaphoreHandle(device, semaphore);
   return std::move(handle).ToGpuFenceHandle();
 }
 
diff --git a/components/viz/service/display_embedder/skia_output_surface_impl_on_gpu.h b/components/viz/service/display_embedder/skia_output_surface_impl_on_gpu.h
index efaad72e9..5f5430b2 100644
--- a/components/viz/service/display_embedder/skia_output_surface_impl_on_gpu.h
+++ b/components/viz/service/display_embedder/skia_output_surface_impl_on_gpu.h
@@ -462,8 +462,7 @@
   // by CreateAndStoreExternalSemaphoreVulkan(). May destroy VkSemaphore that
   // the |semaphore| stores if creation of a release fence fails. In this case,
   // invalid fence handle is returned.
-  gfx::GpuFenceHandle CreateReleaseFenceForVulkan(
-      const GrBackendSemaphore& semaphore);
+  gfx::GpuFenceHandle CreateReleaseFenceForVulkan(VkSemaphore semaphore);
   // Returns true if succeess.
   bool CreateAndStoreExternalSemaphoreVulkan(
       std::vector<GrBackendSemaphore>& end_semaphores);
Loading diff…

Original Bug Report

reported by vm...@google.com

Use-After-Free in GPU Process via Premature vkDestroySemaphore in Viz

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 without the Chrome Security team. Please see https://chromium.googlesource.com/chromium/src/+/main/docs/security/ai-generated-security-bugs-faq.md for more information.

Overview: A potential Use-After-Free (UAF) vulnerability exists in the Viz display compositor when using Vulkan. If exporting a Vulkan semaphore fails after it has been submitted to the GPU, the code immediately destroys the semaphore while it is still in flight. This violation of the Vulkan specification can lead to driver-level memory corruption in the GPU process.

Affected files:

  • components/viz/service/display_embedder/skia_output_surface_impl_on_gpu.cc
  • components/viz/service/display_embedder/skia_output_device.cc
  • gpu/vulkan/vulkan_util_posix.cc

Estimated timestamp from git blame: Unknown (Google3 checkout)

Technical Description

A potential Use-After-Free vulnerability has been identified in SkiaOutputSurfaceImplOnGpu::CreateReleaseFenceForVulkan within the Viz display compositor. The issue stems from a violation of Vulkan validation rule VUID-vkDestroySemaphore-semaphore-05149, which states that all submitted batches referring to a semaphore must have completed execution before the semaphore is destroyed.

In the Viz Ganesh-on-Vulkan path, when a frame contains a resource requiring a release fence (kReleaseFence), a VkSemaphore is created, associated with the current Skia flush, and submitted to the GPU queue. Immediately after submission, Viz attempts to export this semaphore as a file descriptor (FD) to create a gfx::GpuFenceHandle.

If the export operation (vkGetSemaphoreFdKHR) fails—for example, due to file descriptor exhaustion in the GPU process—the code incorrectly performs an immediate destruction of the semaphore:

// components/viz/service/display_embedder/skia_output_surface_impl_on_gpu.cc
gfx::GpuFenceHandle SkiaOutputSurfaceImplOnGpu::CreateReleaseFenceForVulkan(
    const GrBackendSemaphore& semaphore) {
  // ...
  auto handle = implementation->GetSemaphoreHandle(
      device, GrBackendSemaphores::GetVkSemaphore(semaphore));
  if (!handle.is_valid()) {
    // POTENTIAL VULNERABILITY: Immediate destruction of an in-flight semaphore.
    vkDestroySemaphore(device, GrBackendSemaphores::GetVkSemaphore(semaphore),
                       /*pAllocator=*/nullptr);
    LOG(ERROR) << "Failed to create a release fence for Vulkan.";
    return {};
  }
  return std::move(handle).ToGpuFenceHandle();
}

Because the semaphore was submitted to the GPU queue during the Submit() call preceding PostSubmit() (which calls CreateReleaseFenceForVulkan), the semaphore is still active on the GPU. Immediate destruction leads to a Use-After-Free when the GPU later attempts to signal the destroyed object.

Potential Attack Vector

A compromised renderer could potentially trigger this vulnerability through the following steps:

  1. Resource Exhaustion: The renderer exhausts file descriptors in the GPU process by repeatedly requesting the creation of native-buffer-backed SharedImages (e.g., via CreateSharedImage with SCANOUT usage).
  2. Malicious Frame Submission: The renderer submits a CompositorFrame containing a TransferableResource with synchronization_type set to kReleaseFence.
  3. Triggering the Error Path: When Viz processes this frame, it creates a Vulkan semaphore and submits it to the GPU. The subsequent attempt to export this semaphore to an FD fails due to the previously induced FD exhaustion.
  4. UAF Execution: The error path in CreateReleaseFenceForVulkan executes, destroying the semaphore. The GPU then encounters the UAF when attempting to signal the now-freed semaphore memory.

Impact

Successful exploitation could lead to arbitrary memory corruption within the GPU process. On platforms like Android, the GPU process often operates with reduced sandboxing compared to the renderer (relying on OS-level isolation but lacking additional Chromium-specific sandbox layers by default), potentially allowing for privilege escalation from a compromised renderer to the system level.

Suggested Fix

Instead of immediately calling vkDestroySemaphore on the failure path in CreateReleaseFenceForVulkan, the semaphore should be added to the cleanup queue to ensure its destruction is deferred until the associated GPU work has completed. This can be achieved by ensuring the semaphore is always included in the semaphores vector in PostSubmit, which is subsequently passed to VulkanFenceHelper::EnqueueSemaphoresCleanupForSubmittedWork.

Evaluated with Chrome root at commit: b3153093eb3c78c3e88ccf562bcbc20437a04b0e


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