summaryrefslogtreecommitdiffstats
path: root/toolkit/components/antitracking/ContentBlocking.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/antitracking/ContentBlocking.cpp')
-rw-r--r--toolkit/components/antitracking/ContentBlocking.cpp1324
1 files changed, 1324 insertions, 0 deletions
diff --git a/toolkit/components/antitracking/ContentBlocking.cpp b/toolkit/components/antitracking/ContentBlocking.cpp
new file mode 100644
index 0000000000..c82f1b13ea
--- /dev/null
+++ b/toolkit/components/antitracking/ContentBlocking.cpp
@@ -0,0 +1,1324 @@
+/* -*- 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 "AntiTrackingLog.h"
+#include "ContentBlocking.h"
+#include "AntiTrackingUtils.h"
+#include "TemporaryAccessGrantObserver.h"
+
+#include "mozilla/ContentBlockingAllowList.h"
+#include "mozilla/ContentBlockingUserInteraction.h"
+#include "mozilla/dom/BrowsingContext.h"
+#include "mozilla/dom/BrowsingContextGroup.h"
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/dom/WindowContext.h"
+#include "mozilla/dom/WindowGlobalParent.h"
+#include "mozilla/net/CookieJarSettings.h"
+#include "mozilla/PermissionManager.h"
+#include "mozilla/StaticPrefs_privacy.h"
+#include "mozilla/Telemetry.h"
+#include "mozIThirdPartyUtil.h"
+#include "nsContentUtils.h"
+#include "nsGlobalWindowInner.h"
+#include "nsIClassifiedChannel.h"
+#include "nsICookiePermission.h"
+#include "nsICookieService.h"
+#include "nsIPermission.h"
+#include "nsIPrincipal.h"
+#include "nsIURI.h"
+#include "nsIOService.h"
+#include "nsIWebProgressListener.h"
+#include "nsScriptSecurityManager.h"
+#include "RejectForeignAllowList.h"
+
+namespace mozilla {
+
+LazyLogModule gAntiTrackingLog("AntiTracking");
+
+}
+
+using namespace mozilla;
+using mozilla::dom::BrowsingContext;
+using mozilla::dom::ContentChild;
+using mozilla::dom::Document;
+using mozilla::dom::WindowGlobalParent;
+using mozilla::net::CookieJarSettings;
+
+namespace {
+
+bool GetTopLevelWindowId(BrowsingContext* aParentContext, uint32_t aBehavior,
+ uint64_t& aTopLevelInnerWindowId) {
+ MOZ_ASSERT(aParentContext);
+
+ aTopLevelInnerWindowId =
+ (aBehavior == nsICookieService::BEHAVIOR_REJECT_TRACKER)
+ ? AntiTrackingUtils::GetTopLevelStorageAreaWindowId(aParentContext)
+ : AntiTrackingUtils::GetTopLevelAntiTrackingWindowId(aParentContext);
+ return aTopLevelInnerWindowId != 0;
+}
+
+// This internal method returns ACCESS_DENY if the access is denied,
+// ACCESS_DEFAULT if unknown, some other access code if granted.
+uint32_t 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;
+}
+
+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();
+}
+} // namespace
+
+/* static */ RefPtr<ContentBlocking::StorageAccessPermissionGrantPromise>
+ContentBlocking::AllowAccessFor(
+ nsIPrincipal* aPrincipal, dom::BrowsingContext* aParentContext,
+ ContentBlockingNotifier::StorageAccessPermissionGrantedReason aReason,
+ const ContentBlocking::PerformFinalChecks& aPerformFinalChecks) {
+ MOZ_ASSERT(aParentContext);
+
+ switch (aReason) {
+ case ContentBlockingNotifier::eOpener:
+ if (!StaticPrefs::
+ privacy_restrict3rdpartystorage_heuristic_window_open()) {
+ LOG(
+ ("Bailing out early because the "
+ "privacy.restrict3rdpartystorage.heuristic.window_open preference "
+ "has been disabled"));
+ return StorageAccessPermissionGrantPromise::CreateAndReject(false,
+ __func__);
+ }
+ break;
+ case ContentBlockingNotifier::eOpenerAfterUserInteraction:
+ if (!StaticPrefs::
+ privacy_restrict3rdpartystorage_heuristic_opened_window_after_interaction()) {
+ LOG(
+ ("Bailing out early because the "
+ "privacy.restrict3rdpartystorage.heuristic.opened_window_after_"
+ "interaction preference has been disabled"));
+ return StorageAccessPermissionGrantPromise::CreateAndReject(false,
+ __func__);
+ }
+ break;
+ default:
+ break;
+ }
+
+ if (MOZ_LOG_TEST(gAntiTrackingLog, mozilla::LogLevel::Debug)) {
+ nsAutoCString origin;
+ aPrincipal->GetAsciiOrigin(origin);
+ LOG(("Adding a first-party storage exception for %s, triggered by %s",
+ PromiseFlatCString(origin).get(),
+ AntiTrackingUtils::GrantedReasonToString(aReason).get()));
+ }
+
+ RefPtr<dom::WindowContext> parentWindowContext =
+ aParentContext->GetCurrentWindowContext();
+ if (!parentWindowContext) {
+ LOG(
+ ("No window context found for our parent browsing context, bailing out "
+ "early"));
+ return StorageAccessPermissionGrantPromise::CreateAndReject(false,
+ __func__);
+ }
+
+ if (parentWindowContext->GetCookieBehavior().isNothing()) {
+ LOG(
+ ("No cookie behaviour found for our parent window context, bailing "
+ "out early"));
+ return StorageAccessPermissionGrantPromise::CreateAndReject(false,
+ __func__);
+ }
+
+ // Only add storage permission when there is a reason to do so.
+ uint32_t behavior = *parentWindowContext->GetCookieBehavior();
+ if (!CookieJarSettings::IsRejectThirdPartyContexts(behavior)) {
+ LOG(
+ ("Disabled by network.cookie.cookieBehavior pref (%d), bailing out "
+ "early",
+ behavior));
+ return StorageAccessPermissionGrantPromise::CreateAndResolve(true,
+ __func__);
+ }
+
+ MOZ_ASSERT(
+ CookieJarSettings::IsRejectThirdPartyWithExceptions(behavior) ||
+ behavior == nsICookieService::BEHAVIOR_REJECT_TRACKER ||
+ behavior ==
+ nsICookieService::BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN);
+
+ // No need to continue when we are already in the allow list.
+ if (parentWindowContext->GetIsOnContentBlockingAllowList()) {
+ return StorageAccessPermissionGrantPromise::CreateAndResolve(true,
+ __func__);
+ }
+
+ // Make sure storage access isn't disabled
+ if (!aParentContext->IsTopContent() &&
+ Document::StorageAccessSandboxed(aParentContext->GetSandboxFlags())) {
+ LOG(("Our document is sandboxed"));
+ return StorageAccessPermissionGrantPromise::CreateAndReject(false,
+ __func__);
+ }
+
+ bool isParentThirdParty = parentWindowContext->GetIsThirdPartyWindow();
+ uint64_t topLevelWindowId;
+ nsAutoCString trackingOrigin;
+ nsCOMPtr<nsIPrincipal> trackingPrincipal;
+
+ LOG(("The current resource is %s-party",
+ isParentThirdParty ? "third" : "first"));
+
+ // We are a first party resource.
+ if (!isParentThirdParty) {
+ nsAutoCString origin;
+ nsresult rv = aPrincipal->GetAsciiOrigin(origin);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ LOG(("Can't get the origin from the URI"));
+ return StorageAccessPermissionGrantPromise::CreateAndReject(false,
+ __func__);
+ }
+
+ trackingOrigin = origin;
+ trackingPrincipal = aPrincipal;
+ topLevelWindowId = aParentContext->GetCurrentInnerWindowId();
+ if (NS_WARN_IF(!topLevelWindowId)) {
+ LOG(("Top-level storage area window id not found, bailing out early"));
+ return StorageAccessPermissionGrantPromise::CreateAndReject(false,
+ __func__);
+ }
+
+ } else {
+ // We should be a 3rd party source.
+ if (behavior == nsICookieService::BEHAVIOR_REJECT_TRACKER &&
+ !parentWindowContext->GetIsThirdPartyTrackingResourceWindow()) {
+ LOG(("Our window isn't a third-party tracking window"));
+ return StorageAccessPermissionGrantPromise::CreateAndReject(false,
+ __func__);
+ }
+ if ((CookieJarSettings::IsRejectThirdPartyWithExceptions(behavior) ||
+ behavior ==
+ nsICookieService::BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN) &&
+ !isParentThirdParty) {
+ LOG(("Our window isn't a third-party window"));
+ return StorageAccessPermissionGrantPromise::CreateAndReject(false,
+ __func__);
+ }
+
+ if (!GetTopLevelWindowId(aParentContext,
+ // Don't request the ETP specific behaviour of
+ // allowing only singly-nested iframes here,
+ // because we are recording an allow permission.
+ nsICookieService::BEHAVIOR_ACCEPT,
+ topLevelWindowId)) {
+ LOG(("Error while retrieving the parent window id, bailing out early"));
+ return StorageAccessPermissionGrantPromise::CreateAndReject(false,
+ __func__);
+ }
+
+ // If we can't get the principal and tracking origin at this point, the
+ // tracking principal will be gotten while running ::CompleteAllowAccessFor
+ // in the parent.
+ if (aParentContext->IsInProcess()) {
+ if (!AntiTrackingUtils::GetPrincipalAndTrackingOrigin(
+ aParentContext, getter_AddRefs(trackingPrincipal),
+ trackingOrigin)) {
+ LOG(
+ ("Error while computing the parent principal and tracking origin, "
+ "bailing out early"));
+ return StorageAccessPermissionGrantPromise::CreateAndReject(false,
+ __func__);
+ }
+ }
+ }
+
+ // We MAY need information that is only accessible in the parent,
+ // so we need to determine whether we can run it in the current process (in
+ // most of cases it should be a child process).
+ //
+ // If the following two cases are both true, we can continue to run in
+ // the current process, otherwise, we need to ask the parent to continue
+ // the work.
+ // 1. aParentContext is an in-process browsing contex because we need the
+ // principal of the parent window.
+ // 2. tracking origin is not third-party with respect to the parent window
+ // (aParentContext). This is because we need to test whether the user
+ // has interacted with the tracking origin before, and this info is
+ // not supposed to be seen from cross-origin processes.
+
+ // The only case that aParentContext is not in-process is when the heuristic
+ // is triggered because of user interactions.
+ MOZ_ASSERT_IF(
+ !aParentContext->IsInProcess(),
+ aReason == ContentBlockingNotifier::eOpenerAfterUserInteraction);
+
+ bool runInSameProcess;
+ if (XRE_IsParentProcess()) {
+ // If we are already in the parent, then continue run in the parent.
+ runInSameProcess = true;
+ } else {
+ // Only continue to run in child processes when aParentContext is
+ // in-process and tracking origin is not third-party with respect to
+ // the parent window.
+ if (aParentContext->IsInProcess()) {
+ bool isThirdParty;
+ nsCOMPtr<nsIPrincipal> principal =
+ AntiTrackingUtils::GetPrincipal(aParentContext);
+ if (!principal) {
+ LOG(("Can't get the principal from the browsing context"));
+ return StorageAccessPermissionGrantPromise::CreateAndReject(false,
+ __func__);
+ }
+ Unused << trackingPrincipal->IsThirdPartyPrincipal(principal,
+ &isThirdParty);
+ runInSameProcess = !isThirdParty;
+ } else {
+ runInSameProcess = false;
+ }
+ }
+
+ if (runInSameProcess) {
+ return ContentBlocking::CompleteAllowAccessFor(
+ aParentContext, topLevelWindowId, trackingPrincipal, trackingOrigin,
+ behavior, aReason, aPerformFinalChecks);
+ }
+
+ MOZ_ASSERT(XRE_IsContentProcess());
+ // Only support PerformFinalChecks when we run ::CompleteAllowAccessFor in
+ // the same process. This callback is only used by eStorageAccessAPI,
+ // which is always runned in the same process.
+ MOZ_ASSERT(!aPerformFinalChecks);
+
+ ContentChild* cc = ContentChild::GetSingleton();
+ MOZ_ASSERT(cc);
+
+ RefPtr<BrowsingContext> bc = aParentContext;
+ return cc
+ ->SendCompleteAllowAccessFor(aParentContext, topLevelWindowId,
+ IPC::Principal(trackingPrincipal),
+ trackingOrigin, behavior, aReason)
+ ->Then(GetCurrentSerialEventTarget(), __func__,
+ [bc, trackingOrigin, behavior,
+ aReason](const ContentChild::CompleteAllowAccessForPromise::
+ ResolveOrRejectValue& aValue) {
+ if (aValue.IsResolve() && aValue.ResolveValue().isSome()) {
+ // we don't call OnAllowAccessFor in the parent when this is
+ // triggered by the opener heuristic, so we have to do it here.
+ // See storePermission below for the reason.
+ if (aReason == ContentBlockingNotifier::eOpener &&
+ !bc->IsDiscarded()) {
+ MOZ_ASSERT(bc->IsInProcess());
+ ContentBlocking::OnAllowAccessFor(bc, trackingOrigin,
+ behavior, aReason);
+ }
+ return StorageAccessPermissionGrantPromise::CreateAndResolve(
+ aValue.ResolveValue().value(), __func__);
+ }
+ return StorageAccessPermissionGrantPromise::CreateAndReject(
+ false, __func__);
+ });
+}
+
+// CompleteAllowAccessFor is used to process the remaining work in
+// AllowAccessFor that may need to access information not accessible
+// in the current process.
+// This API supports running running in the child process and the
+// parent process. When running in the child, aParentContext must be in-process.
+//
+// Here lists the possible cases based on our heuristics:
+// 1. eStorageAccessAPI
+// aParentContext is the browsing context of the document that calls this
+// API, so it is always in-process. Since the tracking origin is the
+// document's origin, it's same-origin to the parent window.
+// CompleteAllowAccessFor runs in the same process as AllowAccessFor.
+//
+// 2. eOpener
+// aParentContext is the browsing context of the opener that calls this
+// API, so it is always in-process. However, when the opener is a first
+// party and it opens a third-party window, the tracking origin is
+// origin of the third-party window. In this case, we should
+// run this API in the parent, as for the other cases, we can run in the
+// same process.
+//
+// 3. eOpenerAfterUserInteraction
+// aParentContext is the browsing context of the opener window, but
+// AllowAccessFor is called by the opened window. So as long as
+// aParentContext is not in-process, we should run in the parent.
+/* static */ RefPtr<ContentBlocking::StorageAccessPermissionGrantPromise>
+ContentBlocking::CompleteAllowAccessFor(
+ dom::BrowsingContext* aParentContext, uint64_t aTopLevelWindowId,
+ nsIPrincipal* aTrackingPrincipal, const nsCString& aTrackingOrigin,
+ uint32_t aCookieBehavior,
+ ContentBlockingNotifier::StorageAccessPermissionGrantedReason aReason,
+ const PerformFinalChecks& aPerformFinalChecks) {
+ MOZ_ASSERT(aParentContext);
+ MOZ_ASSERT_IF(XRE_IsContentProcess(), aParentContext->IsInProcess());
+
+ nsCOMPtr<nsIPrincipal> trackingPrincipal;
+ nsAutoCString trackingOrigin;
+ if (!aTrackingPrincipal) {
+ // User interaction is the only case that tracking principal is not
+ // available.
+ MOZ_ASSERT(XRE_IsParentProcess() &&
+ aReason == ContentBlockingNotifier::eOpenerAfterUserInteraction);
+
+ if (!AntiTrackingUtils::GetPrincipalAndTrackingOrigin(
+ aParentContext, getter_AddRefs(trackingPrincipal),
+ trackingOrigin)) {
+ LOG(
+ ("Error while computing the parent principal and tracking origin, "
+ "bailing out early"));
+ return StorageAccessPermissionGrantPromise::CreateAndReject(false,
+ __func__);
+ }
+ } else {
+ trackingPrincipal = aTrackingPrincipal;
+ trackingOrigin = aTrackingOrigin;
+ }
+
+ LOG(("Tracking origin is %s", PromiseFlatCString(trackingOrigin).get()));
+
+ // We hardcode this block reason since the first-party storage access
+ // permission is granted for the purpose of blocking trackers.
+ // Note that if aReason is eOpenerAfterUserInteraction and the
+ // trackingPrincipal is not in a blocklist, we don't check the
+ // user-interaction state, because it could be that the current process has
+ // just sent the request to store the user-interaction permission into the
+ // parent, without having received the permission itself yet.
+
+ bool isInPrefList = false;
+ trackingPrincipal->IsURIInPrefList(
+ "privacy.restrict3rdpartystorage."
+ "userInteractionRequiredForHosts",
+ &isInPrefList);
+ if (isInPrefList &&
+ !ContentBlockingUserInteraction::Exists(trackingPrincipal)) {
+ LOG_PRIN(("Tracking principal (%s) hasn't been interacted with before, "
+ "refusing to add a first-party storage permission to access it",
+ _spec),
+ trackingPrincipal);
+ ContentBlockingNotifier::OnDecision(
+ aParentContext, ContentBlockingNotifier::BlockingDecision::eBlock,
+ CookieJarSettings::IsRejectThirdPartyWithExceptions(aCookieBehavior)
+ ? nsIWebProgressListener::STATE_COOKIES_BLOCKED_FOREIGN
+ : nsIWebProgressListener::STATE_COOKIES_BLOCKED_TRACKER);
+ return StorageAccessPermissionGrantPromise::CreateAndReject(false,
+ __func__);
+ }
+
+ // Ensure we can find the window before continuing, so we can safely
+ // execute storePermission.
+ if (aParentContext->IsInProcess() &&
+ (!aParentContext->GetDOMWindow() ||
+ !aParentContext->GetDOMWindow()->GetCurrentInnerWindow())) {
+ LOG(
+ ("No window found for our parent browsing context, bailing out "
+ "early"));
+ return StorageAccessPermissionGrantPromise::CreateAndReject(false,
+ __func__);
+ }
+
+ auto storePermission =
+ [aParentContext, aTopLevelWindowId, trackingOrigin, trackingPrincipal,
+ aCookieBehavior,
+ aReason](int aAllowMode) -> RefPtr<StorageAccessPermissionGrantPromise> {
+ // Inform the window we granted permission for. This has to be done in the
+ // window's process.
+ if (aParentContext->IsInProcess()) {
+ ContentBlocking::OnAllowAccessFor(aParentContext, trackingOrigin,
+ aCookieBehavior, aReason);
+ } else {
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ // We don't have the window, send an IPC to the content process that
+ // owns the parent window. But there is a special case, for window.open,
+ // we'll return to the content process we need to inform when this
+ // function is done. So we don't need to create an extra IPC for the case.
+ if (aReason != ContentBlockingNotifier::eOpener) {
+ ContentParent* cp = aParentContext->Canonical()->GetContentParent();
+ Unused << cp->SendOnAllowAccessFor(aParentContext, trackingOrigin,
+ aCookieBehavior, aReason);
+ }
+ }
+
+ Maybe<ContentBlockingNotifier::StorageAccessPermissionGrantedReason>
+ reportReason;
+ // We can directly report here if we can know the origin of the top.
+ if (XRE_IsParentProcess() || aParentContext->Top()->IsInProcess()) {
+ ContentBlockingNotifier::ReportUnblockingToConsole(
+ aParentContext, NS_ConvertUTF8toUTF16(trackingOrigin), aReason);
+
+ // Set the report reason to nothing if we've already reported.
+ reportReason = Nothing();
+ } else {
+ // Set the report reason, so that we can know the reason when reporting
+ // in the parent.
+ reportReason.emplace(aReason);
+ }
+
+ if (XRE_IsParentProcess()) {
+ LOG(("Saving the permission: trackingOrigin=%s", trackingOrigin.get()));
+ return SaveAccessForOriginOnParentProcess(
+ aTopLevelWindowId, aParentContext, trackingPrincipal,
+ trackingOrigin, aAllowMode)
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [](ParentAccessGrantPromise::ResolveOrRejectValue&& aValue) {
+ if (aValue.IsResolve()) {
+ return StorageAccessPermissionGrantPromise::CreateAndResolve(
+ ContentBlocking::eAllow, __func__);
+ }
+ return StorageAccessPermissionGrantPromise::CreateAndReject(
+ false, __func__);
+ });
+ }
+
+ ContentChild* cc = ContentChild::GetSingleton();
+ MOZ_ASSERT(cc);
+
+ LOG(
+ ("Asking the parent process to save the permission for us: "
+ "trackingOrigin=%s",
+ trackingOrigin.get()));
+
+ // This is not really secure, because here we have the content process
+ // sending the request of storing a permission.
+ return cc
+ ->SendStorageAccessPermissionGrantedForOrigin(
+ aTopLevelWindowId, aParentContext,
+ IPC::Principal(trackingPrincipal), trackingOrigin, aAllowMode,
+ reportReason)
+ ->Then(GetCurrentSerialEventTarget(), __func__,
+ [](const ContentChild::
+ StorageAccessPermissionGrantedForOriginPromise::
+ ResolveOrRejectValue& aValue) {
+ if (aValue.IsResolve()) {
+ return StorageAccessPermissionGrantPromise::CreateAndResolve(
+ aValue.ResolveValue(), __func__);
+ }
+ return StorageAccessPermissionGrantPromise::CreateAndReject(
+ false, __func__);
+ });
+ };
+
+ if (aPerformFinalChecks) {
+ return aPerformFinalChecks()->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [storePermission](
+ StorageAccessPermissionGrantPromise::ResolveOrRejectValue&&
+ aValue) {
+ if (aValue.IsResolve()) {
+ return storePermission(aValue.ResolveValue());
+ }
+ return StorageAccessPermissionGrantPromise::CreateAndReject(false,
+ __func__);
+ });
+ }
+ return storePermission(false);
+}
+
+/* static */ void ContentBlocking::OnAllowAccessFor(
+ dom::BrowsingContext* aParentContext, const nsCString& aTrackingOrigin,
+ uint32_t aCookieBehavior,
+ ContentBlockingNotifier::StorageAccessPermissionGrantedReason aReason) {
+ MOZ_ASSERT(aParentContext->IsInProcess());
+
+ // Let's inform the parent window and the other windows having the
+ // same tracking origin about the stroage permission is granted.
+ ContentBlocking::UpdateAllowAccessOnCurrentProcess(aParentContext,
+ aTrackingOrigin);
+
+ // Let's inform the parent window.
+ nsCOMPtr<nsPIDOMWindowInner> parentInner =
+ AntiTrackingUtils::GetInnerWindow(aParentContext);
+ if (NS_WARN_IF(!parentInner)) {
+ return;
+ }
+
+ Document* doc = parentInner->GetExtantDoc();
+ if (NS_WARN_IF(!doc)) {
+ return;
+ }
+
+ if (!doc->GetChannel()) {
+ return;
+ }
+
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_STORAGE_ACCESS_GRANTED_COUNT::StorageGranted);
+
+ switch (aReason) {
+ case ContentBlockingNotifier::StorageAccessPermissionGrantedReason::
+ eStorageAccessAPI:
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_STORAGE_ACCESS_GRANTED_COUNT::StorageAccessAPI);
+ break;
+ case ContentBlockingNotifier::StorageAccessPermissionGrantedReason::
+ eOpenerAfterUserInteraction:
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_STORAGE_ACCESS_GRANTED_COUNT::OpenerAfterUI);
+ break;
+ case ContentBlockingNotifier::StorageAccessPermissionGrantedReason::eOpener:
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_STORAGE_ACCESS_GRANTED_COUNT::Opener);
+ break;
+ default:
+ break;
+ }
+
+ // Theoratically this can be done in the parent process. But right now,
+ // we need the channel while notifying content blocking events, and
+ // we don't have a trivial way to obtain the channel in the parent
+ // via BrowsingContext. So we just ask the child to do the work.
+ ContentBlockingNotifier::OnEvent(
+ doc->GetChannel(), false,
+ CookieJarSettings::IsRejectThirdPartyWithExceptions(aCookieBehavior)
+ ? nsIWebProgressListener::STATE_COOKIES_BLOCKED_FOREIGN
+ : nsIWebProgressListener::STATE_COOKIES_BLOCKED_TRACKER,
+ aTrackingOrigin, Some(aReason));
+}
+
+/* static */
+RefPtr<mozilla::ContentBlocking::ParentAccessGrantPromise>
+ContentBlocking::SaveAccessForOriginOnParentProcess(
+ uint64_t aTopLevelWindowId, BrowsingContext* aParentContext,
+ nsIPrincipal* aTrackingPrincipal, const nsCString& aTrackingOrigin,
+ int aAllowMode, uint64_t aExpirationTime) {
+ MOZ_ASSERT(aTopLevelWindowId != 0);
+
+ RefPtr<WindowGlobalParent> wgp =
+ WindowGlobalParent::GetByInnerWindowId(aTopLevelWindowId);
+ if (!wgp) {
+ LOG(("Can't get window global parent"));
+ return ParentAccessGrantPromise::CreateAndReject(false, __func__);
+ }
+
+ // If the permission is granted on a first-party window, also have to update
+ // the permission to all the other windows with the same tracking origin (in
+ // the same tab), if any.
+ ContentBlocking::UpdateAllowAccessOnParentProcess(aParentContext,
+ aTrackingOrigin);
+
+ return ContentBlocking::SaveAccessForOriginOnParentProcess(
+ wgp->DocumentPrincipal(), aTrackingPrincipal, aTrackingOrigin, aAllowMode,
+ aExpirationTime);
+}
+
+/* static */
+RefPtr<mozilla::ContentBlocking::ParentAccessGrantPromise>
+ContentBlocking::SaveAccessForOriginOnParentProcess(
+ nsIPrincipal* aParentPrincipal, nsIPrincipal* aTrackingPrincipal,
+ const nsCString& aTrackingOrigin, int aAllowMode,
+ uint64_t aExpirationTime) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ MOZ_ASSERT(aAllowMode == eAllow || aAllowMode == eAllowAutoGrant);
+
+ if (!aParentPrincipal || !aTrackingPrincipal) {
+ LOG(("Invalid input arguments passed"));
+ return ParentAccessGrantPromise::CreateAndReject(false, __func__);
+ };
+
+ LOG_PRIN(("Saving a first-party storage permission on %s for "
+ "trackingOrigin=%s",
+ _spec, aTrackingOrigin.get()),
+ aParentPrincipal);
+
+ if (NS_WARN_IF(!aParentPrincipal)) {
+ // The child process is sending something wrong. Let's ignore it.
+ LOG(("aParentPrincipal is null, bailing out early"));
+ return ParentAccessGrantPromise::CreateAndReject(false, __func__);
+ }
+
+ PermissionManager* permManager = PermissionManager::GetInstance();
+ if (NS_WARN_IF(!permManager)) {
+ LOG(("Permission manager is null, bailing out early"));
+ return ParentAccessGrantPromise::CreateAndReject(false, __func__);
+ }
+
+ // Remember that this pref is stored in seconds!
+ uint32_t expirationType = nsIPermissionManager::EXPIRE_TIME;
+ uint32_t expirationTime = aExpirationTime * 1000;
+ int64_t when = (PR_Now() / PR_USEC_PER_MSEC) + expirationTime;
+
+ uint32_t privateBrowsingId = 0;
+ nsresult rv = aParentPrincipal->GetPrivateBrowsingId(&privateBrowsingId);
+ if ((!NS_WARN_IF(NS_FAILED(rv)) && privateBrowsingId > 0) ||
+ (aAllowMode == eAllowAutoGrant)) {
+ // If we are coming from a private window or are automatically granting a
+ // permission, make sure to store a session-only permission which won't
+ // get persisted to disk.
+ expirationType = nsIPermissionManager::EXPIRE_SESSION;
+ when = 0;
+ }
+
+ nsAutoCString type;
+ AntiTrackingUtils::CreateStoragePermissionKey(aTrackingOrigin, type);
+
+ LOG(
+ ("Computed permission key: %s, expiry: %u, proceeding to save in the "
+ "permission manager",
+ type.get(), expirationTime));
+
+ rv = permManager->AddFromPrincipal(aParentPrincipal, type,
+ nsIPermissionManager::ALLOW_ACTION,
+ expirationType, when);
+ Unused << NS_WARN_IF(NS_FAILED(rv));
+
+ if (StaticPrefs::privacy_antitracking_testing()) {
+ nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+ obs->NotifyObservers(nullptr, "antitracking-test-storage-access-perm-added",
+ nullptr);
+ }
+
+ if (NS_SUCCEEDED(rv) && (aAllowMode == eAllowAutoGrant)) {
+ // Make sure temporary access grants do not survive more than 24 hours.
+ TemporaryAccessGrantObserver::Create(permManager, aParentPrincipal, type);
+ }
+
+ LOG(("Result: %s", NS_SUCCEEDED(rv) ? "success" : "failure"));
+ return ParentAccessGrantPromise::CreateAndResolve(rv, __func__);
+}
+
+// There are two methods to handle permisson update:
+// 1. UpdateAllowAccessOnCurrentProcess
+// 2. UpdateAllowAccessOnParentProcess
+//
+// In general, UpdateAllowAccessOnCurrentProcess is used to propagate storage
+// permission to same-origin frames in the same tab.
+// UpdateAllowAccessOnParentProcess is used to propagate storage permission to
+// same-origin frames in the same agent cluster.
+//
+// However, there is an exception in fission mode. When the heuristic is
+// triggered by a first-party window, for instance, a first-party script calls
+// window.open(tracker), we can't update 3rd-party frames's storage permission
+// in the child process that triggers the permission update because the
+// first-party and the 3rd-party are not in the same process. In this case, we
+// should update the storage permission in UpdateAllowAccessOnParentProcess.
+
+// This function is used to update permission to all in-process windows, so it
+// can be called either from the parent or the child.
+/* static */
+void ContentBlocking::UpdateAllowAccessOnCurrentProcess(
+ BrowsingContext* aParentContext, const nsACString& aTrackingOrigin) {
+ MOZ_ASSERT(aParentContext && aParentContext->IsInProcess());
+
+ bool useRemoteSubframes;
+ aParentContext->GetUseRemoteSubframes(&useRemoteSubframes);
+
+ if (useRemoteSubframes && aParentContext->IsTopContent()) {
+ // If we are a first-party and we are in fission mode, bail out early
+ // because we can't do anything here.
+ return;
+ }
+
+ BrowsingContext* top = aParentContext->Top();
+
+ // Propagate the storage permission to same-origin frames in the same tab.
+ top->PreOrderWalk([&](BrowsingContext* aContext) {
+ // Only check browsing contexts that are in-process.
+ if (aContext->IsInProcess()) {
+ nsAutoCString origin;
+ Unused << AntiTrackingUtils::GetPrincipalAndTrackingOrigin(
+ aContext, nullptr, origin);
+
+ if (aTrackingOrigin == origin) {
+ nsCOMPtr<nsPIDOMWindowInner> inner =
+ AntiTrackingUtils::GetInnerWindow(aContext);
+ if (inner) {
+ inner->SaveStorageAccessPermissionGranted();
+ }
+
+ nsCOMPtr<nsPIDOMWindowOuter> outer =
+ nsPIDOMWindowOuter::GetFromCurrentInner(inner);
+ if (outer) {
+ nsGlobalWindowOuter::Cast(outer)->SetStorageAccessPermissionGranted(
+ true);
+ }
+ }
+ }
+ });
+}
+
+/* static */
+void ContentBlocking::UpdateAllowAccessOnParentProcess(
+ BrowsingContext* aParentContext, const nsACString& aTrackingOrigin) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ nsAutoCString topKey;
+ nsCOMPtr<nsIPrincipal> topPrincipal =
+ AntiTrackingUtils::GetPrincipal(aParentContext->Top());
+ PermissionManager::GetKeyForPrincipal(topPrincipal, false, topKey);
+
+ // Propagate the storage permission to same-origin frames in the same
+ // agent-cluster.
+ for (const auto& topContext : aParentContext->Group()->Toplevels()) {
+ if (topContext == aParentContext->Top()) {
+ // In non-fission mode, storage permission is stored in the top-level,
+ // don't need to propagtes it to tracker frames.
+ bool useRemoteSubframes;
+ aParentContext->GetUseRemoteSubframes(&useRemoteSubframes);
+ if (!useRemoteSubframes) {
+ continue;
+ }
+ // If parent context is third-party, we already propagate permission
+ // in the child process, skip propagating here.
+ RefPtr<dom::WindowContext> ctx =
+ aParentContext->GetCurrentWindowContext();
+ if (ctx && ctx->GetIsThirdPartyWindow()) {
+ continue;
+ }
+ } else {
+ nsCOMPtr<nsIPrincipal> principal =
+ AntiTrackingUtils::GetPrincipal(topContext);
+ if (!principal) {
+ continue;
+ }
+
+ nsAutoCString key;
+ PermissionManager::GetKeyForPrincipal(principal, false, key);
+ // Make sure we only apply to frames that have the same top-level.
+ if (topKey != key) {
+ continue;
+ }
+ }
+
+ topContext->PreOrderWalk([&](BrowsingContext* aContext) {
+ WindowGlobalParent* wgp = aContext->Canonical()->GetCurrentWindowGlobal();
+ if (!wgp) {
+ return;
+ }
+
+ nsAutoCString origin;
+ AntiTrackingUtils::GetPrincipalAndTrackingOrigin(aContext, nullptr,
+ origin);
+ if (aTrackingOrigin == origin) {
+ Unused << wgp->SendSaveStorageAccessPermissionGranted();
+ }
+ });
+ }
+}
+
+bool ContentBlocking::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 = 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;
+ }
+
+ 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;
+ }
+
+#ifdef DEBUG
+ nsCOMPtr<mozIThirdPartyUtil> thirdPartyUtil = services::GetThirdPartyUtil();
+ if (thirdPartyUtil) {
+ bool thirdParty = false;
+ nsresult rv = thirdPartyUtil->IsThirdPartyWindow(aWindow->GetOuterWindow(),
+ aURI, &thirdParty);
+ // The result of this assertion depends on whether IsThirdPartyWindow
+ // succeeds, because otherwise IsThirdPartyWindowOrChannel artificially
+ // fails.
+ MOZ_ASSERT_IF(NS_SUCCEEDED(rv), nsContentUtils::IsThirdPartyWindowOrChannel(
+ aWindow, nullptr, aURI) == thirdParty);
+ }
+#endif
+
+ 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 ContentBlocking::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 =
+ 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 = services::GetThirdPartyUtil();
+ 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;
+ }
+
+ 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.
+
+ nsCOMPtr<nsIURI> trackingURI;
+ rv = aChannel->GetURI(getter_AddRefs(trackingURI));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ LOG(("Failed to get the channel URI"));
+ return true;
+ }
+
+ nsAutoCString trackingOrigin;
+ rv = nsContentUtils::GetASCIIOrigin(trackingURI, trackingOrigin);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ LOG_SPEC(("Failed to compute the origin from %s", _spec), trackingURI);
+ return false;
+ }
+
+ // 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->GetHasStoragePermission();
+ if (!allowed) {
+ *aRejectedReason = blockedReason;
+ }
+
+ return allowed;
+}
+
+bool ContentBlocking::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 ContentBlocking::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 = 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);
+}