diff options
Diffstat (limited to 'chromium/content/browser/security_exploit_browsertest.cc')
-rw-r--r-- | chromium/content/browser/security_exploit_browsertest.cc | 305 |
1 files changed, 115 insertions, 190 deletions
diff --git a/chromium/content/browser/security_exploit_browsertest.cc b/chromium/content/browser/security_exploit_browsertest.cc index fe9275c119c..baf30825dbd 100644 --- a/chromium/content/browser/security_exploit_browsertest.cc +++ b/chromium/content/browser/security_exploit_browsertest.cc @@ -12,7 +12,6 @@ #include "base/memory/ptr_util.h" #include "base/optional.h" #include "base/strings/utf_string_conversions.h" -#include "base/task/post_task.h" #include "base/test/scoped_feature_list.h" #include "build/build_config.h" #include "content/browser/bad_message.h" @@ -25,7 +24,9 @@ #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" @@ -56,7 +57,6 @@ #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/mock_widget_impl.h" #include "content/test/test_content_browser_client.h" #include "ipc/ipc_message.h" #include "ipc/ipc_security_test_util.h" @@ -73,7 +73,6 @@ #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/test/url_request/url_request_slow_download_job.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" @@ -89,6 +88,7 @@ #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; @@ -138,7 +138,7 @@ RenderFrameHostImpl* PrepareToDuplicateHosts(Shell* shell, // 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()->navigator().RequestOpenURL( wc->GetFrameTree()->root()->current_frame_host(), extension_url, GlobalFrameRoutingId() /* initiator_routing_id */, url::Origin::Create(foo), nullptr, std::string(), Referrer(), @@ -159,6 +159,15 @@ RenderFrameHostImpl* PrepareToDuplicateHosts(Shell* shell, 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, @@ -283,10 +292,6 @@ class SecurityExploitBrowserTest : public ContentBrowserTest { // Complete the manual Start() after ContentBrowserTest's own // initialization, ref. comment on InitializeAndListen() above. embedded_test_server()->StartAcceptingConnections(); - - base::PostTask( - FROM_HERE, {BrowserThread::IO}, - base::BindOnce(&net::URLRequestSlowDownloadJob::AddUrlHandler)); } protected: @@ -313,12 +318,12 @@ void SecurityExploitBrowserTest::TestFileChooserWithPath( params->default_file_name = path; mojo::test::BadMessageObserver bad_message_observer; - mojo::Remote<blink::mojom::FileChooser> factory = - static_cast<RenderFrameHostImpl*>(compromised_renderer) - ->BindFileChooserForTesting(); - factory->OpenFileChooser( + mojo::Remote<blink::mojom::FileChooser> chooser = + FileChooserImpl::CreateBoundForTesting( + static_cast<RenderFrameHostImpl*>(compromised_renderer)); + chooser->OpenFileChooser( std::move(params), blink::mojom::FileChooser::OpenFileChooserCallback()); - factory.FlushForTesting(); + chooser.FlushForTesting(); EXPECT_THAT(bad_message_observer.WaitForBadMessage(), ::testing::StartsWith("FileChooser: The default file name")); } @@ -371,16 +376,11 @@ IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest, shell(), embedded_test_server(), &duplicate_routing_id); EXPECT_NE(MSG_ROUTING_NONE, duplicate_routing_id); - mojo::PendingRemote<mojom::Widget> widget; - std::unique_ptr<MockWidgetImpl> widget_impl = - std::make_unique<MockWidgetImpl>(widget.InitWithNewPipeAndPassReceiver()); - // 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( - std::move(widget), mojo::PendingAssociatedReceiver<blink::mojom::WidgetHost>(), mojo::PendingAssociatedRemote<blink::mojom::Widget>(), base::DoNothing()); @@ -412,8 +412,8 @@ IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest, UnexpectedMethodsSequence) { shell()->web_contents()->SetDelegate(delegate.get()); mojo::Remote<blink::mojom::FileChooser> chooser = - static_cast<RenderFrameHostImpl*>(compromised_renderer) - ->BindFileChooserForTesting(); + FileChooserImpl::CreateBoundForTesting( + static_cast<RenderFrameHostImpl*>(compromised_renderer)); base::RunLoop run_loop1; base::RunLoop run_loop2; chooser->OpenFileChooser(blink::mojom::FileChooserParams::New(), @@ -634,24 +634,18 @@ IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest, ASSERT_TRUE(base::CreateTemporaryFileInDir(temp_dir.GetPath(), &file_path)); ASSERT_TRUE(base::WriteFile(file_path, file_content)); - // Simulate an IPC message asking to POST a file that the renderer shouldn't - // have access to. - FrameHostMsg_OpenURL_Params params; - params.url = target_url; - params.post_body = new network::ResourceRequestBody; - params.post_body->AppendFileRange(file_path, 0, file_content.size(), - base::Time()); - params.disposition = WindowOpenDisposition::CURRENT_TAB; - params.should_replace_current_entry = true; - params.user_gesture = true; - - FrameHostMsg_OpenURL msg(root->current_frame_host()->routing_id(), params); - IPC::IpcSecurityTestUtil::PwnMessageReceived( - root->current_frame_host()->GetProcess()->GetChannel(), msg); + // 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|. - WaitForLoadStop(shell()->web_contents()); EXPECT_EQ(start_url, root->current_frame_host()->GetLastCommittedURL()); // Verify that the malicious renderer got killed. @@ -707,6 +701,7 @@ IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest, PageStateToWrongEntry) { 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; @@ -873,30 +868,21 @@ IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest, // Simulate an IPC message where the top frame asks the remote subframe to // navigate to a file: URL. - GURL file_url("file:///"); - FrameHostMsg_OpenURL_Params params; - params.url = file_url; - params.disposition = WindowOpenDisposition::CURRENT_TAB; - params.should_replace_current_entry = false; - params.user_gesture = true; - SiteInstance* a_com_instance = root->current_frame_host()->GetSiteInstance(); RenderFrameProxyHost* proxy = child->render_manager()->GetRenderFrameProxyHost(a_com_instance); EXPECT_TRUE(proxy); - { - FrameHostMsg_OpenURL msg(proxy->GetRoutingID(), params); - IPC::IpcSecurityTestUtil::PwnMessageReceived( - proxy->GetProcess()->GetChannel(), msg); - } + 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_TRUE(WaitForLoadStop(shell()->web_contents())); EXPECT_EQ(GURL(kBlockedURL), child->current_frame_host()->GetLastCommittedURL()); @@ -907,82 +893,51 @@ IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest, 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)); - params.url = chrome_url; - { - FrameHostMsg_OpenURL msg(proxy->GetRoutingID(), params); - IPC::IpcSecurityTestUtil::PwnMessageReceived( - proxy->GetProcess()->GetChannel(), msg); - } - EXPECT_TRUE(WaitForLoadStop(shell()->web_contents())); + proxy->frame_tree_node()->current_frame_host()->OpenURL( + CreateOpenURLParams(chrome_url)); + observer_2.Wait(); EXPECT_EQ(GURL(kBlockedURL), child->current_frame_host()->GetLastCommittedURL()); } -class PostMessageIpcInterceptor : public BrowserMessageFilter { +class RouteMessageEventInterceptor + : public blink::mojom::RemoteFrameHostInterceptorForTesting { public: - // Starts listening for IPC messages to |process|, intercepting - // FrameHostMsg_RouteMessageEvent and storing once it comes. - explicit PostMessageIpcInterceptor(RenderProcessHost* process) - : BrowserMessageFilter(FrameMsgStart) { - process->AddFilter(this); + 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); } - // Waits for FrameMsg_PostMessage_Params (if it didn't come yet) and returns - // message payload to the caller. - void WaitForMessage(int32_t* out_routing_id, - FrameMsg_PostMessage_Params* out_params) { - run_loop_.Run(); - *out_routing_id = intercepted_routing_id_; - *out_params = intercepted_params_; + RemoteFrameHost* GetForwardingInterface() override { + return render_frame_proxy_host_; } - private: - ~PostMessageIpcInterceptor() override = default; - - void OnRouteMessageEvent(const FrameMsg_PostMessage_Params& params) { - intercepted_params_ = params; - - // UaF would have happened without the call below - the call ensures that - // the data is still valid even once the original message is destroyed. - intercepted_params_.message->data.EnsureDataIsOwned(); + 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)); } - bool OnMessageReceived(const IPC::Message& message) override { - // Only intercept one message. - if (already_intercepted_) - return false; - - // See if we got FrameHostMsg_RouteMessageEvent and if so unpack and store - // its payload in OnRouteMessageEvent. - bool handled = true; - IPC_BEGIN_MESSAGE_MAP(PostMessageIpcInterceptor, message) - IPC_MESSAGE_HANDLER(FrameHostMsg_RouteMessageEvent, OnRouteMessageEvent) - IPC_MESSAGE_UNHANDLED(handled = false) - IPC_END_MESSAGE_MAP() - - // If we got FrameHostMsg_RouteMessageEvent, then also store the routing ID - // and signal to the main test thread that it can stop waiting. - if (handled) { - already_intercepted_ = true; - intercepted_routing_id_ = message.routing_id(); - run_loop_.Quit(); - } - - return handled; - } - - bool already_intercepted_ = false; - int32_t intercepted_routing_id_; - FrameMsg_PostMessage_Params intercepted_params_; - base::RunLoop run_loop_; - - DISALLOW_COPY_AND_ASSIGN(PostMessageIpcInterceptor); + private: + RenderFrameProxyHost* render_frame_proxy_host_; + base::string16 evil_origin_; }; -// Test verifying that a compromised renderer can't lie about -// FrameMsg_PostMessage_Params::source_origin. See also +// 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 @@ -1000,91 +955,65 @@ IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest, PostMessageSourceOrigin) { RenderFrameHost* subframe = web_contents->GetAllFrames()[1]; EXPECT_NE(main_frame->GetProcess(), subframe->GetProcess()); - // Prepare to intercept FrameHostMsg_RouteMessageEvent IPC message that will - // come from the subframe process. - RenderProcessHost* subframe_process = subframe->GetProcess(); - auto ipc_interceptor = - base::MakeRefCounted<PostMessageIpcInterceptor>(subframe_process); - - // Post a message from the subframe to the cross-site parent and intercept the - // associated IPC message. - EXPECT_TRUE(ExecJs(subframe, "parent.postMessage('blah', '*')")); - int intercepted_routing_id; - FrameMsg_PostMessage_Params intercepted_params; - ipc_interceptor->WaitForMessage(&intercepted_routing_id, &intercepted_params); - - // Change the intercepted message to simulate a compromised subframe renderer - // lying that the |source_origin| of the postMessage is the origin of the - // parent (not of the subframe). + // 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(); - FrameMsg_PostMessage_Params evil_params = intercepted_params; - evil_params.source_origin = base::UTF8ToUTF16(invalid_origin.Serialize()); - FrameHostMsg_RouteMessageEvent evil_msg(intercepted_routing_id, evil_params); + base::string16 evil_source_origin = + base::UTF8ToUTF16(invalid_origin.Serialize()); + RouteMessageEventInterceptor mojo_interceptor(main_frame_proxy_host, + evil_source_origin); - // Inject the invalid IPC and verify that the renderer gets terminated. - RenderProcessHostBadIpcMessageWaiter kill_waiter(subframe_process); - IpcSecurityTestUtil::PwnMessageReceived(subframe_process->GetChannel(), - evil_msg); + // 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()); } -class OpenUrlIpcInterceptor : public BrowserMessageFilter { +// Intercepts calls to RenderFramHost's OpenURL mojo method, and +// store the passed parameter. +class OpenURLInterceptor : public mojom::FrameHostInterceptorForTesting { public: - // Starts listening for IPC messages to |process|, intercepting - // FrameHostMsg_OpenURL IPC message and storing once it comes. - explicit OpenUrlIpcInterceptor(RenderProcessHost* process) - : BrowserMessageFilter(FrameMsgStart) { - process->AddFilter(this); - } - - // Waits for FrameHostMsg_OpenURL (if it didn't come yet) and returns - // message payload to the caller. - void WaitForMessage(int32_t* out_routing_id, - FrameHostMsg_OpenURL_Params* out_params) { - run_loop_.Run(); - *out_routing_id = intercepted_routing_id_; - *out_params = intercepted_params_; - } + explicit OpenURLInterceptor(RenderFrameHostImpl* render_frame_host) + : render_frame_host_(render_frame_host), + intercepted_params_(mojom::OpenURLParams::New()) {} - private: - ~OpenUrlIpcInterceptor() override = default; + ~OpenURLInterceptor() override = default; - void OnOpenURL(const FrameHostMsg_OpenURL_Params& params) { - intercepted_params_ = params; + mojom::FrameHost* GetForwardingInterface() override { + return render_frame_host_; } - bool OnMessageReceived(const IPC::Message& message) override { - // Only intercept one message. - if (already_intercepted_) - return false; - - // See if we got FrameHostMsg_RouteMessageEvent and if so unpack and store - // its payload in OnRouteMessageEvent. - bool handled = true; - IPC_BEGIN_MESSAGE_MAP(OpenUrlIpcInterceptor, message) - IPC_MESSAGE_HANDLER(FrameHostMsg_OpenURL, OnOpenURL) - IPC_MESSAGE_UNHANDLED(handled = false) - IPC_END_MESSAGE_MAP() - - // If we got FrameHostMsg_RouteMessageEvent, then also store the routing ID - // and signal to the main test thread that it can stop waiting. - if (handled) { - already_intercepted_ = true; - intercepted_routing_id_ = message.routing_id(); - run_loop_.Quit(); - } + void OpenURL(mojom::OpenURLParamsPtr params) override { + intercepted_params_ = std::move(params); + } - return handled; + mojom::OpenURLParamsPtr GetInterceptedParams() { + return std::move(intercepted_params_); } - bool already_intercepted_ = false; - int32_t intercepted_routing_id_; - FrameHostMsg_OpenURL_Params intercepted_params_; - base::RunLoop run_loop_; + private: + RenderFrameHostImpl* render_frame_host_; + mojom::OpenURLParamsPtr intercepted_params_; - DISALLOW_COPY_AND_ASSIGN(OpenUrlIpcInterceptor); + DISALLOW_COPY_AND_ASSIGN(OpenURLInterceptor); }; IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest, @@ -1105,28 +1034,24 @@ IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest, RenderProcessHost* subframe_process = subframe->GetProcess(); EXPECT_NE(main_process->GetID(), subframe_process->GetID()); - // Prepare to intercept FrameHostMsg_OpenURL IPC message that will come from - // the main frame process. - auto ipc_interceptor = - base::MakeRefCounted<OpenUrlIpcInterceptor>(main_process); + // 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 FrameHostMsg_OpenURL IPC being sent to the RenderFrameProxyHost. + // result in OpenURL Mojo message being sent to the RenderFrameProxyHost. EXPECT_TRUE(ExecJs(shell()->web_contents()->GetMainFrame(), "window.frames[0].location = '/title1.html';")); - int intercepted_routing_id; - FrameHostMsg_OpenURL_Params intercepted_params; - ipc_interceptor->WaitForMessage(&intercepted_routing_id, &intercepted_params); // Change the intercepted message to simulate a compromised subframe renderer // lying that the |initiator_origin| is the origin of the |subframe|. - FrameHostMsg_OpenURL_Params evil_params = intercepted_params; - evil_params.initiator_origin = subframe->GetLastCommittedOrigin(); - FrameHostMsg_OpenURL evil_msg(intercepted_routing_id, evil_params); + 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); - IpcSecurityTestUtil::PwnMessageReceived(main_process->GetChannel(), evil_msg); + static_cast<RenderFrameHostImpl*>(main_frame) + ->OpenURL(std::move(evil_params)); EXPECT_EQ(bad_message::INVALID_INITIATOR_ORIGIN, kill_waiter.Wait()); } |