[go: nahoru, domu]

blob: 9395ff13872ee6b7954dd1a55b4beaff6b0cd3ce [file] [log] [blame]
// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/strings/pattern.h"
#include "base/strings/stringprintf.h"
#include "base/values.h"
#include "chrome/browser/extensions/extension_browsertest.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/common/url_constants.h"
#include "chrome/test/base/ui_test_utils.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/web_contents.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "extensions/test/test_extension_dir.h"
#include "net/dns/mock_host_resolver.h"
namespace extensions {
namespace {
// Returns true if |window.scriptExecuted| is true for the given frame.
bool WasFrameWithScriptLoaded(content::RenderFrameHost* render_frame_host) {
if (!render_frame_host) {
return false;
}
return content::EvalJs(render_frame_host, "!!window.scriptExecuted")
.ExtractBool();
}
class ExtensionCSPBypassTest : public ExtensionBrowserTest {
public:
ExtensionCSPBypassTest() {}
ExtensionCSPBypassTest(const ExtensionCSPBypassTest&) = delete;
ExtensionCSPBypassTest& operator=(const ExtensionCSPBypassTest&) = delete;
void SetUpOnMainThread() override {
host_resolver()->AddRule("same-origin.com", "127.0.0.1");
host_resolver()->AddRule("cross-origin.com", "127.0.0.1");
ExtensionBrowserTest::SetUpOnMainThread();
ASSERT_TRUE(embedded_test_server()->Start());
}
protected:
content::WebContents* web_contents() const {
return browser()->tab_strip_model()->GetActiveWebContents();
}
const Extension* AddExtension(bool is_component, bool all_urls_permission) {
TestExtensionDir dir;
std::string unique_name = base::StringPrintf(
"component=%d, all_urls=%d", is_component, all_urls_permission);
auto manifest =
base::Value::Dict()
.Set("name", unique_name)
.Set("version", "1")
.Set("manifest_version", 2)
.Set("web_accessible_resources", base::Value::List().Append("*"));
if (all_urls_permission) {
manifest.Set("permissions", base::Value::List().Append("<all_urls>"));
}
if (is_component) {
// LoadExtensionAsComponent requires the manifest to contain a key.
std::string key;
EXPECT_TRUE(Extension::ProducePEM(unique_name, &key));
manifest.Set("key", key);
}
dir.WriteFile(FILE_PATH_LITERAL("script.js"), "");
dir.WriteManifest(manifest);
const Extension* extension = nullptr;
if (is_component) {
extension = LoadExtensionAsComponent(dir.UnpackedPath());
} else {
extension = LoadExtension(dir.UnpackedPath());
}
CHECK(extension);
temp_dirs_.push_back(std::move(dir));
return extension;
}
bool CanLoadScript(const Extension* extension) {
content::RenderFrameHost* render_frame_host =
web_contents()->GetPrimaryMainFrame();
std::string code = base::StringPrintf(
R"(
function canLoadScript() {
const s = document.createElement('script');
try {
s.src = '%s';
document.body.appendChild(s);
} catch(e) {
// Blocked by TrustedTypes CSP.
return false;
}
// Not blocked by CSP.
return true;
}
canLoadScript();
)",
extension->GetResourceURL("script.js").spec().c_str());
return EvalJs(render_frame_host, code).ExtractBool();
}
content::RenderFrameHost* GetFrameByName(const std::string& name) {
return content::FrameMatchingPredicate(
web_contents()->GetPrimaryPage(),
base::BindRepeating(&content::FrameMatchesName, name));
}
private:
std::vector<TestExtensionDir> temp_dirs_;
};
} // namespace
IN_PROC_BROWSER_TEST_F(ExtensionCSPBypassTest, LoadWebAccessibleScript) {
const Extension* component_ext_with_permission = AddExtension(true, true);
const Extension* component_ext_without_permission = AddExtension(true, false);
const Extension* ext_with_permission = AddExtension(false, true);
const Extension* ext_without_permission = AddExtension(false, false);
// chrome-extension:-URLs can always bypass CSP in normal pages.
GURL non_webui_url(embedded_test_server()->GetURL("/empty.html"));
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), non_webui_url));
EXPECT_TRUE(CanLoadScript(component_ext_with_permission));
EXPECT_TRUE(CanLoadScript(component_ext_without_permission));
EXPECT_TRUE(CanLoadScript(ext_with_permission));
EXPECT_TRUE(CanLoadScript(ext_without_permission));
// chrome-extension:-URLs can never bypass CSP in WebUI.
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), GURL(chrome::kChromeUIExtensionsURL)));
EXPECT_FALSE(CanLoadScript(component_ext_with_permission));
EXPECT_FALSE(CanLoadScript(component_ext_without_permission));
EXPECT_FALSE(CanLoadScript(ext_with_permission));
EXPECT_FALSE(CanLoadScript(ext_without_permission));
}
// Tests that an extension can add a cross-origin iframe to a page
// whose CSP disallows iframes. Regression test for https://crbug.com/408932.
IN_PROC_BROWSER_TEST_F(ExtensionCSPBypassTest, InjectIframe) {
// Install an extension that can add a cross-origin iframe to a document.
const Extension* extension =
LoadExtension(test_data_dir_.AppendASCII("csp/add_iframe_extension"));
ASSERT_TRUE(extension);
// Navigate to a page that has CSP with 'frame-src: none' to block any
// iframes. Use the "same-origin.com" hostname as the test will add iframes to
// "cross-origin.com" to make clear they are cross-origin.
GURL test_url = embedded_test_server()->GetURL(
"same-origin.com", "/extensions/csp/page_with_frame_csp.html");
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), test_url));
// First, verify that adding an iframe to the page from the main world will
// fail. Add the frame. Its onload event fires even if it's blocked
// (see https://crbug.com/365457), and reports back.
EXPECT_EQ(true, content::EvalJs(web_contents(), "addIframe();"));
// Use WasFrameWithScriptLoaded() to check whether the target frame really
// loaded.
content::RenderFrameHost* frame = GetFrameByName("added-by-page");
ASSERT_TRUE(frame);
EXPECT_FALSE(WasFrameWithScriptLoaded(frame));
// Second, verify that adding an iframe to the page from the extension will
// succeed. Click a button whose event handler runs in the extension's world
// which bypasses CSP, and adds the iframe.
content::DOMMessageQueue message_queue;
EXPECT_TRUE(
content::ExecJs(browser()->tab_strip_model()->GetActiveWebContents(),
"document.querySelector('#addIframeButton').click();"));
std::string ack;
EXPECT_TRUE(message_queue.WaitForMessage(&ack));
EXPECT_EQ("true", ack);
frame = GetFrameByName("added-by-extension");
ASSERT_TRUE(frame);
EXPECT_TRUE(WasFrameWithScriptLoaded(frame));
}
// CSP:frame-ancestor is not bypassed by extensions.
IN_PROC_BROWSER_TEST_F(ExtensionCSPBypassTest, FrameAncestors) {
std::string manifest = R"(
{
"name": "CSP frame-ancestors",
"manifest_version": 2,
"version": "0.1",
"browser_action": {
"default_popup": "popup.html"
}
}
)";
std::string popup = R"(
<!doctype html>
<html>
<iframe src = "$1"></iframe>
</html>
)";
GURL iframe_url = embedded_test_server()->GetURL(
"/extensions/csp/frame-ancestors-none.html");
popup = base::ReplaceStringPlaceholders(popup, {iframe_url.spec()}, nullptr);
TestExtensionDir test_dir;
test_dir.WriteManifest(manifest);
test_dir.WriteFile(FILE_PATH_LITERAL("popup.html"), popup);
const Extension* extension = LoadExtension(test_dir.UnpackedPath());
ASSERT_TRUE(extension);
content::WebContentsConsoleObserver console_observer(web_contents());
console_observer.SetPattern(
"Refused to frame * because an ancestor violates *");
GURL popup_url = extension->GetResourceURL("popup.html");
ASSERT_TRUE(content::NavigateToURL(web_contents(), popup_url));
// The iframe must be blocked because of CSP.
ASSERT_TRUE(console_observer.Wait());
content::RenderFrameHost* main_frame = web_contents()->GetPrimaryMainFrame();
content::RenderFrameHost* child_frame = ChildFrameAt(main_frame, 0);
EXPECT_EQ(popup_url, main_frame->GetLastCommittedURL());
EXPECT_EQ(iframe_url, child_frame->GetLastCommittedURL());
EXPECT_TRUE(child_frame->GetLastCommittedOrigin().opaque());
}
} // namespace extensions