diff options
Diffstat (limited to 'toolkit/components/antitracking/StorageAccess.cpp')
-rw-r--r-- | toolkit/components/antitracking/StorageAccess.cpp | 916 |
1 files changed, 916 insertions, 0 deletions
diff --git a/toolkit/components/antitracking/StorageAccess.cpp b/toolkit/components/antitracking/StorageAccess.cpp new file mode 100644 index 0000000000..4bdd7eccc1 --- /dev/null +++ b/toolkit/components/antitracking/StorageAccess.cpp @@ -0,0 +1,916 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "StorageAccess.h" + +#include "mozilla/BasePrincipal.h" +#include "mozilla/Components.h" +#include "mozilla/dom/Document.h" +#include "mozilla/net/CookieJarSettings.h" +#include "mozilla/PermissionManager.h" +#include "mozilla/StaticPrefs_browser.h" +#include "mozilla/StaticPrefs_network.h" +#include "mozilla/StaticPrefs_privacy.h" +#include "mozilla/StorageAccess.h" +#include "nsAboutProtocolUtils.h" +#include "nsContentUtils.h" +#include "nsGlobalWindowInner.h" +#include "nsICookiePermission.h" +#include "nsICookieService.h" +#include "nsICookieJarSettings.h" +#include "nsIHttpChannel.h" +#include "nsIPermission.h" +#include "nsIWebProgressListener.h" +#include "nsIClassifiedChannel.h" +#include "nsNetUtil.h" +#include "nsScriptSecurityManager.h" +#include "nsSandboxFlags.h" +#include "AntiTrackingUtils.h" +#include "AntiTrackingLog.h" +#include "ContentBlockingAllowList.h" +#include "mozIThirdPartyUtil.h" +#include "RejectForeignAllowList.h" + +using namespace mozilla; +using namespace mozilla::dom; +using mozilla::net::CookieJarSettings; + +// This internal method returns ACCESS_DENY if the access is denied, +// ACCESS_DEFAULT if unknown, some other access code if granted. +uint32_t mozilla::detail::CheckCookiePermissionForPrincipal( + nsICookieJarSettings* aCookieJarSettings, nsIPrincipal* aPrincipal) { + MOZ_ASSERT(aCookieJarSettings); + MOZ_ASSERT(aPrincipal); + + uint32_t cookiePermission = nsICookiePermission::ACCESS_DEFAULT; + if (!aPrincipal->GetIsContentPrincipal()) { + return cookiePermission; + } + + nsresult rv = + aCookieJarSettings->CookiePermission(aPrincipal, &cookiePermission); + if (NS_WARN_IF(NS_FAILED(rv))) { + return nsICookiePermission::ACCESS_DEFAULT; + } + + // If we have a custom cookie permission, let's use it. + return cookiePermission; +} + +/* + * Checks if storage for a given principal is permitted by the user's + * preferences. If aWindow is non-null, its principal must be passed as + * aPrincipal, and the third-party iframe and sandboxing status of the window + * are also checked. If aURI is non-null, then it is used as the comparison + * against aWindow to determine if this is a third-party load. We also + * allow a channel instead of the window reference when determining 3rd party + * status. + * + * Used in the implementation of StorageAllowedForWindow, + * StorageAllowedForDocument, StorageAllowedForChannel and + * StorageAllowedForServiceWorker. + */ +static StorageAccess InternalStorageAllowedCheck( + nsIPrincipal* aPrincipal, nsPIDOMWindowInner* aWindow, nsIURI* aURI, + nsIChannel* aChannel, nsICookieJarSettings* aCookieJarSettings, + uint32_t& aRejectedReason) { + MOZ_ASSERT(aPrincipal); + + aRejectedReason = 0; + + StorageAccess access = StorageAccess::eAllow; + + // We don't allow storage on the null principal, in general. Even if the + // calling context is chrome. + if (aPrincipal->GetIsNullPrincipal()) { + return StorageAccess::eDeny; + } + + nsCOMPtr<nsIURI> documentURI; + if (aWindow) { + // If the document is sandboxed, then it is not permitted to use storage + Document* document = aWindow->GetExtantDoc(); + if (document && document->GetSandboxFlags() & SANDBOXED_ORIGIN) { + return StorageAccess::eDeny; + } + + // Check if we are in private browsing, and record that fact + if (nsContentUtils::IsInPrivateBrowsing(document)) { + access = StorageAccess::ePrivateBrowsing; + } + + // Get the document URI for the below about: URI check. + documentURI = document ? document->GetDocumentURI() : nullptr; + } + + // About URIs are allowed to access storage, even if they don't have chrome + // privileges. If this is not desired, than the consumer will have to + // implement their own restriction functionality. + // + // This is due to backwards-compatibility and the state of storage access + // before the introducton of InternalStorageAllowedCheck: + // + // BEFORE: + // localStorage, caches: allowed in 3rd-party iframes always + // IndexedDB: allowed in 3rd-party iframes only if 3rd party URI is an about: + // URI within a specific allowlist + // + // AFTER: + // localStorage, caches: allowed in 3rd-party iframes by default. Preference + // can be set to disable in 3rd-party, which will not disallow in about: + // URIs. + // IndexedDB: allowed in 3rd-party iframes by default. Preference can be set + // to disable in 3rd-party, which will disallow in about: URIs, unless they + // are within a specific allowlist. + // + // This means that behavior for storage with internal about: URIs should not + // be affected, which is desireable due to the lack of automated testing for + // about: URIs with these preferences set, and the importance of the correct + // functioning of these URIs even with custom preferences. + // + // We need to check the aURI or the document URI here instead of only checking + // the URI from the principal. Because the principal might not have a URI if + // it is a system principal. + if ((aURI && aURI->SchemeIs("about") && + !NS_IsContentAccessibleAboutURI(aURI)) || + (documentURI && documentURI->SchemeIs("about") && + !NS_IsContentAccessibleAboutURI(documentURI)) || + aPrincipal->SchemeIs("about")) { + return access; + } + + if (!StorageDisabledByAntiTracking(aWindow, aChannel, aPrincipal, aURI, + aRejectedReason)) { + return access; + } + + // We want to have a partitioned storage only for trackers. + if (aRejectedReason == + static_cast<uint32_t>( + nsIWebProgressListener::STATE_COOKIES_BLOCKED_TRACKER) || + aRejectedReason == + static_cast<uint32_t>( + nsIWebProgressListener::STATE_COOKIES_BLOCKED_SOCIALTRACKER)) { + return StorageAccess::ePartitionTrackersOrDeny; + } + + // We want to have a partitioned storage for all third parties. + if (aRejectedReason == + static_cast<uint32_t>( + nsIWebProgressListener::STATE_COOKIES_PARTITIONED_FOREIGN)) { + return StorageAccess::ePartitionForeignOrDeny; + } + + return StorageAccess::eDeny; +} + +/** + * Wrapper around InternalStorageAllowedCheck which caches the check result on + * the inner window to improve performance. nsGlobalWindowInner is responsible + * for invalidating the cache state if storage access changes during window + * lifetime. + */ +static StorageAccess InternalStorageAllowedCheckCached( + nsIPrincipal* aPrincipal, nsPIDOMWindowInner* aWindow, nsIURI* aURI, + nsIChannel* aChannel, nsICookieJarSettings* aCookieJarSettings, + uint32_t& aRejectedReason) { + // If enabled, check if we have already computed the storage access field + // for this window. This avoids repeated calls to + // InternalStorageAllowedCheck. + nsGlobalWindowInner* win = nullptr; + if (aWindow) { + win = nsGlobalWindowInner::Cast(aWindow); + + Maybe<StorageAccess> storageAccess = + win->GetStorageAllowedCache(aRejectedReason); + if (storageAccess.isSome()) { + return storageAccess.value(); + } + } + + StorageAccess result = InternalStorageAllowedCheck( + aPrincipal, aWindow, aURI, aChannel, aCookieJarSettings, aRejectedReason); + if (win) { + // Remember check result for the lifetime of the window. It's the windows + // responsibility to invalidate this field if storage access changes + // because a storage access permission is granted. + win->SetStorageAllowedCache(result, aRejectedReason); + } + + return result; +} + +static bool StorageDisabledByAntiTrackingInternal( + nsPIDOMWindowInner* aWindow, nsIChannel* aChannel, nsIPrincipal* aPrincipal, + nsIURI* aURI, nsICookieJarSettings* aCookieJarSettings, + uint32_t& aRejectedReason) { + MOZ_ASSERT(aWindow || aChannel || aPrincipal); + + if (aWindow) { + nsIURI* documentURI = aURI ? aURI : aWindow->GetDocumentURI(); + return !documentURI || + !ShouldAllowAccessFor(aWindow, documentURI, &aRejectedReason); + } + + if (aChannel) { + nsCOMPtr<nsIURI> uri; + nsresult rv = aChannel->GetURI(getter_AddRefs(uri)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return false; + } + + return !ShouldAllowAccessFor(aChannel, uri, &aRejectedReason); + } + + MOZ_ASSERT(aPrincipal); + return !ShouldAllowAccessFor(aPrincipal, aCookieJarSettings); +} + +namespace mozilla { + +StorageAccess StorageAllowedForWindow(nsPIDOMWindowInner* aWindow, + uint32_t* aRejectedReason) { + uint32_t rejectedReason; + if (!aRejectedReason) { + aRejectedReason = &rejectedReason; + } + + *aRejectedReason = 0; + + if (Document* document = aWindow->GetExtantDoc()) { + nsCOMPtr<nsIPrincipal> principal = document->NodePrincipal(); + // Note that GetChannel() below may return null, but that's OK, since the + // callee is able to deal with a null channel argument, and if passed null, + // will only fail to notify the UI in case storage gets blocked. + nsIChannel* channel = document->GetChannel(); + return InternalStorageAllowedCheckCached( + principal, aWindow, nullptr, channel, document->CookieJarSettings(), + *aRejectedReason); + } + + // No document? Try checking Private Browsing Mode without document + if (const nsCOMPtr<nsIGlobalObject> global = aWindow->AsGlobal()) { + if (const nsCOMPtr<nsIPrincipal> principal = global->PrincipalOrNull()) { + if (principal->GetPrivateBrowsingId() > 0) { + return StorageAccess::ePrivateBrowsing; + } + } + } + + // Everything failed? Let's return a generic rejected reason. + return StorageAccess::eDeny; +} + +StorageAccess StorageAllowedForDocument(const Document* aDoc) { + StorageAccess cookieAllowed = CookieAllowedForDocument(aDoc); + if (StaticPrefs:: + privacy_partition_always_partition_third_party_non_cookie_storage() && + cookieAllowed > StorageAccess::eDeny) { + return StorageAccess::ePartitionForeignOrDeny; + } + return cookieAllowed; +} + +StorageAccess CookieAllowedForDocument(const Document* aDoc) { + MOZ_ASSERT(aDoc); + + if (nsPIDOMWindowInner* inner = aDoc->GetInnerWindow()) { + nsCOMPtr<nsIPrincipal> principal = aDoc->NodePrincipal(); + // Note that GetChannel() below may return null, but that's OK, since the + // callee is able to deal with a null channel argument, and if passed null, + // will only fail to notify the UI in case storage gets blocked. + nsIChannel* channel = aDoc->GetChannel(); + + uint32_t rejectedReason = 0; + return InternalStorageAllowedCheckCached( + principal, inner, nullptr, channel, + const_cast<Document*>(aDoc)->CookieJarSettings(), rejectedReason); + } + + return StorageAccess::eDeny; +} + +StorageAccess StorageAllowedForNewWindow(nsIPrincipal* aPrincipal, nsIURI* aURI, + nsPIDOMWindowInner* aParent) { + MOZ_ASSERT(aPrincipal); + MOZ_ASSERT(aURI); + // parent may be nullptr + + uint32_t rejectedReason = 0; + nsCOMPtr<nsICookieJarSettings> cjs; + if (aParent && aParent->GetExtantDoc()) { + cjs = aParent->GetExtantDoc()->CookieJarSettings(); + } else { + cjs = net::CookieJarSettings::Create(aPrincipal); + } + return InternalStorageAllowedCheck(aPrincipal, aParent, aURI, nullptr, cjs, + rejectedReason); +} + +StorageAccess StorageAllowedForChannel(nsIChannel* aChannel) { + MOZ_DIAGNOSTIC_ASSERT(nsContentUtils::GetSecurityManager()); + MOZ_DIAGNOSTIC_ASSERT(aChannel); + + nsCOMPtr<nsIPrincipal> principal; + Unused << nsContentUtils::GetSecurityManager()->GetChannelResultPrincipal( + aChannel, getter_AddRefs(principal)); + NS_ENSURE_TRUE(principal, StorageAccess::eDeny); + + nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo(); + nsCOMPtr<nsICookieJarSettings> cookieJarSettings; + nsresult rv = + loadInfo->GetCookieJarSettings(getter_AddRefs(cookieJarSettings)); + NS_ENSURE_SUCCESS(rv, StorageAccess::eDeny); + + uint32_t rejectedReason = 0; + StorageAccess result = InternalStorageAllowedCheck( + principal, nullptr, nullptr, aChannel, cookieJarSettings, rejectedReason); + + return result; +} + +StorageAccess StorageAllowedForServiceWorker( + nsIPrincipal* aPrincipal, nsICookieJarSettings* aCookieJarSettings) { + uint32_t rejectedReason = 0; + return InternalStorageAllowedCheck(aPrincipal, nullptr, nullptr, nullptr, + aCookieJarSettings, rejectedReason); +} + +bool StorageDisabledByAntiTracking(nsPIDOMWindowInner* aWindow, + nsIChannel* aChannel, + nsIPrincipal* aPrincipal, nsIURI* aURI, + uint32_t& aRejectedReason) { + MOZ_ASSERT(aWindow || aChannel || aPrincipal); + nsCOMPtr<nsICookieJarSettings> cookieJarSettings; + if (aWindow) { + if (aWindow->GetExtantDoc()) { + cookieJarSettings = aWindow->GetExtantDoc()->CookieJarSettings(); + } + } else if (aChannel) { + nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo(); + Unused << loadInfo->GetCookieJarSettings(getter_AddRefs(cookieJarSettings)); + } + if (!cookieJarSettings) { + cookieJarSettings = net::CookieJarSettings::Create(aPrincipal); + } + bool disabled = StorageDisabledByAntiTrackingInternal( + aWindow, aChannel, aPrincipal, aURI, cookieJarSettings, aRejectedReason); + + if (aWindow) { + ContentBlockingNotifier::OnDecision( + aWindow, + disabled ? ContentBlockingNotifier::BlockingDecision::eBlock + : ContentBlockingNotifier::BlockingDecision::eAllow, + aRejectedReason); + } else if (aChannel) { + ContentBlockingNotifier::OnDecision( + aChannel, + disabled ? ContentBlockingNotifier::BlockingDecision::eBlock + : ContentBlockingNotifier::BlockingDecision::eAllow, + aRejectedReason); + } + return disabled; +} + +bool StorageDisabledByAntiTracking(dom::Document* aDocument, nsIURI* aURI, + uint32_t& aRejectedReason) { + aRejectedReason = 0; + // Note that GetChannel() below may return null, but that's OK, since the + // callee is able to deal with a null channel argument, and if passed null, + // will only fail to notify the UI in case storage gets blocked. + return StorageDisabledByAntiTracking( + aDocument->GetInnerWindow(), aDocument->GetChannel(), + aDocument->NodePrincipal(), aURI, aRejectedReason); +} + +bool ShouldPartitionStorage(StorageAccess aAccess) { + return aAccess == StorageAccess::ePartitionTrackersOrDeny || + aAccess == StorageAccess::ePartitionForeignOrDeny; +} + +bool ShouldPartitionStorage(uint32_t aRejectedReason) { + return aRejectedReason == + static_cast<uint32_t>( + nsIWebProgressListener::STATE_COOKIES_BLOCKED_TRACKER) || + aRejectedReason == + static_cast<uint32_t>( + nsIWebProgressListener::STATE_COOKIES_BLOCKED_SOCIALTRACKER) || + aRejectedReason == + static_cast<uint32_t>( + nsIWebProgressListener::STATE_COOKIES_PARTITIONED_FOREIGN); +} + +bool StoragePartitioningEnabled(StorageAccess aAccess, + nsICookieJarSettings* aCookieJarSettings) { + return aAccess == StorageAccess::ePartitionForeignOrDeny && + aCookieJarSettings->GetCookieBehavior() == + nsICookieService::BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN; +} + +bool StoragePartitioningEnabled(uint32_t aRejectedReason, + nsICookieJarSettings* aCookieJarSettings) { + return aRejectedReason == + static_cast<uint32_t>( + nsIWebProgressListener::STATE_COOKIES_PARTITIONED_FOREIGN) && + aCookieJarSettings->GetCookieBehavior() == + nsICookieService::BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN; +} + +int32_t CookiesBehavior(Document* a3rdPartyDocument) { + MOZ_ASSERT(a3rdPartyDocument); + + // WebExtensions principals always get BEHAVIOR_ACCEPT as cookieBehavior + // (See Bug 1406675 and Bug 1525917 for rationale). + if (BasePrincipal::Cast(a3rdPartyDocument->NodePrincipal())->AddonPolicy()) { + return nsICookieService::BEHAVIOR_ACCEPT; + } + + return a3rdPartyDocument->CookieJarSettings()->GetCookieBehavior(); +} + +int32_t CookiesBehavior(nsILoadInfo* aLoadInfo, nsIURI* a3rdPartyURI) { + MOZ_ASSERT(aLoadInfo); + MOZ_ASSERT(a3rdPartyURI); + + // WebExtensions 3rd party URI always get BEHAVIOR_ACCEPT as cookieBehavior, + // this is semantically equivalent to the principal having a AddonPolicy(). + if (a3rdPartyURI->SchemeIs("moz-extension")) { + return nsICookieService::BEHAVIOR_ACCEPT; + } + + nsCOMPtr<nsICookieJarSettings> cookieJarSettings; + nsresult rv = + aLoadInfo->GetCookieJarSettings(getter_AddRefs(cookieJarSettings)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return nsICookieService::BEHAVIOR_REJECT; + } + + return cookieJarSettings->GetCookieBehavior(); +} + +int32_t CookiesBehavior(nsIPrincipal* aPrincipal, + nsICookieJarSettings* aCookieJarSettings) { + MOZ_ASSERT(aPrincipal); + MOZ_ASSERT(aCookieJarSettings); + + // WebExtensions principals always get BEHAVIOR_ACCEPT as cookieBehavior + // (See Bug 1406675 for rationale). + if (BasePrincipal::Cast(aPrincipal)->AddonPolicy()) { + return nsICookieService::BEHAVIOR_ACCEPT; + } + + return aCookieJarSettings->GetCookieBehavior(); +} + +bool ShouldAllowAccessFor(nsPIDOMWindowInner* aWindow, nsIURI* aURI, + uint32_t* aRejectedReason) { + MOZ_ASSERT(aWindow); + MOZ_ASSERT(aURI); + + // Let's avoid a null check on aRejectedReason everywhere else. + uint32_t rejectedReason = 0; + if (!aRejectedReason) { + aRejectedReason = &rejectedReason; + } + + LOG_SPEC(("Computing whether window %p has access to URI %s", aWindow, _spec), + aURI); + + nsGlobalWindowInner* innerWindow = nsGlobalWindowInner::Cast(aWindow); + Document* document = innerWindow->GetExtantDoc(); + if (!document) { + LOG(("Our window has no document")); + return false; + } + + uint32_t cookiePermission = detail::CheckCookiePermissionForPrincipal( + document->CookieJarSettings(), document->NodePrincipal()); + if (cookiePermission != nsICookiePermission::ACCESS_DEFAULT) { + LOG( + ("CheckCookiePermissionForPrincipal() returned a non-default access " + "code (%d) for window's principal, returning %s", + int(cookiePermission), + cookiePermission != nsICookiePermission::ACCESS_DENY ? "success" + : "failure")); + if (cookiePermission != nsICookiePermission::ACCESS_DENY) { + return true; + } + + *aRejectedReason = + nsIWebProgressListener::STATE_COOKIES_BLOCKED_BY_PERMISSION; + return false; + } + + int32_t behavior = CookiesBehavior(document); + if (behavior == nsICookieService::BEHAVIOR_ACCEPT) { + LOG(("The cookie behavior pref mandates accepting all cookies!")); + return true; + } + + if (ContentBlockingAllowList::Check(aWindow)) { + return true; + } + + if (behavior == nsICookieService::BEHAVIOR_REJECT) { + LOG(("The cookie behavior pref mandates rejecting all cookies!")); + *aRejectedReason = nsIWebProgressListener::STATE_COOKIES_BLOCKED_ALL; + return false; + } + + // As a performance optimization, we only perform this check for + // BEHAVIOR_REJECT_FOREIGN and BEHAVIOR_LIMIT_FOREIGN. For + // BEHAVIOR_REJECT_TRACKER and BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN, + // third-partiness is implicily checked later below. + if (behavior != nsICookieService::BEHAVIOR_REJECT_TRACKER && + behavior != + nsICookieService::BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN) { + // Let's check if this is a 3rd party context. + if (!AntiTrackingUtils::IsThirdPartyWindow(aWindow, aURI)) { + LOG(("Our window isn't a third-party window")); + return true; + } + } + + if ((behavior == nsICookieService::BEHAVIOR_REJECT_FOREIGN && + !CookieJarSettings::IsRejectThirdPartyWithExceptions(behavior)) || + behavior == nsICookieService::BEHAVIOR_LIMIT_FOREIGN) { + // XXX For non-cookie forms of storage, we handle BEHAVIOR_LIMIT_FOREIGN by + // simply rejecting the request to use the storage. In the future, if we + // change the meaning of BEHAVIOR_LIMIT_FOREIGN to be one which makes sense + // for non-cookie storage types, this may change. + LOG(("Nothing more to do due to the behavior code %d", int(behavior))); + *aRejectedReason = nsIWebProgressListener::STATE_COOKIES_BLOCKED_FOREIGN; + return false; + } + + // The document has been allowlisted. We can return from here directly. + if (document->HasStorageAccessPermissionGrantedByAllowList()) { + return true; + } + + MOZ_ASSERT( + CookieJarSettings::IsRejectThirdPartyWithExceptions(behavior) || + behavior == nsICookieService::BEHAVIOR_REJECT_TRACKER || + behavior == + nsICookieService::BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN); + + uint32_t blockedReason = + nsIWebProgressListener::STATE_COOKIES_BLOCKED_TRACKER; + + if (behavior == nsICookieService::BEHAVIOR_REJECT_TRACKER) { + if (!nsContentUtils::IsThirdPartyTrackingResourceWindow(aWindow)) { + LOG(("Our window isn't a third-party tracking window")); + return true; + } + + nsCOMPtr<nsIClassifiedChannel> classifiedChannel = + do_QueryInterface(document->GetChannel()); + if (classifiedChannel) { + uint32_t classificationFlags = + classifiedChannel->GetThirdPartyClassificationFlags(); + if (classificationFlags & nsIClassifiedChannel::ClassificationFlags:: + CLASSIFIED_SOCIALTRACKING) { + blockedReason = + nsIWebProgressListener::STATE_COOKIES_BLOCKED_SOCIALTRACKER; + } + } + } else if (behavior == + nsICookieService::BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN) { + if (nsContentUtils::IsThirdPartyTrackingResourceWindow(aWindow)) { + // fall through + } else if (AntiTrackingUtils::IsThirdPartyWindow(aWindow, aURI)) { + LOG(("We're in the third-party context, storage should be partitioned")); + // fall through, but remember that we're partitioning. + blockedReason = nsIWebProgressListener::STATE_COOKIES_PARTITIONED_FOREIGN; + } else { + LOG(("Our window isn't a third-party window, storage is allowed")); + return true; + } + } else { + MOZ_ASSERT(CookieJarSettings::IsRejectThirdPartyWithExceptions(behavior)); + if (RejectForeignAllowList::Check(document)) { + LOG(("This window is exceptionlisted for reject foreign")); + return true; + } + + blockedReason = nsIWebProgressListener::STATE_COOKIES_PARTITIONED_FOREIGN; + } + + Document* doc = aWindow->GetExtantDoc(); + // Make sure storage access isn't disabled + if (doc && (doc->StorageAccessSandboxed())) { + LOG(("Our document is sandboxed")); + *aRejectedReason = blockedReason; + return false; + } + + // Document::HasStoragePermission first checks if storage access granted is + // cached in the inner window, if no, it then checks the storage permission + // flag in the channel's loadinfo + bool allowed = document->HasStorageAccessPermissionGranted(); + + if (!allowed) { + *aRejectedReason = blockedReason; + } else { + if (MOZ_LOG_TEST(gAntiTrackingLog, mozilla::LogLevel::Debug) && + aWindow->HasStorageAccessPermissionGranted()) { + LOG(("Permission stored in the window. All good.")); + } + } + + return allowed; +} + +bool ShouldAllowAccessFor(nsIChannel* aChannel, nsIURI* aURI, + uint32_t* aRejectedReason) { + MOZ_ASSERT(aURI); + MOZ_ASSERT(aChannel); + + // Let's avoid a null check on aRejectedReason everywhere else. + uint32_t rejectedReason = 0; + if (!aRejectedReason) { + aRejectedReason = &rejectedReason; + } + + nsIScriptSecurityManager* ssm = + nsScriptSecurityManager::GetScriptSecurityManager(); + MOZ_ASSERT(ssm); + + nsCOMPtr<nsIURI> channelURI; + nsresult rv = NS_GetFinalChannelURI(aChannel, getter_AddRefs(channelURI)); + if (NS_FAILED(rv)) { + LOG(("Failed to get the channel final URI, bail out early")); + return true; + } + LOG_SPEC( + ("Computing whether channel %p has access to URI %s", aChannel, _spec), + channelURI); + + nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo(); + nsCOMPtr<nsICookieJarSettings> cookieJarSettings; + rv = loadInfo->GetCookieJarSettings(getter_AddRefs(cookieJarSettings)); + if (NS_WARN_IF(NS_FAILED(rv))) { + LOG( + ("Failed to get the cookie jar settings from the loadinfo, bail out " + "early")); + return true; + } + + nsCOMPtr<nsIPrincipal> channelPrincipal; + rv = ssm->GetChannelURIPrincipal(aChannel, getter_AddRefs(channelPrincipal)); + if (NS_WARN_IF(NS_FAILED(rv))) { + LOG(("No channel principal, bail out early")); + return false; + } + + uint32_t cookiePermission = detail::CheckCookiePermissionForPrincipal( + cookieJarSettings, channelPrincipal); + if (cookiePermission != nsICookiePermission::ACCESS_DEFAULT) { + LOG( + ("CheckCookiePermissionForPrincipal() returned a non-default access " + "code (%d) for channel's principal, returning %s", + int(cookiePermission), + cookiePermission != nsICookiePermission::ACCESS_DENY ? "success" + : "failure")); + if (cookiePermission != nsICookiePermission::ACCESS_DENY) { + return true; + } + + *aRejectedReason = + nsIWebProgressListener::STATE_COOKIES_BLOCKED_BY_PERMISSION; + return false; + } + + if (!channelURI) { + LOG(("No channel uri, bail out early")); + return false; + } + + int32_t behavior = CookiesBehavior(loadInfo, channelURI); + if (behavior == nsICookieService::BEHAVIOR_ACCEPT) { + LOG(("The cookie behavior pref mandates accepting all cookies!")); + return true; + } + + nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aChannel); + + if (httpChannel && ContentBlockingAllowList::Check(httpChannel)) { + return true; + } + + if (behavior == nsICookieService::BEHAVIOR_REJECT) { + LOG(("The cookie behavior pref mandates rejecting all cookies!")); + *aRejectedReason = nsIWebProgressListener::STATE_COOKIES_BLOCKED_ALL; + return false; + } + + nsCOMPtr<mozIThirdPartyUtil> thirdPartyUtil = + components::ThirdPartyUtil::Service(); + if (!thirdPartyUtil) { + LOG(("No thirdPartyUtil, bail out early")); + return true; + } + + bool thirdParty = false; + rv = thirdPartyUtil->IsThirdPartyChannel(aChannel, aURI, &thirdParty); + // Grant if it's not a 3rd party. + // Be careful to check the return value of IsThirdPartyChannel, since + // IsThirdPartyChannel() will fail if the channel's loading principal is the + // system principal... + if (NS_SUCCEEDED(rv) && !thirdParty) { + LOG(("Our channel isn't a third-party channel")); + return true; + } + + if ((behavior == nsICookieService::BEHAVIOR_REJECT_FOREIGN && + !CookieJarSettings::IsRejectThirdPartyWithExceptions(behavior)) || + behavior == nsICookieService::BEHAVIOR_LIMIT_FOREIGN) { + // XXX For non-cookie forms of storage, we handle BEHAVIOR_LIMIT_FOREIGN by + // simply rejecting the request to use the storage. In the future, if we + // change the meaning of BEHAVIOR_LIMIT_FOREIGN to be one which makes sense + // for non-cookie storage types, this may change. + LOG(("Nothing more to do due to the behavior code %d", int(behavior))); + *aRejectedReason = nsIWebProgressListener::STATE_COOKIES_BLOCKED_FOREIGN; + return false; + } + + // The channel has been allowlisted. We can return from here. + if (loadInfo->GetStoragePermission() == + nsILoadInfo::StoragePermissionAllowListed) { + return true; + } + + MOZ_ASSERT( + CookieJarSettings::IsRejectThirdPartyWithExceptions(behavior) || + behavior == nsICookieService::BEHAVIOR_REJECT_TRACKER || + behavior == + nsICookieService::BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN); + + uint32_t blockedReason = + nsIWebProgressListener::STATE_COOKIES_BLOCKED_TRACKER; + + // Not a tracker. + nsCOMPtr<nsIClassifiedChannel> classifiedChannel = + do_QueryInterface(aChannel); + if (behavior == nsICookieService::BEHAVIOR_REJECT_TRACKER) { + if (classifiedChannel) { + if (!classifiedChannel->IsThirdPartyTrackingResource()) { + LOG(("Our channel isn't a third-party tracking channel")); + return true; + } + + uint32_t classificationFlags = + classifiedChannel->GetThirdPartyClassificationFlags(); + if (classificationFlags & nsIClassifiedChannel::ClassificationFlags:: + CLASSIFIED_SOCIALTRACKING) { + blockedReason = + nsIWebProgressListener::STATE_COOKIES_BLOCKED_SOCIALTRACKER; + } + } + } else if (behavior == + nsICookieService::BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN) { + if (classifiedChannel && + classifiedChannel->IsThirdPartyTrackingResource()) { + // fall through + } else if (AntiTrackingUtils::IsThirdPartyChannel(aChannel)) { + LOG(("We're in the third-party context, storage should be partitioned")); + // fall through but remember that we're partitioning. + blockedReason = nsIWebProgressListener::STATE_COOKIES_PARTITIONED_FOREIGN; + } else { + LOG(("Our channel isn't a third-party channel, storage is allowed")); + return true; + } + } else { + MOZ_ASSERT(CookieJarSettings::IsRejectThirdPartyWithExceptions(behavior)); + if (httpChannel && RejectForeignAllowList::Check(httpChannel)) { + LOG(("This channel is exceptionlisted")); + return true; + } + blockedReason = nsIWebProgressListener::STATE_COOKIES_BLOCKED_FOREIGN; + } + + RefPtr<BrowsingContext> targetBC; + rv = loadInfo->GetTargetBrowsingContext(getter_AddRefs(targetBC)); + if (!targetBC || NS_WARN_IF(NS_FAILED(rv))) { + LOG(("Failed to get the channel's target browsing context")); + return false; + } + + if (Document::StorageAccessSandboxed(targetBC->GetSandboxFlags())) { + LOG(("Our document is sandboxed")); + *aRejectedReason = blockedReason; + return false; + } + + // Let's see if we have to grant the access for this particular channel. + + // HasStorageAccessPermissionGranted only applies to channels that load + // documents, for sub-resources loads, just returns the result from loadInfo. + bool isDocument = false; + aChannel->GetIsDocument(&isDocument); + + if (isDocument) { + nsCOMPtr<nsPIDOMWindowInner> inner = + AntiTrackingUtils::GetInnerWindow(targetBC); + if (inner && inner->HasStorageAccessPermissionGranted()) { + LOG(("Permission stored in the window. All good.")); + return true; + } + } + + bool allowed = + loadInfo->GetStoragePermission() != nsILoadInfo::NoStoragePermission; + if (!allowed) { + *aRejectedReason = blockedReason; + } + + return allowed; +} + +bool ShouldAllowAccessFor(nsIPrincipal* aPrincipal, + nsICookieJarSettings* aCookieJarSettings) { + MOZ_ASSERT(aPrincipal); + MOZ_ASSERT(aCookieJarSettings); + + uint32_t access = nsICookiePermission::ACCESS_DEFAULT; + if (aPrincipal->GetIsContentPrincipal()) { + PermissionManager* permManager = PermissionManager::GetInstance(); + if (permManager) { + Unused << NS_WARN_IF(NS_FAILED(permManager->TestPermissionFromPrincipal( + aPrincipal, "cookie"_ns, &access))); + } + } + + if (access != nsICookiePermission::ACCESS_DEFAULT) { + return access != nsICookiePermission::ACCESS_DENY; + } + + int32_t behavior = CookiesBehavior(aPrincipal, aCookieJarSettings); + return behavior != nsICookieService::BEHAVIOR_REJECT; +} + +/* static */ +bool ApproximateAllowAccessForWithoutChannel( + nsPIDOMWindowInner* aFirstPartyWindow, nsIURI* aURI) { + MOZ_ASSERT(aFirstPartyWindow); + MOZ_ASSERT(aURI); + + LOG_SPEC( + ("Computing a best guess as to whether window %p has access to URI %s", + aFirstPartyWindow, _spec), + aURI); + + Document* parentDocument = + nsGlobalWindowInner::Cast(aFirstPartyWindow)->GetExtantDoc(); + if (NS_WARN_IF(!parentDocument)) { + LOG(("Failed to get the first party window's document")); + return false; + } + + if (!parentDocument->CookieJarSettings()->GetRejectThirdPartyContexts()) { + LOG(("Disabled by the pref (%d), bail out early", + parentDocument->CookieJarSettings()->GetCookieBehavior())); + return true; + } + + if (ContentBlockingAllowList::Check(aFirstPartyWindow)) { + return true; + } + + if (!AntiTrackingUtils::IsThirdPartyWindow(aFirstPartyWindow, aURI)) { + LOG(("Our window isn't a third-party window")); + return true; + } + + uint32_t cookiePermission = detail::CheckCookiePermissionForPrincipal( + parentDocument->CookieJarSettings(), parentDocument->NodePrincipal()); + if (cookiePermission != nsICookiePermission::ACCESS_DEFAULT) { + LOG( + ("CheckCookiePermissionForPrincipal() returned a non-default access " + "code (%d), returning %s", + int(cookiePermission), + cookiePermission != nsICookiePermission::ACCESS_DENY ? "success" + : "failure")); + return cookiePermission != nsICookiePermission::ACCESS_DENY; + } + + nsAutoCString origin; + nsresult rv = nsContentUtils::GetASCIIOrigin(aURI, origin); + if (NS_WARN_IF(NS_FAILED(rv))) { + LOG_SPEC(("Failed to compute the origin from %s", _spec), aURI); + return false; + } + + nsIPrincipal* parentPrincipal = parentDocument->NodePrincipal(); + + nsAutoCString type; + AntiTrackingUtils::CreateStoragePermissionKey(origin, type); + + return AntiTrackingUtils::CheckStoragePermission( + parentPrincipal, type, + nsContentUtils::IsInPrivateBrowsing(parentDocument), nullptr, 0); +} +} // namespace mozilla |