From f1e5578718ea8883438cfea06d3c55d25f5c0278 Mon Sep 17 00:00:00 2001 From: janekptacijarabaci Date: Sun, 22 Apr 2018 19:03:22 +0200 Subject: moebius#226: Consider blocking top level window data: URIs (part 2/2 without tests) https://github.com/MoonchildProductions/moebius/pull/226 --- dom/security/nsContentSecurityManager.cpp | 59 +++++++++------------- dom/security/nsContentSecurityManager.h | 5 +- dom/security/test/general/browser.ini | 6 +++ .../test/general/browser_test_data_download.js | 37 ++++++++++++++ .../test/general/browser_test_data_text_csv.js | 37 ++++++++++++++ dom/security/test/general/file_data_download.html | 14 +++++ dom/security/test/general/file_data_text_csv.html | 14 +++++ .../test_block_toplevel_data_img_navigation.html | 18 ++++--- .../test_block_toplevel_data_navigation.html | 16 +++--- 9 files changed, 149 insertions(+), 57 deletions(-) create mode 100644 dom/security/test/general/browser_test_data_download.js create mode 100644 dom/security/test/general/browser_test_data_text_csv.js create mode 100644 dom/security/test/general/file_data_download.html create mode 100644 dom/security/test/general/file_data_text_csv.html (limited to 'dom') diff --git a/dom/security/nsContentSecurityManager.cpp b/dom/security/nsContentSecurityManager.cpp index 069e7d6a70..c987fed67b 100644 --- a/dom/security/nsContentSecurityManager.cpp +++ b/dom/security/nsContentSecurityManager.cpp @@ -10,20 +10,16 @@ #include "nsIStreamListener.h" #include "nsIDocument.h" #include "nsMixedContentBlocker.h" -#include "nsNullPrincipal.h" #include "mozilla/dom/Element.h" +#include "mozilla/dom/TabChild.h" NS_IMPL_ISUPPORTS(nsContentSecurityManager, nsIContentSecurityManager, nsIChannelEventSink) /* static */ bool -nsContentSecurityManager::AllowTopLevelNavigationToDataURI( - nsIURI* aURI, - nsContentPolicyType aContentPolicyType, - nsIPrincipal* aTriggeringPrincipal, - bool aLoadFromExternal) +nsContentSecurityManager::AllowTopLevelNavigationToDataURI(nsIChannel* aChannel) { // Let's block all toplevel document navigations to a data: URI. // In all cases where the toplevel document is navigated to a @@ -36,17 +32,24 @@ nsContentSecurityManager::AllowTopLevelNavigationToDataURI( if (!mozilla::net::nsIOService::BlockToplevelDataUriNavigations()) { return true; } - if (aContentPolicyType != nsIContentPolicy::TYPE_DOCUMENT) { + nsCOMPtr loadInfo = aChannel->GetLoadInfo(); + if (!loadInfo) { + return true; + } + if (loadInfo->GetExternalContentPolicyType() != nsIContentPolicy::TYPE_DOCUMENT) { return true; } + nsCOMPtr uri; + nsresult rv = NS_GetFinalChannelURI(aChannel, getter_AddRefs(uri)); + NS_ENSURE_SUCCESS(rv, true); bool isDataURI = - (NS_SUCCEEDED(aURI->SchemeIs("data", &isDataURI)) && isDataURI); + (NS_SUCCEEDED(uri->SchemeIs("data", &isDataURI)) && isDataURI); if (!isDataURI) { return true; } // Whitelist data: images as long as they are not SVGs nsAutoCString filePath; - aURI->GetFilePath(filePath); + uri->GetFilePath(filePath); if (StringBeginsWith(filePath, NS_LITERAL_CSTRING("image/")) && !StringBeginsWith(filePath, NS_LITERAL_CSTRING("image/svg+xml"))) { return true; @@ -56,22 +59,29 @@ nsContentSecurityManager::AllowTopLevelNavigationToDataURI( StringBeginsWith(filePath, NS_LITERAL_CSTRING("application/json"))) { return true; } - if (!aLoadFromExternal && - nsContentUtils::IsSystemPrincipal(aTriggeringPrincipal)) { + // Redirecting to a toplevel data: URI is not allowed, hence we make + // sure the RedirectChain is empty. + if (!loadInfo->GetLoadTriggeredFromExternal() && + nsContentUtils::IsSystemPrincipal(loadInfo->TriggeringPrincipal()) && + loadInfo->RedirectChain().IsEmpty()) { return true; } nsAutoCString dataSpec; - aURI->GetSpec(dataSpec); + uri->GetSpec(dataSpec); if (dataSpec.Length() > 50) { dataSpec.Truncate(50); dataSpec.AppendLiteral("..."); } + nsCOMPtr tabChild = do_QueryInterface(loadInfo->ContextForTopLevelLoad()); + nsCOMPtr doc; + if (tabChild) { + doc = static_cast(tabChild.get())->GetDocument(); + } NS_ConvertUTF8toUTF16 specUTF16(NS_UnescapeURL(dataSpec)); const char16_t* params[] = { specUTF16.get() }; nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, NS_LITERAL_CSTRING("DATA_URI_BLOCKED"), - // no doc available, log to browser console - nullptr, + doc, nsContentUtils::eSECURITY_PROPERTIES, "BlockTopLevelDataURINavigation", params, ArrayLength(params)); @@ -541,27 +551,6 @@ nsContentSecurityManager::AsyncOnChannelRedirect(nsIChannel* aOldChannel, } } - // Redirecting to a toplevel data: URI is not allowed, hence we pass - // a NullPrincipal as the TriggeringPrincipal to - // AllowTopLevelNavigationToDataURI() which definitely blocks any - // data: URI load. - nsCOMPtr newLoadInfo = aNewChannel->GetLoadInfo(); - if (newLoadInfo) { - nsCOMPtr uri; - nsresult rv = NS_GetFinalChannelURI(aNewChannel, getter_AddRefs(uri)); - NS_ENSURE_SUCCESS(rv, rv); - nsCOMPtr nullTriggeringPrincipal = nsNullPrincipal::Create(); - if (!nsContentSecurityManager::AllowTopLevelNavigationToDataURI( - uri, - newLoadInfo->GetExternalContentPolicyType(), - nullTriggeringPrincipal, - false)) { - // logging to console happens within AllowTopLevelNavigationToDataURI - aOldChannel->Cancel(NS_ERROR_DOM_BAD_URI); - return NS_ERROR_DOM_BAD_URI; - } - } - // Also verify that the redirecting server is allowed to redirect to the // given URI nsCOMPtr oldPrincipal; diff --git a/dom/security/nsContentSecurityManager.h b/dom/security/nsContentSecurityManager.h index 09b6c86aa1..bab8477434 100644 --- a/dom/security/nsContentSecurityManager.h +++ b/dom/security/nsContentSecurityManager.h @@ -32,10 +32,7 @@ public: static nsresult doContentSecurityCheck(nsIChannel* aChannel, nsCOMPtr& aInAndOutListener); - static bool AllowTopLevelNavigationToDataURI(nsIURI* aURI, - nsContentPolicyType aContentPolicyType, - nsIPrincipal* aTriggeringPrincipal, - bool aLoadFromExternal); + static bool AllowTopLevelNavigationToDataURI(nsIChannel* aChannel); private: static nsresult CheckChannel(nsIChannel* aChannel); diff --git a/dom/security/test/general/browser.ini b/dom/security/test/general/browser.ini index 97ddae3bf3..73ae72dddd 100644 --- a/dom/security/test/general/browser.ini +++ b/dom/security/test/general/browser.ini @@ -3,3 +3,9 @@ support-files = file_toplevel_data_navigations.sjs file_toplevel_data_meta_redirect.html +[browser_test_data_download.js] +support-files = + file_data_download.html +[browser_test_data_text_csv.js] +support-files = + file_data_text_csv.html diff --git a/dom/security/test/general/browser_test_data_download.js b/dom/security/test/general/browser_test_data_download.js new file mode 100644 index 0000000000..1ee8d58449 --- /dev/null +++ b/dom/security/test/general/browser_test_data_download.js @@ -0,0 +1,37 @@ +"use strict"; + +const kTestPath = getRootDirectory(gTestPath) + .replace("chrome://mochitests/content", "http://example.com") +const kTestURI = kTestPath + "file_data_download.html"; + +function addWindowListener(aURL, aCallback) { + Services.wm.addListener({ + onOpenWindow(aXULWindow) { + info("window opened, waiting for focus"); + Services.wm.removeListener(this); + var domwindow = aXULWindow.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDOMWindow); + waitForFocus(function() { + is(domwindow.document.location.href, aURL, "should have seen the right window open"); + aCallback(domwindow); + }, domwindow); + }, + onCloseWindow(aXULWindow) { }, + onWindowTitleChange(aXULWindow, aNewTitle) { } + }); +} + +function test() { + waitForExplicitFinish(); + Services.prefs.setBoolPref("security.data_uri.block_toplevel_data_uri_navigations", true); + registerCleanupFunction(function() { + Services.prefs.clearUserPref("security.data_uri.block_toplevel_data_uri_navigations"); + }); + addWindowListener("chrome://mozapps/content/downloads/unknownContentType.xul", function(win) { + is(win.document.getElementById("location").value, "data-foo.html", + "file name of download should match"); + win.close(); + finish(); + }); + gBrowser.loadURI(kTestURI); +} diff --git a/dom/security/test/general/browser_test_data_text_csv.js b/dom/security/test/general/browser_test_data_text_csv.js new file mode 100644 index 0000000000..c45e40cc2e --- /dev/null +++ b/dom/security/test/general/browser_test_data_text_csv.js @@ -0,0 +1,37 @@ +"use strict"; + +const kTestPath = getRootDirectory(gTestPath) + .replace("chrome://mochitests/content", "http://example.com") +const kTestURI = kTestPath + "file_data_text_csv.html"; + +function addWindowListener(aURL, aCallback) { + Services.wm.addListener({ + onOpenWindow(aXULWindow) { + info("window opened, waiting for focus"); + Services.wm.removeListener(this); + var domwindow = aXULWindow.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDOMWindow); + waitForFocus(function() { + is(domwindow.document.location.href, aURL, "should have seen the right window open"); + aCallback(domwindow); + }, domwindow); + }, + onCloseWindow(aXULWindow) { }, + onWindowTitleChange(aXULWindow, aNewTitle) { } + }); +} + +function test() { + waitForExplicitFinish(); + Services.prefs.setBoolPref("security.data_uri.block_toplevel_data_uri_navigations", true); + registerCleanupFunction(function() { + Services.prefs.clearUserPref("security.data_uri.block_toplevel_data_uri_navigations"); + }); + addWindowListener("chrome://mozapps/content/downloads/unknownContentType.xul", function(win) { + is(win.document.getElementById("location").value, "text/csv;foo,bar,foobar", + "file name of download should match"); + win.close(); + finish(); + }); + gBrowser.loadURI(kTestURI); +} diff --git a/dom/security/test/general/file_data_download.html b/dom/security/test/general/file_data_download.html new file mode 100644 index 0000000000..4cc92fe8f5 --- /dev/null +++ b/dom/security/test/general/file_data_download.html @@ -0,0 +1,14 @@ + + + + Test download attribute for data: URI + + + download data + + + diff --git a/dom/security/test/general/file_data_text_csv.html b/dom/security/test/general/file_data_text_csv.html new file mode 100644 index 0000000000..a9ac369d16 --- /dev/null +++ b/dom/security/test/general/file_data_text_csv.html @@ -0,0 +1,14 @@ + + + + Test open data:text/csv + + + test text/csv + + + diff --git a/dom/security/test/general/test_block_toplevel_data_img_navigation.html b/dom/security/test/general/test_block_toplevel_data_img_navigation.html index 2b8f62760a..7f8dfc7480 100644 --- a/dom/security/test/general/test_block_toplevel_data_img_navigation.html +++ b/dom/security/test/general/test_block_toplevel_data_img_navigation.html @@ -34,15 +34,17 @@ function test_toplevel_data_image_svg() { const DATA_SVG = "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiIgdmlld0JveD0iMCAwIDE2IDE2Ij4KICA8cGF0aCBkPSJNOCwxMkwzLDcsNCw2bDQsNCw0LTQsMSwxWiIgZmlsbD0iIzZBNkE2QSIgLz4KPC9zdmc+Cg=="; let win2 = window.open(DATA_SVG); - let wrappedWin2 = SpecialPowers.wrap(win2); - setTimeout(function () { - isnot(wrappedWin2.document.documentElement.localName, "svg", - "Loading data:image/svg+xml should be blocked"); - wrappedWin2.close(); - SimpleTest.finish(); - }, 1000); + // Unfortunately we can't detect whether the window was closed using some event, + // hence we are constantly polling till we see that win == null. + // Test times out on failure. + var win2Closed = setInterval(function() { + if (win2 == null || win2.closed) { + clearInterval(win2Closed); + ok(true, "Loading data:image/svg+xml should be blocked"); + SimpleTest.finish(); + } + }, 200); } - // fire up the tests test_toplevel_data_image(); diff --git a/dom/security/test/general/test_block_toplevel_data_navigation.html b/dom/security/test/general/test_block_toplevel_data_navigation.html index fc91f2ec0a..cef232b65a 100644 --- a/dom/security/test/general/test_block_toplevel_data_navigation.html +++ b/dom/security/test/general/test_block_toplevel_data_navigation.html @@ -21,16 +21,12 @@ function test1() { // simple data: URI click navigation should be prevented let TEST_FILE = "file_block_toplevel_data_navigation.html"; let win1 = window.open(TEST_FILE); - var readyStateCheckInterval = setInterval(function() { - let state = win1.document.readyState; - if (state === "interactive" || state === "complete") { - clearInterval(readyStateCheckInterval); - ok(win1.document.body.innerHTML.indexOf("test1:") !== -1, - "toplevel data: URI navigation through click() should be blocked"); - win1.close(); - test2(); - } - }, 200); + setTimeout(function () { + ok(SpecialPowers.wrap(win1).document.body.innerHTML.indexOf("test1:") !== -1, + "toplevel data: URI navigation through click() should be blocked"); + win1.close(); + test2(); + }, 1000); } function test2() { -- cgit v1.2.3