High chrome UAF 🔧 Commit mapped

Overview

High
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in GPU
DescriptionUse after free in GPU
ComponentGPU
Bug ClassUAF
Tracker513781328
Fix commit9185cf51e895 (chromium/src) +110/-6
CISA KEVNot listed
CreditedGoogle
Disclosed2026-06-30

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
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.