blob: 0a4dddc06d126591b2f2cd4b2d5f67b96bd416e2 [file] [log] [blame]
Rob Wu956801d12018-01-10 17:06:311// Copyright (c) 2018 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
Matt Falkenhagen19841c392019-03-14 11:14:425#include "base/strings/pattern.h"
Rob Wu956801d12018-01-10 17:06:316#include "base/strings/stringprintf.h"
7#include "chrome/browser/extensions/extension_browsertest.h"
Rob Wu956801d12018-01-10 17:06:318#include "chrome/browser/ui/tabs/tab_strip_model.h"
9#include "chrome/common/url_constants.h"
10#include "chrome/test/base/ui_test_utils.h"
11#include "content/public/browser/render_frame_host.h"
12#include "content/public/browser/web_contents.h"
13#include "content/public/test/browser_test_utils.h"
14#include "extensions/common/value_builder.h"
Devlin Cronin4f455a22018-01-25 01:36:4515#include "extensions/test/test_extension_dir.h"
Matt Falkenhagen19841c392019-03-14 11:14:4216#include "net/dns/mock_host_resolver.h"
Rob Wu956801d12018-01-10 17:06:3117
18namespace extensions {
19
20namespace {
21
Matt Falkenhagen19841c392019-03-14 11:14:4222// Returns true if |window.scriptExecuted| is true for the given frame.
23bool WasFrameWithScriptLoaded(content::RenderFrameHost* rfh) {
24 if (!rfh)
25 return false;
26 bool loaded = false;
27 EXPECT_TRUE(content::ExecuteScriptAndExtractBool(
28 rfh, "domAutomationController.send(!!window.scriptExecuted)", &loaded));
29 return loaded;
30}
31
Rob Wu956801d12018-01-10 17:06:3132class ExtensionCSPBypassTest : public ExtensionBrowserTest {
33 public:
34 ExtensionCSPBypassTest() {}
35
36 void SetUpOnMainThread() override {
Matt Falkenhagen19841c392019-03-14 11:14:4237 host_resolver()->AddRule("same-origin.com", "127.0.0.1");
38 host_resolver()->AddRule("cross-origin.com", "127.0.0.1");
Rob Wu956801d12018-01-10 17:06:3139 ExtensionBrowserTest::SetUpOnMainThread();
40 ASSERT_TRUE(embedded_test_server()->Start());
41 }
42
43 protected:
Matt Falkenhagen19841c392019-03-14 11:14:4244 content::WebContents* web_contents() const {
45 return browser()->tab_strip_model()->GetActiveWebContents();
46 }
47
Rob Wu956801d12018-01-10 17:06:3148 const Extension* AddExtension(bool is_component, bool all_urls_permission) {
49 auto dir = std::make_unique<TestExtensionDir>();
50
51 std::string unique_name = base::StringPrintf(
52 "component=%d, all_urls=%d", is_component, all_urls_permission);
53 DictionaryBuilder manifest;
54 manifest.Set("name", unique_name)
55 .Set("version", "1")
56 .Set("manifest_version", 2)
57 .Set("web_accessible_resources", ListBuilder().Append("*").Build());
58
59 if (all_urls_permission) {
60 manifest.Set("permissions", ListBuilder().Append("<all_urls>").Build());
61 }
62 if (is_component) {
63 // LoadExtensionAsComponent requires the manifest to contain a key.
64 std::string key;
65 EXPECT_TRUE(Extension::ProducePEM(unique_name, &key));
66 manifest.Set("key", key);
67 }
68
69 dir->WriteFile(FILE_PATH_LITERAL("script.js"), "");
70 dir->WriteManifest(manifest.ToJSON());
71
72 const Extension* extension = nullptr;
73 if (is_component) {
74 extension = LoadExtensionAsComponent(dir->UnpackedPath());
75 } else {
76 extension = LoadExtension(dir->UnpackedPath());
77 }
78 CHECK(extension);
79 temp_dirs_.push_back(std::move(dir));
80 return extension;
81 }
82
83 bool CanLoadScript(const Extension* extension) {
Matt Falkenhagen19841c392019-03-14 11:14:4284 content::RenderFrameHost* rfh = web_contents()->GetMainFrame();
Rob Wu956801d12018-01-10 17:06:3185 std::string code = base::StringPrintf(
86 R"(
87 var s = document.createElement('script');
88 s.src = '%s';
89 s.onload = function() {
90 // Not blocked by CSP.
91 window.domAutomationController.send(true);
92 };
93 s.onerror = function() {
94 // Blocked by CSP.
95 window.domAutomationController.send(false);
96 };
97 document.body.appendChild(s);)",
98 extension->GetResourceURL("script.js").spec().c_str());
99 bool script_loaded = false;
100 EXPECT_TRUE(ExecuteScriptAndExtractBool(rfh, code, &script_loaded));
101 return script_loaded;
102 }
103
Matt Falkenhagen19841c392019-03-14 11:14:42104 content::RenderFrameHost* GetFrameByName(const std::string& name) {
105 return content::FrameMatchingPredicate(
106 web_contents(), base::BindRepeating(&content::FrameMatchesName, name));
107 }
108
Rob Wu956801d12018-01-10 17:06:31109 private:
110 std::vector<std::unique_ptr<TestExtensionDir>> temp_dirs_;
111
112 DISALLOW_COPY_AND_ASSIGN(ExtensionCSPBypassTest);
113};
114
115} // namespace
116
117IN_PROC_BROWSER_TEST_F(ExtensionCSPBypassTest, LoadWebAccessibleScript) {
118 const Extension* component_ext_with_permission = AddExtension(true, true);
119 const Extension* component_ext_without_permission = AddExtension(true, false);
120 const Extension* ext_with_permission = AddExtension(false, true);
121 const Extension* ext_without_permission = AddExtension(false, false);
122
123 // chrome-extension:-URLs can always bypass CSP in normal pages.
124 GURL non_webui_url(embedded_test_server()->GetURL("/empty.html"));
125 ui_test_utils::NavigateToURL(browser(), non_webui_url);
126
127 EXPECT_TRUE(CanLoadScript(component_ext_with_permission));
128 EXPECT_TRUE(CanLoadScript(component_ext_without_permission));
129 EXPECT_TRUE(CanLoadScript(ext_with_permission));
130 EXPECT_TRUE(CanLoadScript(ext_without_permission));
131
132 // chrome-extension:-URLs can never bypass CSP in WebUI.
133 ui_test_utils::NavigateToURL(browser(), GURL(chrome::kChromeUIVersionURL));
134
135 EXPECT_FALSE(CanLoadScript(component_ext_with_permission));
136 EXPECT_FALSE(CanLoadScript(component_ext_without_permission));
137 EXPECT_FALSE(CanLoadScript(ext_with_permission));
138 EXPECT_FALSE(CanLoadScript(ext_without_permission));
139}
140
Matt Falkenhagen19841c392019-03-14 11:14:42141// Tests that an extension can add a cross-origin iframe to a page
142// whose CSP disallows iframes. Regression test for https://crbug.com/408932.
143IN_PROC_BROWSER_TEST_F(ExtensionCSPBypassTest, InjectIframe) {
144 // Install an extension that can add a cross-origin iframe to a document.
145 const Extension* extension =
146 LoadExtension(test_data_dir_.AppendASCII("csp/add_iframe_extension"));
147 ASSERT_TRUE(extension);
148
149 // Navigate to a page that has CSP with 'frame-src: none' to block any
150 // iframes. Use the "same-origin.com" hostname as the test will add iframes to
151 // "cross-origin.com" to make clear they are cross-origin.
152 GURL test_url = embedded_test_server()->GetURL(
153 "same-origin.com", "/extensions/csp/page_with_frame_csp.html");
154 ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), test_url));
155
156 // First, verify that adding an iframe to the page from the main world will
157 // fail. Add the frame. Its onload event fires even if it's blocked
158 // (see https://crbug.com/365457), and reports back.
159 bool result = false;
160 ASSERT_TRUE(content::ExecuteScriptAndExtractBool(web_contents(),
161 "addIframe();", &result));
162 EXPECT_TRUE(result);
163
164 // Use WasFrameWithScriptLoaded() to check whether the target frame really
165 // loaded.
166 content::RenderFrameHost* frame = GetFrameByName("added-by-page");
167 ASSERT_TRUE(frame);
168 EXPECT_FALSE(WasFrameWithScriptLoaded(frame));
169
170 // Second, verify that adding an iframe to the page from the extension will
171 // succeed. Click a button whose event handler runs in the extension's world
172 // which bypasses CSP, and adds the iframe.
173 EXPECT_TRUE(content::ExecuteScriptAndExtractBool(
174 web_contents(), "document.querySelector('#addIframeButton').click();",
175 &result));
176 frame = GetFrameByName("added-by-extension");
177 ASSERT_TRUE(frame);
178 EXPECT_TRUE(WasFrameWithScriptLoaded(frame));
179}
180
Rob Wu956801d12018-01-10 17:06:31181} // namespace extensions