CVE-2026-17680
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
row_bytesservices/viz/public/cpp/compositing/bitmap_in_shared_memory_mojom_traits.cc |
modified |
Files Changed
services/viz/public/cpp/compositing/bitmap_in_shared_memory_mojom_traits.ccservices/viz/public/mojom/compositing/bitmap_in_shared_memory.mojom
Patch
From 3f23ed7b52702b5051697ccd3e4e1fbd59dad04d Mon Sep 17 00:00:00 2001 From: Joel Hockey <[email protected]> Date: Fri, 05 Jun 2026 00:46:53 -0700 Subject: [PATCH] Do not trust row_bytes from untrusted sources Always use minRowBytes() to avoid potential buffer overflows. Serialization was changed in crrev.com/c/5238400 to use minRowBytes. Bug: 516486611 Change-Id: If09820e9611a22bd33c24e9e005bdc24d9f2e78d Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7882750 Reviewed-by: Daniel Cheng <[email protected]> Reviewed-by: Tom Sepez <[email protected]> Commit-Queue: Joel Hockey <[email protected]> Cr-Commit-Position: refs/heads/main@{#1642189} --- 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 9d6bdd42..252dceab 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 @@ -28,14 +28,6 @@ } // static -uint64_t StructTraits<viz::mojom::BitmapInSharedMemoryDataView, - viz::CopyOutputResult::ScopedSkBitmap>:: - row_bytes(const viz::CopyOutputResult::ScopedSkBitmap& scoped_bitmap) { - auto sk_bitmap = scoped_bitmap.bitmap(); - return sk_bitmap.info().minRowBytes(); -} - -// static std::optional<base::WritableSharedMemoryRegion> StructTraits<viz::mojom::BitmapInSharedMemoryDataView, viz::CopyOutputResult::ScopedSkBitmap>:: @@ -91,8 +83,6 @@ SkImageInfo image_info; if (!data.ReadImageInfo(&image_info)) return false; - if (!image_info.validRowBytes(data.row_bytes())) - return false; std::optional<base::WritableSharedMemoryRegion> region_opt; if (!data.ReadPixels(®ion_opt)) @@ -100,23 +90,24 @@ *sk_bitmap = SkBitmap(); if (!region_opt) - return sk_bitmap->setInfo(image_info, data.row_bytes()); + return sk_bitmap->setInfo(image_info, image_info.minRowBytes()); auto mapping_ptr = std::make_unique<base::WritableSharedMemoryMapping>(region_opt->Map()); if (!mapping_ptr->IsValid()) return false; - if (mapping_ptr->size() < image_info.computeByteSize(data.row_bytes())) { + if (mapping_ptr->size() < + image_info.computeByteSize(image_info.minRowBytes())) { return false; } // 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.release())) { + if (!sk_bitmap->installPixels( + image_info, bitmap_memory, image_info.minRowBytes(), + &DeleteSharedMemoryMapping, mapping_ptr.release())) { return false; } return true; diff --git a/services/viz/public/mojom/compositing/bitmap_in_shared_memory.mojom b/services/viz/public/mojom/compositing/bitmap_in_shared_memory.mojom index 291b188..902ed165 100644 --- a/services/viz/public/mojom/compositing/bitmap_in_shared_memory.mojom +++ b/services/viz/public/mojom/compositing/bitmap_in_shared_memory.mojom @@ -13,7 +13,6 @@ // shared memory however. struct BitmapInSharedMemory { skia.mojom.ImageInfo image_info; - uint64 row_bytes; // Null indicates SkBitmap does not have pixels allocated. mojo_base.mojom.WritableSharedMemoryRegion? pixels; };
Original Bug Report
Potential heap buffer overflow in EyeDropperView on ChromeOS via untrusted stride
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. Please see https://chromium.googlesource.com/chromium/src/+/main/docs/security/ai-generated-security-bugs-faq.md for more information.
Overview: A compromised Viz/GPU process can potentially trigger a heap buffer overflow in the browser process via a crafted viz.mojom.CopyOutputResult. When the eye dropper tool captures a snapshot, the browser allocates a destination buffer using nominal dimensions but copies data using an unvalidated stride value. This can lead to a linear heap overflow on the browser process’s PartitionAlloc heap.
Affected files:
components/eye_dropper/eye_dropper_view.ccservices/viz/public/cpp/compositing/bitmap_in_shared_memory_mojom_traits.cc
Estimated timestamp from git blame: 2020-04-29
Root Cause Analysis
On ChromeOS, the EyeDropper screen capturer uses DesktopCapturerAsh, which obtains screenshots via ui::GrabWindowSnapshot -> viz::CopyOutputRequest. The response travels from the GPU/Viz process to the browser process as a viz.mojom.CopyOutputResult carrying a BitmapInSharedMemory struct.
During deserialization in the browser process (services/viz/public/cpp/compositing/bitmap_in_shared_memory_mojom_traits.cc), the row_bytes (stride) field of the bitmap is validated via image_info.validRowBytes(data.row_bytes()). However, this Skia function only checks that row_bytes is greater than or equal to the minimum required row bytes for the image width and is properly aligned; it does not enforce an upper bound. The check:
if (mapping_ptr->size() < image_info.computeByteSize(data.row_bytes()))
return false;
is easily satisfied if a compromised GPU process supplies an appropriately large shared memory region.
This untrusted stride/row_bytes value is subsequently wrapped in gfx::Image and then a DesktopFrameSkia instance. Inside EyeDropperView::ScreenCapturer::OnCaptureResult (components/eye_dropper/eye_dropper_view.cc):
// components/eye_dropper/eye_dropper_view.cc
CHECK_EQ(frame->pixel_format(), webrtc::FOURCC_ARGB);
frame_.allocN32Pixels(frame->size().width(), frame->size().height(), true);
UNSAFE_TODO(memcpy(frame_.getAddr32(0, 0), frame->data(),
frame->size().height() * frame->stride()));
Because frame_.allocN32Pixels allocates a tight pixel buffer based on the nominal width and height (allocating exactly width * height * 4 bytes), whereas the subsequent memcpy copies a total of height * stride bytes, a linear heap buffer overflow occurs on the browser’s PartitionAlloc heap.
Potential Trigger Path
Note: These are suggested/potential steps modeled from static analysis; our tooling does not currently run code.
- A user interacts with a page requesting
new EyeDropper().open()with transient user activation. EyeDropperViewinitializes a screen capturer, resulting incontent::DesktopCapturerAshrequesting a window snapshot viaui::GrabWindowSnapshot.- A
viz::CopyOutputRequestis sent from the browser to the Viz/GPU process, establishing a callback receiver (CopyOutputResultSenderImpl) in the browser. - A compromised GPU process intercepts the remote and returns a crafted
viz.mojom.CopyOutputResultwith a custom, excessively largerow_bytesvalue and a correspondingly sized shared memory backing. - The large stride is preserved during deserialization and wrapped into
DesktopFrameSkia. - The callback in
EyeDropperView::ScreenCapturer::OnCaptureResultis triggered. The destination buffer is allocated withwidth * height * 4bytes, butmemcpycopiesheight * stridebytes, causing a heap buffer overflow in the unsandboxed browser process.
Suggested Fix
To remediate this issue, the browser should perform a row-by-row copy instead of a single contiguous memcpy when copying the pixel data if the stride does not match the tight standard layout, or enforce that the input stride must match the minimum expected stride for the given dimensions before executing the copy:
// Verify the input stride matches the allocated destination layout
if (frame->stride() != frame->size().width() * 4) {
return; // Or perform a row-by-row copy
}
Evaluated with Chrome root at commit: a2bea94528f4bd6cc57739c43fa3bb890b8367d3
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.