/* -*- 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 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( nsIWebProgressListener::STATE_COOKIES_BLOCKED_TRACKER) || aRejectedReason == static_cast( nsIWebProgressListener::STATE_COOKIES_BLOCKED_SOCIALTRACKER)) { return StorageAccess::ePartitionTrackersOrDeny; } // We want to have a partitioned storage for all third parties. if (aRejectedReason == static_cast( 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 = 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 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 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 global = aWindow->AsGlobal()) { if (const nsCOMPtr 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 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(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 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 principal; Unused << nsContentUtils::GetSecurityManager()->GetChannelResultPrincipal( aChannel, getter_AddRefs(principal)); NS_ENSURE_TRUE(principal, StorageAccess::eDeny); nsCOMPtr loadInfo = aChannel->LoadInfo(); nsCOMPtr 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 cookieJarSettings; if (aWindow) { if (aWindow->GetExtantDoc()) { cookieJarSettings = aWindow->GetExtantDoc()->CookieJarSettings(); } } else if (aChannel) { nsCOMPtr 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( nsIWebProgressListener::STATE_COOKIES_BLOCKED_TRACKER) || aRejectedReason == static_cast( nsIWebProgressListener::STATE_COOKIES_BLOCKED_SOCIALTRACKER) || aRejectedReason == static_cast( 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( 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 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 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 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 loadInfo = aChannel->LoadInfo(); nsCOMPtr 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 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 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 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 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 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 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