Bug 1331351: Block toplevel window data: URI navigations. r=smaug,francois
authorChristoph Kerschbaumer <ckerschb@christophkerschbaumer.com>
Mon, 24 Jul 2017 18:51:21 +0200
changeset 421852 a721ec6c0170d1a6c624ca23067ed199920c2825
parent 421851 fbc19eff958ca66bf324c592eb6dde84521d3829
child 421853 9cd4b2d56258cccc6cf09175de1e955037c51d4b
push id1517
push userjlorenzo@mozilla.com
push dateThu, 14 Sep 2017 16:50:54 +0000
treeherdermozilla-release@3b41fd564418 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug, francois
bugs1331351
milestone56.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 1331351: Block toplevel window data: URI navigations. r=smaug,francois
docshell/base/nsDocShell.cpp
docshell/base/nsDocShell.h
dom/locales/en-US/chrome/security/security.properties
modules/libpref/init/all.js
netwerk/base/nsIOService.cpp
netwerk/base/nsIOService.h
toolkit/components/telemetry/Histograms.json
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -10285,18 +10285,21 @@ nsDocShell::InternalLoad(nsIURI* aURI,
   nsCOMPtr<nsIDocShellTreeItem> parent = GetParentDocshell();
   if (parent) {
     nsCOMPtr<nsIDocument> doc = parent->GetDocument();
     if (doc) {
       doc->TryCancelFrameLoaderInitialization(this);
     }
   }
 
+  bool loadFromExternal = false;
+
   // Before going any further vet loads initiated by external programs.
   if (aLoadType == LOAD_NORMAL_EXTERNAL) {
+    loadFromExternal = true;
     // Disallow external chrome: loads targetted at content windows
     bool isChrome = false;
     if (NS_SUCCEEDED(aURI->SchemeIs("chrome", &isChrome)) && isChrome) {
       NS_WARNING("blocked external chrome: url -- use '--chrome' option");
       return NS_ERROR_FAILURE;
     }
 
     // clear the decks to prevent context bleed-through (bug 298255)
@@ -10789,17 +10792,18 @@ nsDocShell::InternalLoad(nsIURI* aURI,
   attrs.SetFirstPartyDomain(isTopLevelDoc, aURI);
 
   net::PredictorLearn(aURI, nullptr,
                       nsINetworkPredictor::LEARN_LOAD_TOPLEVEL, attrs);
   net::PredictorPredict(aURI, nullptr,
                         nsINetworkPredictor::PREDICT_LOAD, attrs, nullptr);
 
   nsCOMPtr<nsIRequest> req;
-  rv = DoURILoad(aURI, aOriginalURI, aResultPrincipalURI, aLoadReplace, aReferrer,
+  rv = DoURILoad(aURI, aOriginalURI, aResultPrincipalURI, aLoadReplace,
+                 loadFromExternal, aReferrer,
                  !(aFlags & INTERNAL_LOAD_FLAGS_DONT_SEND_REFERRER),
                  aReferrerPolicy,
                  aTriggeringPrincipal, principalToInherit, aTypeHint,
                  aFileName, aPostData, aHeadersData,
                  aFirstParty, aDocShell, getter_AddRefs(req),
                  (aFlags & INTERNAL_LOAD_FLAGS_FIRST_LOAD) != 0,
                  (aFlags & INTERNAL_LOAD_FLAGS_BYPASS_CLASSIFIER) != 0,
                  (aFlags & INTERNAL_LOAD_FLAGS_FORCE_ALLOW_COOKIES) != 0,
@@ -10870,16 +10874,17 @@ nsDocShell::GetInheritedPrincipal(bool a
   return nullptr;
 }
 
 nsresult
 nsDocShell::DoURILoad(nsIURI* aURI,
                       nsIURI* aOriginalURI,
                       Maybe<nsCOMPtr<nsIURI>> const& aResultPrincipalURI,
                       bool aLoadReplace,
+                      bool aLoadFromExternal,
                       nsIURI* aReferrerURI,
                       bool aSendReferrer,
                       uint32_t aReferrerPolicy,
                       nsIPrincipal* aTriggeringPrincipal,
                       nsIPrincipal* aPrincipalToInherit,
                       const char* aTypeHint,
                       const nsAString& aFileName,
                       nsIInputStream* aPostData,
@@ -11022,35 +11027,44 @@ nsDocShell::DoURILoad(nsIURI* aURI,
 
   nsCOMPtr<nsILoadInfo> loadInfo =
     (aContentPolicyType == nsIContentPolicy::TYPE_DOCUMENT) ?
       new LoadInfo(loadingWindow, aTriggeringPrincipal,
                    securityFlags) :
       new LoadInfo(loadingPrincipal, aTriggeringPrincipal, loadingNode,
                    securityFlags, aContentPolicyType);
 
-  if (aContentPolicyType == nsIContentPolicy::TYPE_DOCUMENT) {
-    enum TopLevelDataState {
-      DATA_NAVIGATED = 0,
-      DATA_TYPED = 1,
-      NO_DATA = 2,
-    };
-    bool isDataURI = (NS_SUCCEEDED(aURI->SchemeIs("data", &isDataURI)) && isDataURI);
-    if (isDataURI) {
-      // In all cases where the toplevel document is navigated to a data: URI
-      // the triggeringPrincipal is a CodeBasePrincipal. In all other cases
-      // e.g. typing a data: URL into the URL-Bar or also clicking a bookmark
-      // uses a SystemPrincipal as the triggeringPrincipal.
-      if (aTriggeringPrincipal->GetIsCodebasePrincipal()) {
-        Telemetry::Accumulate(Telemetry::DOCUMENT_DATA_URI_LOADS, DATA_NAVIGATED);
-      } else {
-        Telemetry::Accumulate(Telemetry::DOCUMENT_DATA_URI_LOADS, DATA_TYPED);
-      }
-    } else {
-      Telemetry::Accumulate(Telemetry::DOCUMENT_DATA_URI_LOADS, NO_DATA);
+  if (aContentPolicyType == 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.
+    if (isDataURI && (aLoadFromExternal || 
+        !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 (aPrincipalToInherit) {
     loadInfo->SetPrincipalToInherit(aPrincipalToInherit);
   }
 
   // We have to do this in case our OriginAttributes are different from the
--- a/docshell/base/nsDocShell.h
+++ b/docshell/base/nsDocShell.h
@@ -372,16 +372,17 @@ protected:
   // and the contents of aSrcdoc will be loaded instead of aURI.
   // aOriginalURI will be set as the originalURI on the channel that does the
   // load. If aOriginalURI is null, aURI will be set as the originalURI.
   // If aLoadReplace is true, LOAD_REPLACE flag will be set to the nsIChannel.
   nsresult DoURILoad(nsIURI* aURI,
                      nsIURI* aOriginalURI,
                      mozilla::Maybe<nsCOMPtr<nsIURI>> const& aResultPrincipalURI,
                      bool aLoadReplace,
+                     bool aLoadFromExternal,
                      nsIURI* aReferrer,
                      bool aSendReferrer,
                      uint32_t aReferrerPolicy,
                      nsIPrincipal* aTriggeringPrincipal,
                      nsIPrincipal* aPrincipalToInherit,
                      const char* aTypeHint,
                      const nsAString& aFileName,
                      nsIInputStream* aPostData,
--- a/dom/locales/en-US/chrome/security/security.properties
+++ b/dom/locales/en-US/chrome/security/security.properties
@@ -76,8 +76,11 @@ WeakCipherSuiteWarning=This site uses th
 
 #XCTO: nosniff
 # LOCALIZATION NOTE: Do not translate "X-Content-Type-Options: nosniff".
 MimeTypeMismatch=The resource from “%1$S” was blocked due to MIME type mismatch (X-Content-Type-Options: nosniff).
 # LOCALIZATION NOTE: Do not translate "X-Content-Type-Options" and also do not trasnlate "nosniff".
 XCTOHeaderValueMissing=X-Content-Type-Options header warning: value was “%1$S”; did you mean to send “nosniff”?
 
 BlockScriptWithWrongMimeType=Script from “%1$S” was blocked because of a disallowed MIME type.
+
+# LOCALIZATION NOTE: Do not translate "data: URI".
+BlockTopLevelDataURINavigation=Navigation to toplevel data: URI not allowed (Blocked loading of: “%1$S”)
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -5752,16 +5752,22 @@ pref("security.mixed_content.hsts_primin
 
 // TODO: Bug 1324406: Treat 'data:' documents as unique, opaque origins
 // If true, data: URIs will be treated as unique opaque origins, hence will use
 // a NullPrincipal as the security context.
 // Otherwise it will inherit the origin from parent node, this is the legacy
 // behavior of Firefox.
 pref("security.data_uri.unique_opaque_origin", false);
 
+// TODO: Bug 1380959: Block toplevel data: URI navigations
+// If true, all toplevel data: URI navigations will be blocked.
+// Please note that manually entering a data: URI in the
+// URL-Bar will not be blocked when flipping this pref.
+pref("security.data_uri.block_toplevel_data_uri_navigations", false);
+
 // Disable Storage api in release builds.
 #if defined(NIGHTLY_BUILD) && !defined(MOZ_WIDGET_ANDROID)
 pref("dom.storageManager.enabled", true);
 #else
 pref("dom.storageManager.enabled", false);
 #endif
 
 pref("dom.storageManager.prompt.testing", false);
--- a/netwerk/base/nsIOService.cpp
+++ b/netwerk/base/nsIOService.cpp
@@ -162,16 +162,17 @@ static const char kProfileChangeNetTeard
 static const char kProfileChangeNetRestoreTopic[] = "profile-change-net-restore";
 static const char kProfileDoChange[] = "profile-do-change";
 
 // Necko buffer defaults
 uint32_t   nsIOService::gDefaultSegmentSize = 4096;
 uint32_t   nsIOService::gDefaultSegmentCount = 24;
 
 bool nsIOService::sIsDataURIUniqueOpaqueOrigin = false;
+bool nsIOService::sBlockToplevelDataUriNavigations = false;
 
 ////////////////////////////////////////////////////////////////////////////////
 
 nsIOService::nsIOService()
     : mOffline(true)
     , mOfflineForProfileChange(false)
     , mManageLinkStatus(false)
     , mConnectivity(true)
@@ -245,16 +246,18 @@ nsIOService::Init()
         observerService->AddObserver(this, NS_NETWORK_LINK_TOPIC, true);
         observerService->AddObserver(this, NS_WIDGET_WAKE_OBSERVER_TOPIC, true);
     }
     else
         NS_WARNING("failed to get observer service");
 
     Preferences::AddBoolVarCache(&sIsDataURIUniqueOpaqueOrigin,
                                  "security.data_uri.unique_opaque_origin", false);
+    Preferences::AddBoolVarCache(&sBlockToplevelDataUriNavigations,
+                                 "security.data_uri.block_toplevel_data_uri_navigations", false);
     Preferences::AddBoolVarCache(&mOfflineMirrorsConnectivity, OFFLINE_MIRRORS_CONNECTIVITY, true);
 
     gIOService = this;
 
     InitializeNetworkLinkService();
 
     SetOffline(false);
 
@@ -1926,10 +1929,16 @@ nsIOService::SpeculativeAnonymousConnect
 }
 
 /*static*/ bool
 nsIOService::IsDataURIUniqueOpaqueOrigin()
 {
   return sIsDataURIUniqueOpaqueOrigin;
 }
 
+/*static*/ bool
+nsIOService::BlockToplevelDataUriNavigations()
+{
+  return sBlockToplevelDataUriNavigations;
+}
+
 } // namespace net
 } // namespace mozilla
--- a/netwerk/base/nsIOService.h
+++ b/netwerk/base/nsIOService.h
@@ -91,16 +91,17 @@ public:
     // caused problems (bug 1242755) so we doing it in this way.
     // As soon as nsIOService gets notification that it is shutdown it is going to
     // reset mHttpHandlerAlreadyShutingDown.
     void SetHttpHandlerAlreadyShutingDown();
 
     bool IsLinkUp();
 
     static bool IsDataURIUniqueOpaqueOrigin();
+    static bool BlockToplevelDataUriNavigations();
 
     // Used to count the total number of HTTP requests made
     void IncrementRequestNumber() { mTotalRequests++; }
     uint32_t GetTotalRequestNumber() { return mTotalRequests; }
     // Used to keep "race cache with network" stats
     void IncrementCacheWonRequestNumber() { mCacheWon++; }
     uint32_t GetCacheWonRequestNumber() { return mCacheWon; }
     void IncrementNetWonRequestNumber() { mNetWon++; }
@@ -181,16 +182,17 @@ private:
     // cached categories
     nsCategoryCache<nsIChannelEventSink> mChannelEventSinks;
 
     nsTArray<int32_t>                    mRestrictedPortList;
 
     bool                                 mNetworkNotifyChanged;
 
     static bool                          sIsDataURIUniqueOpaqueOrigin;
+    static bool                          sBlockToplevelDataUriNavigations;
 
     uint32_t mTotalRequests;
     uint32_t mCacheWon;
     uint32_t mNetWon;
 
     // These timestamps are needed for collecting telemetry on PR_Connect,
     // PR_ConnectContinue and PR_Close blocking time.  If we spend very long
     // time in any of these functions we want to know if and what network
--- a/toolkit/components/telemetry/Histograms.json
+++ b/toolkit/components/telemetry/Histograms.json
@@ -13390,25 +13390,16 @@
     "alert_emails": ["wmccloskey@mozilla.com"],
     "expires_in_version": "60",
     "kind": "exponential",
     "high": 30000,
     "n_buckets": 30,
     "bug_numbers": [1351021],
     "description": "Every time we run an unlabeled runnable, this histogram records the time (in ms) since the last unlabeled runnable ran."
   },
-  "DOCUMENT_DATA_URI_LOADS": {
-    "record_in_processes": ["main", "content"],
-    "alert_emails": ["seceng-telemetry@mozilla.com"],
-    "bug_numbers": [1357386],
-    "expires_in_version": "60",
-    "kind": "enumerated",
-    "n_values": 3,
-    "description": "Whether a toplevel document uses data: URI? (0=data-navigated, 1=data-typed, 2=nodata)"
-  },
   "VFC_INVALIDATE_LOCK_WAIT_MS": {
     "record_in_processes": ["main", "content"],
     "alert_emails": ["jwwang@mozilla.com"],
     "expires_in_version": "57",
     "kind": "exponential",
     "low": 1000,
     "high": 100000,
     "n_buckets": 100,