mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-27 23:02:20 +00:00
Bug 1746115 - Perform data URI blocking from DocumentLoadListener, r=smaug
Differential Revision: https://phabricator.services.mozilla.com/D138213
This commit is contained in:
parent
f319e6a711
commit
001c77587c
@ -60,6 +60,7 @@ EXPORTS += [
|
||||
"nsDocShellLoadState.h",
|
||||
"nsDocShellLoadTypes.h",
|
||||
"nsDocShellTreeOwner.h",
|
||||
"nsDSURIContentListener.h",
|
||||
"nsIScrollObserver.h",
|
||||
"nsWebNavigationInfo.h",
|
||||
"SerializedLoadContext.h",
|
||||
|
@ -138,26 +138,8 @@ nsDSURIContentListener::DoContent(const nsACString& aContentType,
|
||||
|
||||
// determine if the channel has just been retargeted to us...
|
||||
nsLoadFlags loadFlags = 0;
|
||||
nsCOMPtr<nsIChannel> aOpenedChannel = do_QueryInterface(aRequest);
|
||||
|
||||
if (aOpenedChannel) {
|
||||
aOpenedChannel->GetLoadFlags(&loadFlags);
|
||||
|
||||
// block top-level data URI navigations if triggered by the web
|
||||
if (!nsContentSecurityManager::AllowTopLevelNavigationToDataURI(
|
||||
aOpenedChannel)) {
|
||||
// logging to console happens within AllowTopLevelNavigationToDataURI
|
||||
aRequest->Cancel(NS_ERROR_DOM_BAD_URI);
|
||||
*aAbortProcess = true;
|
||||
// close the window since the navigation to a data URI was blocked
|
||||
if (mDocShell && mDocShell->GetBrowsingContext()) {
|
||||
RefPtr<MaybeCloseWindowHelper> maybeCloseWindowHelper =
|
||||
new MaybeCloseWindowHelper(mDocShell->GetBrowsingContext());
|
||||
maybeCloseWindowHelper->SetShouldCloseWindow(true);
|
||||
Unused << maybeCloseWindowHelper->MaybeCloseWindow();
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
if (nsCOMPtr<nsIChannel> openedChannel = do_QueryInterface(aRequest)) {
|
||||
openedChannel->GetLoadFlags(&loadFlags);
|
||||
}
|
||||
|
||||
if (loadFlags & nsIChannel::LOAD_RETARGETED_DOCUMENT_URI) {
|
||||
|
@ -125,25 +125,29 @@ bool nsContentSecurityManager::AllowTopLevelNavigationToDataURI(
|
||||
loadInfo->RedirectChain().IsEmpty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// We're going to block the request, construct the localized error message to
|
||||
// report to the console.
|
||||
nsAutoCString dataSpec;
|
||||
uri->GetSpec(dataSpec);
|
||||
if (dataSpec.Length() > 50) {
|
||||
dataSpec.Truncate(50);
|
||||
dataSpec.AppendLiteral("...");
|
||||
}
|
||||
nsCOMPtr<nsISupports> context = loadInfo->ContextForTopLevelLoad();
|
||||
nsCOMPtr<nsIBrowserChild> browserChild = do_QueryInterface(context);
|
||||
nsCOMPtr<Document> doc;
|
||||
if (browserChild) {
|
||||
doc = static_cast<mozilla::dom::BrowserChild*>(browserChild.get())
|
||||
->GetTopLevelDocument();
|
||||
}
|
||||
AutoTArray<nsString, 1> params;
|
||||
CopyUTF8toUTF16(NS_UnescapeURL(dataSpec), *params.AppendElement());
|
||||
nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
|
||||
"DATA_URI_BLOCKED"_ns, doc,
|
||||
nsContentUtils::eSECURITY_PROPERTIES,
|
||||
"BlockTopLevelDataURINavigation", params);
|
||||
nsAutoString errorText;
|
||||
rv = nsContentUtils::FormatLocalizedString(
|
||||
nsContentUtils::eSECURITY_PROPERTIES, "BlockTopLevelDataURINavigation",
|
||||
params, errorText);
|
||||
NS_ENSURE_SUCCESS(rv, false);
|
||||
|
||||
// Report the localized error message to the console for the loading
|
||||
// BrowsingContext's current inner window.
|
||||
RefPtr<BrowsingContext> target = loadInfo->GetBrowsingContext();
|
||||
nsContentUtils::ReportToConsoleByWindowID(
|
||||
errorText, nsIScriptError::warningFlag, "DATA_URI_BLOCKED"_ns,
|
||||
target ? target->GetCurrentInnerWindowId() : 0);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -7,8 +7,7 @@
|
||||
<body>
|
||||
test1: clicking data: URI tries to navigate window<br/>
|
||||
<!-- postMessage will not be sent if data: URI is blocked -->
|
||||
<a id="testlink" href="data:text/html,<body><script
|
||||
window.opener.postMessage('test1','*');</script>toplevel data: URI navigations
|
||||
<a id="testlink" href="data:text/html,<body>toplevel data: URI navigations
|
||||
should be blocked</body>">click me</a>
|
||||
<script>
|
||||
document.getElementById('testlink').click();
|
||||
|
@ -8,33 +8,9 @@
|
||||
test2: data: URI in iframe tries to window.open(data:, _blank);<br/>
|
||||
<iframe id="testFrame" src=""></iframe>
|
||||
<script>
|
||||
// GeckoView displays an error page for invalid navigations,
|
||||
// so catch the security error trying to access the cross-origin error
|
||||
// document and treat that as blocked.
|
||||
let DATA_URI = `data:text/html,<body><script>
|
||||
var win = window.open("data:text/html,<body>toplevel data: URI navigations should be blocked</body>", "_blank");
|
||||
setTimeout(function () {
|
||||
let result = "navigated";
|
||||
try {
|
||||
result = win.document.body.innerHTML === "" ? "blocked" : "navigated";
|
||||
} catch (e) {
|
||||
if (e instanceof DOMException && e.name === "SecurityError") {
|
||||
result = "blocked";
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
parent.postMessage(result, "*");
|
||||
win.close();
|
||||
}, 1000);
|
||||
<\/script></body>`;
|
||||
|
||||
window.addEventListener("message", receiveMessage);
|
||||
function receiveMessage(event) {
|
||||
window.removeEventListener("message", receiveMessage);
|
||||
// propagate the information back to the caller
|
||||
window.opener.postMessage(event.data, "*");
|
||||
}
|
||||
document.getElementById('testFrame').src = DATA_URI;
|
||||
</script>
|
||||
</body>
|
||||
|
@ -9,139 +9,124 @@
|
||||
</head>
|
||||
<body>
|
||||
<script class="testbody" type="text/javascript">
|
||||
SpecialPowers.setBoolPref("security.data_uri.block_toplevel_data_uri_navigations", true);
|
||||
SimpleTest.registerCleanupFunction(() => {
|
||||
SpecialPowers.clearUserPref("security.data_uri.block_toplevel_data_uri_navigations");
|
||||
});
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
SimpleTest.requestFlakyTimeout("have to test that top level data: URI navgiation is blocked");
|
||||
async function expectBlockedToplevelData() {
|
||||
await SpecialPowers.spawnChrome([], async () => {
|
||||
let progressListener;
|
||||
let bid = await new Promise(resolve => {
|
||||
let bcs = [];
|
||||
progressListener = {
|
||||
QueryInterface: ChromeUtils.generateQI(["nsIWebProgressListener", "nsISupportsWeakReference"]),
|
||||
onStateChange(webProgress, request, stateFlags, status) {
|
||||
if (!(request instanceof Ci.nsIChannel) || !webProgress.isTopLevel ||
|
||||
!(stateFlags & Ci.nsIWebProgressListener.STATE_IS_WINDOW) ||
|
||||
!(stateFlags & Ci.nsIWebProgressListener.STATE_STOP)) {
|
||||
return;
|
||||
}
|
||||
|
||||
var testsToRun = {
|
||||
test1: false,
|
||||
test3: false,
|
||||
};
|
||||
if (ChromeUtils.getXPCOMErrorName(status) != "NS_ERROR_DOM_BAD_URI") {
|
||||
isnot(request.URI.scheme, "data");
|
||||
return;
|
||||
}
|
||||
|
||||
// test1 and test3 event messages will not be received if toplevel data: URI
|
||||
// is blocked.
|
||||
window.addEventListener("message", receiveMessage);
|
||||
function receiveMessage(event) {
|
||||
switch (event.data) {
|
||||
case "test1":
|
||||
testsToRun.test1 = true;
|
||||
break;
|
||||
case "test3":
|
||||
testsToRun.test3 = true;
|
||||
break;
|
||||
}
|
||||
// We can't check for the scheme to be "data" because in the case of a
|
||||
// redirected load, we'll get a `NS_ERROR_DOM_BAD_URI` load error
|
||||
// before observing the redirect, cancelling the load. Instead we just
|
||||
// wait for any load to error with `NS_ERROR_DOM_BAD_URI`.
|
||||
for (let bc of bcs) {
|
||||
try {
|
||||
bc.webProgress.removeProgressListener(progressListener);
|
||||
} catch(e) { }
|
||||
}
|
||||
bcs = [];
|
||||
Services.obs.removeObserver(observer, "browsing-context-attached");
|
||||
resolve(webProgress.browsingContext.browserId);
|
||||
}
|
||||
};
|
||||
|
||||
function observer(subject, topic) {
|
||||
if (!bcs.includes(subject.webProgress)) {
|
||||
bcs.push(subject.webProgress);
|
||||
subject.webProgress.addProgressListener(progressListener, Ci.nsIWebProgress.NOTIFY_ALL);
|
||||
}
|
||||
}
|
||||
Services.obs.addObserver(observer, "browsing-context-attached");
|
||||
});
|
||||
return bid;
|
||||
});
|
||||
}
|
||||
|
||||
function test1() {
|
||||
async function expectBlockedURIWarning() {
|
||||
await SpecialPowers.spawnChrome([], async () => {
|
||||
return new Promise(resolve => {
|
||||
Services.console.registerListener(function onConsoleMessage(msg) {
|
||||
info("Seeing console message: " + msg.message);
|
||||
if (!(msg instanceof Ci.nsIScriptError)) {
|
||||
return;
|
||||
}
|
||||
if (msg.category != "DATA_URI_BLOCKED") {
|
||||
return;
|
||||
}
|
||||
|
||||
Services.console.unregisterListener(onConsoleMessage);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function expectBrowserDiscarded(browserId) {
|
||||
await SpecialPowers.spawnChrome([browserId], async (browserId) => {
|
||||
return new Promise(resolve => {
|
||||
function check() {
|
||||
if (!BrowsingContext.getCurrentTopByBrowserId(browserId)) {
|
||||
ok(true, `BrowserID ${browserId} discarded`);
|
||||
resolve();
|
||||
Services.obs.removeObserver(check, "browsing-context-discarded");
|
||||
}
|
||||
}
|
||||
Services.obs.addObserver(check, "browsing-context-discarded");
|
||||
check();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function popupTest(uri, expectClose) {
|
||||
info(`Running expect blocked test for ${uri}`);
|
||||
let reqBlockedPromise = expectBlockedToplevelData();
|
||||
let warningPromise = expectBlockedURIWarning();
|
||||
let win = window.open(uri);
|
||||
let browserId = await reqBlockedPromise;
|
||||
await warningPromise;
|
||||
if (expectClose) {
|
||||
await expectBrowserDiscarded(browserId);
|
||||
}
|
||||
win.close();
|
||||
}
|
||||
|
||||
add_task(async function() {
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
set: [["security.data_uri.block_toplevel_data_uri_navigations", true]],
|
||||
});
|
||||
|
||||
// simple data: URI click navigation should be prevented
|
||||
let TEST_FILE = "file_block_toplevel_data_navigation.html";
|
||||
let win1 = window.open(TEST_FILE);
|
||||
// testsToRun["test1"] will be false if toplevel data: URI is blocked
|
||||
setTimeout(function () {
|
||||
is(testsToRun.test1, false,
|
||||
"toplevel data: URI navigation through click() should be blocked");
|
||||
win1.close();
|
||||
test2();
|
||||
}, 1000);
|
||||
}
|
||||
await popupTest("file_block_toplevel_data_navigation.html", false);
|
||||
|
||||
function test2() {
|
||||
// data: URI in iframe which opens data: URI in _blank should be blocked
|
||||
let win2 = window.open("file_block_toplevel_data_navigation2.html");
|
||||
window.addEventListener("message", receiveMessage);
|
||||
function receiveMessage(event) {
|
||||
window.removeEventListener("message", receiveMessage);
|
||||
is(event.data, "blocked",
|
||||
"data: URI navigation using _blank from data: URI should be blocked");
|
||||
win2.close();
|
||||
test3();
|
||||
}
|
||||
}
|
||||
// data: URI in iframe which opens data: URI in _blank should be blocked
|
||||
await popupTest("file_block_toplevel_data_navigation2.html", false);
|
||||
|
||||
function test3() {
|
||||
// navigating to a data: URI using window.location.href should be blocked
|
||||
let win3 = window.open("file_block_toplevel_data_navigation3.html");
|
||||
// testsToRun["test3"] will be false if toplevel data: URI is blocked
|
||||
setTimeout(function () {
|
||||
is(testsToRun.test3, false,
|
||||
"data: URI navigation through win.loc.href should be blocked");
|
||||
win3.close();
|
||||
test4();
|
||||
}, 1000);
|
||||
}
|
||||
await popupTest("file_block_toplevel_data_navigation3.html", false);
|
||||
|
||||
function test4() {
|
||||
// navigating to a data: URI using window.open() should be blocked
|
||||
let win4 = window.open("data:text/html,<body>toplevel data: URI navigations should be blocked</body>");
|
||||
setTimeout(function () {
|
||||
// Please note that the data: URI will be displayed in the URL-Bar but not
|
||||
// loaded, hence we rather rely on document.body than document.location
|
||||
// GeckoView displays an error page for invalid navigations,
|
||||
// so catch the case where we're not allowed to access to (cross-origin)
|
||||
// error document and treat that as blocked.
|
||||
let body = "Error";
|
||||
try {
|
||||
body = win4.document.body.innerHTML;
|
||||
} catch (e) {
|
||||
if (e instanceof DOMException && e.name === "SecurityError") {
|
||||
body = "";
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
is(body, "", "navigating to a data: URI using window.open() should be blocked");
|
||||
test5();
|
||||
}, 1000);
|
||||
}
|
||||
await popupTest("data:text/html,<body>toplevel data: URI navigations should be blocked</body>", false);
|
||||
|
||||
function test5() {
|
||||
// navigating to a URI which redirects to a data: URI using window.open() should be blocked
|
||||
let win5 = window.open("file_block_toplevel_data_redirect.sjs");
|
||||
setTimeout(function () {
|
||||
// Please note that the data: URI will be displayed in the URL-Bar but not
|
||||
// loaded, hence we rather rely on document.body than document.location
|
||||
let body = "Error";
|
||||
try {
|
||||
body = win5.document.body.innerHTML;
|
||||
} catch (e) {
|
||||
if (e instanceof DOMException && e.name === "SecurityError") {
|
||||
body = "";
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
is(body, "", "navigating to URI which redirects to a data: URI using window.open() should be blocked");
|
||||
win5.close();
|
||||
test6();
|
||||
}, 1000);
|
||||
}
|
||||
await popupTest("file_block_toplevel_data_redirect.sjs", false);
|
||||
|
||||
function test6() {
|
||||
// navigating to a data: URI without a Content Type should be blocked
|
||||
let win6 = window.open("data:DataURIsWithNoContentTypeShouldBeBlocked");
|
||||
setTimeout(function () {
|
||||
let body = "Error";
|
||||
try {
|
||||
body = win6.document.body.innerHTML;
|
||||
} catch (e) {
|
||||
if (e instanceof DOMException && e.name === "SecurityError") {
|
||||
body = "";
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
is(body, "", "navigating to a data: URI without a Content Type should be blocked");
|
||||
win6.close();
|
||||
SimpleTest.finish();
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
// fire up the tests
|
||||
test1();
|
||||
await popupTest("data:,DataURIsWithNoContentTypeShouldBeBlocked", false);
|
||||
});
|
||||
|
||||
</script>
|
||||
</body>
|
||||
|
@ -30,10 +30,12 @@
|
||||
#include "mozilla/net/HttpChannelParent.h"
|
||||
#include "mozilla/net/RedirectChannelRegistrar.h"
|
||||
#include "nsContentSecurityUtils.h"
|
||||
#include "nsContentSecurityManager.h"
|
||||
#include "nsDocShell.h"
|
||||
#include "nsDocShellLoadState.h"
|
||||
#include "nsDocShellLoadTypes.h"
|
||||
#include "nsDOMNavigationTiming.h"
|
||||
#include "nsDSURIContentListener.h"
|
||||
#include "nsObjectLoadingContent.h"
|
||||
#include "nsExternalHelperAppService.h"
|
||||
#include "nsHttpChannel.h"
|
||||
@ -2191,6 +2193,28 @@ DocumentLoadListener::OnStartRequest(nsIRequest* aRequest) {
|
||||
return NS_ERROR_UNEXPECTED;
|
||||
}
|
||||
|
||||
// Block top-level data URI navigations if triggered by the web. Logging is
|
||||
// performed in AllowTopLevelNavigationToDataURI.
|
||||
if (!nsContentSecurityManager::AllowTopLevelNavigationToDataURI(mChannel)) {
|
||||
mChannel->Cancel(NS_ERROR_DOM_BAD_URI);
|
||||
if (loadingContext) {
|
||||
RefPtr<MaybeCloseWindowHelper> maybeCloseWindowHelper =
|
||||
new MaybeCloseWindowHelper(loadingContext);
|
||||
// If a new window was opened specifically for this request, close it
|
||||
// after blocking the navigation.
|
||||
if (nsCOMPtr<nsIPropertyBag2> props = do_QueryInterface(mChannel)) {
|
||||
bool tmp = false;
|
||||
if (NS_SUCCEEDED(props->GetPropertyAsBool(
|
||||
u"docshell.newWindowTarget"_ns, &tmp))) {
|
||||
maybeCloseWindowHelper->SetShouldCloseWindow(tmp);
|
||||
}
|
||||
}
|
||||
Unused << maybeCloseWindowHelper->MaybeCloseWindow();
|
||||
}
|
||||
DisconnectListeners(NS_ERROR_DOM_BAD_URI, NS_ERROR_DOM_BAD_URI);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// Generally we want to switch to a real channel even if the request failed,
|
||||
// since the listener might want to access protocol-specific data (like http
|
||||
// response headers) in its error handling.
|
||||
|
Loading…
Reference in New Issue
Block a user