High watch CVSS 7.5 chrome UAF 🔧 Commit mapped

Overview

High
Severity
7.5
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in GPU
DescriptionUse after free in GPU
ComponentGPU
Sub-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.
Tracker513781328
Fix commit9185cf51e895 (chromium/src) +110/-6
CWECWE-416 (Use-after-free)
CVSS vectorCVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:H/A:H
CISA KEVNot listed
CreditedGoogle
Reported2026-05-16 (per vendor advisory)
Fix landed2026-05-27 (public git)
Disclosed2026-06-30
Patch gap34 days — fix public in source before the advisory

Background

dma-buf
a Linux/ChromeOS kernel buffer-sharing file descriptor used to pass GPU memory between processes; its allocation size can be queried with lseek(fd, 0, SEEK_END).
`GpuMemoryBufferHandle` / `NativePixmapHandle`
the IPC-transported description of shared GPU memory, carrying attacker-influenceable fields such as image dimensions, stride, and the backing fd.
`vkGetImageMemoryRequirements`
the Vulkan query that reports how many bytes a created VkImage actually needs for its backing memory.
External memory import
binding an imported dma-buf fd to a VkImage via VkImportMemoryFdInfoKHR and vkAllocateMemory, with an allocationSize derived from the driver’s requirements rather than from the fd’s real size.

Root Cause Analysis

When importing a dma-buf into a Vulkan image on Linux/ChromeOS, the GPU process trusted the attacker-supplied image dimensions from GpuMemoryBufferHandle and allocated/bound device memory sized by the driver’s vkGetImageMemoryRequirements without ever verifying that the imported fd was physically large enough to back that image. The old code path (InitializeSingleOrJointPlanes with a nullptr requirements pointer) imported the fd and bound it directly, so a small dma-buf bound to a large VkImage left the driver addressing memory beyond the fd’s real allocation.

The fix restructures initialization to create the VkImage first, call GetMemoryRequirements(0) to learn the required size, then measure the fd’s physical size with lseek(scoped_fd.get(), 0, SEEK_END); it rejects an un-seekable fd (fd_size < 0) and, when static_cast<uint64_t>(fd_size) < requirements.size, logs, records a crash dump, and (with kVulkanValidateDmabufSize enabled by default) calls Destroy() and returns false before any import. Only after passing validation does it perform AllocateAndBindMemory and, for VK_IMAGE_TILING_LINEAR, query the subresource layout, ensuring an undersized allocation is never bound.

Key insight
The single mistake was binding a Vulkan image to an externally-supplied dma-buf using a driver-derived allocation size without checking the fd’s actual physical size; the fix inserts an explicit lseek(SEEK_END) size check against vkGetImageMemoryRequirements and refuses import when the fd is too small.

Attack Path

  1. Compromise a renderer The attacker gains code execution in a renderer able to send GPU IPC that creates shared images / Vulkan-backed buffers.
  2. Forge an undersized handle They construct a GpuMemoryBufferHandle whose NativePixmapHandle claims large dimensions (e.g. 1024x1024) but whose backing fd is a tiny buffer (e.g. a 4 KB memfd, per the regression test).
  3. Trigger Vulkan import The GPU process calls the external-memory import path, which sizes the allocation from vkGetImageMemoryRequirements for the large image and, pre-patch, binds the small fd without a size check.
  4. Undersized binding The large VkImage is now backed by memory smaller than it needs, so GPU accesses to the image address bytes past the fd’s real allocation.
  5. GPU out-of-bounds read/write Subsequent reads/writes to the image produce GPU-level out-of-bounds accesses in the GPU process, an information leak or corruption primitive (specific downstream primitive inferred).

Impact Assessment

A compromised renderer can cause the GPU process to bind an undersized dma-buf to a large Vulkan image, yielding GPU-level out-of-bounds reads and writes within the GPU process, as the patch description states. Preconditions are Linux/ChromeOS with Vulkan external-memory import and the ability to supply a GpuMemoryBufferHandle with mismatched dimensions and fd size. The advisory classifies this as insufficient validation of untrusted input in GPU; it reports no exploited-in-the-wild status, and escalation beyond the described OOB (e.g. sandbox escape) is (inferred).

Changed Functions

FunctionChangeNotes
if
gpu/vulkan/vulkan_image_linux.cc
modified
TEST_F
gpu/vulkan/vulkan_image_unittest.cc
modified
if
gpu/vulkan/vulkan_image_unittest.cc
modified

Files Changed

  • gpu/vulkan/vulkan_image_linux.cc
  • gpu/vulkan/vulkan_image_unittest.cc

Audit Directions

  • Trusting IPC-supplied buffer geometry
    Audit GPU import paths that size allocations or accesses from renderer-supplied dimensions/stride without cross-checking the backing fd/handle’s real capacity.
  • Missing physical-size validation on shared fds
    Look for dma-buf / shared-memory imports (Vulkan, GL, native pixmap) that bind memory without lseek(SEEK_END) or equivalent size verification against required bytes.
  • Requirement-vs-supply mismatches
    Review code that derives allocationSize from driver requirements while the actual backing comes from an untrusted external source, ensuring the smaller of the two governs safety.
From 9185cf51e895d20790e4b93705c78bd3dd3f35a9 Mon Sep 17 00:00:00 2001
From: Tzarial <zork@google.com>
Date: Wed, 27 May 2026 16:19:03 -0700
Subject: [PATCH] gpu: Validate dma-buf size against driver requirements during Vulkan import

A compromised renderer could cause the GPU process to bind an undersized
dma-buf to a large Vulkan image, leading to GPU-level out-of-bounds
reads and writes. This occurred because the GPU process trusted
attacker-supplied image dimensions during memory allocation without
verifying the actual physical size of the imported file descriptor.

This CL implements a targeted fix in VulkanImage initialization on
Linux/ChromeOS:
1. Create the VkImage and query its actual memory requirements via
   vkGetImageMemoryRequirements before importing external memory.
2. Explicitly verify the physical size of the dma-buf FD using
   lseek(fd, 0, SEEK_END) and reject if it is smaller than the
   driver-reported requirement.
3. Manually perform memory allocation and layout setup steps for
   imported buffers to allow the validation check to occur at the
   correct stage of the Vulkan object lifecycle.

This ensures that the GPU process never binds an undersized memory
allocation to a Vulkan image, effectively mitigating the OOB access
vulnerability while maintaining compatibility with Linux platforms where
GpuMemoryBuffer metadata may be incomplete or unreliable.

Test: vulkan_tests --gtest_filter=VulkanImageTest.RejectUndersizedDmaBuf
Fixed: 513781328
Change-Id: Id079bfb0545728013b862c7a8cfc70f356bc5175
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7857471
Reviewed-by: Zhenyao Mo <zmo@chromium.org>
Commit-Queue: Tzarial <zork@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1637317}
---

diff --git a/gpu/vulkan/vulkan_image_linux.cc b/gpu/vulkan/vulkan_image_linux.cc
index 2ca3580..20f8ada 100644
--- a/gpu/vulkan/vulkan_image_linux.cc
+++ b/gpu/vulkan/vulkan_image_linux.cc
@@ -4,9 +4,14 @@
 
 #include "gpu/vulkan/vulkan_image.h"
 
+#include <sys/types.h>
+#include <unistd.h>
+
 #include <tuple>
 #include <vector>
 
+#include "base/debug/dump_without_crashing.h"
+#include "base/feature_list.h"
 #include "base/logging.h"
 #include "gpu/vulkan/vulkan_device_queue.h"
 #include "gpu/vulkan/vulkan_function_pointers.h"
@@ -14,6 +19,10 @@
 
 namespace gpu {
 
+namespace {
+BASE_FEATURE(kVulkanValidateDmabufSize, base::FEATURE_ENABLED_BY_DEFAULT);
+}  // namespace
+
 //  static
 std::unique_ptr<VulkanImage> VulkanImage::CreateWithExternalMemoryAndModifiers(
     VulkanDeviceQueue* device_queue,
@@ -87,6 +96,42 @@
     external_image_create_info.pNext = &modifier_info;
   }
 
+  device_queue_ = device_queue;
+  disjoint_planes_ = false;
+
+  if (!CreateVkImage(size, format, usage, flags, image_tiling,
+                     &external_image_create_info)) {
+    device_queue_ = nullptr;
+    return false;
+  }
+
+  VkMemoryRequirements requirements = GetMemoryRequirements(0);
+
+  // SECURITY: |gmb_handle| (including |size| and the fd) may originate from an
+  // untrusted process. Verify that the dma-buf is at least large enough to back
+  // an image of |size| before letting vkAllocateMemory import it with an
+  // allocationSize derived from vkGetImageMemoryRequirements().
+  // dma-bufs support lseek(SEEK_END) to report their allocation size; treat an
+  // un-seekable fd as invalid since real dma-bufs always do.
+  off_t fd_size = lseek(scoped_fd.get(), 0, SEEK_END);
+  if (fd_size < 0) {
+    DLOG(ERROR) << "lseek() on dma-buf fd failed.";
+    Destroy();
+    return false;
+  }
+  lseek(scoped_fd.get(), 0, SEEK_SET);
+
+  if (static_cast<uint64_t>(fd_size) < requirements.size) {
+    LOG(ERROR) << "dma-buf (" << fd_size << " bytes) is too small for "
+               << size.width() << "x" << size.height() << " VkImage "
+               << "(requires " << requirements.size << " bytes)";
+    base::debug::DumpWithoutCrashing();
+    if (base::FeatureList::IsEnabled(kVulkanValidateDmabufSize)) {
+      Destroy();
+      return false;
+    }
+  }
+
   int memory_fd = scoped_fd.release();
   VkImportMemoryFdInfoKHR import_memory_fd_info = {
       .sType = VK_STRUCTURE_TYPE_IMPORT_MEMORY_FD_INFO_KHR,
@@ -109,18 +154,36 @@
     import_memory_fd_info.pNext = &export_memory_info;
   }
 
-  VkMemoryRequirements* requirements = nullptr;
-  // TODO support multiple plane
-  auto result = InitializeSingleOrJointPlanes(
-      device_queue, size, format, usage, flags, image_tiling,
-      &external_image_create_info, &import_memory_fd_info, requirements);
+  // Allocate device memory and bind it to the image. On Linux, if
+  // |import_memory_fd_info| is provided, it will import the dma-buf FD.
+  auto result = AllocateAndBindMemory(0, &requirements, &import_memory_fd_info);
   // If vkAllocateMemory() returned successfully, the fd in scoped_fd should be
   // owned by vulkan, otherwise take the ownership of the fd back.
   if (result == kFailedBeforeAllocateMemory) {
     scoped_fd.reset(memory_fd);
   }
 
-  return result;
+  if (result != kSuccess) {
+    Destroy();
+    return false;
+  }
+
+  // Get subresource layout for images with VK_IMAGE_TILING_LINEAR.
+  // For images with VK_IMAGE_TILING_DRM_FORMAT_MODIFIER_EXT, the layout is
+  // initialized in InitializeWithExternalMemoryAndModifiers(). For
+  // VK_IMAGE_TILING_OPTIMAL the layout is not usable and
+  // vkGetImageSubresourceLayout() is illegal.
+  if (image_tiling == VK_IMAGE_TILING_LINEAR) {
+    const VkImageSubresource image_subresource = {
+        .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
+        .mipLevel = 0,
+        .arrayLayer = 0,
+    };
+    vkGetImageSubresourceLayout(device_queue_->GetVulkanDevice(), image_,
+                                &image_subresource, &layouts_[0]);
+  }
+
+  return true;
 }
 
 bool VulkanImage::InitializeWithExternalMemoryAndModifiers(
diff --git a/gpu/vulkan/vulkan_image_unittest.cc b/gpu/vulkan/vulkan_image_unittest.cc
index 64fddbe..89857ad 100644
--- a/gpu/vulkan/vulkan_image_unittest.cc
+++ b/gpu/vulkan/vulkan_image_unittest.cc
@@ -4,7 +4,13 @@
 
 #include "gpu/vulkan/vulkan_image.h"
 
+#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
+#include <sys/mman.h>
+#include <unistd.h>
+#endif
+
 #include "base/logging.h"
+#include "base/test/scoped_feature_list.h"
 #include "build/build_config.h"
 #include "gpu/config/gpu_info_collector.h"
 #include "gpu/config/gpu_test_config.h"
@@ -120,4 +126,39 @@
   }
 }
 
+#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
+TEST_F(VulkanImageTest, RejectUndersizedDmaBuf) {
+  auto* device_queue = GetDeviceQueue();
+  if (!device_queue) {
+    return;
+  }
+
+  // 1. Create a small anonymous file (memfd) to simulate an undersized dma-buf.
+  int fd = memfd_create("undersized_dmabuf", 0);
+  ASSERT_GE(fd, 0);
+  base::ScopedFD scoped_fd(fd);
+  // Give it a tiny size (e.g., 4KB).
+  ASSERT_EQ(ftruncate(fd, 4096), 0);
+
+  // 2. Construct a GpuMemoryBufferHandle claiming it's large (e.g., 1024x1024).
+  constexpr gfx::Size image_size(1024, 1024);
+  const int stride = image_size.width() * 4;  // 4 bytes per pixel
+  const uint64_t size = static_cast<uint64_t>(stride) * image_size.height();
+
+  gfx::NativePixmapHandle native_pixmap_handle;
+  native_pixmap_handle.modifier = 0;  // Linear tiling
+  native_pixmap_handle.planes.emplace_back(stride, 0, size,
+                                           base::ScopedFD(dup(fd)));
+
+  gfx::GpuMemoryBufferHandle gmb_handle(std::move(native_pixmap_handle));
+
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/gpu/vulkan/vulkan_image_unittest.cc b/gpu/vulkan/vulkan_image_unittest.cc
index 64fddbe..89857ad 100644
--- a/gpu/vulkan/vulkan_image_unittest.cc
+++ b/gpu/vulkan/vulkan_image_unittest.cc
@@ -4,7 +4,13 @@
 
 #include "gpu/vulkan/vulkan_image.h"
 
+#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
+#include <sys/mman.h>
+#include <unistd.h>
+#endif
+
 #include "base/logging.h"
+#include "base/test/scoped_feature_list.h"
 #include "build/build_config.h"
 #include "gpu/config/gpu_info_collector.h"
 #include "gpu/config/gpu_test_config.h"
@@ -120,4 +126,39 @@
   }
 }
 
+#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
+TEST_F(VulkanImageTest, RejectUndersizedDmaBuf) {
+  auto* device_queue = GetDeviceQueue();
+  if (!device_queue) {
+    return;
+  }
+
+  // 1. Create a small anonymous file (memfd) to simulate an undersized dma-buf.
+  int fd = memfd_create("undersized_dmabuf", 0);
+  ASSERT_GE(fd, 0);
+  base::ScopedFD scoped_fd(fd);
+  // Give it a tiny size (e.g., 4KB).
+  ASSERT_EQ(ftruncate(fd, 4096), 0);
+
+  // 2. Construct a GpuMemoryBufferHandle claiming it's large (e.g., 1024x1024).
+  constexpr gfx::Size image_size(1024, 1024);
+  const int stride = image_size.width() * 4;  // 4 bytes per pixel
+  const uint64_t size = static_cast<uint64_t>(stride) * image_size.height();
+
+  gfx::NativePixmapHandle native_pixmap_handle;
+  native_pixmap_handle.modifier = 0;  // Linear tiling
+  native_pixmap_handle.planes.emplace_back(stride, 0, size,
+                                           base::ScopedFD(dup(fd)));
+
+  gfx::GpuMemoryBufferHandle gmb_handle(std::move(native_pixmap_handle));
+
+  // 3. Verify that InitializeFromGpuMemoryBufferHandle rejects it.
+  auto image = VulkanImage::CreateFromGpuMemoryBufferHandle(
+      device_queue, std::move(gmb_handle), image_size, VK_FORMAT_R8G8B8A8_UNORM,
+      VK_IMAGE_USAGE_SAMPLED_BIT, 0, VK_IMAGE_TILING_LINEAR,
+      VK_QUEUE_FAMILY_IGNORED);
+  EXPECT_FALSE(image);
+}
+#endif
+
 }  // namespace gpu
Loading diff…

Original Bug Report

The reporter's bug is still restricted on the tracker. Chrome de-restricts security bugs ~30–90 days after the fix ships; a later run will backfill it here.