Overview

High
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in Viz
DescriptionUse after free in Viz
ComponentViz
Bug ClassUAF
Tracker497846428
Fix commit4002a66778d2 (chromium/src) +5/-3
CISA KEVNot listed
CreditedGoogle
Disclosed2026-04-15

Files Changed

  • services/viz/public/cpp/compositing/bitmap_in_shared_memory_mojom_traits.cc
From 4002a66778d2aef40fb7d72c580d7bbc10115ea8 Mon Sep 17 00:00:00 2001
From: Vasiliy Telezhnikov <[email protected]>
Date: Wed, 01 Apr 2026 11:19:52 -0700
Subject: [PATCH] Fix potential double free in deserializing CopyOutputResults

Skia always runs release proc of the SkBitmap::installPixels [1].

[1] https://source.chromium.org/chromium/chromium/src/+/main:third_party/skia/include/core/SkBitmap.h;drc=405f385dce2db578ff3b2301686d231ee8f0b042;l=586

Bug: 497846428
Change-Id: I4d3fd2e676fa7aa74e1022cbd2c9d9db8970a90c
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7718978
Commit-Queue: Vasiliy Telezhnikov <[email protected]>
Reviewed-by: Joe Mason <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1608675}
---

diff --git a/services/viz/public/cpp/compositing/bitmap_in_shared_memory_mojom_traits.cc b/services/viz/public/cpp/compositing/bitmap_in_shared_memory_mojom_traits.cc
index 59fbc85e..9d6bdd42 100644
--- a/services/viz/public/cpp/compositing/bitmap_in_shared_memory_mojom_traits.cc
+++ b/services/viz/public/cpp/compositing/bitmap_in_shared_memory_mojom_traits.cc
@@ -111,12 +111,14 @@
     return false;
   }
 
-  if (!sk_bitmap->installPixels(image_info, mapping_ptr->memory(),
+  // Skia guarantees that it will call release proc, so we pass release()'ed
+  // pointer into it.
+  void* bitmap_memory = mapping_ptr->memory();
+  if (!sk_bitmap->installPixels(image_info, bitmap_memory,
                                 data.row_bytes(), &DeleteSharedMemoryMapping,
-                                mapping_ptr.get())) {
+                                mapping_ptr.release())) {
     return false;
   }
-  mapping_ptr.release();
   return true;
 }
 
Loading diff…

Original Bug Report

reported by [email protected]

Potential Double-Free in BitmapInSharedMemory Deserialization via SkBitmap::installPixels

Project Fortify, an experimental security project, has identified the following potential security issue.

Overview: A potential double-free vulnerability exists in the browser process when deserializing viz::mojom::BitmapInSharedMemory from an untrusted process. If SkBitmap::installPixels fails, it synchronously invokes a release callback that frees a shared memory mapping, which is then freed a second time by a std::unique_ptr when the function returns early.

Affected files:

  • services/viz/public/cpp/compositing/bitmap_in_shared_memory_mojom_traits.cc

Estimated timestamp from git blame: 2020-04-29

Description

A double-free vulnerability exists in services/viz/public/cpp/compositing/bitmap_in_shared_memory_mojom_traits.cc within the StructTraits<viz::mojom::BitmapInSharedMemoryDataView, SkBitmap>::Read method. This method is used by the browser process to deserialize SkBitmap data stored in shared memory, typically received from a potentially compromised GPU or Renderer process via IPCs such as viz.mojom.CopyOutputResultSender.SendResult.

In the Read method, a base::WritableSharedMemoryMapping object is allocated on the heap and managed by a std::unique_ptr named mapping_ptr. This mapping is passed to sk_bitmap->installPixels along with a release callback, DeleteSharedMemoryMapping, and the raw pointer mapping_ptr.get() as the context:

// services/viz/public/cpp/compositing/bitmap_in_shared_memory_mojom_traits.cc
auto mapping_ptr =
    std::make_unique<base::WritableSharedMemoryMapping>(region_opt->Map());
// ...
if (!sk_bitmap->installPixels(image_info, mapping_ptr->memory(),
                              data.row_bytes(), &DeleteSharedMemoryMapping,
                              mapping_ptr.get())) {
  return false;
}
mapping_ptr.release();
return true;

The DeleteSharedMemoryMapping function is responsible for deleting the mapping object:

void DeleteSharedMemoryMapping(void* not_used, void* context) {
  delete static_cast<base::WritableSharedMemoryMapping*>(context);
}

If sk_bitmap->installPixels fails (e.g., due to an invalid row_bytes value that wasn’t caught by earlier checks), it internally invokes the provided releaseProc (DeleteSharedMemoryMapping), which performs the first delete of the mapping object. The Read method then returns false without calling mapping_ptr.release(). Consequently, when the mapping_ptr std::unique_ptr goes out of scope, its destructor attempts to delete the same pointer a second time, resulting in a synchronous double-free on the browser process heap.

Potential Attack Vector

(Note: These are suggested steps based on code analysis; our tooling agent does not yet have the ability to run code to verify an exploit.)

  1. An attacker compromises the GPU process.
  2. The browser process requests a screenshot (e.g., via tab thumbnail capture or DevTools) using a CopyOutputRequest.
  3. The compromised GPU process responds by sending a malicious viz::mojom::CopyOutputResult over IPC, containing a crafted BitmapInSharedMemory struct.
  4. The attacker crafts the BitmapInSharedMemory with:
    • image_info.width = 1
    • image_info.height = 1
    • image_info.colorType = kAlpha_8_SkColorType
    • row_bytes = 0x80000000 (2147483648)
    • pixels = a valid shared memory region of at least 1 byte.
  5. During deserialization in the browser process:
    • The check image_info.validRowBytes(0x80000000) passes because 0x80000000 >= minRowBytes (which is 1) and alignment is correct.
    • The check mapping_ptr->size() < image_info.computeByteSize(data.row_bytes()) passes because computeByteSize multiplies row_bytes by height - 1 (which is 0), resulting in a required size of 1 byte.
    • sk_bitmap->installPixels is called.
  6. Inside Skia’s SkBitmap::installPixels, it calls SkBitmap::setInfo. This function checks if row_bytes fits in a signed 32-bit integer: SkTFitsIn<int32_t>(rowBytes). Since 0x80000000 > INT32_MAX (0x7FFFFFFF), this check fails, causing setInfo to return false.
  7. SkBitmap::installPixels handles the failure by calling the releaseProc (DeleteSharedMemoryMapping), performing the first free of the WritableSharedMemoryMapping object.
  8. installPixels returns false to StructTraits::Read.
  9. StructTraits::Read takes the error path return false;. The std::unique_ptr mapping_ptr goes out of scope and its destructor performs the second free of the exact same object.
  10. This corrupts the PartitionAlloc freelist in the Browser process, providing a powerful primitive for arbitrary Remote Code Execution (RCE) and sandbox escape.

Suggested Fix

Release the std::unique_ptr before calling installPixels. Since installPixels guarantees that the releaseProc will be called even if it fails, ownership of the mapping should be transferred to Skia before the call.

  auto* mapping_raw = mapping_ptr.release();
  if (!sk_bitmap->installPixels(image_info, mapping_raw->memory(),
                                data.row_bytes(), &DeleteSharedMemoryMapping,
                                mapping_raw)) {
    return false;
  }
  return true;

Evaluated with Chrome root at commit: 876d480da1f794d87813cfa2e6ff4fcf9771e939


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