blob: 6e7367ecb7c77636ad070702d285402b628851ed [file]
// 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