Commit 1b1036ca authored by jessing's avatar jessing Committed by Commit Bot

[system-web-apps] Allow chrome-untrusted schemes to be embedded in WebUI

pages.

We want to embed an iframe with a URL of scheme 'chrome-untrusted://'.
Currently, embedding chrome-untrusted:// iframes is blocked in multiple
areas: the WebUI navigation throttle in the browser side and in the
renderer side which currently treats chrome-untrusted as a display
isolated scheme. This means it expects only the same scheme can load the
same scheme e.g. chrome-untrusted:// can only load chrome-untrusted://.
Since we want chrome:// to load chrome-untrusted://, it makes sense to
no longer treat chrome-untrusted as a display isolated scheme.

This cl unblocks those 2 areas (though there are more blocks we need to
remove) by deregistering chrome-untrusted as a display
isolated scheme and we allow chrome-untrusted schemes to proceed in the
WebUI navigation throttle.

Bug: 1048951
Change-Id: I9bfce2909f6a25c535866d1b8d57507393429bb0
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2038192
Commit-Queue: Jessica Huang <jessing@google.com>
Reviewed-by: default avatarAlex Moshchuk <alexmos@chromium.org>
Reviewed-by: default avatarGiovanni Ortuño Urquidi <ortuno@chromium.org>
Reviewed-by: default avatarcalamity <calamity@chromium.org>
Cr-Commit-Position: refs/heads/master@{#742982}
parent 8bf3bbcc
......@@ -22,8 +22,10 @@ WebUINavigationThrottle::~WebUINavigationThrottle() {}
NavigationThrottle::ThrottleCheckResult
WebUINavigationThrottle::WillStartRequest() {
// Allow only chrome: scheme documents to be navigated to.
if (navigation_handle()->GetURL().SchemeIs(kChromeUIScheme))
// Allow only chrome: and chrome-untrusted: scheme documents to be navigated
// to.
if (navigation_handle()->GetURL().SchemeIs(kChromeUIScheme) ||
navigation_handle()->GetURL().SchemeIs(kChromeUIUntrustedScheme))
return PROCEED;
return BLOCK_REQUEST;
......
......@@ -202,8 +202,9 @@ IN_PROC_BROWSER_TEST_F(WebUINavigationBrowserTest,
}
// Navigate to a chrome-untrusted URL and verify that the navigation was
// blocked by a renderer-side check.
// blocked. This tests the Frame::BeginNavigation path.
{
TestNavigationObserver observer(shell()->web_contents());
TestUntrustedDataSourceCSP csp;
csp.no_xfo = true;
// Add a DataSource for chrome-untrusted:// that can be iframe'd.
......@@ -211,27 +212,12 @@ IN_PROC_BROWSER_TEST_F(WebUINavigationBrowserTest,
"test-host", csp);
GURL untrusted_url(GetChromeUntrustedUIURL("test-host/title1.html"));
auto console_delegate = std::make_unique<ConsoleObserverDelegate>(
shell()->web_contents(),
"Not allowed to load local resource: " + untrusted_url.spec());
// Save the delegate since we are about to replace it.
auto* web_contents_delegate = shell()->web_contents()->GetDelegate();
shell()->web_contents()->SetDelegate(console_delegate.get());
EXPECT_TRUE(ExecJs(shell(), JsReplace(kAddIframeScript, untrusted_url),
EXECUTE_SCRIPT_DEFAULT_OPTIONS, 1 /* world_id */));
observer.Wait();
console_delegate->Wait();
FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
->GetFrameTree()
->root();
EXPECT_EQ(3U, root->child_count());
RenderFrameHost* child = root->child_at(2)->current_frame_host();
EXPECT_EQ(GURL(), child->GetLastCommittedURL());
// Restore the delegate that we replaced.
shell()->web_contents()->SetDelegate(web_contents_delegate);
EXPECT_EQ(kBlockedURL, child->GetLastCommittedURL());
}
// Verify that an iframe with "about:blank" URL is actually allowed. Not
......@@ -454,8 +440,7 @@ IN_PROC_BROWSER_TEST_F(
EXPECT_EQ("about:blank", child->GetLastCommittedURL());
// Simulate an IPC message to navigate the subframe to a
// chrome-untrusted:// URL. This bypasses the renderer-side check that would
// have stopped the navigation.
// chrome-untrusted:// URL.
TestNavigationObserver observer(shell()->web_contents());
content::PwnMessageHelper::OpenURL(child->GetProcess(), child->GetRoutingID(),
untrusted_url);
......@@ -610,8 +595,51 @@ IN_PROC_BROWSER_TEST_F(WebUINavigationBrowserTest,
}
}
// TODO(jessing): Add a testcase to verify adding chrome-untrusted scheme as a
// frame ancestors in header allows chrome-untrusted to be embedded.
// Verify that a chrome-untrusted:// scheme iframe can be embedded in chrome://
// frame.
IN_PROC_BROWSER_TEST_F(WebUINavigationBrowserTest,
ChromeUntrustedWebFrameInChromeSchemeAllowed) {
// Serve a WebUI with no iframe restrictions.
GURL main_frame_url(
GetWebUIURL("web-ui/"
"title1.html?childsrc=&requestableschemes=chrome-untrusted"));
EXPECT_TRUE(NavigateToURL(shell(), main_frame_url));
FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
->GetFrameTree()
->root();
RenderFrameHost* webui_rfh = root->current_frame_host();
scoped_refptr<SiteInstance> webui_site_instance =
webui_rfh->GetSiteInstance();
EXPECT_EQ(main_frame_url, webui_rfh->GetLastCommittedURL());
EXPECT_TRUE(ChildProcessSecurityPolicyImpl::GetInstance()->HasWebUIBindings(
webui_rfh->GetProcess()->GetID()));
EXPECT_EQ(ChildProcessSecurityPolicyImpl::GetInstance()->GetOriginLock(
root->current_frame_host()->GetProcess()->GetID()),
webui_site_instance->GetSiteURL());
TestUntrustedDataSourceCSP csp;
std::vector<std::string> frame_ancestors({"chrome://web-ui"});
csp.frame_ancestors =
base::make_optional<std::vector<std::string>>(std::move(frame_ancestors));
// Add a DataSource for the chrome-untrusted:// iframe with frame ancestor
// chrome://web-ui.
AddUntrustedDataSource(shell()->web_contents()->GetBrowserContext(),
"test-host", csp);
GURL untrusted_url(GetChromeUntrustedUIURL("test-host/title1.html"));
TestNavigationObserver observer(shell()->web_contents());
// Add the iframe to the chrome://web-ui WebUI and verify it was successfully
// embedded.
EXPECT_TRUE(ExecJs(shell(), JsReplace(kAddIframeScript, untrusted_url),
EXECUTE_SCRIPT_DEFAULT_OPTIONS, 1 /* world_id */));
observer.Wait();
EXPECT_TRUE(observer.last_navigation_succeeded());
EXPECT_EQ(untrusted_url,
root->child_at(0)->current_frame_host()->GetLastCommittedURL());
}
// Verify that a renderer check stops websites from embeding chrome:// iframes.
IN_PROC_BROWSER_TEST_F(WebUINavigationBrowserTest,
......@@ -639,39 +667,6 @@ IN_PROC_BROWSER_TEST_F(WebUINavigationBrowserTest,
EXPECT_EQ(GURL(), child->GetLastCommittedURL());
}
// Verify that a renderer check stops websites from embeding chrome-untrusted://
// iframes.
IN_PROC_BROWSER_TEST_F(
WebUINavigationBrowserTest,
DisallowEmbeddingChromeUntrustedSchemeFromWebFrameRendererCheck) {
GURL main_frame_url(embedded_test_server()->GetURL("/title1.html"));
EXPECT_TRUE(NavigateToURL(shell(), main_frame_url));
FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
->GetFrameTree()
->root();
TestUntrustedDataSourceCSP csp;
csp.no_xfo = true;
AddUntrustedDataSource(shell()->web_contents()->GetBrowserContext(),
"test-iframe-host", csp);
GURL untrusted_url(GetChromeUntrustedUIURL("test-iframe-host/title1.html"));
auto console_delegate = std::make_unique<ConsoleObserverDelegate>(
shell()->web_contents(),
"Not allowed to load local resource: " + untrusted_url.spec());
shell()->web_contents()->SetDelegate(console_delegate.get());
// Add iframe and navigate it to a chrome-untrusted:// URL and verify that the
// navigation was blocked.
EXPECT_TRUE(ExecJs(shell(), JsReplace(kAddIframeScript, untrusted_url),
EXECUTE_SCRIPT_DEFAULT_OPTIONS, 1 /* world_id */));
console_delegate->Wait();
EXPECT_EQ(1U, root->child_count());
RenderFrameHost* child = root->child_at(0)->current_frame_host();
EXPECT_EQ(GURL(), child->GetLastCommittedURL());
}
// Used to test browser-side checks by disabling some renderer-side checks.
class WebUINavigationDisabledWebSecurityBrowserTest
: public WebUINavigationBrowserTest {
......@@ -705,34 +700,6 @@ IN_PROC_BROWSER_TEST_F(WebUINavigationDisabledWebSecurityBrowserTest,
EXPECT_EQ(kBlockedURL, child->GetLastCommittedURL());
}
// Verify that a browser check stops websites from embeding chrome-untrusted://
// iframes. This tests the Frame::BeginNavigation path.
IN_PROC_BROWSER_TEST_F(
WebUINavigationDisabledWebSecurityBrowserTest,
DisallowEmbeddingChromeUntrustedSchemeFromWebFrameBrowserCheck2) {
GURL main_frame_url(embedded_test_server()->GetURL("/title1.html"));
EXPECT_TRUE(NavigateToURL(shell(), main_frame_url));
TestUntrustedDataSourceCSP csp;
csp.no_xfo = true;
AddUntrustedDataSource(shell()->web_contents()->GetBrowserContext(),
"test-iframe-host", csp);
GURL untrusted_url(GetChromeUntrustedUIURL("test-iframe-host/title1.html"));
TestNavigationObserver observer(shell()->web_contents());
EXPECT_TRUE(ExecJs(shell(), JsReplace(kAddIframeScript, untrusted_url),
EXECUTE_SCRIPT_DEFAULT_OPTIONS, 1 /* world_id */));
observer.Wait();
FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
->GetFrameTree()
->root();
EXPECT_EQ(1U, root->child_count());
RenderFrameHost* child = root->child_at(0)->current_frame_host();
EXPECT_EQ(kBlockedURL, child->GetLastCommittedURL());
}
// Verify that a browser check stops websites from navigating to
// chrome:// documents in the main frame. This tests the Frame::BeginNavigation
// path.
......
......@@ -425,4 +425,115 @@ IN_PROC_BROWSER_TEST_F(WebUISecurityTest, WebUIFailedNavigation) {
EXPECT_EQ(0, root->current_frame_host()->GetEnabledBindings());
}
// Verify fetch request to chrome-untrusted:// is blocked.
IN_PROC_BROWSER_TEST_F(WebUISecurityTest,
DisallowFetchRequestToChromeUntrusted) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL web_url(embedded_test_server()->GetURL("/title2.html"));
AddUntrustedDataSource(shell()->web_contents()->GetBrowserContext(),
"test-host");
EXPECT_TRUE(NavigateToURL(shell(), web_url));
EXPECT_EQ(web_url, shell()->web_contents()->GetLastCommittedURL());
const char kFetchRequestScript[] =
"(async () => {"
" try {"
" let response = await fetch($1); "
" }"
" catch (e) {"
" return e.message;"
" }"
" throw 'Fetch should fail';"
"})();";
{
GURL untrusted_url(GetChromeUntrustedUIURL("test-host/script.js"));
auto console_delegate = std::make_unique<ConsoleObserverDelegate>(
shell()->web_contents(),
"Fetch API cannot load " + untrusted_url.spec() +
". URL scheme must be \"http\" or \"https\" for CORS request.");
shell()->web_contents()->SetDelegate(console_delegate.get());
EXPECT_EQ("Failed to fetch",
EvalJs(shell(), JsReplace(kFetchRequestScript, untrusted_url),
EXECUTE_SCRIPT_DEFAULT_OPTIONS, 1 /* world_id */));
console_delegate->Wait();
}
}
// Verify XHR request to chrome-untrusted:// is blocked.
IN_PROC_BROWSER_TEST_F(WebUISecurityTest, DisallowXHRRequestToChromeUntrusted) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL web_url(embedded_test_server()->GetURL("/title2.html"));
AddUntrustedDataSource(shell()->web_contents()->GetBrowserContext(),
"test-host");
EXPECT_TRUE(NavigateToURL(shell(), web_url));
EXPECT_EQ(web_url, shell()->web_contents()->GetLastCommittedURL());
const char kXHRRequest[] =
"new Promise((resolve) => {"
" const xhttp = new XMLHttpRequest();"
" xhttp.open('GET', $1, true);"
" xhttp.onload = () => { "
" resolve('Request should have failed');"
" };"
" xhttp.onerror = () => {"
" resolve('Request failed');"
" };"
" xhttp.send();"
"}); ";
{
GURL untrusted_url(GetChromeUntrustedUIURL("test-host/script.js"));
const std::string host = web_url.GetOrigin().spec();
auto console_delegate = std::make_unique<ConsoleObserverDelegate>(
shell()->web_contents(),
"Access to XMLHttpRequest at '" + untrusted_url.spec() +
"' from origin '" + host.substr(0, host.length() - 1) +
"' has been blocked by CORS policy: Cross origin requests are only "
"supported for protocol schemes: http, data, chrome, https.");
shell()->web_contents()->SetDelegate(console_delegate.get());
EXPECT_EQ("Request failed",
EvalJs(shell(), JsReplace(kXHRRequest, untrusted_url),
EXECUTE_SCRIPT_DEFAULT_OPTIONS, 1 /* world_id */));
console_delegate->Wait();
}
}
// Verify load script from chrome-untrusted:// is blocked.
IN_PROC_BROWSER_TEST_F(WebUISecurityTest,
DisallowResourceRequestToChromeUntrusted) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL web_url(embedded_test_server()->GetURL("/title2.html"));
AddUntrustedDataSource(shell()->web_contents()->GetBrowserContext(),
"test-host");
EXPECT_TRUE(NavigateToURL(shell(), web_url));
EXPECT_EQ(web_url, shell()->web_contents()->GetLastCommittedURL());
const char kLoadResourceScript[] =
"new Promise((resolve) => {"
" const script = document.createElement('script');"
" script.onload = () => {"
" resolve('Script load should have failed');"
" };"
" script.onerror = () => {"
" resolve('Load failed');"
" };"
" script.src = $1;"
" document.body.appendChild(script);"
"});";
// There are no error messages in the console which is why we cannot check for
// them.
{
GURL untrusted_url(GetChromeUntrustedUIURL("test-host/script.js"));
EXPECT_EQ("Load failed",
EvalJs(shell(), JsReplace(kLoadResourceScript, untrusted_url),
EXECUTE_SCRIPT_DEFAULT_OPTIONS, 1 /* world_id */));
}
}
} // namespace content
......@@ -1045,8 +1045,6 @@ void RenderThreadImpl::RegisterSchemes() {
// chrome-untrusted:
WebString chrome_untrusted_scheme(
WebString::FromASCII(kChromeUIUntrustedScheme));
WebSecurityPolicy::RegisterURLSchemeAsDisplayIsolated(
chrome_untrusted_scheme);
WebSecurityPolicy::RegisterURLSchemeAsNotAllowingJavascriptURLs(
chrome_untrusted_scheme);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment