|  | // Copyright (c) 2013 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 <stdint.h> | 
|  |  | 
|  | #include "base/bind.h" | 
|  | #include "base/command_line.h" | 
|  | #include "base/feature_list.h" | 
|  | #include "base/files/file_util.h" | 
|  | #include "base/macros.h" | 
|  | #include "base/memory/ptr_util.h" | 
|  | #include "base/optional.h" | 
|  | #include "base/strings/utf_string_conversions.h" | 
|  | #include "base/test/scoped_feature_list.h" | 
|  | #include "build/build_config.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/frame_host/navigator.h" | 
|  | #include "content/browser/frame_host/render_frame_host_impl.h" | 
|  | #include "content/browser/frame_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/frame.mojom-test-utils.h" | 
|  | #include "content/common/frame.mojom.h" | 
|  | #include "content/common/frame_messages.h" | 
|  | #include "content/common/render_message_filter.mojom.h" | 
|  | #include "content/common/view_messages.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/navigation_policy.h" | 
|  | #include "content/public/common/url_constants.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/navigation_handle_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/bindings/self_owned_associated_receiver.h" | 
|  | #include "mojo/public/cpp/test_support/test_utils.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/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/blink/public/common/blob/blob_utils.h" | 
|  | #include "third_party/blink/public/common/navigation/triggering_event_info.h" | 
|  | #include "third_party/blink/public/mojom/appcache/appcache.mojom.h" | 
|  | #include "third_party/blink/public/mojom/blob/blob_url_store.mojom-test-utils.h" | 
|  | #include "third_party/blink/public/mojom/choosers/file_chooser.mojom.h" | 
|  | #include "third_party/blink/public/mojom/frame/frame.mojom-test-utils.h" | 
|  |  | 
|  | using IPC::IpcSecurityTestUtil; | 
|  | using ::testing::HasSubstr; | 
|  | using ::testing::Optional; | 
|  |  | 
|  | namespace content { | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | // This is a helper function for the tests which attempt to create a | 
|  | // duplicate RenderViewHost or RenderWidgetHost. It tries to create two objects | 
|  | // with the same process and routing ids, which causes a collision. | 
|  | // It creates a couple of windows in process 1, which causes a few routing ids | 
|  | // to be allocated. Then a cross-process navigation is initiated, which causes a | 
|  | // new process 2 to be created and have a pending RenderViewHost for it. The | 
|  | // routing id of the RenderViewHost which is target for a duplicate is set | 
|  | // into |target_routing_id| and the pending RenderFrameHost which is used for | 
|  | // the attempt is the return value. | 
|  | RenderFrameHostImpl* PrepareToDuplicateHosts(Shell* shell, | 
|  | net::EmbeddedTestServer* server, | 
|  | int* target_routing_id) { | 
|  | GURL foo("http://foo.com/simple_page.html"); | 
|  |  | 
|  | if (AreDefaultSiteInstancesEnabled()) { | 
|  | // Isolate "bar.com" so we are guaranteed to get a different process | 
|  | // for navigations to this origin. | 
|  | IsolateOriginsForTesting(server, shell->web_contents(), {"bar.com"}); | 
|  | } | 
|  |  | 
|  | // Start off with initial navigation, so we get the first process allocated. | 
|  | EXPECT_TRUE(NavigateToURL(shell, foo)); | 
|  | EXPECT_EQ(base::ASCIIToUTF16("OK"), shell->web_contents()->GetTitle()); | 
|  |  | 
|  | // Open another window, so we generate some more routing ids. | 
|  | ShellAddedObserver shell2_observer; | 
|  | EXPECT_TRUE(ExecuteScript(shell, "window.open(document.URL + '#2');")); | 
|  | Shell* shell2 = shell2_observer.GetShell(); | 
|  |  | 
|  | // The new window must be in the same process, but have a new routing id. | 
|  | EXPECT_EQ(shell->web_contents()->GetMainFrame()->GetProcess()->GetID(), | 
|  | shell2->web_contents()->GetMainFrame()->GetProcess()->GetID()); | 
|  | *target_routing_id = | 
|  | shell2->web_contents()->GetRenderViewHost()->GetRoutingID(); | 
|  | EXPECT_NE(*target_routing_id, | 
|  | shell->web_contents()->GetRenderViewHost()->GetRoutingID()); | 
|  |  | 
|  | // Now, simulate a link click coming from the renderer. | 
|  | GURL extension_url("http://bar.com/simple_page.html"); | 
|  | WebContentsImpl* wc = static_cast<WebContentsImpl*>(shell->web_contents()); | 
|  | wc->GetFrameTree()->root()->navigator().RequestOpenURL( | 
|  | wc->GetFrameTree()->root()->current_frame_host(), extension_url, | 
|  | GlobalFrameRoutingId() /* initiator_routing_id */, | 
|  | url::Origin::Create(foo), nullptr, std::string(), Referrer(), | 
|  | WindowOpenDisposition::CURRENT_TAB, false, true, | 
|  | blink::TriggeringEventInfo::kFromTrustedEvent, std::string(), | 
|  | nullptr /* blob_url_loader_factory */, base::nullopt /* impression */); | 
|  |  | 
|  | // Since the navigation above requires a cross-process swap, there will be a | 
|  | // speculative/pending RenderFrameHost. Ensure it exists and is in a different | 
|  | // process than the initial page. | 
|  | RenderFrameHostImpl* next_rfh = | 
|  | wc->GetRenderManagerForTesting()->speculative_frame_host(); | 
|  |  | 
|  | EXPECT_TRUE(next_rfh); | 
|  | EXPECT_NE(shell->web_contents()->GetMainFrame()->GetProcess()->GetID(), | 
|  | next_rfh->GetProcess()->GetID()); | 
|  |  | 
|  | return next_rfh; | 
|  | } | 
|  |  | 
|  | content::mojom::OpenURLParamsPtr CreateOpenURLParams(const GURL& url) { | 
|  | auto params = 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; | 
|  | BrowserContext::CreateMemoryBackedBlob( | 
|  | browser_context, 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; | 
|  | } | 
|  |  | 
|  | // Helper class to interpose on Blob URL registrations, replacing the URL | 
|  | // contained in incoming registration requests with the specified URL. | 
|  | class BlobURLStoreInterceptor | 
|  | : public blink::mojom::BlobURLStoreInterceptorForTesting { | 
|  | public: | 
|  | static void Intercept( | 
|  | GURL target_url, | 
|  | mojo::SelfOwnedAssociatedReceiverRef<blink::mojom::BlobURLStore> | 
|  | receiver) { | 
|  | auto interceptor = | 
|  | base::WrapUnique(new BlobURLStoreInterceptor(target_url)); | 
|  | auto* raw_interceptor = interceptor.get(); | 
|  | auto impl = receiver->SwapImplForTesting(std::move(interceptor)); | 
|  | raw_interceptor->url_store_ = std::move(impl); | 
|  | } | 
|  |  | 
|  | blink::mojom::BlobURLStore* GetForwardingInterface() override { | 
|  | return url_store_.get(); | 
|  | } | 
|  |  | 
|  | void Register(mojo::PendingRemote<blink::mojom::Blob> blob, | 
|  | const GURL& url, | 
|  | RegisterCallback callback) override { | 
|  | GetForwardingInterface()->Register(std::move(blob), target_url_, | 
|  | std::move(callback)); | 
|  | } | 
|  |  | 
|  | private: | 
|  | explicit BlobURLStoreInterceptor(GURL target_url) : target_url_(target_url) {} | 
|  |  | 
|  | std::unique_ptr<blink::mojom::BlobURLStore> url_store_; | 
|  | GURL target_url_; | 
|  | }; | 
|  |  | 
|  | // Constructs a WebContentsDelegate that mocks a file dialog. | 
|  | // Unlike content::FileChooserDelegate, this class doesn't make a response in | 
|  | // RunFileChooser(), and a user needs to call Choose(). | 
|  | class DelayedFileChooserDelegate : public WebContentsDelegate { | 
|  | public: | 
|  | void Choose(const base::FilePath& file) { | 
|  | auto file_info = blink::mojom::FileChooserFileInfo::NewNativeFile( | 
|  | blink::mojom::NativeFileInfo::New(file, base::string16())); | 
|  | 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(); | 
|  | } | 
|  |  | 
|  | // WebContentsDelegate overrides | 
|  | void RunFileChooser(RenderFrameHost* render_frame_host, | 
|  | std::unique_ptr<FileSelectListener> listener, | 
|  | const blink::mojom::FileChooserParams& params) override { | 
|  | listener_ = std::move(listener); | 
|  | } | 
|  |  | 
|  | void EnumerateDirectory(WebContents* web_contents, | 
|  | std::unique_ptr<FileSelectListener> listener, | 
|  | const base::FilePath& directory_path) override { | 
|  | listener->FileSelectionCanceled(); | 
|  | } | 
|  |  | 
|  | private: | 
|  | std::unique_ptr<FileSelectListener> listener_; | 
|  | }; | 
|  |  | 
|  | void FileChooserCallback(base::RunLoop* run_loop, | 
|  | blink::mojom::FileChooserResultPtr result) { | 
|  | run_loop->Quit(); | 
|  | } | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | // The goal of these tests will be to "simulate" exploited renderer processes, | 
|  | // which can send arbitrary IPC messages and confuse browser process internal | 
|  | // state, leading to security bugs. We are trying to verify that the browser | 
|  | // doesn't perform any dangerous operations in such cases. | 
|  | class SecurityExploitBrowserTest : public ContentBrowserTest { | 
|  | public: | 
|  | SecurityExploitBrowserTest() {} | 
|  |  | 
|  | void SetUpCommandLine(base::CommandLine* command_line) override { | 
|  | // EmbeddedTestServer::InitializeAndListen() initializes its |base_url_| | 
|  | // which is required below. This cannot invoke Start() however as that kicks | 
|  | // off the "EmbeddedTestServer IO Thread" which then races with | 
|  | // initialization in ContentBrowserTest::SetUp(), http://crbug.com/674545. | 
|  | ASSERT_TRUE(embedded_test_server()->InitializeAndListen()); | 
|  |  | 
|  | // Add a host resolver rule to map all outgoing requests to the test server. | 
|  | // This allows us to use "real" hostnames in URLs, which we can use to | 
|  | // create arbitrary SiteInstances. | 
|  | command_line->AppendSwitchASCII( | 
|  | network::switches::kHostResolverRules, | 
|  | "MAP * " + | 
|  | net::HostPortPair::FromURL(embedded_test_server()->base_url()) | 
|  | .ToString() + | 
|  | ",EXCLUDE localhost"); | 
|  | } | 
|  |  | 
|  | void SetUpOnMainThread() override { | 
|  | // Complete the manual Start() after ContentBrowserTest's own | 
|  | // initialization, ref. comment on InitializeAndListen() above. | 
|  | embedded_test_server()->StartAcceptingConnections(); | 
|  | } | 
|  |  | 
|  | protected: | 
|  | // Tests that a given file path sent in a FrameHostMsg_RunFileChooser will | 
|  | // cause renderer to be killed. | 
|  | 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(base::ASCIIToUTF16("OK"), shell()->web_contents()->GetTitle()); | 
|  |  | 
|  | RenderFrameHost* compromised_renderer = | 
|  | shell()->web_contents()->GetMainFrame(); | 
|  | 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")); | 
|  | } | 
|  |  | 
|  | // Ensure that we kill the renderer process if we try to give it WebUI | 
|  | // properties and it doesn't have enabled WebUI bindings. | 
|  | IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest, SetWebUIProperty) { | 
|  | GURL foo("http://foo.com/simple_page.html"); | 
|  |  | 
|  | EXPECT_TRUE(NavigateToURL(shell(), foo)); | 
|  | EXPECT_EQ(base::ASCIIToUTF16("OK"), shell()->web_contents()->GetTitle()); | 
|  | EXPECT_EQ(0, shell()->web_contents()->GetMainFrame()->GetEnabledBindings()); | 
|  |  | 
|  | RenderFrameHost* compromised_renderer = | 
|  | shell()->web_contents()->GetMainFrame(); | 
|  | RenderProcessHostBadIpcMessageWaiter kill_waiter( | 
|  | compromised_renderer->GetProcess()); | 
|  | compromised_renderer->SetWebUIProperty("toolkit", "views"); | 
|  | EXPECT_EQ(bad_message::RVH_WEB_UI_BINDINGS_MISMATCH, kill_waiter.Wait()); | 
|  | } | 
|  |  | 
|  | // This is a test for crbug.com/312016 attempting to create duplicate | 
|  | // RenderViewHosts. SetupForDuplicateHosts sets up this test case and leaves | 
|  | // it in a state with pending RenderViewHost. Before the commit of the new | 
|  | // pending RenderViewHost, this test case creates a new window through the new | 
|  | // process. | 
|  | 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) {})); | 
|  | // If the above operation doesn't cause a crash, the test has succeeded! | 
|  | } | 
|  |  | 
|  | // This is a test for crbug.com/312016. It tries to create two RenderWidgetHosts | 
|  | // with the same process and routing ids, which causes a collision. It is almost | 
|  | // identical to the AttemptDuplicateRenderViewHost test case. | 
|  | // Crashes on all platforms.  http://crbug.com/939338 | 
|  | IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest, | 
|  | DISABLED_AttemptDuplicateRenderWidgetHost) { | 
|  | int 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); | 
|  |  | 
|  | // Since this test executes on the UI thread and hopping threads might cause | 
|  | // different timing in the test, let's simulate a CreateNewWidget call coming | 
|  | // from the IO thread.  Use the existing window routing id to cause a | 
|  | // deliberate collision. | 
|  | pending_rfh->CreateNewWidget( | 
|  | mojo::PendingAssociatedReceiver<blink::mojom::WidgetHost>(), | 
|  | mojo::PendingAssociatedRemote<blink::mojom::Widget>(), base::DoNothing()); | 
|  |  | 
|  | // If the above operation doesn't crash, the test has succeeded! | 
|  | } | 
|  |  | 
|  | // This is a test for crbug.com/444198. It tries to send a | 
|  | // FrameHostMsg_RunFileChooser containing an invalid path. The browser should | 
|  | // correctly terminate the renderer in these cases. | 
|  | IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest, AttemptRunFileChoosers) { | 
|  | TestFileChooserWithPath(base::FilePath(FILE_PATH_LITERAL("../../*.txt"))); | 
|  | TestFileChooserWithPath(base::FilePath(FILE_PATH_LITERAL("/etc/*.conf"))); | 
|  | #if defined(OS_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 | 
|  | } | 
|  |  | 
|  | // A test for crbug.com/941008. | 
|  | // Calling OpenFileChooser() and EnumerateChosenDirectory() for a single | 
|  | // FileChooser instance had a problem. | 
|  | IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest, UnexpectedMethodsSequence) { | 
|  | EXPECT_TRUE(NavigateToURL(shell(), GURL("http://foo.com/simple_page.html"))); | 
|  | RenderFrameHost* compromised_renderer = | 
|  | shell()->web_contents()->GetMainFrame(); | 
|  | 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)); | 
|  | // The following EnumerateChosenDirectory() runs the specified callback | 
|  | // immediately regardless of the content of the first argument FilePath. | 
|  | 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(); | 
|  |  | 
|  | // The test passes if it doesn't crash. | 
|  | } | 
|  |  | 
|  | class CorsExploitBrowserTest : public ContentBrowserTest { | 
|  | public: | 
|  | CorsExploitBrowserTest() = default; | 
|  |  | 
|  | void SetUpCommandLine(base::CommandLine* command_line) override { | 
|  | // TODO(yoichio): This is temporary switch to support chrome internal | 
|  | // components migration from the old web APIs. | 
|  | // After completion of the migration, we should remove this. | 
|  | // See https://crbug.com/911943 for detail. | 
|  | command_line->AppendSwitchASCII(switches::kEnableBlinkFeatures, | 
|  | "HTMLImports"); | 
|  | } | 
|  |  | 
|  | void SetUpOnMainThread() override { | 
|  | host_resolver()->AddRule("*", "127.0.0.1"); | 
|  | SetupCrossSiteRedirector(embedded_test_server()); | 
|  | } | 
|  |  | 
|  | private: | 
|  | DISALLOW_COPY_AND_ASSIGN(CorsExploitBrowserTest); | 
|  | }; | 
|  |  | 
|  | // This is a regression test for https://crbug.com/961614 - it makes sure that | 
|  | // the trustworthy |request_initiator_site_lock| takes precedent over | 
|  | // the untrustworthy |request.request_initiator|. | 
|  | // | 
|  | // For spoofing a |request.request_initiator| that doesn't match | 
|  | // |request_initiator_site_lock|, the test relies on a misfeature of HTML | 
|  | // Imports.  It is unclear how to replicate such spoofing once HTML imports are | 
|  | // deprecated. | 
|  | IN_PROC_BROWSER_TEST_F(CorsExploitBrowserTest, | 
|  | OriginHeaderSpoofViaHtmlImports) { | 
|  | std::string victim_path = "/victim/secret.json"; | 
|  | net::test_server::ControllableHttpResponse victim_response( | 
|  | embedded_test_server(), victim_path, false); | 
|  |  | 
|  | ASSERT_TRUE(embedded_test_server()->Start()); | 
|  | GURL attacker_url( | 
|  | embedded_test_server()->GetURL("attacker.com", "/title1.html")); | 
|  | GURL module_url(embedded_test_server()->GetURL( | 
|  | "module.com", "/cross_site_document_blocking/html_import3.html")); | 
|  | GURL victim_url(embedded_test_server()->GetURL("victim.com", victim_path)); | 
|  |  | 
|  | EXPECT_TRUE(NavigateToURL(shell(), attacker_url)); | 
|  |  | 
|  | // From a renderer process locked to attacker.com, load a HTML Import from | 
|  | // module.com.  HTML Imports implementation allows attacker.com to issue | 
|  | // requests on behalf of the module.com module - here attacker.com initiates a | 
|  | // request for a victim.com resource. | 
|  | const char kScriptTemplate[] = R"( | 
|  | link = document.createElement('link'); | 
|  | link.rel = 'import'; | 
|  | link.href = $1; | 
|  | link.onload = () => { | 
|  | with(link.import.documentElement.appendChild( | 
|  | link.import.createElement('script'))) { | 
|  | crossOrigin = 'use-credentials'; | 
|  | src = $2 | 
|  | } | 
|  | }; | 
|  | document.documentElement.appendChild(link); | 
|  | )"; | 
|  | std::string script = JsReplace(kScriptTemplate, module_url, victim_url); | 
|  | ASSERT_TRUE(ExecJs(shell(), script)); | 
|  |  | 
|  | // Verify that attacker.com-controlled request for a victim.com resource uses | 
|  | // CORS and has `Origin: http://attacker.com` request header (rather than | 
|  | // `Origin: http://module.com`). | 
|  | victim_response.WaitForRequest(); | 
|  | net::test_server::HttpRequest::HeaderMap headers = | 
|  | victim_response.http_request()->headers; | 
|  | ASSERT_TRUE(base::Contains(headers, "Origin")); | 
|  | EXPECT_EQ(url::Origin::Create(attacker_url).Serialize(), headers["Origin"]); | 
|  | } | 
|  |  | 
|  | // Test that receiving a commit with incorrect origin properly terminates the | 
|  | // renderer process. | 
|  | 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()) | 
|  | ->GetFrameTree() | 
|  | ->root(); | 
|  |  | 
|  | // Navigate to a new URL, with an interceptor that replaces the origin with | 
|  | // one that does not match params.url. | 
|  | GURL url(embedded_test_server()->GetURL("/title2.html")); | 
|  | PwnCommitIPC(shell()->web_contents(), url, url, | 
|  | url::Origin::Create(GURL("http://bar.com/"))); | 
|  |  | 
|  | // Use LoadURL, as the test shouldn't wait for navigation commit. | 
|  | 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()); | 
|  |  | 
|  | // When the IPC message is received and validation fails, the process is | 
|  | // terminated. However, the notification for that should be processed in a | 
|  | // separate task of the message loop, so ensure that the process is still | 
|  | // considered alive. | 
|  | EXPECT_TRUE( | 
|  | root->current_frame_host()->GetProcess()->IsInitializedAndNotDead()); | 
|  |  | 
|  | EXPECT_EQ(bad_message::RFH_INVALID_ORIGIN_ON_COMMIT, kill_waiter.Wait()); | 
|  | } | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | // Interceptor that replaces |interface_params| with the specified | 
|  | // value for the first DidCommitProvisionalLoad message it observes in the given | 
|  | // |web_contents| while in scope. | 
|  | class ScopedInterfaceParamsReplacer : public DidCommitNavigationInterceptor { | 
|  | public: | 
|  | ScopedInterfaceParamsReplacer( | 
|  | WebContents* web_contents, | 
|  | mojom::DidCommitProvisionalLoadInterfaceParamsPtr params_override) | 
|  | : DidCommitNavigationInterceptor(web_contents), | 
|  | params_override_(std::move(params_override)) {} | 
|  | ~ScopedInterfaceParamsReplacer() override = default; | 
|  |  | 
|  | protected: | 
|  | bool WillProcessDidCommitNavigation( | 
|  | RenderFrameHost* render_frame_host, | 
|  | NavigationRequest* navigation_request, | 
|  | ::FrameHostMsg_DidCommitProvisionalLoad_Params* params, | 
|  | mojom::DidCommitProvisionalLoadInterfaceParamsPtr* interface_params) | 
|  | override { | 
|  | interface_params->Swap(¶ms_override_); | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | private: | 
|  | mojom::DidCommitProvisionalLoadInterfaceParamsPtr params_override_; | 
|  |  | 
|  | DISALLOW_COPY_AND_ASSIGN(ScopedInterfaceParamsReplacer); | 
|  | }; | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | // Test that, as a general rule, not receiving new | 
|  | // DidCommitProvisionalLoadInterfaceParamsPtr for a cross-document navigation | 
|  | // properly terminates the renderer process. There is one exception to this | 
|  | // rule, see: RenderFrameHostImplBrowserTest. | 
|  | // InterfaceProviderRequestIsOptionalForFirstCommit. | 
|  | // TODO(crbug.com/718652): when all clients are converted to use | 
|  | // BrowserInterfaceBroker, PendingReceiver<InterfaceProvider>-related code will | 
|  | // be removed. | 
|  | 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()->GetMainFrame()); | 
|  | 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()); | 
|  |  | 
|  | // Verify that the death of the renderer process doesn't leave behind and | 
|  | // leak NavigationRequests - see https://crbug.com/869193. | 
|  | 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()); | 
|  | } | 
|  |  | 
|  | // Test that a compromised renderer cannot ask to upload an arbitrary file in | 
|  | // OpenURL.  This is a regression test for https://crbug.com/726067. | 
|  | 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()) | 
|  | ->GetFrameTree() | 
|  | ->root(); | 
|  |  | 
|  | RenderProcessHostBadIpcMessageWaiter kill_waiter( | 
|  | root->current_frame_host()->GetProcess()); | 
|  |  | 
|  | // Prepare a file to upload. | 
|  | 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)); | 
|  |  | 
|  | // Simulate an OpenURL Mojo method asking to POST a file that the renderer | 
|  | // shouldn't have access to. | 
|  | 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; | 
|  |  | 
|  | root->current_frame_host()->OpenURL(std::move(params)); | 
|  |  | 
|  | // Verify that the malicious navigation did not commit the navigation to | 
|  | // |target_url|. | 
|  | EXPECT_EQ(start_url, root->current_frame_host()->GetLastCommittedURL()); | 
|  |  | 
|  | // Verify that the malicious renderer got killed. | 
|  | EXPECT_EQ(bad_message::ILLEGAL_UPLOAD_PARAMS, kill_waiter.Wait()); | 
|  | } | 
|  |  | 
|  | // Test that forging a frame's unique name and commit won't allow changing the | 
|  | // PageState of a cross-process FrameNavigationEntry. | 
|  | // See https://crbug.com/766262. | 
|  | IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest, PageStateToWrongEntry) { | 
|  | IsolateAllSitesForTesting(base::CommandLine::ForCurrentProcess()); | 
|  |  | 
|  | // Commit a page with nested iframes and a separate cross-process iframe. | 
|  | GURL main_url(embedded_test_server()->GetURL( | 
|  | "a.com", "/cross_site_iframe_factory.html?a(a(a),b)")); | 
|  | EXPECT_TRUE(NavigateToURL(shell(), main_url)); | 
|  | NavigationEntryImpl* back_entry = static_cast<NavigationEntryImpl*>( | 
|  | shell()->web_contents()->GetController().GetLastCommittedEntry()); | 
|  | int nav_entry_id = back_entry->GetUniqueID(); | 
|  |  | 
|  | FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents()) | 
|  | ->GetFrameTree() | 
|  | ->root(); | 
|  | FrameTreeNode* child0_0 = root->child_at(0)->child_at(0); | 
|  | std::string child0_0_unique_name = child0_0->unique_name(); | 
|  | FrameTreeNode* child1 = root->child_at(1); | 
|  | GURL child1_url = child1->current_url(); | 
|  | int child1_pid = child1->current_frame_host()->GetProcess()->GetID(); | 
|  | PageState child1_page_state = back_entry->GetFrameEntry(child1)->page_state(); | 
|  |  | 
|  | // Add a history item in the nested frame.  It's important to do it there and | 
|  | // not the main frame for the repro to work, since we don't walk the subtree | 
|  | // when navigating back/forward between same document items. | 
|  | TestNavigationObserver fragment_observer(shell()->web_contents()); | 
|  | EXPECT_TRUE(ExecuteScript(child0_0, "location.href = '#foo';")); | 
|  | fragment_observer.Wait(); | 
|  |  | 
|  | // Simulate a name change IPC from the nested iframe, matching the cross-site | 
|  | // iframe's unique name. | 
|  | child0_0->SetFrameName("foo", child1->unique_name()); | 
|  |  | 
|  | // Simulate a back navigation from the now renamed nested iframe, which would | 
|  | // put a PageState on the cross-site iframe's FrameNavigationEntry.  Forge a | 
|  | // data URL within the PageState that differs from child1_url. | 
|  | std::unique_ptr<FrameHostMsg_DidCommitProvisionalLoad_Params> params = | 
|  | std::make_unique<FrameHostMsg_DidCommitProvisionalLoad_Params>(); | 
|  | params->nav_entry_id = nav_entry_id; | 
|  | params->did_create_new_entry = false; | 
|  | params->url = GURL("about:blank"); | 
|  | params->transition = ui::PAGE_TRANSITION_AUTO_SUBFRAME; | 
|  | params->should_update_history = false; | 
|  | params->gesture = NavigationGestureAuto; | 
|  | params->method = "GET"; | 
|  | params->page_state = PageState::CreateFromURL(GURL("data:text/html,foo")); | 
|  | params->origin = url::Origin::Create(GURL("about:blank")); | 
|  | params->embedding_token = base::UnguessableToken::Create(); | 
|  |  | 
|  | mojo::PendingRemote<service_manager::mojom::InterfaceProvider> | 
|  | isolated_interface_provider; | 
|  | static_cast<mojom::FrameHost*>(child0_0->current_frame_host()) | 
|  | ->DidCommitProvisionalLoad( | 
|  | std::move(params), | 
|  | mojom::DidCommitProvisionalLoadInterfaceParams::New( | 
|  | isolated_interface_provider.InitWithNewPipeAndPassReceiver(), | 
|  | mojo::PendingRemote<blink::mojom::BrowserInterfaceBroker>() | 
|  | .InitWithNewPipeAndPassReceiver())); | 
|  |  | 
|  | // Make sure we haven't changed the FrameNavigationEntry.  An attack would | 
|  | // modify the PageState but leave the SiteInstance as it was. | 
|  | EXPECT_EQ(child1->current_frame_host()->GetSiteInstance(), | 
|  | back_entry->GetFrameEntry(child1)->site_instance()); | 
|  | EXPECT_EQ(child1_page_state, back_entry->GetFrameEntry(child1)->page_state()); | 
|  |  | 
|  | // Put the frame's unique name back. | 
|  | child0_0->SetFrameName("bar", child0_0_unique_name); | 
|  |  | 
|  | // Go forward after the fake back navigation. | 
|  | TestNavigationObserver forward_observer(shell()->web_contents()); | 
|  | shell()->web_contents()->GetController().GoForward(); | 
|  | forward_observer.Wait(); | 
|  |  | 
|  | // Go back to the possibly corrupted entry and ensure we didn't load the data | 
|  | // URL in the previous process.  A test failure here would appear as a failure | 
|  | // of the URL check and not the process ID check. | 
|  | TestNavigationObserver back_observer(shell()->web_contents()); | 
|  | shell()->web_contents()->GetController().GoBack(); | 
|  | back_observer.Wait(); | 
|  | EXPECT_EQ(child1_pid, child1->current_frame_host()->GetProcess()->GetID()); | 
|  | ASSERT_EQ(child1_url, child1->current_url()); | 
|  | } | 
|  |  | 
|  | class SecurityExploitBrowserTestMojoBlobURLs | 
|  | : public SecurityExploitBrowserTest { | 
|  | public: | 
|  | SecurityExploitBrowserTestMojoBlobURLs() = default; | 
|  |  | 
|  | void TearDown() override { | 
|  | storage::BlobRegistryImpl::SetURLStoreCreationHookForTesting(nullptr); | 
|  | } | 
|  | }; | 
|  |  | 
|  | // Check that when site isolation is enabled, an origin can't create a blob URL | 
|  | // for a different origin.  Similar to the test above, but checks the | 
|  | // mojo-based Blob URL implementation.  See https://crbug.com/886976. | 
|  | IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTestMojoBlobURLs, | 
|  | 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()->GetMainFrame(); | 
|  |  | 
|  | // Intercept future blob URL registrations and overwrite the blob URL origin | 
|  | // with b.com. | 
|  | std::string target_origin = "http://b.com"; | 
|  | std::string blob_path = "5881f76e-10d2-410d-8c61-ef210502acfd"; | 
|  | auto intercept_hook = | 
|  | base::BindRepeating(&BlobURLStoreInterceptor::Intercept, | 
|  | GURL("blob:" + target_origin + "/" + blob_path)); | 
|  | storage::BlobRegistryImpl::SetURLStoreCreationHookForTesting(&intercept_hook); | 
|  |  | 
|  | // Register a blob URL from the a.com main frame, which will go through the | 
|  | // interceptor above and be rewritten to register the blob URL with the b.com | 
|  | // origin. This should result in a kill because a.com should not be allowed | 
|  | // to create blob URLs outside of its own origin. | 
|  | content::RenderProcessHostBadMojoMessageWaiter crash_observer( | 
|  | rfh->GetProcess()); | 
|  |  | 
|  | // The renderer should always get killed, but sometimes ExecuteScript returns | 
|  | // true anyway, so just ignore the result. | 
|  | ignore_result( | 
|  | content::ExecuteScript(rfh, "URL.createObjectURL(new Blob(['foo']))")); | 
|  |  | 
|  | // If the process is killed, this test passes. | 
|  | EXPECT_EQ( | 
|  | "Received bad user message: " | 
|  | "Non committable URL passed to BlobURLStore::Register", | 
|  | crash_observer.Wait()); | 
|  | } | 
|  |  | 
|  | // Check that with site isolation enabled, an origin can't create a filesystem | 
|  | // URL for a different origin.  See https://crbug.com/888001. | 
|  | 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)); | 
|  | RenderFrameHost* rfh = shell()->web_contents()->GetMainFrame(); | 
|  |  | 
|  | // Block the renderer on operation that never completes, to shield it from | 
|  | // receiving unexpected browser->renderer IPCs that might CHECK. | 
|  | rfh->ExecuteJavaScriptWithUserGestureForTests( | 
|  | base::ASCIIToUTF16("var r = new XMLHttpRequest();" | 
|  | "r.open('GET', '/slow?99999', false);" | 
|  | "r.send(null);" | 
|  | "while (1);")); | 
|  |  | 
|  | // Set up a blob ID and populate it with attacker-controlled value. This | 
|  | // is just using the blob APIs directly since creating arbitrary blobs is not | 
|  | // what is prohibited; this data is not in any origin. | 
|  | 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(); | 
|  |  | 
|  | // Target a different origin. | 
|  | std::string target_origin = "http://b.com"; | 
|  | GURL target_url = | 
|  | GURL("filesystem:" + target_origin + "/temporary/exploit.html"); | 
|  |  | 
|  | // Note: a well-behaved renderer would always call Open first before calling | 
|  | // Create and Write, but it's actually not necessary for the original attack | 
|  | // to succeed, so we omit it. As a result there are some log warnings from the | 
|  | // quota observer. | 
|  |  | 
|  | PwnMessageHelper::FileSystemCreate(rfh->GetProcess(), 23, target_url, false, | 
|  | false, false); | 
|  |  | 
|  | // Write the blob into the file. If successful, this places an | 
|  | // attacker-controlled value in a resource on the target origin. | 
|  | PwnMessageHelper::FileSystemWrite(rfh->GetProcess(), 24, target_url, blob_id, | 
|  | 0); | 
|  |  | 
|  | // Now navigate to |target_url| in a subframe. It should not succeed, and the | 
|  | // subframe should not contain |payload|. | 
|  | TestNavigationObserver observer(shell()->web_contents()); | 
|  | FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents()) | 
|  | ->GetFrameTree() | 
|  | ->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; | 
|  | } | 
|  |  | 
|  | // Verify that when a compromised renderer tries to navigate a remote frame to | 
|  | // a disallowed URL (e.g., file URL), that navigation is blocked. | 
|  | IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest, | 
|  | BlockIllegalOpenURLFromRemoteFrame) { | 
|  | // Explicitly isolating a.com helps ensure that this test is applicable on | 
|  | // platforms without site-per-process. | 
|  | 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()) | 
|  | ->GetFrameTree() | 
|  | ->root(); | 
|  | FrameTreeNode* child = root->child_at(0); | 
|  |  | 
|  | // Simulate an IPC message where the top frame asks the remote subframe to | 
|  | // navigate to a file: URL. | 
|  | SiteInstance* a_com_instance = root->current_frame_host()->GetSiteInstance(); | 
|  | RenderFrameProxyHost* proxy = | 
|  | child->render_manager()->GetRenderFrameProxyHost(a_com_instance); | 
|  | EXPECT_TRUE(proxy); | 
|  |  | 
|  | TestNavigationObserver observer(shell()->web_contents()); | 
|  | proxy->frame_tree_node()->current_frame_host()->OpenURL( | 
|  | CreateOpenURLParams(GURL("file:///"))); | 
|  | observer.Wait(); | 
|  |  | 
|  | // Verify that the malicious navigation was blocked.  Currently, this happens | 
|  | // by rewriting the target URL to about:blank#blocked. | 
|  | // | 
|  | // TODO(alexmos): Consider killing the renderer process in this case, since | 
|  | // this security check is already enforced in the renderer process. | 
|  | EXPECT_EQ(GURL(kBlockedURL), | 
|  | child->current_frame_host()->GetLastCommittedURL()); | 
|  |  | 
|  | // Navigate to the starting page again to recreate the proxy, then try the | 
|  | // same malicious navigation with a chrome:// URL. | 
|  | EXPECT_TRUE(NavigateToURL(shell(), main_url)); | 
|  | child = root->child_at(0); | 
|  | proxy = child->render_manager()->GetRenderFrameProxyHost(a_com_instance); | 
|  | EXPECT_TRUE(proxy); | 
|  |  | 
|  | TestNavigationObserver observer_2(shell()->web_contents()); | 
|  | GURL chrome_url(std::string(kChromeUIScheme) + "://" + | 
|  | std::string(kChromeUIGpuHost)); | 
|  | proxy->frame_tree_node()->current_frame_host()->OpenURL( | 
|  | CreateOpenURLParams(chrome_url)); | 
|  | observer_2.Wait(); | 
|  | EXPECT_EQ(GURL(kBlockedURL), | 
|  | child->current_frame_host()->GetLastCommittedURL()); | 
|  | } | 
|  |  | 
|  | class RouteMessageEventInterceptor | 
|  | : public blink::mojom::RemoteFrameHostInterceptorForTesting { | 
|  | public: | 
|  | explicit RouteMessageEventInterceptor( | 
|  | RenderFrameProxyHost* render_frame_proxy_host, | 
|  | const base::string16& evil_origin) | 
|  | : render_frame_proxy_host_(render_frame_proxy_host), | 
|  | evil_origin_(evil_origin) { | 
|  | render_frame_proxy_host_->frame_host_receiver_for_testing() | 
|  | .SwapImplForTesting(this); | 
|  | } | 
|  |  | 
|  | RemoteFrameHost* GetForwardingInterface() override { | 
|  | return render_frame_proxy_host_; | 
|  | } | 
|  |  | 
|  | void RouteMessageEvent( | 
|  | const base::Optional<base::UnguessableToken>& source_frame_token, | 
|  | const base::string16& source_origin, | 
|  | const base::string16& target_origin, | 
|  | blink::TransferableMessage message) override { | 
|  | // Forward the message to the actual RFPH replacing |source_origin| with the | 
|  | // "evil origin" as especified in SetEvilSourceOriginAndWaitForMessage(). | 
|  | GetForwardingInterface()->RouteMessageEvent( | 
|  | std::move(source_frame_token), std::move(evil_origin_), | 
|  | std::move(target_origin), std::move(message)); | 
|  | } | 
|  |  | 
|  | private: | 
|  | RenderFrameProxyHost* render_frame_proxy_host_; | 
|  | base::string16 evil_origin_; | 
|  | }; | 
|  |  | 
|  | // Test verifying that a compromised renderer can't lie about the source_origin | 
|  | // passed along with the RouteMessageEvent() mojo message.  See also | 
|  | // https://crbug.com/915721. | 
|  | IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest, PostMessageSourceOrigin) { | 
|  | // Explicitly isolating a.com helps ensure that this test is applicable on | 
|  | // platforms without site-per-process. | 
|  | IsolateOrigin("b.com"); | 
|  |  | 
|  | // Navigate to a page with an OOPIF. | 
|  | GURL main_url(embedded_test_server()->GetURL( | 
|  | "a.com", "/cross_site_iframe_factory.html?a(b)")); | 
|  | EXPECT_TRUE(NavigateToURL(shell(), main_url)); | 
|  |  | 
|  | // Sanity check of test setup: main frame and subframe should be isolated. | 
|  | WebContents* web_contents = shell()->web_contents(); | 
|  | RenderFrameHost* main_frame = web_contents->GetMainFrame(); | 
|  | RenderFrameHost* subframe = web_contents->GetAllFrames()[1]; | 
|  | EXPECT_NE(main_frame->GetProcess(), subframe->GetProcess()); | 
|  |  | 
|  | // We need to get ahold of the RenderFrameProxyHost representing the main | 
|  | // frame for the subframe's process, to install the mojo interceptor. | 
|  | FrameTreeNode* main_frame_node = | 
|  | static_cast<WebContentsImpl*>(shell()->web_contents()) | 
|  | ->GetFrameTree() | 
|  | ->root(); | 
|  | FrameTreeNode* subframe_node = main_frame_node->child_at(0); | 
|  | SiteInstance* b_com_instance = | 
|  | subframe_node->current_frame_host()->GetSiteInstance(); | 
|  | RenderFrameProxyHost* main_frame_proxy_host = | 
|  | main_frame_node->render_manager()->GetRenderFrameProxyHost( | 
|  | b_com_instance); | 
|  |  | 
|  | // Prepare to intercept the RouteMessageEvent IPC message that will come | 
|  | // from the subframe process. | 
|  | url::Origin invalid_origin = | 
|  | web_contents->GetMainFrame()->GetLastCommittedOrigin(); | 
|  | base::string16 evil_source_origin = | 
|  | base::UTF8ToUTF16(invalid_origin.Serialize()); | 
|  | RouteMessageEventInterceptor mojo_interceptor(main_frame_proxy_host, | 
|  | evil_source_origin); | 
|  |  | 
|  | // Post a message from the subframe to the cross-site parent and intercept the | 
|  | // associated IPC message, changing it to simulate a compromised subframe | 
|  | // renderer lying that the |source_origin| of the postMessage is the origin of | 
|  | // the parent (not of the subframe). | 
|  | 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()); | 
|  | } | 
|  |  | 
|  | // Intercepts calls to RenderFramHost's OpenURL mojo method, and | 
|  | // store the passed parameter. | 
|  | class OpenURLInterceptor : public mojom::FrameHostInterceptorForTesting { | 
|  | public: | 
|  | explicit OpenURLInterceptor(RenderFrameHostImpl* render_frame_host) | 
|  | : render_frame_host_(render_frame_host), | 
|  | intercepted_params_(mojom::OpenURLParams::New()) {} | 
|  |  | 
|  | ~OpenURLInterceptor() override = default; | 
|  |  | 
|  | mojom::FrameHost* GetForwardingInterface() override { | 
|  | return render_frame_host_; | 
|  | } | 
|  |  | 
|  | void OpenURL(mojom::OpenURLParamsPtr params) override { | 
|  | intercepted_params_ = std::move(params); | 
|  | } | 
|  |  | 
|  | mojom::OpenURLParamsPtr GetInterceptedParams() { | 
|  | return std::move(intercepted_params_); | 
|  | } | 
|  |  | 
|  | private: | 
|  | RenderFrameHostImpl* render_frame_host_; | 
|  | mojom::OpenURLParamsPtr intercepted_params_; | 
|  |  | 
|  | DISALLOW_COPY_AND_ASSIGN(OpenURLInterceptor); | 
|  | }; | 
|  |  | 
|  | IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest, | 
|  | InvalidRemoteNavigationInitiator) { | 
|  | // Explicitly isolating a.com helps ensure that this test is applicable on | 
|  | // platforms without site-per-process. | 
|  | IsolateOrigin("a.com"); | 
|  |  | 
|  | // Navigate to a test page where the subframe is cross-site (and because of | 
|  | // IsolateOrigin call above in a separate process) from the main frame. | 
|  | GURL main_url(embedded_test_server()->GetURL( | 
|  | "a.com", "/cross_site_iframe_factory.html?a(b)")); | 
|  | EXPECT_TRUE(NavigateToURL(shell(), main_url)); | 
|  | RenderFrameHost* main_frame = shell()->web_contents()->GetMainFrame(); | 
|  | RenderProcessHost* main_process = main_frame->GetProcess(); | 
|  | EXPECT_EQ(2u, shell()->web_contents()->GetAllFrames().size()); | 
|  | RenderFrameHost* subframe = shell()->web_contents()->GetAllFrames()[1]; | 
|  | RenderProcessHost* subframe_process = subframe->GetProcess(); | 
|  | EXPECT_NE(main_process->GetID(), subframe_process->GetID()); | 
|  |  | 
|  | // Prepare to intercept OpenURL Mojo message that will come from | 
|  | // the main frame. | 
|  | OpenURLInterceptor interceptor(static_cast<RenderFrameHostImpl*>(main_frame)); | 
|  |  | 
|  | // Have the main frame request navigation in the "remote" subframe.  This will | 
|  | // result in OpenURL Mojo message being sent to the RenderFrameProxyHost. | 
|  | EXPECT_TRUE(ExecJs(shell()->web_contents()->GetMainFrame(), | 
|  | "window.frames[0].location = '/title1.html';")); | 
|  |  | 
|  | // Change the intercepted message to simulate a compromised subframe renderer | 
|  | // lying that the |initiator_origin| is the origin of the |subframe|. | 
|  | auto evil_params = interceptor.GetInterceptedParams(); | 
|  | evil_params->initiator_origin = subframe->GetLastCommittedOrigin(); | 
|  |  | 
|  | // Inject the invalid IPC and verify that the renderer gets terminated. | 
|  | RenderProcessHostBadIpcMessageWaiter kill_waiter(main_process); | 
|  | static_cast<RenderFrameHostImpl*>(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, | 
|  | base::Optional<url::Origin> initiator_to_inject) | 
|  | : FrameHostInterceptor(web_contents), | 
|  | initiator_to_inject_(initiator_to_inject) {} | 
|  |  | 
|  | bool WillDispatchBeginNavigation( | 
|  | RenderFrameHost* render_frame_host, | 
|  | mojom::CommonNavigationParamsPtr* common_params, | 
|  | mojom::BeginNavigationParamsPtr* begin_params, | 
|  | mojo::PendingRemote<blink::mojom::BlobURLToken>* blob_url_token, | 
|  | mojo::PendingAssociatedRemote<mojom::NavigationClient>* navigation_client, | 
|  | mojo::PendingRemote<blink::mojom::NavigationInitiator>* | 
|  | navigation_initiator) override { | 
|  | if (is_activated_) { | 
|  | (*common_params)->initiator_origin = initiator_to_inject_; | 
|  | is_activated_ = false; | 
|  | } | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | void Activate() { is_activated_ = true; } | 
|  |  | 
|  | private: | 
|  | base::Optional<url::Origin> initiator_to_inject_; | 
|  | bool is_activated_ = false; | 
|  |  | 
|  | DISALLOW_COPY_AND_ASSIGN(BeginNavigationInitiatorReplacer); | 
|  | }; | 
|  |  | 
|  | IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest, | 
|  | InvalidBeginNavigationInitiator) { | 
|  | WebContentsImpl* web_contents = | 
|  | static_cast<WebContentsImpl*>(shell()->web_contents()); | 
|  |  | 
|  | // Prepare to intercept BeginNavigation mojo IPC. This has to be done before | 
|  | // the test creates the RenderFrameHostImpl that is the target of the IPC. | 
|  | BeginNavigationInitiatorReplacer injector( | 
|  | web_contents, url::Origin::Create(GURL("http://b.com"))); | 
|  |  | 
|  | // Explicitly isolating a.com helps ensure that this test is applicable on | 
|  | // platforms without site-per-process. | 
|  | IsolateOrigin("a.com"); | 
|  |  | 
|  | // Navigate to a test page that will be locked to a.com. | 
|  | GURL main_url(embedded_test_server()->GetURL("a.com", "/title1.html")); | 
|  | EXPECT_TRUE(NavigateToURL(web_contents, main_url)); | 
|  |  | 
|  | // Start monitoring for renderer kills. | 
|  | RenderProcessHost* main_process = web_contents->GetMainFrame()->GetProcess(); | 
|  | RenderProcessHostBadIpcMessageWaiter kill_waiter(main_process); | 
|  |  | 
|  | // Have the main frame navigate and lie that the initiator origin is b.com. | 
|  | injector.Activate(); | 
|  | // Don't expect a response for the script, as the process may be killed | 
|  | // before the script sends its completion message. | 
|  | ExecuteScriptAsync(web_contents, "window.location = '/title2.html';"); | 
|  |  | 
|  | // Verify that the renderer was terminated. | 
|  | EXPECT_EQ(bad_message::INVALID_INITIATOR_ORIGIN, kill_waiter.Wait()); | 
|  | } | 
|  |  | 
|  | IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest, | 
|  | MissingBeginNavigationInitiator) { | 
|  | // Prepare to intercept BeginNavigation mojo IPC.  This has to be done before | 
|  | // the test creates the RenderFrameHostImpl that is the target of the IPC. | 
|  | WebContents* web_contents = shell()->web_contents(); | 
|  | BeginNavigationInitiatorReplacer injector(web_contents, base::nullopt); | 
|  |  | 
|  | // Navigate to a test page. | 
|  | GURL main_url(embedded_test_server()->GetURL("a.com", "/title1.html")); | 
|  | EXPECT_TRUE(NavigateToURL(web_contents, main_url)); | 
|  |  | 
|  | // Start monitoring for renderer kills. | 
|  | RenderProcessHost* main_process = web_contents->GetMainFrame()->GetProcess(); | 
|  | RenderProcessHostBadIpcMessageWaiter kill_waiter(main_process); | 
|  |  | 
|  | // Have the main frame submit a BeginNavigation IPC with a missing initiator. | 
|  | injector.Activate(); | 
|  | // Don't expect a response for the script, as the process may be killed | 
|  | // before the script sends its completion message. | 
|  | ExecuteScriptAsync(web_contents, "window.location = '/title2.html';"); | 
|  |  | 
|  | // Verify that the renderer was terminated. | 
|  | EXPECT_EQ(bad_message::RFHI_BEGIN_NAVIGATION_MISSING_INITIATOR_ORIGIN, | 
|  | kill_waiter.Wait()); | 
|  | } | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | // An interceptor class that allows replacing the URL of the commit IPC from | 
|  | // the renderer process to the browser process. | 
|  | class DidCommitUrlReplacer : public DidCommitNavigationInterceptor { | 
|  | public: | 
|  | DidCommitUrlReplacer(WebContents* web_contents, const GURL& replacement_url) | 
|  | : DidCommitNavigationInterceptor(web_contents), | 
|  | replacement_url_(replacement_url) {} | 
|  | ~DidCommitUrlReplacer() override = default; | 
|  |  | 
|  | protected: | 
|  | bool WillProcessDidCommitNavigation( | 
|  | RenderFrameHost* render_frame_host, | 
|  | NavigationRequest* navigation_request, | 
|  | ::FrameHostMsg_DidCommitProvisionalLoad_Params* params, | 
|  | mojom::DidCommitProvisionalLoadInterfaceParamsPtr* interface_params) | 
|  | override { | 
|  | params->url = replacement_url_; | 
|  | return true; | 
|  | } | 
|  |  | 
|  | private: | 
|  | GURL replacement_url_; | 
|  |  | 
|  | DISALLOW_COPY_AND_ASSIGN(DidCommitUrlReplacer); | 
|  | }; | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | // Test which verifies that when an exploited renderer process sends a commit | 
|  | // message with URL that the process is not allowed to commit. | 
|  | IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest, DidCommitInvalidURL) { | 
|  | // Explicitly isolating foo.com helps ensure that this test is applicable on | 
|  | // platforms without site-per-process. | 
|  | IsolateOrigin("foo.com"); | 
|  |  | 
|  | // Navigate to foo.com initially. | 
|  | GURL foo_url(embedded_test_server()->GetURL("foo.com", "/title1.html")); | 
|  | EXPECT_TRUE(NavigateToURL(shell(), foo_url)); | 
|  |  | 
|  | // Create the interceptor object which will replace the URL of the subsequent | 
|  | // navigation with bar.com based URL. | 
|  | GURL bar_url(embedded_test_server()->GetURL("bar.com", "/title3.html")); | 
|  | DidCommitUrlReplacer url_replacer(shell()->web_contents(), bar_url); | 
|  |  | 
|  | // Navigate to another URL within foo.com, which would usually be committed | 
|  | // successfully, but when the URL is modified it should result in the | 
|  | // termination of the renderer process. | 
|  | RenderProcessHostBadIpcMessageWaiter kill_waiter( | 
|  | shell()->web_contents()->GetMainFrame()->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()); | 
|  | } | 
|  |  | 
|  | // Test which verifies that a WebUI process cannot send a commit message with | 
|  | // URL for a web document. | 
|  | IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest, | 
|  | WebUIProcessDidCommitWebURL) { | 
|  | // Navigate to a WebUI document. | 
|  | GURL webui_url(GetWebUIURL(kChromeUIGpuHost)); | 
|  | EXPECT_TRUE(NavigateToURL(shell(), webui_url)); | 
|  |  | 
|  | // Create the interceptor object which will replace the URL of the subsequent | 
|  | // navigation with |web_url|. | 
|  | GURL web_url(embedded_test_server()->GetURL("foo.com", "/title3.html")); | 
|  | DidCommitUrlReplacer url_replacer(shell()->web_contents(), web_url); | 
|  |  | 
|  | // Navigate to another URL within the WebUI, which would usually be committed | 
|  | // successfully, but when the URL is modified it should result in the | 
|  | // termination of the renderer process. | 
|  | RenderProcessHostBadIpcMessageWaiter kill_waiter( | 
|  | shell()->web_contents()->GetMainFrame()->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()); | 
|  | } | 
|  |  | 
|  | // Test that verifies that if a RenderFrameHost is incorrectly given WebUI | 
|  | // bindings, committing a non-WebUI URL in it is detected and the process is | 
|  | // correctly terminated. | 
|  | IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest, | 
|  | DidCommitNonWebUIURLInProcessWithBindings) { | 
|  | // Navigate to a web URL. | 
|  | GURL initial_url(embedded_test_server()->GetURL("foo.com", "/title1.html")); | 
|  | EXPECT_TRUE(NavigateToURL(shell(), initial_url)); | 
|  |  | 
|  | // Start a second navigation. | 
|  | GURL web_url(embedded_test_server()->GetURL("foo.com", "/title2.html")); | 
|  | TestNavigationManager navigation(shell()->web_contents(), web_url); | 
|  | RenderProcessHostBadIpcMessageWaiter kill_waiter( | 
|  | shell()->web_contents()->GetMainFrame()->GetProcess()); | 
|  |  | 
|  | shell()->LoadURL(web_url); | 
|  | EXPECT_TRUE(navigation.WaitForResponse()); | 
|  |  | 
|  | // Grant WebUI bindings to the navigated frame to simulate a bug in the code | 
|  | // that incorrectly does it for a navigation that does not require it. | 
|  | navigation.GetNavigationHandle()->GetRenderFrameHost()->AllowBindings( | 
|  | BINDINGS_POLICY_WEB_UI); | 
|  |  | 
|  | // Resume the navigation and upon receiving the commit message the renderer | 
|  | // process will be terminated. | 
|  | navigation.ResumeNavigation(); | 
|  | EXPECT_EQ(bad_message::RFH_CAN_COMMIT_URL_BLOCKED, 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) {} | 
|  |  | 
|  | bool WillDispatchBeginNavigation( | 
|  | RenderFrameHost* render_frame_host, | 
|  | mojom::CommonNavigationParamsPtr* common_params, | 
|  | mojom::BeginNavigationParamsPtr* begin_params, | 
|  | mojo::PendingRemote<blink::mojom::BlobURLToken>* blob_url_token, | 
|  | mojo::PendingAssociatedRemote<mojom::NavigationClient>* navigation_client, | 
|  | mojo::PendingRemote<blink::mojom::NavigationInitiator>* | 
|  | navigation_initiator) 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; | 
|  |  | 
|  | DISALLOW_COPY_AND_ASSIGN(BeginNavigationTransitionReplacer); | 
|  | }; | 
|  |  | 
|  | 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) { | 
|  | // Prepare to intercept BeginNavigation mojo IPC.  This has to be done | 
|  | // before the test creates the RenderFrameHostImpl that is the target of the | 
|  | // IPC. | 
|  | WebContents* web_contents = shell()->web_contents(); | 
|  | BeginNavigationTransitionReplacer injector(web_contents, transition); | 
|  |  | 
|  | // Navigate to a test page. | 
|  | GURL main_url(embedded_test_server()->GetURL("a.com", "/title1.html")); | 
|  | EXPECT_TRUE(NavigateToURL(web_contents, main_url)); | 
|  |  | 
|  | // Start monitoring for renderer kills. | 
|  | RenderProcessHost* main_process = | 
|  | web_contents->GetMainFrame()->GetProcess(); | 
|  | RenderProcessHostBadIpcMessageWaiter kill_waiter(main_process); | 
|  |  | 
|  | // Have the main frame submit a BeginNavigation IPC with a missing | 
|  | // initiator. | 
|  | injector.Activate(); | 
|  | // Don't expect a response for the script, as the process may be killed | 
|  | // before the script sends its completion message. | 
|  | ExecuteScriptAsync(web_contents, "window.location = '/title2.html';"); | 
|  |  | 
|  | // Verify that the renderer was terminated. | 
|  | EXPECT_EQ(bad_message::RFHI_BEGIN_NAVIGATION_NON_WEBBY_TRANSITION, | 
|  | kill_waiter.Wait()); | 
|  | } | 
|  | } | 
|  |  | 
|  | class SecurityExploitViaDisabledWebSecurityTest | 
|  | : public SecurityExploitBrowserTest { | 
|  | public: | 
|  | SecurityExploitViaDisabledWebSecurityTest() { | 
|  | // To get around BlockedSchemeNavigationThrottle. Other attempts at getting | 
|  | // around it don't work, i.e.: | 
|  | // -if the request is made in a child frame then the frame is torn down | 
|  | // immediately on process killing so the navigation doesn't complete | 
|  | // -if it's classified as same document, then a DCHECK in | 
|  | // NavigationRequest::CreateRendererInitiated fires | 
|  | feature_list_.InitAndEnableFeature( | 
|  | features::kAllowContentInitiatedDataUrlNavigations); | 
|  | } | 
|  |  | 
|  | protected: | 
|  | void SetUpCommandLine(base::CommandLine* command_line) override { | 
|  | // Simulate a compromised renderer, otherwise the cross-origin request to | 
|  | // file: is blocked. | 
|  | command_line->AppendSwitch(switches::kDisableWebSecurity); | 
|  | SecurityExploitBrowserTest::SetUpCommandLine(command_line); | 
|  | } | 
|  |  | 
|  | private: | 
|  | base::test::ScopedFeatureList feature_list_; | 
|  | }; | 
|  |  | 
|  | // Test to verify that an exploited renderer process trying to specify a | 
|  | // non-empty URL for base_url_for_data_url on navigation is correctly | 
|  | // terminated. | 
|  | 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()->GetMainFrame()); | 
|  |  | 
|  | GURL data_url("data:text/html,foo"); | 
|  | base::FilePath file_path = GetTestFilePath("", "simple_page.html"); | 
|  | GURL file_url = net::FilePathToFileURL(file_path); | 
|  |  | 
|  | // Setup a BeginNavigate IPC with non-empty base_url_for_data_url. | 
|  | mojom::CommonNavigationParamsPtr common_params = | 
|  | mojom::CommonNavigationParams::New( | 
|  | data_url, url::Origin::Create(data_url), | 
|  | blink::mojom::Referrer::New(), ui::PAGE_TRANSITION_LINK, | 
|  | mojom::NavigationType::DIFFERENT_DOCUMENT, NavigationDownloadPolicy(), | 
|  | false /* should_replace_current_entry */, | 
|  | file_url, /* base_url_for_data_url */ | 
|  | GURL() /* history_url_for_data_url */, PREVIEWS_UNSPECIFIED, | 
|  | base::TimeTicks::Now() /* navigation_start */, "GET", | 
|  | nullptr /* post_data */, network::mojom::SourceLocation::New(), | 
|  | false /* started_from_context_menu */, false /* has_user_gesture */, | 
|  | CreateInitiatorCSPInfo(), | 
|  | std::vector<int>() /* initiator_origin_trial_features */, | 
|  | std::string() /* href_translate */, | 
|  | false /* is_history_navigation_in_new_child_frame */, | 
|  | base::TimeTicks()); | 
|  | mojom::BeginNavigationParamsPtr begin_params = | 
|  | mojom::BeginNavigationParams::New( | 
|  | MSG_ROUTING_NONE /* initiator_routing_id */, | 
|  | std::string() /* headers */, net::LOAD_NORMAL, | 
|  | false /* skip_service_worker */, | 
|  | blink::mojom::RequestContextType::LOCATION, | 
|  | network::mojom::RequestDestination::kDocument, | 
|  | blink::WebMixedContentContextType::kBlockable, | 
|  | false /* is_form_submission */, | 
|  | false /* was_initiated_by_link_click */, | 
|  | GURL() /* searchable_form_url */, | 
|  | std::string() /* searchable_form_encoding */, | 
|  | GURL() /* client_side_redirect_url */, | 
|  | base::nullopt /* devtools_initiator_info */, | 
|  | false /* force_ignore_site_for_cookies */, | 
|  | nullptr /* trust_token_params */, base::nullopt /* impression */); | 
|  |  | 
|  | // Receiving the invalid IPC message should lead to renderer process | 
|  | // termination. | 
|  | 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()); | 
|  | 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)); | 
|  |  | 
|  | // Reload the page to create another renderer process. | 
|  | TestNavigationObserver tab_observer(shell()->web_contents(), 1); | 
|  | shell()->web_contents()->GetController().Reload(ReloadType::NORMAL, false); | 
|  | tab_observer.Wait(); | 
|  |  | 
|  | // Make an XHR request to check if the page has access. | 
|  | std::string script = base::StringPrintf( | 
|  | "var xhr = new XMLHttpRequest()\n" | 
|  | "xhr.open('GET', '%s', false);\n" | 
|  | "try { xhr.send(); } catch (e) {}\n" | 
|  | "window.domAutomationController.send(xhr.responseText);", | 
|  | file_url.spec().c_str()); | 
|  | std::string result; | 
|  | EXPECT_TRUE( | 
|  | ExecuteScriptAndExtractString(shell()->web_contents(), script, &result)); | 
|  | EXPECT_TRUE(result.empty()); | 
|  | } | 
|  |  | 
|  | // Tests what happens when a web renderer asks to begin navigating to a file | 
|  | // url. | 
|  | IN_PROC_BROWSER_TEST_F(SecurityExploitViaDisabledWebSecurityTest, | 
|  | WebToFileNavigation) { | 
|  | // Navigate to a web page. | 
|  | GURL start_url(embedded_test_server()->GetURL("/title1.html")); | 
|  | EXPECT_TRUE(NavigateToURL(shell(), start_url)); | 
|  |  | 
|  | // Have the webpage attempt to open a window with a file URL. | 
|  | // | 
|  | // Note that such attempt would normally be blocked in the renderer ("Not | 
|  | // allowed to load local resource: file:///..."), but the test here simulates | 
|  | // a compromised renderer by using --disable-web-security cmdline flag. | 
|  | 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(); | 
|  |  | 
|  | // Verify that the navigation got blocked. | 
|  | EXPECT_TRUE(nav_observer.last_navigation_succeeded()); | 
|  | EXPECT_EQ(GURL(kBlockedURL), nav_observer.last_navigation_url()); | 
|  | EXPECT_EQ(GURL(kBlockedURL), | 
|  | new_window->GetMainFrame()->GetLastCommittedURL()); | 
|  | EXPECT_EQ(shell()->web_contents()->GetMainFrame()->GetLastCommittedOrigin(), | 
|  | new_window->GetMainFrame()->GetLastCommittedOrigin()); | 
|  | EXPECT_EQ(shell()->web_contents()->GetMainFrame()->GetProcess(), | 
|  | new_window->GetMainFrame()->GetProcess()); | 
|  |  | 
|  | // Even though the navigation is blocked, we expect the opener relationship to | 
|  | // be established between the 2 windows. | 
|  | EXPECT_EQ(true, ExecJs(new_window, "!!window.opener")); | 
|  | } | 
|  |  | 
|  | // Tests what happens when a web renderer asks to begin navigating to a | 
|  | // view-source url. | 
|  | IN_PROC_BROWSER_TEST_F(SecurityExploitViaDisabledWebSecurityTest, | 
|  | WebToViewSourceNavigation) { | 
|  | // Navigate to a web page. | 
|  | GURL start_url(embedded_test_server()->GetURL("/title1.html")); | 
|  | EXPECT_TRUE(NavigateToURL(shell(), start_url)); | 
|  |  | 
|  | // Have the webpage attempt to open a window with a view-source URL. | 
|  | // | 
|  | // Note that such attempt would normally be blocked in the renderer ("Not | 
|  | // allowed to load local resource: view-source:///..."), but the test here | 
|  | // simulates a compromised renderer by using --disable-web-security flag. | 
|  | 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(); | 
|  |  | 
|  | // Verify that the navigation got blocked. | 
|  | EXPECT_TRUE(nav_observer.last_navigation_succeeded()); | 
|  | EXPECT_EQ(GURL(kBlockedURL), nav_observer.last_navigation_url()); | 
|  | EXPECT_EQ(GURL(kBlockedURL), | 
|  | new_window->GetMainFrame()->GetLastCommittedURL()); | 
|  | EXPECT_EQ(shell()->web_contents()->GetMainFrame()->GetLastCommittedOrigin(), | 
|  | new_window->GetMainFrame()->GetLastCommittedOrigin()); | 
|  | EXPECT_EQ(shell()->web_contents()->GetMainFrame()->GetProcess(), | 
|  | new_window->GetMainFrame()->GetProcess()); | 
|  |  | 
|  | // Even though the navigation is blocked, we expect the opener relationship to | 
|  | // be established between the 2 windows. | 
|  | 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, | 
|  | mojom::CommonNavigationParamsPtr* common_params, | 
|  | mojom::BeginNavigationParamsPtr* begin_params, | 
|  | mojo::PendingRemote<blink::mojom::BlobURLToken>* blob_url_token, | 
|  | mojo::PendingAssociatedRemote<mojom::NavigationClient>* navigation_client, | 
|  | mojo::PendingRemote<blink::mojom::NavigationInitiator>* | 
|  | navigation_initiator) 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::kTrustTokens); | 
|  | } | 
|  |  | 
|  | private: | 
|  | base::test::ScopedFeatureList feature_list_; | 
|  | }; | 
|  |  | 
|  | // Test that the browser correctly reports a bad message when a child frame | 
|  | // attempts to navigate with a Trust Tokens redemption operation associated with | 
|  | // the navigation, but its parent lacks the trust-token-redemption Feature | 
|  | // Policy feature. | 
|  | IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTestWithTrustTokensEnabled, | 
|  | BrowserForbidsTrustTokenRedemptionWithoutFeaturePolicy) { | 
|  | WebContents* web_contents = shell()->web_contents(); | 
|  |  | 
|  | // Prepare to intercept BeginNavigation mojo IPC. This has to be done before | 
|  | // the test creates the RenderFrameHostImpl that is the target of the IPC. | 
|  | auto params = network::mojom::TrustTokenParams::New(); | 
|  | params->type = network::mojom::TrustTokenOperationType::kRedemption; | 
|  | BeginNavigationTrustTokenParamsReplacer replacer(web_contents, | 
|  | std::move(params)); | 
|  |  | 
|  | GURL start_url(embedded_test_server()->GetURL( | 
|  | "/page-with-trust-token-feature-policy-disabled.html")); | 
|  | EXPECT_TRUE(NavigateToURL(shell(), start_url)); | 
|  |  | 
|  | RenderFrameHost* parent = web_contents->GetMainFrame(); | 
|  | ASSERT_FALSE(parent->IsFeatureEnabled( | 
|  | blink::mojom::FeaturePolicyFeature::kTrustTokenRedemption)); | 
|  |  | 
|  | RenderFrameHost* child = static_cast<WebContentsImpl*>(web_contents) | 
|  | ->GetFrameTree() | 
|  | ->root() | 
|  | ->child_at(0) | 
|  | ->current_frame_host(); | 
|  | RenderProcessHostBadMojoMessageWaiter kill_waiter(child->GetProcess()); | 
|  |  | 
|  | replacer.Activate(); | 
|  |  | 
|  | // Note: this can't use NavigateFrameToURL, because that method doesn't | 
|  | // route through RFHI::BeginNavigation. It also can't use NavigateIframeToURL, | 
|  | // because that navigation will hang. | 
|  | // | 
|  | // It also can't EXPECT_TRUE or EXPECT_FALSE: sometimes the ExecJs call will | 
|  | // finish before the renderer gets killed, and sometimes it won't. | 
|  | ignore_result( | 
|  | ExecJs(child, JsReplace("location.href=$1;", GURL("/title2.html")))); | 
|  |  | 
|  | EXPECT_THAT(kill_waiter.Wait(), | 
|  | Optional(HasSubstr("Feature Policy feature is absent"))); | 
|  | } | 
|  |  | 
|  | // Test that the browser correctly reports a bad message when a child frame | 
|  | // attempts to navigate with a Trust Tokens signing operation associated with | 
|  | // the navigation, but its parent lacks the trust-token-redemption (sic) Feature | 
|  | // Policy feature. | 
|  | IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTestWithTrustTokensEnabled, | 
|  | BrowserForbidsTrustTokenSigningWithoutFeaturePolicy) { | 
|  | WebContents* web_contents = shell()->web_contents(); | 
|  |  | 
|  | // Prepare to intercept BeginNavigation mojo IPC. This has to be done before | 
|  | // the test creates the RenderFrameHostImpl that is the target of the IPC. | 
|  | auto params = network::mojom::TrustTokenParams::New(); | 
|  | params->type = network::mojom::TrustTokenOperationType::kSigning; | 
|  | BeginNavigationTrustTokenParamsReplacer replacer(web_contents, | 
|  | std::move(params)); | 
|  |  | 
|  | GURL start_url(embedded_test_server()->GetURL( | 
|  | "/page-with-trust-token-feature-policy-disabled.html")); | 
|  | EXPECT_TRUE(NavigateToURL(shell(), start_url)); | 
|  |  | 
|  | RenderFrameHost* parent = web_contents->GetMainFrame(); | 
|  | ASSERT_FALSE(parent->IsFeatureEnabled( | 
|  | blink::mojom::FeaturePolicyFeature::kTrustTokenRedemption)); | 
|  |  | 
|  | RenderFrameHost* child = static_cast<WebContentsImpl*>(web_contents) | 
|  | ->GetFrameTree() | 
|  | ->root() | 
|  | ->child_at(0) | 
|  | ->current_frame_host(); | 
|  | RenderProcessHostBadMojoMessageWaiter kill_waiter(child->GetProcess()); | 
|  |  | 
|  | replacer.Activate(); | 
|  |  | 
|  | // Note: this can't use NavigateFrameToURL, because that method doesn't | 
|  | // route through RFHI::BeginNavigation. It also can't use NavigateIframeToURL, | 
|  | // because that navigation will hang. | 
|  | // | 
|  | // It also can't EXPECT_TRUE or EXPECT_FALSE: sometimes the ExecJs call will | 
|  | // finish before the renderer gets killed, and sometimes it won't. | 
|  | ignore_result( | 
|  | ExecJs(child, JsReplace("location.href=$1;", GURL("/title2.html")))); | 
|  |  | 
|  | EXPECT_THAT(kill_waiter.Wait(), | 
|  | Optional(HasSubstr("Feature Policy feature is absent"))); | 
|  | } | 
|  |  | 
|  | IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTestWithTrustTokensEnabled, | 
|  | BrowserForbidsTrustTokenParamsOnMainFrameNav) { | 
|  | WebContents* web_contents = shell()->web_contents(); | 
|  |  | 
|  | // Prepare to intercept BeginNavigation mojo IPC. This has to be done before | 
|  | // the test creates the RenderFrameHostImpl that is the target of the IPC. | 
|  | 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->GetMainFrame(); | 
|  | RenderProcessHostBadMojoMessageWaiter kill_waiter( | 
|  | compromised_renderer->GetProcess()); | 
|  |  | 
|  | replacer.Activate(); | 
|  |  | 
|  | // Can't use NavigateToURL here because it would hang. Additionally, we can't | 
|  | // EXPECT_TRUE or EXPECT_FALSE: sometimes the ExecJs call will finish | 
|  | // before the renderer gets killed, and sometimes it won't. | 
|  | ignore_result(ExecJs(compromised_renderer, | 
|  | JsReplace("location.href=$1", GURL("/title2.html")))); | 
|  |  | 
|  | EXPECT_THAT(kill_waiter.Wait(), | 
|  | Optional(HasSubstr("Trust Token params in main frame nav"))); | 
|  | } | 
|  |  | 
|  | }  // namespace content |