forked from mirrors/gecko-dev
Differential Revision: https://phabricator.services.mozilla.com/D60505 --HG-- extra : moz-landing-system : lando
2488 lines
88 KiB
C++
2488 lines
88 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 "AntiTrackingCommon.h"
|
|
|
|
#include "mozilla/dom/BrowsingContext.h"
|
|
#include "mozilla/dom/ContentChild.h"
|
|
#include "mozilla/ipc/MessageChannel.h"
|
|
#include "mozilla/AbstractThread.h"
|
|
#include "mozilla/HashFunctions.h"
|
|
#include "mozilla/IntegerPrintfMacros.h"
|
|
#include "mozilla/Logging.h"
|
|
#include "mozilla/MruCache.h"
|
|
#include "mozilla/Pair.h"
|
|
#include "mozilla/ScopeExit.h"
|
|
#include "mozilla/StaticPrefs_extensions.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 "nsIDocShell.h"
|
|
#include "nsIHttpChannelInternal.h"
|
|
#include "nsIParentChannel.h"
|
|
#include "nsIPermission.h"
|
|
#include "nsPermissionManager.h"
|
|
#include "nsIPrincipal.h"
|
|
#include "nsIRedirectHistoryEntry.h"
|
|
#include "nsIScriptError.h"
|
|
#include "nsIURI.h"
|
|
#include "nsIURIFixup.h"
|
|
#include "nsIWebProgressListener.h"
|
|
#include "nsNetUtil.h"
|
|
#include "nsPIDOMWindow.h"
|
|
#include "nsPrintfCString.h"
|
|
#include "nsScriptSecurityManager.h"
|
|
#include "nsSandboxFlags.h"
|
|
#include "prtime.h"
|
|
|
|
#define ANTITRACKING_PERM_KEY "3rdPartyStorage"
|
|
#define ANTITRACKING_CONSOLE_CATEGORY NS_LITERAL_CSTRING("Content Blocking")
|
|
|
|
using namespace mozilla;
|
|
using mozilla::dom::BrowsingContext;
|
|
using mozilla::dom::ContentChild;
|
|
using mozilla::dom::Document;
|
|
|
|
static LazyLogModule gAntiTrackingLog("AntiTracking");
|
|
static const nsCString::size_type sMaxSpecLength = 128;
|
|
static const uint32_t kMaxConsoleOutputDelayMs = 100;
|
|
|
|
#define LOG(format) MOZ_LOG(gAntiTrackingLog, mozilla::LogLevel::Debug, format)
|
|
|
|
#define LOG_SPEC(format, uri) \
|
|
PR_BEGIN_MACRO \
|
|
if (MOZ_LOG_TEST(gAntiTrackingLog, mozilla::LogLevel::Debug)) { \
|
|
nsAutoCString _specStr(NS_LITERAL_CSTRING("(null)")); \
|
|
_specStr.Truncate(std::min(_specStr.Length(), sMaxSpecLength)); \
|
|
if (uri) { \
|
|
_specStr = uri->GetSpecOrDefault(); \
|
|
} \
|
|
const char* _spec = _specStr.get(); \
|
|
LOG(format); \
|
|
} \
|
|
PR_END_MACRO
|
|
|
|
#define LOG_SPEC2(format, uri1, uri2) \
|
|
PR_BEGIN_MACRO \
|
|
if (MOZ_LOG_TEST(gAntiTrackingLog, mozilla::LogLevel::Debug)) { \
|
|
nsAutoCString _specStr1(NS_LITERAL_CSTRING("(null)")); \
|
|
_specStr1.Truncate(std::min(_specStr1.Length(), sMaxSpecLength)); \
|
|
if (uri1) { \
|
|
_specStr1 = uri1->GetSpecOrDefault(); \
|
|
} \
|
|
const char* _spec1 = _specStr1.get(); \
|
|
nsAutoCString _specStr2(NS_LITERAL_CSTRING("(null)")); \
|
|
_specStr2.Truncate(std::min(_specStr2.Length(), sMaxSpecLength)); \
|
|
if (uri2) { \
|
|
_specStr2 = uri2->GetSpecOrDefault(); \
|
|
} \
|
|
const char* _spec2 = _specStr2.get(); \
|
|
LOG(format); \
|
|
} \
|
|
PR_END_MACRO
|
|
|
|
namespace {
|
|
|
|
UniquePtr<nsTArray<AntiTrackingCommon::AntiTrackingSettingsChangedCallback>>
|
|
gSettingsChangedCallbacks;
|
|
|
|
bool GetParentPrincipalAndTrackingOrigin(
|
|
nsGlobalWindowInner* a3rdPartyTrackingWindow, uint32_t aBehavior,
|
|
nsIPrincipal** aTopLevelStoragePrincipal, nsACString& aTrackingOrigin,
|
|
nsIURI** aTrackingURI, nsIPrincipal** aTrackingPrincipal) {
|
|
// Now we need the principal and the origin of the parent window.
|
|
nsCOMPtr<nsIPrincipal> topLevelStoragePrincipal =
|
|
// Use the "top-level storage area principal" behaviour in reject tracker
|
|
// mode only.
|
|
(aBehavior == nsICookieService::BEHAVIOR_REJECT_TRACKER)
|
|
? a3rdPartyTrackingWindow->GetTopLevelStorageAreaPrincipal()
|
|
: a3rdPartyTrackingWindow->GetTopLevelAntiTrackingPrincipal();
|
|
if (!topLevelStoragePrincipal) {
|
|
LOG(("No top-level storage area principal at hand"));
|
|
return false;
|
|
}
|
|
|
|
// Let's take the principal and the origin of the tracker.
|
|
nsCOMPtr<nsIPrincipal> trackingPrincipal =
|
|
a3rdPartyTrackingWindow->GetPrincipal();
|
|
if (NS_WARN_IF(!trackingPrincipal)) {
|
|
return false;
|
|
}
|
|
|
|
nsCOMPtr<nsIURI> trackingURI;
|
|
nsresult rv = trackingPrincipal->GetURI(getter_AddRefs(trackingURI));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return false;
|
|
}
|
|
|
|
rv = trackingPrincipal->GetOriginNoSuffix(aTrackingOrigin);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return false;
|
|
}
|
|
|
|
topLevelStoragePrincipal.forget(aTopLevelStoragePrincipal);
|
|
if (aTrackingURI) {
|
|
trackingURI.forget(aTrackingURI);
|
|
}
|
|
if (aTrackingPrincipal) {
|
|
trackingPrincipal.forget(aTrackingPrincipal);
|
|
}
|
|
return true;
|
|
};
|
|
|
|
void CreatePermissionKey(const nsCString& aTrackingOrigin,
|
|
nsACString& aPermissionKey) {
|
|
MOZ_ASSERT(aPermissionKey.IsEmpty());
|
|
|
|
static const nsLiteralCString prefix =
|
|
NS_LITERAL_CSTRING(ANTITRACKING_PERM_KEY "^");
|
|
|
|
aPermissionKey.SetCapacity(prefix.Length() + aTrackingOrigin.Length());
|
|
aPermissionKey.Append(prefix);
|
|
aPermissionKey.Append(aTrackingOrigin);
|
|
}
|
|
|
|
// This internal method returns ACCESS_DENY if the access is denied,
|
|
// ACCESS_DEFAULT if unknown, some other access code if granted.
|
|
uint32_t CheckCookiePermissionForPrincipal(nsICookieSettings* aCookieSettings,
|
|
nsIPrincipal* aPrincipal) {
|
|
MOZ_ASSERT(aCookieSettings);
|
|
MOZ_ASSERT(aPrincipal);
|
|
|
|
uint32_t cookiePermission = nsICookiePermission::ACCESS_DEFAULT;
|
|
if (!aPrincipal->GetIsContentPrincipal()) {
|
|
return cookiePermission;
|
|
}
|
|
|
|
nsresult rv =
|
|
aCookieSettings->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->CookieSettings()->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<nsICookieSettings> cookieSettings;
|
|
nsresult rv = aLoadInfo->GetCookieSettings(getter_AddRefs(cookieSettings));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return nsICookieService::BEHAVIOR_REJECT;
|
|
}
|
|
|
|
return cookieSettings->GetCookieBehavior();
|
|
}
|
|
|
|
int32_t CookiesBehavior(nsIPrincipal* aPrincipal,
|
|
nsICookieSettings* aCookieSettings) {
|
|
MOZ_ASSERT(aPrincipal);
|
|
MOZ_ASSERT(aCookieSettings);
|
|
|
|
// WebExtensions principals always get BEHAVIOR_ACCEPT as cookieBehavior
|
|
// (See Bug 1406675 for rationale).
|
|
if (BasePrincipal::Cast(aPrincipal)->AddonPolicy()) {
|
|
return nsICookieService::BEHAVIOR_ACCEPT;
|
|
}
|
|
|
|
return aCookieSettings->GetCookieBehavior();
|
|
}
|
|
|
|
struct ContentBlockingAllowListKey {
|
|
ContentBlockingAllowListKey() : mHash(mozilla::HashGeneric(uintptr_t(0))) {}
|
|
|
|
// Ensure that we compute a different hash for window and channel pointers of
|
|
// the same numeric value, in the off chance that we get unlucky and encounter
|
|
// a case where the allocator reallocates a window object where a channel used
|
|
// to live and vice versa.
|
|
explicit ContentBlockingAllowListKey(nsPIDOMWindowInner* aWindow)
|
|
: mHash(mozilla::AddToHash(aWindow->WindowID(),
|
|
mozilla::HashString("window"))) {}
|
|
explicit ContentBlockingAllowListKey(nsIHttpChannel* aChannel)
|
|
: mHash(mozilla::AddToHash(aChannel->ChannelId(),
|
|
mozilla::HashString("channel"))) {}
|
|
|
|
ContentBlockingAllowListKey(const ContentBlockingAllowListKey& aRHS)
|
|
: mHash(aRHS.mHash) {}
|
|
|
|
bool operator==(const ContentBlockingAllowListKey& aRHS) const {
|
|
return mHash == aRHS.mHash;
|
|
}
|
|
|
|
HashNumber GetHash() const { return mHash; }
|
|
|
|
private:
|
|
HashNumber mHash;
|
|
};
|
|
|
|
struct ContentBlockingAllowListEntry {
|
|
ContentBlockingAllowListEntry() : mResult(false) {}
|
|
ContentBlockingAllowListEntry(nsPIDOMWindowInner* aWindow, bool aResult)
|
|
: mKey(aWindow), mResult(aResult) {}
|
|
ContentBlockingAllowListEntry(nsIHttpChannel* aChannel, bool aResult)
|
|
: mKey(aChannel), mResult(aResult) {}
|
|
|
|
ContentBlockingAllowListKey mKey;
|
|
bool mResult;
|
|
};
|
|
|
|
struct ContentBlockingAllowListCache
|
|
: MruCache<ContentBlockingAllowListKey, ContentBlockingAllowListEntry,
|
|
ContentBlockingAllowListCache> {
|
|
static HashNumber Hash(const ContentBlockingAllowListKey& aKey) {
|
|
return aKey.GetHash();
|
|
}
|
|
static bool Match(const ContentBlockingAllowListKey& aKey,
|
|
const ContentBlockingAllowListEntry& aValue) {
|
|
return aValue.mKey == aKey;
|
|
}
|
|
};
|
|
|
|
ContentBlockingAllowListCache& GetContentBlockingAllowListCache() {
|
|
static bool initialized = false;
|
|
static ContentBlockingAllowListCache cache;
|
|
if (!initialized) {
|
|
AntiTrackingCommon::OnAntiTrackingSettingsChanged([&] {
|
|
// Drop everything in the cache, since the result of content blocking
|
|
// allow list checks may change past this point.
|
|
cache.Clear();
|
|
});
|
|
initialized = true;
|
|
}
|
|
return cache;
|
|
}
|
|
|
|
bool CheckContentBlockingAllowList(nsIPrincipal* aTopWinPrincipal,
|
|
bool aIsPrivateBrowsing) {
|
|
bool isAllowed = false;
|
|
nsresult rv = AntiTrackingCommon::IsOnContentBlockingAllowList(
|
|
aTopWinPrincipal, aIsPrivateBrowsing, isAllowed);
|
|
if (NS_SUCCEEDED(rv) && isAllowed) {
|
|
LOG(
|
|
("The top-level window is on the content blocking allow list, "
|
|
"bail out early"));
|
|
return true;
|
|
}
|
|
if (NS_FAILED(rv)) {
|
|
LOG(("Checking the content blocking allow list for failed with %" PRIx32,
|
|
static_cast<uint32_t>(rv)));
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool CheckContentBlockingAllowList(nsPIDOMWindowInner* aWindow) {
|
|
ContentBlockingAllowListKey cacheKey(aWindow);
|
|
auto entry = GetContentBlockingAllowListCache().Lookup(cacheKey);
|
|
if (entry) {
|
|
// We've recently performed a content blocking allow list check for this
|
|
// window, so let's quickly return the answer instead of continuing with the
|
|
// rest of this potentially expensive computation.
|
|
return entry.Data().mResult;
|
|
}
|
|
|
|
nsPIDOMWindowOuter* top =
|
|
aWindow->GetBrowsingContext()->Top()->GetDOMWindow();
|
|
Document* doc = top ? top->GetExtantDoc() : nullptr;
|
|
if (doc) {
|
|
bool isPrivateBrowsing = nsContentUtils::IsInPrivateBrowsing(doc);
|
|
|
|
const bool result = CheckContentBlockingAllowList(
|
|
doc->GetContentBlockingAllowListPrincipal(), isPrivateBrowsing);
|
|
|
|
entry.Set(ContentBlockingAllowListEntry(aWindow, result));
|
|
|
|
return result;
|
|
}
|
|
|
|
LOG(
|
|
("Could not check the content blocking allow list because the top "
|
|
"window wasn't accessible"));
|
|
entry.Set(ContentBlockingAllowListEntry(aWindow, false));
|
|
return false;
|
|
}
|
|
|
|
bool CheckContentBlockingAllowList(nsIHttpChannel* aChannel) {
|
|
ContentBlockingAllowListKey cacheKey(aChannel);
|
|
auto entry = GetContentBlockingAllowListCache().Lookup(cacheKey);
|
|
if (entry) {
|
|
// We've recently performed a content blocking allow list check for this
|
|
// channel, so let's quickly return the answer instead of continuing with
|
|
// the rest of this potentially expensive computation.
|
|
return entry.Data().mResult;
|
|
}
|
|
|
|
nsCOMPtr<nsIPrincipal> principal;
|
|
nsCOMPtr<nsIHttpChannelInternal> httpChan = do_QueryInterface(aChannel);
|
|
if (httpChan) {
|
|
nsresult rv = httpChan->GetContentBlockingAllowListPrincipal(
|
|
getter_AddRefs(principal));
|
|
if (NS_FAILED(rv) || !principal) {
|
|
LOG(
|
|
("Could not check the content blocking allow list because the top "
|
|
"window wasn't accessible"));
|
|
entry.Set(ContentBlockingAllowListEntry(aChannel, false));
|
|
return false;
|
|
}
|
|
}
|
|
|
|
const bool result =
|
|
CheckContentBlockingAllowList(principal, NS_UsePrivateBrowsing(aChannel));
|
|
entry.Set(ContentBlockingAllowListEntry(aChannel, result));
|
|
return result;
|
|
}
|
|
|
|
void RunConsoleReportingRunnable(already_AddRefed<nsIRunnable>&& aRunnable) {
|
|
if (StaticPrefs::privacy_restrict3rdpartystorage_console_lazy()) {
|
|
nsresult rv = NS_DispatchToCurrentThreadQueue(std::move(aRunnable),
|
|
kMaxConsoleOutputDelayMs,
|
|
EventQueuePriority::Idle);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return;
|
|
}
|
|
} else {
|
|
nsCOMPtr<nsIRunnable> runnable(std::move(aRunnable));
|
|
nsresult rv = runnable->Run();
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
void ReportBlockingToConsole(nsPIDOMWindowOuter* aWindow, nsIURI* aURI,
|
|
uint32_t aRejectedReason) {
|
|
MOZ_ASSERT(aWindow && aURI);
|
|
MOZ_ASSERT(
|
|
aRejectedReason == 0 ||
|
|
aRejectedReason ==
|
|
nsIWebProgressListener::STATE_COOKIES_BLOCKED_BY_PERMISSION ||
|
|
aRejectedReason ==
|
|
nsIWebProgressListener::STATE_COOKIES_BLOCKED_TRACKER ||
|
|
aRejectedReason ==
|
|
nsIWebProgressListener::STATE_COOKIES_BLOCKED_SOCIALTRACKER ||
|
|
aRejectedReason ==
|
|
nsIWebProgressListener::STATE_COOKIES_PARTITIONED_FOREIGN ||
|
|
aRejectedReason == nsIWebProgressListener::STATE_COOKIES_BLOCKED_ALL ||
|
|
aRejectedReason == nsIWebProgressListener::STATE_COOKIES_BLOCKED_FOREIGN);
|
|
|
|
RefPtr<Document> doc = aWindow->GetExtantDoc();
|
|
if (NS_WARN_IF(!doc)) {
|
|
return;
|
|
}
|
|
|
|
nsAutoString sourceLine;
|
|
uint32_t lineNumber = 0, columnNumber = 0;
|
|
JSContext* cx = nsContentUtils::GetCurrentJSContext();
|
|
if (cx) {
|
|
nsJSUtils::GetCallingLocation(cx, sourceLine, &lineNumber, &columnNumber);
|
|
}
|
|
|
|
nsCOMPtr<nsIURI> uri(aURI);
|
|
|
|
RefPtr<Runnable> runnable = NS_NewRunnableFunction(
|
|
"ReportBlockingToConsoleDelayed",
|
|
[doc, sourceLine, lineNumber, columnNumber, uri, aRejectedReason]() {
|
|
const char* message = nullptr;
|
|
nsAutoCString category;
|
|
// When changing this list, please make sure to update the corresponding
|
|
// code in antitracking_head.js (inside _createTask).
|
|
switch (aRejectedReason) {
|
|
case nsIWebProgressListener::STATE_COOKIES_BLOCKED_BY_PERMISSION:
|
|
message = "CookieBlockedByPermission";
|
|
category = NS_LITERAL_CSTRING("cookieBlockedPermission");
|
|
break;
|
|
|
|
case nsIWebProgressListener::STATE_COOKIES_BLOCKED_TRACKER:
|
|
message = "CookieBlockedTracker";
|
|
category = NS_LITERAL_CSTRING("cookieBlockedTracker");
|
|
break;
|
|
|
|
case nsIWebProgressListener::STATE_COOKIES_BLOCKED_ALL:
|
|
message = "CookieBlockedAll";
|
|
category = NS_LITERAL_CSTRING("cookieBlockedAll");
|
|
break;
|
|
|
|
case nsIWebProgressListener::STATE_COOKIES_BLOCKED_FOREIGN:
|
|
message = "CookieBlockedForeign";
|
|
category = NS_LITERAL_CSTRING("cookieBlockedForeign");
|
|
break;
|
|
|
|
default:
|
|
return;
|
|
}
|
|
|
|
MOZ_ASSERT(message);
|
|
|
|
// Strip the URL of any possible username/password and make it ready
|
|
// to be presented in the UI.
|
|
nsCOMPtr<nsIURIFixup> urifixup = services::GetURIFixup();
|
|
NS_ENSURE_TRUE_VOID(urifixup);
|
|
nsCOMPtr<nsIURI> exposableURI;
|
|
nsresult rv =
|
|
urifixup->CreateExposableURI(uri, getter_AddRefs(exposableURI));
|
|
NS_ENSURE_SUCCESS_VOID(rv);
|
|
|
|
AutoTArray<nsString, 1> params;
|
|
CopyUTF8toUTF16(exposableURI->GetSpecOrDefault(),
|
|
*params.AppendElement());
|
|
|
|
nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, category,
|
|
doc, nsContentUtils::eNECKO_PROPERTIES,
|
|
message, params, nullptr, sourceLine,
|
|
lineNumber, columnNumber);
|
|
});
|
|
|
|
RunConsoleReportingRunnable(runnable.forget());
|
|
}
|
|
|
|
void ReportUnblockingToConsole(
|
|
nsPIDOMWindowInner* aWindow, const nsAString& aTrackingOrigin,
|
|
AntiTrackingCommon::StorageAccessGrantedReason aReason) {
|
|
nsCOMPtr<nsIPrincipal> principal =
|
|
nsGlobalWindowInner::Cast(aWindow)->GetPrincipal();
|
|
if (NS_WARN_IF(!principal)) {
|
|
return;
|
|
}
|
|
|
|
RefPtr<Document> doc = aWindow->GetExtantDoc();
|
|
if (NS_WARN_IF(!doc)) {
|
|
return;
|
|
}
|
|
|
|
nsAutoString trackingOrigin(aTrackingOrigin);
|
|
|
|
nsAutoString sourceLine;
|
|
uint32_t lineNumber = 0, columnNumber = 0;
|
|
JSContext* cx = nsContentUtils::GetCurrentJSContext();
|
|
if (cx) {
|
|
nsJSUtils::GetCallingLocation(cx, sourceLine, &lineNumber, &columnNumber);
|
|
}
|
|
|
|
RefPtr<Runnable> runnable = NS_NewRunnableFunction(
|
|
"ReportUnblockingToConsoleDelayed",
|
|
[doc, principal, trackingOrigin, sourceLine, lineNumber, columnNumber,
|
|
aReason]() {
|
|
nsAutoString origin;
|
|
nsresult rv = nsContentUtils::GetUTFOrigin(principal, origin);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return;
|
|
}
|
|
|
|
// Not adding grantedOrigin yet because we may not want it later.
|
|
AutoTArray<nsString, 3> params = {origin, trackingOrigin};
|
|
const char* messageWithSameOrigin = nullptr;
|
|
|
|
switch (aReason) {
|
|
case AntiTrackingCommon::eStorageAccessAPI:
|
|
messageWithSameOrigin = "CookieAllowedForTrackerByStorageAccessAPI";
|
|
break;
|
|
|
|
case AntiTrackingCommon::eOpenerAfterUserInteraction:
|
|
[[fallthrough]];
|
|
case AntiTrackingCommon::eOpener:
|
|
messageWithSameOrigin = "CookieAllowedForTrackerByHeuristic";
|
|
break;
|
|
}
|
|
|
|
nsContentUtils::ReportToConsole(
|
|
nsIScriptError::warningFlag, ANTITRACKING_CONSOLE_CATEGORY, doc,
|
|
nsContentUtils::eNECKO_PROPERTIES, messageWithSameOrigin, params,
|
|
nullptr, sourceLine, lineNumber, columnNumber);
|
|
});
|
|
|
|
RunConsoleReportingRunnable(runnable.forget());
|
|
}
|
|
|
|
already_AddRefed<nsPIDOMWindowOuter> GetTopWindow(nsPIDOMWindowInner* aWindow) {
|
|
Document* document = aWindow->GetExtantDoc();
|
|
if (!document) {
|
|
return nullptr;
|
|
}
|
|
|
|
nsIChannel* channel = document->GetChannel();
|
|
if (!channel) {
|
|
return nullptr;
|
|
}
|
|
|
|
nsCOMPtr<nsPIDOMWindowOuter> pwin =
|
|
aWindow->GetBrowsingContext()->Top()->GetDOMWindow();
|
|
|
|
if (!pwin) {
|
|
return nullptr;
|
|
}
|
|
|
|
return pwin.forget();
|
|
}
|
|
|
|
class TemporaryAccessGrantCacheKey : public PLDHashEntryHdr {
|
|
public:
|
|
typedef Pair<nsCOMPtr<nsIPrincipal>, nsCString> KeyType;
|
|
typedef const KeyType* KeyTypePointer;
|
|
|
|
explicit TemporaryAccessGrantCacheKey(KeyTypePointer aKey)
|
|
: mPrincipal(aKey->first()), mType(aKey->second()) {}
|
|
TemporaryAccessGrantCacheKey(TemporaryAccessGrantCacheKey&& aOther) = default;
|
|
|
|
~TemporaryAccessGrantCacheKey() = default;
|
|
|
|
KeyType GetKey() const { return MakePair(mPrincipal, mType); }
|
|
bool KeyEquals(KeyTypePointer aKey) const {
|
|
return !!mPrincipal == !!aKey->first() && mType == aKey->second() &&
|
|
(mPrincipal ? (mPrincipal->Equals(aKey->first())) : true);
|
|
}
|
|
|
|
static KeyTypePointer KeyToPointer(KeyType& aKey) { return &aKey; }
|
|
static PLDHashNumber HashKey(KeyTypePointer aKey) {
|
|
if (!aKey) {
|
|
return 0;
|
|
}
|
|
|
|
BasePrincipal* bp = BasePrincipal::Cast(aKey->first());
|
|
return HashGeneric(bp->GetOriginNoSuffixHash(), bp->GetOriginSuffixHash(),
|
|
HashString(aKey->second()));
|
|
}
|
|
|
|
enum { ALLOW_MEMMOVE = true };
|
|
|
|
private:
|
|
nsCOMPtr<nsIPrincipal> mPrincipal;
|
|
nsCString mType;
|
|
};
|
|
|
|
class TemporaryAccessGrantObserver final : public nsIObserver {
|
|
public:
|
|
NS_DECL_ISUPPORTS
|
|
NS_DECL_NSIOBSERVER
|
|
|
|
static void Create(nsPermissionManager* aPM, nsIPrincipal* aPrincipal,
|
|
const nsACString& aType) {
|
|
MOZ_ASSERT(XRE_IsParentProcess());
|
|
|
|
if (!sObservers) {
|
|
sObservers = MakeUnique<ObserversTable>();
|
|
}
|
|
Unused << sObservers
|
|
->LookupForAdd(MakePair(nsCOMPtr<nsIPrincipal>(aPrincipal),
|
|
nsCString(aType)))
|
|
.OrInsert([&]() -> nsITimer* {
|
|
// Only create a new observer if we don't have a matching
|
|
// entry in our hashtable.
|
|
nsCOMPtr<nsITimer> timer;
|
|
RefPtr<TemporaryAccessGrantObserver> observer =
|
|
new TemporaryAccessGrantObserver(aPM, aPrincipal,
|
|
aType);
|
|
nsresult rv = NS_NewTimerWithObserver(
|
|
getter_AddRefs(timer), observer,
|
|
24 * 60 * 60 * 1000, // 24 hours
|
|
nsITimer::TYPE_ONE_SHOT);
|
|
|
|
if (NS_SUCCEEDED(rv)) {
|
|
observer->SetTimer(timer);
|
|
return timer;
|
|
}
|
|
timer->Cancel();
|
|
return nullptr;
|
|
});
|
|
}
|
|
|
|
void SetTimer(nsITimer* aTimer) {
|
|
mTimer = aTimer;
|
|
nsCOMPtr<nsIObserverService> observerService =
|
|
mozilla::services::GetObserverService();
|
|
if (observerService) {
|
|
observerService->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
|
|
}
|
|
}
|
|
|
|
private:
|
|
TemporaryAccessGrantObserver(nsPermissionManager* aPM,
|
|
nsIPrincipal* aPrincipal,
|
|
const nsACString& aType)
|
|
: mPM(aPM), mPrincipal(aPrincipal), mType(aType) {
|
|
MOZ_ASSERT(XRE_IsParentProcess(),
|
|
"Enforcing temporary access grant lifetimes can only be done in "
|
|
"the parent process");
|
|
}
|
|
|
|
~TemporaryAccessGrantObserver() = default;
|
|
|
|
private:
|
|
typedef nsDataHashtable<TemporaryAccessGrantCacheKey, nsCOMPtr<nsITimer>>
|
|
ObserversTable;
|
|
static UniquePtr<ObserversTable> sObservers;
|
|
nsCOMPtr<nsITimer> mTimer;
|
|
RefPtr<nsPermissionManager> mPM;
|
|
nsCOMPtr<nsIPrincipal> mPrincipal;
|
|
nsCString mType;
|
|
};
|
|
|
|
UniquePtr<TemporaryAccessGrantObserver::ObserversTable>
|
|
TemporaryAccessGrantObserver::sObservers;
|
|
|
|
NS_IMPL_ISUPPORTS(TemporaryAccessGrantObserver, nsIObserver)
|
|
|
|
NS_IMETHODIMP
|
|
TemporaryAccessGrantObserver::Observe(nsISupports* aSubject, const char* aTopic,
|
|
const char16_t* aData) {
|
|
if (strcmp(aTopic, NS_TIMER_CALLBACK_TOPIC) == 0) {
|
|
Unused << mPM->RemoveFromPrincipal(mPrincipal, mType);
|
|
|
|
MOZ_ASSERT(sObservers);
|
|
sObservers->Remove(MakePair(mPrincipal, mType));
|
|
} else if (strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) {
|
|
nsCOMPtr<nsIObserverService> observerService =
|
|
mozilla::services::GetObserverService();
|
|
if (observerService) {
|
|
observerService->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
|
|
}
|
|
if (mTimer) {
|
|
mTimer->Cancel();
|
|
mTimer = nullptr;
|
|
}
|
|
sObservers.reset();
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
class SettingsChangeObserver final : public nsIObserver {
|
|
~SettingsChangeObserver() = default;
|
|
|
|
public:
|
|
NS_DECL_ISUPPORTS
|
|
NS_DECL_NSIOBSERVER
|
|
|
|
static void PrivacyPrefChanged(const char* aPref = nullptr, void* = nullptr);
|
|
|
|
private:
|
|
static void RunAntiTrackingSettingsChangedCallbacks();
|
|
};
|
|
|
|
NS_IMPL_ISUPPORTS(SettingsChangeObserver, nsIObserver)
|
|
|
|
NS_IMETHODIMP SettingsChangeObserver::Observe(nsISupports* aSubject,
|
|
const char* aTopic,
|
|
const char16_t* aData) {
|
|
if (!strcmp(aTopic, "xpcom-shutdown")) {
|
|
nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
|
|
if (obs) {
|
|
obs->RemoveObserver(this, "perm-added");
|
|
obs->RemoveObserver(this, "perm-changed");
|
|
obs->RemoveObserver(this, "perm-cleared");
|
|
obs->RemoveObserver(this, "perm-deleted");
|
|
obs->RemoveObserver(this, "xpcom-shutdown");
|
|
|
|
Preferences::UnregisterPrefixCallback(
|
|
SettingsChangeObserver::PrivacyPrefChanged,
|
|
"browser.contentblocking.");
|
|
Preferences::UnregisterPrefixCallback(
|
|
SettingsChangeObserver::PrivacyPrefChanged, "network.cookie.");
|
|
Preferences::UnregisterPrefixCallback(
|
|
SettingsChangeObserver::PrivacyPrefChanged, "privacy.");
|
|
|
|
gSettingsChangedCallbacks = nullptr;
|
|
}
|
|
} else {
|
|
nsCOMPtr<nsIPermission> perm = do_QueryInterface(aSubject);
|
|
if (perm) {
|
|
nsAutoCString type;
|
|
nsresult rv = perm->GetType(type);
|
|
if (NS_WARN_IF(NS_FAILED(rv)) || type.Equals(USER_INTERACTION_PERM)) {
|
|
// Ignore failures or notifications that have been sent because of
|
|
// user interactions.
|
|
return NS_OK;
|
|
}
|
|
}
|
|
|
|
RunAntiTrackingSettingsChangedCallbacks();
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
// static
|
|
void SettingsChangeObserver::PrivacyPrefChanged(const char* aPref,
|
|
void* aClosure) {
|
|
RunAntiTrackingSettingsChangedCallbacks();
|
|
}
|
|
|
|
// static
|
|
void SettingsChangeObserver::RunAntiTrackingSettingsChangedCallbacks() {
|
|
if (gSettingsChangedCallbacks) {
|
|
for (auto& callback : *gSettingsChangedCallbacks) {
|
|
callback();
|
|
}
|
|
}
|
|
}
|
|
|
|
bool CheckAntiTrackingPermission(nsIPrincipal* aPrincipal,
|
|
const nsAutoCString& aType,
|
|
bool aIsInPrivateBrowsing,
|
|
uint32_t* aRejectedReason,
|
|
uint32_t aBlockedReason,
|
|
nsIURI* aPrincipalURI) {
|
|
nsPermissionManager* permManager = nsPermissionManager::GetInstance();
|
|
if (NS_WARN_IF(!permManager)) {
|
|
LOG(("Failed to obtain the permission manager"));
|
|
return false;
|
|
}
|
|
|
|
uint32_t result = 0;
|
|
if (aIsInPrivateBrowsing) {
|
|
LOG_SPEC(("Querying the permissions for private modei looking for a "
|
|
"permission of type %s for %s",
|
|
aType.get(), _spec),
|
|
aPrincipalURI);
|
|
if (!permManager->PermissionAvailable(aPrincipal, aType)) {
|
|
LOG(
|
|
("Permission isn't available for this principal in the current "
|
|
"process"));
|
|
return false;
|
|
}
|
|
nsTArray<RefPtr<nsIPermission>> permissions;
|
|
nsresult rv = permManager->GetAllForPrincipal(aPrincipal, permissions);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
LOG(("Failed to get the list of permissions"));
|
|
return false;
|
|
}
|
|
|
|
bool found = false;
|
|
for (const auto& permission : permissions) {
|
|
if (!permission) {
|
|
LOG(("Couldn't get the permission for unknown reasons"));
|
|
continue;
|
|
}
|
|
|
|
nsAutoCString permissionType;
|
|
if (NS_SUCCEEDED(permission->GetType(permissionType)) &&
|
|
permissionType != aType) {
|
|
LOG(("Non-matching permission type: %s", aType.get()));
|
|
continue;
|
|
}
|
|
|
|
uint32_t capability = 0;
|
|
if (NS_SUCCEEDED(permission->GetCapability(&capability)) &&
|
|
capability != nsIPermissionManager::ALLOW_ACTION) {
|
|
LOG(("Non-matching permission capability: %d", capability));
|
|
continue;
|
|
}
|
|
|
|
uint32_t expirationType = 0;
|
|
if (NS_SUCCEEDED(permission->GetExpireType(&expirationType)) &&
|
|
expirationType != nsIPermissionManager ::EXPIRE_SESSION) {
|
|
LOG(("Non-matching permission expiration type: %d", expirationType));
|
|
continue;
|
|
}
|
|
|
|
int64_t expirationTime = 0;
|
|
if (NS_SUCCEEDED(permission->GetExpireTime(&expirationTime)) &&
|
|
expirationTime != 0) {
|
|
LOG(("Non-matching permission expiration time: %" PRId64,
|
|
expirationTime));
|
|
continue;
|
|
}
|
|
|
|
LOG(("Found a matching permission"));
|
|
found = true;
|
|
break;
|
|
}
|
|
|
|
if (!found) {
|
|
if (aRejectedReason) {
|
|
*aRejectedReason = aBlockedReason;
|
|
}
|
|
return false;
|
|
}
|
|
} else {
|
|
nsresult rv = permManager->TestPermissionWithoutDefaultsFromPrincipal(
|
|
aPrincipal, aType, &result);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
LOG(("Failed to test the permission"));
|
|
return false;
|
|
}
|
|
|
|
LOG_SPEC(
|
|
("Testing permission type %s for %s resulted in %d (%s)", aType.get(),
|
|
_spec, int(result),
|
|
result == nsIPermissionManager::ALLOW_ACTION ? "success" : "failure"),
|
|
aPrincipalURI);
|
|
|
|
if (result != nsIPermissionManager::ALLOW_ACTION) {
|
|
if (aRejectedReason) {
|
|
*aRejectedReason = aBlockedReason;
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void NotifyBlockingDecisionInternal(
|
|
nsIChannel* aReportingChannel, nsIChannel* aTrackingChannel,
|
|
AntiTrackingCommon::BlockingDecision aDecision, uint32_t aRejectedReason,
|
|
nsIURI* aURI, nsPIDOMWindowOuter* aWindow) {
|
|
MOZ_DIAGNOSTIC_ASSERT_IF(
|
|
nsGlobalWindowOuter::Cast(aWindow)->IsChromeWindow(),
|
|
aDecision == AntiTrackingCommon::BlockingDecision::eAllow);
|
|
|
|
if (aDecision == AntiTrackingCommon::BlockingDecision::eBlock) {
|
|
AntiTrackingCommon::NotifyContentBlockingEvent(aWindow, aReportingChannel,
|
|
aTrackingChannel, true,
|
|
aRejectedReason, aURI);
|
|
|
|
ReportBlockingToConsole(aWindow, aURI, aRejectedReason);
|
|
}
|
|
|
|
// Now send the generic "cookies loaded" notifications, from the most generic
|
|
// to the most specific.
|
|
AntiTrackingCommon::NotifyContentBlockingEvent(
|
|
aWindow, aReportingChannel, aTrackingChannel, false,
|
|
nsIWebProgressListener::STATE_COOKIES_LOADED, aURI);
|
|
|
|
nsCOMPtr<nsIClassifiedChannel> classifiedChannel =
|
|
do_QueryInterface(aTrackingChannel);
|
|
if (!classifiedChannel) {
|
|
return;
|
|
}
|
|
|
|
uint32_t classificationFlags =
|
|
classifiedChannel->GetThirdPartyClassificationFlags();
|
|
if (classificationFlags &
|
|
nsIClassifiedChannel::ClassificationFlags::CLASSIFIED_TRACKING) {
|
|
AntiTrackingCommon::NotifyContentBlockingEvent(
|
|
aWindow, aReportingChannel, aTrackingChannel, false,
|
|
nsIWebProgressListener::STATE_COOKIES_LOADED_TRACKER, aURI);
|
|
}
|
|
|
|
if (classificationFlags &
|
|
nsIClassifiedChannel::ClassificationFlags::CLASSIFIED_SOCIALTRACKING) {
|
|
AntiTrackingCommon::NotifyContentBlockingEvent(
|
|
aWindow, aReportingChannel, aTrackingChannel, false,
|
|
nsIWebProgressListener::STATE_COOKIES_LOADED_SOCIALTRACKER, aURI);
|
|
}
|
|
}
|
|
|
|
} // namespace
|
|
|
|
/* static */ RefPtr<AntiTrackingCommon::StorageAccessGrantPromise>
|
|
AntiTrackingCommon::AddFirstPartyStorageAccessGrantedFor(
|
|
nsIPrincipal* aPrincipal, nsPIDOMWindowInner* aParentWindow,
|
|
StorageAccessGrantedReason aReason,
|
|
const AntiTrackingCommon::PerformFinalChecks& aPerformFinalChecks) {
|
|
MOZ_ASSERT(aParentWindow);
|
|
|
|
switch (aReason) {
|
|
case eOpener:
|
|
if (!StaticPrefs::
|
|
privacy_restrict3rdpartystorage_heuristic_window_open()) {
|
|
LOG(
|
|
("Bailing out early because the "
|
|
"privacy.restrict3rdpartystorage.heuristic.window_open preference "
|
|
"has been disabled"));
|
|
return StorageAccessGrantPromise::CreateAndReject(false, __func__);
|
|
}
|
|
break;
|
|
case 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 StorageAccessGrantPromise::CreateAndReject(false, __func__);
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
nsCOMPtr<nsIURI> uri;
|
|
aPrincipal->GetURI(getter_AddRefs(uri));
|
|
if (NS_WARN_IF(!uri)) {
|
|
LOG(("Can't get the URI from the principal"));
|
|
return StorageAccessGrantPromise::CreateAndReject(false, __func__);
|
|
}
|
|
|
|
if (MOZ_LOG_TEST(gAntiTrackingLog, mozilla::LogLevel::Debug)) {
|
|
nsAutoCString origin;
|
|
Unused << nsContentUtils::GetASCIIOrigin(uri, origin);
|
|
LOG(("Adding a first-party storage exception for %s...",
|
|
PromiseFlatCString(origin).get()));
|
|
}
|
|
|
|
Document* parentDoc = aParentWindow->GetExtantDoc();
|
|
if (!parentDoc) {
|
|
LOG(("Parent window has no doc"));
|
|
return StorageAccessGrantPromise::CreateAndReject(false, __func__);
|
|
}
|
|
int32_t behavior = parentDoc->CookieSettings()->GetCookieBehavior();
|
|
|
|
if (!parentDoc->CookieSettings()->GetRejectThirdPartyTrackers()) {
|
|
LOG(
|
|
("Disabled by network.cookie.cookieBehavior pref (%d), bailing out "
|
|
"early",
|
|
behavior));
|
|
return StorageAccessGrantPromise::CreateAndResolve(true, __func__);
|
|
}
|
|
|
|
MOZ_ASSERT(
|
|
behavior == nsICookieService::BEHAVIOR_REJECT_TRACKER ||
|
|
behavior ==
|
|
nsICookieService::BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN);
|
|
|
|
if (CheckContentBlockingAllowList(aParentWindow)) {
|
|
return StorageAccessGrantPromise::CreateAndResolve(true, __func__);
|
|
}
|
|
|
|
nsCOMPtr<nsIPrincipal> topLevelStoragePrincipal;
|
|
nsCOMPtr<nsIURI> trackingURI;
|
|
nsAutoCString trackingOrigin;
|
|
nsCOMPtr<nsIPrincipal> trackingPrincipal;
|
|
|
|
RefPtr<nsGlobalWindowInner> parentWindow =
|
|
nsGlobalWindowInner::Cast(aParentWindow);
|
|
nsGlobalWindowOuter* outerParentWindow =
|
|
nsGlobalWindowOuter::Cast(parentWindow->GetOuterWindow());
|
|
if (NS_WARN_IF(!outerParentWindow)) {
|
|
LOG(("No outer window found for our parent window, bailing out early"));
|
|
return StorageAccessGrantPromise::CreateAndReject(false, __func__);
|
|
}
|
|
|
|
LOG(("The current resource is %s-party",
|
|
outerParentWindow->IsTopLevelWindow() ? "first" : "third"));
|
|
|
|
// We are a first party resource.
|
|
if (outerParentWindow->IsTopLevelWindow()) {
|
|
nsAutoCString origin;
|
|
nsresult rv = nsContentUtils::GetASCIIOrigin(uri, origin);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
LOG(("Can't get the origin from the URI"));
|
|
return StorageAccessGrantPromise::CreateAndReject(false, __func__);
|
|
}
|
|
|
|
trackingOrigin = origin;
|
|
trackingPrincipal = aPrincipal;
|
|
rv = trackingPrincipal->GetURI(getter_AddRefs(trackingURI));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
LOG(("Couldn't get the tracking principal URI"));
|
|
return StorageAccessGrantPromise::CreateAndReject(false, __func__);
|
|
}
|
|
topLevelStoragePrincipal = parentWindow->GetPrincipal();
|
|
if (NS_WARN_IF(!topLevelStoragePrincipal)) {
|
|
LOG(("Top-level storage area principal not found, bailing out early"));
|
|
return StorageAccessGrantPromise::CreateAndReject(false, __func__);
|
|
}
|
|
|
|
} else {
|
|
// We should be a 3rd party source.
|
|
bool isThirdParty = false;
|
|
if (behavior == nsICookieService::BEHAVIOR_REJECT_TRACKER) {
|
|
isThirdParty =
|
|
nsContentUtils::IsThirdPartyTrackingResourceWindow(parentWindow);
|
|
} else if (behavior == nsICookieService::
|
|
BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN) {
|
|
isThirdParty = nsContentUtils::IsThirdPartyWindowOrChannel(
|
|
parentWindow, nullptr, nullptr);
|
|
}
|
|
|
|
if (!isThirdParty) {
|
|
if (behavior == nsICookieService::BEHAVIOR_REJECT_TRACKER) {
|
|
LOG(("Our window isn't a third-party tracking window"));
|
|
} else if (behavior ==
|
|
nsICookieService::
|
|
BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN) {
|
|
LOG(("Our window isn't a third-party window"));
|
|
}
|
|
return StorageAccessGrantPromise::CreateAndReject(false, __func__);
|
|
}
|
|
|
|
Document* doc = parentWindow->GetExtantDoc();
|
|
// Make sure storage access isn't disabled
|
|
if (doc && (doc->StorageAccessSandboxed())) {
|
|
LOG(("Our document is sandboxed"));
|
|
return StorageAccessGrantPromise::CreateAndReject(false, __func__);
|
|
}
|
|
|
|
if (!GetParentPrincipalAndTrackingOrigin(
|
|
parentWindow,
|
|
// Don't request the ETP specific behaviour of allowing only
|
|
// singly-nested iframes here, because we are recording an allow
|
|
// permission.
|
|
nsICookieService::BEHAVIOR_ACCEPT,
|
|
getter_AddRefs(topLevelStoragePrincipal), trackingOrigin,
|
|
getter_AddRefs(trackingURI), getter_AddRefs(trackingPrincipal))) {
|
|
LOG(
|
|
("Error while computing the parent principal and tracking origin, "
|
|
"bailing out early"));
|
|
return StorageAccessGrantPromise::CreateAndReject(false, __func__);
|
|
}
|
|
}
|
|
|
|
nsPIDOMWindowOuter* topOuterWindow =
|
|
aParentWindow->GetBrowsingContext()->Top()->GetDOMWindow();
|
|
nsGlobalWindowOuter* topWindow = nsGlobalWindowOuter::Cast(topOuterWindow);
|
|
if (NS_WARN_IF(!topWindow)) {
|
|
LOG(("No top outer window."));
|
|
return StorageAccessGrantPromise::CreateAndReject(false, __func__);
|
|
}
|
|
|
|
nsPIDOMWindowInner* topInnerWindow = topWindow->GetCurrentInnerWindow();
|
|
if (NS_WARN_IF(!topInnerWindow)) {
|
|
LOG(("No top inner window."));
|
|
return StorageAccessGrantPromise::CreateAndReject(false, __func__);
|
|
}
|
|
|
|
// 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 blacklist, 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.
|
|
//
|
|
// We define this as an enum, since without that MSVC fails to capturing this
|
|
// name inside the lambda without the explicit capture and clang warns if
|
|
// there is an explicit capture with -Wunused-lambda-capture.
|
|
enum : uint32_t {
|
|
blockReason = nsIWebProgressListener::STATE_COOKIES_BLOCKED_TRACKER
|
|
};
|
|
if (nsContentUtils::IsURIInPrefList(trackingURI,
|
|
"privacy.restrict3rdpartystorage."
|
|
"userInteractionRequiredForHosts") &&
|
|
!HasUserInteraction(trackingPrincipal)) {
|
|
LOG_SPEC(("Tracking principal (%s) hasn't been interacted with before, "
|
|
"refusing to add a first-party storage permission to access it",
|
|
_spec),
|
|
trackingURI);
|
|
NotifyBlockingDecision(aParentWindow, BlockingDecision::eBlock,
|
|
blockReason);
|
|
return StorageAccessGrantPromise::CreateAndReject(false, __func__);
|
|
}
|
|
|
|
nsCOMPtr<nsPIDOMWindowOuter> pwin = GetTopWindow(parentWindow);
|
|
if (!pwin) {
|
|
LOG(("Couldn't get the top window"));
|
|
return StorageAccessGrantPromise::CreateAndReject(false, __func__);
|
|
}
|
|
|
|
auto storePermission =
|
|
[pwin, parentWindow, trackingOrigin, trackingPrincipal, trackingURI,
|
|
topInnerWindow, topLevelStoragePrincipal,
|
|
aReason](int aAllowMode) -> RefPtr<StorageAccessGrantPromise> {
|
|
nsAutoCString permissionKey;
|
|
CreatePermissionKey(trackingOrigin, permissionKey);
|
|
|
|
// Let's store the permission in the current parent window.
|
|
topInnerWindow->SaveStorageAccessGranted(permissionKey);
|
|
|
|
// Let's inform the parent window.
|
|
parentWindow->StorageAccessGranted();
|
|
|
|
nsIChannel* channel =
|
|
pwin->GetCurrentInnerWindow()->GetExtantDoc()->GetChannel();
|
|
|
|
NotifyContentBlockingEvent(pwin, channel,
|
|
parentWindow->GetExtantDoc()->GetChannel(),
|
|
false, blockReason, trackingURI, Some(aReason));
|
|
|
|
ReportUnblockingToConsole(parentWindow,
|
|
NS_ConvertUTF8toUTF16(trackingOrigin), aReason);
|
|
|
|
if (XRE_IsParentProcess()) {
|
|
LOG(("Saving the permission: trackingOrigin=%s", trackingOrigin.get()));
|
|
return SaveFirstPartyStorageAccessGrantedForOriginOnParentProcess(
|
|
topLevelStoragePrincipal, trackingPrincipal, trackingOrigin,
|
|
aAllowMode)
|
|
->Then(GetCurrentThreadSerialEventTarget(), __func__,
|
|
[](FirstPartyStorageAccessGrantPromise::ResolveOrRejectValue&&
|
|
aValue) {
|
|
if (aValue.IsResolve()) {
|
|
return StorageAccessGrantPromise::CreateAndResolve(
|
|
eAllow, __func__);
|
|
}
|
|
return StorageAccessGrantPromise::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
|
|
->SendFirstPartyStorageAccessGrantedForOrigin(
|
|
IPC::Principal(topLevelStoragePrincipal),
|
|
IPC::Principal(trackingPrincipal), trackingOrigin, aAllowMode)
|
|
->Then(GetCurrentThreadSerialEventTarget(), __func__,
|
|
[](const ContentChild::
|
|
FirstPartyStorageAccessGrantedForOriginPromise::
|
|
ResolveOrRejectValue& aValue) {
|
|
if (aValue.IsResolve()) {
|
|
return StorageAccessGrantPromise::CreateAndResolve(
|
|
aValue.ResolveValue(), __func__);
|
|
}
|
|
return StorageAccessGrantPromise::CreateAndReject(false,
|
|
__func__);
|
|
});
|
|
};
|
|
|
|
if (aPerformFinalChecks) {
|
|
return aPerformFinalChecks()->Then(
|
|
GetCurrentThreadSerialEventTarget(), __func__,
|
|
[storePermission](
|
|
StorageAccessGrantPromise::ResolveOrRejectValue&& aValue) {
|
|
if (aValue.IsResolve()) {
|
|
return storePermission(aValue.ResolveValue());
|
|
}
|
|
return StorageAccessGrantPromise::CreateAndReject(false, __func__);
|
|
});
|
|
}
|
|
return storePermission(false);
|
|
}
|
|
|
|
/* static */
|
|
RefPtr<mozilla::AntiTrackingCommon::FirstPartyStorageAccessGrantPromise>
|
|
AntiTrackingCommon::SaveFirstPartyStorageAccessGrantedForOriginOnParentProcess(
|
|
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 FirstPartyStorageAccessGrantPromise::CreateAndReject(false,
|
|
__func__);
|
|
};
|
|
|
|
nsCOMPtr<nsIURI> parentPrincipalURI;
|
|
Unused << aParentPrincipal->GetURI(getter_AddRefs(parentPrincipalURI));
|
|
LOG_SPEC(("Saving a first-party storage permission on %s for "
|
|
"trackingOrigin=%s",
|
|
_spec, aTrackingOrigin.get()),
|
|
parentPrincipalURI);
|
|
|
|
if (NS_WARN_IF(!aParentPrincipal)) {
|
|
// The child process is sending something wrong. Let's ignore it.
|
|
LOG(("aParentPrincipal is null, bailing out early"));
|
|
return FirstPartyStorageAccessGrantPromise::CreateAndReject(false,
|
|
__func__);
|
|
}
|
|
|
|
nsPermissionManager* permManager = nsPermissionManager::GetInstance();
|
|
if (NS_WARN_IF(!permManager)) {
|
|
LOG(("Permission manager is null, bailing out early"));
|
|
return FirstPartyStorageAccessGrantPromise::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;
|
|
CreatePermissionKey(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 FirstPartyStorageAccessGrantPromise::CreateAndResolve(rv, __func__);
|
|
}
|
|
|
|
// static
|
|
bool AntiTrackingCommon::CreateStoragePermissionKey(nsIPrincipal* aPrincipal,
|
|
nsACString& aKey) {
|
|
if (!aPrincipal) {
|
|
return false;
|
|
}
|
|
|
|
nsAutoCString origin;
|
|
nsresult rv = aPrincipal->GetOriginNoSuffix(origin);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return false;
|
|
}
|
|
|
|
CreatePermissionKey(origin, aKey);
|
|
return true;
|
|
}
|
|
|
|
// static
|
|
bool AntiTrackingCommon::IsStorageAccessPermission(nsIPermission* aPermission,
|
|
nsIPrincipal* aPrincipal) {
|
|
MOZ_ASSERT(aPermission);
|
|
MOZ_ASSERT(aPrincipal);
|
|
|
|
// The permission key may belong either to a tracking origin on the same
|
|
// origin as the granted origin, or on another origin as the granted origin
|
|
// (for example when a tracker in a third-party context uses window.open to
|
|
// open another origin where that second origin would be the granted origin.)
|
|
// But even in the second case, the type of the permission would still be
|
|
// formed by concatenating the granted origin to the end of the type name
|
|
// (see CreatePermissionKey). Therefore, we pass in the same argument to
|
|
// both tracking origin and granted origin here in order to compute the
|
|
// shorter permission key and will then do a prefix match on the type of the
|
|
// input permission to see if it is a storage access permission or not.
|
|
nsAutoCString permissionKey;
|
|
bool result = CreateStoragePermissionKey(aPrincipal, permissionKey);
|
|
if (NS_WARN_IF(!result)) {
|
|
return false;
|
|
}
|
|
|
|
nsAutoCString type;
|
|
nsresult rv = aPermission->GetType(type);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return false;
|
|
}
|
|
|
|
return StringBeginsWith(type, permissionKey);
|
|
}
|
|
|
|
bool AntiTrackingCommon::IsFirstPartyStorageAccessGrantedFor(
|
|
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;
|
|
}
|
|
|
|
BrowsingContext* topBC = aWindow->GetBrowsingContext()->Top();
|
|
nsGlobalWindowOuter* topWindow = nullptr;
|
|
if (topBC->IsInProcess()) {
|
|
topWindow = nsGlobalWindowOuter::Cast(topBC->GetDOMWindow());
|
|
} else {
|
|
// For out-of-process top frames, we need to be able to access three things
|
|
// from the top BrowsingContext in order to be able to port this code to
|
|
// Fission successfully:
|
|
// * The CookieSettings of the top BrowsingContext.
|
|
// * The HasStorageAccessGranted() API on BrowsingContext.
|
|
// For now, if we face an out-of-process top frame, instead of failing here,
|
|
// we revert back to looking at the in-process top frame. This is of course
|
|
// the wrong thing to do, but we seem to have a number of tests in the tree
|
|
// which are depending on this incorrect behaviour. This path is intended
|
|
// to temporarily keep those tests working...
|
|
nsGlobalWindowOuter* outerWindow =
|
|
nsGlobalWindowOuter::Cast(aWindow->GetOuterWindow());
|
|
if (!outerWindow) {
|
|
LOG(("Our window has no outer window"));
|
|
return false;
|
|
}
|
|
|
|
nsCOMPtr<nsPIDOMWindowOuter> topOuterWindow =
|
|
outerWindow->GetInProcessTop();
|
|
topWindow = nsGlobalWindowOuter::Cast(topOuterWindow);
|
|
}
|
|
|
|
if (NS_WARN_IF(!topWindow)) {
|
|
LOG(("No top outer window"));
|
|
return false;
|
|
}
|
|
|
|
nsPIDOMWindowInner* topInnerWindow = topWindow->GetCurrentInnerWindow();
|
|
if (NS_WARN_IF(!topInnerWindow)) {
|
|
LOG(("No top inner window."));
|
|
return false;
|
|
}
|
|
|
|
uint32_t cookiePermission = CheckCookiePermissionForPrincipal(
|
|
document->CookieSettings(), 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 (CheckContentBlockingAllowList(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 (!nsContentUtils::IsThirdPartyWindowOrChannel(aWindow, nullptr, aURI)) {
|
|
LOG(("Our window isn't a third-party window"));
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if (behavior == nsICookieService::BEHAVIOR_REJECT_FOREIGN ||
|
|
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(
|
|
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 {
|
|
MOZ_ASSERT(behavior ==
|
|
nsICookieService::BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN);
|
|
if (nsContentUtils::IsThirdPartyTrackingResourceWindow(aWindow)) {
|
|
// fall through
|
|
} else if (nsContentUtils::IsThirdPartyWindowOrChannel(aWindow, nullptr,
|
|
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;
|
|
}
|
|
}
|
|
|
|
#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;
|
|
}
|
|
|
|
nsCOMPtr<nsIPrincipal> parentPrincipal;
|
|
nsCOMPtr<nsIURI> parentPrincipalURI;
|
|
nsCOMPtr<nsIURI> trackingURI;
|
|
nsAutoCString trackingOrigin;
|
|
if (!GetParentPrincipalAndTrackingOrigin(
|
|
nsGlobalWindowInner::Cast(aWindow), behavior,
|
|
getter_AddRefs(parentPrincipal), trackingOrigin,
|
|
getter_AddRefs(trackingURI), nullptr)) {
|
|
LOG(("Failed to obtain the parent principal and the tracking origin"));
|
|
*aRejectedReason = blockedReason;
|
|
return false;
|
|
}
|
|
Unused << parentPrincipal->GetURI(getter_AddRefs(parentPrincipalURI));
|
|
|
|
nsAutoCString type;
|
|
CreatePermissionKey(trackingOrigin, type);
|
|
|
|
if (topInnerWindow->HasStorageAccessGranted(type)) {
|
|
LOG(("Permission stored in the window. All good."));
|
|
return true;
|
|
}
|
|
|
|
return CheckAntiTrackingPermission(
|
|
parentPrincipal, type, nsContentUtils::IsInPrivateBrowsing(document),
|
|
aRejectedReason, blockedReason, parentPrincipalURI);
|
|
}
|
|
|
|
bool AntiTrackingCommon::IsFirstPartyStorageAccessGrantedFor(
|
|
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();
|
|
// We need to find the correct principal to check the cookie permission. For
|
|
// third-party contexts, we want to check if the top-level window has a custom
|
|
// cookie permission.
|
|
nsCOMPtr<nsIPrincipal> toplevelPrincipal = loadInfo->GetTopLevelPrincipal();
|
|
|
|
// If this is already the top-level window, we should use the loading
|
|
// principal.
|
|
if (!toplevelPrincipal) {
|
|
LOG(
|
|
("Our loadInfo lacks a top-level principal, use the loadInfo's loading "
|
|
"principal instead"));
|
|
toplevelPrincipal = loadInfo->LoadingPrincipal();
|
|
}
|
|
|
|
nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aChannel);
|
|
|
|
// If we don't have a loading principal and this is a document channel, we are
|
|
// a top-level window!
|
|
if (!toplevelPrincipal) {
|
|
LOG(
|
|
("We don't have a loading principal, let's see if this is a document "
|
|
"channel"
|
|
" that belongs to a top-level window"));
|
|
bool isDocument = false;
|
|
if (httpChannel) {
|
|
rv = httpChannel->GetIsMainDocumentChannel(&isDocument);
|
|
}
|
|
if (httpChannel && NS_SUCCEEDED(rv) && isDocument) {
|
|
rv = ssm->GetChannelResultPrincipal(aChannel,
|
|
getter_AddRefs(toplevelPrincipal));
|
|
if (NS_SUCCEEDED(rv)) {
|
|
LOG(("Yes, we guessed right!"));
|
|
} else {
|
|
LOG(
|
|
("Yes, we guessed right, but minting the channel result principal "
|
|
"failed"));
|
|
}
|
|
} else {
|
|
LOG(("No, we guessed wrong!"));
|
|
}
|
|
}
|
|
|
|
// Let's use the triggering principal then.
|
|
if (!toplevelPrincipal) {
|
|
LOG(
|
|
("Our loadInfo lacks a top-level principal, use the loadInfo's "
|
|
"triggering principal instead"));
|
|
toplevelPrincipal = loadInfo->TriggeringPrincipal();
|
|
}
|
|
|
|
if (NS_WARN_IF(!toplevelPrincipal)) {
|
|
LOG(("No top-level principal! Bail out early"));
|
|
return false;
|
|
}
|
|
|
|
nsCOMPtr<nsICookieSettings> cookieSettings;
|
|
rv = loadInfo->GetCookieSettings(getter_AddRefs(cookieSettings));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
LOG(
|
|
("Failed to get the cookie settings from the loadinfo, bail out "
|
|
"early"));
|
|
return true;
|
|
}
|
|
|
|
nsCOMPtr<nsIPrincipal> channelPrincipal;
|
|
rv = ssm->GetChannelResultPrincipal(aChannel,
|
|
getter_AddRefs(channelPrincipal));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
LOG(("No channel principal, bail out early"));
|
|
return false;
|
|
}
|
|
|
|
uint32_t cookiePermission =
|
|
CheckCookiePermissionForPrincipal(cookieSettings, 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;
|
|
}
|
|
|
|
if (httpChannel && CheckContentBlockingAllowList(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 ||
|
|
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(
|
|
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 {
|
|
MOZ_ASSERT(behavior ==
|
|
nsICookieService::BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN);
|
|
if (classifiedChannel &&
|
|
classifiedChannel->IsThirdPartyTrackingResource()) {
|
|
// fall through
|
|
} else if (nsContentUtils::IsThirdPartyWindowOrChannel(nullptr, aChannel,
|
|
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 channel isn't a third-party channel, storage is allowed"));
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// Only use the "top-level storage area principal" behaviour for reject
|
|
// tracker mode only.
|
|
nsIPrincipal* parentPrincipal =
|
|
(behavior == nsICookieService::BEHAVIOR_REJECT_TRACKER)
|
|
? loadInfo->GetTopLevelStorageAreaPrincipal()
|
|
: loadInfo->GetTopLevelPrincipal();
|
|
if (!parentPrincipal) {
|
|
LOG(("No top-level storage area principal at hand"));
|
|
|
|
// parentPrincipal can be null if the parent window is not the top-level
|
|
// window.
|
|
if (loadInfo->GetTopLevelPrincipal()) {
|
|
LOG(("Parent window is the top-level window, bail out early"));
|
|
*aRejectedReason = blockedReason;
|
|
return false;
|
|
}
|
|
|
|
parentPrincipal = toplevelPrincipal;
|
|
if (NS_WARN_IF(!parentPrincipal)) {
|
|
LOG(
|
|
("No triggering principal, this shouldn't be happening! Bail out "
|
|
"early"));
|
|
// Why we are here?!?
|
|
return true;
|
|
}
|
|
}
|
|
|
|
nsCOMPtr<nsIURI> parentPrincipalURI;
|
|
Unused << parentPrincipal->GetURI(getter_AddRefs(parentPrincipalURI));
|
|
|
|
// 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;
|
|
}
|
|
|
|
nsAutoCString type;
|
|
CreatePermissionKey(trackingOrigin, type);
|
|
|
|
uint32_t privateBrowsingId = 0;
|
|
rv = channelPrincipal->GetPrivateBrowsingId(&privateBrowsingId);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
LOG(("Failed to get the channel principal's private browsing ID"));
|
|
return false;
|
|
}
|
|
|
|
return CheckAntiTrackingPermission(parentPrincipal, type, !!privateBrowsingId,
|
|
aRejectedReason, blockedReason,
|
|
parentPrincipalURI);
|
|
}
|
|
|
|
bool AntiTrackingCommon::IsFirstPartyStorageAccessGrantedFor(
|
|
nsIPrincipal* aPrincipal, nsICookieSettings* aCookieSettings) {
|
|
MOZ_ASSERT(aPrincipal);
|
|
MOZ_ASSERT(aCookieSettings);
|
|
|
|
uint32_t access = nsICookiePermission::ACCESS_DEFAULT;
|
|
if (aPrincipal->GetIsContentPrincipal()) {
|
|
nsPermissionManager* permManager = nsPermissionManager::GetInstance();
|
|
if (permManager) {
|
|
Unused << NS_WARN_IF(NS_FAILED(permManager->TestPermissionFromPrincipal(
|
|
aPrincipal, NS_LITERAL_CSTRING("cookie"), &access)));
|
|
}
|
|
}
|
|
|
|
if (access != nsICookiePermission::ACCESS_DEFAULT) {
|
|
return access != nsICookiePermission::ACCESS_DENY;
|
|
}
|
|
|
|
int32_t behavior = CookiesBehavior(aPrincipal, aCookieSettings);
|
|
return behavior != nsICookieService::BEHAVIOR_REJECT;
|
|
}
|
|
|
|
/* static */
|
|
bool AntiTrackingCommon::MaybeIsFirstPartyStorageAccessGrantedFor(
|
|
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->CookieSettings()->GetRejectThirdPartyTrackers()) {
|
|
LOG(("Disabled by the pref (%d), bail out early",
|
|
parentDocument->CookieSettings()->GetCookieBehavior()));
|
|
return true;
|
|
}
|
|
|
|
if (CheckContentBlockingAllowList(aFirstPartyWindow)) {
|
|
return true;
|
|
}
|
|
|
|
if (!nsContentUtils::IsThirdPartyWindowOrChannel(aFirstPartyWindow, nullptr,
|
|
aURI)) {
|
|
LOG(("Our window isn't a third-party window"));
|
|
return true;
|
|
}
|
|
|
|
uint32_t cookiePermission = CheckCookiePermissionForPrincipal(
|
|
parentDocument->CookieSettings(), 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();
|
|
nsCOMPtr<nsIURI> parentPrincipalURI;
|
|
Unused << parentPrincipal->GetURI(getter_AddRefs(parentPrincipalURI));
|
|
|
|
nsAutoCString type;
|
|
CreatePermissionKey(origin, type);
|
|
|
|
return CheckAntiTrackingPermission(
|
|
parentPrincipal, type,
|
|
nsContentUtils::IsInPrivateBrowsing(parentDocument), nullptr, 0,
|
|
parentPrincipalURI);
|
|
}
|
|
|
|
nsresult AntiTrackingCommon::IsOnContentBlockingAllowList(
|
|
nsIPrincipal* aTopWinPrincipal, bool aIsPrivateBrowsing,
|
|
bool& aIsAllowListed) {
|
|
aIsAllowListed = false;
|
|
|
|
if (!aTopWinPrincipal) {
|
|
// Nothing to do!
|
|
return NS_OK;
|
|
}
|
|
|
|
nsCOMPtr<nsIURI> uri;
|
|
Unused << aTopWinPrincipal->GetURI(getter_AddRefs(uri));
|
|
|
|
LOG_SPEC(("Deciding whether the user has overridden content blocking for %s",
|
|
_spec),
|
|
uri);
|
|
|
|
nsPermissionManager* permManager = nsPermissionManager::GetInstance();
|
|
NS_ENSURE_TRUE(permManager, NS_ERROR_FAILURE);
|
|
|
|
// Check both the normal mode and private browsing mode user override
|
|
// permissions.
|
|
Pair<const nsLiteralCString, bool> types[] = {
|
|
{NS_LITERAL_CSTRING("trackingprotection"), false},
|
|
{NS_LITERAL_CSTRING("trackingprotection-pb"), true}};
|
|
|
|
for (size_t i = 0; i < ArrayLength(types); ++i) {
|
|
if (aIsPrivateBrowsing != types[i].second()) {
|
|
continue;
|
|
}
|
|
|
|
uint32_t permissions = nsIPermissionManager::UNKNOWN_ACTION;
|
|
nsresult rv = permManager->TestPermissionFromPrincipal(
|
|
aTopWinPrincipal, types[i].first(), &permissions);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
if (permissions == nsIPermissionManager::ALLOW_ACTION) {
|
|
aIsAllowListed = true;
|
|
LOG(("Found user override type %s", types[i].first().get()));
|
|
// Stop checking the next permisson type if we decided to override.
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!aIsAllowListed) {
|
|
LOG(("No user override found"));
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
/* static */ void AntiTrackingCommon::ComputeContentBlockingAllowListPrincipal(
|
|
nsIPrincipal* aDocumentPrincipal, nsIPrincipal** aPrincipal) {
|
|
MOZ_ASSERT(aPrincipal);
|
|
|
|
auto returnInputArgument =
|
|
MakeScopeExit([&] { NS_IF_ADDREF(*aPrincipal = aDocumentPrincipal); });
|
|
|
|
BasePrincipal* bp = BasePrincipal::Cast(aDocumentPrincipal);
|
|
if (!bp || !bp->IsContentPrincipal()) {
|
|
// If we have something other than a content principal, just return what we
|
|
// have. This includes the case where we were passed a nullptr.
|
|
return;
|
|
}
|
|
|
|
nsCOMPtr<nsIURI> uri;
|
|
nsresult rv = aDocumentPrincipal->GetURI(getter_AddRefs(uri));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return;
|
|
}
|
|
|
|
// Take the host/port portion so we can allowlist by site. Also ignore the
|
|
// scheme, since users who put sites on the allowlist probably don't expect
|
|
// allowlisting to depend on scheme.
|
|
nsAutoCString escaped(NS_LITERAL_CSTRING("https://"));
|
|
nsAutoCString temp;
|
|
rv = uri->GetHostPort(temp);
|
|
// view-source URIs will be handled by the next block.
|
|
if (NS_FAILED(rv) && !uri->SchemeIs("view-source")) {
|
|
// Normal for some loads, no need to print a warning
|
|
return;
|
|
}
|
|
|
|
// GetHostPort returns an empty string (with a success error code) for file://
|
|
// URIs.
|
|
if (temp.IsEmpty()) {
|
|
// In this case we want to make sure that our allow list principal would be
|
|
// computed as null.
|
|
returnInputArgument.release();
|
|
*aPrincipal = nullptr;
|
|
return;
|
|
}
|
|
escaped.Append(temp);
|
|
|
|
rv = NS_NewURI(getter_AddRefs(uri), escaped);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return;
|
|
}
|
|
|
|
nsCOMPtr<nsIPrincipal> principal = BasePrincipal::CreateContentPrincipal(
|
|
uri, aDocumentPrincipal->OriginAttributesRef());
|
|
if (NS_WARN_IF(!principal)) {
|
|
return;
|
|
}
|
|
|
|
returnInputArgument.release();
|
|
principal.forget(aPrincipal);
|
|
}
|
|
|
|
/* static */ void
|
|
AntiTrackingCommon::RecomputeContentBlockingAllowListPrincipal(
|
|
nsIURI* aURIBeingLoaded, const OriginAttributes& aAttrs,
|
|
nsIPrincipal** aPrincipal) {
|
|
MOZ_ASSERT(aPrincipal);
|
|
|
|
auto returnInputArgument = MakeScopeExit([&] { *aPrincipal = nullptr; });
|
|
|
|
// Take the host/port portion so we can allowlist by site. Also ignore the
|
|
// scheme, since users who put sites on the allowlist probably don't expect
|
|
// allowlisting to depend on scheme.
|
|
nsAutoCString escaped(NS_LITERAL_CSTRING("https://"));
|
|
nsAutoCString temp;
|
|
nsresult rv = aURIBeingLoaded->GetHostPort(temp);
|
|
// view-source URIs will be handled by the next block.
|
|
if (NS_FAILED(rv) && !aURIBeingLoaded->SchemeIs("view-source")) {
|
|
// Normal for some loads, no need to print a warning
|
|
return;
|
|
}
|
|
|
|
// GetHostPort returns an empty string (with a success error code) for file://
|
|
// URIs.
|
|
if (temp.IsEmpty()) {
|
|
return;
|
|
}
|
|
escaped.Append(temp);
|
|
|
|
nsCOMPtr<nsIURI> uri;
|
|
rv = NS_NewURI(getter_AddRefs(uri), escaped);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return;
|
|
}
|
|
|
|
nsCOMPtr<nsIPrincipal> principal =
|
|
BasePrincipal::CreateContentPrincipal(uri, aAttrs);
|
|
if (NS_WARN_IF(!principal)) {
|
|
return;
|
|
}
|
|
|
|
returnInputArgument.release();
|
|
principal.forget(aPrincipal);
|
|
}
|
|
|
|
/* static */
|
|
void AntiTrackingCommon::NotifyBlockingDecision(nsIChannel* aChannel,
|
|
BlockingDecision aDecision,
|
|
uint32_t aRejectedReason) {
|
|
MOZ_ASSERT(
|
|
aRejectedReason == 0 ||
|
|
aRejectedReason ==
|
|
nsIWebProgressListener::STATE_COOKIES_BLOCKED_BY_PERMISSION ||
|
|
aRejectedReason ==
|
|
nsIWebProgressListener::STATE_COOKIES_BLOCKED_TRACKER ||
|
|
aRejectedReason ==
|
|
nsIWebProgressListener::STATE_COOKIES_BLOCKED_SOCIALTRACKER ||
|
|
aRejectedReason ==
|
|
nsIWebProgressListener::STATE_COOKIES_PARTITIONED_FOREIGN ||
|
|
aRejectedReason == nsIWebProgressListener::STATE_COOKIES_BLOCKED_ALL ||
|
|
aRejectedReason == nsIWebProgressListener::STATE_COOKIES_BLOCKED_FOREIGN);
|
|
MOZ_ASSERT(aDecision == BlockingDecision::eBlock ||
|
|
aDecision == BlockingDecision::eAllow);
|
|
|
|
if (!aChannel) {
|
|
return;
|
|
}
|
|
|
|
// Can be called in EITHER the parent or child process.
|
|
if (XRE_IsParentProcess()) {
|
|
nsCOMPtr<nsIParentChannel> parentChannel;
|
|
NS_QueryNotificationCallbacks(aChannel, parentChannel);
|
|
if (parentChannel) {
|
|
// This channel is a parent-process proxy for a child process request.
|
|
// Tell the child process channel to do this instead.
|
|
if (aDecision == BlockingDecision::eBlock) {
|
|
parentChannel->NotifyCookieBlocked(aRejectedReason);
|
|
} else {
|
|
parentChannel->NotifyCookieAllowed();
|
|
}
|
|
|
|
// TODO: For ETP fission, we don't need to notify the
|
|
// OnContentBlockingEvent in the current stage. Because we still
|
|
// send a IPC to the content process in order to update the log in
|
|
// the content. And the content would send back to parent to notify
|
|
// the event. So, we don't need to notify here. But, we will have to
|
|
// notify here once the log move to parent entirely (Bug 1599046).
|
|
}
|
|
return;
|
|
}
|
|
|
|
MOZ_ASSERT(XRE_IsContentProcess());
|
|
|
|
nsCOMPtr<mozIThirdPartyUtil> thirdPartyUtil = services::GetThirdPartyUtil();
|
|
if (!thirdPartyUtil) {
|
|
return;
|
|
}
|
|
|
|
nsCOMPtr<nsIURI> uriBeingLoaded = MaybeGetDocumentURIBeingLoaded(aChannel);
|
|
nsCOMPtr<mozIDOMWindowProxy> win;
|
|
nsresult rv = thirdPartyUtil->GetTopWindowForChannel(aChannel, uriBeingLoaded,
|
|
getter_AddRefs(win));
|
|
NS_ENSURE_SUCCESS_VOID(rv);
|
|
|
|
nsCOMPtr<nsPIDOMWindowOuter> pwin = nsPIDOMWindowOuter::From(win);
|
|
if (!pwin) {
|
|
return;
|
|
}
|
|
|
|
nsCOMPtr<nsIURI> uri;
|
|
aChannel->GetURI(getter_AddRefs(uri));
|
|
|
|
NotifyBlockingDecisionInternal(aChannel, aChannel, aDecision, aRejectedReason,
|
|
uri, pwin);
|
|
}
|
|
|
|
/* static */
|
|
void AntiTrackingCommon::NotifyBlockingDecision(nsPIDOMWindowInner* aWindow,
|
|
BlockingDecision aDecision,
|
|
uint32_t aRejectedReason) {
|
|
MOZ_ASSERT(aWindow);
|
|
MOZ_ASSERT(
|
|
aRejectedReason == 0 ||
|
|
aRejectedReason ==
|
|
nsIWebProgressListener::STATE_COOKIES_BLOCKED_BY_PERMISSION ||
|
|
aRejectedReason ==
|
|
nsIWebProgressListener::STATE_COOKIES_BLOCKED_TRACKER ||
|
|
aRejectedReason ==
|
|
nsIWebProgressListener::STATE_COOKIES_BLOCKED_SOCIALTRACKER ||
|
|
aRejectedReason ==
|
|
nsIWebProgressListener::STATE_COOKIES_PARTITIONED_FOREIGN ||
|
|
aRejectedReason == nsIWebProgressListener::STATE_COOKIES_BLOCKED_ALL ||
|
|
aRejectedReason == nsIWebProgressListener::STATE_COOKIES_BLOCKED_FOREIGN);
|
|
MOZ_ASSERT(aDecision == BlockingDecision::eBlock ||
|
|
aDecision == BlockingDecision::eAllow);
|
|
|
|
nsCOMPtr<nsPIDOMWindowOuter> pwin = GetTopWindow(aWindow);
|
|
if (!pwin) {
|
|
return;
|
|
}
|
|
|
|
nsPIDOMWindowInner* inner = pwin->GetCurrentInnerWindow();
|
|
if (!inner) {
|
|
return;
|
|
}
|
|
Document* pwinDoc = inner->GetExtantDoc();
|
|
if (!pwinDoc) {
|
|
return;
|
|
}
|
|
nsIChannel* channel = pwinDoc->GetChannel();
|
|
if (!channel) {
|
|
return;
|
|
}
|
|
|
|
Document* document = aWindow->GetExtantDoc();
|
|
if (!document) {
|
|
return;
|
|
}
|
|
nsIURI* uri = document->GetDocumentURI();
|
|
nsIChannel* trackingChannel = document->GetChannel();
|
|
|
|
NotifyBlockingDecisionInternal(channel, trackingChannel, aDecision,
|
|
aRejectedReason, uri, pwin);
|
|
}
|
|
|
|
/* static */
|
|
void AntiTrackingCommon::StoreUserInteractionFor(nsIPrincipal* aPrincipal) {
|
|
if (!aPrincipal) {
|
|
// The content process may have sent us garbage data.
|
|
return;
|
|
}
|
|
|
|
if (XRE_IsParentProcess()) {
|
|
nsCOMPtr<nsIURI> uri;
|
|
Unused << aPrincipal->GetURI(getter_AddRefs(uri));
|
|
LOG_SPEC(("Saving the userInteraction for %s", _spec), uri);
|
|
|
|
nsPermissionManager* permManager = nsPermissionManager::GetInstance();
|
|
if (NS_WARN_IF(!permManager)) {
|
|
LOG(("Permission manager is null, bailing out early"));
|
|
return;
|
|
}
|
|
|
|
// Remember that this pref is stored in seconds!
|
|
uint32_t expirationType = nsIPermissionManager::EXPIRE_TIME;
|
|
uint32_t expirationTime =
|
|
StaticPrefs::privacy_userInteraction_expiration() * 1000;
|
|
int64_t when = (PR_Now() / PR_USEC_PER_MSEC) + expirationTime;
|
|
|
|
uint32_t privateBrowsingId = 0;
|
|
nsresult rv = aPrincipal->GetPrivateBrowsingId(&privateBrowsingId);
|
|
if (!NS_WARN_IF(NS_FAILED(rv)) && privateBrowsingId > 0) {
|
|
// If we are coming from a private window, make sure to store a
|
|
// session-only permission which won't get persisted to disk.
|
|
expirationType = nsIPermissionManager::EXPIRE_SESSION;
|
|
when = 0;
|
|
}
|
|
|
|
rv = permManager->AddFromPrincipal(aPrincipal, USER_INTERACTION_PERM,
|
|
nsIPermissionManager::ALLOW_ACTION,
|
|
expirationType, when);
|
|
Unused << NS_WARN_IF(NS_FAILED(rv));
|
|
return;
|
|
}
|
|
|
|
ContentChild* cc = ContentChild::GetSingleton();
|
|
MOZ_ASSERT(cc);
|
|
|
|
nsCOMPtr<nsIURI> uri;
|
|
Unused << aPrincipal->GetURI(getter_AddRefs(uri));
|
|
LOG_SPEC(("Asking the parent process to save the user-interaction for us: %s",
|
|
_spec),
|
|
uri);
|
|
cc->SendStoreUserInteractionAsPermission(IPC::Principal(aPrincipal));
|
|
}
|
|
|
|
/* static */
|
|
bool AntiTrackingCommon::HasUserInteraction(nsIPrincipal* aPrincipal) {
|
|
nsPermissionManager* permManager = nsPermissionManager::GetInstance();
|
|
if (NS_WARN_IF(!permManager)) {
|
|
return false;
|
|
}
|
|
|
|
uint32_t result = 0;
|
|
nsresult rv = permManager->TestPermissionWithoutDefaultsFromPrincipal(
|
|
aPrincipal, USER_INTERACTION_PERM, &result);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return false;
|
|
}
|
|
|
|
return result == nsIPermissionManager::ALLOW_ACTION;
|
|
}
|
|
|
|
// static
|
|
void AntiTrackingCommon::OnAntiTrackingSettingsChanged(
|
|
const AntiTrackingCommon::AntiTrackingSettingsChangedCallback& aCallback) {
|
|
static bool initialized = false;
|
|
if (!initialized) {
|
|
// It is possible that while we have some data in our cache, something
|
|
// changes in our environment that causes the anti-tracking checks below to
|
|
// change their response. Therefore, we need to clear our cache when we
|
|
// detect a related change.
|
|
Preferences::RegisterPrefixCallback(
|
|
SettingsChangeObserver::PrivacyPrefChanged, "browser.contentblocking.");
|
|
Preferences::RegisterPrefixCallback(
|
|
SettingsChangeObserver::PrivacyPrefChanged, "network.cookie.");
|
|
Preferences::RegisterPrefixCallback(
|
|
SettingsChangeObserver::PrivacyPrefChanged, "privacy.");
|
|
|
|
nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
|
|
if (obs) {
|
|
RefPtr<SettingsChangeObserver> observer = new SettingsChangeObserver();
|
|
obs->AddObserver(observer, "perm-added", false);
|
|
obs->AddObserver(observer, "perm-changed", false);
|
|
obs->AddObserver(observer, "perm-cleared", false);
|
|
obs->AddObserver(observer, "perm-deleted", false);
|
|
obs->AddObserver(observer, "xpcom-shutdown", false);
|
|
}
|
|
|
|
gSettingsChangedCallbacks =
|
|
MakeUnique<nsTArray<AntiTrackingSettingsChangedCallback>>();
|
|
|
|
initialized = true;
|
|
}
|
|
|
|
gSettingsChangedCallbacks->AppendElement(aCallback);
|
|
}
|
|
|
|
/* static */
|
|
already_AddRefed<nsIURI> AntiTrackingCommon::MaybeGetDocumentURIBeingLoaded(
|
|
nsIChannel* aChannel) {
|
|
nsCOMPtr<nsIURI> uriBeingLoaded;
|
|
nsLoadFlags loadFlags = 0;
|
|
nsresult rv = aChannel->GetLoadFlags(&loadFlags);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return nullptr;
|
|
}
|
|
if (loadFlags & nsIChannel::LOAD_DOCUMENT_URI) {
|
|
// If the channel being loaded is a document channel, this call may be
|
|
// coming from an OnStopRequest notification, which might mean that our
|
|
// document may still be in the loading process, so we may need to pass in
|
|
// the uriBeingLoaded argument explicitly.
|
|
rv = aChannel->GetURI(getter_AddRefs(uriBeingLoaded));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return nullptr;
|
|
}
|
|
}
|
|
return uriBeingLoaded.forget();
|
|
}
|
|
|
|
/* static */
|
|
void AntiTrackingCommon::NotifyContentBlockingEvent(
|
|
nsPIDOMWindowOuter* aWindow, nsIChannel* aReportingChannel,
|
|
nsIChannel* aTrackingChannel, bool aBlocked, uint32_t aRejectedReason,
|
|
nsIURI* aURI, const Maybe<StorageAccessGrantedReason>& aReason) {
|
|
MOZ_ASSERT(aWindow);
|
|
|
|
// Log the content blocking event. This should be removed in Bug 1599046.
|
|
aWindow->NotifyContentBlockingEvent(aRejectedReason, aReportingChannel,
|
|
aBlocked, aURI, aTrackingChannel,
|
|
aReason);
|
|
|
|
RefPtr<dom::BrowserChild> browserChild = dom::BrowserChild::GetFrom(aWindow);
|
|
NS_ENSURE_TRUE_VOID(browserChild);
|
|
|
|
nsTArray<nsCString> trackingFullHashes;
|
|
nsCOMPtr<nsIClassifiedChannel> classifiedChannel =
|
|
do_QueryInterface(aTrackingChannel);
|
|
|
|
if (classifiedChannel) {
|
|
Unused << classifiedChannel->GetMatchedTrackingFullHashes(
|
|
trackingFullHashes);
|
|
}
|
|
|
|
// Send a message to notify OnContentBlockingEvent in the parent, which will
|
|
// update the ContentBlockingLog in the parent.
|
|
// We also notify the event in the content above since we haven't move the log
|
|
// into the parent process entirely. So, we need to notify and update the
|
|
// ContentBlockingLog in the content processes. We can remove this once we
|
|
// finish the Bug 1599046.
|
|
browserChild->NotifyContentBlockingEvent(aRejectedReason, aReportingChannel,
|
|
aBlocked, aURI, trackingFullHashes,
|
|
aReason);
|
|
}
|
|
|
|
/* static */
|
|
void AntiTrackingCommon::RedirectHeuristic(nsIChannel* aOldChannel,
|
|
nsIURI* aOldURI,
|
|
nsIChannel* aNewChannel,
|
|
nsIURI* aNewURI) {
|
|
MOZ_ASSERT(aOldChannel);
|
|
MOZ_ASSERT(aOldURI);
|
|
MOZ_ASSERT(aNewChannel);
|
|
MOZ_ASSERT(aNewURI);
|
|
|
|
nsresult rv;
|
|
|
|
if (!StaticPrefs::privacy_restrict3rdpartystorage_heuristic_redirect()) {
|
|
return;
|
|
}
|
|
|
|
nsCOMPtr<nsIHttpChannel> newChannel = do_QueryInterface(aNewChannel);
|
|
if (!newChannel) {
|
|
return;
|
|
}
|
|
|
|
LOG_SPEC(("Checking redirect-heuristic for %s", _spec), aOldURI);
|
|
|
|
nsCOMPtr<nsILoadInfo> oldLoadInfo = aOldChannel->LoadInfo();
|
|
MOZ_ASSERT(oldLoadInfo);
|
|
|
|
nsCOMPtr<nsILoadInfo> newLoadInfo = aNewChannel->LoadInfo();
|
|
MOZ_ASSERT(newLoadInfo);
|
|
|
|
nsContentPolicyType contentType = oldLoadInfo->GetExternalContentPolicyType();
|
|
if (contentType != nsIContentPolicy::TYPE_DOCUMENT ||
|
|
!aOldChannel->IsDocument()) {
|
|
LOG_SPEC(("Ignoring redirect for %s because it's not a document", _spec),
|
|
aOldURI);
|
|
// We care about document redirects only.
|
|
return;
|
|
}
|
|
|
|
nsCOMPtr<nsIClassifiedChannel> classifiedOldChannel =
|
|
do_QueryInterface(aOldChannel);
|
|
nsCOMPtr<nsIClassifiedChannel> classifiedNewChannel =
|
|
do_QueryInterface(aNewChannel);
|
|
if (!classifiedOldChannel || !classifiedNewChannel) {
|
|
LOG_SPEC2(("Ignoring redirect for %s to %s because there is not "
|
|
"nsIClassifiedChannel interface",
|
|
_spec1, _spec2),
|
|
aOldURI, aNewURI);
|
|
return;
|
|
}
|
|
|
|
bool allowedByPreviousRedirect =
|
|
oldLoadInfo->GetAllowListFutureDocumentsCreatedFromThisRedirectChain();
|
|
if (classifiedNewChannel->IsTrackingResource()) {
|
|
// This is not a tracking -> non-tracking redirect.
|
|
LOG_SPEC2(("Redirect for %s to %s because it's not tracking to "
|
|
"non-tracking. Part of a chain of granted redirects: %d",
|
|
_spec1, _spec2, allowedByPreviousRedirect),
|
|
aOldURI, aNewURI);
|
|
newLoadInfo->SetAllowListFutureDocumentsCreatedFromThisRedirectChain(
|
|
allowedByPreviousRedirect);
|
|
return;
|
|
}
|
|
|
|
if (!classifiedOldChannel->IsTrackingResource() &&
|
|
!allowedByPreviousRedirect) {
|
|
// This is not a tracking -> non-tracking redirect.
|
|
LOG_SPEC2(
|
|
("Redirect for %s to %s because it's not tracking to non-tracking.",
|
|
_spec1, _spec2),
|
|
aOldURI, aNewURI);
|
|
return;
|
|
}
|
|
|
|
nsIScriptSecurityManager* ssm =
|
|
nsScriptSecurityManager::GetScriptSecurityManager();
|
|
MOZ_ASSERT(ssm);
|
|
|
|
nsCOMPtr<nsIPrincipal> trackingPrincipal;
|
|
nsCOMPtr<nsIURI> trackingURI;
|
|
|
|
if (allowedByPreviousRedirect && !chain.IsEmpty()) {
|
|
const nsTArray<nsCOMPtr<nsIRedirectHistoryEntry>>& chain =
|
|
oldLoadInfo->RedirectChain();
|
|
rv = chain[0]->GetPrincipal(getter_AddRefs(trackingPrincipal));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
LOG(("Can't obtain the principal from the redirect chain"));
|
|
return;
|
|
}
|
|
|
|
rv = trackingPrincipal->GetURI(getter_AddRefs(trackingURI));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
LOG(("Can't obtain the uri from the redirect chain"));
|
|
return;
|
|
}
|
|
} else {
|
|
trackingURI = aOldURI;
|
|
rv = ssm->GetChannelResultPrincipal(aOldChannel,
|
|
getter_AddRefs(trackingPrincipal));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
LOG(("Can't obtain the principal from the tracking"));
|
|
return;
|
|
}
|
|
}
|
|
|
|
MOZ_ASSERT(trackingURI);
|
|
|
|
nsCOMPtr<nsIPrincipal> redirectedPrincipal;
|
|
rv = ssm->GetChannelResultPrincipal(aNewChannel,
|
|
getter_AddRefs(redirectedPrincipal));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
LOG(("Can't obtain the principal from the redirected"));
|
|
return;
|
|
}
|
|
|
|
if (!AntiTrackingCommon::HasUserInteraction(trackingPrincipal)) {
|
|
LOG_SPEC2(("Ignoring redirect for %s to %s because no user-interaction on "
|
|
"tracker",
|
|
_spec1, _spec2),
|
|
aOldURI, aNewURI);
|
|
return;
|
|
}
|
|
|
|
nsAutoCString trackingOrigin;
|
|
rv = nsContentUtils::GetASCIIOrigin(trackingURI, trackingOrigin);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
LOG(("Can't get the origin from the URI"));
|
|
return;
|
|
}
|
|
|
|
nsAutoCString redirectedOrigin;
|
|
rv = nsContentUtils::GetASCIIOrigin(aNewURI, redirectedOrigin);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
LOG(("Can't get the origin from the URI"));
|
|
return;
|
|
}
|
|
|
|
LOG(("Adding a first-party storage exception for %s...",
|
|
PromiseFlatCString(redirectedOrigin).get()));
|
|
|
|
nsCOMPtr<nsICookieSettings> cookieSettings;
|
|
rv = oldLoadInfo->GetCookieSettings(getter_AddRefs(cookieSettings));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
LOG(("Can't get the cookieSettings"));
|
|
return;
|
|
}
|
|
|
|
int32_t behavior = cookieSettings->GetCookieBehavior();
|
|
|
|
if (!cookieSettings->GetRejectThirdPartyTrackers()) {
|
|
LOG(
|
|
("Disabled by network.cookie.cookieBehavior pref (%d), bailing out "
|
|
"early",
|
|
behavior));
|
|
return;
|
|
}
|
|
|
|
MOZ_ASSERT(
|
|
behavior == nsICookieService::BEHAVIOR_REJECT_TRACKER ||
|
|
behavior ==
|
|
nsICookieService::BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN);
|
|
|
|
if (CheckContentBlockingAllowList(newChannel)) {
|
|
return;
|
|
}
|
|
|
|
LOG(("Saving the permission: trackingOrigin=%s, grantedOrigin=%s",
|
|
trackingOrigin.get(), redirectedOrigin.get()));
|
|
|
|
// Any new redirect from this loadInfo must be considered as granted.
|
|
newLoadInfo->SetAllowListFutureDocumentsCreatedFromThisRedirectChain(true);
|
|
|
|
uint64_t innerWindowID;
|
|
Unused << newChannel->GetTopLevelContentWindowId(&innerWindowID);
|
|
|
|
nsAutoString errorText;
|
|
AutoTArray<nsString, 2> params = {NS_ConvertUTF8toUTF16(redirectedOrigin),
|
|
NS_ConvertUTF8toUTF16(trackingOrigin)};
|
|
rv = nsContentUtils::FormatLocalizedString(
|
|
nsContentUtils::eNECKO_PROPERTIES, "CookieAllowedForTrackerByHeuristic",
|
|
params, errorText);
|
|
if (NS_SUCCEEDED(rv)) {
|
|
nsContentUtils::ReportToConsoleByWindowID(
|
|
errorText, nsIScriptError::warningFlag, ANTITRACKING_CONSOLE_CATEGORY,
|
|
innerWindowID);
|
|
}
|
|
|
|
// We don't care about this promise because the operation is actually sync.
|
|
RefPtr<FirstPartyStorageAccessGrantPromise> promise =
|
|
SaveFirstPartyStorageAccessGrantedForOriginOnParentProcess(
|
|
redirectedPrincipal, trackingPrincipal, trackingOrigin,
|
|
StorageAccessPromptChoices::eAllow,
|
|
StaticPrefs::privacy_restrict3rdpartystorage_expiration_redirect());
|
|
Unused << promise;
|
|
}
|