Commit ec3242c5 authored by Daniel Vogelheim's avatar Daniel Vogelheim Committed by Commit Bot

[Trusted Types] Handle navigation to javascript:-URLs as a TT violation.

This adds a second CSP-triggered check when navigating to
javascript:-URLs. Newer Trusted Type spec versions treat this
similar to an assignment to a <script> tag and run the TT
default policy on it.

The implementation is a bit more complicated, because this is
a TT check that does not normally occur during JS execution.

This updates the TT implementation to the latest spec version.

R=mkwst

Bug: 1002555
Change-Id: I4b815c74c5b9e3e4a11c7cc35c8668d32d2ae7e5
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1865313
Commit-Queue: Daniel Vogelheim <vogelheim@chromium.org>
Reviewed-by: default avatarKentaro Hara <haraken@chromium.org>
Reviewed-by: default avatarMike West <mkwst@chromium.org>
Cr-Commit-Position: refs/heads/master@{#712159}
parent b930ff73
...@@ -244,18 +244,26 @@ void ScriptController::ExecuteJavaScriptURL( ...@@ -244,18 +244,26 @@ void ScriptController::ExecuteJavaScriptURL(
bool should_bypass_main_world_content_security_policy = bool should_bypass_main_world_content_security_policy =
check_main_world_csp == kDoNotCheckContentSecurityPolicy || check_main_world_csp == kDoNotCheckContentSecurityPolicy ||
ContentSecurityPolicy::ShouldBypassMainWorld(GetFrame()->GetDocument()); ContentSecurityPolicy::ShouldBypassMainWorld(GetFrame()->GetDocument());
if (!GetFrame()->GetPage() || if (!GetFrame()->GetPage())
(!should_bypass_main_world_content_security_policy &&
!GetFrame()->GetDocument()->GetContentSecurityPolicy()->AllowInline(
ContentSecurityPolicy::InlineType::kNavigation, nullptr,
script_source, String() /* nonce */,
GetFrame()->GetDocument()->Url(), EventHandlerPosition().line_))) {
return; return;
}
bool had_navigation_before = GetFrame()->Loader().HasProvisionalNavigation(); if (!should_bypass_main_world_content_security_policy &&
!GetFrame()->GetDocument()->GetContentSecurityPolicy()->AllowInline(
ContentSecurityPolicy::InlineType::kNavigation, nullptr,
script_source, String() /* nonce */, GetFrame()->GetDocument()->Url(),
EventHandlerPosition().line_)) {
return;
}
script_source = script_source.Substring(kJavascriptSchemeLength); script_source = script_source.Substring(kJavascriptSchemeLength);
if (!should_bypass_main_world_content_security_policy) {
script_source = TrustedTypesCheckForJavascriptURLinNavigation(
script_source, GetFrame()->GetDocument());
if (script_source.IsEmpty())
return;
}
bool had_navigation_before = GetFrame()->Loader().HasProvisionalNavigation();
v8::HandleScope handle_scope(GetIsolate()); v8::HandleScope handle_scope(GetIsolate());
......
...@@ -11,10 +11,12 @@ ...@@ -11,10 +11,12 @@
#include "third_party/blink/renderer/bindings/core/v8/string_or_trusted_script.h" #include "third_party/blink/renderer/bindings/core/v8/string_or_trusted_script.h"
#include "third_party/blink/renderer/bindings/core/v8/string_or_trusted_script_url.h" #include "third_party/blink/renderer/bindings/core/v8/string_or_trusted_script_url.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h" #include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h"
#include "third_party/blink/renderer/bindings/core/v8/window_proxy_manager.h"
#include "third_party/blink/renderer/core/dom/document.h" #include "third_party/blink/renderer/core/dom/document.h"
#include "third_party/blink/renderer/core/dom/node.h" #include "third_party/blink/renderer/core/dom/node.h"
#include "third_party/blink/renderer/core/dom/text.h" #include "third_party/blink/renderer/core/dom/text.h"
#include "third_party/blink/renderer/core/frame/csp/content_security_policy.h" #include "third_party/blink/renderer/core/frame/csp/content_security_policy.h"
#include "third_party/blink/renderer/core/frame/local_frame.h"
#include "third_party/blink/renderer/core/trustedtypes/trusted_html.h" #include "third_party/blink/renderer/core/trustedtypes/trusted_html.h"
#include "third_party/blink/renderer/core/trustedtypes/trusted_script.h" #include "third_party/blink/renderer/core/trustedtypes/trusted_script.h"
#include "third_party/blink/renderer/core/trustedtypes/trusted_script_url.h" #include "third_party/blink/renderer/core/trustedtypes/trusted_script_url.h"
...@@ -41,6 +43,8 @@ enum TrustedTypeViolationKind { ...@@ -41,6 +43,8 @@ enum TrustedTypeViolationKind {
kTrustedScriptURLAssignmentAndDefaultPolicyFailed, kTrustedScriptURLAssignmentAndDefaultPolicyFailed,
kTextNodeScriptAssignment, kTextNodeScriptAssignment,
kTextNodeScriptAssignmentAndDefaultPolicyFailed, kTextNodeScriptAssignmentAndDefaultPolicyFailed,
kNavigateToJavascriptURL,
kNavigateToJavascriptURLAndDefaultPolicyFailed,
}; };
const char* GetMessage(TrustedTypeViolationKind kind) { const char* GetMessage(TrustedTypeViolationKind kind) {
...@@ -71,6 +75,15 @@ const char* GetMessage(TrustedTypeViolationKind kind) { ...@@ -71,6 +75,15 @@ const char* GetMessage(TrustedTypeViolationKind kind) {
"Inserting a text node into a script element is equivalent to " "Inserting a text node into a script element is equivalent to "
"a 'TrustedScript' assignment and the default policy failed to " "a 'TrustedScript' assignment and the default policy failed to "
"execute."; "execute.";
case kNavigateToJavascriptURL:
return "This document requires 'TrustedScript' assignment. "
"Navigating to a javascript:-URL is equivalent to a "
"'TrustedScript' assignment.";
case kNavigateToJavascriptURLAndDefaultPolicyFailed:
return "This document requires 'TrustedScript' assignment. "
"Navigating to a javascript:-URL is equivalent to a "
"'TrustedScript' assignment and the default policy failed to"
"execute.";
} }
NOTREACHED(); NOTREACHED();
return ""; return "";
...@@ -287,7 +300,6 @@ String GetStringFromTrustedScript( ...@@ -287,7 +300,6 @@ String GetStringFromTrustedScript(
// string_or_trusted_script.IsNull(), unlike the various similar methods in // string_or_trusted_script.IsNull(), unlike the various similar methods in
// this file. // this file.
if (string_or_trusted_script.IsTrustedScript()) { if (string_or_trusted_script.IsTrustedScript()) {
return string_or_trusted_script.GetAsTrustedScript()->toString(); return string_or_trusted_script.GetAsTrustedScript()->toString();
} }
...@@ -414,4 +426,64 @@ Node* TrustedTypesCheckForHTMLScriptElement(Node* child, ...@@ -414,4 +426,64 @@ Node* TrustedTypesCheckForHTMLScriptElement(Node* child,
return Text::Create(*doc, result->toString()); return Text::Create(*doc, result->toString());
} }
String TrustedTypesCheckForJavascriptURLinNavigation(
const String& javascript_url,
Document* doc) {
bool require_trusted_type = RequireTrustedTypesCheck(doc);
if (!require_trusted_type)
return javascript_url;
// Set up JS context & friends.
//
// All other functions in here are expected to be called during JS execution,
// where naturally everything is propertly set up for more JS execution.
// This one is called during navigation, and thus needs to do a bit more
// work. We need two JavaScript-ish things:
// - TrustedTypeFail expects an ExceptionState, which it will use to throw
// an exception. In our case, we will always clear the exception (as there
// is no user script to pass it to), and we only use this as a signalling
// mechanism.
// - If the default policy applies, we need to execute the JS callback.
// Unlike the various ScriptController::Execute* and ..::Eval* methods,
// we are not executing a source String, but an already compiled callback
// function.
v8::HandleScope handle_scope(doc->GetIsolate());
ScriptState::Scope script_state_scope(
ScriptState::From(static_cast<LocalWindowProxyManager*>(
doc->GetFrame()->GetWindowProxyManager())
->MainWorldProxy()
->ContextIfInitialized()));
ExceptionState exception_state(
doc->GetIsolate(), ExceptionState::kUnknownContext, "Location", "href");
TrustedTypePolicy* default_policy = GetDefaultPolicy(doc);
if (!default_policy) {
if (TrustedTypeFail(kNavigateToJavascriptURL, doc, exception_state,
javascript_url)) {
exception_state.ClearException();
return String();
}
return javascript_url;
}
TrustedScript* result = default_policy->CreateScript(
doc->GetIsolate(), javascript_url, exception_state);
if (exception_state.HadException()) {
exception_state.ClearException();
return String();
}
if (result->toString().IsNull()) {
if (TrustedTypeFail(kNavigateToJavascriptURLAndDefaultPolicyFailed, doc,
exception_state, javascript_url)) {
exception_state.ClearException();
return String();
}
return javascript_url;
}
// TODO(vogelheim): Figure out whether we need to check whether this string
// parses as a URL, and whether we need to do this here.
return result->toString();
}
} // namespace blink } // namespace blink
...@@ -70,6 +70,8 @@ Node* TrustedTypesCheckForHTMLScriptElement(Node* child, ...@@ -70,6 +70,8 @@ Node* TrustedTypesCheckForHTMLScriptElement(Node* child,
Document*, Document*,
ExceptionState&); ExceptionState&);
String TrustedTypesCheckForJavascriptURLinNavigation(const String&, Document*);
// Determine whether a Trusted Types check is needed in this execution context. // Determine whether a Trusted Types check is needed in this execution context.
// //
// Note: All methods above handle this internally and will return success if a // Note: All methods above handle this internally and will return success if a
......
<!DOCTYPE html>
<head>
</head>
<body>
<p>Support page for trusted-types-navigation-report-only.*.html tests.</p>
<a id="anchor" href="#">link</a>
<script>
if (location.search == "?defaultpolicy") {
trustedTypes.createPolicy("default", {
createScript: s => s.replace("continue", "defaultpolicywashere"),
});
}
function bounceEventToOpener(e) {
const msg = {};
for (const field of ["effectiveDirective", "sample", "type"]) {
msg[field] = e[field];
}
msg["uri"] = location.href;
window.opener.postMessage(msg, "*");
}
// If a navigation is blocked by Trusted Types, we expect this window to
// throw a SecurityPolicyViolationEvent. If it's not blocked, we expect the
// loaded frame to through DOMContentLoaded. In either case there should be
// _some_ event that we can expect.
document.addEventListener("DOMContentLoaded", bounceEventToOpener);
document.addEventListener("securitypolicyviolation", bounceEventToOpener);
// Navigate to the non-report-only version of the test. That has the same
// event listening setup as this, but is a different target URI.
const target_script = `location.href='${location.href.replace("-report-only", "") + "#continue"}';`;
const target = `javascript:"<script>${target_script}</scri${""}pt>"`;
// Navigate the anchor, but only after the content is loaded (so that we
// won't disturb delivery of that event to the opener.
const anchor = document.getElementById("anchor");
anchor.href = target;
if (!location.hash) {
document.addEventListener("DOMContentLoaded", _ => anchor.click());
}
</script>
</body>
<!DOCTYPE html>
<head>
</head>
<body>
<p>Support page for trusted-types-navigation.*.html tests.</p>
<a id="anchor" href="#">link</a>
<script>
if (location.search == "?defaultpolicy") {
trustedTypes.createPolicy("default", {
createScript: s => s.replace("continue", "defaultpolicywashere"),
});
}
function bounceEventToOpener(e) {
const msg = {};
for (const field of ["effectiveDirective", "sample", "type"]) {
msg[field] = e[field];
}
msg["uri"] = location.href;
window.opener.postMessage(msg, "*");
}
// If a navigation is blocked by Trusted Types, we expect this window to
// throw a SecurityPolicyViolationEvent. If it's not blocked, we expect the
// loaded frame to through DOMContentLoaded. In either case there should be
// _some_ event that we can expect.
document.addEventListener("DOMContentLoaded", bounceEventToOpener);
document.addEventListener("securitypolicyviolation", bounceEventToOpener);
// We'll use a javascript:-url to navigate to ourselves, so that we can
// re-use the messageing mechanisms above. In order to not end up in a loop,
// we'll only click if we don't find fragment in the current URL.
const target_script = `location.href='${location.href}&continue';`;
const target = `javascript:"<script>${target_script}</scri${""}pt>"`;
const anchor = document.getElementById("anchor");
anchor.href = target;
if (!location.hash)
document.addEventListener("DOMContentLoaded", _ => anchor.click());
</script>
</body>
<!DOCTYPE html>
<head>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
</head>
<body>
<script>
function expectMessage(filter) {
return new Promise(resolve => {
window.addEventListener("message", e => { if (filter(e)) resolve(); });
});
}
function expectViolationAsMessage(sample) {
const filter = e => (e.data.effectiveDirective == "trusted-types" &&
(!sample || e.data.sample.startsWith(sample)));
return new expectMessage(filter);
}
function expectLoadedAsMessage(uri) {
const filter = e => (e.data.type == "DOMContentLoaded" &&
(!uri || e.data.uri.endsWith(uri)));
return new expectMessage(filter);
}
function openWindow(test, uri) {
const win = window.open(uri);
test.add_cleanup(_ => win.close());
}
promise_test(t => {
openWindow(t, "support/navigation-support.html");
return Promise.all([
expectLoadedAsMessage("navigation-support.html"),
expectViolationAsMessage("Location.href"),
]);
}, "Navigate a window with javascript:-urls in enforcing mode.");
promise_test(t => {
openWindow(t, "support/navigation-support.html?defaultpolicy");
return Promise.all([
expectLoadedAsMessage("navigation-support.html?defaultpolicy"),
expectLoadedAsMessage("navigation-support.html?defaultpolicy&defaultpolicywashere"),
]);
}, "Navigate a window with javascript:-urls w/ default policy in enforcing mode.");
promise_test(t => {
const page = "navigation-report-only-support.html"
openWindow(t, `support/${page}`);
return Promise.all([
expectLoadedAsMessage(page),
expectLoadedAsMessage("navigation-support.html#continue"),
]);
}, "Navigate a window with javascript:-urls in report-only mode.");
promise_test(t => {
const page = "navigation-report-only-support.html?defaultpolicy";
openWindow(t, `support/${page}`);
return Promise.all([
expectLoadedAsMessage(page),
expectLoadedAsMessage("navigation-support.html?defaultpolicy#defaultpolicywashere"),
]);
}, "Navigate a window with javascript:-urls w/ default policy in report-only mode.");
</script>
</body>
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