blob: ac0bb3d3600106b4c79c778a5834134d8376c9db [file] [log] [blame]
// Copyright 2021 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <memory>
#include "base/callback.h"
#include "base/files/file_path.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/scoped_refptr.h"
#include "base/memory/weak_ptr.h"
#include "base/test/bind.h"
#include "chrome/browser/chrome_content_browser_client.h"
#include "chrome/browser/extensions/extension_browsertest.h"
#include "chrome/browser/extensions/extension_tab_util.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/test/base/ui_test_utils.h"
#include "content/public/browser/browser_message_filter.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/content_client.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "extensions/browser/api/storage/storage_api.h"
#include "extensions/browser/bad_message.h"
#include "extensions/browser/browsertest_util.h"
#include "extensions/browser/extension_frame_host.h"
#include "extensions/browser/extension_web_contents_observer.h"
#include "extensions/browser/process_manager.h"
#include "extensions/common/constants.h"
#include "extensions/common/extension_features.h"
#include "extensions/common/extension_messages.h"
#include "extensions/common/mojom/frame.mojom-test-utils.h"
#include "extensions/test/extension_test_message_listener.h"
#include "extensions/test/test_extension_dir.h"
#include "ipc/ipc_security_test_util.h"
#include "net/dns/mock_host_resolver.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/mojom/service_worker/service_worker_database.mojom-forward.h"
#include "url/gurl.h"
namespace extensions {
// ExtensionFrameHostInterceptor is a helper for:
// - Intercepting extensions::mojom::LocalFrameHost method calls (e.g. methods
// that would normally be handled / implemented by ExtensionFrameHost).
// - Allowing the test to mutate the method arguments (e.g. to simulate a
// compromised renderer) before passing the method call to the usual handler.
class ExtensionFrameHostInterceptor
: mojom::LocalFrameHostInterceptorForTesting {
public:
// The caller needs to ensure that `frame` stays alive longer than the
// constructed ExtensionFrameHostInterceptor.
using RequestMutator =
base::RepeatingCallback<void(mojom::RequestParams& request_params)>;
ExtensionFrameHostInterceptor(content::RenderFrameHost* frame,
RequestMutator request_mutator)
: frame_(frame),
request_mutator_(std::move(request_mutator)),
extension_frame_host_(
ExtensionWebContentsObserver::GetForWebContents(
content::WebContents::FromRenderFrameHost(frame_))
->extension_frame_host_for_testing()),
scoped_swap_impl_(extension_frame_host_->receivers_for_testing(),
this) {}
~ExtensionFrameHostInterceptor() override = default;
private:
mojom::LocalFrameHost* GetForwardingInterface() override {
return scoped_swap_impl_.old_impl();
}
void Request(mojom::RequestParamsPtr params,
RequestCallback callback) override {
// `//extensions/common/mojom/frame.mojom` specifies that `params` is
// non-optional.
CHECK(params);
content::RenderFrameHost* current_target_frame =
extension_frame_host_->receivers_for_testing().GetCurrentTargetFrame();
if (frame_ == current_target_frame)
request_mutator_.Run(*params);
GetForwardingInterface()->Request(std::move(params), std::move(callback));
}
content::RenderFrameHost* const frame_ = nullptr;
const RequestMutator request_mutator_;
ExtensionFrameHost* const extension_frame_host_ = nullptr;
const mojo::test::ScopedSwapImplForTesting<
content::RenderFrameHostReceiverSet<mojom::LocalFrameHost>>
scoped_swap_impl_;
};
// Waits for a kill of the given RenderProcessHost and returns the
// BadMessageReason that caused an //extensions-triggerred kill.
//
// Example usage:
// RenderProcessHostBadIpcMessageWaiter kill_waiter(render_process_host);
// ... test code that triggers a renderer kill ...
// EXPECT_EQ(bad_message::EFD_BAD_MESSAGE_PROCESS, kill_waiter.Wait());
class RenderProcessHostBadIpcMessageWaiter {
public:
explicit RenderProcessHostBadIpcMessageWaiter(
content::RenderProcessHost* render_process_host)
: internal_waiter_(render_process_host,
"Stability.BadMessageTerminated.Extensions") {}
// Waits until the renderer process exits. Returns the bad message that made
// //extensions kill the renderer. `absl::nullopt` is returned if the
// renderer was killed outside of //extensions or exited normally.
[[nodiscard]] absl::optional<bad_message::BadMessageReason> Wait() {
absl::optional<int> internal_result = internal_waiter_.Wait();
if (!internal_result.has_value())
return absl::nullopt;
return static_cast<bad_message::BadMessageReason>(internal_result.value());
}
RenderProcessHostBadIpcMessageWaiter(
const RenderProcessHostBadIpcMessageWaiter&) = delete;
RenderProcessHostBadIpcMessageWaiter& operator=(
const RenderProcessHostBadIpcMessageWaiter&) = delete;
private:
content::RenderProcessHostKillWaiter internal_waiter_;
};
// Intercepts legacy IPC messages of type TMessageType. Only meant for
// intercepting message once.
template <typename TMessageType>
class ExtensionMessageWaiter {
public:
using MessageParamType = typename TMessageType::Param;
ExtensionMessageWaiter() : weak_ptr_factory_(this) {
test_content_browser_client_ = std::make_unique<TestContentBrowserClient>(
weak_ptr_factory_.GetWeakPtr());
}
~ExtensionMessageWaiter() = default;
ExtensionMessageWaiter(const ExtensionMessageWaiter&) = delete;
ExtensionMessageWaiter& operator=(const ExtensionMessageWaiter&) = delete;
using IpcMatcher =
base::RepeatingCallback<bool(int captured_render_process_id,
const MessageParamType& param)>;
void SetIpcMatcher(IpcMatcher ipc_matcher) { ipc_matcher_ = ipc_matcher; }
MessageParamType WaitForMessage() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
// Wait for `captured_message_param_` in a nested message loop if needed
// (i.e. if CaptureMessageParam hasn't called `run_loop_.Quit()` yet).
run_loop_.Run();
// Return the `captured_message_param_`.
DCHECK(captured_message_param_.has_value());
return *captured_message_param_;
}
private:
void CaptureMessageParam(MessageParamType param, int render_process_id) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
// Do nothing if we already captured a matching IPC.
if (captured_message_param_.has_value())
return;
// Do nothing if the IPC doesn't match.
if (ipc_matcher_.is_null() || !ipc_matcher_.Run(render_process_id, param))
return;
// Capture the IPC payload.
captured_message_param_.emplace(std::move(param));
captured_render_process_id_ = render_process_id;
// Once we have `captured_message_param_` there is no need to inject
// TestFilter into additional RenderProcessHosts.
test_content_browser_client_.reset();
// Wake up WaitForMessage if necessary.
run_loop_.Quit();
}
// A BrowserMessageFilter implementation that posts a copy of the payload of
// the TMessageType into ExtensionMessageWaiter::CaptureMessageParam, but
// otherwise leaves all the message unhandled (i.e. allows other filters to
// process the message).
class TestFilter : public content::BrowserMessageFilter {
public:
TestFilter(base::WeakPtr<ExtensionMessageWaiter> ipc_message_waiter,
int render_process_id)
: content::BrowserMessageFilter(ExtensionMsgStart),
ipc_message_waiter_(ipc_message_waiter),
render_process_id_(render_process_id) {}
private:
// content::BrowserMessageFilter overrides:
bool OnMessageReceived(const IPC::Message& message) override {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
MessageParamType param;
if (message.type() == TMessageType::ID &&
TMessageType::Read(&message, &param)) {
content::GetUIThreadTaskRunner({})->PostTask(
FROM_HERE,
base::BindOnce(&ExtensionMessageWaiter::CaptureMessageParam,
ipc_message_waiter_, std::move(param),
render_process_id_));
}
return false; // Not handled - let another filter handle the message.
}
~TestFilter() override = default;
base::WeakPtr<ExtensionMessageWaiter> ipc_message_waiter_;
const int render_process_id_;
};
// A content::ContentBrowserClient that injects a TestFilter (as the very
// first filter) into all new RenderProcessHost objects, but otherwise behaves
// identically to ChromeContentBrowserClient.
class TestContentBrowserClient : public ChromeContentBrowserClient {
public:
explicit TestContentBrowserClient(
base::WeakPtr<ExtensionMessageWaiter> ipc_message_waiter)
: ipc_message_waiter_(ipc_message_waiter) {
old_client_ = content::SetBrowserClientForTesting(this);
}
~TestContentBrowserClient() override {
content::SetBrowserClientForTesting(old_client_);
}
private:
// content::ContentBrowserClient overrides:
void RenderProcessWillLaunch(content::RenderProcessHost* host) override {
auto test_filter =
base::MakeRefCounted<TestFilter>(ipc_message_waiter_, host->GetID());
host->AddFilter(test_filter.get());
ChromeContentBrowserClient::RenderProcessWillLaunch(host);
}
base::WeakPtr<ExtensionMessageWaiter> ipc_message_waiter_;
raw_ptr<content::ContentBrowserClient> old_client_;
};
std::unique_ptr<TestContentBrowserClient> test_content_browser_client_;
base::RunLoop run_loop_;
IpcMatcher ipc_matcher_;
absl::optional<MessageParamType> captured_message_param_;
int captured_render_process_id_ = -1;
base::WeakPtrFactory<ExtensionMessageWaiter> weak_ptr_factory_;
};
// Test suite covering how mojo/IPC messages are verified after being received
// from a (potentially compromised) renderer process.
class ExtensionSecurityExploitBrowserTest : public ExtensionBrowserTest {
public:
ExtensionSecurityExploitBrowserTest() = default;
void SetUpOnMainThread() override {
ExtensionBrowserTest::SetUpOnMainThread();
host_resolver()->AddRule("*", "127.0.0.1");
content::SetupCrossSiteRedirector(embedded_test_server());
ASSERT_TRUE(embedded_test_server()->Start());
InstallTestExtensions();
}
// Asks the `extension_id` to inject `content_script` into `web_contents`.
// Returns true if the content script execution started successfully.
bool ExecuteProgrammaticContentScript(content::WebContents* web_contents,
const ExtensionId& extension_id,
const std::string& content_script) {
DCHECK(web_contents);
int tab_id = ExtensionTabUtil::GetTabId(web_contents);
std::string background_script = content::JsReplace(
"chrome.tabs.executeScript($1, { code: $2 });", tab_id, content_script);
return browsertest_util::ExecuteScriptInBackgroundPageNoWait(
browser()->profile(), extension_id, background_script);
}
const ExtensionId& active_extension_id() { return active_extension_->id(); }
const ExtensionId& spoofed_extension_id() { return spoofed_extension_->id(); }
private:
void InstallTestExtensions() {
// Install an `active_extension` and a separate, but otherwise identical
// `spoofed_extension` (the only difference will be the extension id).
auto install_extension = [this](TestExtensionDir& dir) -> const Extension* {
const char kManifestTemplate[] = R"(
{
"name": "ContentScriptTrackerBrowserTest - Programmatic",
"version": "1.0",
"manifest_version": 2,
"permissions": [ "tabs", "<all_urls>", "storage" ],
"background": {"scripts": ["background_script.js"]}
} )";
dir.WriteManifest(kManifestTemplate);
dir.WriteFile(FILE_PATH_LITERAL("background_script.js"), "");
return LoadExtension(dir.UnpackedPath());
};
TestExtensionDir active_dir;
TestExtensionDir spoofed_dir;
active_extension_ = install_extension(active_dir);
spoofed_extension_ = install_extension(spoofed_dir);
ASSERT_TRUE(active_extension_);
ASSERT_TRUE(spoofed_extension_);
ASSERT_NE(active_extension_id(), spoofed_extension_id());
}
const Extension* active_extension_ = nullptr;
const Extension* spoofed_extension_ = nullptr;
};
// Test suite for covering ExtensionHostMsg_OpenChannelToExtension IPC.
class OpenChannelToExtensionExploitTest
: public ExtensionSecurityExploitBrowserTest {
public:
OpenChannelToExtensionExploitTest() = default;
void SetUpOnMainThread() override {
ExtensionSecurityExploitBrowserTest::SetUpOnMainThread();
GURL test_page_url =
embedded_test_server()->GetURL("foo.com", "/title1.html");
ipc_message_waiter_ = StartInterceptingIpcs(test_page_url);
}
// Waits for ExtensionHostMsg_OpenChannelToExtension IPC and returns its
// payload.
ExtensionHostMsg_OpenChannelToExtension::Param WaitForMessage() {
return ipc_message_waiter_->WaitForMessage();
}
private:
using OpenChannelMessageWaiter =
ExtensionMessageWaiter<ExtensionHostMsg_OpenChannelToExtension>;
std::unique_ptr<OpenChannelMessageWaiter> StartInterceptingIpcs(
const GURL& test_page_url) {
// Start capturing IPC messages in all future/new RenderProcessHosts.
auto ipc_message_waiter = std::make_unique<OpenChannelMessageWaiter>();
// Navigate to an arbitrary, mostly empty test page. Make sure that a new
// RenderProcessHost is created to make sure it is covered by the
// `ipc_message_waiter`. (A WebUI -> http navigation should swap the
// RenderProcessHost on all platforms.)
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
int old_process_id =
web_contents->GetPrimaryMainFrame()->GetProcess()->GetID();
EXPECT_TRUE(
ui_test_utils::NavigateToURL(browser(), GURL("chrome://version")));
EXPECT_TRUE(ui_test_utils::NavigateToURL(browser(), test_page_url));
int new_process_id =
web_contents->GetPrimaryMainFrame()->GetProcess()->GetID();
EXPECT_NE(old_process_id, new_process_id);
// Only intercept messages from `active_extension`'s content script running
// in the main frame's process.
ExtensionId matching_extension_id = active_extension_id();
int matching_process_id = new_process_id;
ipc_message_waiter->SetIpcMatcher(base::BindLambdaForTesting(
[matching_extension_id, matching_process_id](
int captured_render_process_id,
const ExtensionHostMsg_OpenChannelToExtension::Param& param) {
if (captured_render_process_id != matching_process_id)
return false;
auto [source_context, info, channel_name, port_id] = param;
if (info.source_endpoint.extension_id != matching_extension_id)
return false;
if (info.source_endpoint.type != MessagingEndpoint::Type::kTab)
return false;
return true;
}));
return ipc_message_waiter;
}
std::unique_ptr<OpenChannelMessageWaiter> ipc_message_waiter_;
raw_ptr<const Extension> active_extension_ = nullptr;
raw_ptr<const Extension> spoofed_extension_ = nullptr;
};
IN_PROC_BROWSER_TEST_F(OpenChannelToExtensionExploitTest,
FromContentScript_BadExtensionIdInMessagingSource) {
// This test only makes sense if the enforcement is enabled.
if (!base::FeatureList::IsEnabled(
extensions_features::
kCheckingUnexpectedExtensionIdInContentScriptIpcs)) {
GTEST_SKIP() << "Content script extension ID validation disabled.";
}
// Trigger sending of a valid ExtensionHostMsg_OpenChannelToExtension IPC
// from a content script of an `active_extension_id`.
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
ASSERT_TRUE(ExecuteProgrammaticContentScript(
web_contents, active_extension_id(),
"chrome.runtime.sendMessage({greeting: 'hello'}, (response) => {});"));
// Capture the IPC.
auto [source_context, info, channel_name, port_id] = WaitForMessage();
// Mutate the IPC payload.
EXPECT_EQ(MessagingEndpoint::Type::kTab, info.source_endpoint.type);
EXPECT_EQ(active_extension_id(), info.source_endpoint.extension_id);
info.source_endpoint.extension_id = spoofed_extension_id();
// Inject the malformed/mutated IPC and verify that the renderer is terminated
// as expected.
content::RenderProcessHost* main_frame_process =
web_contents->GetPrimaryMainFrame()->GetProcess();
RenderProcessHostBadIpcMessageWaiter kill_waiter(main_frame_process);
IPC::IpcSecurityTestUtil::PwnMessageReceived(
main_frame_process->GetChannel(),
ExtensionHostMsg_OpenChannelToExtension(source_context, info,
channel_name, port_id));
EXPECT_EQ(bad_message::EMF_INVALID_EXTENSION_ID_FOR_CONTENT_SCRIPT,
kill_waiter.Wait());
}
IN_PROC_BROWSER_TEST_F(OpenChannelToExtensionExploitTest,
FromContentScript_UnexpectedNativeAppType) {
// Trigger sending of a valid ExtensionHostMsg_OpenChannelToExtension IPC
// from a content script of an `active_extension_id`.
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
ASSERT_TRUE(ExecuteProgrammaticContentScript(
web_contents, active_extension_id(),
"chrome.runtime.sendMessage({greeting: 'hello'}, (response) => {});"));
// Capture the IPC.
auto [source_context, info, channel_name, port_id] = WaitForMessage();
// Mutate the IPC payload.
EXPECT_EQ(MessagingEndpoint::Type::kTab, info.source_endpoint.type);
EXPECT_EQ(active_extension_id(), info.source_endpoint.extension_id);
info.source_endpoint.type = MessagingEndpoint::Type::kNativeApp;
// Inject the malformed/mutated IPC and verify that the renderer is terminated
// as expected.
content::RenderProcessHost* main_frame_process =
web_contents->GetPrimaryMainFrame()->GetProcess();
RenderProcessHostBadIpcMessageWaiter kill_waiter(main_frame_process);
IPC::IpcSecurityTestUtil::PwnMessageReceived(
main_frame_process->GetChannel(),
ExtensionHostMsg_OpenChannelToExtension(source_context, info,
channel_name, port_id));
EXPECT_EQ(bad_message::EMF_INVALID_CHANNEL_SOURCE_TYPE, kill_waiter.Wait());
}
IN_PROC_BROWSER_TEST_F(OpenChannelToExtensionExploitTest,
FromContentScript_UnexpectedExtensionType) {
// Trigger sending of a valid ExtensionHostMsg_OpenChannelToExtension IPC
// from a content script of an `active_extension_id`.
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
ASSERT_TRUE(ExecuteProgrammaticContentScript(
web_contents, active_extension_id(),
"chrome.runtime.sendMessage({greeting: 'hello'}, (response) => {});"));
// Capture the IPC.
auto [source_context, info, channel_name, port_id] = WaitForMessage();
// Mutate the IPC payload.
EXPECT_EQ(MessagingEndpoint::Type::kTab, info.source_endpoint.type);
EXPECT_EQ(active_extension_id(), info.source_endpoint.extension_id);
info.source_endpoint.type = MessagingEndpoint::Type::kExtension;
// Inject the malformed/mutated IPC and verify that the renderer is terminated
// as expected.
content::RenderProcessHost* main_frame_process =
web_contents->GetPrimaryMainFrame()->GetProcess();
RenderProcessHostBadIpcMessageWaiter kill_waiter(main_frame_process);
IPC::IpcSecurityTestUtil::PwnMessageReceived(
main_frame_process->GetChannel(),
ExtensionHostMsg_OpenChannelToExtension(source_context, info,
channel_name, port_id));
EXPECT_EQ(bad_message::EMF_INVALID_EXTENSION_ID_FOR_EXTENSION_SOURCE,
kill_waiter.Wait());
}
IN_PROC_BROWSER_TEST_F(OpenChannelToExtensionExploitTest,
FromContentScript_NoExtensionIdForExtensionType) {
// Trigger sending of a valid ExtensionHostMsg_OpenChannelToExtension IPC
// from a content script of an `active_extension_id`.
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
ASSERT_TRUE(ExecuteProgrammaticContentScript(
web_contents, active_extension_id(),
"chrome.runtime.sendMessage({greeting: 'hello'}, (response) => {});"));
// Capture the IPC.
auto [source_context, info, channel_name, port_id] = WaitForMessage();
// Mutate the IPC payload.
EXPECT_EQ(MessagingEndpoint::Type::kTab, info.source_endpoint.type);
EXPECT_EQ(active_extension_id(), info.source_endpoint.extension_id);
info.source_endpoint.type = MessagingEndpoint::Type::kExtension;
info.source_endpoint.extension_id = absl::nullopt;
// Inject the malformed/mutated IPC and verify that the renderer is terminated
// as expected.
content::RenderProcessHost* main_frame_process =
web_contents->GetPrimaryMainFrame()->GetProcess();
RenderProcessHostBadIpcMessageWaiter kill_waiter(main_frame_process);
IPC::IpcSecurityTestUtil::PwnMessageReceived(
main_frame_process->GetChannel(),
ExtensionHostMsg_OpenChannelToExtension(source_context, info,
channel_name, port_id));
EXPECT_EQ(bad_message::EMF_NO_EXTENSION_ID_FOR_EXTENSION_SOURCE,
kill_waiter.Wait());
}
IN_PROC_BROWSER_TEST_F(OpenChannelToExtensionExploitTest,
FromContentScript_UnexpectedWorkerContext) {
// Trigger sending of a valid ExtensionHostMsg_OpenChannelToExtension IPC
// from a content script of an `active_extension_id`.
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
ASSERT_TRUE(ExecuteProgrammaticContentScript(
web_contents, active_extension_id(),
"chrome.runtime.sendMessage({greeting: 'hello'}, (response) => {});"));
// Capture the IPC.
auto [source_context, info, channel_name, port_id] = WaitForMessage();
// Mutate the IPC payload.
EXPECT_TRUE(source_context.is_for_render_frame());
EXPECT_FALSE(source_context.is_for_service_worker());
source_context.frame = absl::nullopt;
source_context.worker = PortContext::WorkerContext(
/* thread_id = */ 123, /* version_id = */ 456,
/* extension_id = */ active_extension_id());
// Inject the malformed/mutated IPC and verify that the renderer is terminated
// as expected.
content::RenderProcessHost* main_frame_process =
web_contents->GetPrimaryMainFrame()->GetProcess();
RenderProcessHostBadIpcMessageWaiter kill_waiter(main_frame_process);
IPC::IpcSecurityTestUtil::PwnMessageReceived(
main_frame_process->GetChannel(),
ExtensionHostMsg_OpenChannelToExtension(source_context, info,
channel_name, port_id));
EXPECT_EQ(bad_message::EMF_INVALID_EXTENSION_ID_FOR_WORKER_CONTEXT,
kill_waiter.Wait());
}
IN_PROC_BROWSER_TEST_F(ExtensionSecurityExploitBrowserTest,
SpoofedExtensionId_ExtensionFunctionDispatcher) {
// Navigate to a test page.
GURL test_page_url =
embedded_test_server()->GetURL("foo.com", "/title1.html");
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), test_page_url));
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
content::RenderFrameHost* main_frame = web_contents->GetPrimaryMainFrame();
// Verify the test setup by checking if the non-intercepted `chrome.storage`
// API call will succeed.
{
ExtensionTestMessageListener listener("Got chrome.storage response");
ExecuteProgrammaticContentScript(web_contents, active_extension_id(), R"(
chrome.storage.local.set(
{ test_key: 'test value'},
() => {
chrome.test.sendMessage('Got chrome.storage response');
}
); )");
ASSERT_TRUE(listener.WaitUntilSatisfied());
}
// Prepare to mutate the extension id in the IPC associated with the
// `chrome.storage.local.set`.
auto interceptor = std::make_unique<ExtensionFrameHostInterceptor>(
main_frame,
base::BindLambdaForTesting([this](mojom::RequestParams& request_params) {
if (request_params.name != "storage.set")
return;
EXPECT_EQ(active_extension_id(), request_params.extension_id);
request_params.extension_id = spoofed_extension_id();
}));
// Trigger an IPC associated with the `chrome.storage.local.set` API and
// verify that the mutated/spoofed extension id is detected and leads to
// terminating the misbehaving renderer process.
content::RenderProcessHostBadMojoMessageWaiter kill_waiter(
main_frame->GetProcess());
ExecuteProgrammaticContentScript(web_contents, active_extension_id(), R"(
chrome.storage.local.set({ test_key: 'test value2'}, () => {}); )");
EXPECT_EQ(
"Received bad user message: LocalFrameHost::Request: renderer never "
"hosted such extension",
kill_waiter.Wait());
}
} // namespace extensions