tor-browser

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

SecFetch.cpp (14881B)


      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 "SecFetch.h"
      8 
      9 #include "mozIThirdPartyUtil.h"
     10 #include "mozilla/BasePrincipal.h"
     11 #include "mozilla/StaticPrefs_dom.h"
     12 #include "mozilla/dom/RequestBinding.h"
     13 #include "nsContentSecurityManager.h"
     14 #include "nsContentUtils.h"
     15 #include "nsIHttpChannel.h"
     16 #include "nsIRedirectHistoryEntry.h"
     17 #include "nsIReferrerInfo.h"
     18 #include "nsMixedContentBlocker.h"
     19 #include "nsNetUtil.h"
     20 
     21 // Helper function which maps an internal content policy type
     22 // to the corresponding destination for the context of SecFetch.
     23 nsCString MapInternalContentPolicyTypeToDest(nsContentPolicyType aType) {
     24  switch (aType) {
     25    case nsIContentPolicy::TYPE_OTHER:
     26      return "empty"_ns;
     27    case nsIContentPolicy::TYPE_INTERNAL_SCRIPT:
     28    case nsIContentPolicy::TYPE_INTERNAL_SCRIPT_PRELOAD:
     29    case nsIContentPolicy::TYPE_INTERNAL_MODULE:
     30    case nsIContentPolicy::TYPE_INTERNAL_MODULE_PRELOAD:
     31    case nsIContentPolicy::TYPE_INTERNAL_WORKER_IMPORT_SCRIPTS:
     32    case nsIContentPolicy::TYPE_INTERNAL_CHROMEUTILS_COMPILED_SCRIPT:
     33    case nsIContentPolicy::TYPE_INTERNAL_FRAME_MESSAGEMANAGER_SCRIPT:
     34    case nsIContentPolicy::TYPE_SCRIPT:
     35      return "script"_ns;
     36    case nsIContentPolicy::TYPE_JSON:
     37    case nsIContentPolicy::TYPE_INTERNAL_JSON_PRELOAD:
     38      return "json"_ns;
     39    case nsIContentPolicy::TYPE_INTERNAL_WORKER:
     40    case nsIContentPolicy::TYPE_INTERNAL_WORKER_STATIC_MODULE:
     41      return "worker"_ns;
     42    case nsIContentPolicy::TYPE_INTERNAL_SHARED_WORKER:
     43      return "sharedworker"_ns;
     44    case nsIContentPolicy::TYPE_INTERNAL_SERVICE_WORKER:
     45      return "serviceworker"_ns;
     46    case nsIContentPolicy::TYPE_INTERNAL_AUDIOWORKLET:
     47      return "audioworklet"_ns;
     48    case nsIContentPolicy::TYPE_INTERNAL_PAINTWORKLET:
     49      return "paintworklet"_ns;
     50    case nsIContentPolicy::TYPE_IMAGESET:
     51    case nsIContentPolicy::TYPE_INTERNAL_IMAGE:
     52    case nsIContentPolicy::TYPE_INTERNAL_IMAGE_PRELOAD:
     53    case nsIContentPolicy::TYPE_INTERNAL_IMAGE_FAVICON:
     54    case nsIContentPolicy::TYPE_IMAGE:
     55      return "image"_ns;
     56    case nsIContentPolicy::TYPE_STYLESHEET:
     57    case nsIContentPolicy::TYPE_INTERNAL_STYLESHEET:
     58    case nsIContentPolicy::TYPE_INTERNAL_STYLESHEET_PRELOAD:
     59      return "style"_ns;
     60    case nsIContentPolicy::TYPE_OBJECT:
     61    case nsIContentPolicy::TYPE_INTERNAL_OBJECT:
     62      return "object"_ns;
     63    case nsIContentPolicy::TYPE_INTERNAL_EMBED:
     64      return "embed"_ns;
     65    case nsIContentPolicy::TYPE_DOCUMENT:
     66      return "document"_ns;
     67    case nsIContentPolicy::TYPE_SUBDOCUMENT:
     68    case nsIContentPolicy::TYPE_INTERNAL_IFRAME:
     69      return "iframe"_ns;
     70    case nsIContentPolicy::TYPE_INTERNAL_FRAME:
     71      return "frame"_ns;
     72    case nsIContentPolicy::TYPE_PING:
     73      return "empty"_ns;
     74    case nsIContentPolicy::TYPE_XMLHTTPREQUEST:
     75    case nsIContentPolicy::TYPE_INTERNAL_XMLHTTPREQUEST_ASYNC:
     76    case nsIContentPolicy::TYPE_INTERNAL_XMLHTTPREQUEST_SYNC:
     77      return "empty"_ns;
     78    case nsIContentPolicy::TYPE_INTERNAL_EVENTSOURCE:
     79      return "empty"_ns;
     80    case nsIContentPolicy::TYPE_DTD:
     81    case nsIContentPolicy::TYPE_INTERNAL_DTD:
     82    case nsIContentPolicy::TYPE_INTERNAL_FORCE_ALLOWED_DTD:
     83      return "empty"_ns;
     84    case nsIContentPolicy::TYPE_FONT:
     85    case nsIContentPolicy::TYPE_INTERNAL_FONT_PRELOAD:
     86    case nsIContentPolicy::TYPE_UA_FONT:
     87      return "font"_ns;
     88    case nsIContentPolicy::TYPE_MEDIA:
     89      return "empty"_ns;
     90    case nsIContentPolicy::TYPE_INTERNAL_AUDIO:
     91      return "audio"_ns;
     92    case nsIContentPolicy::TYPE_INTERNAL_VIDEO:
     93      return "video"_ns;
     94    case nsIContentPolicy::TYPE_INTERNAL_TRACK:
     95      return "track"_ns;
     96    case nsIContentPolicy::TYPE_WEBSOCKET:
     97      return "empty"_ns;
     98    case nsIContentPolicy::TYPE_CSP_REPORT:
     99      return "report"_ns;
    100    case nsIContentPolicy::TYPE_XSLT:
    101      return "xslt"_ns;
    102    case nsIContentPolicy::TYPE_BEACON:
    103      return "empty"_ns;
    104    case nsIContentPolicy::TYPE_FETCH:
    105    case nsIContentPolicy::TYPE_INTERNAL_FETCH_PRELOAD:
    106      return "empty"_ns;
    107    case nsIContentPolicy::TYPE_WEB_MANIFEST:
    108      return "manifest"_ns;
    109    case nsIContentPolicy::TYPE_SAVEAS_DOWNLOAD:
    110      return "empty"_ns;
    111    case nsIContentPolicy::TYPE_SPECULATIVE:
    112      return "empty"_ns;
    113    case nsIContentPolicy::TYPE_PROXIED_WEBRTC_MEDIA:
    114      return "empty"_ns;
    115    case nsIContentPolicy::TYPE_WEB_IDENTITY:
    116      return "webidentity"_ns;
    117    case nsIContentPolicy::TYPE_WEB_TRANSPORT:
    118      return "webtransport"_ns;
    119    case nsIContentPolicy::TYPE_INTERNAL_EXTERNAL_RESOURCE:
    120      return "image"_ns;
    121    case nsIContentPolicy::TYPE_END:
    122    case nsIContentPolicy::TYPE_INVALID:
    123      break;
    124      // Do not add default: so that compilers can catch the missing case.
    125  }
    126 
    127  MOZ_CRASH("Unhandled nsContentPolicyType value");
    128 }
    129 
    130 // Helper function to determine if a ExpandedPrincipal is of the same-origin as
    131 // a URI in the sec-fetch context.
    132 void IsExpandedPrincipalSameOrigin(
    133    nsCOMPtr<nsIExpandedPrincipal> aExpandedPrincipal, nsIURI* aURI,
    134    bool* aRes) {
    135  *aRes = false;
    136  for (const auto& principal : aExpandedPrincipal->AllowList()) {
    137    // Ignore extension principals to continue treating
    138    // "moz-extension:"-requests as not "same-origin".
    139    if (!mozilla::BasePrincipal::Cast(principal)->AddonPolicy()) {
    140      // A ExpandedPrincipal usually has at most one ContentPrincipal, so we can
    141      // check IsSameOrigin on it here and return early.
    142      mozilla::BasePrincipal::Cast(principal)->IsSameOrigin(aURI, aRes);
    143      return;
    144    }
    145  }
    146 }
    147 
    148 // Helper function to determine whether a request (including involved
    149 // redirects) is same-origin in the context of SecFetch.
    150 bool IsSameOrigin(nsIHttpChannel* aHTTPChannel) {
    151  nsCOMPtr<nsIURI> channelURI;
    152  NS_GetFinalChannelURI(aHTTPChannel, getter_AddRefs(channelURI));
    153 
    154  nsCOMPtr<nsILoadInfo> loadInfo = aHTTPChannel->LoadInfo();
    155 
    156  if (mozilla::BasePrincipal::Cast(loadInfo->TriggeringPrincipal())
    157          ->AddonPolicy()) {
    158    // If an extension triggered the load that has access to the URI then the
    159    // load is considered as same-origin.
    160    return mozilla::BasePrincipal::Cast(loadInfo->TriggeringPrincipal())
    161        ->AddonAllowsLoad(channelURI);
    162  }
    163 
    164  bool isSameOrigin = false;
    165  if (nsContentUtils::IsExpandedPrincipal(loadInfo->TriggeringPrincipal())) {
    166    nsCOMPtr<nsIExpandedPrincipal> ep =
    167        do_QueryInterface(loadInfo->TriggeringPrincipal());
    168    IsExpandedPrincipalSameOrigin(ep, channelURI, &isSameOrigin);
    169  } else {
    170    isSameOrigin = loadInfo->TriggeringPrincipal()->IsSameOrigin(channelURI);
    171  }
    172 
    173  // if the initial request is not same-origin, we can return here
    174  // because we already know it's not a same-origin request
    175  if (!isSameOrigin) {
    176    return false;
    177  }
    178 
    179  // let's further check all the hoops in the redirectChain to
    180  // ensure all involved redirects are same-origin
    181  nsCOMPtr<nsIPrincipal> redirectPrincipal;
    182  for (nsIRedirectHistoryEntry* entry : loadInfo->RedirectChain()) {
    183    entry->GetPrincipal(getter_AddRefs(redirectPrincipal));
    184    if (redirectPrincipal && !redirectPrincipal->IsSameOrigin(channelURI)) {
    185      return false;
    186    }
    187  }
    188 
    189  // must be a same-origin request
    190  return true;
    191 }
    192 
    193 // Helper function to determine whether a request (including involved
    194 // redirects) is same-site in the context of SecFetch.
    195 bool IsSameSite(nsIChannel* aHTTPChannel) {
    196  nsCOMPtr<mozIThirdPartyUtil> thirdPartyUtil =
    197      do_GetService(THIRDPARTYUTIL_CONTRACTID);
    198  if (!thirdPartyUtil) {
    199    return false;
    200  }
    201 
    202  nsAutoCString hostDomain;
    203  nsCOMPtr<nsILoadInfo> loadInfo = aHTTPChannel->LoadInfo();
    204  nsresult rv = loadInfo->TriggeringPrincipal()->GetBaseDomain(hostDomain);
    205  (void)NS_WARN_IF(NS_FAILED(rv));
    206 
    207  nsAutoCString channelDomain;
    208  nsCOMPtr<nsIURI> channelURI;
    209  NS_GetFinalChannelURI(aHTTPChannel, getter_AddRefs(channelURI));
    210  rv = thirdPartyUtil->GetBaseDomain(channelURI, channelDomain);
    211  (void)NS_WARN_IF(NS_FAILED(rv));
    212 
    213  // if the initial request is not same-site, or not https, we can
    214  // return here because we already know it's not a same-site request
    215  if (!hostDomain.Equals(channelDomain) ||
    216      (!loadInfo->TriggeringPrincipal()->SchemeIs("https") &&
    217       !nsMixedContentBlocker::IsPotentiallyTrustworthyLoopbackHost(
    218           hostDomain))) {
    219    return false;
    220  }
    221 
    222  // let's further check all the hoops in the redirectChain to
    223  // ensure all involved redirects are same-site and https
    224  nsCOMPtr<nsIPrincipal> redirectPrincipal;
    225  for (nsIRedirectHistoryEntry* entry : loadInfo->RedirectChain()) {
    226    entry->GetPrincipal(getter_AddRefs(redirectPrincipal));
    227    if (redirectPrincipal) {
    228      redirectPrincipal->GetBaseDomain(hostDomain);
    229      if (!hostDomain.Equals(channelDomain) ||
    230          !redirectPrincipal->SchemeIs("https")) {
    231        return false;
    232      }
    233    }
    234  }
    235 
    236  // must be a same-site request
    237  return true;
    238 }
    239 
    240 // Helper function to determine whether a request was triggered
    241 // by the end user in the context of SecFetch.
    242 // The more secure/closed state to return for this function is "false".
    243 // A user triggered action is less restricted because it is not cross-origin.
    244 bool IsUserTriggeredForSecFetchSite(nsIHttpChannel* aHTTPChannel) {
    245  /*
    246   * The goal is to distinguish between "webby" navigations that are controlled
    247   * by a given website (e.g. links, the window.location setter,form
    248   * submissions, etc.), and those that are not (e.g. user interaction with a
    249   * user agent’s address bar, bookmarks, etc).
    250   */
    251  nsCOMPtr<nsILoadInfo> loadInfo = aHTTPChannel->LoadInfo();
    252  ExtContentPolicyType contentType = loadInfo->GetExternalContentPolicyType();
    253 
    254  // A request issued by the browser is always user initiated.
    255  if (loadInfo->TriggeringPrincipal()->IsSystemPrincipal()) {
    256    return true;
    257  }
    258 
    259  // only requests wich result in type "document" are subject to
    260  // user initiated actions in the context of SecFetch.
    261  if (contentType != ExtContentPolicy::TYPE_DOCUMENT &&
    262      contentType != ExtContentPolicy::TYPE_SUBDOCUMENT) {
    263    return false;
    264  }
    265 
    266  // The load is considered user triggered if it was triggered by an external
    267  // application.
    268  if (loadInfo->GetLoadTriggeredFromExternal()) {
    269    return true;
    270  }
    271 
    272  // sec-fetch-site can only be user triggered if the load was user triggered.
    273  if (!loadInfo->GetHasValidUserGestureActivation()) {
    274    return false;
    275  }
    276 
    277  // We can assert that the navigation must be "webby" if the load was triggered
    278  // by a meta refresh. See also Bug 1647128.
    279  if (loadInfo->GetIsMetaRefresh()) {
    280    return false;
    281  }
    282 
    283  // All web requests have a valid "original" referrer set in the
    284  // ReferrerInfo which we can use to determine whether a request
    285  // was triggered by a user or not.
    286  nsCOMPtr<nsIReferrerInfo> referrerInfo = aHTTPChannel->GetReferrerInfo();
    287  if (referrerInfo) {
    288    nsCOMPtr<nsIURI> originalReferrer;
    289    referrerInfo->GetOriginalReferrer(getter_AddRefs(originalReferrer));
    290    if (!originalReferrer) {
    291      return true;
    292    }
    293  }
    294 
    295  return false;
    296 }
    297 
    298 void mozilla::dom::SecFetch::AddSecFetchDest(nsIHttpChannel* aHTTPChannel) {
    299  nsCOMPtr<nsILoadInfo> loadInfo = aHTTPChannel->LoadInfo();
    300  nsContentPolicyType contentType = loadInfo->InternalContentPolicyType();
    301  nsCString dest = MapInternalContentPolicyTypeToDest(contentType);
    302 
    303  nsresult rv =
    304      aHTTPChannel->SetRequestHeader("Sec-Fetch-Dest"_ns, dest, false);
    305  (void)NS_WARN_IF(NS_FAILED(rv));
    306 }
    307 
    308 void mozilla::dom::SecFetch::AddSecFetchMode(nsIHttpChannel* aHTTPChannel) {
    309  nsCOMPtr<nsILoadInfo> loadInfo = aHTTPChannel->LoadInfo();
    310  uint32_t securityMode = loadInfo->GetSecurityMode();
    311  ExtContentPolicyType externalType = loadInfo->GetExternalContentPolicyType();
    312 
    313  nsAutoCString mode;
    314  if (externalType == ExtContentPolicy::TYPE_DOCUMENT ||
    315      externalType == ExtContentPolicy::TYPE_SUBDOCUMENT ||
    316      externalType == ExtContentPolicy::TYPE_OBJECT) {
    317    mode = "navigate"_ns;
    318  } else if (externalType == ExtContentPolicy::TYPE_WEBSOCKET) {
    319    mode = "websocket"_ns;
    320  } else {
    321    mode = GetEnumString(
    322        nsContentSecurityManager::SecurityModeToRequestMode(securityMode));
    323  }
    324 
    325  nsresult rv =
    326      aHTTPChannel->SetRequestHeader("Sec-Fetch-Mode"_ns, mode, false);
    327  (void)NS_WARN_IF(NS_FAILED(rv));
    328 }
    329 
    330 void mozilla::dom::SecFetch::AddSecFetchSite(nsIHttpChannel* aHTTPChannel) {
    331  nsAutoCString site("same-origin");
    332 
    333  bool isSameOrigin = IsSameOrigin(aHTTPChannel);
    334  if (!isSameOrigin) {
    335    bool isSameSite = IsSameSite(aHTTPChannel);
    336    if (isSameSite) {
    337      site = "same-site"_ns;
    338    } else {
    339      site = "cross-site"_ns;
    340    }
    341  }
    342 
    343  if (IsUserTriggeredForSecFetchSite(aHTTPChannel)) {
    344    site = "none"_ns;
    345  }
    346 
    347  nsresult rv =
    348      aHTTPChannel->SetRequestHeader("Sec-Fetch-Site"_ns, site, false);
    349  (void)NS_WARN_IF(NS_FAILED(rv));
    350 }
    351 
    352 void mozilla::dom::SecFetch::AddSecFetchUser(nsIHttpChannel* aHTTPChannel) {
    353  nsCOMPtr<nsILoadInfo> loadInfo = aHTTPChannel->LoadInfo();
    354  ExtContentPolicyType externalType = loadInfo->GetExternalContentPolicyType();
    355 
    356  // sec-fetch-user only applies to loads of type document or subdocument
    357  if (externalType != ExtContentPolicy::TYPE_DOCUMENT &&
    358      externalType != ExtContentPolicy::TYPE_SUBDOCUMENT) {
    359    return;
    360  }
    361 
    362  // sec-fetch-user only applies if the request is user triggered.
    363  // requests triggered by an external application are considerd user triggered.
    364  if (!loadInfo->GetLoadTriggeredFromExternal() &&
    365      !loadInfo->GetHasValidUserGestureActivation()) {
    366    return;
    367  }
    368 
    369  nsAutoCString user("?1");
    370  nsresult rv =
    371      aHTTPChannel->SetRequestHeader("Sec-Fetch-User"_ns, user, false);
    372  (void)NS_WARN_IF(NS_FAILED(rv));
    373 }
    374 
    375 void mozilla::dom::SecFetch::AddSecFetchHeader(nsIHttpChannel* aHTTPChannel) {
    376  nsCOMPtr<nsIURI> uri;
    377  nsresult rv = aHTTPChannel->GetURI(getter_AddRefs(uri));
    378  if (NS_WARN_IF(NS_FAILED(rv))) {
    379    return;
    380  }
    381 
    382  // if we are not dealing with a potentially trustworthy URL, then
    383  // there is nothing to do here
    384  if (!nsMixedContentBlocker::IsPotentiallyTrustworthyOrigin(uri)) {
    385    return;
    386  }
    387 
    388  // If we're dealing with a system XMLHttpRequest or fetch, don't add
    389  // Sec- headers.
    390  nsCOMPtr<nsILoadInfo> loadInfo = aHTTPChannel->LoadInfo();
    391  if (loadInfo->TriggeringPrincipal()->IsSystemPrincipal()) {
    392    ExtContentPolicy extType = loadInfo->GetExternalContentPolicyType();
    393    if (extType == ExtContentPolicy::TYPE_FETCH ||
    394        extType == ExtContentPolicy::TYPE_XMLHTTPREQUEST) {
    395      return;
    396    }
    397  }
    398 
    399  AddSecFetchDest(aHTTPChannel);
    400  AddSecFetchMode(aHTTPChannel);
    401  AddSecFetchSite(aHTTPChannel);
    402  AddSecFetchUser(aHTTPChannel);
    403 }