forked from mirrors/gecko-dev
1319 lines
51 KiB
C++
1319 lines
51 KiB
C++
/* -*- 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 "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__);
|
|
}
|
|
|
|
bool isParentTopLevel = aParentContext->IsTopContent();
|
|
|
|
// Make sure storage access isn't disabled
|
|
if (!isParentTopLevel &&
|
|
Document::StorageAccessSandboxed(aParentContext->GetSandboxFlags())) {
|
|
LOG(("Our document is sandboxed"));
|
|
return StorageAccessPermissionGrantPromise::CreateAndReject(false,
|
|
__func__);
|
|
}
|
|
|
|
uint64_t topLevelWindowId;
|
|
nsAutoCString trackingOrigin;
|
|
nsCOMPtr<nsIPrincipal> trackingPrincipal;
|
|
|
|
LOG(("The current resource is %s-party",
|
|
isParentTopLevel ? "first" : "third"));
|
|
|
|
// We are a first party resource.
|
|
if (isParentTopLevel) {
|
|
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) &&
|
|
!parentWindowContext->GetIsThirdPartyWindow()) {
|
|
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;
|
|
}
|
|
|
|
// 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 (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();
|
|
uint32_t behavior = AntiTrackingUtils::GetCookieBehavior(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);
|
|
|
|
// Permission is only synced to first-level iframes.
|
|
if ((aParentContext != aContext) &&
|
|
(behavior == nsICookieService::BEHAVIOR_REJECT_TRACKER &&
|
|
!AntiTrackingUtils::IsFirstLevelSubContext(aContext))) {
|
|
return;
|
|
}
|
|
|
|
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());
|
|
|
|
uint32_t behavior = AntiTrackingUtils::GetCookieBehavior(aParentContext);
|
|
|
|
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()) {
|
|
// We don't have to update same-origin frames in the same tab unless
|
|
// when we are in fission mode and the storage permission granted is
|
|
// called by a first-party window and we are in fission mode.
|
|
bool useRemoteSubframes;
|
|
aParentContext->GetUseRemoteSubframes(&useRemoteSubframes);
|
|
if (!useRemoteSubframes || !aParentContext->IsTop()) {
|
|
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;
|
|
}
|
|
|
|
if (behavior == nsICookieService::BEHAVIOR_REJECT_TRACKER &&
|
|
!AntiTrackingUtils::IsFirstLevelSubContext(aContext)) {
|
|
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"));
|
|
return false;
|
|
}
|
|
|
|
// We will only allow the storage access for the first-level iframe in cookie
|
|
// behavior BEHAVIOR_REJECT_TRACKER. We don't need to consider the top window
|
|
// here since we only get here if the window is not a top.
|
|
if (behavior == nsICookieService::BEHAVIOR_REJECT_TRACKER &&
|
|
!AntiTrackingUtils::IsFirstLevelSubContext(
|
|
aWindow->GetBrowsingContext())) {
|
|
*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;
|
|
}
|
|
|
|
// We will only allow the storage access for the channel of the first-level
|
|
// iframe or top-level sub-resource in cookie behavior
|
|
// BEHAVIOR_REJECT_TRACKER.
|
|
if (behavior == nsICookieService::BEHAVIOR_REJECT_TRACKER &&
|
|
!targetBC->IsTopContent() &&
|
|
!AntiTrackingUtils::IsFirstLevelSubContext(targetBC)) {
|
|
*aRejectedReason = blockedReason;
|
|
return false;
|
|
}
|
|
|
|
if (Document::StorageAccessSandboxed(targetBC->GetSandboxFlags())) {
|
|
LOG(("Our document is sandboxed"));
|
|
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);
|
|
}
|