Bug 1394554: Block toplevel data: URI navigations after redirect. r=smaug
authorChristoph Kerschbaumer <ckerschb@christophkerschbaumer.com>
Wed, 06 Sep 2017 09:33:10 +0200
changeset 379346 bb932a1656cd4f8850457d85f4916030afbdcdc8
parent 379345 dbb4fb7aa5bb28d19623297c03bdfb23d73019cf
child 379347 5fb3040f8887f30b7f86c7d0afe474282c6abaa2
push id50642
push userarchaeopteryx@coole-files.de
push dateThu, 07 Sep 2017 10:41:07 +0000
treeherderautoland@bd0ce93776fe [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug
bugs1394554
milestone57.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1394554: Block toplevel data: URI navigations after redirect. r=smaug
docshell/base/nsDocShell.cpp
dom/security/nsContentSecurityManager.cpp
dom/security/nsContentSecurityManager.h
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -38,16 +38,17 @@
 #include "nsIContent.h"
 #include "nsIContentInlines.h"
 #include "nsIDocument.h"
 #include "nsIDOMDocument.h"
 #include "nsIDOMElement.h"
 
 #include "nsArray.h"
 #include "nsArrayUtils.h"
+#include "nsContentSecurityManager.h"
 #include "nsICaptivePortalService.h"
 #include "nsIDOMStorage.h"
 #include "nsIContentViewer.h"
 #include "nsIDocumentLoaderFactory.h"
 #include "nsCURILoader.h"
 #include "nsContentDLF.h"
 #include "nsDocShellCID.h"
 #include "nsDOMCID.h"
@@ -9937,46 +9938,23 @@ nsDocShell::InternalLoad(nsIURI* aURI,
       // an iframe since that's more common.
       contentType = nsIContentPolicy::TYPE_INTERNAL_IFRAME;
     }
   } else {
     contentType = nsIContentPolicy::TYPE_DOCUMENT;
     isTargetTopLevelDocShell = true;
   }
 
-  if (contentType == nsIContentPolicy::TYPE_DOCUMENT &&
-      nsIOService::BlockToplevelDataUriNavigations()) {
-    bool isDataURI =
-      (NS_SUCCEEDED(aURI->SchemeIs("data", &isDataURI)) && isDataURI);
-    // Let's block all toplevel document navigations to a data: URI.
-    // In all cases where the toplevel document is navigated to a
-    // data: URI the triggeringPrincipal is a codeBasePrincipal, or
-    // a NullPrincipal. In other cases, e.g. typing a data: URL into
-    // the URL-Bar, the triggeringPrincipal is a SystemPrincipal;
-    // we don't want to block those loads. Only exception, loads coming
-    // from an external applicaton (e.g. Thunderbird) don't load
-    // using a codeBasePrincipal, but we want to block those loads.
-    bool loadFromExternal = (aLoadType == LOAD_NORMAL_EXTERNAL);
-    if (isDataURI && (loadFromExternal || 
-        !nsContentUtils::IsSystemPrincipal(aTriggeringPrincipal))) {
-      NS_ConvertUTF8toUTF16 specUTF16(aURI->GetSpecOrDefault());
-      if (specUTF16.Length() > 50) {
-        specUTF16.Truncate(50);
-        specUTF16.AppendLiteral("...");
-      }
-      const char16_t* params[] = { specUTF16.get() };
-      nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
-                                      NS_LITERAL_CSTRING("DATA_URI_BLOCKED"),
-                                      // no doc available, log to browser console
-                                      nullptr,
-                                      nsContentUtils::eSECURITY_PROPERTIES,
-                                      "BlockTopLevelDataURINavigation",
-                                      params, ArrayLength(params));
-      return NS_OK;
-    }
+  if (!nsContentSecurityManager::AllowTopLevelNavigationToDataURI(
+        aURI,
+        contentType,
+        aTriggeringPrincipal,
+        (aLoadType == LOAD_NORMAL_EXTERNAL))) {
+    // logging to console happens within AllowTopLevelNavigationToDataURI
+    return NS_OK;
   }
 
   // If there's no targetDocShell, that means we are about to create a new
   // window (or aWindowTarget is empty). Perform a content policy check before
   // creating the window. Please note for all other docshell loads
   // content policy checks are performed within the contentSecurityManager
   // when the channel is about to be openend.
   if (!targetDocShell && !aWindowTarget.IsEmpty()) {
--- a/dom/security/nsContentSecurityManager.cpp
+++ b/dom/security/nsContentSecurityManager.cpp
@@ -1,28 +1,79 @@
 #include "nsContentSecurityManager.h"
 #include "nsIChannel.h"
 #include "nsIHttpChannelInternal.h"
 #include "nsIStreamListener.h"
 #include "nsILoadInfo.h"
+#include "nsIOService.h"
 #include "nsContentUtils.h"
 #include "nsCORSListenerProxy.h"
 #include "nsIStreamListener.h"
 #include "nsIDocument.h"
 #include "nsMixedContentBlocker.h"
 #include "nsCDefaultURIFixup.h"
 #include "nsIURIFixup.h"
 #include "nsIImageLoadingContent.h"
+#include "NullPrincipal.h"
 
 #include "mozilla/dom/Element.h"
 
 NS_IMPL_ISUPPORTS(nsContentSecurityManager,
                   nsIContentSecurityManager,
                   nsIChannelEventSink)
 
+/* static */ bool
+nsContentSecurityManager::AllowTopLevelNavigationToDataURI(
+  nsIURI* aURI,
+  nsContentPolicyType aContentPolicyType,
+  nsIPrincipal* aTriggeringPrincipal,
+  bool aLoadFromExternal)
+{
+  // Let's block all toplevel document navigations to a data: URI.
+  // In all cases where the toplevel document is navigated to a
+  // data: URI the triggeringPrincipal is a codeBasePrincipal, or
+  // a NullPrincipal. In other cases, e.g. typing a data: URL into
+  // the URL-Bar, the triggeringPrincipal is a SystemPrincipal;
+  // we don't want to block those loads. Only exception, loads coming
+  // from an external applicaton (e.g. Thunderbird) don't load
+  // using a codeBasePrincipal, but we want to block those loads.
+  if (!mozilla::net::nsIOService::BlockToplevelDataUriNavigations()) {
+    return true;
+  }
+  if (aContentPolicyType != nsIContentPolicy::TYPE_DOCUMENT) {
+    return true;
+  }
+  bool isDataURI =
+    (NS_SUCCEEDED(aURI->SchemeIs("data", &isDataURI)) && isDataURI);
+  if (!isDataURI) {
+    return true;
+  }
+  if (!aLoadFromExternal &&
+      nsContentUtils::IsSystemPrincipal(aTriggeringPrincipal)) {
+    return true;
+  }
+
+  nsAutoCString spec;
+  aURI->GetSpec(spec);
+  NS_ConvertUTF8toUTF16 specUTF16(aURI->GetSpecOrDefault());
+  if (specUTF16.Length() > 50) {
+    specUTF16.Truncate(50);
+    specUTF16.AppendLiteral("...");
+  }
+  const char16_t* params[] = { specUTF16.get() };
+  nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
+                                  NS_LITERAL_CSTRING("DATA_URI_BLOCKED"),
+                                  // no doc available, log to browser console
+                                  nullptr,
+                                  nsContentUtils::eSECURITY_PROPERTIES,
+                                  "BlockTopLevelDataURINavigation",
+                                  params, ArrayLength(params));
+  return false;
+}
+
 static nsresult
 ValidateSecurityFlags(nsILoadInfo* aLoadInfo)
 {
   nsSecurityFlags securityMode = aLoadInfo->GetSecurityMode();
 
   if (securityMode != nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_INHERITS &&
       securityMode != nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_IS_BLOCKED &&
       securityMode != nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS &&
@@ -519,16 +570,37 @@ nsContentSecurityManager::AsyncOnChannel
   if (loadInfo && loadInfo->GetEnforceSecurity()) {
     nsresult rv = CheckChannel(aNewChannel);
     if (NS_FAILED(rv)) {
       aOldChannel->Cancel(rv);
       return rv;
     }
   }
 
+  // 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<nsILoadInfo> newLoadInfo = aNewChannel->GetLoadInfo();
+  if (newLoadInfo) {
+    nsCOMPtr<nsIURI> uri;
+    nsresult rv = NS_GetFinalChannelURI(aNewChannel, getter_AddRefs(uri));
+    NS_ENSURE_SUCCESS(rv, rv);
+    nsCOMPtr<nsIPrincipal> nullTriggeringPrincipal = NullPrincipal::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<nsIPrincipal> oldPrincipal;
   nsContentUtils::GetSecurityManager()->
     GetChannelResultPrincipal(aOldChannel, getter_AddRefs(oldPrincipal));
 
   nsCOMPtr<nsIURI> newURI;
   Unused << NS_GetFinalChannelURI(aNewChannel, getter_AddRefs(newURI));
--- a/dom/security/nsContentSecurityManager.h
+++ b/dom/security/nsContentSecurityManager.h
@@ -27,16 +27,21 @@ public:
   NS_DECL_NSICONTENTSECURITYMANAGER
   NS_DECL_NSICHANNELEVENTSINK
 
   nsContentSecurityManager() {}
 
   static nsresult doContentSecurityCheck(nsIChannel* aChannel,
                                          nsCOMPtr<nsIStreamListener>& aInAndOutListener);
 
+  static bool AllowTopLevelNavigationToDataURI(nsIURI* aURI,
+                                               nsContentPolicyType aContentPolicyType,
+                                               nsIPrincipal* aTriggeringPrincipal,
+                                               bool aLoadFromExternal);
+
 private:
   static nsresult CheckChannel(nsIChannel* aChannel);
 
   virtual ~nsContentSecurityManager() {}
 
 };
 
 #endif /* nsContentSecurityManager_h___ */