CVE-2026-8546
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
switchcomponents/viz/common/resources/shared_image_format_utils.cc |
modified | |
ifgpu/command_buffer/common/BUILD.gn |
modified | |
source_setgpu/command_buffer/common/BUILD.gn |
modified |
Files Changed
components/viz/common/resources/shared_image_format_utils.cccomponents/viz/common/resources/shared_image_format_utils.hgpu/command_buffer/client/internal/mappable_buffer_io_surface.ccgpu/command_buffer/common/BUILD.gngpu/command_buffer/common/iosurface_validation.cc
Patch
From 37d6e75fe5a7a5a0352c9d9e09d769fcd6a98d06 Mon Sep 17 00:00:00 2001 From: Bo Liu <[email protected]> Date: Wed, 03 Jun 2026 14:33:13 -0700 Subject: [PATCH] gpu: Validate IOSurface in MappableBufferIOSurface MappableBufferIOSurface should validate its IOSurface since it can be sent from untrusted process to more trusted process; and it can be used similar to shared memory without involving the "service" side of command buffer that has the necessary validations. This change duplicates the validation in IOSurfaceImageBackingFactory::CreateSharedImageGMBs to MappableBufferIOSurface::CreateFromHandleImpl, and change the latter to return null instead of CHECK-ing on failure. Changes: * Move SharedImageFormatToIOSurfacePixelFormat to newly created gpu/command_buffer/common/iosurface_validation.h because it is needed for validation. * Move validation code from IOSurfaceImageBackingFactory to iosurface_validation.h * Relax the validation to consider RGBA and BGRA the same to avoid complexity * Replace call new validation from MappableBufferIOSurface. This also effectively reverts the previous bandaid fix that wasn't really complete: https://chromium-review.googlesource.com/7710673 Bug: 497531791 Change-Id: I85a63f0747a68b0e241f3d2d1e5fb97357cf73e4 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7882552 Reviewed-by: Vasiliy Telezhnikov <[email protected]> Reviewed-by: Dominic Farolino <[email protected]> Auto-Submit: Bo Liu <[email protected]> Commit-Queue: Bo Liu <[email protected]> Cr-Commit-Position: refs/heads/main@{#1641218} --- diff --git a/components/viz/common/resources/shared_image_format_utils.cc b/components/viz/common/resources/shared_image_format_utils.cc index 9891b5d5..9966920 100644 --- a/components/viz/common/resources/shared_image_format_utils.cc +++ b/components/viz/common/resources/shared_image_format_utils.cc @@ -377,55 +377,4 @@ return kMappableSharedImageFormats; } -#if BUILDFLAG(IS_MAC) - -bool MatchesSharedImageFormatWithIOSurfacePixelFormat( - const SharedImageFormat& format, - OSType io_surface_format, - bool override_rgba_to_brga) { - switch (io_surface_format) { - case kCVPixelFormatType_OneComponent8: - return format == SinglePlaneFormat::kR_8; - case kCVPixelFormatType_TwoComponent8: - return format == SinglePlaneFormat::kRG_88; - case kCVPixelFormatType_OneComponent16: - return format == SinglePlaneFormat::kR_16; - case kCVPixelFormatType_TwoComponent16: - return format == SinglePlaneFormat::kRG_1616; - case kCVPixelFormatType_ARGB2101010LEPacked: - return format == SinglePlaneFormat::kBGRA_1010102; - case kCVPixelFormatType_32BGRA: - return format == SinglePlaneFormat::kBGRA_8888 || - format == SinglePlaneFormat::kBGRX_8888 || - (override_rgba_to_brga && - (format == SinglePlaneFormat::kRGBA_8888 || - format == SinglePlaneFormat::kRGBX_8888)); - case kCVPixelFormatType_32RGBA: - return format == SinglePlaneFormat::kRGBA_8888 || - format == SinglePlaneFormat::kRGBX_8888; - case kCVPixelFormatType_64RGBAHalf: - return format == SinglePlaneFormat::kRGBA_F16; - - case kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange: - return format == MultiPlaneFormat::kNV12; - case kCVPixelFormatType_422YpCbCr8BiPlanarVideoRange: - return format == MultiPlaneFormat::kNV16; - case kCVPixelFormatType_444YpCbCr8BiPlanarVideoRange: - return format == MultiPlaneFormat::kNV24; - case kCVPixelFormatType_420YpCbCr8VideoRange_8A_TriPlanar: - return format == MultiPlaneFormat::kNV12A; - case kCVPixelFormatType_420YpCbCr10BiPlanarVideoRange: - return format == MultiPlaneFormat::kP010; - case kCVPixelFormatType_422YpCbCr10BiPlanarVideoRange: - return format == MultiPlaneFormat::kP210; - case kCVPixelFormatType_444YpCbCr10BiPlanarVideoRange: - return format == MultiPlaneFormat::kP410; - case kCVPixelFormatType_420YpCbCr8Planar: - return format == MultiPlaneFormat::kI420; - } - return false; -} - -#endif // BUILDFLAG(IS_MAC) - } // namespace viz diff --git a/components/viz/common/resources/shared_image_format_utils.h b/components/viz/common/resources/shared_image_format_utils.h index ba264df..83902bb 100644 --- a/components/viz/common/resources/shared_image_format_utils.h +++ b/components/viz/common/resources/shared_image_format_utils.h @@ -6,14 +6,8 @@ #define COMPONENTS_VIZ_COMMON_RESOURCES_SHARED_IMAGE_FORMAT_UTILS_H_ #include "base/component_export.h" -#include "build/build_config.h" #include "components/viz/common/resources/shared_image_format.h" -#if BUILDFLAG(IS_MAC) -// Includes `MacTypes.h` for an io surface pixel format, i.e. `OSType`. -#include <MacTypes.h> -#endif // BUILDFLAG(IS_MAC) - enum SkColorType : int; namespace viz { @@ -136,16 +130,6 @@ COMPONENT_EXPORT(VIZ_SHARED_IMAGE_FORMAT) base::span<const SharedImageFormat> GetMappableSharedImageFormatForTesting(); -#if BUILDFLAG(IS_MAC) -// Returns whether the given shared image format matches io surface pixel -// format or not. -COMPONENT_EXPORT(VIZ_SHARED_IMAGE_FORMAT) -bool MatchesSharedImageFormatWithIOSurfacePixelFormat( - const SharedImageFormat& format, - OSType io_surface_format, - bool override_rgba_to_brga); -#endif // BUILDFLAG(IS_MAC) - } // namespace viz #endif // COMPONENTS_VIZ_COMMON_RESOURCES_SHARED_IMAGE_FORMAT_UTILS_H_ diff --git a/gpu/command_buffer/client/internal/mappable_buffer_io_surface.cc b/gpu/command_buffer/client/internal/mappable_buffer_io_surface.cc index de2db7f..77e6b415 100644 --- a/gpu/command_buffer/client/internal/mappable_buffer_io_surface.cc +++ b/gpu/command_buffer/client/internal/mappable_buffer_io_surface.cc @@ -13,6 +13,7 @@ #include "base/notimplemented.h" #include "base/numerics/safe_conversions.h" #include "components/viz/common/resources/shared_image_format_utils.h" +#include "gpu/command_buffer/common/iosurface_validation.h" #include "ui/gfx/mac/io_surface.h" namespace gpu { @@ -132,14 +133,12 @@ return nullptr; } - // TODO(crbug.com/497531791): Check whether IOSurface pixel format matches - // the given SharedImageFormat: `format` or not. - // Since there is no information about whether `gr_context_type_ == - // GrContextType::kGL` or not, we will treat `override_rgba_to_brga` as always - // true. c.f. IOSurfaceImageBackingFactory::CreateSharedImageGMBs(). - CHECK(viz::MatchesSharedImageFormatWithIOSurfacePixelFormat( - format, IOSurfaceGetPixelFormat(handle.io_surface().get()), - true /*override_rgba_to_brga*/)); + std::string validation_error; + if (!ValidateIOSurface(handle.io_surface(), format, size, + &validation_error)) { + LOG(ERROR) << validation_error; + return nullptr; + } int64_t io_surface_width = IOSurfaceGetWidth(handle.io_surface().get()); int64_t io_surface_height = IOSurfaceGetHeight(handle.io_surface().get()); diff --git a/gpu/command_buffer/common/BUILD.gn b/gpu/command_buffer/common/BUILD.gn index 2e98429..fad6c570 100644 --- a/gpu/command_buffer/common/BUILD.gn +++ b/gpu/command_buffer/common/BUILD.gn @@ -108,6 +108,18 @@ ] deps = [ "//base" ] + + if (is_apple) { + sources += [ + "iosurface_validation.cc", + "iosurface_validation.h", + ] + + frameworks = [ + "CoreVideo.framework", + "IOSurface.framework", + ] + } } source_set("mailbox_sources") { diff --git a/gpu/command_buffer/common/iosurface_validation.cc b/gpu/command_buffer/common/iosurface_validation.cc new file mode 100644 index 0000000..eab41bf4 --- /dev/null +++ b/gpu/command_buffer/common/iosurface_validation.cc @@ -0,0 +1,153 @@ +// Copyright 2026 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +
Original Bug Report
Potential GPU process OOB read via unvalidated MappableBufferIOSurface planes
Project Fortify, an experimental security project, has identified the following potential security issue.
Overview: On macOS, MappableBufferIOSurface::CreateFromHandleImpl fails to validate IOSurface plane properties against the claimed SharedImageFormat. A compromised renderer can provide an IOSurface with a maliciously under-allocated plane, leading to a massive out-of-bounds memory read in the GPU process during video encoding fallback paths. This leaks GPU process memory back to the renderer within the video bitstream, potentially defeating ASLR.
Affected files:
gpu/command_buffer/client/internal/mappable_buffer_io_surface.ccmedia/base/mac/video_frame_mac.mmgpu/command_buffer/client/client_shared_image.ccmedia/mojo/services/mojo_video_encode_accelerator_service.ccmedia/base/video_frame.cc
Estimated timestamp from git blame: 2026-01-12
Disclaimer
Note: The following attack steps are potential and based on static analysis. Our tooling agent does not currently have the ability to run code to provide a working proof-of-concept.
Vulnerability Details
In gpu/command_buffer/client/internal/mappable_buffer_io_surface.cc, the CreateFromHandleImpl function takes an IOSurface from an IPC handle and wraps it for use in the GPU process. While the function verifies that the global IOSurface width and height are at least as large as the requested size, it completely fails to validate the pixel format, the number of planes, or the dimensions of individual planes against the claimed viz::SharedImageFormat.
Because the claimed SharedImageFormat is later trusted to calculate memory span sizes when the surface is mapped, an attacker can intentionally under-allocate a specific plane (e.g., the UV plane of an NV12 image) while maintaining large global dimensions.
Potential Attack Path
An attacker with a compromised macOS renderer process could exploit this via the following steps:
- Create Malicious IOSurface: The renderer natively creates an
IOSurfacewith large global dimensions (e.g., 4096x4096), but specifies a customkIOSurfacePlaneInfoarray where Plane 1 (the UV plane) is drastically under-allocated (e.g., only 2 rows high instead of 2048). - Send via IPC: The renderer wraps this in an
ExportedSharedImageand sends it to the GPU process viamedia.mojom.VideoEncodeAccelerator::Encode(). It claims the format is NV12, the size is 4096x4096, and intentionally mismatchesvisible_rectfromcoded_sizeto force a cropping operation. - Validation Bypass: The GPU process receives the handle.
MappableBufferIOSurface::CreateFromHandleImplchecksIOSurfaceGetWidth/Height(which are 4096), accepts the handle, and ignores the maliciously small Plane 1. - Fallback Mapping: In
media/base/mac/video_frame_mac.mm, the mismatchingvisible_rectforcesWrapVideoFrameInCVPixelBufferto take a software mapping fallback path viaConvertToMemoryMappedFrame(). - OOB Span Calculation:
ClientSharedImage::ScopedMapping::GetMemoryForPlane(1)calculates the expected memory span for Plane 1 based on the claimed NV12 format. For a 4096x4096 image, it expects 2048 rows. It computesspan_lengthusing theIOSurfacestride multiplied by 2047 rows. - Information Leak: The resulting
base::spanextends wildly out of bounds of the actual 2-row allocation, encompassing ~8MB of adjacent GPU process memory. This span is wrapped in aCVPixelBufferand passed to VideoToolbox for hardware encoding. - ASLR Defeat: VideoToolbox encodes the out-of-bounds GPU heap memory into the H.264/HEVC bitstream. The bitstream is sent back to the renderer via the
BitstreamBufferReadycallback, allowing the attacker to decode it, observe leaked pointers/data, and defeat GPU process ASLR.
Suggested Fix
Update MappableBufferIOSurface::CreateFromHandleImpl to strictly validate the properties of the imported IOSurface. Specifically:
- Ensure
IOSurfaceGetPixelFormat(io_surface)matches the expected pixel format derived fromSharedImageFormat(similar to the checks performed inIOSurfaceImageBackingFactory::CreateSharedImageGMBs). - Verify that
IOSurfaceGetPlaneCount(io_surface)matchesformat.NumberOfPlanes(). - Iterate over the planes and ensure that
IOSurfaceGetWidthOfPlaneandIOSurfaceGetHeightOfPlanemeet or exceed the expected dimensions for each plane given the claimedSharedImageFormatandsize.
Evaluated with Chrome root at commit: a9cbf6e8b275fe4147435aa905f3b7f5a656f5f0
Results from 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.