| // 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/command_line.h" |
| #include "base/containers/hash_tables.h" |
| #include "base/macros.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "build/build_config.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/renderer_host/render_view_host_factory.h" |
| #include "content/browser/renderer_host/render_view_host_impl.h" |
| #include "content/browser/web_contents/web_contents_impl.h" |
| #include "content/common/frame_messages.h" |
| #include "content/common/resource_messages.h" |
| #include "content/common/view_messages.h" |
| #include "content/public/browser/browser_context.h" |
| #include "content/public/browser/content_browser_client.h" |
| #include "content/public/browser/interstitial_page.h" |
| #include "content/public/browser/interstitial_page_delegate.h" |
| #include "content/public/browser/storage_partition.h" |
| #include "content/public/common/appcache_info.h" |
| #include "content/public/common/browser_side_navigation_policy.h" |
| #include "content/public/common/content_switches.h" |
| #include "content/public/common/file_chooser_params.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/test_utils.h" |
| #include "content/shell/browser/shell.h" |
| #include "content/test/test_content_browser_client.h" |
| #include "ipc/ipc_security_test_util.h" |
| #include "net/dns/mock_host_resolver.h" |
| #include "net/test/embedded_test_server/embedded_test_server.h" |
| |
| using IPC::IpcSecurityTestUtil; |
| |
| 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 RenderViewHost which is used for |
| // the attempt is the return value. |
| RenderViewHostImpl* PrepareToDuplicateHosts(Shell* shell, |
| int* target_routing_id) { |
| GURL foo("http://foo.com/simple_page.html"); |
| |
| // Start off with initial navigation, so we get the first process allocated. |
| 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->web_contents(), "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()->GetRenderViewHost()->GetProcess()->GetID(), |
| shell2->web_contents()->GetRenderViewHost()->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("https://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, nullptr, |
| Referrer(), CURRENT_TAB, false, true); |
| |
| // 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; |
| if (IsBrowserSideNavigationEnabled()) |
| next_rfh = wc->GetRenderManagerForTesting()->speculative_frame_host(); |
| else |
| next_rfh = wc->GetRenderManagerForTesting()->pending_frame_host(); |
| |
| EXPECT_TRUE(next_rfh); |
| EXPECT_NE(shell->web_contents()->GetRenderProcessHost()->GetID(), |
| next_rfh->GetProcess()->GetID()); |
| |
| return next_rfh->render_view_host(); |
| } |
| |
| ResourceHostMsg_Request CreateXHRRequestWithOrigin(const char* origin) { |
| ResourceHostMsg_Request request; |
| request.method = "GET"; |
| request.url = GURL("http://bar.com/simple_page.html"); |
| request.first_party_for_cookies = GURL(origin); |
| request.referrer_policy = blink::WebReferrerPolicyDefault; |
| request.headers = base::StringPrintf("Origin: %s\r\n", origin); |
| request.load_flags = 0; |
| request.origin_pid = 0; |
| request.resource_type = RESOURCE_TYPE_XHR; |
| request.request_context = 0; |
| request.appcache_host_id = kAppCacheNoHostId; |
| request.download_to_file = false; |
| request.should_reset_appcache = false; |
| request.is_main_frame = true; |
| request.parent_is_main_frame = false; |
| request.parent_render_frame_id = -1; |
| request.transition_type = ui::PAGE_TRANSITION_LINK; |
| request.allow_download = true; |
| return request; |
| } |
| |
| } // 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 { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| |
| // 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( |
| switches::kHostResolverRules, |
| "MAP * " + |
| net::HostPortPair::FromURL(embedded_test_server()->base_url()) |
| .ToString() + |
| ",EXCLUDE localhost"); |
| } |
| |
| protected: |
| // Tests that a given file path sent in a ViewHostMsg_RunFileChooser will |
| // cause renderer to be killed. |
| void TestFileChooserWithPath(const base::FilePath& path); |
| }; |
| |
| void SecurityExploitBrowserTest::TestFileChooserWithPath( |
| const base::FilePath& path) { |
| GURL foo("http://foo.com/simple_page.html"); |
| NavigateToURL(shell(), foo); |
| EXPECT_EQ(base::ASCIIToUTF16("OK"), shell()->web_contents()->GetTitle()); |
| |
| RenderViewHost* compromised_renderer = |
| shell()->web_contents()->GetRenderViewHost(); |
| RenderProcessHostWatcher terminated( |
| shell()->web_contents(), |
| RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT); |
| |
| FileChooserParams params; |
| params.default_file_name = path; |
| |
| ViewHostMsg_RunFileChooser evil(compromised_renderer->GetRoutingID(), params); |
| |
| IpcSecurityTestUtil::PwnMessageReceived( |
| compromised_renderer->GetProcess()->GetChannel(), evil); |
| terminated.Wait(); |
| } |
| |
| // 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"); |
| |
| NavigateToURL(shell(), foo); |
| EXPECT_EQ(base::ASCIIToUTF16("OK"), shell()->web_contents()->GetTitle()); |
| EXPECT_EQ(0, |
| shell()->web_contents()->GetRenderViewHost()->GetEnabledBindings()); |
| |
| RenderProcessHostWatcher terminated( |
| shell()->web_contents(), |
| RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT); |
| shell()->web_contents()->GetRenderViewHost()->SetWebUIProperty( |
| "toolkit", "views"); |
| terminated.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; |
| RenderViewHostImpl* pending_rvh = |
| PrepareToDuplicateHosts(shell(), &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 CreateNewWindow call coming |
| // from the IO thread. |
| ViewHostMsg_CreateWindow_Params params; |
| DOMStorageContextWrapper* dom_storage_context = |
| static_cast<DOMStorageContextWrapper*>( |
| BrowserContext::GetStoragePartition( |
| shell()->web_contents()->GetBrowserContext(), |
| pending_rvh->GetSiteInstance())->GetDOMStorageContext()); |
| scoped_refptr<SessionStorageNamespaceImpl> session_storage( |
| new SessionStorageNamespaceImpl(dom_storage_context)); |
| // Cause a deliberate collision in routing ids. |
| int32_t main_frame_routing_id = duplicate_routing_id + 1; |
| // TODO(avi): This should be made unique from the view routing ID once |
| // RenderViewHostImpl has-a RenderWidgetHostImpl. https://crbug.com/545684 |
| int32_t main_frame_widget_routing_id = duplicate_routing_id; |
| pending_rvh->CreateNewWindow(duplicate_routing_id, main_frame_routing_id, |
| main_frame_widget_routing_id, params, |
| session_storage.get()); |
| |
| // 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. |
| IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest, |
| AttemptDuplicateRenderWidgetHost) { |
| int duplicate_routing_id = MSG_ROUTING_NONE; |
| RenderViewHostImpl* pending_rvh = |
| PrepareToDuplicateHosts(shell(), &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_rvh->CreateNewWidget(duplicate_routing_id, blink::WebPopupTypePage); |
| |
| // If the above operation doesn't crash, the test has succeeded! |
| } |
| |
| // This is a test for crbug.com/444198. It tries to send a |
| // ViewHostMsg_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 |
| } |
| |
| class SecurityExploitTestInterstitialPage : public InterstitialPageDelegate { |
| public: |
| explicit SecurityExploitTestInterstitialPage(WebContents* contents) { |
| InterstitialPage* interstitial = InterstitialPage::Create( |
| contents, true, contents->GetLastCommittedURL(), this); |
| interstitial->Show(); |
| } |
| |
| // InterstitialPageDelegate implementation. |
| void CommandReceived(const std::string& command) override { |
| last_command_ = command; |
| } |
| |
| std::string GetHTMLContents() override { |
| return "<html><head><script>" |
| "window.domAutomationController.setAutomationId(1);" |
| "window.domAutomationController.send(\"okay\");" |
| "</script></head>" |
| "<body>this page is an interstitial</body></html>"; |
| } |
| |
| std::string last_command() { return last_command_; } |
| |
| private: |
| std::string last_command_; |
| DISALLOW_COPY_AND_ASSIGN(SecurityExploitTestInterstitialPage); |
| }; |
| |
| // Fails due to InterstitialPage's reliance on PostNonNestableTask |
| // http://crbug.com/432737 |
| #if defined(OS_ANDROID) |
| #define MAYBE_InterstitialCommandFromUnderlyingContent \ |
| DISABLED_InterstitialCommandFromUnderlyingContent |
| #else |
| #define MAYBE_InterstitialCommandFromUnderlyingContent \ |
| InterstitialCommandFromUnderlyingContent |
| #endif |
| |
| // The interstitial should not be controllable by the underlying content. |
| IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest, |
| MAYBE_InterstitialCommandFromUnderlyingContent) { |
| // Start off with initial navigation, to allocate the process. |
| GURL foo("http://foo.com/simple_page.html"); |
| NavigateToURL(shell(), foo); |
| EXPECT_EQ(base::ASCIIToUTF16("OK"), shell()->web_contents()->GetTitle()); |
| |
| DOMMessageQueue message_queue; |
| |
| // Install and show an interstitial page. |
| SecurityExploitTestInterstitialPage* interstitial = |
| new SecurityExploitTestInterstitialPage(shell()->web_contents()); |
| |
| ASSERT_EQ("", interstitial->last_command()); |
| WaitForInterstitialAttach(shell()->web_contents()); |
| |
| InterstitialPage* interstitial_page = |
| shell()->web_contents()->GetInterstitialPage(); |
| ASSERT_TRUE(interstitial_page != NULL); |
| ASSERT_TRUE(shell()->web_contents()->ShowingInterstitialPage()); |
| ASSERT_TRUE(interstitial_page->GetDelegateForTesting() == interstitial); |
| |
| // The interstitial page ought to be able to send a message. |
| std::string message; |
| ASSERT_TRUE(message_queue.WaitForMessage(&message)); |
| ASSERT_EQ("\"okay\"", message); |
| ASSERT_EQ("\"okay\"", interstitial->last_command()); |
| |
| // Send an automation message from the underlying content and wait for it to |
| // be dispatched on this thread. This message should not be received by the |
| // interstitial. |
| RenderFrameHost* compromised_renderer = |
| shell()->web_contents()->GetMainFrame(); |
| FrameHostMsg_DomOperationResponse evil(compromised_renderer->GetRoutingID(), |
| "evil"); |
| IpcSecurityTestUtil::PwnMessageReceived( |
| compromised_renderer->GetProcess()->GetChannel(), evil); |
| |
| ASSERT_TRUE(message_queue.WaitForMessage(&message)); |
| ASSERT_EQ("evil", message) |
| << "Automation message should be received by WebContents."; |
| ASSERT_EQ("\"okay\"", interstitial->last_command()) |
| << "Interstitial should not be affected."; |
| |
| // Send a second message from the interstitial page, and make sure that the |
| // "evil" message doesn't arrive in the intervening period. |
| ASSERT_TRUE(ExecuteScript(interstitial_page->GetMainFrame(), |
| "window.domAutomationController.send(\"okay2\");")); |
| ASSERT_TRUE(message_queue.WaitForMessage(&message)); |
| ASSERT_EQ("\"okay2\"", message); |
| ASSERT_EQ("\"okay2\"", interstitial->last_command()); |
| } |
| |
| class IsolatedAppContentBrowserClient : public TestContentBrowserClient { |
| public: |
| bool IsIllegalOrigin(content::ResourceContext* resource_context, |
| int child_process_id, |
| const GURL& origin) override { |
| // Simulate a case where an app origin is not in an app process. |
| return true; |
| } |
| }; |
| |
| // Renderer processes should not be able to spoof Origin HTTP headers. |
| IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest, InvalidOriginHeaders) { |
| // Create a set of IPC messages with various Origin headers. |
| ResourceHostMsg_Request chrome_origin_msg( |
| CreateXHRRequestWithOrigin("chrome://settings")); |
| ResourceHostMsg_Request embedder_isolated_origin_msg( |
| CreateXHRRequestWithOrigin("https://isolated.bar.com")); |
| ResourceHostMsg_Request invalid_origin_msg( |
| CreateXHRRequestWithOrigin("invalidurl")); |
| ResourceHostMsg_Request invalid_scheme_origin_msg( |
| CreateXHRRequestWithOrigin("fake-scheme://foo")); |
| |
| GURL web_url("http://foo.com/simple_page.html"); |
| NavigateToURL(shell(), web_url); |
| RenderFrameHost* web_rfh = shell()->web_contents()->GetMainFrame(); |
| |
| // Web processes cannot make XHRs with chrome:// Origin headers. |
| { |
| RenderProcessHostWatcher web_process_killed( |
| web_rfh->GetProcess(), |
| RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT); |
| IPC::IpcSecurityTestUtil::PwnMessageReceived( |
| web_rfh->GetProcess()->GetChannel(), |
| ResourceHostMsg_RequestResource(web_rfh->GetRoutingID(), 1, |
| chrome_origin_msg)); |
| web_process_killed.Wait(); |
| } |
| |
| // Web processes cannot make XHRs with URLs that the content embedder expects |
| // to have process isolation. Ideally this would test chrome-extension:// |
| // URLs for Chrome Apps, but those can't be tested inside content/ and the |
| // ResourceHostMsg_Request IPC can't be created in a test outside content/. |
| NavigateToURL(shell(), web_url); |
| { |
| // Set up a ContentBrowserClient that simulates an app URL in a non-app |
| // process. |
| IsolatedAppContentBrowserClient app_client; |
| ContentBrowserClient* old_client = SetBrowserClientForTesting(&app_client); |
| RenderProcessHostWatcher web_process_killed( |
| web_rfh->GetProcess(), |
| RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT); |
| IPC::IpcSecurityTestUtil::PwnMessageReceived( |
| web_rfh->GetProcess()->GetChannel(), |
| ResourceHostMsg_RequestResource(web_rfh->GetRoutingID(), 1, |
| embedder_isolated_origin_msg)); |
| web_process_killed.Wait(); |
| SetBrowserClientForTesting(old_client); |
| } |
| |
| // Web processes cannot make XHRs with invalid Origin headers. |
| NavigateToURL(shell(), web_url); |
| { |
| RenderProcessHostWatcher web_process_killed( |
| web_rfh->GetProcess(), |
| RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT); |
| IPC::IpcSecurityTestUtil::PwnMessageReceived( |
| web_rfh->GetProcess()->GetChannel(), |
| ResourceHostMsg_RequestResource(web_rfh->GetRoutingID(), 1, |
| invalid_origin_msg)); |
| web_process_killed.Wait(); |
| } |
| |
| // Web processes cannot make XHRs with invalid scheme Origin headers. |
| NavigateToURL(shell(), web_url); |
| { |
| RenderProcessHostWatcher web_process_killed( |
| web_rfh->GetProcess(), |
| RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT); |
| IPC::IpcSecurityTestUtil::PwnMessageReceived( |
| web_rfh->GetProcess()->GetChannel(), |
| ResourceHostMsg_RequestResource(web_rfh->GetRoutingID(), 1, |
| invalid_scheme_origin_msg)); |
| web_process_killed.Wait(); |
| } |
| } |
| |
| } // namespace content |