tor-browser

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

ThirdPartyUtil.cpp (17628B)


      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 "ThirdPartyUtil.h"
      8 
      9 #include <cstdint>
     10 
     11 #include "MainThreadUtils.h"
     12 #include "mozIDOMWindow.h"
     13 #include "mozilla/Assertions.h"
     14 #include "mozilla/BasePrincipal.h"
     15 #include "mozilla/ClearOnShutdown.h"
     16 #include "mozilla/Components.h"
     17 #include "mozilla/ContentBlockingNotifier.h"
     18 #include "mozilla/Logging.h"
     19 #include "mozilla/NullPrincipal.h"
     20 #include "mozilla/StaticPtr.h"
     21 #include "mozilla/StorageAccess.h"
     22 #include "mozilla/dom/BlobURLProtocolHandler.h"
     23 #include "mozilla/dom/BrowsingContext.h"
     24 #include "mozilla/dom/CanonicalBrowsingContext.h"
     25 #include "mozilla/dom/Document.h"
     26 #include "mozilla/dom/WindowContext.h"
     27 #include "mozilla/dom/WindowGlobalParent.h"
     28 #include "nsCOMPtr.h"
     29 #include "nsDebug.h"
     30 #include "nsError.h"
     31 #include "nsGlobalWindowOuter.h"
     32 #include "nsIChannel.h"
     33 #include "nsIClassifiedChannel.h"
     34 #include "nsIContentPolicy.h"
     35 #include "nsIHttpChannelInternal.h"
     36 #include "nsILoadContext.h"
     37 #include "nsILoadInfo.h"
     38 #include "nsIPrincipal.h"
     39 #include "nsIScriptObjectPrincipal.h"
     40 #include "nsIURI.h"
     41 #include "nsLiteralString.h"
     42 #include "nsNetUtil.h"
     43 #include "nsPIDOMWindow.h"
     44 #include "nsPIDOMWindowInlines.h"
     45 #include "nsSandboxFlags.h"
     46 #include "nsServiceManagerUtils.h"
     47 #include "nsTLiteralString.h"
     48 
     49 using namespace mozilla;
     50 using namespace mozilla::dom;
     51 
     52 NS_IMPL_ISUPPORTS(ThirdPartyUtil, mozIThirdPartyUtil)
     53 
     54 //
     55 // MOZ_LOG=thirdPartyUtil:5
     56 //
     57 static mozilla::LazyLogModule gThirdPartyLog("thirdPartyUtil");
     58 #undef LOG
     59 #define LOG(args) MOZ_LOG(gThirdPartyLog, mozilla::LogLevel::Debug, args)
     60 
     61 static mozilla::StaticRefPtr<ThirdPartyUtil> gService;
     62 
     63 // static
     64 void ThirdPartyUtil::Startup() {
     65  nsCOMPtr<mozIThirdPartyUtil> tpu;
     66  if (NS_WARN_IF(!(tpu = do_GetService(THIRDPARTYUTIL_CONTRACTID)))) {
     67    NS_WARNING("Failed to get third party util!");
     68  }
     69 }
     70 
     71 nsresult ThirdPartyUtil::Init() {
     72  NS_ENSURE_TRUE(NS_IsMainThread(), NS_ERROR_NOT_AVAILABLE);
     73 
     74  MOZ_ASSERT(!gService);
     75  gService = this;
     76  mozilla::ClearOnShutdown(&gService);
     77 
     78  mTLDService = mozilla::components::EffectiveTLD::Service();
     79  return mTLDService ? NS_OK : NS_ERROR_FAILURE;
     80 }
     81 
     82 ThirdPartyUtil::~ThirdPartyUtil() { gService = nullptr; }
     83 
     84 // static
     85 ThirdPartyUtil* ThirdPartyUtil::GetInstance() {
     86  if (gService) {
     87    return gService;
     88  }
     89  nsCOMPtr<mozIThirdPartyUtil> tpuService =
     90      mozilla::components::ThirdPartyUtil::Service();
     91  if (!tpuService) {
     92    return nullptr;
     93  }
     94  MOZ_ASSERT(
     95      gService,
     96      "gService must have been initialized in nsEffectiveTLDService::Init");
     97  return gService;
     98 }
     99 
    100 // Determine if aFirstDomain is a different base domain to aSecondURI; or, if
    101 // the concept of base domain does not apply, determine if the two hosts are not
    102 // string-identical.
    103 nsresult ThirdPartyUtil::IsThirdPartyInternal(const nsCString& aFirstDomain,
    104                                              nsIURI* aSecondURI,
    105                                              bool* aResult) {
    106  if (!aSecondURI) {
    107    return NS_ERROR_INVALID_ARG;
    108  }
    109 
    110  // BlobURLs are always first-party.
    111  if (aSecondURI->SchemeIs("blob")) {
    112    *aResult = false;
    113    return NS_OK;
    114  }
    115 
    116  // Get the base domain for aSecondURI.
    117  nsAutoCString secondDomain;
    118  nsresult rv = GetBaseDomain(aSecondURI, secondDomain);
    119  LOG(("ThirdPartyUtil::IsThirdPartyInternal %s =? %s", aFirstDomain.get(),
    120       secondDomain.get()));
    121  if (NS_FAILED(rv)) {
    122    return rv;
    123  }
    124 
    125  *aResult = IsThirdPartyInternal(aFirstDomain, secondDomain);
    126  return NS_OK;
    127 }
    128 
    129 NS_IMETHODIMP
    130 ThirdPartyUtil::GetPrincipalFromWindow(mozIDOMWindowProxy* aWin,
    131                                       nsIPrincipal** result) {
    132  nsCOMPtr<nsIScriptObjectPrincipal> scriptObjPrin = do_QueryInterface(aWin);
    133  if (!scriptObjPrin) {
    134    return NS_ERROR_INVALID_ARG;
    135  }
    136 
    137  nsCOMPtr<nsIPrincipal> prin = scriptObjPrin->GetPrincipal();
    138  if (!prin) {
    139    return NS_ERROR_INVALID_ARG;
    140  }
    141 
    142  prin.forget(result);
    143  return NS_OK;
    144 }
    145 
    146 // Get the URI associated with a window.
    147 NS_IMETHODIMP
    148 ThirdPartyUtil::GetURIFromWindow(mozIDOMWindowProxy* aWin, nsIURI** result) {
    149  nsCOMPtr<nsIPrincipal> prin;
    150  nsresult rv = GetPrincipalFromWindow(aWin, getter_AddRefs(prin));
    151  if (NS_FAILED(rv)) {
    152    return rv;
    153  }
    154 
    155  if (prin->GetIsNullPrincipal()) {
    156    LOG(("ThirdPartyUtil::GetURIFromWindow can't use null principal\n"));
    157    return NS_ERROR_INVALID_ARG;
    158  }
    159  auto* basePrin = BasePrincipal::Cast(prin);
    160  return basePrin->GetURI(result);
    161 }
    162 
    163 // Determine if aFirstURI is third party with respect to aSecondURI. See docs
    164 // for mozIThirdPartyUtil.
    165 NS_IMETHODIMP
    166 ThirdPartyUtil::IsThirdPartyURI(nsIURI* aFirstURI, nsIURI* aSecondURI,
    167                                bool* aResult) {
    168  NS_ENSURE_ARG(aFirstURI);
    169  NS_ENSURE_ARG(aSecondURI);
    170  NS_ASSERTION(aResult, "null outparam pointer");
    171 
    172  nsAutoCString firstHost;
    173  nsresult rv = GetBaseDomain(aFirstURI, firstHost);
    174  if (NS_FAILED(rv)) {
    175    return rv;
    176  }
    177 
    178  return IsThirdPartyInternal(firstHost, aSecondURI, aResult);
    179 }
    180 
    181 // If the optional aURI is provided, determine whether aWindow is foreign with
    182 // respect to aURI. If the optional aURI is not provided, determine whether the
    183 // given "window hierarchy" is third party. See docs for mozIThirdPartyUtil.
    184 NS_IMETHODIMP
    185 ThirdPartyUtil::IsThirdPartyWindow(mozIDOMWindowProxy* aWindow, nsIURI* aURI,
    186                                   bool* aResult) {
    187  NS_ENSURE_ARG(aWindow);
    188  NS_ASSERTION(aResult, "null outparam pointer");
    189 
    190  bool result;
    191 
    192  // Ignore about:blank and about:srcdoc URIs here since they have no domain
    193  // and attempting to compare against them will fail.
    194  if (aURI && !NS_IsAboutBlank(aURI) && !NS_IsAboutSrcdoc(aURI)) {
    195    nsCOMPtr<nsIPrincipal> prin;
    196    nsresult rv = GetPrincipalFromWindow(aWindow, getter_AddRefs(prin));
    197    NS_ENSURE_SUCCESS(rv, rv);
    198    // Determine whether aURI is foreign with respect to the current principal.
    199    rv = prin->IsThirdPartyURI(aURI, &result);
    200    if (NS_FAILED(rv)) {
    201      return rv;
    202    }
    203 
    204    if (result) {
    205      *aResult = true;
    206      return NS_OK;
    207    }
    208  }
    209 
    210  nsPIDOMWindowOuter* current = nsPIDOMWindowOuter::From(aWindow);
    211  auto* const browsingContext = current->GetBrowsingContext();
    212  MOZ_ASSERT(browsingContext);
    213 
    214  if (browsingContext->IsTopContent()) {
    215    *aResult = false;
    216    return NS_OK;
    217  }
    218 
    219  // If the document is sandboxed, it's always third party.
    220  RefPtr<Document> doc = current->GetExtantDoc();
    221  if (doc && (doc->GetSandboxFlags() & SANDBOXED_ORIGIN)) {
    222    *aResult = true;
    223    return NS_OK;
    224  }
    225 
    226  WindowContext* wc = browsingContext->GetCurrentWindowContext();
    227  if (NS_WARN_IF(!wc)) {
    228    *aResult = true;
    229    return NS_OK;
    230  }
    231 
    232  *aResult = wc->GetIsThirdPartyWindow();
    233  return NS_OK;
    234 }
    235 
    236 nsresult ThirdPartyUtil::IsThirdPartyGlobal(
    237    mozilla::dom::WindowGlobalParent* aWindowGlobal, bool* aResult) {
    238  NS_ENSURE_ARG(aWindowGlobal);
    239  NS_ASSERTION(aResult, "null outparam pointer");
    240 
    241  auto* currentWGP = aWindowGlobal;
    242  do {
    243    MOZ_ASSERT(currentWGP->BrowsingContext());
    244    if (currentWGP->BrowsingContext()->IsTop()) {
    245      *aResult = false;
    246      return NS_OK;
    247    }
    248 
    249    // If the window global is sandboxed, it's always third party.
    250    if (currentWGP->SandboxFlags() & SANDBOXED_ORIGIN) {
    251      *aResult = true;
    252      return NS_OK;
    253    }
    254 
    255    nsCOMPtr<nsIPrincipal> currentPrincipal = currentWGP->DocumentPrincipal();
    256    RefPtr<WindowGlobalParent> parent =
    257        currentWGP->BrowsingContext()->GetEmbedderWindowGlobal();
    258    if (!parent) {
    259      return NS_ERROR_FAILURE;
    260    }
    261    nsCOMPtr<nsIPrincipal> parentPrincipal = parent->DocumentPrincipal();
    262    nsresult rv =
    263        currentPrincipal->IsThirdPartyPrincipal(parentPrincipal, aResult);
    264    if (NS_FAILED(rv)) {
    265      return rv;
    266    }
    267 
    268    if (*aResult) {
    269      return NS_OK;
    270    }
    271 
    272    currentWGP = parent;
    273  } while (true);
    274 }
    275 
    276 // Determine if the URI associated with aChannel or any URI of the window
    277 // hierarchy associated with the channel is foreign with respect to aSecondURI.
    278 // See docs for mozIThirdPartyUtil.
    279 NS_IMETHODIMP
    280 ThirdPartyUtil::IsThirdPartyChannel(nsIChannel* aChannel, nsIURI* aURI,
    281                                    bool* aResult) {
    282  LOG(("ThirdPartyUtil::IsThirdPartyChannel [channel=%p]", aChannel));
    283  NS_ENSURE_ARG(aChannel);
    284  NS_ASSERTION(aResult, "null outparam pointer");
    285 
    286  nsresult rv;
    287  bool doForce = false;
    288  nsCOMPtr<nsIHttpChannelInternal> httpChannelInternal =
    289      do_QueryInterface(aChannel);
    290  if (httpChannelInternal) {
    291    uint32_t flags = 0;
    292    // Avoid checking the return value here since some channel implementations
    293    // may return NS_ERROR_NOT_IMPLEMENTED.
    294    (void)httpChannelInternal->GetThirdPartyFlags(&flags);
    295 
    296    doForce = (flags & nsIHttpChannelInternal::THIRD_PARTY_FORCE_ALLOW);
    297 
    298    // If aURI was not supplied, and we're forcing, then we're by definition
    299    // not foreign. If aURI was supplied, we still want to check whether it's
    300    // foreign with respect to the channel URI. (The forcing only applies to
    301    // whatever window hierarchy exists above the channel.)
    302    if (doForce && !aURI) {
    303      *aResult = false;
    304      return NS_OK;
    305    }
    306  }
    307 
    308  bool parentIsThird = false;
    309  nsAutoCString channelDomain;
    310 
    311  // Obtain the URI from the channel, and its base domain.
    312  nsCOMPtr<nsIURI> channelURI;
    313  rv = NS_GetFinalChannelURI(aChannel, getter_AddRefs(channelURI));
    314  if (NS_FAILED(rv)) {
    315    return rv;
    316  }
    317 
    318  BasePrincipal* loadingPrincipal = nullptr;
    319  nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
    320 
    321  if (!doForce) {
    322    parentIsThird = loadInfo->GetIsInThirdPartyContext();
    323    if (!parentIsThird && loadInfo->GetExternalContentPolicyType() !=
    324                              ExtContentPolicy::TYPE_DOCUMENT) {
    325      // Check if the channel itself is third-party to its own requestor.
    326      // Unfortunately, we have to go through the loading principal.
    327      loadingPrincipal = BasePrincipal::Cast(loadInfo->GetLoadingPrincipal());
    328    }
    329  }
    330 
    331  // Special consideration must be done for about:blank and about:srcdoc URIs
    332  // because those inherit the principal from the parent context. For them,
    333  // let's consider the principal URI.
    334  if (NS_IsAboutBlank(channelURI) || NS_IsAboutSrcdoc(channelURI)) {
    335    nsCOMPtr<nsIPrincipal> principalToInherit =
    336        loadInfo->FindPrincipalToInherit(aChannel);
    337    if (!principalToInherit) {
    338      *aResult = true;
    339      return NS_OK;
    340    }
    341 
    342    rv = principalToInherit->GetBaseDomain(channelDomain);
    343    if (NS_FAILED(rv)) {
    344      return rv;
    345    }
    346 
    347    if (loadingPrincipal) {
    348      rv = loadingPrincipal->IsThirdPartyPrincipal(principalToInherit,
    349                                                   &parentIsThird);
    350      if (NS_FAILED(rv)) {
    351        return rv;
    352      }
    353    }
    354  } else {
    355    rv = GetBaseDomain(channelURI, channelDomain);
    356    if (NS_FAILED(rv)) {
    357      return rv;
    358    }
    359 
    360    if (loadingPrincipal) {
    361      rv = loadingPrincipal->IsThirdPartyURI(channelURI, &parentIsThird);
    362      if (NS_FAILED(rv)) {
    363        return rv;
    364      }
    365    }
    366  }
    367 
    368  // If we're not comparing to a URI, we have our answer. Otherwise, if
    369  // parentIsThird, we're not forcing and we know that we're a third-party
    370  // request.
    371  if (!aURI || parentIsThird) {
    372    *aResult = parentIsThird;
    373    return NS_OK;
    374  }
    375 
    376  // Determine whether aURI is foreign with respect to channelURI.
    377  return IsThirdPartyInternal(channelDomain, aURI, aResult);
    378 }
    379 
    380 NS_IMETHODIMP
    381 ThirdPartyUtil::GetTopWindowForChannel(nsIChannel* aChannel,
    382                                       nsIURI* aURIBeingLoaded,
    383                                       mozIDOMWindowProxy** aWin) {
    384  NS_ENSURE_ARG(aWin);
    385 
    386  // Find the associated window and its parent window.
    387  nsCOMPtr<nsILoadContext> ctx;
    388  NS_QueryNotificationCallbacks(aChannel, ctx);
    389  if (!ctx) {
    390    return NS_ERROR_INVALID_ARG;
    391  }
    392 
    393  nsCOMPtr<mozIDOMWindowProxy> window;
    394  ctx->GetAssociatedWindow(getter_AddRefs(window));
    395  if (!window) {
    396    return NS_ERROR_INVALID_ARG;
    397  }
    398 
    399  nsCOMPtr<nsPIDOMWindowOuter> top =
    400      nsGlobalWindowOuter::Cast(window)
    401          ->GetTopExcludingExtensionAccessibleContentFrames(aURIBeingLoaded);
    402  top.forget(aWin);
    403  return NS_OK;
    404 }
    405 
    406 // Get the base domain for aHostURI; e.g. for "www.bbc.co.uk", this would be
    407 // "bbc.co.uk". Only properly-formed URI's are tolerated, though a trailing
    408 // dot may be present. If aHostURI is an IP address, an alias such as
    409 // 'localhost', an eTLD such as 'co.uk', or the empty string, aBaseDomain will
    410 // be the exact host. Blob URIs will incur a lookup for their blob URL entry,
    411 // and will perform the same construction from their principal's base domain.
    412 // The result of this function should only be used in exact
    413 // string comparisons, since substring comparisons will not be valid for the
    414 // special cases elided above.
    415 NS_IMETHODIMP
    416 ThirdPartyUtil::GetBaseDomain(nsIURI* aHostURI, nsACString& aBaseDomain) {
    417  if (!aHostURI) {
    418    return NS_ERROR_INVALID_ARG;
    419  }
    420 
    421  // First, get the base domain from aHostURI. In the common case, this is
    422  // direct. For blob URLs we get this from the blob url's entry in the blob url
    423  // store.
    424  nsresult rv;
    425  nsCOMPtr<nsIPrincipal> blobPrincipal;
    426  if (aHostURI->SchemeIs("blob")) {
    427    if (BlobURLProtocolHandler::GetBlobURLPrincipal(
    428            aHostURI, getter_AddRefs(blobPrincipal))) {
    429      // If the blob URL is expired, this will be the uuid of a NullPrincipal
    430      rv = blobPrincipal->GetBaseDomain(aBaseDomain);
    431    } else {
    432      // If the blob is expired and no longer has a map entry, we fail
    433      rv = nsresult::NS_ERROR_DOM_BAD_URI;
    434    }
    435  } else {
    436    rv = mTLDService->GetBaseDomain(aHostURI, 0, aBaseDomain);
    437  }
    438 
    439  // If the URI is a null principal, we get the UUID portion instead of the base
    440  // domain because a null principal uri doesn't have a base domain.
    441  if (aHostURI->SchemeIs(NS_NULLPRINCIPAL_SCHEME)) {
    442    rv = aHostURI->GetFilePath(aBaseDomain);
    443  }
    444 
    445  // Get the base domain. this will fail if the host contains a leading dot,
    446  // more than one trailing dot, or is otherwise malformed.
    447  if (rv == NS_ERROR_HOST_IS_IP_ADDRESS ||
    448      rv == NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS) {
    449    // aHostURI is either an IP address, an alias such as 'localhost', an eTLD
    450    // such as 'co.uk', or the empty string. Uses the normalized host in such
    451    // cases.
    452 
    453    // The aHostURI can be a view-source URI, in which case we need to get the
    454    // base domain from the inner most URI.
    455    if (aHostURI->SchemeIs("view-source")) {
    456      rv = NS_GetInnermostURIHost(aHostURI, aBaseDomain);
    457    } else {
    458      rv = aHostURI->GetAsciiHost(aBaseDomain);
    459    }
    460  }
    461 
    462  if (NS_FAILED(rv)) {
    463    return rv;
    464  }
    465 
    466  // aHostURI (and thus aBaseDomain) may be the string '.'. If so, fail.
    467  if (aBaseDomain.Length() == 1 && aBaseDomain.Last() == '.') {
    468    return NS_ERROR_INVALID_ARG;
    469  }
    470 
    471  // Reject any URIs without a host that aren't file:// URIs. This makes it the
    472  // only way we can get a base domain consisting of the empty string, which
    473  // means we can safely perform foreign tests on such URIs where "not foreign"
    474  // means "the involved URIs are all file://".
    475  if (aBaseDomain.IsEmpty() && !aHostURI->SchemeIs("file")) {
    476    return NS_ERROR_INVALID_ARG;
    477  }
    478 
    479  return NS_OK;
    480 }
    481 
    482 NS_IMETHODIMP_(ThirdPartyAnalysisResult)
    483 ThirdPartyUtil::AnalyzeChannel(nsIChannel* aChannel, bool aNotify, nsIURI* aURI,
    484                               RequireThirdPartyCheck aRequireThirdPartyCheck,
    485                               uint32_t* aRejectedReason) {
    486  MOZ_ASSERT_IF(aNotify, aRejectedReason);
    487 
    488  ThirdPartyAnalysisResult result;
    489 
    490  nsCOMPtr<nsIURI> uri;
    491  if (!aURI && aChannel) {
    492    aChannel->GetURI(getter_AddRefs(uri));
    493  }
    494  nsCOMPtr<nsILoadInfo> loadInfo = aChannel ? aChannel->LoadInfo() : nullptr;
    495 
    496  bool isForeign = true;
    497  if (aChannel &&
    498      (!aRequireThirdPartyCheck || aRequireThirdPartyCheck(loadInfo))) {
    499    IsThirdPartyChannel(aChannel, aURI ? aURI : uri.get(), &isForeign);
    500  }
    501  if (isForeign) {
    502    result += ThirdPartyAnalysis::IsForeign;
    503  }
    504 
    505  nsCOMPtr<nsIClassifiedChannel> classifiedChannel =
    506      do_QueryInterface(aChannel);
    507  if (classifiedChannel) {
    508    if (classifiedChannel->IsThirdPartyTrackingResource()) {
    509      result += ThirdPartyAnalysis::IsThirdPartyTrackingResource;
    510    }
    511    if (classifiedChannel->IsThirdPartySocialTrackingResource()) {
    512      result += ThirdPartyAnalysis::IsThirdPartySocialTrackingResource;
    513    }
    514 
    515    // Check first-party storage access even for non-tracking resources, since
    516    // we will need the result when computing the access rights for the reject
    517    // foreign cookie behavior mode.
    518 
    519    // If the caller has requested third-party checks, we will only perform the
    520    // storage access check once we know we're in the third-party context.
    521    bool performStorageChecks =
    522        aRequireThirdPartyCheck ? result.contains(ThirdPartyAnalysis::IsForeign)
    523                                : true;
    524    if (performStorageChecks &&
    525        ShouldAllowAccessFor(aChannel, aURI ? aURI : uri.get(),
    526                             aRejectedReason)) {
    527      result += ThirdPartyAnalysis::IsStorageAccessPermissionGranted;
    528    }
    529 
    530    if (aNotify && !result.contains(
    531                       ThirdPartyAnalysis::IsStorageAccessPermissionGranted)) {
    532      ContentBlockingNotifier::OnDecision(
    533          aChannel, ContentBlockingNotifier::BlockingDecision::eBlock,
    534          *aRejectedReason);
    535    }
    536  }
    537 
    538  return result;
    539 }