High watch 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 GPU
DescriptionUse after free in GPU
ComponentGPU
Bug ClassUAF
Action tierwatch — corruption primitive but gated (uncommon) — not default-reachable
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.
Tracker513044017
Fix commit7247631764e0 (chromium/src) +15/-20
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-14 (per vendor advisory)
Fix landed2026-07-17 (public git)
Disclosed2026-08-06
Patch gap20 days — fix public in source before the advisory

Background

`SkiaOutputSurfaceImplOnGpu`
the GPU-process object that drives Skia-based compositing output, including creating Vulkan release fences that signal when the compositor is done with a resource.
`VkSemaphore` / `GrBackendSemaphore`
a Vulkan synchronization primitive; GrBackendSemaphores::GetVkSemaphore() extracts the raw VkSemaphore handle from Skia’s GrBackendSemaphore wrapper.
`CreateReleaseFenceForVulkan`
a helper that turns a semaphore into an exportable gfx::GpuFenceHandle via GetSemaphoreHandle(), previously also owning the semaphore’s lifetime on the failure path.
`VulkanFenceHelper`
the canonical mechanism for deferring VkSemaphore destruction until the GPU has finished using it, avoiding destruction of an in-use handle.
`pending_release_fence_cbs_`
a deque of (GrBackendSemaphore, callback) items processed to emit release fences back to callers.

Root Cause Analysis

In the loop that drains pending_release_fence_cbs_, the old code called CreateReleaseFenceForVulkan(item.first), and inside that helper, when GetSemaphoreHandle() failed, it called vkDestroySemaphore(device, ...) directly to destroy the raw VkSemaphore and returned an invalid handle. However, the same item.first semaphore was (on the success path) also pushed into the semaphores vector via GrBackendSemaphores::GetVkSemaphore(item.first), meaning the handle was tracked/used elsewhere for destruction or signaling; destroying it immediately and inconsistently created a lifetime mismatch where a VkSemaphore could be destroyed while still referenced, a use-after-free of the Vulkan handle.

The fix restructures the loop to first extract the VkSemaphore, and only if it is non-VK_NULL_HANDLE push it into semaphores and then attempt CreateReleaseFenceForVulkan(semaphore); the helper is simplified to take a raw VkSemaphore, no longer performs the null check or the direct vkDestroySemaphore on failure, and simply returns whatever handle GetSemaphoreHandle() produced. Because the helper no longer destroys the semaphore, all destruction is now funneled through the normal path (VulkanFenceHelper, per the commit message), eliminating the divergent success/fail lifetime handling. The invariant restored is that a VkSemaphore is destroyed exactly once, through the deferred fence helper, never eagerly inside the fence-creation helper.

Key insight
The single mistake was destroying the VkSemaphore eagerly with vkDestroySemaphore inside CreateReleaseFenceForVulkan’s failure branch while the same handle was also being tracked/used by the caller, causing double/early destruction; the fix removes that eager destruction so the semaphore’s lifetime is always managed by the standard VulkanFenceHelper path.

Attack Path

  1. Trigger Vulkan release-fence path A renderer drives compositing so that resources need Vulkan release fences, populating pending_release_fence_cbs_ with semaphores in the GPU process.
  2. Force fence-export failure Conditions cause GetSemaphoreHandle() to fail so the old failure branch runs vkDestroySemaphore on the semaphore. (inferred that the failure branch is reachable under attacker-influenceable conditions.)
  3. Inconsistent lifetime The same VkSemaphore remains referenced by the success-path bookkeeping / normal destruction path, so the handle is destroyed while still tracked, producing a use-after-free of the Vulkan semaphore object.
  4. Post-fix outcome After the patch the helper never destroys the semaphore; destruction flows solely through VulkanFenceHelper, so the handle is freed exactly once.

Impact Assessment

An attacker able to influence the Vulkan release-fence path in the GPU process could cause a VkSemaphore to be freed while still referenced, a use-after-free within the GPU process. The advisory classifies this as use-after-free in GPU and states no exploited-in-the-wild status; escalation to controlled corruption or code execution from this UAF is (inferred) and not demonstrated by the diff.

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

Audit Directions

  • Divergent success/failure lifetime
    Look for helpers that destroy a resource on their error path while callers also track/destroy the same handle; unify ownership so a resource is freed in exactly one place.
  • Eager vs. deferred GPU-object destruction
    Audit Vulkan/GL object teardown for direct vkDestroy* calls that bypass deferred helpers like VulkanFenceHelper, which can destroy handles still in use by the GPU.
  • Raw handle sharing
    Review paths where a raw VkSemaphore (or similar) is extracted into more than one container/consumer, ensuring only one owner is responsible for destruction.
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