#include <stdint.h>
#include <tuple>
#include "base/command_line.h"
#include "base/feature_list.h"
#include "base/files/file_util.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/ptr_util.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/bind.h"
#include "base/test/scoped_feature_list.h"
#include "base/unguessable_token.h"
#include "build/build_config.h"
#include "content/browser/attribution_reporting/attribution_manager.h"
#include "content/browser/bad_message.h"
#include "content/browser/child_process_security_policy_impl.h"
#include "content/browser/dom_storage/dom_storage_context_wrapper.h"
#include "content/browser/dom_storage/session_storage_namespace_impl.h"
#include "content/browser/fenced_frame/fenced_frame.h"
#include "content/browser/private_aggregation/private_aggregation_manager.h"
#include "content/browser/renderer_host/navigator.h"
#include "content/browser/renderer_host/render_frame_host_impl.h"
#include "content/browser/renderer_host/render_frame_proxy_host.h"
#include "content/browser/renderer_host/render_process_host_impl.h"
#include "content/browser/renderer_host/render_view_host_factory.h"
#include "content/browser/renderer_host/render_view_host_impl.h"
#include "content/browser/web_contents/file_chooser_impl.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/common/features.h"
#include "content/common/frame.mojom.h"
#include "content/common/frame_messages.mojom.h"
#include "content/common/render_message_filter.mojom.h"
#include "content/public/browser/blob_handle.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/content_browser_client.h"
#include "content/public/browser/file_select_listener.h"
#include "content/public/browser/navigation_handle.h"
#include "content/public/browser/resource_context.h"
#include "content/public/browser/storage_partition.h"
#include "content/public/common/bindings_policy.h"
#include "content/public/common/content_features.h"
#include "content/public/common/content_switches.h"
#include "content/public/common/url_constants.h"
#include "content/public/test/back_forward_cache_util.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/content_browser_test.h"
#include "content/public/test/content_browser_test_utils.h"
#include "content/public/test/fenced_frame_test_util.h"
#include "content/public/test/navigation_handle_observer.h"
#include "content/public/test/test_frame_navigation_observer.h"
#include "content/public/test/test_navigation_observer.h"
#include "content/public/test/test_renderer_host.h"
#include "content/public/test/test_utils.h"
#include "content/shell/browser/shell.h"
#include "content/test/content_browser_test_utils_internal.h"
#include "content/test/did_commit_navigation_interceptor.h"
#include "content/test/frame_host_interceptor.h"
#include "content/test/test_content_browser_client.h"
#include "ipc/ipc_message.h"
#include "ipc/ipc_security_test_util.h"
#include "mojo/core/embedder/embedder.h"
#include "mojo/public/cpp/bindings/pending_associated_remote.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "mojo/public/cpp/test_support/test_utils.h"
#include "net/base/features.h"
#include "net/base/filename_util.h"
#include "net/base/network_isolation_key.h"
#include "net/dns/mock_host_resolver.h"
#include "net/test/embedded_test_server/controllable_http_response.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "net/test/embedded_test_server/http_request.h"
#include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
#include "services/network/public/cpp/features.h"
#include "services/network/public/cpp/network_switches.h"
#include "services/network/public/cpp/resource_request.h"
#include "services/network/public/mojom/fetch_api.mojom.h"
#include "services/network/public/mojom/trust_tokens.mojom.h"
#include "services/network/public/mojom/url_loader.mojom.h"
#include "services/network/test/test_url_loader_client.h"
#include "storage/browser/blob/blob_registry_impl.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
#include "third_party/blink/public/common/blob/blob_utils.h"
#include "third_party/blink/public/common/fenced_frame/fenced_frame_utils.h"
#include "third_party/blink/public/common/frame/fenced_frame_sandbox_flags.h"
#include "third_party/blink/public/common/navigation/navigation_policy.h"
#include "third_party/blink/public/mojom/blob/blob_url_store.mojom.h"
#include "third_party/blink/public/mojom/choosers/file_chooser.mojom.h"
#include "third_party/blink/public/mojom/fenced_frame/fenced_frame.mojom.h"
#include "third_party/blink/public/mojom/frame/frame.mojom-test-utils.h"
#include "third_party/blink/public/mojom/frame/frame.mojom.h"
#include "third_party/blink/public/mojom/loader/mixed_content.mojom.h"
using IPC::IpcSecurityTestUtil;
using ::testing::HasSubstr;
using ::testing::Optional;
namespace content {
namespace {
RenderFrameHostImpl* PrepareToDuplicateHosts(Shell* shell,
net::EmbeddedTestServer* server,
int* target_routing_id) {
GURL foo("http://foo.com/simple_page.html");
if (IsIsolatedOriginRequiredToGuaranteeDedicatedProcess()) {
IsolateOriginsForTesting(server, shell->web_contents(), {"bar.com"});
}
EXPECT_TRUE(NavigateToURL(shell, foo));
EXPECT_EQ(u"OK", shell->web_contents()->GetTitle());
ShellAddedObserver shell2_observer;
EXPECT_TRUE(ExecJs(shell, "window.open(document.URL + '#2');"));
Shell* shell2 = shell2_observer.GetShell();
EXPECT_EQ(
shell->web_contents()->GetPrimaryMainFrame()->GetProcess()->GetID(),
shell2->web_contents()->GetPrimaryMainFrame()->GetProcess()->GetID());
*target_routing_id = shell2->web_contents()
->GetPrimaryMainFrame()
->GetRenderViewHost()
->GetRoutingID();
EXPECT_NE(*target_routing_id, shell->web_contents()
->GetPrimaryMainFrame()
->GetRenderViewHost()
->GetRoutingID());
GURL extension_url("http://bar.com/simple_page.html");
WebContentsImpl* wc = static_cast<WebContentsImpl*>(shell->web_contents());
wc->GetPrimaryFrameTree().root()->navigator().RequestOpenURL(
wc->GetPrimaryFrameTree().root()->current_frame_host(), extension_url,
nullptr ,
ChildProcessHost::kInvalidUniqueID ,
url::Origin::Create(foo), absl::nullopt,
nullptr, std::string(), Referrer(), WindowOpenDisposition::CURRENT_TAB,
false , true ,
blink::mojom::TriggeringEventInfo::kFromTrustedEvent, std::string(),
nullptr , absl::nullopt );
RenderFrameHostImpl* next_rfh = wc->GetPrimaryFrameTree()
.root()
->render_manager()
->speculative_frame_host();
EXPECT_TRUE(next_rfh);
EXPECT_NE(shell->web_contents()->GetPrimaryMainFrame()->GetProcess()->GetID(),
next_rfh->GetProcess()->GetID());
return next_rfh;
}
blink::mojom::OpenURLParamsPtr CreateOpenURLParams(const GURL& url) {
auto params = blink::mojom::OpenURLParams::New();
params->url = url;
params->disposition = WindowOpenDisposition::CURRENT_TAB;
params->should_replace_current_entry = false;
params->user_gesture = true;
return params;
}
std::unique_ptr<content::BlobHandle> CreateMemoryBackedBlob(
BrowserContext* browser_context,
const std::string& contents,
const std::string& content_type) {
std::unique_ptr<content::BlobHandle> result;
base::RunLoop loop;
browser_context->CreateMemoryBackedBlob(
base::as_bytes(base::make_span(contents)), content_type,
base::BindOnce(
[](std::unique_ptr<content::BlobHandle>* out_blob,
base::OnceClosure done,
std::unique_ptr<content::BlobHandle> blob) {
*out_blob = std::move(blob);
std::move(done).Run();
},
&result, loop.QuitClosure()));
loop.Run();
EXPECT_TRUE(result);
return result;
}
class DelayedFileChooserDelegate : public WebContentsDelegate {
public:
void Choose(const base::FilePath& file) {
auto file_info = blink::mojom::FileChooserFileInfo::NewNativeFile(
blink::mojom::NativeFileInfo::New(file, std::u16string()));
std::vector<blink::mojom::FileChooserFileInfoPtr> files;
files.push_back(std::move(file_info));
listener_->FileSelected(std::move(files), base::FilePath(),
blink::mojom::FileChooserParams::Mode::kOpen);
listener_.reset();
}
void RunFileChooser(RenderFrameHost* render_frame_host,
scoped_refptr<FileSelectListener> listener,
const blink::mojom::FileChooserParams& params) override {
listener_ = std::move(listener);
}
void EnumerateDirectory(WebContents* web_contents,
scoped_refptr<FileSelectListener> listener,
const base::FilePath& directory_path) override {
listener->FileSelectionCanceled();
}
private:
scoped_refptr<FileSelectListener> listener_;
};
void FileChooserCallback(base::RunLoop* run_loop,
blink::mojom::FileChooserResultPtr result) {
run_loop->Quit();
}
}
class SecurityExploitBrowserTest : public ContentBrowserTest {
public:
SecurityExploitBrowserTest() {}
void SetUpCommandLine(base::CommandLine* command_line) override {
ASSERT_TRUE(embedded_test_server()->InitializeAndListen());
command_line->AppendSwitchASCII(
network::switches::kHostResolverRules,
"MAP * " +
net::HostPortPair::FromURL(embedded_test_server()->base_url())
.ToString() +
",EXCLUDE localhost");
}
void SetUpOnMainThread() override {
embedded_test_server()->StartAcceptingConnections();
}
protected:
void TestFileChooserWithPath(const base::FilePath& path);
void IsolateOrigin(const std::string& hostname) {
IsolateOriginsForTesting(embedded_test_server(), shell()->web_contents(),
{hostname});
}
};
void SecurityExploitBrowserTest::TestFileChooserWithPath(
const base::FilePath& path) {
GURL foo("http://foo.com/simple_page.html");
EXPECT_TRUE(NavigateToURL(shell(), foo));
EXPECT_EQ(u"OK", shell()->web_contents()->GetTitle());
RenderFrameHost* compromised_renderer =
shell()->web_contents()->GetPrimaryMainFrame();
blink::mojom::FileChooserParamsPtr params =
blink::mojom::FileChooserParams::New();
params->default_file_name = path;
mojo::test::BadMessageObserver bad_message_observer;
mojo::Remote<blink::mojom::FileChooser> chooser =
FileChooserImpl::CreateBoundForTesting(
static_cast<RenderFrameHostImpl*>(compromised_renderer));
chooser->OpenFileChooser(
std::move(params), blink::mojom::FileChooser::OpenFileChooserCallback());
chooser.FlushForTesting();
EXPECT_THAT(bad_message_observer.WaitForBadMessage(),
::testing::StartsWith("FileChooser: The default file name"));
}
IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest, SetWebUIProperty) {
GURL foo("http://foo.com/simple_page.html");
EXPECT_TRUE(NavigateToURL(shell(), foo));
EXPECT_EQ(u"OK", shell()->web_contents()->GetTitle());
EXPECT_EQ(
0, shell()->web_contents()->GetPrimaryMainFrame()->GetEnabledBindings());
RenderFrameHost* compromised_renderer =
shell()->web_contents()->GetPrimaryMainFrame();
RenderProcessHostBadIpcMessageWaiter kill_waiter(
compromised_renderer->GetProcess());
compromised_renderer->SetWebUIProperty("toolkit", "views");
EXPECT_EQ(bad_message::RVH_WEB_UI_BINDINGS_MISMATCH, kill_waiter.Wait());
}
IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest,
AttemptDuplicateRenderViewHost) {
int32_t duplicate_routing_id = MSG_ROUTING_NONE;
RenderFrameHostImpl* pending_rfh = PrepareToDuplicateHosts(
shell(), embedded_test_server(), &duplicate_routing_id);
EXPECT_NE(MSG_ROUTING_NONE, duplicate_routing_id);
mojom::CreateNewWindowParamsPtr params = mojom::CreateNewWindowParams::New();
params->target_url = GURL("about:blank");
pending_rfh->CreateNewWindow(
std::move(params), base::BindOnce([](mojom::CreateNewWindowStatus,
mojom::CreateNewWindowReplyPtr) {}));
}
IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest, AttemptRunFileChoosers) {
TestFileChooserWithPath(base::FilePath(FILE_PATH_LITERAL("../../*.txt")));
TestFileChooserWithPath(base::FilePath(FILE_PATH_LITERAL("/etc/*.conf")));
#if BUILDFLAG(IS_WIN)
TestFileChooserWithPath(
base::FilePath(FILE_PATH_LITERAL("\\\\evilserver\\evilshare\\*.txt")));
TestFileChooserWithPath(base::FilePath(FILE_PATH_LITERAL("c:\\*.txt")));
TestFileChooserWithPath(base::FilePath(FILE_PATH_LITERAL("..\\..\\*.txt")));
#endif
}
IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest, UnexpectedMethodsSequence) {
EXPECT_TRUE(NavigateToURL(shell(), GURL("http://foo.com/simple_page.html")));
RenderFrameHost* compromised_renderer =
shell()->web_contents()->GetPrimaryMainFrame();
auto delegate = std::make_unique<DelayedFileChooserDelegate>();
shell()->web_contents()->SetDelegate(delegate.get());
mojo::Remote<blink::mojom::FileChooser> chooser =
FileChooserImpl::CreateBoundForTesting(
static_cast<RenderFrameHostImpl*>(compromised_renderer));
base::RunLoop run_loop1;
base::RunLoop run_loop2;
chooser->OpenFileChooser(blink::mojom::FileChooserParams::New(),
base::BindOnce(FileChooserCallback, &run_loop2));
chooser->EnumerateChosenDirectory(
base::FilePath(FILE_PATH_LITERAL(":*?\"<>|")),
base::BindOnce(FileChooserCallback, &run_loop1));
run_loop1.Run();
delegate->Choose(base::FilePath(FILE_PATH_LITERAL("foo.txt")));
run_loop2.Run();
}
class CorsExploitBrowserTest : public ContentBrowserTest {
public:
CorsExploitBrowserTest() = default;
CorsExploitBrowserTest(const CorsExploitBrowserTest&) = delete;
CorsExploitBrowserTest& operator=(const CorsExploitBrowserTest&) = delete;
void SetUpOnMainThread() override {
host_resolver()->AddRule("*", "127.0.0.1");
SetupCrossSiteRedirector(embedded_test_server());
}
};
IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest, MismatchedOriginOnCommit) {
GURL start_url(embedded_test_server()->GetURL("/title1.html"));
EXPECT_TRUE(NavigateToURL(shell(), start_url));
FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
->GetPrimaryFrameTree()
.root();
GURL url(embedded_test_server()->GetURL("/title2.html"));
PwnCommitIPC(shell()->web_contents(), url, url,
url::Origin::Create(GURL("http://bar.com/")));
NavigationController& controller = shell()->web_contents()->GetController();
controller.LoadURL(url, Referrer(), ui::PAGE_TRANSITION_LINK, std::string());
EXPECT_NE(nullptr, controller.GetPendingEntry());
EXPECT_EQ(url, controller.GetPendingEntry()->GetURL());
RenderProcessHostBadIpcMessageWaiter kill_waiter(
root->current_frame_host()->GetProcess());
EXPECT_TRUE(
root->current_frame_host()->GetProcess()->IsInitializedAndNotDead());
EXPECT_EQ(bad_message::RFH_INVALID_ORIGIN_ON_COMMIT, kill_waiter.Wait());
}
IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest,
MismatchedOriginOnDocumentOpenURLUpdate) {
GURL start_url(embedded_test_server()->GetURL("/title1.html"));
EXPECT_TRUE(NavigateToURL(shell(), start_url));
RenderFrameHostImpl* rfh = static_cast<RenderFrameHostImpl*>(
shell()->web_contents()->GetPrimaryMainFrame());
RenderProcessHostBadIpcMessageWaiter kill_waiter(rfh->GetProcess());
static_cast<mojom::FrameHost*>(rfh)->DidOpenDocumentInputStream(
embedded_test_server()->GetURL("evil.com", "/title1.html"));
EXPECT_EQ(AreAllSitesIsolatedForTesting()
? bad_message::RFH_CAN_COMMIT_URL_BLOCKED
: bad_message::RFH_INVALID_ORIGIN_ON_COMMIT,
kill_waiter.Wait());
}
IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest,
CrossOriginSameDocumentCommit) {
GURL start_url(embedded_test_server()->GetURL("foo.com", "/title1.html"));
EXPECT_TRUE(NavigateToURL(shell(), start_url));
GURL dest_url(embedded_test_server()->GetURL("bar.foo.com", "/title2.html"));
PwnCommitIPC(shell()->web_contents(), start_url, dest_url,
url::Origin::Create(dest_url));
RenderProcessHostBadIpcMessageWaiter kill_waiter(
shell()->web_contents()->GetPrimaryMainFrame()->GetProcess());
std::ignore = ExecJs(shell()->web_contents()->GetPrimaryMainFrame(),
"history.pushState({}, '', location.href);");
EXPECT_EQ(bad_message::RFH_INVALID_ORIGIN_ON_COMMIT, kill_waiter.Wait());
}
IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest,
CrossOriginSameDocumentCommitFromAboutBlank) {
GURL start_url(embedded_test_server()->GetURL("foo.com", "/title1.html"));
EXPECT_TRUE(NavigateToURL(shell(), start_url));
RenderFrameHost* subframe =
CreateSubframe(static_cast<WebContentsImpl*>(shell()->web_contents()),
"child1", GURL(), false );
EXPECT_EQ(url::Origin::Create(start_url), subframe->GetLastCommittedOrigin());
GURL blank_url("about:blank#foo");
GURL fake_url(embedded_test_server()->GetURL("bar.foo.com", "/"));
PwnCommitIPC(shell()->web_contents(), blank_url, blank_url,
url::Origin::Create(fake_url));
RenderProcessHostBadIpcMessageWaiter kill_waiter(subframe->GetProcess());
std::ignore = ExecJs(subframe, "location.hash='foo';");
EXPECT_EQ(bad_message::RFH_INVALID_ORIGIN_ON_COMMIT, kill_waiter.Wait());
}
IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest,
CrossOriginSameDocumentCommitUniversalAccessNonFile) {
auto prefs = shell()->web_contents()->GetOrCreateWebPreferences();
prefs.allow_universal_access_from_file_urls = true;
shell()->web_contents()->SetWebPreferences(prefs);
GURL start_url(embedded_test_server()->GetURL("foo.com", "/title1.html"));
EXPECT_TRUE(NavigateToURL(shell(), start_url));
GURL dest_url(embedded_test_server()->GetURL("bar.foo.com", "/title2.html"));
PwnCommitIPC(shell()->web_contents(), start_url, dest_url,
url::Origin::Create(start_url));
RenderProcessHostBadIpcMessageWaiter kill_waiter(
shell()->web_contents()->GetPrimaryMainFrame()->GetProcess());
std::ignore = ExecJs(shell()->web_contents()->GetPrimaryMainFrame(),
"history.pushState({}, '', location.href);");
EXPECT_EQ(bad_message::RFH_INVALID_ORIGIN_ON_COMMIT, kill_waiter.Wait());
}
namespace {
class ScopedInterfaceParamsReplacer : public DidCommitNavigationInterceptor {
public:
ScopedInterfaceParamsReplacer(
WebContents* web_contents,
mojom::DidCommitProvisionalLoadInterfaceParamsPtr params_override)
: DidCommitNavigationInterceptor(web_contents),
params_override_(std::move(params_override)) {}
ScopedInterfaceParamsReplacer(const ScopedInterfaceParamsReplacer&) = delete;
ScopedInterfaceParamsReplacer& operator=(
const ScopedInterfaceParamsReplacer&) = delete;
~ScopedInterfaceParamsReplacer() override = default;
protected:
bool WillProcessDidCommitNavigation(
RenderFrameHost* render_frame_host,
NavigationRequest* navigation_request,
mojom::DidCommitProvisionalLoadParamsPtr*,
mojom::DidCommitProvisionalLoadInterfaceParamsPtr* interface_params)
override {
interface_params->Swap(¶ms_override_);
return true;
}
private:
mojom::DidCommitProvisionalLoadInterfaceParamsPtr params_override_;
};
}
IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest,
MissingInterfaceProviderOnNonSameDocumentCommit) {
const GURL start_url(embedded_test_server()->GetURL("/title1.html"));
const GURL non_same_document_url(
embedded_test_server()->GetURL("/title2.html"));
EXPECT_TRUE(NavigateToURL(shell(), start_url));
RenderFrameHostImpl* frame = static_cast<RenderFrameHostImpl*>(
shell()->web_contents()->GetPrimaryMainFrame());
RenderProcessHostBadIpcMessageWaiter kill_waiter(frame->GetProcess());
NavigationHandleObserver navigation_observer(shell()->web_contents(),
non_same_document_url);
ScopedInterfaceParamsReplacer replacer(shell()->web_contents(), nullptr);
EXPECT_TRUE(NavigateToURLAndExpectNoCommit(shell(), non_same_document_url));
EXPECT_EQ(bad_message::RFH_INTERFACE_PROVIDER_MISSING, kill_waiter.Wait());
EXPECT_FALSE(frame->HasPendingCommitNavigation());
EXPECT_FALSE(navigation_observer.has_committed());
EXPECT_TRUE(navigation_observer.is_error());
EXPECT_TRUE(navigation_observer.last_committed_url().is_empty());
EXPECT_EQ(net::OK, navigation_observer.net_error_code());
}
IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest,
OpenUrl_ResourceRequestBody) {
GURL start_url(embedded_test_server()->GetURL("/title1.html"));
GURL target_url(embedded_test_server()->GetURL("/echoall"));
EXPECT_TRUE(NavigateToURL(shell(), start_url));
FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
->GetPrimaryFrameTree()
.root();
RenderProcessHostBadIpcMessageWaiter kill_waiter(
root->current_frame_host()->GetProcess());
base::ScopedAllowBlockingForTesting allow_blocking;
base::ScopedTempDir temp_dir;
base::FilePath file_path;
std::string file_content("test-file-content");
ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
ASSERT_TRUE(base::CreateTemporaryFileInDir(temp_dir.GetPath(), &file_path));
ASSERT_TRUE(base::WriteFile(file_path, file_content));
auto params = CreateOpenURLParams(target_url);
params->post_body = new network::ResourceRequestBody;
params->post_body->AppendFileRange(file_path, 0, file_content.size(),
base::Time());
params->should_replace_current_entry = true;
static_cast<mojom::FrameHost*>(root->current_frame_host())
->OpenURL(std::move(params));
EXPECT_EQ(start_url, root->current_frame_host()->GetLastCommittedURL());
EXPECT_EQ(bad_message::ILLEGAL_UPLOAD_PARAMS, kill_waiter.Wait());
}
IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest,
NonInitialAboutBlankRendererKill) {
GURL url(embedded_test_server()->GetURL("/title1.html"));
EXPECT_TRUE(NavigateToURL(shell(), url));
RenderFrameHostImpl* rfh = static_cast<RenderFrameHostImpl*>(
shell()->web_contents()->GetPrimaryMainFrame());
auto params = mojom::DidCommitProvisionalLoadParams::New();
params->did_create_new_entry = false;
params->url = GURL("about:blank");
params->referrer = blink::mojom::Referrer::New();
params->transition = ui::PAGE_TRANSITION_LINK;
params->should_update_history = false;
params->method = "GET";
params->page_state = blink::PageState::CreateFromURL(GURL("about:blank"));
params->origin = url::Origin::Create(GURL("about:blank"));
params->embedding_token = base::UnguessableToken::Create();
RenderProcessHostBadIpcMessageWaiter kill_waiter(rfh->GetProcess());
static_cast<mojom::FrameHost*>(rfh)->DidCommitProvisionalLoad(
std::move(params),
mojom::DidCommitProvisionalLoadInterfaceParams::New(
mojo::PendingRemote<blink::mojom::BrowserInterfaceBroker>()
.InitWithNewPipeAndPassReceiver()));
EXPECT_EQ(bad_message::RFH_NO_MATCHING_NAVIGATION_REQUEST_ON_COMMIT,
kill_waiter.Wait());
}
enum class SecurityExploitBrowserTestMojoBlobURLsTestCase {
kSupportPartitionedBlobUrlDisabled,
kSupportPartitionedBlobUrlEnabled,
};
class SecurityExploitBrowserTestMojoBlobURLsP
: public SecurityExploitBrowserTest,
public testing::WithParamInterface<
SecurityExploitBrowserTestMojoBlobURLsTestCase> {
public:
SecurityExploitBrowserTestMojoBlobURLsP() = default;
void SetUp() override {
test_case_ = GetParam();
scoped_feature_list_.InitWithFeatureState(
net::features::kSupportPartitionedBlobUrl,
test_case_ == SecurityExploitBrowserTestMojoBlobURLsTestCase::
kSupportPartitionedBlobUrlEnabled);
SecurityExploitBrowserTest::SetUp();
}
void TearDown() override {
if (base::FeatureList::IsEnabled(
net::features::kSupportPartitionedBlobUrl)) {
storage::BlobUrlRegistry::SetURLStoreCreationHookForTesting(nullptr);
} else {
storage::BlobRegistryImpl::SetURLStoreCreationHookForTesting(nullptr);
}
}
private:
SecurityExploitBrowserTestMojoBlobURLsTestCase test_case_;
base::test::ScopedFeatureList scoped_feature_list_;
};
IN_PROC_BROWSER_TEST_P(SecurityExploitBrowserTestMojoBlobURLsP,
CreateMojoBlobURLInDifferentOrigin) {
IsolateAllSitesForTesting(base::CommandLine::ForCurrentProcess());
GURL main_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
EXPECT_TRUE(NavigateToURL(shell(), main_url));
RenderFrameHost* rfh = shell()->web_contents()->GetPrimaryMainFrame();
std::string target_origin = "http://b.com";
std::string blob_path = "5881f76e-10d2-410d-8c61-ef210502acfd";
base::RepeatingCallback<void(storage::BlobUrlRegistry*, mojo::ReceiverId)>
blob_url_registry_intercept_hook;
base::RepeatingCallback<void(
mojo::SelfOwnedAssociatedReceiverRef<blink::mojom::BlobURLStore>)>
blob_registry_impl_intercept_hook;
if (base::FeatureList::IsEnabled(net::features::kSupportPartitionedBlobUrl)) {
blob_url_registry_intercept_hook =
base::BindRepeating(&BlobURLStoreInterceptor::Intercept,
GURL("blob:" + target_origin + "/" + blob_path));
storage::BlobUrlRegistry::SetURLStoreCreationHookForTesting(
&blob_url_registry_intercept_hook);
} else {
blob_registry_impl_intercept_hook =
base::BindRepeating(&BlobURLStoreInterceptor::InterceptDeprecated,
GURL("blob:" + target_origin + "/" + blob_path));
storage::BlobRegistryImpl::SetURLStoreCreationHookForTesting(
&blob_registry_impl_intercept_hook);
}
content::RenderProcessHostBadMojoMessageWaiter crash_observer(
rfh->GetProcess());
std::ignore = ExecJs(rfh, "URL.createObjectURL(new Blob(['foo']))");
EXPECT_EQ(
"Received bad user message: "
"URL with invalid origin passed to BlobURLStore::Register",
crash_observer.Wait());
}
IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest,
CreateFilesystemURLInDifferentOrigin) {
IsolateAllSitesForTesting(base::CommandLine::ForCurrentProcess());
GURL main_url(embedded_test_server()->GetURL(
"a.com", "/cross_site_iframe_factory.html?a(b)"));
EXPECT_TRUE(NavigateToURL(shell(), main_url));
RenderFrameHostImpl* rfh = static_cast<RenderFrameHostImpl*>(
shell()->web_contents()->GetPrimaryMainFrame());
rfh->ExecuteJavaScriptWithUserGestureForTests(
u"var r = new XMLHttpRequest();"
u"r.open('GET', '/slow?99999', false);"
u"r.send(null);"
u"while (1);",
base::NullCallback());
std::string payload = "<html><body>pwned.</body></html>";
std::string payload_type = "text/html";
std::unique_ptr<content::BlobHandle> blob = CreateMemoryBackedBlob(
rfh->GetSiteInstance()->GetBrowserContext(), payload, payload_type);
std::string blob_id = blob->GetUUID();
std::string target_origin = "http://b.com";
GURL target_url =
GURL("filesystem:" + target_origin + "/temporary/exploit.html");
PwnMessageHelper::FileSystemCreate(rfh->GetProcess(), 23, target_url, false,
false, false, rfh->storage_key());
PwnMessageHelper::FileSystemWrite(rfh->GetProcess(), 24, target_url, blob_id,
0, rfh->storage_key());
TestNavigationObserver observer(shell()->web_contents());
FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
->GetPrimaryFrameTree()
.root();
NavigateFrameToURL(root->child_at(0), target_url);
EXPECT_FALSE(observer.last_navigation_succeeded());
EXPECT_EQ(net::ERR_FILE_NOT_FOUND, observer.last_net_error_code());
RenderFrameHost* attacked_rfh = root->child_at(0)->current_frame_host();
std::string body =
EvalJs(attacked_rfh, "document.body.innerText").ExtractString();
EXPECT_TRUE(base::StartsWith(body, "Could not load the requested resource",
base::CompareCase::INSENSITIVE_ASCII))
<< " body=" << body;
}
IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest,
BlockIllegalOpenURLFromRemoteFrame) {
IsolateOrigin("a.com");
GURL main_url(embedded_test_server()->GetURL(
"a.com", "/cross_site_iframe_factory.html?a(b)"));
EXPECT_TRUE(NavigateToURL(shell(), main_url));
FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
->GetPrimaryFrameTree()
.root();
FrameTreeNode* child = root->child_at(0);
SiteInstanceImpl* a_com_instance =
root->current_frame_host()->GetSiteInstance();
RenderFrameProxyHost* proxy =
child->current_frame_host()
->browsing_context_state()
->GetRenderFrameProxyHost(a_com_instance->group());
EXPECT_TRUE(proxy);
TestNavigationObserver observer(shell()->web_contents());
static_cast<mojom::FrameHost*>(proxy->frame_tree_node()->current_frame_host())
->OpenURL(CreateOpenURLParams(GURL("file:///")));
observer.Wait();
EXPECT_EQ(GURL(kBlockedURL),
child->current_frame_host()->GetLastCommittedURL());
EXPECT_TRUE(NavigateToURL(shell(), main_url));
child = root->child_at(0);
proxy = child->current_frame_host()
->browsing_context_state()
->GetRenderFrameProxyHost(a_com_instance->group());
EXPECT_TRUE(proxy);
TestNavigationObserver observer_2(shell()->web_contents());
GURL chrome_url(std::string(kChromeUIScheme) + "://" +
std::string(kChromeUIGpuHost));
static_cast<mojom::FrameHost*>(proxy->frame_tree_node()->current_frame_host())
->OpenURL(CreateOpenURLParams(chrome_url));
observer_2.Wait();
EXPECT_EQ(GURL(kBlockedURL),
child->current_frame_host()->GetLastCommittedURL());
}
class RemoteFrameHostInterceptor
: public blink::mojom::RemoteFrameHostInterceptorForTesting {
public:
explicit RemoteFrameHostInterceptor(
RenderFrameProxyHost* render_frame_proxy_host,
const std::u16string& evil_origin)
: render_frame_proxy_host_(render_frame_proxy_host),
evil_origin_(evil_origin),
swapped_impl_(
render_frame_proxy_host_->frame_host_receiver_for_testing(),
this) {}
~RemoteFrameHostInterceptor() override = default;
RemoteFrameHost* GetForwardingInterface() override {
return render_frame_proxy_host_;
}
void RouteMessageEvent(
const absl::optional<blink::LocalFrameToken>& source_frame_token,
const std::u16string& source_origin,
const std::u16string& target_origin,
blink::TransferableMessage message) override {
GetForwardingInterface()->RouteMessageEvent(
std::move(source_frame_token), std::move(evil_origin_),
std::move(target_origin), std::move(message));
}
void OpenURL(blink::mojom::OpenURLParamsPtr params) override {
intercepted_params_ = std::move(params);
}
blink::mojom::OpenURLParamsPtr GetInterceptedParams() {
return std::move(intercepted_params_);
}
private:
raw_ptr<RenderFrameProxyHost> render_frame_proxy_host_;
std::u16string evil_origin_;
blink::mojom::OpenURLParamsPtr intercepted_params_;
mojo::test::ScopedSwapImplForTesting<
mojo::AssociatedReceiver<blink::mojom::RemoteFrameHost>>
swapped_impl_;
};
IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest, PostMessageSourceOrigin) {
IsolateOrigin("b.com");
GURL main_url(embedded_test_server()->GetURL(
"a.com", "/cross_site_iframe_factory.html?a(b)"));
EXPECT_TRUE(NavigateToURL(shell(), main_url));
WebContents* web_contents = shell()->web_contents();
RenderFrameHost* main_frame = web_contents->GetPrimaryMainFrame();
RenderFrameHost* subframe = ChildFrameAt(main_frame, 0);
EXPECT_NE(main_frame->GetProcess(), subframe->GetProcess());
FrameTreeNode* main_frame_node =
static_cast<WebContentsImpl*>(shell()->web_contents())
->GetPrimaryFrameTree()
.root();
FrameTreeNode* subframe_node = main_frame_node->child_at(0);
SiteInstanceImpl* b_com_instance =
subframe_node->current_frame_host()->GetSiteInstance();
RenderFrameProxyHost* main_frame_proxy_host =
main_frame_node->current_frame_host()
->browsing_context_state()
->GetRenderFrameProxyHost(b_com_instance->group());
url::Origin invalid_origin =
web_contents->GetPrimaryMainFrame()->GetLastCommittedOrigin();
std::u16string evil_source_origin =
base::UTF8ToUTF16(invalid_origin.Serialize());
RemoteFrameHostInterceptor mojo_interceptor(main_frame_proxy_host,
evil_source_origin);
RenderProcessHostBadIpcMessageWaiter kill_waiter(subframe->GetProcess());
EXPECT_TRUE(ExecJs(subframe, "parent.postMessage('blah', '*')"));
EXPECT_EQ(bad_message::RFPH_POST_MESSAGE_INVALID_SOURCE_ORIGIN,
kill_waiter.Wait());
}
IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest,
InvalidRemoteNavigationInitiator) {
IsolateOrigin("a.com");
GURL main_url(embedded_test_server()->GetURL(
"a.com", "/cross_site_iframe_factory.html?a(b)"));
EXPECT_TRUE(NavigateToURL(shell(), main_url));
RenderFrameHostImpl* main_frame = static_cast<RenderFrameHostImpl*>(
shell()->web_contents()->GetPrimaryMainFrame());
RenderProcessHost* main_process = main_frame->GetProcess();
RenderFrameHost* subframe = ChildFrameAt(main_frame, 0);
ASSERT_TRUE(subframe);
RenderProcessHost* subframe_process = subframe->GetProcess();
EXPECT_NE(main_process->GetID(), subframe_process->GetID());
FrameTreeNode* main_frame_node =
static_cast<WebContentsImpl*>(shell()->web_contents())
->GetPrimaryFrameTree()
.root();
FrameTreeNode* child_node = main_frame_node->child_at(0);
SiteInstanceImpl* a_com_instance =
main_frame_node->current_frame_host()->GetSiteInstance();
RenderFrameProxyHost* proxy =
child_node->current_frame_host()
->browsing_context_state()
->GetRenderFrameProxyHost(a_com_instance->group());
RenderProcessHostBadIpcMessageWaiter kill_waiter(main_process);
{
RemoteFrameHostInterceptor interceptor(proxy, std::u16string());
EXPECT_TRUE(ExecJs(shell()->web_contents()->GetPrimaryMainFrame(),
"window.frames[0].location = '/title1.html';"));
auto evil_params = interceptor.GetInterceptedParams();
evil_params->initiator_origin = subframe->GetLastCommittedOrigin();
static_cast<mojom::FrameHost*>(main_frame)->OpenURL(std::move(evil_params));
}
EXPECT_EQ(bad_message::INVALID_INITIATOR_ORIGIN, kill_waiter.Wait());
}
class BeginNavigationInitiatorReplacer : public FrameHostInterceptor {
public:
BeginNavigationInitiatorReplacer(
WebContents* web_contents,
absl::optional<url::Origin> initiator_to_inject)
: FrameHostInterceptor(web_contents),
initiator_to_inject_(initiator_to_inject) {}
BeginNavigationInitiatorReplacer(const BeginNavigationInitiatorReplacer&) =
delete;
BeginNavigationInitiatorReplacer& operator=(
const BeginNavigationInitiatorReplacer&) = delete;
bool WillDispatchBeginNavigation(
RenderFrameHost* render_frame_host,
blink::mojom::CommonNavigationParamsPtr* common_params,
blink::mojom::BeginNavigationParamsPtr* begin_params,
mojo::PendingRemote<blink::mojom::BlobURLToken>* blob_url_token,
mojo::PendingAssociatedRemote<mojom::NavigationClient>* navigation_client)
override {
if (is_activated_) {
(*common_params)->initiator_origin = initiator_to_inject_;
is_activated_ = false;
}
return true;
}
void Activate() { is_activated_ = true; }
private:
absl::optional<url::Origin> initiator_to_inject_;
bool is_activated_ = false;
};
IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest,
InvalidBeginNavigationInitiator) {
WebContentsImpl* web_contents =
static_cast<WebContentsImpl*>(shell()->web_contents());
BeginNavigationInitiatorReplacer injector(
web_contents, url::Origin::Create(GURL("http://b.com")));
IsolateOrigin("a.com");
GURL main_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
EXPECT_TRUE(NavigateToURL(web_contents, main_url));
RenderProcessHost* main_process =
web_contents->GetPrimaryMainFrame()->GetProcess();
RenderProcessHostBadIpcMessageWaiter kill_waiter(main_process);
injector.Activate();
ExecuteScriptAsync(web_contents, "window.location = '/title2.html';");
EXPECT_EQ(bad_message::INVALID_INITIATOR_ORIGIN, kill_waiter.Wait());
}
IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest,
MissingBeginNavigationInitiator) {
WebContents* web_contents = shell()->web_contents();
BeginNavigationInitiatorReplacer injector(web_contents, absl::nullopt);
GURL main_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
EXPECT_TRUE(NavigateToURL(web_contents, main_url));
RenderProcessHost* main_process =
web_contents->GetPrimaryMainFrame()->GetProcess();
RenderProcessHostBadIpcMessageWaiter kill_waiter(main_process);
injector.Activate();
ExecuteScriptAsync(web_contents, "window.location = '/title2.html';");
EXPECT_EQ(bad_message::RFHI_BEGIN_NAVIGATION_MISSING_INITIATOR_ORIGIN,
kill_waiter.Wait());
}
namespace {
class DidCommitUrlReplacer : public DidCommitNavigationInterceptor {
public:
DidCommitUrlReplacer(WebContents* web_contents, const GURL& replacement_url)
: DidCommitNavigationInterceptor(web_contents),
replacement_url_(replacement_url) {}
DidCommitUrlReplacer(const DidCommitUrlReplacer&) = delete;
DidCommitUrlReplacer& operator=(const DidCommitUrlReplacer&) = delete;
~DidCommitUrlReplacer() override = default;
protected:
bool WillProcessDidCommitNavigation(
RenderFrameHost* render_frame_host,
NavigationRequest* navigation_request,
mojom::DidCommitProvisionalLoadParamsPtr* params,
mojom::DidCommitProvisionalLoadInterfaceParamsPtr* interface_params)
override {
(**params).url = replacement_url_;
return true;
}
private:
GURL replacement_url_;
};
}
IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest, DidCommitInvalidURL) {
IsolateOrigin("foo.com");
RenderFrameDeletedObserver initial_frame_deleted_observer(
shell()->web_contents()->GetPrimaryMainFrame());
DisableBackForwardCacheForTesting(shell()->web_contents(),
BackForwardCache::TEST_REQUIRES_NO_CACHING);
GURL foo_url(embedded_test_server()->GetURL("foo.com", "/title1.html"));
EXPECT_TRUE(NavigateToURL(shell(), foo_url));
initial_frame_deleted_observer.WaitUntilDeleted();
GURL bar_url(embedded_test_server()->GetURL("bar.com", "/title3.html"));
DidCommitUrlReplacer url_replacer(shell()->web_contents(), bar_url);
RenderProcessHostBadIpcMessageWaiter kill_waiter(
shell()->web_contents()->GetPrimaryMainFrame()->GetProcess());
EXPECT_FALSE(NavigateToURL(
shell(), embedded_test_server()->GetURL("foo.com", "/title2.html")));
EXPECT_EQ(bad_message::RFH_CAN_COMMIT_URL_BLOCKED, kill_waiter.Wait());
}
IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest,
DidCommitInvalidURLWithOpaqueOrigin) {
IsolateOrigin("foo.com");
RenderFrameDeletedObserver initial_frame_deleted_observer(
shell()->web_contents()->GetPrimaryMainFrame());
DisableBackForwardCacheForTesting(shell()->web_contents(),
BackForwardCache::TEST_REQUIRES_NO_CACHING);
GURL foo_url(embedded_test_server()->GetURL("foo.com",
"/page_with_blank_iframe.html"));
EXPECT_TRUE(NavigateToURL(shell(), foo_url));
initial_frame_deleted_observer.WaitUntilDeleted();
GURL bar_url(embedded_test_server()->GetURL("bar.com", "/title3.html"));
DidCommitUrlReplacer url_replacer(shell()->web_contents(), bar_url);
RenderProcessHostBadIpcMessageWaiter kill_waiter(
shell()->web_contents()->GetPrimaryMainFrame()->GetProcess());
GURL data_url(R"(data:text/html,%3Ch1%3EHello%2C%20World!%3C%2Fh1%3E)");
EXPECT_TRUE(BeginNavigateIframeToURL(shell()->web_contents(), "test_iframe",
data_url));
EXPECT_EQ(bad_message::RFH_CAN_COMMIT_URL_BLOCKED, kill_waiter.Wait());
}
IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest,
WebUIProcessDidCommitWebURL) {
GURL webui_url(GetWebUIURL(kChromeUIGpuHost));
EXPECT_TRUE(NavigateToURL(shell(), webui_url));
GURL web_url(embedded_test_server()->GetURL("foo.com", "/title3.html"));
DidCommitUrlReplacer url_replacer(shell()->web_contents(), web_url);
RenderProcessHostBadIpcMessageWaiter kill_waiter(
shell()->web_contents()->GetPrimaryMainFrame()->GetProcess());
GURL second_webui_url(webui_url.Resolve("/foo"));
EXPECT_FALSE(NavigateToURL(shell(), second_webui_url));
EXPECT_EQ(bad_message::RFH_CAN_COMMIT_URL_BLOCKED, kill_waiter.Wait());
}
IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest,
DidCommitNonWebUIURLInProcessWithBindings) {
if (base::FeatureList::IsEnabled(kEnsureAllowBindingsIsAlwaysForWebUI)) {
GTEST_SKIP();
}
GURL initial_url(embedded_test_server()->GetURL("foo.com", "/title1.html"));
EXPECT_TRUE(NavigateToURL(shell(), initial_url));
GURL web_url(embedded_test_server()->GetURL("foo.com", "/title2.html"));
TestNavigationManager navigation(shell()->web_contents(), web_url);
RenderProcessHostBadIpcMessageWaiter kill_waiter(
shell()->web_contents()->GetPrimaryMainFrame()->GetProcess());
shell()->LoadURL(web_url);
EXPECT_TRUE(navigation.WaitForResponse());
navigation.GetNavigationHandle()->GetRenderFrameHost()->AllowBindings(
BINDINGS_POLICY_WEB_UI);
navigation.ResumeNavigation();
EXPECT_EQ(bad_message::RFH_CAN_COMMIT_URL_BLOCKED, kill_waiter.Wait());
}
IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest, BindToWebUIFromWebViaMojo) {
GURL web_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
TestNavigationManager navigation(shell()->web_contents(), web_url);
shell()->LoadURL(web_url);
EXPECT_TRUE(navigation.WaitForResponse());
RenderFrameHostImpl* main_frame = static_cast<RenderFrameHostImpl*>(
shell()->web_contents()->GetPrimaryMainFrame());
main_frame->GetFrameBindingsControl()->EnableMojoJsBindings(nullptr);
ASSERT_TRUE(navigation.WaitForNavigationFinished());
OpenBlankWindow(static_cast<WebContentsImpl*>(shell()->web_contents()));
main_frame->DisableUnloadTimerForTesting();
DisableBackForwardCacheForTesting(shell()->web_contents(),
BackForwardCache::TEST_USES_UNLOAD_EVENT);
ASSERT_TRUE(ExecJs(main_frame, R"(
// Intentionally leak pipe as a global so it doesn't get GCed.
newMessagePipe = Mojo.createMessagePipe();
onunload = function () {
Mojo.bindInterface('mojom.ProcessInternalsHandler',
newMessagePipe.handle0);
};
)"));
GURL webui_url(
GetWebUIURL(kChromeUIProcessInternalsHost).Resolve("#general"));
RenderProcessHostBadIpcMessageWaiter kill_waiter(main_frame->GetProcess());
EXPECT_TRUE(NavigateToURL(shell(), webui_url));
EXPECT_EQ(bad_message::RFH_INVALID_WEB_UI_CONTROLLER, kill_waiter.Wait());
}
class BeginNavigationTransitionReplacer : public FrameHostInterceptor {
public:
BeginNavigationTransitionReplacer(WebContents* web_contents,
ui::PageTransition transition_to_inject)
: FrameHostInterceptor(web_contents),
transition_to_inject_(transition_to_inject) {}
BeginNavigationTransitionReplacer(const BeginNavigationTransitionReplacer&) =
delete;
BeginNavigationTransitionReplacer& operator=(
const BeginNavigationTransitionReplacer&) = delete;
bool WillDispatchBeginNavigation(
RenderFrameHost* render_frame_host,
blink::mojom::CommonNavigationParamsPtr* common_params,
blink::mojom::BeginNavigationParamsPtr* begin_params,
mojo::PendingRemote<blink::mojom::BlobURLToken>* blob_url_token,
mojo::PendingAssociatedRemote<mojom::NavigationClient>* navigation_client)
override {
if (is_activated_) {
(*common_params)->transition = transition_to_inject_;
is_activated_ = false;
}
return true;
}
void Activate() { is_activated_ = true; }
private:
ui::PageTransition transition_to_inject_;
bool is_activated_ = false;
};
IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest, NonWebbyTransition) {
const ui::PageTransition test_cases[] = {
ui::PAGE_TRANSITION_TYPED,
ui::PAGE_TRANSITION_AUTO_BOOKMARK,
ui::PAGE_TRANSITION_GENERATED,
ui::PAGE_TRANSITION_AUTO_TOPLEVEL,
ui::PAGE_TRANSITION_RELOAD,
ui::PAGE_TRANSITION_KEYWORD,
ui::PAGE_TRANSITION_KEYWORD_GENERATED};
for (ui::PageTransition transition : test_cases) {
WebContents* web_contents = shell()->web_contents();
BeginNavigationTransitionReplacer injector(web_contents, transition);
GURL main_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
EXPECT_TRUE(NavigateToURL(web_contents, main_url));
RenderProcessHost* main_process =
web_contents->GetPrimaryMainFrame()->GetProcess();
RenderProcessHostBadIpcMessageWaiter kill_waiter(main_process);
injector.Activate();
ExecuteScriptAsync(web_contents, "window.location = '/title2.html';");
EXPECT_EQ(bad_message::RFHI_BEGIN_NAVIGATION_NON_WEBBY_TRANSITION,
kill_waiter.Wait());
}
}
class SecurityExploitViaDisabledWebSecurityTest
: public SecurityExploitBrowserTest {
public:
SecurityExploitViaDisabledWebSecurityTest() {
feature_list_.InitAndEnableFeature(
features::kAllowContentInitiatedDataUrlNavigations);
}
protected:
void SetUpCommandLine(base::CommandLine* command_line) override {
command_line->AppendSwitch(switches::kDisableWebSecurity);
SecurityExploitBrowserTest::SetUpCommandLine(command_line);
}
private:
base::test::ScopedFeatureList feature_list_;
};
IN_PROC_BROWSER_TEST_F(SecurityExploitViaDisabledWebSecurityTest,
ValidateBaseUrlForDataUrl) {
GURL start_url(embedded_test_server()->GetURL("/title1.html"));
EXPECT_TRUE(NavigateToURL(shell(), start_url));
RenderFrameHostImpl* rfh = static_cast<RenderFrameHostImpl*>(
shell()->web_contents()->GetPrimaryMainFrame());
GURL data_url("data:text/html,foo");
base::FilePath file_path = GetTestFilePath("", "simple_page.html");
GURL file_url = net::FilePathToFileURL(file_path);
blink::mojom::CommonNavigationParamsPtr common_params =
blink::mojom::CommonNavigationParams::New(
data_url, url::Origin::Create(data_url),
absl::nullopt,
blink::mojom::Referrer::New(), ui::PAGE_TRANSITION_LINK,
blink::mojom::NavigationType::DIFFERENT_DOCUMENT,
blink::NavigationDownloadPolicy(),
false ,
file_url,
base::TimeTicks::Now() , "GET",
nullptr , network::mojom::SourceLocation::New(),
false , false ,
false ,
network::mojom::CSPDisposition::CHECK,
std::vector<int>() ,
std::string() ,
false ,
base::TimeTicks() ,
network::mojom::RequestDestination::kDocument);
blink::mojom::BeginNavigationParamsPtr begin_params =
blink::mojom::BeginNavigationParams::New(
absl::nullopt ,
std::string() , net::LOAD_NORMAL,
false ,
blink::mojom::RequestContextType::LOCATION,
blink::mojom::MixedContentContextType::kBlockable,
false ,
false ,
blink::mojom::ForceHistoryPush::kNo, GURL() ,
std::string() ,
GURL() ,
absl::nullopt ,
nullptr , absl::nullopt ,
base::TimeTicks() ,
base::TimeTicks() ,
absl::nullopt ,
blink::mojom::NavigationInitiatorActivationAndAdStatus::
kDidNotStartWithTransientActivation,
false ,
false , false );
RenderProcessHostBadIpcMessageWaiter process_kill_waiter(rfh->GetProcess());
mojo::PendingAssociatedRemote<mojom::NavigationClient> navigation_client;
auto navigation_client_receiver =
navigation_client.InitWithNewEndpointAndPassReceiver();
rfh->frame_host_receiver_for_testing().impl()->BeginNavigation(
std::move(common_params), std::move(begin_params), mojo::NullRemote(),
std::move(navigation_client), mojo::NullRemote(), mojo::NullReceiver());
EXPECT_EQ(bad_message::RFH_BASE_URL_FOR_DATA_URL_SPECIFIED,
process_kill_waiter.Wait());
EXPECT_FALSE(ChildProcessSecurityPolicyImpl::GetInstance()->CanReadFile(
rfh->GetProcess()->GetID(), file_path));
TestNavigationObserver tab_observer(shell()->web_contents(), 1);
shell()->web_contents()->GetController().Reload(ReloadType::NORMAL, false);
tab_observer.Wait();
std::string script = base::StringPrintf(
"var xhr = new XMLHttpRequest()\n"
"xhr.open('GET', '%s', false);\n"
"try { xhr.send(); } catch (e) {}\n"
"xhr.responseText;",
file_url.spec().c_str());
std::string result = EvalJs(shell()->web_contents(), script).ExtractString();
EXPECT_TRUE(result.empty());
}
IN_PROC_BROWSER_TEST_F(SecurityExploitViaDisabledWebSecurityTest,
FullscreenRequestMustBeFromEmptyDocument) {
GURL start_url(embedded_test_server()->GetURL("/title1.html"));
EXPECT_TRUE(NavigateToURL(shell(), start_url));
RenderFrameHostImpl* rfh = static_cast<RenderFrameHostImpl*>(
shell()->web_contents()->GetPrimaryMainFrame());
blink::mojom::CommonNavigationParamsPtr common_params =
blink::mojom::CommonNavigationParams::New();
common_params->url = GetTestUrl("", "simple_page.html"),
common_params->initiator_origin = url::Origin::Create(start_url);
blink::mojom::BeginNavigationParamsPtr begin_params =
blink::mojom::BeginNavigationParams::New();
begin_params->is_fullscreen_requested = true;
RenderProcessHostBadIpcMessageWaiter process_kill_waiter(rfh->GetProcess());
mojo::PendingAssociatedRemote<mojom::NavigationClient> navigation_client;
auto navigation_client_receiver =
navigation_client.InitWithNewEndpointAndPassReceiver();
rfh->frame_host_receiver_for_testing().impl()->BeginNavigation(
std::move(common_params), std::move(begin_params), mojo::NullRemote(),
std::move(navigation_client), mojo::NullRemote(), mojo::NullReceiver());
EXPECT_EQ(bad_message::RFHI_FULLSCREEN_NAV_INVALID_INITIAL_DOCUMENT,
process_kill_waiter.Wait());
}
IN_PROC_BROWSER_TEST_F(SecurityExploitViaDisabledWebSecurityTest,
WebToFileNavigation) {
GURL start_url(embedded_test_server()->GetURL("/title1.html"));
EXPECT_TRUE(NavigateToURL(shell(), start_url));
GURL file_url = GetTestUrl("", "simple_page.html");
WebContentsAddedObserver new_window_observer;
TestNavigationObserver nav_observer(nullptr);
nav_observer.StartWatchingNewWebContents();
ASSERT_TRUE(ExecJs(shell()->web_contents(),
JsReplace("window.open($1, '_blank')", file_url)));
WebContents* new_window = new_window_observer.GetWebContents();
nav_observer.WaitForNavigationFinished();
EXPECT_TRUE(nav_observer.last_navigation_succeeded());
EXPECT_EQ(GURL(kBlockedURL), nav_observer.last_navigation_url());
EXPECT_EQ(GURL(kBlockedURL),
new_window->GetPrimaryMainFrame()->GetLastCommittedURL());
EXPECT_EQ(
shell()->web_contents()->GetPrimaryMainFrame()->GetLastCommittedOrigin(),
new_window->GetPrimaryMainFrame()->GetLastCommittedOrigin());
EXPECT_EQ(shell()->web_contents()->GetPrimaryMainFrame()->GetProcess(),
new_window->GetPrimaryMainFrame()->GetProcess());
EXPECT_EQ(true, ExecJs(new_window, "!!window.opener"));
}
IN_PROC_BROWSER_TEST_F(SecurityExploitViaDisabledWebSecurityTest,
WebToViewSourceNavigation) {
GURL start_url(embedded_test_server()->GetURL("/title1.html"));
EXPECT_TRUE(NavigateToURL(shell(), start_url));
base::FilePath file_path = GetTestFilePath("", "simple_page.html");
GURL view_source_url =
GURL(std::string(kViewSourceScheme) + ":" + start_url.spec());
WebContentsAddedObserver new_window_observer;
TestNavigationObserver nav_observer(nullptr);
nav_observer.StartWatchingNewWebContents();
ASSERT_TRUE(ExecJs(shell()->web_contents(),
JsReplace("window.open($1, '_blank')", view_source_url)));
WebContents* new_window = new_window_observer.GetWebContents();
nav_observer.WaitForNavigationFinished();
EXPECT_TRUE(nav_observer.last_navigation_succeeded());
EXPECT_EQ(GURL(kBlockedURL), nav_observer.last_navigation_url());
EXPECT_EQ(GURL(kBlockedURL),
new_window->GetPrimaryMainFrame()->GetLastCommittedURL());
EXPECT_EQ(
shell()->web_contents()->GetPrimaryMainFrame()->GetLastCommittedOrigin(),
new_window->GetPrimaryMainFrame()->GetLastCommittedOrigin());
EXPECT_EQ(shell()->web_contents()->GetPrimaryMainFrame()->GetProcess(),
new_window->GetPrimaryMainFrame()->GetProcess());
EXPECT_EQ(true, ExecJs(new_window, "!!window.opener"));
}
class BeginNavigationTrustTokenParamsReplacer : public FrameHostInterceptor {
public:
BeginNavigationTrustTokenParamsReplacer(
WebContents* web_contents,
network::mojom::TrustTokenParamsPtr params_to_inject)
: FrameHostInterceptor(web_contents),
params_to_inject_(std::move(params_to_inject)) {}
BeginNavigationTrustTokenParamsReplacer(
const BeginNavigationTrustTokenParamsReplacer&) = delete;
BeginNavigationTrustTokenParamsReplacer& operator=(
const BeginNavigationTrustTokenParamsReplacer&) = delete;
bool WillDispatchBeginNavigation(
RenderFrameHost* render_frame_host,
blink::mojom::CommonNavigationParamsPtr* common_params,
blink::mojom::BeginNavigationParamsPtr* begin_params,
mojo::PendingRemote<blink::mojom::BlobURLToken>* blob_url_token,
mojo::PendingAssociatedRemote<mojom::NavigationClient>* navigation_client)
override {
if (is_activated_) {
(*begin_params)->trust_token_params = params_to_inject_.Clone();
is_activated_ = false;
}
return true;
}
void Activate() { is_activated_ = true; }
private:
network::mojom::TrustTokenParamsPtr params_to_inject_;
bool is_activated_ = false;
};
class SecurityExploitBrowserTestWithTrustTokensEnabled
: public SecurityExploitBrowserTest {
public:
SecurityExploitBrowserTestWithTrustTokensEnabled() {
feature_list_.InitAndEnableFeature(network::features::kPrivateStateTokens);
}
private:
base::test::ScopedFeatureList feature_list_;
};
IN_PROC_BROWSER_TEST_F(
SecurityExploitBrowserTestWithTrustTokensEnabled,
BrowserForbidsTrustTokenRedemptionWithoutPermissionsPolicy) {
WebContents* web_contents = shell()->web_contents();
auto params = network::mojom::TrustTokenParams::New();
params->version =
network::mojom::TrustTokenMajorVersion::kPrivateStateTokenV1;
params->operation = network::mojom::TrustTokenOperationType::kRedemption;
BeginNavigationTrustTokenParamsReplacer replacer(web_contents,
std::move(params));
GURL start_url(embedded_test_server()->GetURL(
"/page-with-trust-token-permissions-policy-disabled.html"));
EXPECT_TRUE(NavigateToURL(shell(), start_url));
RenderFrameHost* parent = web_contents->GetPrimaryMainFrame();
ASSERT_FALSE(parent->IsFeatureEnabled(
blink::mojom::PermissionsPolicyFeature::kTrustTokenRedemption));
RenderFrameHost* child = static_cast<WebContentsImpl*>(web_contents)
->GetPrimaryFrameTree()
.root()
->child_at(0)
->current_frame_host();
RenderProcessHostBadMojoMessageWaiter kill_waiter(child->GetProcess());
replacer.Activate();
EXPECT_TRUE(BeginNavigateToURLFromRenderer(child, GURL("/title2.html")));
EXPECT_THAT(kill_waiter.Wait(),
Optional(HasSubstr("Permissions Policy feature is absent")));
}
IN_PROC_BROWSER_TEST_F(
SecurityExploitBrowserTestWithTrustTokensEnabled,
BrowserForbidsTrustTokenSigningWithoutPermissionsPolicy) {
WebContents* web_contents = shell()->web_contents();
auto params = network::mojom::TrustTokenParams::New();
params->version =
network::mojom::TrustTokenMajorVersion::kPrivateStateTokenV1;
params->operation = network::mojom::TrustTokenOperationType::kSigning;
BeginNavigationTrustTokenParamsReplacer replacer(web_contents,
std::move(params));
GURL start_url(embedded_test_server()->GetURL(
"/page-with-trust-token-permissions-policy-disabled.html"));
EXPECT_TRUE(NavigateToURL(shell(), start_url));
RenderFrameHost* parent = web_contents->GetPrimaryMainFrame();
ASSERT_FALSE(parent->IsFeatureEnabled(
blink::mojom::PermissionsPolicyFeature::kTrustTokenRedemption));
RenderFrameHost* child = static_cast<WebContentsImpl*>(web_contents)
->GetPrimaryFrameTree()
.root()
->child_at(0)
->current_frame_host();
RenderProcessHostBadMojoMessageWaiter kill_waiter(child->GetProcess());
replacer.Activate();
EXPECT_TRUE(BeginNavigateToURLFromRenderer(child, GURL("/title2.html")));
EXPECT_THAT(kill_waiter.Wait(),
Optional(HasSubstr("Permissions Policy feature is absent")));
}
IN_PROC_BROWSER_TEST_F(
SecurityExploitBrowserTestWithTrustTokensEnabled,
BrowserForbidsTrustTokenIssuanceWithoutPermissionsPolicy) {
WebContents* web_contents = shell()->web_contents();
auto params = network::mojom::TrustTokenParams::New();
params->version =
network::mojom::TrustTokenMajorVersion::kPrivateStateTokenV1;
params->operation = network::mojom::TrustTokenOperationType::kIssuance;
BeginNavigationTrustTokenParamsReplacer replacer(web_contents,
std::move(params));
GURL start_url(embedded_test_server()->GetURL(
"/page-with-trust-token-permissions-policy-disabled.html"));
EXPECT_TRUE(NavigateToURL(shell(), start_url));
RenderFrameHost* parent = web_contents->GetPrimaryMainFrame();
ASSERT_FALSE(parent->IsFeatureEnabled(
blink::mojom::PermissionsPolicyFeature::kPrivateStateTokenIssuance));
RenderFrameHost* child = static_cast<WebContentsImpl*>(web_contents)
->GetPrimaryFrameTree()
.root()
->child_at(0)
->current_frame_host();
RenderProcessHostBadMojoMessageWaiter kill_waiter(child->GetProcess());
replacer.Activate();
EXPECT_TRUE(BeginNavigateToURLFromRenderer(child, GURL("/title2.html")));
EXPECT_THAT(kill_waiter.Wait(),
Optional(HasSubstr("Permissions Policy feature is absent")));
}
IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTestWithTrustTokensEnabled,
BrowserForbidsTrustTokenParamsOnMainFrameNav) {
WebContents* web_contents = shell()->web_contents();
BeginNavigationTrustTokenParamsReplacer replacer(
web_contents, network::mojom::TrustTokenParams::New());
GURL start_url(embedded_test_server()->GetURL("/title1.html"));
EXPECT_TRUE(NavigateToURL(shell(), start_url));
RenderFrameHost* compromised_renderer = web_contents->GetPrimaryMainFrame();
RenderProcessHostBadMojoMessageWaiter kill_waiter(
compromised_renderer->GetProcess());
replacer.Activate();
std::ignore = ExecJs(compromised_renderer,
JsReplace("location.href=$1", GURL("/title2.html")));
EXPECT_THAT(
kill_waiter.Wait(),
Optional(HasSubstr("Private State Token params in main frame nav")));
}
class FencedFrameSecurityExploitBrowserTestWithTrustTokensEnabled
: public SecurityExploitBrowserTestWithTrustTokensEnabled {
protected:
FencedFrameSecurityExploitBrowserTestWithTrustTokensEnabled() = default;
WebContentsImpl* web_contents() {
return static_cast<WebContentsImpl*>(shell()->web_contents());
}
RenderFrameHostImpl* primary_main_frame_host() {
return web_contents()->GetPrimaryMainFrame();
}
test::FencedFrameTestHelper& fenced_frame_test_helper() {
return fenced_frame_test_helper_;
}
private:
test::FencedFrameTestHelper fenced_frame_test_helper_;
};
class FencedFrameBeginNavigationTrustTokenParamsReplacer
: public BeginNavigationTrustTokenParamsReplacer {
public:
FencedFrameBeginNavigationTrustTokenParamsReplacer(
WebContents* web_contents,
network::mojom::TrustTokenParamsPtr params_to_inject)
: BeginNavigationTrustTokenParamsReplacer(web_contents,
std::move(params_to_inject)) {}
FencedFrameBeginNavigationTrustTokenParamsReplacer(
const FencedFrameBeginNavigationTrustTokenParamsReplacer&) = delete;
FencedFrameBeginNavigationTrustTokenParamsReplacer& operator=(
const FencedFrameBeginNavigationTrustTokenParamsReplacer&) = delete;
bool WillDispatchBeginNavigation(
RenderFrameHost* render_frame_host,
blink::mojom::CommonNavigationParamsPtr* common_params,
blink::mojom::BeginNavigationParamsPtr* begin_params,
mojo::PendingRemote<blink::mojom::BlobURLToken>* blob_url_token,
mojo::PendingAssociatedRemote<mojom::NavigationClient>* navigation_client)
override {
if (render_frame_host->IsFencedFrameRoot()) {
BeginNavigationTrustTokenParamsReplacer::WillDispatchBeginNavigation(
render_frame_host, common_params, begin_params, blob_url_token,
navigation_client);
}
return true;
}
};
IN_PROC_BROWSER_TEST_F(
FencedFrameSecurityExploitBrowserTestWithTrustTokensEnabled,
BrowserForbidsTrustTokenParamsOnFencedFrameNav) {
WebContents* web_contents = shell()->web_contents();
FencedFrameBeginNavigationTrustTokenParamsReplacer replacer(
web_contents, network::mojom::TrustTokenParams::New());
GURL start_url(embedded_test_server()->GetURL("/empty.html"));
EXPECT_TRUE(NavigateToURL(shell(), start_url));
RenderFrameHostImplWrapper primary_rfh(primary_main_frame_host());
RenderFrameHostImplWrapper inner_fenced_frame_rfh(
fenced_frame_test_helper().CreateFencedFrame(
primary_rfh.get(),
embedded_test_server()->GetURL("/fenced_frames/empty.html")));
RenderFrameHost* compromised_renderer = inner_fenced_frame_rfh.get();
RenderProcessHostBadMojoMessageWaiter kill_waiter(
compromised_renderer->GetProcess());
replacer.Activate();
std::ignore = ExecJs(
compromised_renderer,
JsReplace("location.href=$1",
embedded_test_server()->GetURL("/fenced_frames/title1.html")));
absl::optional<std::string> result = kill_waiter.Wait();
EXPECT_THAT(result,
Optional(HasSubstr("Private State Token params in fenced frame "
"nav")));
}
class SecurityExploitTestFencedFramesDisabled
: public SecurityExploitBrowserTest {
public:
SecurityExploitTestFencedFramesDisabled() {
feature_list_.InitAndDisableFeature(blink::features::kFencedFrames);
}
private:
base::test::ScopedFeatureList feature_list_;
};
IN_PROC_BROWSER_TEST_F(SecurityExploitTestFencedFramesDisabled,
CreateFencedFrameWhenFeatureDisabled) {
GURL foo("http://foo.com/simple_page.html");
EXPECT_TRUE(NavigateToURL(shell(), foo));
EXPECT_EQ(u"OK", shell()->web_contents()->GetTitle());
EXPECT_FALSE(blink::features::IsFencedFramesEnabled());
RenderFrameHostImpl* compromised_rfh = static_cast<RenderFrameHostImpl*>(
shell()->web_contents()->GetPrimaryMainFrame());
mojo::PendingAssociatedRemote<blink::mojom::FencedFrameOwnerHost> remote;
mojo::PendingAssociatedReceiver<blink::mojom::FencedFrameOwnerHost> receiver;
receiver = remote.InitWithNewEndpointAndPassReceiver();
auto remote_frame_interfaces =
blink::mojom::RemoteFrameInterfacesFromRenderer::New();
remote_frame_interfaces->frame_host_receiver =
mojo::AssociatedRemote<blink::mojom::RemoteFrameHost>()
.BindNewEndpointAndPassDedicatedReceiver();
mojo::AssociatedRemote<blink::mojom::RemoteFrame> frame;
std::ignore = frame.BindNewEndpointAndPassDedicatedReceiver();
remote_frame_interfaces->frame = frame.Unbind();
RenderProcessHostBadIpcMessageWaiter kill_waiter(
compromised_rfh->GetProcess());
static_cast<blink::mojom::LocalFrameHost*>(compromised_rfh)
->CreateFencedFrame(
std::move(receiver), std::move(remote_frame_interfaces),
blink::RemoteFrameToken(), base::UnguessableToken::Create());
EXPECT_EQ(bad_message::RFH_FENCED_FRAME_MOJO_WHEN_DISABLED,
kill_waiter.Wait());
}
IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest,
UnfencedTopFromOutsideFencedFrame) {
GURL main_url(embedded_test_server()->GetURL(
"a.com", "/cross_site_iframe_factory.html?a(b)"));
EXPECT_TRUE(NavigateToURL(shell(), main_url));
FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
->GetPrimaryFrameTree()
.root();
RenderFrameHostImpl* compromised_rfh =
root->child_at(0)->current_frame_host();
RenderProcessHostBadIpcMessageWaiter kill_waiter(
compromised_rfh->GetProcess());
GURL url("http://foo.com/simple_page.html");
auto params = CreateOpenURLParams(url);
params->is_unfenced_top_navigation = true;
static_cast<mojom::FrameHost*>(compromised_rfh)->OpenURL(std::move(params));
EXPECT_EQ(bad_message::RFHI_UNFENCED_TOP_IPC_OUTSIDE_FENCED_FRAME,
kill_waiter.Wait());
}
class SecurityExploitBrowserTestFencedFrames
: public SecurityExploitBrowserTest {
public:
SecurityExploitBrowserTestFencedFrames() {
scoped_feature_list_.InitAndEnableFeatureWithParameters(
blink::features::kFencedFrames, {});
}
void SetUpOnMainThread() override {
host_resolver()->AddRule("*", "127.0.0.1");
https_server()->StartAcceptingConnections();
}
void SetUpCommandLine(base::CommandLine* command_line) override {
https_server()->AddDefaultHandlers(GetTestDataFilePath());
https_server()->ServeFilesFromSourceDirectory(GetTestDataFilePath());
https_server()->SetSSLConfig(net::EmbeddedTestServer::CERT_TEST_NAMES);
SetupCrossSiteRedirector(https_server());
ASSERT_TRUE(https_server()->InitializeAndListen());
}
test::FencedFrameTestHelper& fenced_frame_test_helper() {
return fenced_frame_test_helper_;
}
net::EmbeddedTestServer* https_server() { return &https_server_; }
private:
test::FencedFrameTestHelper fenced_frame_test_helper_{};
base::test::ScopedFeatureList scoped_feature_list_;
net::EmbeddedTestServer https_server_{net::EmbeddedTestServer::TYPE_HTTPS};
};
IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTestFencedFrames,
NavigateFencedFrameToInvalidURL) {
GURL main_frame_url(https_server()->GetURL("a.test", "/simple_page.html"));
std::vector<GURL> invalid_urls = {
GURL("http://example.com"),
GURL("http://example.com?<\n=block"),
GURL("about:srcdoc"),
GURL("data:text/html,<p>foo"),
GURL("blob:https://example.com/a9400bf5-aaa8-4166-86e4-492c50f4ca2b"),
GURL("file://folder"),
GURL("javascript:console.log('foo');"),
GetWebUIURL(kChromeUIHistogramHost),
GURL(blink::kChromeUIHangURL)};
for (const GURL& invalid_url : invalid_urls) {
EXPECT_FALSE(blink::IsValidFencedFrameURL(invalid_url));
EXPECT_TRUE(blink::features::IsFencedFramesEnabled());
EXPECT_TRUE(NavigateToURL(shell(), main_frame_url));
EXPECT_EQ(u"OK", shell()->web_contents()->GetTitle());
RenderFrameHostImpl* compromised_rfh = static_cast<RenderFrameHostImpl*>(
shell()->web_contents()->GetPrimaryMainFrame());
mojo::AssociatedRemote<blink::mojom::FencedFrameOwnerHost> remote;
mojo::PendingAssociatedReceiver<blink::mojom::FencedFrameOwnerHost>
pending_receiver = remote.BindNewEndpointAndPassReceiver();
auto remote_frame_interfaces =
blink::mojom::RemoteFrameInterfacesFromRenderer::New();
remote_frame_interfaces->frame_host_receiver =
mojo::AssociatedRemote<blink::mojom::RemoteFrameHost>()
.BindNewEndpointAndPassDedicatedReceiver();
mojo::AssociatedRemote<blink::mojom::RemoteFrame> frame;
std::ignore = frame.BindNewEndpointAndPassDedicatedReceiver();
remote_frame_interfaces->frame = frame.Unbind();
RenderProcessHostBadIpcMessageWaiter kill_waiter(
compromised_rfh->GetProcess());
static_cast<blink::mojom::LocalFrameHost*>(compromised_rfh)
->CreateFencedFrame(
std::move(pending_receiver), std::move(remote_frame_interfaces),
blink::RemoteFrameToken(), base::UnguessableToken::Create());
EXPECT_EQ(compromised_rfh->GetFencedFrames().size(), 1u);
FencedFrame* fenced_frame = compromised_rfh->GetFencedFrames()[0];
static_cast<blink::mojom::FencedFrameOwnerHost*>(fenced_frame)
->Navigate(invalid_url, base::TimeTicks(),
absl::nullopt);
EXPECT_EQ(bad_message::FF_NAVIGATION_INVALID_URL, kill_waiter.Wait());
}
}
IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTestFencedFrames,
ChangeFencedFrameSandboxFlags) {
GURL main_frame_url(https_server()->GetURL("a.test", "/simple_page.html"));
EXPECT_TRUE(NavigateToURL(shell(), main_frame_url));
RenderFrameHostImpl* root_rfh = static_cast<RenderFrameHostImpl*>(
shell()->web_contents()->GetPrimaryMainFrame());
const GURL fenced_frame_url =
https_server()->GetURL("a.test", "/fenced_frames/sandbox_flags.html");
constexpr char kAddFencedFrameScript[] = R"({
const fenced_frame = document.createElement('fencedframe');
fenced_frame.config = new FencedFrameConfig($1);
document.body.appendChild(fenced_frame);
})";
EXPECT_TRUE(
ExecJs(root_rfh, JsReplace(kAddFencedFrameScript, fenced_frame_url)));
RenderFrameHostImpl* fenced_rfh = nullptr;
RenderFrameHostImpl* parent_rfh = nullptr;
std::vector<FencedFrame*> fenced_frames = root_rfh->GetFencedFrames();
EXPECT_EQ(fenced_frames.size(), 1u);
FencedFrame* new_fenced_frame = fenced_frames.back();
fenced_rfh = new_fenced_frame->GetInnerRoot();
parent_rfh = fenced_rfh;
RenderProcessHostBadIpcMessageWaiter kill_waiter(fenced_rfh->GetProcess());
blink::FramePolicy first_policy =
fenced_rfh->frame_tree_node()->pending_frame_policy();
first_policy.sandbox_flags = blink::kFencedFrameMandatoryUnsandboxedFlags;
static_cast<blink::mojom::LocalFrameHost*>(parent_rfh)
->DidChangeFramePolicy(std::move(fenced_rfh->GetFrameToken()),
std::move(first_policy));
EXPECT_EQ(bad_message::RFH_SANDBOX_FLAGS, kill_waiter.Wait());
}
INSTANTIATE_TEST_SUITE_P(
SecurityExploitBrowserTestMojoBlobURLs,
SecurityExploitBrowserTestMojoBlobURLsP,
::testing::Values(SecurityExploitBrowserTestMojoBlobURLsTestCase::
kSupportPartitionedBlobUrlDisabled,
SecurityExploitBrowserTestMojoBlobURLsTestCase::
kSupportPartitionedBlobUrlEnabled));
}