tor-browser

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

nsCSPService.cpp (14871B)


      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 "nsCSPService.h"
      8 
      9 #include "mozilla/Logging.h"
     10 #include "mozilla/Preferences.h"
     11 #include "mozilla/StaticPrefs_security.h"
     12 #include "mozilla/dom/PolicyContainer.h"
     13 #include "mozilla/net/DocumentChannel.h"
     14 #include "mozilla/net/DocumentLoadListener.h"
     15 #include "nsAsyncRedirectVerifyHelper.h"
     16 #include "nsCOMPtr.h"
     17 #include "nsContentPolicyUtils.h"
     18 #include "nsContentUtils.h"
     19 #include "nsError.h"
     20 #include "nsIAsyncVerifyRedirectCallback.h"
     21 #include "nsIContent.h"
     22 #include "nsIContentSecurityPolicy.h"
     23 #include "nsIProtocolHandler.h"
     24 #include "nsIURI.h"
     25 #include "nsNetUtil.h"
     26 #include "nsQueryObject.h"
     27 #include "nsString.h"
     28 
     29 using namespace mozilla;
     30 
     31 static LazyLogModule gCspPRLog("CSP");
     32 
     33 CSPService::CSPService() = default;
     34 
     35 CSPService::~CSPService() = default;
     36 
     37 NS_IMPL_ISUPPORTS(CSPService, nsIContentPolicy, nsIChannelEventSink)
     38 
     39 static bool SubjectToCSP(nsILoadInfo* aLoadInfo, nsIURI* aURI,
     40                         nsContentPolicyType aContentType) {
     41  ExtContentPolicyType contentType =
     42      nsContentUtils::InternalContentPolicyTypeToExternal(aContentType);
     43 
     44  // These content types are not subject to CSP content policy checks:
     45  // TYPE_CSP_REPORT -- csp can't block csp reports
     46  // TYPE_DOCUMENT   -- used for frame-ancestors
     47  if (contentType == ExtContentPolicy::TYPE_CSP_REPORT ||
     48      contentType == ExtContentPolicy::TYPE_DOCUMENT) {
     49    return false;
     50  }
     51 
     52  // The three protocols: data:, blob: and filesystem: share the same
     53  // protocol flag (URI_IS_LOCAL_RESOURCE) with other protocols,
     54  // but those three protocols get special attention in CSP and
     55  // are subject to CSP, hence we have to make sure those
     56  // protocols are subject to CSP, see:
     57  // http://www.w3.org/TR/CSP2/#source-list-guid-matching
     58  if (aURI->SchemeIs("data") || aURI->SchemeIs("blob") ||
     59      aURI->SchemeIs("filesystem")) {
     60    return true;
     61  }
     62 
     63  // For resources that will be used with a system principal we don't want to
     64  // exempt any protocols from being subject to the CSP.
     65  // TODO(bug 1945838): Extend this to all content types, not just scripts.
     66  if (contentType == ExtContentPolicyType::TYPE_SCRIPT) {
     67    if (BasePrincipal::Cast(aLoadInfo->GetLoadingPrincipal())
     68            ->IsSystemPrincipal()) {
     69      return true;
     70    }
     71  }
     72 
     73  // Finally we have to allowlist "about:" which does not fall into
     74  // the category underneath and also "javascript:" which is not
     75  // subject to CSP content loading rules.
     76  if (aURI->SchemeIs("about") || aURI->SchemeIs("javascript")) {
     77    return false;
     78  }
     79 
     80  // Please note that it should be possible for websites to
     81  // allowlist their own protocol handlers with respect to CSP,
     82  // hence we use protocol flags to accomplish that, but we also
     83  // want resource:, chrome: and moz-icon to be subject to CSP
     84  // (which also use URI_IS_LOCAL_RESOURCE).
     85  // Exception to the rule are images, styles, and localization
     86  // DTDs using a scheme of resource: or chrome:
     87  bool isImgOrStyleOrDTD = contentType == ExtContentPolicy::TYPE_IMAGE ||
     88                           contentType == ExtContentPolicy::TYPE_STYLESHEET ||
     89                           contentType == ExtContentPolicy::TYPE_DTD;
     90  if (aURI->SchemeIs("resource")) {
     91    nsAutoCString uriSpec;
     92    aURI->GetSpec(uriSpec);
     93    // Exempt pdf.js from being subject to a page's CSP.
     94    if (StringBeginsWith(uriSpec, "resource://pdf.js/"_ns)) {
     95      return false;
     96    }
     97    if (!isImgOrStyleOrDTD) {
     98      return true;
     99    }
    100  }
    101  if (aURI->SchemeIs("chrome")) {
    102    nsAutoCString uriSpec;
    103    aURI->GetSpec(uriSpec);
    104    // Exempt the script used by the top-level VideoDocument.
    105    if (contentType == ExtContentPolicyType::TYPE_SCRIPT &&
    106        uriSpec.EqualsLiteral(
    107            "chrome://global/content/TopLevelVideoDocument.js")) {
    108      return false;
    109    }
    110    if (!isImgOrStyleOrDTD) {
    111      return true;
    112    }
    113  }
    114  if (aURI->SchemeIs("moz-icon") || aURI->SchemeIs("moz-src")) {
    115    return true;
    116  }
    117  bool match;
    118  nsresult rv = NS_URIChainHasFlags(
    119      aURI, nsIProtocolHandler::URI_IS_LOCAL_RESOURCE, &match);
    120  if (NS_SUCCEEDED(rv) && match) {
    121    return false;
    122  }
    123  // all other protocols are subject To CSP.
    124  return true;
    125 }
    126 
    127 /* static */ nsresult CSPService::ConsultCSP(nsIURI* aContentLocation,
    128                                             nsILoadInfo* aLoadInfo,
    129                                             int16_t* aDecision) {
    130  if (!aContentLocation) {
    131    return NS_ERROR_FAILURE;
    132  }
    133 
    134  nsContentPolicyType contentType = aLoadInfo->InternalContentPolicyType();
    135 
    136  nsCOMPtr<nsICSPEventListener> cspEventListener;
    137  nsresult rv =
    138      aLoadInfo->GetCspEventListener(getter_AddRefs(cspEventListener));
    139  NS_ENSURE_SUCCESS(rv, rv);
    140 
    141  if (MOZ_LOG_TEST(gCspPRLog, LogLevel::Debug)) {
    142    MOZ_LOG(gCspPRLog, LogLevel::Debug,
    143            ("CSPService::ShouldLoad called for %s",
    144             aContentLocation->GetSpecOrDefault().get()));
    145  }
    146 
    147  // default decision, CSP can revise it if there's a policy to enforce
    148  *aDecision = nsIContentPolicy::ACCEPT;
    149 
    150  // No need to continue processing the CSP if the load should *not* be subject
    151  // to CSP. Please note, the correct way to opt-out of CSP using a custom
    152  // protocolHandler is to set one of the nsIProtocolHandler flags
    153  // that are allowlistet in subjectToCSP()
    154  if (!SubjectToCSP(aLoadInfo, aContentLocation, contentType)) {
    155    return NS_OK;
    156  }
    157 
    158  // 1) Apply speculate CSP for preloads
    159  bool isPreload = nsContentUtils::IsPreloadType(contentType);
    160 
    161  if (isPreload) {
    162    nsCOMPtr<nsIContentSecurityPolicy> preloadCsp = aLoadInfo->GetPreloadCsp();
    163    if (preloadCsp) {
    164      // obtain the enforcement decision
    165      rv = preloadCsp->ShouldLoad(
    166          contentType, cspEventListener, aLoadInfo, aContentLocation,
    167          nullptr,  // no redirect, aOriginal URL is null.
    168          false, aDecision);
    169      NS_ENSURE_SUCCESS(rv, rv);
    170 
    171      // if the preload policy already denied the load, then there
    172      // is no point in checking the real policy
    173      if (NS_CP_REJECTED(*aDecision)) {
    174        NS_SetRequestBlockingReason(
    175            aLoadInfo, nsILoadInfo::BLOCKING_REASON_CONTENT_POLICY_PRELOAD);
    176 
    177        return NS_OK;
    178      }
    179    }
    180  }
    181 
    182  // 2) Apply actual CSP to all loads. Please note that in case
    183  // the csp should be overruled (e.g. by an ExpandedPrincipal)
    184  // then loadinfo->GetCsp() returns that CSP instead of the
    185  // document's CSP.
    186  nsCOMPtr<nsIPolicyContainer> policyContainer =
    187      aLoadInfo->GetPolicyContainer();
    188  nsCOMPtr<nsIContentSecurityPolicy> csp =
    189      PolicyContainer::GetCSP(policyContainer);
    190 
    191  if (csp) {
    192    // Generally aOriginalURI denotes the URI before a redirect and hence
    193    // will always be a nullptr here. Only exception are frame navigations
    194    // which we want to treat as a redirect for the purpose of CSP reporting
    195    // and in particular the `blocked-uri` in the CSP report where we want
    196    // to report the prePath information.
    197    nsCOMPtr<nsIURI> originalURI = nullptr;
    198    ExtContentPolicyType extType =
    199        nsContentUtils::InternalContentPolicyTypeToExternal(contentType);
    200    if (extType == ExtContentPolicy::TYPE_SUBDOCUMENT &&
    201        !aLoadInfo->GetOriginalFrameSrcLoad() &&
    202        mozilla::StaticPrefs::
    203            security_csp_truncate_blocked_uri_for_frame_navigations()) {
    204      nsAutoCString prePathStr;
    205      nsresult rv = aContentLocation->GetPrePath(prePathStr);
    206      NS_ENSURE_SUCCESS(rv, rv);
    207      rv = NS_NewURI(getter_AddRefs(originalURI), prePathStr);
    208      NS_ENSURE_SUCCESS(rv, rv);
    209    }
    210 
    211    // obtain the enforcement decision
    212    rv = csp->ShouldLoad(
    213        contentType, cspEventListener, aLoadInfo, aContentLocation,
    214        originalURI,  // no redirect, unless it's a frame navigation.
    215        !isPreload && aLoadInfo->GetSendCSPViolationEvents(), aDecision);
    216 
    217    if (NS_CP_REJECTED(*aDecision)) {
    218      NS_SetRequestBlockingReason(
    219          aLoadInfo, nsILoadInfo::BLOCKING_REASON_CONTENT_POLICY_GENERAL);
    220    }
    221 
    222    NS_ENSURE_SUCCESS(rv, rv);
    223  }
    224  return NS_OK;
    225 }
    226 
    227 /* nsIContentPolicy implementation */
    228 NS_IMETHODIMP
    229 CSPService::ShouldLoad(nsIURI* aContentLocation, nsILoadInfo* aLoadInfo,
    230                       int16_t* aDecision) {
    231  return ConsultCSP(aContentLocation, aLoadInfo, aDecision);
    232 }
    233 
    234 NS_IMETHODIMP
    235 CSPService::ShouldProcess(nsIURI* aContentLocation, nsILoadInfo* aLoadInfo,
    236                          int16_t* aDecision) {
    237  if (!aContentLocation) {
    238    return NS_ERROR_FAILURE;
    239  }
    240  nsContentPolicyType contentType = aLoadInfo->InternalContentPolicyType();
    241 
    242  if (MOZ_LOG_TEST(gCspPRLog, LogLevel::Debug)) {
    243    MOZ_LOG(gCspPRLog, LogLevel::Debug,
    244            ("CSPService::ShouldProcess called for %s",
    245             aContentLocation->GetSpecOrDefault().get()));
    246  }
    247 
    248  // ShouldProcess is only relevant to TYPE_OBJECT, so let's convert the
    249  // internal contentPolicyType to the mapping external one.
    250  // If it is not TYPE_OBJECT, we can return at this point.
    251  // Note that we should still pass the internal contentPolicyType
    252  // (contentType) to ShouldLoad().
    253  ExtContentPolicyType policyType =
    254      nsContentUtils::InternalContentPolicyTypeToExternal(contentType);
    255 
    256  if (policyType != ExtContentPolicy::TYPE_OBJECT) {
    257    *aDecision = nsIContentPolicy::ACCEPT;
    258    return NS_OK;
    259  }
    260 
    261  return ShouldLoad(aContentLocation, aLoadInfo, aDecision);
    262 }
    263 
    264 /* nsIChannelEventSink implementation */
    265 NS_IMETHODIMP
    266 CSPService::AsyncOnChannelRedirect(nsIChannel* oldChannel,
    267                                   nsIChannel* newChannel, uint32_t flags,
    268                                   nsIAsyncVerifyRedirectCallback* callback) {
    269  net::nsAsyncRedirectAutoCallback autoCallback(callback);
    270 
    271  if (XRE_IsE10sParentProcess()) {
    272    nsCOMPtr<nsIParentChannel> parentChannel;
    273    NS_QueryNotificationCallbacks(oldChannel, parentChannel);
    274    RefPtr<net::DocumentLoadListener> docListener =
    275        do_QueryObject(parentChannel);
    276    // Since this is an IPC'd channel we do not have access to the request
    277    // context. In turn, we do not have an event target for policy violations.
    278    // Enforce the CSP check in the content process where we have that info.
    279    // We allow redirect checks to run for document loads via
    280    // DocumentLoadListener, since these are fully supported and we don't
    281    // expose the redirects to the content process. We can't do this for all
    282    // request types yet because we don't serialize nsICSPEventListener.
    283    if (parentChannel && !docListener) {
    284      return NS_OK;
    285    }
    286  }
    287 
    288  // Don't do these checks if we're switching from DocumentChannel
    289  // to a real channel. In that case, we should already have done
    290  // the checks in the parent process. AsyncOnChannelRedirect
    291  // isn't called in the content process if we switch process,
    292  // so checking here would just hide bugs in the process switch
    293  // cases.
    294  if (RefPtr<net::DocumentChannel> docChannel = do_QueryObject(oldChannel)) {
    295    return NS_OK;
    296  }
    297 
    298  nsCOMPtr<nsIURI> newUri;
    299  nsresult rv = newChannel->GetURI(getter_AddRefs(newUri));
    300  NS_ENSURE_SUCCESS(rv, rv);
    301 
    302  nsCOMPtr<nsILoadInfo> loadInfo = oldChannel->LoadInfo();
    303 
    304  /* Since redirecting channels don't call into nsIContentPolicy, we call our
    305   * Content Policy implementation directly when redirects occur using the
    306   * information set in the LoadInfo when channels are created.
    307   *
    308   * We check if the CSP permits this host for this type of load, if not,
    309   * we cancel the load now.
    310   */
    311  nsCOMPtr<nsIURI> originalUri;
    312  rv = oldChannel->GetOriginalURI(getter_AddRefs(originalUri));
    313  if (NS_FAILED(rv)) {
    314    autoCallback.DontCallback();
    315    oldChannel->Cancel(NS_ERROR_DOM_BAD_URI);
    316    return rv;
    317  }
    318 
    319  Maybe<nsresult> cancelCode;
    320  rv = ConsultCSPForRedirect(originalUri, newUri, loadInfo, cancelCode);
    321  if (cancelCode) {
    322    oldChannel->Cancel(*cancelCode);
    323  }
    324  if (NS_FAILED(rv)) {
    325    autoCallback.DontCallback();
    326  }
    327 
    328  return rv;
    329 }
    330 
    331 nsresult CSPService::ConsultCSPForRedirect(nsIURI* aOriginalURI,
    332                                           nsIURI* aNewURI,
    333                                           nsILoadInfo* aLoadInfo,
    334                                           Maybe<nsresult>& aCancelCode) {
    335  // No need to continue processing if CSP is disabled or if the protocol
    336  // is *not* subject to CSP.
    337  // Please note, the correct way to opt-out of CSP using a custom
    338  // protocolHandler is to set one of the nsIProtocolHandler flags
    339  // that are allowlistet in subjectToCSP()
    340  nsContentPolicyType policyType = aLoadInfo->InternalContentPolicyType();
    341  if (!SubjectToCSP(aLoadInfo, aNewURI, policyType)) {
    342    return NS_OK;
    343  }
    344 
    345  nsCOMPtr<nsICSPEventListener> cspEventListener;
    346  nsresult rv =
    347      aLoadInfo->GetCspEventListener(getter_AddRefs(cspEventListener));
    348  MOZ_ALWAYS_SUCCEEDS(rv);
    349 
    350  bool isPreload = nsContentUtils::IsPreloadType(policyType);
    351 
    352  /* On redirect, if the content policy is a preload type, rejecting the
    353   * preload results in the load silently failing, so we pass true to
    354   * the aSendViolationReports parameter. See Bug 1219453.
    355   */
    356 
    357  int16_t decision = nsIContentPolicy::ACCEPT;
    358 
    359  // 1) Apply speculative CSP for preloads
    360  if (isPreload) {
    361    nsCOMPtr<nsIContentSecurityPolicy> preloadCsp = aLoadInfo->GetPreloadCsp();
    362    if (preloadCsp) {
    363      // Pass  originalURI to indicate the redirect
    364      preloadCsp->ShouldLoad(
    365          policyType,  // load type per nsIContentPolicy (uint32_t)
    366          cspEventListener, aLoadInfo,
    367          aNewURI,       // nsIURI
    368          aOriginalURI,  // Original nsIURI
    369          true,          // aSendViolationReports
    370          &decision);
    371 
    372      // if the preload policy already denied the load, then there
    373      // is no point in checking the real policy
    374      if (NS_CP_REJECTED(decision)) {
    375        aCancelCode = Some(NS_ERROR_DOM_BAD_URI);
    376        return NS_BINDING_FAILED;
    377      }
    378    }
    379  }
    380 
    381  // 2) Apply actual CSP to all loads
    382  nsCOMPtr<nsIPolicyContainer> policyContainer =
    383      aLoadInfo->GetPolicyContainer();
    384  nsCOMPtr<nsIContentSecurityPolicy> csp =
    385      PolicyContainer::GetCSP(policyContainer);
    386  if (csp) {
    387    // Pass  originalURI to indicate the redirect
    388    csp->ShouldLoad(policyType,  // load type per nsIContentPolicy (uint32_t)
    389                    cspEventListener, aLoadInfo,
    390                    aNewURI,       // nsIURI
    391                    aOriginalURI,  // Original nsIURI
    392                    true,          // aSendViolationReports
    393                    &decision);
    394    if (NS_CP_REJECTED(decision)) {
    395      aCancelCode = Some(NS_ERROR_DOM_BAD_URI);
    396      return NS_BINDING_FAILED;
    397    }
    398  }
    399 
    400  return NS_OK;
    401 }