
This reverts commit9b4af06d02
. Reason for revert: This does not seem like the root cause. Original change's description: > Revert "Add std::move() to local base::Callback instances in //content" > > This reverts commitc55a5a47ee
. > > Reason for revert: Speculative revert because of renderer crashes across > the board on Android with: > [FATAL:weak_ptr.cc(26)] Check failed: sequence_checker_.CalledOnValidSequence(). WeakPtrs must be checked on the same sequenced thread > > Original change's description: > > Add std::move() to local base::Callback instances in //content > > > > This adds std::move() around base::Callback instances where it looks > > relevant, by applying `base_bind_rewriters -rewriter=add_std_move`. > > https://crrev.com/c/970143, plus manual fixes. > > > > Example: > > // Before: > > void set_callback(base::Closure cb) { g_cb = cb; } > > void RunCallback(base::Callback<void(int)> cb) { cb.Run(42); } > > void Post() { > > base::Closure task = base::Bind(&Foo); > > PostTask(FROM_HERE, task); > > } > > > > // After: > > void set_callback(base::Closure cb) { g_cb = std::move(cb); } > > void RunCallback(base::Callback<void(int)> cb) { std::move(cb).Run(42); } > > void Post() { > > base::Closure task = base::Bind(&Foo); > > PostTask(FROM_HERE, std::move(task)); > > } > > > > Specifically, it inserts std::move() if: > > - it's a pass-by-value parameter or non-const local variable. > > - the occurrence is the latest in its control flow. > > - no pointer is taken for the variable. > > - no capturing lambda exists for the variable. > > > > Change-Id: I53853f9b9c8604994e2065af66ed4607af9c12ed > > Reviewed-on: https://chromium-review.googlesource.com/970056 > > Reviewed-by: Kinuko Yasuda <kinuko@chromium.org> > > Commit-Queue: Taiju Tsuiki <tzik@chromium.org> > > Cr-Commit-Position: refs/heads/master@{#544356} > > TBR=kinuko@chromium.org,tzik@chromium.org > > Change-Id: Ie7392a2229e1ef0f740d8958f8fe43d99b0460e9 > No-Presubmit: true > No-Tree-Checks: true > No-Try: true > Reviewed-on: https://chromium-review.googlesource.com/972321 > Reviewed-by: Tommy Nyquist <nyquist@chromium.org> > Commit-Queue: Tommy Nyquist <nyquist@chromium.org> > Cr-Commit-Position: refs/heads/master@{#544527} TBR=kinuko@chromium.org,nyquist@chromium.org,tzik@chromium.org Change-Id: I0aabd6032a070a28d0e5a4f796f37fe18f1e5cd4 No-Presubmit: true No-Tree-Checks: true No-Try: true Reviewed-on: https://chromium-review.googlesource.com/972302 Reviewed-by: Tommy Nyquist <nyquist@chromium.org> Commit-Queue: Tommy Nyquist <nyquist@chromium.org> Cr-Commit-Position: refs/heads/master@{#544536}
759 lines
33 KiB
C++
759 lines
33 KiB
C++
// 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/memory/ptr_util.h"
|
|
#include "base/strings/utf_string_conversions.h"
|
|
#include "build/build_config.h"
|
|
#include "content/browser/bad_message.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/loader/resource_message_filter.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/web_contents_impl.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/browser_context.h"
|
|
#include "content/public/browser/browser_thread.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/resource_context.h"
|
|
#include "content/public/browser/resource_dispatcher_host.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_navigation_observer.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_provisional_load_interceptor.h"
|
|
#include "content/test/mock_widget_impl.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"
|
|
#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/network_switches.h"
|
|
#include "services/network/public/cpp/resource_request.h"
|
|
#include "services/network/public/mojom/url_loader.mojom.h"
|
|
#include "services/network/test/test_url_loader_client.h"
|
|
#include "third_party/WebKit/public/web/WebTriggeringEventInfo.h"
|
|
|
|
using IPC::IpcSecurityTestUtil;
|
|
|
|
namespace content {
|
|
|
|
namespace {
|
|
|
|
// This request id is used by tests that call CreateLoaderAndStart. The id is
|
|
// sufficiently large that it doesn't collide with ids used by previous
|
|
// navigation requests.
|
|
const int kRequestIdNotPreviouslyUsed = 10000;
|
|
|
|
// 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,
|
|
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, "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("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, false,
|
|
nullptr, std::string(), Referrer(), WindowOpenDisposition::CURRENT_TAB,
|
|
false, true, blink::WebTriggeringEventInfo::kFromTrustedEvent,
|
|
base::nullopt);
|
|
|
|
// 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;
|
|
}
|
|
|
|
network::ResourceRequest CreateXHRRequest(const char* url) {
|
|
network::ResourceRequest request;
|
|
request.method = "GET";
|
|
request.url = GURL(url);
|
|
request.referrer_policy = Referrer::GetDefaultReferrerPolicy();
|
|
request.request_initiator = url::Origin();
|
|
request.load_flags = 0;
|
|
request.plugin_child_id = -1;
|
|
request.resource_type = RESOURCE_TYPE_XHR;
|
|
request.appcache_host_id = kAppCacheNoHostId;
|
|
request.download_to_file = false;
|
|
request.should_reset_appcache = false;
|
|
request.is_main_frame = true;
|
|
request.transition_type = ui::PAGE_TRANSITION_LINK;
|
|
request.allow_download = true;
|
|
return request;
|
|
}
|
|
|
|
network::ResourceRequest CreateXHRRequestWithOrigin(const char* origin) {
|
|
network::ResourceRequest request =
|
|
CreateXHRRequest("http://bar.com/simple_page.html");
|
|
request.site_for_cookies = GURL(origin);
|
|
request.headers.SetHeader(net::HttpRequestHeaders::kOrigin, origin);
|
|
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 {
|
|
// 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();
|
|
|
|
BrowserThread::PostTask(
|
|
BrowserThread::IO, FROM_HERE,
|
|
base::BindOnce(&net::URLRequestSlowDownloadJob::AddUrlHandler));
|
|
}
|
|
|
|
static void CreateLoaderAndStartOnIOThread(
|
|
scoped_refptr<ResourceMessageFilter> filter,
|
|
int route_id,
|
|
int request_id,
|
|
const network::ResourceRequest& request) {
|
|
network::mojom::URLLoaderPtr loader;
|
|
network::TestURLLoaderClient client;
|
|
filter->CreateLoaderAndStart(
|
|
mojo::MakeRequest(&loader), route_id, request_id,
|
|
network::mojom::kURLLoadOptionNone, request,
|
|
client.CreateInterfacePtr(),
|
|
net::MutableNetworkTrafficAnnotationTag(TRAFFIC_ANNOTATION_FOR_TESTS));
|
|
}
|
|
|
|
static void CreateLoaderAndStart(RenderProcessHost* process,
|
|
int route_id,
|
|
int request_id,
|
|
const network::ResourceRequest& request) {
|
|
RenderProcessHostImpl* impl = static_cast<RenderProcessHostImpl*>(process);
|
|
auto filter = impl->resource_message_filter_;
|
|
|
|
process->GetChannel()->ipc_task_runner()->PostTask(
|
|
FROM_HERE, base::BindOnce(CreateLoaderAndStartOnIOThread, filter,
|
|
route_id, request_id, request));
|
|
}
|
|
|
|
void TryCreateDuplicateRequestIds(Shell* shell, bool block_loaders) {
|
|
NavigateToURL(shell, GURL("http://foo.com/simple_page.html"));
|
|
RenderFrameHost* rfh = shell->web_contents()->GetMainFrame();
|
|
|
|
if (block_loaders) {
|
|
// Test the case where loaders are placed into blocked_loaders_map_.
|
|
rfh->BlockRequestsForFrame();
|
|
}
|
|
|
|
// URLRequestSlowDownloadJob waits for another request to kFinishDownloadUrl
|
|
// to finish all pending requests. It is never sent, so the following URL
|
|
// blocks indefinitely, which is good because the request stays alive and
|
|
// the test can try to reuse the request id without a race.
|
|
const char* blocking_url = net::URLRequestSlowDownloadJob::kUnknownSizeUrl;
|
|
network::ResourceRequest request(CreateXHRRequest(blocking_url));
|
|
|
|
// Use the same request id twice.
|
|
RenderProcessHostKillWaiter kill_waiter(rfh->GetProcess());
|
|
|
|
CreateLoaderAndStart(rfh->GetProcess(), rfh->GetRoutingID(),
|
|
kRequestIdNotPreviouslyUsed, request);
|
|
CreateLoaderAndStart(rfh->GetProcess(), rfh->GetRoutingID(),
|
|
kRequestIdNotPreviouslyUsed, request);
|
|
EXPECT_EQ(bad_message::RDH_INVALID_REQUEST_ID, kill_waiter.Wait());
|
|
}
|
|
|
|
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 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());
|
|
|
|
RenderFrameHost* compromised_renderer =
|
|
shell()->web_contents()->GetMainFrame();
|
|
RenderProcessHostKillWaiter kill_waiter(compromised_renderer->GetProcess());
|
|
|
|
FileChooserParams params;
|
|
params.default_file_name = path;
|
|
|
|
FrameHostMsg_RunFileChooser evil(compromised_renderer->GetRoutingID(),
|
|
params);
|
|
|
|
IpcSecurityTestUtil::PwnMessageReceived(
|
|
compromised_renderer->GetProcess()->GetChannel(), evil);
|
|
EXPECT_EQ(bad_message::RFH_FILE_CHOOSER_PATH, kill_waiter.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()->GetMainFrame()->GetEnabledBindings());
|
|
|
|
RenderViewHost* compromised_renderer =
|
|
shell()->web_contents()->GetRenderViewHost();
|
|
RenderProcessHostKillWaiter 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(), &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.
|
|
IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest,
|
|
AttemptDuplicateRenderWidgetHost) {
|
|
#if defined(OS_WIN)
|
|
// PlzNavigate
|
|
// This test is failing in a flaky manner on Android as it appears to be
|
|
// leaking URLRequests. See crbug.com/702584.
|
|
if (IsBrowserSideNavigationEnabled())
|
|
return;
|
|
#endif
|
|
int duplicate_routing_id = MSG_ROUTING_NONE;
|
|
RenderFrameHostImpl* pending_rfh =
|
|
PrepareToDuplicateHosts(shell(), &duplicate_routing_id);
|
|
EXPECT_NE(MSG_ROUTING_NONE, duplicate_routing_id);
|
|
|
|
mojom::WidgetPtr widget;
|
|
std::unique_ptr<MockWidgetImpl> widget_impl =
|
|
std::make_unique<MockWidgetImpl>(mojo::MakeRequest(&widget));
|
|
|
|
// 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->render_view_host()->CreateNewWidget(
|
|
duplicate_routing_id, std::move(widget), blink::kWebPopupTypePage);
|
|
|
|
// 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
|
|
}
|
|
|
|
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.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 != nullptr);
|
|
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.
|
|
ExecuteScriptAsync(interstitial_page->GetMainFrame(),
|
|
"window.domAutomationController.send(\"okay2\");");
|
|
ASSERT_TRUE(message_queue.WaitForMessage(&message));
|
|
ASSERT_EQ("\"okay2\"", message);
|
|
ASSERT_EQ("\"okay2\"", interstitial->last_command());
|
|
}
|
|
|
|
// Intercepts the HTTP origin header and on being invoked once it is found
|
|
// aborts the requeest.
|
|
void OnHttpHeaderReceived(const std::string& header,
|
|
const std::string& value,
|
|
int child_process_id,
|
|
content::ResourceContext* resource_context,
|
|
OnHeaderProcessedCallback callback) {
|
|
std::move(callback).Run(HeaderInterceptorResult::KILL);
|
|
}
|
|
|
|
// 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.
|
|
network::ResourceRequest chrome_origin_msg(
|
|
CreateXHRRequestWithOrigin("chrome://settings"));
|
|
network::ResourceRequest embedder_isolated_origin_msg(
|
|
CreateXHRRequestWithOrigin("https://isolated.bar.com"));
|
|
network::ResourceRequest invalid_origin_msg(
|
|
CreateXHRRequestWithOrigin("invalidurl"));
|
|
network::ResourceRequest 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.
|
|
{
|
|
RenderProcessHostKillWaiter kill_waiter(web_rfh->GetProcess());
|
|
|
|
CreateLoaderAndStart(web_rfh->GetProcess(), web_rfh->GetRoutingID(),
|
|
kRequestIdNotPreviouslyUsed, chrome_origin_msg);
|
|
EXPECT_EQ(bad_message::RDH_ILLEGAL_ORIGIN, kill_waiter.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
|
|
// ResourceRequest IPC can't be created in a test outside content/.
|
|
NavigateToURL(shell(), web_url);
|
|
{
|
|
content::ResourceDispatcherHost::Get()->RegisterInterceptor(
|
|
"Origin", "", base::Bind(&OnHttpHeaderReceived));
|
|
|
|
RenderProcessHostKillWaiter kill_waiter(web_rfh->GetProcess());
|
|
CreateLoaderAndStart(web_rfh->GetProcess(), web_rfh->GetRoutingID(),
|
|
kRequestIdNotPreviouslyUsed,
|
|
embedder_isolated_origin_msg);
|
|
EXPECT_EQ(bad_message::RDH_ILLEGAL_ORIGIN, kill_waiter.Wait());
|
|
}
|
|
|
|
// Web processes cannot make XHRs with invalid Origin headers.
|
|
NavigateToURL(shell(), web_url);
|
|
{
|
|
RenderProcessHostKillWaiter kill_waiter(web_rfh->GetProcess());
|
|
CreateLoaderAndStart(web_rfh->GetProcess(), web_rfh->GetRoutingID(),
|
|
kRequestIdNotPreviouslyUsed, invalid_origin_msg);
|
|
EXPECT_EQ(bad_message::RDH_ILLEGAL_ORIGIN, kill_waiter.Wait());
|
|
}
|
|
|
|
// Web processes cannot make XHRs with invalid scheme Origin headers.
|
|
NavigateToURL(shell(), web_url);
|
|
{
|
|
RenderProcessHostKillWaiter kill_waiter(web_rfh->GetProcess());
|
|
CreateLoaderAndStart(web_rfh->GetProcess(), web_rfh->GetRoutingID(),
|
|
kRequestIdNotPreviouslyUsed,
|
|
invalid_scheme_origin_msg);
|
|
EXPECT_EQ(bad_message::RDH_ILLEGAL_ORIGIN, kill_waiter.Wait());
|
|
}
|
|
}
|
|
|
|
// Renderer process should not be able to create multiple requests with the same
|
|
// id.
|
|
// Disabled due to flakiness on all operating systems: crbug.com/799516
|
|
IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTest, DISABLED_InvalidRequestId) {
|
|
// Existing loader in pending_loaders_.
|
|
TryCreateDuplicateRequestIds(shell(), false);
|
|
// Existing loader in blocked_loaders_map_.
|
|
TryCreateDuplicateRequestIds(shell(), true);
|
|
}
|
|
|
|
// 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();
|
|
|
|
// Setup an URL which will never commit, allowing this test to send its own,
|
|
// malformed, commit message.
|
|
GURL url(embedded_test_server()->GetURL("/hung"));
|
|
|
|
// 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());
|
|
|
|
RenderProcessHostKillWaiter kill_waiter(
|
|
root->current_frame_host()->GetProcess());
|
|
|
|
// Create commit params with different origins in params.url and
|
|
// params.origin.
|
|
std::unique_ptr<FrameHostMsg_DidCommitProvisionalLoad_Params> params =
|
|
std::make_unique<FrameHostMsg_DidCommitProvisionalLoad_Params>();
|
|
params->nav_entry_id = 0;
|
|
params->did_create_new_entry = false;
|
|
params->url = url;
|
|
params->transition = ui::PAGE_TRANSITION_LINK;
|
|
params->should_update_history = false;
|
|
params->gesture = NavigationGestureAuto;
|
|
params->method = "GET";
|
|
params->page_state = PageState::CreateFromURL(url);
|
|
params->origin = url::Origin::Create(GURL("http://bar.com/"));
|
|
|
|
service_manager::mojom::InterfaceProviderPtr interface_provider;
|
|
static_cast<mojom::FrameHost*>(root->current_frame_host())
|
|
->DidCommitProvisionalLoad(std::move(params),
|
|
mojo::MakeRequest(&interface_provider));
|
|
|
|
// 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()->HasConnection());
|
|
|
|
EXPECT_EQ(bad_message::RFH_INVALID_ORIGIN_ON_COMMIT, kill_waiter.Wait());
|
|
}
|
|
|
|
namespace {
|
|
|
|
// Interceptor that replaces |interface_provider_request| with the specified
|
|
// value for the first DidCommitProvisionalLoad message it observes in the given
|
|
// |web_contents| while in scope.
|
|
class ScopedInterfaceProviderRequestReplacer
|
|
: public DidCommitProvisionalLoadInterceptor {
|
|
public:
|
|
ScopedInterfaceProviderRequestReplacer(
|
|
WebContents* web_contents,
|
|
service_manager::mojom::InterfaceProviderRequest
|
|
interface_provider_request_override)
|
|
: DidCommitProvisionalLoadInterceptor(web_contents),
|
|
interface_provider_request_override_(
|
|
std::move(interface_provider_request_override)) {}
|
|
~ScopedInterfaceProviderRequestReplacer() override = default;
|
|
|
|
protected:
|
|
void WillDispatchDidCommitProvisionalLoad(
|
|
RenderFrameHost* render_frame_host,
|
|
::FrameHostMsg_DidCommitProvisionalLoad_Params* params,
|
|
service_manager::mojom::InterfaceProviderRequest*
|
|
interface_provider_request) override {
|
|
ASSERT_TRUE(interface_provider_request_override_.has_value());
|
|
*interface_provider_request =
|
|
std::move(interface_provider_request_override_).value();
|
|
}
|
|
|
|
private:
|
|
base::Optional<service_manager::mojom::InterfaceProviderRequest>
|
|
interface_provider_request_override_;
|
|
|
|
DISALLOW_COPY_AND_ASSIGN(ScopedInterfaceProviderRequestReplacer);
|
|
};
|
|
|
|
} // namespace
|
|
|
|
// Test that, as a general rule, not receiving a new InterfaceProviderRequest
|
|
// for a cross-document navigation properly terminates the renderer process.
|
|
// There is one exception to this rule, see: RenderFrameHostImplBrowserTest.
|
|
// InterfaceProviderRequestIsOptionalForFirstCommit.
|
|
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));
|
|
|
|
RenderProcessHostKillWaiter kill_waiter(
|
|
shell()->web_contents()->GetMainFrame()->GetProcess());
|
|
|
|
ScopedInterfaceProviderRequestReplacer replacer(shell()->web_contents(),
|
|
nullptr);
|
|
NavigateToURLAndExpectNoCommit(shell(), non_same_document_url);
|
|
EXPECT_EQ(bad_message::RFH_INTERFACE_PROVIDER_MISSING, kill_waiter.Wait());
|
|
}
|
|
|
|
// 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();
|
|
|
|
RenderProcessHostKillWaiter kill_waiter(
|
|
root->current_frame_host()->GetProcess());
|
|
|
|
// Prepare a file to upload.
|
|
base::ThreadRestrictions::ScopedAllowIO allow_io_for_temp_dir;
|
|
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_LT(
|
|
0, base::WriteFile(file_path, file_content.data(), file_content.size()));
|
|
|
|
// 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.uses_post = true;
|
|
params.resource_request_body = new network::ResourceRequestBody;
|
|
params.resource_request_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;
|
|
params.is_history_navigation_in_new_child = false;
|
|
|
|
FrameHostMsg_OpenURL msg(root->current_frame_host()->routing_id(), params);
|
|
IPC::IpcSecurityTestUtil::PwnMessageReceived(
|
|
root->current_frame_host()->GetProcess()->GetChannel(), msg);
|
|
|
|
// 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.
|
|
EXPECT_EQ(bad_message::RFH_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"));
|
|
|
|
service_manager::mojom::InterfaceProviderPtr isolated_interface_provider;
|
|
static_cast<mojom::FrameHost*>(child0_0->current_frame_host())
|
|
->DidCommitProvisionalLoad(
|
|
std::move(params), mojo::MakeRequest(&isolated_interface_provider));
|
|
|
|
// 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());
|
|
}
|
|
|
|
} // namespace content
|