Restrict [un]registerProtocolHandler to secure contexts
The registerProtocolHandler() and unregisterProtocolHandler() functions
should only be permitted for secure contexts. When called on non-secure
contexts, a SecurityError should be thrown.
Bug: 882284
Change-Id: Iacf3d31f80f5118e9e9aacad2c99a0111d6e7cc5
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1892213
Reviewed-by: Gyuyoung Kim <gyuyoung@igalia.com>
Reviewed-by: Tarun Bansal <tbansal@chromium.org>
Reviewed-by: Kentaro Hara <haraken@chromium.org>
Reviewed-by: Mike West <mkwst@chromium.org>
Commit-Queue: Eric Lawrence [MSFT] <ericlaw@microsoft.com>
Cr-Commit-Position: refs/heads/master@{#717501}
diff --git a/chrome/browser/prerender/prerender_browsertest.cc b/chrome/browser/prerender/prerender_browsertest.cc
index 4cae576..dcd6c324 100644
--- a/chrome/browser/prerender/prerender_browsertest.cc
+++ b/chrome/browser/prerender/prerender_browsertest.cc
@@ -1290,6 +1290,7 @@
// Checks that registering a protocol handler causes cancellation.
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, PrerenderRegisterProtocolHandler) {
+ UseHttpsSrcServer();
PrerenderTestURL("/prerender/prerender_register_protocol_handler.html",
FINAL_STATUS_REGISTER_PROTOCOL_HANDLER, 0);
}
diff --git a/third_party/blink/renderer/modules/navigatorcontentutils/navigator_content_utils.cc b/third_party/blink/renderer/modules/navigatorcontentutils/navigator_content_utils.cc
index 737cb4d..3be6ca96 100644
--- a/third_party/blink/renderer/modules/navigatorcontentutils/navigator_content_utils.cc
+++ b/third_party/blink/renderer/modules/navigatorcontentutils/navigator_content_utils.cc
@@ -80,13 +80,14 @@
return false;
}
- // Although not enforced in the spec the spec gives freedom to do additional
- // security checks. Bugs have arisen from allowing non-http/https URLs, e.g.
+ // Although not required by the spec, the spec allows additional security
+ // checks. Bugs have arisen from allowing non-http/https URLs, e.g.
// https://crbug.com/971917 and it doesn't make a lot of sense to support
- // them. We also need to allow extensions to continue using the API.
+ // them. We do need to allow extensions to continue using the API.
if (!kurl.ProtocolIsInHTTPFamily() && !kurl.ProtocolIs("chrome-extension")) {
exception_state.ThrowSecurityError(
- "The scheme of the url provided must be the 'http' or 'https'.");
+ "The scheme of the url provided must be 'https' or "
+ "'chrome-extension'.");
return false;
}
@@ -104,18 +105,19 @@
static bool VerifyCustomHandlerScheme(const String& scheme,
ExceptionState& exception_state) {
if (!IsValidProtocol(scheme)) {
- exception_state.ThrowSecurityError("The scheme '" + scheme +
- "' is not valid protocol");
+ exception_state.ThrowSecurityError(
+ "The scheme name '" + scheme +
+ "' is not allowed by URI syntax (RFC3986).");
return false;
}
if (scheme.StartsWith("web+")) {
// The specification requires that the length of scheme is at least five
- // characteres (including 'web+' prefix).
+ // characters (including 'web+' prefix).
if (scheme.length() >= 5)
return true;
- exception_state.ThrowSecurityError("The scheme '" + scheme +
+ exception_state.ThrowSecurityError("The scheme name '" + scheme +
"' is less than five characters long.");
return false;
}
@@ -125,8 +127,8 @@
exception_state.ThrowSecurityError(
"The scheme '" + scheme +
- "' doesn't belong to the scheme whitelist. "
- "Please prefix non-whitelisted schemes "
+ "' doesn't belong to the scheme allowlist. "
+ "Please prefix non-allowlisted schemes "
"with the string 'web+'.");
return false;
}
@@ -154,14 +156,15 @@
LocalFrame* frame = navigator.GetFrame();
if (!frame)
return;
-
Document* document = frame->GetDocument();
DCHECK(document);
- if (!VerifyCustomHandlerURL(*document, url, exception_state))
+ // Per the HTML specification, exceptions for arguments must be surfaced in
+ // the order of the arguments.
+ if (!VerifyCustomHandlerScheme(scheme, exception_state))
return;
- if (!VerifyCustomHandlerScheme(scheme, exception_state))
+ if (!VerifyCustomHandlerURL(*document, url, exception_state))
return;
// Count usage; perhaps we can forbid this from cross-origin subframes as
@@ -170,7 +173,8 @@
*document, frame->IsCrossOriginSubframe()
? WebFeature::kRegisterProtocolHandlerCrossOriginSubframe
: WebFeature::kRegisterProtocolHandlerSameOriginAsTop);
- // Count usage; perhaps we can lock this to secure contexts.
+ // Count usage. Context should now always be secure due to the same-origin
+ // check and the requirement that the calling context be secure.
UseCounter::Count(*document,
document->IsSecureContext()
? WebFeature::kRegisterProtocolHandlerSecureOrigin
@@ -192,10 +196,10 @@
Document* document = frame->GetDocument();
DCHECK(document);
- if (!VerifyCustomHandlerURL(*document, url, exception_state))
+ if (!VerifyCustomHandlerScheme(scheme, exception_state))
return;
- if (!VerifyCustomHandlerScheme(scheme, exception_state))
+ if (!VerifyCustomHandlerURL(*document, url, exception_state))
return;
NavigatorContentUtils::From(navigator, *frame)
diff --git a/third_party/blink/renderer/modules/navigatorcontentutils/navigator_content_utils.idl b/third_party/blink/renderer/modules/navigatorcontentutils/navigator_content_utils.idl
index d36a5f4b..8c570f7 100644
--- a/third_party/blink/renderer/modules/navigatorcontentutils/navigator_content_utils.idl
+++ b/third_party/blink/renderer/modules/navigatorcontentutils/navigator_content_utils.idl
@@ -23,6 +23,6 @@
[
ImplementedAs=NavigatorContentUtils
] partial interface Navigator {
- [RuntimeEnabled=NavigatorContentUtils, RaisesException] void registerProtocolHandler(DOMString scheme, USVString url, DOMString title);
- [RuntimeEnabled=NavigatorContentUtils, RaisesException] void unregisterProtocolHandler(DOMString scheme, USVString url);
+ [RuntimeEnabled=NavigatorContentUtils, RaisesException, SecureContext] void registerProtocolHandler(DOMString scheme, USVString url, DOMString title);
+ [RuntimeEnabled=NavigatorContentUtils, RaisesException, SecureContext] void unregisterProtocolHandler(DOMString scheme, USVString url);
};
diff --git a/third_party/blink/web_tests/external/wpt/html/dom/usvstring-reflection.https.html b/third_party/blink/web_tests/external/wpt/html/dom/usvstring-reflection.https.html
index b9cafd1..162d58c 100644
--- a/third_party/blink/web_tests/external/wpt/html/dom/usvstring-reflection.https.html
+++ b/third_party/blink/web_tests/external/wpt/html/dom/usvstring-reflection.https.html
@@ -107,12 +107,12 @@
test(() => {
// This shouldn't throw an exception.
window.navigator.registerProtocolHandler('web+myprotocol', "custom-scheme\uD800/url=%s", "title");
-}, "RegisterPtotocolHandler URL: unpaired surrogate codepoint should not make any exceptions.")
+}, "RegisterProtocolHandler URL: unpaired surrogate codepoint should not make any exceptions.")
test(() => {
// This shouldn't throw an exception.
window.navigator.unregisterProtocolHandler('web+myprotocol', "custom-scheme\uD800/url=%s");
-}, "UnregisterPtotocolHandler URL: unpaired surrogate codepoint should not make any exceptions.")
+}, "UnregisterProtocolHandler URL: unpaired surrogate codepoint should not make any exceptions.")
test(() => {
var w = window.open("about:blank#\uD800");
diff --git a/third_party/blink/web_tests/external/wpt/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol.https.html b/third_party/blink/web_tests/external/wpt/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol.https.html
index 006827f1..dcf1007 100644
--- a/third_party/blink/web_tests/external/wpt/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol.https.html
+++ b/third_party/blink/web_tests/external/wpt/html/webappapis/system-state-and-capabilities/the-navigator-object/protocol.https.html
@@ -21,7 +21,7 @@
}, 'the unregisterProtocolHandler method should exist on the navigator object');
/* URL argument */
-const vaild_urls = [
+const valid_urls = [
'%s',
location.href + '/%s',
location.href + '#%s',
@@ -31,7 +31,7 @@
location.href + '/%s/bar/baz/?foo=1337&bar#baz',
location.href + '/%s/foo/%s/',
];
-for (const url of vaild_urls) {
+for (const url of valid_urls) {
test(() => {
navigator.registerProtocolHandler('tel', url, 'foo');
}, 'registerProtocolHandler: Valid URL "' + url + '" should work.');
@@ -61,7 +61,7 @@
}, 'unregisterProtocolHandler: Invalid URL "' + url + '" should throw SYNTAX_ERR.');
}
-const invaild_urls2 = [
+const invalid_urls2 = [
'http://%s.com',
'http://%s.example.com',
'http://example.com/%s',
@@ -70,7 +70,7 @@
'mailto:%s@example.com',
'mailto:%s',
];
-for (const url of invaild_urls2) {
+for (const url of invalid_urls2) {
test(() => {
assert_throws('SECURITY_ERR', () => { navigator.registerProtocolHandler('mailto', url, 'foo'); });
}, 'registerProtocolHandler: Invalid URL "' + url + '" should throw SECURITY_ERR.');
@@ -84,7 +84,7 @@
/* Overriding any of the following protocols must never be allowed. That would
* break the browser. */
-const blacklist = [
+const denylist = [
'about',
'attachment',
'blob',
@@ -124,13 +124,13 @@
'tel:sip',
'web+',
];
-for (const scheme of blacklist) {
+for (const scheme of denylist) {
test(() => {
assert_throws('SECURITY_ERR', () => { navigator.registerProtocolHandler(scheme, location.href + '/%s', 'foo'); });
}, 'registerProtocolHandler: Attempting to override the "' + scheme + '" protocol should throw SECURITY_ERR.');
test(() => {
- assert_throws('SECURITY_ERR', () => { navigator.unregisterProtocolHandler(scheme, location.href + '/%s', 'foo'); });
+ assert_throws('SECURITY_ERR', () => { navigator.unregisterProtocolHandler(scheme, location.href + '/%s'); });
}, 'unregisterProtocolHandler: Attempting to override the "' + scheme + '" protocol should throw SECURITY_ERR.');
}
@@ -161,7 +161,7 @@
'webcal',
'wtai',
'xmpp',
- /*other vaild schemes*/
+ /*other valid schemes*/
'BitcoIn',
'Irc',
'MagneT',
diff --git a/third_party/blink/web_tests/external/wpt/html/webappapis/system-state-and-capabilities/the-navigator-object/secure_context.html b/third_party/blink/web_tests/external/wpt/html/webappapis/system-state-and-capabilities/the-navigator-object/secure_context.html
new file mode 100644
index 0000000..685f5d1
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/webappapis/system-state-and-capabilities/the-navigator-object/secure_context.html
@@ -0,0 +1,18 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script>
+ test(t => {
+ assert_false('registerProtocolHandler' in navigator);
+ assert_equals(navigator.registerProtocolHandler, undefined);
+ }, "navigator.registerProtocolHandler does not exist in non-secure contexts.");
+
+ test(t => {
+ assert_false('unregisterProtocolHandler' in navigator);
+ assert_equals(navigator.unregisterProtocolHandler, undefined);
+ }, "navigator.unregisterProtocolHandler does not exist in non-secure contexts.");
+ </script>
+</head>
+</html>
diff --git a/third_party/blink/web_tests/register-protocol-handler/blob-urls.html b/third_party/blink/web_tests/register-protocol-handler/blob-urls.html
index 9bee82d..c528464 100644
--- a/third_party/blink/web_tests/register-protocol-handler/blob-urls.html
+++ b/third_party/blink/web_tests/register-protocol-handler/blob-urls.html
@@ -3,10 +3,9 @@
<script src="../resources/testharnessreport.js"></script>
<title>Test that blob: URLs fail registerProtocolHandler checks</title>
<body>
-<iframe id='frame'></iframe>
<script>
-const frameSrc =
+const winSrc =
`<html>
<head>
<script>
@@ -14,9 +13,9 @@
navigator.registerProtocolHandler('web+test',
location.href + '?%s',
'Test handler');
- window.parent.postMessage('rph succeeded', '*');
+ window.opener.postMessage('rph succeeded', '*');
} catch (e) {
- window.parent.postMessage('rph failed: ' + e.message, '*');
+ window.opener.postMessage('rph failed: ' + e.message, '*');
}
<\/script>
<\/head>
@@ -28,12 +27,13 @@
window.addEventListener('message', (msg) => {
assert_equals(msg.data,
- 'rph failed: Failed to execute \'registerProtocolHandler\' on \'Navigator\': The scheme of the url provided must be the \'http\' or \'https\'.',
+ 'rph failed: Failed to execute \'registerProtocolHandler\' on \'Navigator\': The scheme of the url provided must be \'https\' or \'chrome-extension\'.',
'registerProtocolHandler should have failed');
done();
});
-const data = new Blob([frameSrc], {type: 'text/html'});
-document.getElementById('frame').src = window.URL.createObjectURL(data);
+const data = new Blob([winSrc], {type: 'text/html'});
+const objectURL = window.URL.createObjectURL(data);
+window.open(objectURL, '_blank');
</script>
</body>
diff --git a/third_party/blink/web_tests/register-protocol-handler/data-urls.html b/third_party/blink/web_tests/register-protocol-handler/data-urls.https.html
similarity index 78%
rename from third_party/blink/web_tests/register-protocol-handler/data-urls.html
rename to third_party/blink/web_tests/register-protocol-handler/data-urls.https.html
index 89bde5f..2c2c5a3c 100644
--- a/third_party/blink/web_tests/register-protocol-handler/data-urls.html
+++ b/third_party/blink/web_tests/register-protocol-handler/data-urls.https.html
@@ -28,8 +28,7 @@
window.addEventListener('message', (msg) => {
assert_equals(msg.data,
- 'rph failed: Failed to execute \'registerProtocolHandler\' on \'Navigator\': The scheme of the url provided must be the \'http\' or \'https\'.',
- 'registerProtocolHandler should have failed');
+ 'rph failed: navigator.registerProtocolHandler is not a function');
done();
});