Medium chrome Type Confusion 📄 Reporter bug report 🔧 Commit mapped

Overview

Medium
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactType Confusion in GPU
DescriptionType Confusion in GPU
ComponentGPU
Bug ClassType Confusion
Tracker498834967
Fix commit1f7736e2f3be (chromium/src) +44/-0
CISA KEVNot listed
CreditedGoogle
Disclosed2026-06-02

Changed Functions

FunctionChangeNotes
TEST_F
gpu/command_buffer/service/shared_image/compound_image_backing_unittest.cc
modified

Files Changed

  • gpu/command_buffer/service/shared_image/compound_image_backing.cc
  • gpu/command_buffer/service/shared_image/compound_image_backing_unittest.cc
From 1f7736e2f3be118a7732bb78416e24b2f1ad785d Mon Sep 17 00:00:00 2001
From: vikas soni <vikassoni@chromium.org>
Date: Tue, 07 Apr 2026 14:19:09 -0700
Subject: [PATCH] [GPU] Guard against type confusion in CompoundImageBacking.

When kUseCompoundImageBackingAsDefault is on, WrapExternalBacking wraps
arbitrary backing types and gives them AccessStreamSet::All() (including
kMemory). GetShmElement() then returns that wrapped backing regardless
of its actual type. This CL adds a CHECK to guard against potential type
confusion in GetSharedMemoryPixmaps().

Bug: 498834967
Change-Id: I43c8a306d154d8569240f5c2a88aa006b25bd928
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7728969
Reviewed-by: Vasiliy Telezhnikov <vasilyt@chromium.org>
Commit-Queue: Vikas Soni <vikassoni@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1610988}
---

diff --git a/gpu/command_buffer/service/shared_image/compound_image_backing.cc b/gpu/command_buffer/service/shared_image/compound_image_backing.cc
index fce45e5..abea87d 100644
--- a/gpu/command_buffer/service/shared_image/compound_image_backing.cc
+++ b/gpu/command_buffer/service/shared_image/compound_image_backing.cc
@@ -1602,6 +1602,15 @@
   auto* shm_backing = GetShmElement().GetBacking();
   DCHECK(shm_backing);
 
+  // SECURITY: When kUseCompoundImageBackingAsDefault is on, WrapExternalBacking
+  // wraps arbitrary backing types and gives them AccessStreamSet::All()
+  // (including kMemory). GetShmElement() then returns that wrapped backing
+  // here regardless of its actual type. Guard against the resulting type
+  // confusion in the static_cast below. Note marking a backing to support all
+  // access stream is expected behavior wheres ::GetSharedMemoryPixmaps should
+  // only be invoked on SharedImageBackingType::kSharedMemory currently.
+  CHECK_EQ(shm_backing->GetType(), SharedImageBackingType::kSharedMemory);
+
   return static_cast<SharedMemoryImageBacking*>(shm_backing)->pixmaps();
 }
 
diff --git a/gpu/command_buffer/service/shared_image/compound_image_backing_unittest.cc b/gpu/command_buffer/service/shared_image/compound_image_backing_unittest.cc
index 4b91671..c0edd97 100644
--- a/gpu/command_buffer/service/shared_image/compound_image_backing_unittest.cc
+++ b/gpu/command_buffer/service/shared_image/compound_image_backing_unittest.cc
@@ -161,6 +161,23 @@
     return false;
   }
 
+  // Construct a CompoundImageBacking via the WrapExternalBacking constructor
+  // (private). This mirrors CompoundImageBacking::WrapExternalBacking exactly,
+  // minus the SharedImageFactory consultation.
+  std::unique_ptr<CompoundImageBacking> WrapExternal(
+      std::unique_ptr<SharedImageBacking> backing) {
+    backing->SetNotRefCounted();
+    return std::unique_ptr<CompoundImageBacking>(new CompoundImageBacking(
+        /*is_thread_safe=*/false,
+        /*buffer_usage=*/std::nullopt, std::move(backing), copy_manager_,
+        /*shared_image_factory=*/base::WeakPtr<SharedImageFactory>()));
+  }
+
+  const std::vector<SkPixmap>& CallGetSharedMemoryPixmaps(
+      CompoundImageBacking* backing) {
+    return backing->GetSharedMemoryPixmaps();
+  }
+
   // Create a compound backing containing shared memory + GPU backing.
   std::unique_ptr<SharedImageBacking> CreateCompoundBacking(
       SharedImageUsageSet usage) {
@@ -442,6 +459,24 @@
   EXPECT_FALSE(HasGpuCreateBackingCallback(compound_backing));
 }
 
+TEST_F(CompoundImageBackingTest,
+       GetSharedMemoryPixmaps_ChecksOnWrongBackingType) {
+  auto tiny = std::make_unique<TestImageBacking>(
+      Mailbox::Generate(), viz::SinglePlaneFormat::kRGBA_8888,
+      gfx::Size(10, 10), gfx::ColorSpace(), kTopLeft_GrSurfaceOrigin,
+      kOpaque_SkAlphaType,
+      SharedImageUsageSet({SHARED_IMAGE_USAGE_DISPLAY_READ}), kTestBackingSize);
+
+  // WrapExternalBacking constructor sets elements_[0].access_streams =
+  // AccessStreamSet::All(), which includes kMemory. GetSharedMemoryPixmaps()
+  // then performs an unchecked static_cast to SharedMemoryImageBacking*.
+  auto compound = WrapExternal(std::move(tiny));
+
+  // Verify that the security fix correctly triggers a CHECK failure when
+  // the backing is not of type SharedMemoryImageBacking.
+  EXPECT_DEATH(CallGetSharedMemoryPixmaps(compound.get()), "");
+}
+
 TEST_F(CompoundImageBackingTest, Multiplanar) {
   auto backing = CreateMultiplanarCompoundBacking();
   auto* compound_backing = static_cast<CompoundImageBacking*>(backing.get());
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/gpu/command_buffer/service/shared_image/compound_image_backing_unittest.cc b/gpu/command_buffer/service/shared_image/compound_image_backing_unittest.cc
index 4b91671..c0edd97 100644
--- a/gpu/command_buffer/service/shared_image/compound_image_backing_unittest.cc
+++ b/gpu/command_buffer/service/shared_image/compound_image_backing_unittest.cc
@@ -161,6 +161,23 @@
     return false;
   }
 
+  // Construct a CompoundImageBacking via the WrapExternalBacking constructor
+  // (private). This mirrors CompoundImageBacking::WrapExternalBacking exactly,
+  // minus the SharedImageFactory consultation.
+  std::unique_ptr<CompoundImageBacking> WrapExternal(
+      std::unique_ptr<SharedImageBacking> backing) {
+    backing->SetNotRefCounted();
+    return std::unique_ptr<CompoundImageBacking>(new CompoundImageBacking(
+        /*is_thread_safe=*/false,
+        /*buffer_usage=*/std::nullopt, std::move(backing), copy_manager_,
+        /*shared_image_factory=*/base::WeakPtr<SharedImageFactory>()));
+  }
+
+  const std::vector<SkPixmap>& CallGetSharedMemoryPixmaps(
+      CompoundImageBacking* backing) {
+    return backing->GetSharedMemoryPixmaps();
+  }
+
   // Create a compound backing containing shared memory + GPU backing.
   std::unique_ptr<SharedImageBacking> CreateCompoundBacking(
       SharedImageUsageSet usage) {
@@ -442,6 +459,24 @@
   EXPECT_FALSE(HasGpuCreateBackingCallback(compound_backing));
 }
 
+TEST_F(CompoundImageBackingTest,
+       GetSharedMemoryPixmaps_ChecksOnWrongBackingType) {
+  auto tiny = std::make_unique<TestImageBacking>(
+      Mailbox::Generate(), viz::SinglePlaneFormat::kRGBA_8888,
+      gfx::Size(10, 10), gfx::ColorSpace(), kTopLeft_GrSurfaceOrigin,
+      kOpaque_SkAlphaType,
+      SharedImageUsageSet({SHARED_IMAGE_USAGE_DISPLAY_READ}), kTestBackingSize);
+
+  // WrapExternalBacking constructor sets elements_[0].access_streams =
+  // AccessStreamSet::All(), which includes kMemory. GetSharedMemoryPixmaps()
+  // then performs an unchecked static_cast to SharedMemoryImageBacking*.
+  auto compound = WrapExternal(std::move(tiny));
+
+  // Verify that the security fix correctly triggers a CHECK failure when
+  // the backing is not of type SharedMemoryImageBacking.
+  EXPECT_DEATH(CallGetSharedMemoryPixmaps(compound.get()), "");
+}
+
 TEST_F(CompoundImageBackingTest, Multiplanar) {
   auto backing = CreateMultiplanarCompoundBacking();
   auto* compound_backing = static_cast<CompoundImageBacking*>(backing.get());
Loading diff…

Original Bug Report

reported by vm...@google.com

Potential type confusion in CompoundImageBacking::GetSharedMemoryPixmaps

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

Overview: When CompoundImageBacking wraps a non-SHM backing, it incorrectly flags the backing as supporting kMemory. A subsequent call to GetSharedMemoryPixmaps results in an unchecked static_cast to SharedMemoryImageBacking*, causing an out-of-bounds read that can be leveraged for an arbitrary memory write in the GPU process.

Affected files:

  • gpu/command_buffer/service/shared_image/compound_image_backing.cc
  • gpu/command_buffer/service/shared_image/shared_memory_image_backing.h
  • gpu/command_buffer/service/shared_image/wrapped_graphite_texture_backing.cc

Estimated timestamp from git blame: 2026-02-26

Summary

A potential type confusion vulnerability exists in CompoundImageBacking::GetSharedMemoryPixmaps due to incorrect initialization in the WrapExternalBacking path. When a non-SharedMemoryImageBacking (such as a WrappedGraphiteTextureBacking) is wrapped, it is incorrectly marked as supporting SharedImageAccessStream::kMemory. Subsequent calls to GetSharedMemoryPixmaps perform an unchecked static_cast to SharedMemoryImageBacking*, leading to an out-of-bounds read. By grooming the GPU process heap, a compromised renderer could exploit this out-of-bounds read to forge a std::vector<SkPixmap> object and achieve an arbitrary memory write in the GPU process.

Vulnerability Details

  1. Incorrect Initialization: In gpu/command_buffer/service/shared_image/compound_image_backing.cc, the CompoundImageBacking constructor used by WrapExternalBacking (lines 947-997) initializes the wrapped backing’s access streams with element.access_streams = AccessStreamSet::All() (line 982). This incorrectly includes SharedImageAccessStream::kMemory, regardless of the actual backing type.

  2. Type Confusion: The method GetShmElement() returns the first element whose access_streams contains kMemory. Due to the incorrect initialization, this returns the wrapped non-SHM backing. The method GetSharedMemoryPixmaps() then performs a static_cast<SharedMemoryImageBacking*> on this element (line 1605) and calls .pixmaps().

  3. Out-of-Bounds Read: Because SharedMemoryImageBacking is larger than a backing like WrappedGraphiteTextureBacking (by roughly 120-180 bytes depending on the platform), the access to the pixmaps_ member at the end of the class layout results in reading memory past the end of the actual object allocation.

Potential Exploitation Scenario

The following is a theoretical exploitation path, as we have not executed a full proof-of-concept:

  1. Heap Grooming: A compromised renderer process grooms the GPU process heap to place a fake std::vector<SkPixmap> structure immediately adjacent to where a new WrappedGraphiteTextureBacking will be allocated. This fake vector points to fake SkPixmap objects with attacker-controlled destination addresses in their fPixels (writable_addr()) members.
  2. Backing Creation: The renderer requests a non-SHM SharedImage. The GPU process allocates a WrappedGraphiteTextureBacking, which is then wrapped by CompoundImageBacking::WrapExternalBacking (due to the kUseCompoundImageBackingAsDefault feature).
  3. State Invalidation: The renderer accesses the SharedImage via a stream (e.g., kGL) that triggers a dynamic backing allocation (due to the kUseDynamicBackingAllocations feature). Writing to this new backing makes the original “SHM” element stale.
  4. Trigger IPC: The renderer calls the Windows-specific Mojo IPC GpuChannel::CopyToGpuMemoryBufferAsync.
  5. Arbitrary Write: CompoundImageBacking::CopyToGpuMemoryBufferAsync detects the stale SHM element, retrieves the new GPU backing, and calls GetSharedMemoryPixmaps(). This triggers the type confusion and out-of-bounds read, returning a reference to the attacker’s fake std::vector. This reference is passed to gpu_backing->ReadbackToMemoryAsync(). The underlying implementation (e.g., GLTextureImageBacking::ReadbackToMemory) iterates over the fake vector and executes a readback (like glReadPixels) to the attacker-specified memory addresses, resulting in a controlled memory write in the GPU process.

Note: MiraclePtr (BRP) does not mitigate this issue, as the vulnerability relies on an unchecked static_cast causing a deterministic memory offset read, rather than out-of-bounds indexing on a protected raw_ptr.

Suggested Fix

The initialization in CompoundImageBacking should be updated to ensure kMemory is not blindly added to access_streams for backings that do not genuinely support it. Additionally, GetSharedMemoryPixmaps() could be fortified by replacing the static_cast with a type check or a dynamic cast to ensure the underlying backing is indeed a SharedMemoryImageBacking before accessing its members.

Evaluated with Chrome root at commit: ff3d2b74fa39431785bd60e51463b08fcc71ee33


Results so far have been promising, but there can be wrong deductions. If this proves to be a false positive, please close as WAI; 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