summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authornasko@chromium.org <nasko@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-04-03 19:50:47 +0000
committernasko@chromium.org <nasko@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-04-03 19:50:47 +0000
commit5cd56344c217d3e01263e0745c39a30c22c203ba (patch)
tree9c007d78640af9f9488b31115374203f71d23700
parent49d6f06fcda53bbc68c152b4285ba86d7cc61694 (diff)
downloadchromium_src-5cd56344c217d3e01263e0745c39a30c22c203ba.zip
chromium_src-5cd56344c217d3e01263e0745c39a30c22c203ba.tar.gz
chromium_src-5cd56344c217d3e01263e0745c39a30c22c203ba.tar.bz2
Non-web-accessible extension URLs should not load in non-extension processes
This is a slightly modified version of my previous CL: https://codereview.chromium.org/12218064/. The only difference is that we allow any resource request to succeed, if the extension has any web_acessible_resources. The reason for that we have been lax and allowed subresource loads, even if they are not explicitly added to the manifest (see crbug.com/179127 for details). This should be tightened up with a v3 manifest requirement to explicitly list all subresources. BUG=173688 Review URL: https://chromiumcodereview.appspot.com/12457042 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@192121 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--chrome/browser/chrome_security_exploit_browsertest.cc72
-rw-r--r--chrome/browser/extensions/extension_protocols.cc70
-rw-r--r--chrome/chrome_tests.gypi1
-rw-r--r--chrome/common/extensions/extension.h3
-rw-r--r--chrome/renderer/extensions/resource_request_policy.cc6
-rw-r--r--chrome/test/data/chrome_extension_resource.html33
6 files changed, 180 insertions, 5 deletions
diff --git a/chrome/browser/chrome_security_exploit_browsertest.cc b/chrome/browser/chrome_security_exploit_browsertest.cc
new file mode 100644
index 0000000..fd9bdc5
--- /dev/null
+++ b/chrome/browser/chrome_security_exploit_browsertest.cc
@@ -0,0 +1,72 @@
+// 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 "base/command_line.h"
+#include "base/stringprintf.h"
+#include "base/utf_string_conversions.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/browser_commands.h"
+#include "chrome/browser/ui/singleton_tabs.h"
+#include "chrome/browser/ui/tabs/tab_strip_model.h"
+#include "chrome/test/base/in_process_browser_test.h"
+#include "chrome/test/base/ui_test_utils.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/resource_request_details.h"
+#include "content/public/browser/web_contents_observer.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/test/browser_test_utils.h"
+
+// The goal of these tests is 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.
+// This is similar to the security_exploit_browsertest.cc tests, but also
+// includes chrome/ layer concepts such as extensions.
+class ChromeSecurityExploitBrowserTest : public InProcessBrowserTest {
+ public:
+ ChromeSecurityExploitBrowserTest() {}
+ virtual ~ChromeSecurityExploitBrowserTest() {}
+
+ virtual void SetUpCommandLine(CommandLine* command_line) {
+ ASSERT_TRUE(test_server()->Start());
+ net::TestServer https_server(
+ net::TestServer::TYPE_HTTPS,
+ net::TestServer::kLocalhost,
+ base::FilePath(FILE_PATH_LITERAL("chrome/test/data")));
+ ASSERT_TRUE(https_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 * " + test_server()->host_port_pair().ToString() +
+ ",EXCLUDE localhost");
+
+ // Since we assume exploited renderer process, it can bypass the same origin
+ // policy at will. Simulate that by passing the disable-web-security flag.
+ command_line->AppendSwitch(switches::kDisableWebSecurity);
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ChromeSecurityExploitBrowserTest);
+};
+
+IN_PROC_BROWSER_TEST_F(ChromeSecurityExploitBrowserTest,
+ ChromeExtensionResources) {
+ // Load a page that requests a chrome-extension:// image through XHR. We
+ // expect this load to fail, as it is an illegal request.
+ GURL foo("http://foo.com/files/chrome_extension_resource.html");
+
+ content::DOMMessageQueue msg_queue;
+
+ ui_test_utils::NavigateToURL(browser(), foo);
+
+ std::string status;
+ std::string expected_status("0");
+ EXPECT_TRUE(msg_queue.WaitForMessage(&status));
+ EXPECT_STREQ(status.c_str(), expected_status.c_str());
+}
diff --git a/chrome/browser/extensions/extension_protocols.cc b/chrome/browser/extensions/extension_protocols.cc
index 55f3cc7..049d813 100644
--- a/chrome/browser/extensions/extension_protocols.cc
+++ b/chrome/browser/extensions/extension_protocols.cc
@@ -26,6 +26,7 @@
#include "chrome/common/extensions/extension.h"
#include "chrome/common/extensions/extension_file_util.h"
#include "chrome/common/extensions/incognito_handler.h"
+#include "chrome/common/extensions/manifest_url_handler.h"
#include "chrome/common/extensions/web_accessible_resources_handler.h"
#include "chrome/common/url_constants.h"
#include "content/public/browser/resource_request_info.h"
@@ -270,6 +271,7 @@ bool ExtensionCanLoadInIncognito(const ResourceRequestInfo* info,
// first need to find a way to get CanLoadInIncognito state into the renderers.
bool AllowExtensionResourceLoad(net::URLRequest* request,
bool is_incognito,
+ const Extension* extension,
ExtensionInfoMap* extension_info_map) {
const ResourceRequestInfo* info = ResourceRequestInfo::ForRequest(request);
@@ -286,6 +288,63 @@ bool AllowExtensionResourceLoad(net::URLRequest* request,
return false;
}
+ // The following checks are meant to replicate similar set of checks in the
+ // renderer process, performed by ResourceRequestPolicy::CanRequestResource.
+ // These are not exactly equivalent, because we don't have the same bits of
+ // information. The two checks need to be kept in sync as much as possible, as
+ // an exploited renderer can bypass the checks in ResourceRequestPolicy.
+
+ // Check if the extension for which this request is made is indeed loaded in
+ // the process sending the request. If not, we need to explicitly check if
+ // the resource is explicitly accessible or fits in a set of exception cases.
+ // Note: This allows a case where two extensions execute in the same renderer
+ // process to request each other's resources. We can't do a more precise
+ // check, since the renderer can lie about which extension has made the
+ // request.
+ if (extension_info_map->process_map().Contains(
+ request->url().host(), info->GetChildID())) {
+ return true;
+ }
+
+ if (!content::PageTransitionIsWebTriggerable(info->GetPageTransition()))
+ return false;
+
+ // The following checks require that we have an actual extension object. If we
+ // don't have it, allow the request handling to continue with the rest of the
+ // checks.
+ if (!extension)
+ return true;
+
+ // Disallow loading of packaged resources for hosted apps. We don't allow
+ // hybrid hosted/packaged apps. The one exception is access to icons, since
+ // some extensions want to be able to do things like create their own
+ // launchers.
+ std::string resource_root_relative_path =
+ request->url().path().empty() ? "" : request->url().path().substr(1);
+ if (extension->is_hosted_app() &&
+ !extensions::IconsInfo::GetIcons(extension)
+ .ContainsPath(resource_root_relative_path)) {
+ LOG(ERROR) << "Denying load of " << request->url().spec() << " from "
+ << "hosted app.";
+ return false;
+ }
+
+ // Extensions with web_accessible_resources: allow loading by regular
+ // renderers. Since not all subresources are required to be listed in a v2
+ // manifest, we must allow all loads if there are any web accessible
+ // resources. See http://crbug.com/179127.
+ if (extension->manifest_version() < 2 ||
+ extensions::WebAccessibleResourcesInfo::HasWebAccessibleResources(
+ extension)) {
+ return true;
+ }
+
+ // If there aren't any explicitly marked web accessible resources, the
+ // load should be allowed only if it is by DevTools. A close approximation is
+ // checking if the extension contains a DevTools page.
+ if (extensions::ManifestURL::GetDevToolsPage(extension).is_empty())
+ return false;
+
return true;
}
@@ -327,17 +386,18 @@ class ExtensionProtocolHandler
net::URLRequestJob*
ExtensionProtocolHandler::MaybeCreateJob(
net::URLRequest* request, net::NetworkDelegate* network_delegate) const {
+ // chrome-extension://extension-id/resource/path.js
+ const std::string& extension_id = request->url().host();
+ const Extension* extension =
+ extension_info_map_->extensions().GetByID(extension_id);
+
// TODO(mpcomplete): better error code.
if (!AllowExtensionResourceLoad(
- request, is_incognito_, extension_info_map_)) {
+ request, is_incognito_, extension, extension_info_map_)) {
return new net::URLRequestErrorJob(
request, network_delegate, net::ERR_ADDRESS_UNREACHABLE);
}
- // chrome-extension://extension-id/resource/path.js
- const std::string& extension_id = request->url().host();
- const Extension* extension =
- extension_info_map_->extensions().GetByID(extension_id);
base::FilePath directory_path;
if (extension)
directory_path = extension->path();
diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi
index a1b3af2..eff0185 100644
--- a/chrome/chrome_tests.gypi
+++ b/chrome/chrome_tests.gypi
@@ -1118,6 +1118,7 @@
'browser/chrome_content_browser_client_browsertest.cc',
'browser/chrome_main_browsertest.cc',
'browser/chrome_plugin_browsertest.cc',
+ 'browser/chrome_security_exploit_browsertest.cc',
'browser/chrome_switches_browsertest.cc',
'browser/chromeos/accessibility/magnification_manager_browsertest.cc',
'browser/chromeos/app_mode/kiosk_app_manager_browsertest.cc',
diff --git a/chrome/common/extensions/extension.h b/chrome/common/extensions/extension.h
index e1d0ae9..0ab8b4e 100644
--- a/chrome/common/extensions/extension.h
+++ b/chrome/common/extensions/extension.h
@@ -51,6 +51,9 @@ class APIPermissionSet;
class PermissionSet;
// Represents a Chrome extension.
+// Once created, an Extension object is immutable, with the exception of its
+// RuntimeData. This makes it safe to use on any thread, since access to the
+// RuntimeData is protected by a lock.
class Extension : public base::RefCountedThreadSafe<Extension> {
public:
struct ManifestData;
diff --git a/chrome/renderer/extensions/resource_request_policy.cc b/chrome/renderer/extensions/resource_request_policy.cc
index 756844f..116b218 100644
--- a/chrome/renderer/extensions/resource_request_policy.cc
+++ b/chrome/renderer/extensions/resource_request_policy.cc
@@ -24,6 +24,12 @@
namespace extensions {
+// This method does a security check whether chrome-extension:// URLs can be
+// requested by the renderer. Since this is in an untrusted process, the browser
+// has a similar check to enforce the policy, in case this process is exploited.
+// If you are changing this function, ensure equivalent checks are added to
+// extension_protocols.cc's AllowExtensionResourceLoad.
+
// static
bool ResourceRequestPolicy::CanRequestResource(
const GURL& resource_url,
diff --git a/chrome/test/data/chrome_extension_resource.html b/chrome/test/data/chrome_extension_resource.html
new file mode 100644
index 0000000..34c996c
--- /dev/null
+++ b/chrome/test/data/chrome_extension_resource.html
@@ -0,0 +1,33 @@
+<html>
+<head>
+<script>
+var xhrStatus = -1;
+var imgUrl = 'chrome-extension://eemcgdkfndhakfknompkggombfjjjeno/images/bookmark_manager_recent.png';
+
+window.onload = function() {
+ // The call to pushState with chrome-extension:// URL will succeed, since the
+ // test uses --disable-web-security.
+ history.pushState('', '',
+ 'chrome-extension://eemcgdkfndhakfknompkggombfjjjeno/main.html');
+ var xhr = new XMLHttpRequest();
+ xhr.onreadystatechange = function() {
+ if (xhr.readyState == 4) {
+ xhrStatus = xhr.status;
+ if (xhrStatus == 200) {
+ document.getElementById('star').src =
+ window.URL.createObjectURL(this.response);
+ }
+ domAutomationController.setAutomationId(0);
+ domAutomationController.send(xhr.status);
+ }
+ }
+ xhr.open('GET', imgUrl);
+ xhr.responseType = 'blob';
+ xhr.send();
+}
+</script>
+</head>
+<body>
+<img id='star'>
+</body>
+</html>