| // 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. |
| |
| #include "gpu/command_buffer/common/iosurface_validation.h" |
| |
| #include <CoreVideo/CoreVideo.h> |
| #include <IOSurface/IOSurfaceRef.h> |
| |
| namespace gpu { |
| |
| bool ValidateIOSurface(const gfx::ScopedIOSurface& io_surface, |
| viz::SharedImageFormat format, |
| gfx::Size size, |
| bool cpu_access, |
| std::string* out_error_str) { |
| // Validate top-level IOSurface format. |
| uint32_t io_surface_format = IOSurfaceGetPixelFormat(io_surface.get()); |
| if (!gfx::IOSurfacePixelFormatMatchesSharedImageFormat( |
| io_surface_format, format, /*match_rgba_and_bgra=*/true)) { |
| if (out_error_str) { |
| *out_error_str = |
| "IOSurface pixel format does not match specified shared " |
| "image format."; |
| } |
| return false; |
| } |
| |
| // Validate top-level IOSurface dimensions. |
| if (IOSurfaceGetWidth(io_surface.get()) != |
| static_cast<size_t>(size.width()) || |
| IOSurfaceGetHeight(io_surface.get()) != |
| static_cast<size_t>(size.height())) { |
| if (out_error_str) { |
| *out_error_str = "IOSurface size does not match specified size."; |
| } |
| return false; |
| } |
| |
| // Ensure the IOSurface has at least as many planes as the requested format. |
| // For single-planar IOSurfaces, IOSurfaceGetPlaneCount returns 0. |
| size_t io_surface_plane_count = |
| std::max<size_t>(1, IOSurfaceGetPlaneCount(io_surface.get())); |
| if (io_surface_plane_count < static_cast<size_t>(format.NumberOfPlanes())) { |
| if (out_error_str) { |
| *out_error_str = "IOSurface plane count is too small."; |
| } |
| return false; |
| } |
| |
| // Verify that the IOSurface format allows CPU access if it is expected. |
| const bool io_surface_format_supports_cpu_access = |
| gfx::IOSurfacePixelFormatSupportsCpuAccess(io_surface_format); |
| if (cpu_access && !io_surface_format_supports_cpu_access) { |
| if (out_error_str) { |
| *out_error_str = "IOSurface format does not allow CPU access."; |
| } |
| return false; |
| } |
| |
| // Validate per-plane dimensions and stride. A malformed IOSurface could |
| // have planes with dimensions inconsistent with its top-level size and |
| // format, leading to out-of-bounds access during buffer operations. |
| for (int plane_index = 0; plane_index < format.NumberOfPlanes(); |
| ++plane_index) { |
| gfx::Size plane_size = format.GetPlaneSize(plane_index, size); |
| if (IOSurfaceGetWidthOfPlane(io_surface.get(), plane_index) != |
| static_cast<size_t>(plane_size.width()) || |
| IOSurfaceGetHeightOfPlane(io_surface.get(), plane_index) != |
| static_cast<size_t>(plane_size.height())) { |
| if (out_error_str) { |
| *out_error_str = "IOSurface plane size does not match specified size."; |
| } |
| return false; |
| } |
| |
| // Ensure the IOSurface has enough bytes per row for the plane to prevent |
| // potential out-of-bounds access when copying or accessing the buffer on |
| // the CPU. Do this for all CPU-accessible formats, even if CPU access was |
| // not requested. |
| if (io_surface_format_supports_cpu_access) { |
| size_t io_surface_bytes_per_row = |
| IOSurfaceGetBytesPerRowOfPlane(io_surface.get(), plane_index); |
| size_t min_bytes_per_row; |
| if (format.is_single_plane()) { |
| CHECK(!format.IsCompressed()); |
| min_bytes_per_row = static_cast<size_t>(format.BytesPerPixel()) * |
| static_cast<size_t>(plane_size.width()); |
| } else { |
| min_bytes_per_row = |
| static_cast<size_t>(format.MultiplanarStorageBytesPerChannel()) * |
| static_cast<size_t>(format.NumChannelsInPlane(plane_index)) * |
| static_cast<size_t>(plane_size.width()); |
| } |
| if (io_surface_bytes_per_row < min_bytes_per_row) { |
| if (out_error_str) { |
| *out_error_str = "IOSurface bytes per row is too small."; |
| } |
| return false; |
| } |
| } |
| } |
| |
| return true; |
| } |
| |
| } // namespace gpu |