tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

nsMixedContentBlocker.cpp (40203B)


      1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
      3 /* This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #include "nsMixedContentBlocker.h"
      8 
      9 #include "mozilla/BasePrincipal.h"
     10 #include "mozilla/LoadInfo.h"
     11 #include "mozilla/Logging.h"
     12 #include "mozilla/Preferences.h"
     13 #include "mozilla/StaticPrefs_dom.h"
     14 #include "mozilla/StaticPrefs_fission.h"
     15 #include "mozilla/StaticPrefs_security.h"
     16 #include "mozilla/dom/BrowsingContext.h"
     17 #include "mozilla/dom/ContentChild.h"
     18 #include "mozilla/dom/Document.h"
     19 #include "mozilla/dom/WindowContext.h"
     20 #include "mozilla/dom/nsHTTPSOnlyUtils.h"
     21 #include "mozilla/glean/DomSecurityMetrics.h"
     22 #include "mozilla/ipc/URIUtils.h"
     23 #include "mozilla/net/DNS.h"
     24 #include "mozilla/net/DocumentChannel.h"
     25 #include "mozilla/net/DocumentLoadListener.h"
     26 #include "nsAsyncRedirectVerifyHelper.h"
     27 #include "nsCOMPtr.h"
     28 #include "nsCSPContext.h"
     29 #include "nsCharSeparatedTokenizer.h"
     30 #include "nsContentPolicyUtils.h"
     31 #include "nsContentUtils.h"
     32 #include "nsDocShell.h"
     33 #include "nsIChannel.h"
     34 #include "nsIChannelEventSink.h"
     35 #include "nsINode.h"
     36 #include "nsIParentChannel.h"
     37 #include "nsIProtocolHandler.h"
     38 #include "nsIScriptError.h"
     39 #include "nsIScriptObjectPrincipal.h"
     40 #include "nsISecureBrowserUI.h"
     41 #include "nsISiteSecurityService.h"
     42 #include "nsIURI.h"
     43 #include "nsIWebNavigation.h"
     44 #include "nsIWebProgressListener.h"
     45 #include "nsLoadGroup.h"
     46 #include "nsNetUtil.h"
     47 #include "nsQueryObject.h"
     48 #include "nsThreadUtils.h"
     49 #include "prnetdb.h"
     50 
     51 using namespace mozilla;
     52 using namespace mozilla::dom;
     53 
     54 static mozilla::LazyLogModule sMCBLog("MCBLog");
     55 
     56 enum nsMixedContentBlockerMessageType { eBlocked = 0x00, eUserOverride = 0x01 };
     57 
     58 // Allowlist of hostnames that should be considered secure contexts even when
     59 // served over http:// or ws://
     60 nsCString* nsMixedContentBlocker::sSecurecontextAllowlist = nullptr;
     61 bool nsMixedContentBlocker::sSecurecontextAllowlistCached = false;
     62 
     63 enum MixedContentHSTSState {
     64  MCB_HSTS_PASSIVE_NO_HSTS = 0,
     65  MCB_HSTS_PASSIVE_WITH_HSTS = 1,
     66  MCB_HSTS_ACTIVE_NO_HSTS = 2,
     67  MCB_HSTS_ACTIVE_WITH_HSTS = 3
     68 };
     69 
     70 nsMixedContentBlocker::~nsMixedContentBlocker() = default;
     71 
     72 NS_IMPL_ISUPPORTS(nsMixedContentBlocker, nsIContentPolicy, nsIChannelEventSink)
     73 
     74 static void LogMixedContentMessage(
     75    MixedContentTypes aClassification, nsIURI* aContentLocation,
     76    uint64_t aInnerWindowID, nsMixedContentBlockerMessageType aMessageType,
     77    nsIURI* aRequestingLocation,
     78    const nsACString& aOverruleMessageLookUpKeyWithThis = ""_ns) {
     79  nsAutoCString messageCategory;
     80  uint32_t severityFlag;
     81  nsAutoCString messageLookupKey;
     82 
     83  if (aMessageType == eBlocked) {
     84    severityFlag = nsIScriptError::errorFlag;
     85    messageCategory.AssignLiteral("Mixed Content Blocker");
     86    if (aClassification == eMixedDisplay) {
     87      messageLookupKey.AssignLiteral("BlockMixedDisplayContent");
     88    } else {
     89      messageLookupKey.AssignLiteral("BlockMixedActiveContent");
     90    }
     91  } else {
     92    severityFlag = nsIScriptError::warningFlag;
     93    messageCategory.AssignLiteral("Mixed Content Message");
     94    if (aClassification == eMixedDisplay) {
     95      messageLookupKey.AssignLiteral("LoadingMixedDisplayContent2");
     96    } else {
     97      messageLookupKey.AssignLiteral("LoadingMixedActiveContent2");
     98    }
     99  }
    100 
    101  // if the callee explicitly wants to use a special message for this
    102  // console report, then we allow to overrule the default with the
    103  // explicitly provided one here.
    104  if (!aOverruleMessageLookUpKeyWithThis.IsEmpty()) {
    105    messageLookupKey = aOverruleMessageLookUpKeyWithThis;
    106  }
    107 
    108  nsAutoString localizedMsg;
    109  AutoTArray<nsString, 1> params;
    110  CopyUTF8toUTF16(aContentLocation->GetSpecOrDefault(),
    111                  *params.AppendElement());
    112  nsContentUtils::FormatLocalizedString(nsContentUtils::eSECURITY_PROPERTIES,
    113                                        messageLookupKey.get(), params,
    114                                        localizedMsg);
    115 
    116  nsContentUtils::ReportToConsoleByWindowID(
    117      localizedMsg, severityFlag, messageCategory, aInnerWindowID,
    118      SourceLocation(aRequestingLocation));
    119 }
    120 
    121 /* nsIChannelEventSink implementation
    122 * This code is called when a request is redirected.
    123 * We check the channel associated with the new uri is allowed to load
    124 * in the current context
    125 */
    126 NS_IMETHODIMP
    127 nsMixedContentBlocker::AsyncOnChannelRedirect(
    128    nsIChannel* aOldChannel, nsIChannel* aNewChannel, uint32_t aFlags,
    129    nsIAsyncVerifyRedirectCallback* aCallback) {
    130  mozilla::net::nsAsyncRedirectAutoCallback autoCallback(aCallback);
    131 
    132  if (!aOldChannel) {
    133    NS_ERROR("No channel when evaluating mixed content!");
    134    return NS_ERROR_FAILURE;
    135  }
    136 
    137  // If we are in the parent process in e10s, we don't have access to the
    138  // document node, and hence ShouldLoad will fail when we try to get
    139  // the docShell.  If that's the case, ignore mixed content checks
    140  // on redirects in the parent.  Let the child check for mixed content.
    141  nsCOMPtr<nsIParentChannel> is_ipc_channel;
    142  NS_QueryNotificationCallbacks(aNewChannel, is_ipc_channel);
    143  RefPtr<net::DocumentLoadListener> docListener =
    144      do_QueryObject(is_ipc_channel);
    145  if (is_ipc_channel && !docListener) {
    146    return NS_OK;
    147  }
    148 
    149  // Don't do these checks if we're switching from DocumentChannel
    150  // to a real channel. In that case, we should already have done
    151  // the checks in the parent process. AsyncOnChannelRedirect
    152  // isn't called in the content process if we switch process,
    153  // so checking here would just hide bugs in the process switch
    154  // cases.
    155  if (RefPtr<net::DocumentChannel> docChannel = do_QueryObject(aOldChannel)) {
    156    return NS_OK;
    157  }
    158 
    159  nsresult rv;
    160  nsCOMPtr<nsIURI> oldUri;
    161  rv = aOldChannel->GetURI(getter_AddRefs(oldUri));
    162  NS_ENSURE_SUCCESS(rv, rv);
    163 
    164  nsCOMPtr<nsIURI> newUri;
    165  rv = aNewChannel->GetURI(getter_AddRefs(newUri));
    166  NS_ENSURE_SUCCESS(rv, rv);
    167 
    168  // Get the loading Info from the old channel
    169  nsCOMPtr<nsILoadInfo> loadInfo = aOldChannel->LoadInfo();
    170  nsCOMPtr<nsIPrincipal> requestingPrincipal = loadInfo->GetLoadingPrincipal();
    171 
    172  // Since we are calling shouldLoad() directly on redirects, we don't go
    173  // through the code in nsContentPolicyUtils::NS_CheckContentLoadPolicy().
    174  // Hence, we have to duplicate parts of it here.
    175  if (requestingPrincipal) {
    176    // We check to see if the loadingPrincipal is systemPrincipal and return
    177    // early if it is
    178    if (requestingPrincipal->IsSystemPrincipal()) {
    179      return NS_OK;
    180    }
    181  }
    182 
    183  int16_t decision = REJECT_REQUEST;
    184  rv = ShouldLoad(newUri, loadInfo, &decision);
    185  if (NS_FAILED(rv)) {
    186    autoCallback.DontCallback();
    187    aOldChannel->Cancel(NS_ERROR_DOM_BAD_URI);
    188    return NS_BINDING_FAILED;
    189  }
    190 
    191  // If the channel is about to load mixed content, abort the channel
    192  if (!NS_CP_ACCEPTED(decision)) {
    193    autoCallback.DontCallback();
    194    aOldChannel->Cancel(NS_ERROR_DOM_BAD_URI);
    195    return NS_BINDING_FAILED;
    196  }
    197 
    198  return NS_OK;
    199 }
    200 
    201 /* This version of ShouldLoad() is non-static and called by the Content Policy
    202 * API and AsyncOnChannelRedirect().  See nsIContentPolicy::ShouldLoad()
    203 * for detailed description of the parameters.
    204 */
    205 NS_IMETHODIMP
    206 nsMixedContentBlocker::ShouldLoad(nsIURI* aContentLocation,
    207                                  nsILoadInfo* aLoadInfo, int16_t* aDecision) {
    208  // We pass in false as the first parameter to ShouldLoad(), because the
    209  // callers of this method don't know whether the load went through cached
    210  // image redirects.  This is handled by direct callers of the static
    211  // ShouldLoad.
    212  nsresult rv = ShouldLoad(false,  // aHadInsecureImageRedirect
    213                           aContentLocation, aLoadInfo, true, aDecision);
    214 
    215  if (*aDecision == nsIContentPolicy::REJECT_REQUEST) {
    216    NS_SetRequestBlockingReason(aLoadInfo,
    217                                nsILoadInfo::BLOCKING_REASON_MIXED_BLOCKED);
    218  }
    219 
    220  return rv;
    221 }
    222 
    223 bool nsMixedContentBlocker::IsPotentiallyTrustworthyLoopbackHost(
    224    const nsACString& aAsciiHost) {
    225  if (mozilla::net::IsLoopbackHostname(aAsciiHost)) {
    226    return true;
    227  }
    228 
    229  using namespace mozilla::net;
    230  NetAddr addr;
    231  if (NS_FAILED(addr.InitFromString(aAsciiHost))) {
    232    return false;
    233  }
    234 
    235  // Step 4 of
    236  // https://w3c.github.io/webappsec-secure-contexts/#is-origin-trustworthy says
    237  // we should only consider [::1]/128 as a potentially trustworthy IPv6
    238  // address, whereas for IPv4 127.0.0.1/8 are considered as potentially
    239  // trustworthy.
    240  return addr.IsLoopBackAddressWithoutIPv6Mapping();
    241 }
    242 
    243 bool nsMixedContentBlocker::IsPotentiallyTrustworthyLoopbackURL(nsIURI* aURL) {
    244  if (!aURL) {
    245    return false;
    246  }
    247  nsAutoCString asciiHost;
    248  nsresult rv = aURL->GetAsciiHost(asciiHost);
    249  NS_ENSURE_SUCCESS(rv, false);
    250  return IsPotentiallyTrustworthyLoopbackHost(asciiHost);
    251 }
    252 
    253 /* Maybe we have a .onion URL. Treat it as trustworthy as well if
    254 * `dom.securecontext.allowlist_onions` is `true`.
    255 */
    256 bool nsMixedContentBlocker::IsPotentiallyTrustworthyOnion(nsIURI* aURL) {
    257  if (!StaticPrefs::dom_securecontext_allowlist_onions()) {
    258    return false;
    259  }
    260 
    261  nsAutoCString host;
    262  nsresult rv = aURL->GetHost(host);
    263  NS_ENSURE_SUCCESS(rv, false);
    264  return StringEndsWith(host, ".onion"_ns);
    265 }
    266 
    267 // static
    268 void nsMixedContentBlocker::OnPrefChange(const char* aPref, void* aClosure) {
    269  MOZ_ASSERT(NS_IsMainThread());
    270  MOZ_ASSERT(!strcmp(aPref, "dom.securecontext.allowlist"));
    271  Preferences::GetCString("dom.securecontext.allowlist",
    272                          *sSecurecontextAllowlist);
    273 }
    274 
    275 // static
    276 void nsMixedContentBlocker::GetSecureContextAllowList(nsACString& aList) {
    277  MOZ_ASSERT(NS_IsMainThread());
    278  if (!sSecurecontextAllowlistCached) {
    279    MOZ_ASSERT(!sSecurecontextAllowlist);
    280    sSecurecontextAllowlistCached = true;
    281    sSecurecontextAllowlist = new nsCString();
    282    Preferences::RegisterCallbackAndCall(OnPrefChange,
    283                                         "dom.securecontext.allowlist");
    284  }
    285  aList = *sSecurecontextAllowlist;
    286 }
    287 
    288 // static
    289 void nsMixedContentBlocker::Shutdown() {
    290  if (sSecurecontextAllowlist) {
    291    delete sSecurecontextAllowlist;
    292    sSecurecontextAllowlist = nullptr;
    293  }
    294 }
    295 
    296 bool nsMixedContentBlocker::IsPotentiallyTrustworthyOrigin(nsIURI* aURI) {
    297  // The following implements:
    298  // https://w3c.github.io/webappsec-secure-contexts/#is-origin-trustworthy
    299 
    300  nsAutoCString scheme;
    301  nsresult rv = aURI->GetScheme(scheme);
    302  if (NS_FAILED(rv)) {
    303    return false;
    304  }
    305 
    306  // Blobs are expected to inherit their principal so we don't expect to have
    307  // a content principal with scheme 'blob' here.  We can't assert that though
    308  // since someone could mess with a non-blob URI to give it that scheme.
    309  NS_WARNING_ASSERTION(!scheme.EqualsLiteral("blob"),
    310                       "IsOriginPotentiallyTrustworthy ignoring blob scheme");
    311 
    312  // According to the specification, the user agent may choose to extend the
    313  // trust to other, vendor-specific URL schemes. We use this for "resource:",
    314  // which is technically a substituting protocol handler that is not limited to
    315  // local resource mapping, but in practice is never mapped remotely as this
    316  // would violate assumptions a lot of code makes.
    317  // We use nsIProtocolHandler flags to determine which protocols we consider a
    318  // priori authenticated.
    319  bool aPrioriAuthenticated = false;
    320  if (NS_FAILED(NS_URIChainHasFlags(
    321          aURI, nsIProtocolHandler::URI_IS_POTENTIALLY_TRUSTWORTHY,
    322          &aPrioriAuthenticated))) {
    323    return false;
    324  }
    325 
    326  if (aPrioriAuthenticated) {
    327    return true;
    328  }
    329 
    330  nsAutoCString host;
    331  rv = aURI->GetHost(host);
    332  if (NS_FAILED(rv)) {
    333    return false;
    334  }
    335 
    336  if (IsPotentiallyTrustworthyLoopbackURL(aURI)) {
    337    return true;
    338  }
    339 
    340  // If a host is not considered secure according to the default algorithm, then
    341  // check to see if it has been allowlisted by the user.  We only apply this
    342  // allowlist for network resources, i.e., those with scheme "http" or "ws".
    343  // The pref should contain a comma-separated list of hostnames.
    344 
    345  if (!scheme.EqualsLiteral("http") && !scheme.EqualsLiteral("ws")) {
    346    return false;
    347  }
    348 
    349  nsAutoCString allowlist;
    350  GetSecureContextAllowList(allowlist);
    351  for (const nsACString& allowedHost :
    352       nsCCharSeparatedTokenizer(allowlist, ',').ToRange()) {
    353    if (host.Equals(allowedHost)) {
    354      return true;
    355    }
    356  }
    357 
    358  // Maybe we have a .onion URL. Treat it as trustworthy as well if
    359  // `dom.securecontext.allowlist_onions` is `true`.
    360  if (nsMixedContentBlocker::IsPotentiallyTrustworthyOnion(aURI)) {
    361    return true;
    362  }
    363  return false;
    364 }
    365 
    366 /* static */
    367 bool nsMixedContentBlocker::IsUpgradableContentType(nsContentPolicyType aType) {
    368  MOZ_ASSERT(NS_IsMainThread());
    369 
    370  switch (aType) {
    371    case nsIContentPolicy::TYPE_INTERNAL_IMAGE:
    372    case nsIContentPolicy::TYPE_INTERNAL_IMAGE_PRELOAD:
    373    case nsIContentPolicy::TYPE_INTERNAL_IMAGE_FAVICON:
    374    case nsIContentPolicy::TYPE_INTERNAL_AUDIO:
    375    case nsIContentPolicy::TYPE_INTERNAL_VIDEO:
    376      return true;
    377    default:
    378      return false;
    379  }
    380 }
    381 
    382 /*
    383 * Return the URI of the precusor principal or the URI of aPrincipal if there is
    384 * no precursor URI.
    385 */
    386 static already_AddRefed<nsIURI> GetPrincipalURIOrPrecursorPrincipalURI(
    387    nsIPrincipal* aPrincipal) {
    388  nsCOMPtr<nsIPrincipal> precursorPrincipal =
    389      aPrincipal->GetPrecursorPrincipal();
    390 
    391 #ifdef DEBUG
    392  if (precursorPrincipal) {
    393    MOZ_ASSERT(aPrincipal->GetIsNullPrincipal(),
    394               "Only Null Principals should have a Precursor Principal");
    395  }
    396 #endif
    397 
    398  return precursorPrincipal ? precursorPrincipal->GetURI()
    399                            : aPrincipal->GetURI();
    400 }
    401 
    402 /* Static version of ShouldLoad() that contains all the Mixed Content Blocker
    403 * logic.  Called from non-static ShouldLoad().
    404 */
    405 nsresult nsMixedContentBlocker::ShouldLoad(bool aHadInsecureImageRedirect,
    406                                           nsIURI* aContentLocation,
    407                                           nsILoadInfo* aLoadInfo,
    408                                           bool aReportError,
    409                                           int16_t* aDecision) {
    410  // Asserting that we are on the main thread here and hence do not have to lock
    411  // and unlock security.mixed_content.block_active_content and
    412  // security.mixed_content.block_display_content before reading/writing to
    413  // them.
    414  MOZ_ASSERT(NS_IsMainThread());
    415 
    416  if (MOZ_UNLIKELY(MOZ_LOG_TEST(sMCBLog, LogLevel::Verbose))) {
    417    nsAutoCString asciiUrl;
    418    aContentLocation->GetAsciiSpec(asciiUrl);
    419    MOZ_LOG(sMCBLog, LogLevel::Verbose, ("shouldLoad:"));
    420    MOZ_LOG(sMCBLog, LogLevel::Verbose,
    421            ("  - contentLocation: %s", asciiUrl.get()));
    422  }
    423 
    424  nsContentPolicyType internalContentType =
    425      aLoadInfo->InternalContentPolicyType();
    426  nsCOMPtr<nsIPrincipal> loadingPrincipal = aLoadInfo->GetLoadingPrincipal();
    427  nsCOMPtr<nsIPrincipal> triggeringPrincipal = aLoadInfo->TriggeringPrincipal();
    428 
    429  if (MOZ_UNLIKELY(MOZ_LOG_TEST(sMCBLog, LogLevel::Verbose))) {
    430    MOZ_LOG(sMCBLog, LogLevel::Verbose,
    431            ("  - internalContentPolicyType: %s",
    432             NS_CP_ContentTypeName(internalContentType)));
    433 
    434    if (loadingPrincipal != nullptr) {
    435      nsAutoCString loadingPrincipalAsciiUrl;
    436      loadingPrincipal->GetAsciiSpec(loadingPrincipalAsciiUrl);
    437      MOZ_LOG(sMCBLog, LogLevel::Verbose,
    438              ("  - loadingPrincipal: %s", loadingPrincipalAsciiUrl.get()));
    439    } else {
    440      MOZ_LOG(sMCBLog, LogLevel::Verbose, ("  - loadingPrincipal: (nullptr)"));
    441    }
    442 
    443    nsAutoCString triggeringPrincipalAsciiUrl;
    444    triggeringPrincipal->GetAsciiSpec(triggeringPrincipalAsciiUrl);
    445    MOZ_LOG(sMCBLog, LogLevel::Verbose,
    446            ("  - triggeringPrincipal: %s", triggeringPrincipalAsciiUrl.get()));
    447  }
    448 
    449  RefPtr<WindowContext> requestingWindow =
    450      WindowContext::GetById(aLoadInfo->GetInnerWindowID());
    451 
    452  bool isPreload = nsContentUtils::IsPreloadType(internalContentType);
    453 
    454  // The content policy type that we receive may be an internal type for
    455  // scripts.  Let's remember if we have seen a worker type, and reset it to the
    456  // external type in all cases right now.
    457  bool isWorkerType =
    458      internalContentType == nsIContentPolicy::TYPE_INTERNAL_WORKER ||
    459      internalContentType ==
    460          nsIContentPolicy::TYPE_INTERNAL_WORKER_STATIC_MODULE ||
    461      internalContentType == nsIContentPolicy::TYPE_INTERNAL_SHARED_WORKER ||
    462      internalContentType == nsIContentPolicy::TYPE_INTERNAL_SERVICE_WORKER;
    463  ExtContentPolicyType contentType =
    464      nsContentUtils::InternalContentPolicyTypeToExternal(internalContentType);
    465 
    466  // Assume active (high risk) content and blocked by default
    467  MixedContentTypes classification = eMixedScript;
    468  // Make decision to block/reject by default
    469  *aDecision = REJECT_REQUEST;
    470 
    471  // Notes on non-obvious decisions:
    472  //
    473  // TYPE_DTD: A DTD can contain entity definitions that expand to scripts.
    474  //
    475  // TYPE_FONT: The TrueType hinting mechanism is basically a scripting
    476  // language that gets interpreted by the operating system's font rasterizer.
    477  // Mixed content web fonts are relatively uncommon, and we can can fall back
    478  // to built-in fonts with minimal disruption in almost all cases.
    479  //
    480  // TYPE_CSP_REPORT: High-risk because they directly leak information about
    481  // the content of the page, and because blocking them does not have any
    482  // negative effect on the page loading.
    483  //
    484  // TYPE_PING: Ping requests are POSTS, not GETs like images and media.
    485  // Also, PING requests have no bearing on the rendering or operation of
    486  // the page when used as designed, so even though they are lower risk than
    487  // scripts, blocking them is basically risk-free as far as compatibility is
    488  // concerned.
    489  //
    490  // TYPE_STYLESHEET: XSLT stylesheets can insert scripts. CSS positioning
    491  // and other advanced CSS features can possibly be exploited to cause
    492  // spoofing attacks (e.g. make a "grant permission" button look like a
    493  // "refuse permission" button).
    494  //
    495  // TYPE_BEACON: Beacon requests are similar to TYPE_PING, and are blocked by
    496  // default.
    497  //
    498  // TYPE_WEBSOCKET: The Websockets API requires browsers to
    499  // reject mixed-content websockets: "If secure is false but the origin of
    500  // the entry script has a scheme component that is itself a secure protocol,
    501  // e.g. HTTPS, then throw a SecurityError exception." We already block mixed
    502  // content websockets within the websockets implementation, so we don't need
    503  // to do any blocking here, nor do we need to provide a way to undo or
    504  // override the blocking. Websockets without TLS are very flaky anyway in the
    505  // face of many HTTP-aware proxies. Compared to passive content, there is
    506  // additional risk that the script using WebSockets will disclose sensitive
    507  // information from the HTTPS page and/or eval (directly or indirectly)
    508  // received data.
    509  //
    510  // TYPE_XMLHTTPREQUEST: XHR requires either same origin or CORS, so most
    511  // mixed-content XHR will already be blocked by that check. This will also
    512  // block HTTPS-to-HTTP XHR with CORS. The same security concerns mentioned
    513  // above for WebSockets apply to XHR, and XHR should have the same security
    514  // properties as WebSockets w.r.t. mixed content. XHR's handling of redirects
    515  // amplifies these concerns.
    516  //
    517  // TYPE_PROXIED_WEBRTC_MEDIA: Ordinarily, webrtc uses low-level sockets for
    518  // peer-to-peer media, which bypasses this code entirely. However, when a
    519  // web proxy is being used, the TCP and TLS webrtc connections are routed
    520  // through the web proxy (using HTTP CONNECT), which causes these connections
    521  // to be checked. We just skip mixed content blocking in that case.
    522 
    523  switch (contentType) {
    524    // The top-level document cannot be mixed content by definition
    525    case ExtContentPolicy::TYPE_DOCUMENT:
    526      *aDecision = ACCEPT;
    527      return NS_OK;
    528    // Creating insecure websocket connections in a secure page is blocked
    529    // already in the websocket constructor. We don't need to check the blocking
    530    // here and we don't want to un-block
    531    case ExtContentPolicy::TYPE_WEBSOCKET:
    532      *aDecision = ACCEPT;
    533      return NS_OK;
    534 
    535      // TYPE_SAVEAS_DOWNLOAD: Save-link-as feature is used to download a
    536      // resource
    537      // without involving a docShell. This kind of loading must be
    538      // allowed, if not disabled in the preferences.
    539      // Creating insecure connections for a save-as link download is
    540      // acceptable. This download is completely disconnected from the docShell,
    541      // but still using the same loading principal.
    542 
    543    case ExtContentPolicy::TYPE_SAVEAS_DOWNLOAD:
    544      *aDecision = ACCEPT;
    545      return NS_OK;
    546      break;
    547 
    548    // It does not make sense to subject webrtc media connections to mixed
    549    // content blocking, since those connections are peer-to-peer and will
    550    // therefore almost never match the origin.
    551    case ExtContentPolicy::TYPE_PROXIED_WEBRTC_MEDIA:
    552      *aDecision = ACCEPT;
    553      return NS_OK;
    554 
    555    // Static display content is considered moderate risk for mixed content so
    556    // these will be blocked according to the mixed display preference
    557    case ExtContentPolicy::TYPE_IMAGE:
    558    case ExtContentPolicy::TYPE_MEDIA:
    559      classification = eMixedDisplay;
    560      break;
    561 
    562    // Active content (or content with a low value/risk-of-blocking ratio)
    563    // that has been explicitly evaluated; listed here for documentation
    564    // purposes and to avoid the assertion and warning for the default case.
    565    case ExtContentPolicy::TYPE_BEACON:
    566    case ExtContentPolicy::TYPE_CSP_REPORT:
    567    case ExtContentPolicy::TYPE_DTD:
    568    case ExtContentPolicy::TYPE_FETCH:
    569    case ExtContentPolicy::TYPE_FONT:
    570    case ExtContentPolicy::TYPE_UA_FONT:
    571    case ExtContentPolicy::TYPE_IMAGESET:
    572    case ExtContentPolicy::TYPE_OBJECT:
    573    case ExtContentPolicy::TYPE_SCRIPT:
    574    case ExtContentPolicy::TYPE_STYLESHEET:
    575    case ExtContentPolicy::TYPE_SUBDOCUMENT:
    576    case ExtContentPolicy::TYPE_PING:
    577    case ExtContentPolicy::TYPE_WEB_MANIFEST:
    578    case ExtContentPolicy::TYPE_XMLHTTPREQUEST:
    579    case ExtContentPolicy::TYPE_XSLT:
    580    case ExtContentPolicy::TYPE_OTHER:
    581    case ExtContentPolicy::TYPE_SPECULATIVE:
    582    case ExtContentPolicy::TYPE_WEB_TRANSPORT:
    583    case ExtContentPolicy::TYPE_WEB_IDENTITY:
    584    case ExtContentPolicy::TYPE_JSON:
    585      break;
    586 
    587    case ExtContentPolicy::TYPE_INVALID:
    588      MOZ_ASSERT(false, "Mixed content of unknown type");
    589      // Do not add default: so that compilers can catch the missing case.
    590  }
    591 
    592  // Make sure to get the URI the load started with. No need to check
    593  // outer schemes because all the wrapping pseudo protocols inherit the
    594  // security properties of the actual network request represented
    595  // by the innerMost URL.
    596  nsCOMPtr<nsIURI> innerContentLocation = NS_GetInnermostURI(aContentLocation);
    597  if (!innerContentLocation) {
    598    NS_ERROR("Can't get innerURI from aContentLocation");
    599    *aDecision = REJECT_REQUEST;
    600    MOZ_LOG(sMCBLog, LogLevel::Verbose,
    601            ("  -> decision: Request will be rejected because the innermost "
    602             "URI could not be "
    603             "retrieved"));
    604    return NS_OK;
    605  }
    606 
    607  // TYPE_IMAGE redirects are cached based on the original URI, not the final
    608  // destination and hence cache hits for images may not have the correct
    609  // innerContentLocation.  Check if the cached hit went through an http
    610  // redirect, and if it did, we can't treat this as a secure subresource.
    611  if (!aHadInsecureImageRedirect &&
    612      URISafeToBeLoadedInSecureContext(innerContentLocation)) {
    613    *aDecision = ACCEPT;
    614    return NS_OK;
    615  }
    616 
    617  /*
    618   * Most likely aLoadingPrincipal reflects the security context of the owning
    619   * document for this mixed content check. There are cases where that is not
    620   * true, hence we have to we process requests in the following order:
    621   * 1) If the load is triggered by the SystemPrincipal, we allow the load.
    622   *    Content scripts from addon code do provide aTriggeringPrincipal, which
    623   *    is an ExpandedPrincipal. If encountered, we allow the load.
    624   * 2) If aLoadingPrincipal does not yield to a requestingLocation, then we
    625   *    fall back to querying the requestingLocation from aTriggeringPrincipal.
    626   * 3) If we still end up not having a requestingLocation, we reject the load.
    627   */
    628 
    629  // 1) Check if the load was triggered by the system (SystemPrincipal) or
    630  // a content script from addons code (ExpandedPrincipal) in which case the
    631  // load is not subject to mixed content blocking.
    632  if (triggeringPrincipal) {
    633    if (triggeringPrincipal->IsSystemPrincipal()) {
    634      *aDecision = ACCEPT;
    635      return NS_OK;
    636    }
    637    nsCOMPtr<nsIExpandedPrincipal> expanded =
    638        do_QueryInterface(triggeringPrincipal);
    639    if (expanded) {
    640      *aDecision = ACCEPT;
    641      return NS_OK;
    642    }
    643  }
    644 
    645  // 2) If aLoadingPrincipal does not provide a requestingLocation, then
    646  // we fall back to to querying the requestingLocation from
    647  // aTriggeringPrincipal.
    648  nsCOMPtr<nsIURI> requestingLocation =
    649      GetPrincipalURIOrPrecursorPrincipalURI(loadingPrincipal);
    650  if (!requestingLocation) {
    651    requestingLocation =
    652        GetPrincipalURIOrPrecursorPrincipalURI(triggeringPrincipal);
    653  }
    654 
    655  // 3) Giving up. We still don't have a requesting location, therefore we can't
    656  // tell if this is a mixed content load. Deny to be safe.
    657  if (!requestingLocation) {
    658    *aDecision = REJECT_REQUEST;
    659    MOZ_LOG(sMCBLog, LogLevel::Verbose,
    660            ("  -> decision: Request will be rejected because no requesting "
    661             "location could be "
    662             "gathered."));
    663    return NS_OK;
    664  }
    665 
    666  // Check the parent scheme. If it is not an HTTPS or .onion page then mixed
    667  // content restrictions do not apply.
    668  nsCOMPtr<nsIURI> innerRequestingLocation =
    669      NS_GetInnermostURI(requestingLocation);
    670  if (!innerRequestingLocation) {
    671    NS_ERROR("Can't get innerURI from requestingLocation");
    672    *aDecision = REJECT_REQUEST;
    673    MOZ_LOG(sMCBLog, LogLevel::Verbose,
    674            ("  -> decision: Request will be rejected because the innermost "
    675             "URI of the "
    676             "requesting location could be gathered."));
    677    return NS_OK;
    678  }
    679 
    680  bool parentIsHttps = innerRequestingLocation->SchemeIs("https");
    681  if (!parentIsHttps) {
    682    bool parentIsOnion = IsPotentiallyTrustworthyOnion(innerRequestingLocation);
    683    if (!parentIsOnion) {
    684      *aDecision = ACCEPT;
    685      return NS_OK;
    686    }
    687  }
    688 
    689  bool isHttpScheme = innerContentLocation->SchemeIs("http");
    690  // .onion URLs are encrypted and authenticated. Don't treat them as mixed
    691  // content if potentially trustworthy (i.e. whitelisted).
    692  if (isHttpScheme && IsPotentiallyTrustworthyOnion(innerContentLocation)) {
    693    *aDecision = ACCEPT;
    694    MOZ_LOG(sMCBLog, LogLevel::Verbose,
    695            ("  -> decision: Request will be allowed because the requesting "
    696             "location is not using "
    697             "HTTPS."));
    698    return NS_OK;
    699  }
    700 
    701  // Disallow mixed content loads for workers, shared workers and service
    702  // workers.
    703  if (isWorkerType) {
    704    // For workers, we can assume that we're mixed content at this point, since
    705    // the parent is https, and the protocol associated with
    706    // innerContentLocation doesn't map to the secure URI flags checked above.
    707    // Assert this for sanity's sake
    708 #ifdef DEBUG
    709    bool isHttpsScheme = innerContentLocation->SchemeIs("https");
    710    MOZ_ASSERT(!isHttpsScheme);
    711 #endif
    712    *aDecision = REJECT_REQUEST;
    713    MOZ_LOG(sMCBLog, LogLevel::Verbose,
    714            ("  -> decision: Request will be rejected, trying to load a worker "
    715             "from an insecure origin."));
    716    return NS_OK;
    717  }
    718 
    719  if (isHttpScheme && IsPotentiallyTrustworthyOrigin(innerContentLocation)) {
    720    *aDecision = ACCEPT;
    721    return NS_OK;
    722  }
    723 
    724  // Check if https-only mode upgrades this later anyway
    725  if (nsHTTPSOnlyUtils::IsSafeToAcceptCORSOrMixedContent(aLoadInfo)) {
    726    *aDecision = ACCEPT;
    727    return NS_OK;
    728  }
    729 
    730  // The page might have set the CSP directive 'upgrade-insecure-requests'. In
    731  // such a case allow the http: load to succeed with the promise that the
    732  // channel will get upgraded to https before fetching any data from the
    733  // netwerk. Please see: nsHttpChannel::Connect()
    734  //
    735  // Please note that the CSP directive 'upgrade-insecure-requests' only applies
    736  // to http: and ws: (for websockets). Websockets are not subject to mixed
    737  // content blocking since insecure websockets are not allowed within secure
    738  // pages. Hence, we only have to check against http: here. Skip mixed content
    739  // blocking if the subresource load uses http: and the CSP directive
    740  // 'upgrade-insecure-requests' is present on the page.
    741 
    742  // Carve-out: if we're in the parent and we're loading media, e.g. through
    743  // webbrowserpersist, don't reject it if we can't find a docshell.
    744  if (XRE_IsParentProcess() && !requestingWindow &&
    745      (contentType == ExtContentPolicy::TYPE_IMAGE ||
    746       contentType == ExtContentPolicy::TYPE_MEDIA)) {
    747    *aDecision = ACCEPT;
    748    return NS_OK;
    749  }
    750  // Otherwise, we must have a window
    751  NS_ENSURE_TRUE(requestingWindow, NS_OK);
    752 
    753  if (isHttpScheme && aLoadInfo->GetUpgradeInsecureRequests()) {
    754    *aDecision = ACCEPT;
    755    return NS_OK;
    756  }
    757 
    758  // Allow http: mixed content if we are choosing to upgrade them when the
    759  // pref "security.mixed_content.upgrade_display_content" is true.
    760  // This behaves like GetUpgradeInsecureRequests above in that the channel will
    761  // be upgraded to https before fetching any data from the netwerk.
    762  if (isHttpScheme) {
    763    bool isUpgradableContentType =
    764        StaticPrefs::security_mixed_content_upgrade_display_content() &&
    765        IsUpgradableContentType(internalContentType);
    766    if (isUpgradableContentType) {
    767      *aDecision = ACCEPT;
    768      return NS_OK;
    769    }
    770  }
    771 
    772  // The page might have set the CSP directive 'block-all-mixed-content' which
    773  // should block not only active mixed content loads but in fact all mixed
    774  // content loads, see https://www.w3.org/TR/mixed-content/#strict-checking
    775  // Block all non secure loads in case the CSP directive is present. Please
    776  // note that at this point we already know, based on |schemeSecure| that the
    777  // load is not secure, so we can bail out early at this point.
    778  if (aLoadInfo->GetBlockAllMixedContent()) {
    779    // log a message to the console before returning.
    780    nsAutoCString spec;
    781    nsresult rv = aContentLocation->GetSpec(spec);
    782    NS_ENSURE_SUCCESS(rv, rv);
    783 
    784    AutoTArray<nsString, 1> params;
    785    CopyUTF8toUTF16(spec, *params.AppendElement());
    786 
    787    CSP_LogLocalizedStr("blockAllMixedContent", params,
    788                        ""_ns,   // aSourceFile
    789                        u""_ns,  // aScriptSample
    790                        0,       // aLineNumber
    791                        1,       // aColumnNumber
    792                        nsIScriptError::errorFlag, "blockAllMixedContent"_ns,
    793                        requestingWindow->Id(),
    794                        aLoadInfo->GetOriginAttributes().IsPrivateBrowsing());
    795    *aDecision = REJECT_REQUEST;
    796    MOZ_LOG(
    797        sMCBLog, LogLevel::Verbose,
    798        ("  -> decision: Request will be rejected because the CSP directive "
    799         "'block-all-mixed-content' was set while trying to load data from "
    800         "a non-secure origin."));
    801    return NS_OK;
    802  }
    803 
    804  // Determine if the rootDoc is https and if the user decided to allow Mixed
    805  // Content
    806  WindowContext* topWC = requestingWindow->TopWindowContext();
    807  bool rootHasSecureConnection = topWC->GetIsSecure();
    808 
    809  // When navigating an iframe, the iframe may be https but its parents may not
    810  // be. Check the parents to see if any of them are https. If none of the
    811  // parents are https, allow the load.
    812  if (contentType == ExtContentPolicyType::TYPE_SUBDOCUMENT &&
    813      !rootHasSecureConnection && !parentIsHttps) {
    814    bool httpsParentExists = false;
    815 
    816    RefPtr<WindowContext> curWindow = requestingWindow;
    817    while (!httpsParentExists && curWindow) {
    818      httpsParentExists = curWindow->GetIsSecure();
    819      curWindow = curWindow->GetParentWindowContext();
    820    }
    821 
    822    if (!httpsParentExists) {
    823      *aDecision = nsIContentPolicy::ACCEPT;
    824      return NS_OK;
    825    }
    826  }
    827 
    828  OriginAttributes originAttributes;
    829  if (loadingPrincipal) {
    830    originAttributes = loadingPrincipal->OriginAttributesRef();
    831  } else if (triggeringPrincipal) {
    832    originAttributes = triggeringPrincipal->OriginAttributesRef();
    833  }
    834 
    835  // At this point we know that the request is mixed content, and the only
    836  // question is whether we block it.  Record telemetry at this point as to
    837  // whether HSTS would have fixed things by making the content location
    838  // into an HTTPS URL.
    839  //
    840  // Note that we count this for redirects as well as primary requests. This
    841  // will cause some degree of double-counting, especially when mixed content
    842  // is not blocked (e.g., for images).  For more detail, see:
    843  //   https://bugzilla.mozilla.org/show_bug.cgi?id=1198572#c19
    844  //
    845  // We do not count requests aHadInsecureImageRedirect=true, since these are
    846  // just an artifact of the image caching system.
    847  bool active = (classification == eMixedScript);
    848  if (!aHadInsecureImageRedirect) {
    849    if (XRE_IsParentProcess()) {
    850      AccumulateMixedContentHSTS(innerContentLocation, active,
    851                                 originAttributes);
    852    } else {
    853      // Ask the parent process to do the same call
    854      mozilla::dom::ContentChild* cc =
    855          mozilla::dom::ContentChild::GetSingleton();
    856      if (cc) {
    857        cc->SendAccumulateMixedContentHSTS(innerContentLocation, active,
    858                                           originAttributes);
    859      }
    860    }
    861  }
    862 
    863  uint32_t newState = 0;
    864  // If the content is display content, and the pref says display content should
    865  // be blocked, block it.
    866  if (classification == eMixedDisplay) {
    867    if (!StaticPrefs::security_mixed_content_block_display_content()) {
    868      *aDecision = nsIContentPolicy::ACCEPT;
    869      // User has overriden the pref and the root is not https;
    870      // mixed display content was allowed on an https subframe.
    871      newState |= nsIWebProgressListener::STATE_LOADED_MIXED_DISPLAY_CONTENT;
    872    } else {
    873      *aDecision = nsIContentPolicy::REJECT_REQUEST;
    874      MOZ_LOG(sMCBLog, LogLevel::Verbose,
    875              ("  -> decision: Request will be rejected because the content is "
    876               "display "
    877               "content (blocked by pref "
    878               "security.mixed_content.block_display_content)."));
    879      newState |= nsIWebProgressListener::STATE_BLOCKED_MIXED_DISPLAY_CONTENT;
    880    }
    881  } else {
    882    MOZ_ASSERT(classification == eMixedScript);
    883    // If the content is active content, and the pref says active content should
    884    // be blocked, block it unless the user has choosen to override the pref
    885    if (!StaticPrefs::security_mixed_content_block_active_content()) {
    886      *aDecision = nsIContentPolicy::ACCEPT;
    887      // User has already overriden the pref and the root is not https;
    888      // mixed active content was allowed on an https subframe.
    889      newState |= nsIWebProgressListener::STATE_LOADED_MIXED_ACTIVE_CONTENT;
    890    } else {
    891      // User has not overriden the pref by Disabling protection. Reject the
    892      // request and update the security state.
    893      *aDecision = nsIContentPolicy::REJECT_REQUEST;
    894      MOZ_LOG(sMCBLog, LogLevel::Verbose,
    895              ("  -> decision: Request will be rejected because the content is "
    896               "active "
    897               "content (blocked by pref "
    898               "security.mixed_content.block_active_content)."));
    899      // The user has not overriden the pref, so make sure they still have an
    900      // option by calling nativeDocShell which will invoke the doorhanger
    901      newState |= nsIWebProgressListener::STATE_BLOCKED_MIXED_ACTIVE_CONTENT;
    902    }
    903  }
    904 
    905  // To avoid duplicate errors on the console, we do not report blocked
    906  // preloads to the console.
    907  if (!isPreload && aReportError) {
    908    LogMixedContentMessage(classification, aContentLocation, topWC->Id(),
    909                           (*aDecision == nsIContentPolicy::REJECT_REQUEST)
    910                               ? eBlocked
    911                               : eUserOverride,
    912                           requestingLocation);
    913  }
    914 
    915  // Notify the top WindowContext of the flags we've computed, and it
    916  // will handle updating any relevant security UI.
    917  topWC->AddSecurityState(newState);
    918  return NS_OK;
    919 }
    920 
    921 bool nsMixedContentBlocker::URISafeToBeLoadedInSecureContext(nsIURI* aURI) {
    922  /* Returns a bool if the URI can be loaded as a sub resource safely.
    923   *
    924   * Check Protocol Flags to determine if scheme is safe to load:
    925   * URI_DOES_NOT_RETURN_DATA - e.g.
    926   *   "mailto"
    927   * URI_IS_LOCAL_RESOURCE - e.g.
    928   *   "data",
    929   *   "resource",
    930   *   "moz-icon"
    931   * URI_INHERITS_SECURITY_CONTEXT - e.g.
    932   *   "javascript"
    933   * URI_IS_POTENTIALLY_TRUSTWORTHY - e.g.
    934   *   "https",
    935   *   "moz-safe-about"
    936   *
    937   */
    938  bool schemeLocal = false;
    939  bool schemeNoReturnData = false;
    940  bool schemeInherits = false;
    941  bool schemeSecure = false;
    942  if (NS_FAILED(NS_URIChainHasFlags(
    943          aURI, nsIProtocolHandler::URI_IS_LOCAL_RESOURCE, &schemeLocal)) ||
    944      NS_FAILED(NS_URIChainHasFlags(
    945          aURI, nsIProtocolHandler::URI_DOES_NOT_RETURN_DATA,
    946          &schemeNoReturnData)) ||
    947      NS_FAILED(NS_URIChainHasFlags(
    948          aURI, nsIProtocolHandler::URI_INHERITS_SECURITY_CONTEXT,
    949          &schemeInherits)) ||
    950      NS_FAILED(NS_URIChainHasFlags(
    951          aURI, nsIProtocolHandler::URI_IS_POTENTIALLY_TRUSTWORTHY,
    952          &schemeSecure))) {
    953    return false;
    954  }
    955 
    956  MOZ_LOG(sMCBLog, LogLevel::Verbose,
    957          ("  - URISafeToBeLoadedInSecureContext:"));
    958  MOZ_LOG(sMCBLog, LogLevel::Verbose, ("    - schemeLocal: %i", schemeLocal));
    959  MOZ_LOG(sMCBLog, LogLevel::Verbose,
    960          ("    - schemeNoReturnData: %i", schemeNoReturnData));
    961  MOZ_LOG(sMCBLog, LogLevel::Verbose,
    962          ("    - schemeInherits: %i", schemeInherits));
    963  MOZ_LOG(sMCBLog, LogLevel::Verbose, ("    - schemeSecure: %i", schemeSecure));
    964  return (schemeLocal || schemeNoReturnData || schemeInherits || schemeSecure);
    965 }
    966 
    967 NS_IMETHODIMP
    968 nsMixedContentBlocker::ShouldProcess(nsIURI* aContentLocation,
    969                                     nsILoadInfo* aLoadInfo,
    970                                     int16_t* aDecision) {
    971  if (!aContentLocation) {
    972    // aContentLocation may be null when a plugin is loading without an
    973    // associated URI resource
    974    if (aLoadInfo->GetExternalContentPolicyType() ==
    975        ExtContentPolicyType::TYPE_OBJECT) {
    976      *aDecision = ACCEPT;
    977      return NS_OK;
    978    }
    979 
    980    NS_SetRequestBlockingReason(aLoadInfo,
    981                                nsILoadInfo::BLOCKING_REASON_MIXED_BLOCKED);
    982    *aDecision = REJECT_REQUEST;
    983    return NS_ERROR_FAILURE;
    984  }
    985 
    986  return ShouldLoad(aContentLocation, aLoadInfo, aDecision);
    987 }
    988 
    989 // Record information on when HSTS would have made mixed content not mixed
    990 // content (regardless of whether it was actually blocked)
    991 void nsMixedContentBlocker::AccumulateMixedContentHSTS(
    992    nsIURI* aURI, bool aActive, const OriginAttributes& aOriginAttributes) {
    993  // This method must only be called in the parent, because
    994  // nsSiteSecurityService is only available in the parent
    995  if (!XRE_IsParentProcess()) {
    996    MOZ_ASSERT(false);
    997    return;
    998  }
    999 
   1000  bool hsts;
   1001  nsresult rv;
   1002  nsCOMPtr<nsISiteSecurityService> sss =
   1003      do_GetService(NS_SSSERVICE_CONTRACTID, &rv);
   1004  if (NS_FAILED(rv)) {
   1005    return;
   1006  }
   1007  rv = sss->IsSecureURI(aURI, aOriginAttributes, &hsts);
   1008  if (NS_FAILED(rv)) {
   1009    return;
   1010  }
   1011 
   1012  // states: would upgrade, would prime, hsts info cached
   1013  // active, passive
   1014  //
   1015  if (!aActive) {
   1016    if (!hsts) {
   1017      glean::mixed_content::hsts.AccumulateSingleSample(
   1018          MCB_HSTS_PASSIVE_NO_HSTS);
   1019    } else {
   1020      glean::mixed_content::hsts.AccumulateSingleSample(
   1021          MCB_HSTS_PASSIVE_WITH_HSTS);
   1022    }
   1023  } else {
   1024    if (!hsts) {
   1025      glean::mixed_content::hsts.AccumulateSingleSample(
   1026          MCB_HSTS_ACTIVE_NO_HSTS);
   1027    } else {
   1028      glean::mixed_content::hsts.AccumulateSingleSample(
   1029          MCB_HSTS_ACTIVE_WITH_HSTS);
   1030    }
   1031  }
   1032 }