Prevent cross-site pages if the --site-per-process flag is passed
BUG=159215
Review URL: https://chromiumcodereview.appspot.com/11416121
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@172403 0039d316-1c4b-4281-b951-d872f2087c98
diff --git a/content/browser/child_process_security_policy_impl.cc b/content/browser/child_process_security_policy_impl.cc
index e86ccae..21ccb5f 100644
--- a/content/browser/child_process_security_policy_impl.cc
+++ b/content/browser/child_process_security_policy_impl.cc
@@ -167,6 +167,17 @@
return false;
}
+ bool CanLoadPage(const GURL& gurl) {
+ if (origin_lock_.is_empty())
+ return true;
+
+ // TODO(creis): We must pass the valid browser_context to convert hosted
+ // apps URLs. Currently, hosted apps cannot be loaded in this mode.
+ // See http://crbug.com/160576.
+ GURL site_gurl = SiteInstanceImpl::GetSiteForURL(NULL, gurl);
+ return origin_lock_ == site_gurl;
+ }
+
bool CanAccessCookiesForOrigin(const GURL& gurl) {
if (origin_lock_.is_empty())
return true;
@@ -487,6 +498,27 @@
state->second->RevokeReadRawCookies();
}
+bool ChildProcessSecurityPolicyImpl::CanLoadPage(
+ int child_id,
+ const GURL& url,
+ ResourceType::Type resource_type) {
+ // If --site-per-process flag is passed, we should enforce
+ // stronger security restrictions on page navigation.
+ if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kSitePerProcess) &&
+ ResourceType::IsFrame(resource_type)) {
+ // TODO(irobert): This currently breaks some WebUI page such as
+ // "chrome://extensions/" (belongs to site chrome://chrome/) which
+ // will load an iframe for the page "chrome://uber-frame/"
+ // (belongs to site chrome://uber-frame/).
+ base::AutoLock lock(lock_);
+ SecurityStateMap::iterator state = security_state_.find(child_id);
+ if (state == security_state_.end())
+ return false;
+ return state->second->CanLoadPage(url);
+ }
+ return true;
+}
+
bool ChildProcessSecurityPolicyImpl::CanRequestURL(
int child_id, const GURL& url) {
if (!url.is_valid())
diff --git a/content/browser/child_process_security_policy_impl.h b/content/browser/child_process_security_policy_impl.h
index 8a3d410e..93d2b116 100644
--- a/content/browser/child_process_security_policy_impl.h
+++ b/content/browser/child_process_security_policy_impl.h
@@ -15,6 +15,7 @@
#include "base/memory/singleton.h"
#include "base/synchronization/lock.h"
#include "content/public/browser/child_process_security_policy.h"
+#include "webkit/glue/resource_type.h"
class FilePath;
class GURL;
@@ -110,6 +111,13 @@
// request the URL.
bool CanRequestURL(int child_id, const GURL& url);
+ // Returns true if the process is permitted to load pages from
+ // the given origin in main frames or subframes.
+ // Only might return false if --site-per-process flag is used.
+ bool CanLoadPage(int child_id,
+ const GURL& url,
+ ResourceType::Type resource_type);
+
// Before servicing a child process's request to enumerate a directory
// the browser should call this method to check for the capability.
bool CanReadDirectory(int child_id, const FilePath& directory);
diff --git a/content/browser/loader/resource_loader.cc b/content/browser/loader/resource_loader.cc
index 2d35482..4c49590 100644
--- a/content/browser/loader/resource_loader.cc
+++ b/content/browser/loader/resource_loader.cc
@@ -4,6 +4,7 @@
#include "content/browser/loader/resource_loader.h"
+#include "base/command_line.h"
#include "base/message_loop.h"
#include "base/time.h"
#include "content/browser/child_process_security_policy_impl.h"
@@ -15,6 +16,8 @@
#include "content/common/ssl_status_serialization.h"
#include "content/public/browser/cert_store.h"
#include "content/public/browser/resource_dispatcher_host_login_delegate.h"
+#include "content/public/common/content_client.h"
+#include "content/public/common/content_switches.h"
#include "content/public/common/resource_response.h"
#include "net/base/load_flags.h"
#include "net/http/http_response_headers.h"
@@ -303,6 +306,19 @@
VLOG(1) << "OnResponseStarted: " << request_->url().spec();
+ // The CanLoadPage check should take place after any server redirects have
+ // finished, at the point in time that we know a page will commit in the
+ // renderer process.
+ ResourceRequestInfoImpl* info = GetRequestInfo();
+ ChildProcessSecurityPolicyImpl* policy =
+ ChildProcessSecurityPolicyImpl::GetInstance();
+ if (!policy->CanLoadPage(info->GetChildID(),
+ request_->url(),
+ info->GetResourceType())) {
+ Cancel();
+ return;
+ }
+
if (!request_->status().is_success()) {
ResponseCompleted();
return;
diff --git a/content/browser/site_per_process_browsertest.cc b/content/browser/site_per_process_browsertest.cc
new file mode 100644
index 0000000..fa033fd5
--- /dev/null
+++ b/content/browser/site_per_process_browsertest.cc
@@ -0,0 +1,397 @@
+// Copyright (c) 2012 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 "base/command_line.h"
+#include "base/stringprintf.h"
+#include "base/utf_string_conversions.h"
+#include "content/browser/web_contents/web_contents_impl.h"
+#include "content/public/browser/notification_observer.h"
+#include "content/public/browser/notification_service.h"
+#include "content/public/browser/notification_types.h"
+#include "content/public/browser/web_contents_observer.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/public/test/test_utils.h"
+#include "content/shell/shell.h"
+#include "content/test/content_browser_test.h"
+#include "content/test/content_browser_test_utils.h"
+
+namespace content {
+
+class SitePerProcessWebContentsObserver: public WebContentsObserver {
+ public:
+ explicit SitePerProcessWebContentsObserver(WebContents* web_contents)
+ : WebContentsObserver(web_contents),
+ navigation_succeeded_(true) {}
+ virtual ~SitePerProcessWebContentsObserver() {}
+
+ virtual void DidFailProvisionalLoad(
+ int64 frame_id,
+ bool is_main_frame,
+ const GURL& validated_url,
+ int error_code,
+ const string16& error_description,
+ RenderViewHost* render_view_host) OVERRIDE {
+ navigation_url_ = validated_url;
+ navigation_succeeded_ = false;
+ }
+
+ virtual void DidCommitProvisionalLoadForFrame(
+ int64 frame_id,
+ bool is_main_frame,
+ const GURL& url,
+ PageTransition transition_type,
+ RenderViewHost* render_view_host) OVERRIDE{
+ navigation_url_ = url;
+ navigation_succeeded_ = true;
+ }
+
+ const GURL& navigation_url() const {
+ return navigation_url_;
+ }
+
+ int navigation_succeeded() const { return navigation_succeeded_; }
+
+ private:
+ GURL navigation_url_;
+ bool navigation_succeeded_;
+
+ DISALLOW_COPY_AND_ASSIGN(SitePerProcessWebContentsObserver);
+};
+
+class RedirectNotificationObserver : public NotificationObserver {
+ public:
+ // Register to listen for notifications of the given type from either a
+ // specific source, or from all sources if |source| is
+ // NotificationService::AllSources().
+ RedirectNotificationObserver(int notification_type,
+ const NotificationSource& source);
+ virtual ~RedirectNotificationObserver();
+
+ // Wait until the specified notification occurs. If the notification was
+ // emitted between the construction of this object and this call then it
+ // returns immediately.
+ void Wait();
+
+ // Returns NotificationService::AllSources() if we haven't observed a
+ // notification yet.
+ const NotificationSource& source() const {
+ return source_;
+ }
+
+ const NotificationDetails& details() const {
+ return details_;
+ }
+
+ // NotificationObserver:
+ virtual void Observe(int type,
+ const NotificationSource& source,
+ const NotificationDetails& details) OVERRIDE;
+
+ private:
+ bool seen_;
+ bool seen_twice_;
+ bool running_;
+ NotificationRegistrar registrar_;
+
+ NotificationSource source_;
+ NotificationDetails details_;
+ scoped_refptr<MessageLoopRunner> message_loop_runner_;
+
+ DISALLOW_COPY_AND_ASSIGN(RedirectNotificationObserver);
+};
+
+RedirectNotificationObserver::RedirectNotificationObserver(
+ int notification_type,
+ const NotificationSource& source)
+ : seen_(false),
+ running_(false),
+ source_(NotificationService::AllSources()) {
+ registrar_.Add(this, notification_type, source);
+}
+
+RedirectNotificationObserver::~RedirectNotificationObserver() {}
+
+void RedirectNotificationObserver::Wait() {
+ if (seen_ && seen_twice_)
+ return;
+
+ running_ = true;
+ message_loop_runner_ = new MessageLoopRunner;
+ message_loop_runner_->Run();
+ EXPECT_TRUE(seen_);
+}
+
+void RedirectNotificationObserver::Observe(
+ int type,
+ const NotificationSource& source,
+ const NotificationDetails& details) {
+ source_ = source;
+ details_ = details;
+ seen_twice_ = seen_;
+ seen_ = true;
+ if (!running_)
+ return;
+
+ message_loop_runner_->Quit();
+ running_ = false;
+}
+
+class SitePerProcessBrowserTest : public ContentBrowserTest {
+ public:
+ SitePerProcessBrowserTest() {}
+
+ bool NavigateIframeToURL(Shell* window,
+ const GURL& url,
+ std::string iframe_id) {
+ std::string script = base::StringPrintf(
+ "var iframes = document.getElementById('%s');iframes.src='%s';",
+ iframe_id.c_str(), url.spec().c_str());
+ WindowedNotificationObserver load_observer(
+ NOTIFICATION_LOAD_STOP,
+ Source<NavigationController>(
+ &shell()->web_contents()->GetController()));
+ bool result = content::ExecuteJavaScript(
+ window->web_contents()->GetRenderViewHost(),
+ L"", ASCIIToWide(script));
+ load_observer.Wait();
+ return result;
+ }
+
+ void SetUpCommandLine(CommandLine* command_line) {
+ command_line->AppendSwitch(switches::kSitePerProcess);
+ }
+};
+
+IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest, CrossSiteIframe) {
+ ASSERT_TRUE(test_server()->Start());
+ net::TestServer https_server(
+ net::TestServer::TYPE_HTTPS,
+ net::TestServer::kLocalhost,
+ FilePath(FILE_PATH_LITERAL("content/test/data")));
+ ASSERT_TRUE(https_server.Start());
+ GURL main_url(test_server()->GetURL("files/site_per_process_main.html"));
+
+ NavigateToURL(shell(), main_url);
+
+ SitePerProcessWebContentsObserver observer(shell()->web_contents());
+ {
+ // Load same-site page into Iframe.
+ GURL http_url(test_server()->GetURL("files/title1.html"));
+ EXPECT_TRUE(NavigateIframeToURL(shell(), http_url, "test"));
+ EXPECT_EQ(observer.navigation_url(), http_url);
+ EXPECT_TRUE(observer.navigation_succeeded());
+ }
+
+ {
+ // Load cross-site page into Iframe.
+ GURL https_url(https_server.GetURL("files/title1.html"));
+ EXPECT_TRUE(NavigateIframeToURL(shell(), https_url, "test"));
+ EXPECT_EQ(observer.navigation_url(), https_url);
+ EXPECT_FALSE(observer.navigation_succeeded());
+ }
+}
+
+IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest, CrossSiteIframeRedirectOnce) {
+ ASSERT_TRUE(test_server()->Start());
+ net::TestServer https_server(
+ net::TestServer::TYPE_HTTPS,
+ net::TestServer::kLocalhost,
+ FilePath(FILE_PATH_LITERAL("content/test/data")));
+ ASSERT_TRUE(https_server.Start());
+
+ GURL main_url(test_server()->GetURL("files/site_per_process_main.html"));
+ GURL http_url(test_server()->GetURL("files/title1.html"));
+ GURL https_url(https_server.GetURL("files/title1.html"));
+
+ NavigateToURL(shell(), main_url);
+
+ SitePerProcessWebContentsObserver observer(shell()->web_contents());
+ {
+ // Load cross-site client-redirect page into Iframe.
+ // Should be blocked.
+ GURL client_redirect_https_url(https_server.GetURL(
+ "client-redirect?files/title1.html"));
+ EXPECT_TRUE(NavigateIframeToURL(shell(),
+ client_redirect_https_url, "test"));
+ // DidFailProvisionalLoad when navigating to client_redirect_https_url.
+ EXPECT_EQ(observer.navigation_url(), client_redirect_https_url);
+ EXPECT_FALSE(observer.navigation_succeeded());
+ }
+
+ {
+ // Load cross-site server-redirect page into Iframe,
+ // which redirects to same-site page.
+ GURL server_redirect_http_url(https_server.GetURL(
+ "server-redirect?" + http_url.spec()));
+ EXPECT_TRUE(NavigateIframeToURL(shell(),
+ server_redirect_http_url, "test"));
+ EXPECT_EQ(observer.navigation_url(), http_url);
+ EXPECT_TRUE(observer.navigation_succeeded());
+ }
+
+ {
+ // Load cross-site server-redirect page into Iframe,
+ // which redirects to cross-site page.
+ GURL server_redirect_http_url(https_server.GetURL(
+ "server-redirect?files/title1.html"));
+ EXPECT_TRUE(NavigateIframeToURL(shell(),
+ server_redirect_http_url, "test"));
+ // DidFailProvisionalLoad when navigating to https_url.
+ EXPECT_EQ(observer.navigation_url(), https_url);
+ EXPECT_FALSE(observer.navigation_succeeded());
+ }
+
+ {
+ // Load same-site server-redirect page into Iframe,
+ // which redirects to cross-site page.
+ GURL server_redirect_http_url(test_server()->GetURL(
+ "server-redirect?" + https_url.spec()));
+ EXPECT_TRUE(NavigateIframeToURL(shell(),
+ server_redirect_http_url, "test"));
+
+ EXPECT_EQ(observer.navigation_url(), https_url);
+ EXPECT_FALSE(observer.navigation_succeeded());
+ }
+
+ {
+ // Load same-site client-redirect page into Iframe,
+ // which redirects to cross-site page.
+ GURL client_redirect_http_url(test_server()->GetURL(
+ "client-redirect?" + https_url.spec()));
+
+ RedirectNotificationObserver load_observer2(
+ NOTIFICATION_LOAD_STOP,
+ Source<NavigationController>(
+ &shell()->web_contents()->GetController()));
+
+ EXPECT_TRUE(NavigateIframeToURL(shell(),
+ client_redirect_http_url, "test"));
+
+ // Same-site Client-Redirect Page should be loaded successfully.
+ EXPECT_EQ(observer.navigation_url(), client_redirect_http_url);
+ EXPECT_TRUE(observer.navigation_succeeded());
+
+ // Redirecting to Cross-site Page should be blocked.
+ load_observer2.Wait();
+ EXPECT_EQ(observer.navigation_url(), https_url);
+ EXPECT_FALSE(observer.navigation_succeeded());
+ }
+
+ {
+ // Load same-site server-redirect page into Iframe,
+ // which redirects to same-site page.
+ GURL server_redirect_http_url(test_server()->GetURL(
+ "server-redirect?files/title1.html"));
+ EXPECT_TRUE(NavigateIframeToURL(shell(),
+ server_redirect_http_url, "test"));
+ EXPECT_EQ(observer.navigation_url(), http_url);
+ EXPECT_TRUE(observer.navigation_succeeded());
+ }
+
+ {
+ // Load same-site client-redirect page into Iframe,
+ // which redirects to same-site page.
+ GURL client_redirect_http_url(test_server()->GetURL(
+ "client-redirect?" + http_url.spec()));
+ RedirectNotificationObserver load_observer2(
+ NOTIFICATION_LOAD_STOP,
+ Source<NavigationController>(
+ &shell()->web_contents()->GetController()));
+
+ EXPECT_TRUE(NavigateIframeToURL(shell(),
+ client_redirect_http_url, "test"));
+
+ // Same-site Client-Redirect Page should be loaded successfully.
+ EXPECT_EQ(observer.navigation_url(), client_redirect_http_url);
+ EXPECT_TRUE(observer.navigation_succeeded());
+
+ // Redirecting to Same-site Page should be loaded successfully.
+ load_observer2.Wait();
+ EXPECT_EQ(observer.navigation_url(), http_url);
+ EXPECT_TRUE(observer.navigation_succeeded());
+ }
+}
+
+IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest,
+ CrossSiteIframeRedirectTwice) {
+ ASSERT_TRUE(test_server()->Start());
+ net::TestServer https_server(
+ net::TestServer::TYPE_HTTPS,
+ net::TestServer::kLocalhost,
+ FilePath(FILE_PATH_LITERAL("content/test/data")));
+ ASSERT_TRUE(https_server.Start());
+
+ GURL main_url(test_server()->GetURL("files/site_per_process_main.html"));
+ GURL http_url(test_server()->GetURL("files/title1.html"));
+ GURL https_url(https_server.GetURL("files/title1.html"));
+
+ NavigateToURL(shell(), main_url);
+
+ SitePerProcessWebContentsObserver observer(shell()->web_contents());
+ {
+ // Load client-redirect page pointing to a cross-site client-redirect page,
+ // which eventually redirects back to same-site page.
+ GURL client_redirect_https_url(https_server.GetURL(
+ "client-redirect?" + http_url.spec()));
+ GURL client_redirect_http_url(test_server()->GetURL(
+ "client-redirect?" + client_redirect_https_url.spec()));
+
+ // We should wait until second client redirect get cancelled.
+ RedirectNotificationObserver load_observer2(
+ NOTIFICATION_LOAD_STOP,
+ Source<NavigationController>(
+ &shell()->web_contents()->GetController()));
+
+ EXPECT_TRUE(NavigateIframeToURL(shell(), client_redirect_http_url, "test"));
+
+ // DidFailProvisionalLoad when navigating to client_redirect_https_url.
+ load_observer2.Wait();
+ EXPECT_EQ(observer.navigation_url(), client_redirect_https_url);
+ EXPECT_FALSE(observer.navigation_succeeded());
+ }
+
+ {
+ // Load server-redirect page pointing to a cross-site server-redirect page,
+ // which eventually redirect back to same-site page.
+ GURL server_redirect_https_url(https_server.GetURL(
+ "server-redirect?" + http_url.spec()));
+ GURL server_redirect_http_url(test_server()->GetURL(
+ "server-redirect?" + server_redirect_https_url.spec()));
+ EXPECT_TRUE(NavigateIframeToURL(shell(),
+ server_redirect_http_url, "test"));
+ EXPECT_EQ(observer.navigation_url(), http_url);
+ EXPECT_TRUE(observer.navigation_succeeded());
+ }
+
+ {
+ // Load server-redirect page pointing to a cross-site server-redirect page,
+ // which eventually redirects back to cross-site page.
+ GURL server_redirect_https_url(https_server.GetURL(
+ "server-redirect?" + https_url.spec()));
+ GURL server_redirect_http_url(test_server()->GetURL(
+ "server-redirect?" + server_redirect_https_url.spec()));
+ EXPECT_TRUE(NavigateIframeToURL(shell(), server_redirect_http_url, "test"));
+
+ // DidFailProvisionalLoad when navigating to https_url.
+ EXPECT_EQ(observer.navigation_url(), https_url);
+ EXPECT_FALSE(observer.navigation_succeeded());
+ }
+
+ {
+ // Load server-redirect page pointing to a cross-site client-redirect page,
+ // which eventually redirects back to same-site page.
+ GURL client_redirect_http_url(https_server.GetURL(
+ "client-redirect?" + http_url.spec()));
+ GURL server_redirect_http_url(test_server()->GetURL(
+ "server-redirect?" + client_redirect_http_url.spec()));
+ EXPECT_TRUE(NavigateIframeToURL(shell(), server_redirect_http_url, "test"));
+
+ // DidFailProvisionalLoad when navigating to client_redirect_http_url.
+ EXPECT_EQ(observer.navigation_url(), client_redirect_http_url);
+ EXPECT_FALSE(observer.navigation_succeeded());
+ }
+}
+
+}