fune/toolkit/components/antitracking/StoragePrincipalHelper.cpp
Sandor Molnar 09e7cbd0b7 Backed out 6 changesets (bug 1876575, bug 1876574) for causing build bustages @ toolkit/components/resistfingerprinting/nsRFPService.cpp CLOSED TREE
Backed out changeset 455ce831c73c (bug 1876575)
Backed out changeset 4fa3fbf3a3ae (bug 1876575)
Backed out changeset ac4c41cb3b67 (bug 1876575)
Backed out changeset 15e06d10788e (bug 1876575)
Backed out changeset dcd6bbea816a (bug 1876575)
Backed out changeset cc547125fda9 (bug 1876574)
2024-03-29 16:29:47 +02:00

677 lines
21 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 "StoragePrincipalHelper.h"
#include "mozilla/ipc/PBackgroundSharedTypes.h"
#include "mozilla/dom/Document.h"
#include "mozilla/dom/WorkerPrivate.h"
#include "mozilla/net/CookieJarSettings.h"
#include "mozilla/ScopeExit.h"
#include "mozilla/StaticPrefs_privacy.h"
#include "mozilla/StorageAccess.h"
#include "nsContentUtils.h"
#include "nsICookieJarSettings.h"
#include "nsICookieService.h"
#include "nsIDocShell.h"
#include "nsIEffectiveTLDService.h"
#include "nsIPrivateBrowsingChannel.h"
#include "AntiTrackingUtils.h"
namespace mozilla {
namespace {
bool ShouldPartitionChannel(nsIChannel* aChannel,
nsICookieJarSettings* aCookieJarSettings) {
MOZ_ASSERT(aChannel);
nsCOMPtr<nsIURI> uri;
nsresult rv = aChannel->GetURI(getter_AddRefs(uri));
if (NS_FAILED(rv)) {
return false;
}
uint32_t rejectedReason = 0;
if (ShouldAllowAccessFor(aChannel, uri, &rejectedReason)) {
return false;
}
// Let's use the storage principal only if we need to partition the cookie
// jar. We use the lower-level ContentBlocking API here to ensure this
// check doesn't send notifications.
if (!ShouldPartitionStorage(rejectedReason) ||
!StoragePartitioningEnabled(rejectedReason, aCookieJarSettings)) {
return false;
}
return true;
}
bool ChooseOriginAttributes(nsIChannel* aChannel, OriginAttributes& aAttrs,
bool aForcePartitionedPrincipal) {
MOZ_ASSERT(aChannel);
nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
nsCOMPtr<nsICookieJarSettings> cjs;
Unused << loadInfo->GetCookieJarSettings(getter_AddRefs(cjs));
if (!aForcePartitionedPrincipal && !ShouldPartitionChannel(aChannel, cjs)) {
return false;
}
nsAutoString partitionKey;
Unused << cjs->GetPartitionKey(partitionKey);
if (!partitionKey.IsEmpty()) {
aAttrs.SetPartitionKey(partitionKey);
return true;
}
// Fallback to get first-party domain from top-level principal when we can't
// get it from CookieJarSetting. This might happen when a channel is not
// opened via http, for example, about page.
nsCOMPtr<nsIPrincipal> toplevelPrincipal = loadInfo->GetTopLevelPrincipal();
if (!toplevelPrincipal) {
return false;
}
// Cast to BasePrincipal to continue to get acess to GetUri()
auto* basePrin = BasePrincipal::Cast(toplevelPrincipal);
nsCOMPtr<nsIURI> principalURI;
nsresult rv = basePrin->GetURI(getter_AddRefs(principalURI));
if (NS_WARN_IF(NS_FAILED(rv))) {
return false;
}
aAttrs.SetPartitionKey(principalURI);
return true;
}
bool VerifyValidPartitionedPrincipalInfoForPrincipalInfoInternal(
const ipc::PrincipalInfo& aPartitionedPrincipalInfo,
const ipc::PrincipalInfo& aPrincipalInfo,
bool aIgnoreSpecForContentPrincipal,
bool aIgnoreDomainForContentPrincipal) {
if (aPartitionedPrincipalInfo.type() != aPrincipalInfo.type()) {
return false;
}
if (aPartitionedPrincipalInfo.type() ==
mozilla::ipc::PrincipalInfo::TContentPrincipalInfo) {
const mozilla::ipc::ContentPrincipalInfo& spInfo =
aPartitionedPrincipalInfo.get_ContentPrincipalInfo();
const mozilla::ipc::ContentPrincipalInfo& pInfo =
aPrincipalInfo.get_ContentPrincipalInfo();
return spInfo.attrs().EqualsIgnoringPartitionKey(pInfo.attrs()) &&
spInfo.originNoSuffix() == pInfo.originNoSuffix() &&
(aIgnoreSpecForContentPrincipal || spInfo.spec() == pInfo.spec()) &&
(aIgnoreDomainForContentPrincipal ||
spInfo.domain() == pInfo.domain()) &&
spInfo.baseDomain() == pInfo.baseDomain();
}
if (aPartitionedPrincipalInfo.type() ==
mozilla::ipc::PrincipalInfo::TSystemPrincipalInfo) {
// Nothing to check here.
return true;
}
if (aPartitionedPrincipalInfo.type() ==
mozilla::ipc::PrincipalInfo::TNullPrincipalInfo) {
const mozilla::ipc::NullPrincipalInfo& spInfo =
aPartitionedPrincipalInfo.get_NullPrincipalInfo();
const mozilla::ipc::NullPrincipalInfo& pInfo =
aPrincipalInfo.get_NullPrincipalInfo();
return spInfo.spec() == pInfo.spec() &&
spInfo.attrs().EqualsIgnoringPartitionKey(pInfo.attrs());
}
if (aPartitionedPrincipalInfo.type() ==
mozilla::ipc::PrincipalInfo::TExpandedPrincipalInfo) {
const mozilla::ipc::ExpandedPrincipalInfo& spInfo =
aPartitionedPrincipalInfo.get_ExpandedPrincipalInfo();
const mozilla::ipc::ExpandedPrincipalInfo& pInfo =
aPrincipalInfo.get_ExpandedPrincipalInfo();
if (!spInfo.attrs().EqualsIgnoringPartitionKey(pInfo.attrs())) {
return false;
}
if (spInfo.allowlist().Length() != pInfo.allowlist().Length()) {
return false;
}
for (uint32_t i = 0; i < spInfo.allowlist().Length(); ++i) {
if (!VerifyValidPartitionedPrincipalInfoForPrincipalInfoInternal(
spInfo.allowlist()[i], pInfo.allowlist()[i],
aIgnoreSpecForContentPrincipal,
aIgnoreDomainForContentPrincipal)) {
return false;
}
}
return true;
}
MOZ_CRASH("Invalid principalInfo type");
return false;
}
} // namespace
// static
nsresult StoragePrincipalHelper::Create(nsIChannel* aChannel,
nsIPrincipal* aPrincipal,
bool aForceIsolation,
nsIPrincipal** aStoragePrincipal) {
MOZ_ASSERT(aChannel);
MOZ_ASSERT(aPrincipal);
MOZ_ASSERT(aStoragePrincipal);
auto scopeExit = MakeScopeExit([&] {
nsCOMPtr<nsIPrincipal> storagePrincipal = aPrincipal;
storagePrincipal.forget(aStoragePrincipal);
});
OriginAttributes attrs = aPrincipal->OriginAttributesRef();
if (!ChooseOriginAttributes(aChannel, attrs, aForceIsolation)) {
return NS_OK;
}
scopeExit.release();
nsCOMPtr<nsIPrincipal> storagePrincipal =
BasePrincipal::Cast(aPrincipal)->CloneForcingOriginAttributes(attrs);
// If aPrincipal is not a ContentPrincipal, e.g. a NullPrincipal, the clone
// call will return a nullptr.
NS_ENSURE_TRUE(storagePrincipal, NS_ERROR_FAILURE);
storagePrincipal.forget(aStoragePrincipal);
return NS_OK;
}
// static
nsresult StoragePrincipalHelper::CreatePartitionedPrincipalForServiceWorker(
nsIPrincipal* aPrincipal, nsICookieJarSettings* aCookieJarSettings,
nsIPrincipal** aPartitionedPrincipal) {
MOZ_ASSERT(aPrincipal);
MOZ_ASSERT(aPartitionedPrincipal);
OriginAttributes attrs = aPrincipal->OriginAttributesRef();
nsAutoString partitionKey;
Unused << aCookieJarSettings->GetPartitionKey(partitionKey);
if (!partitionKey.IsEmpty()) {
attrs.SetPartitionKey(partitionKey);
}
nsCOMPtr<nsIPrincipal> partitionedPrincipal =
BasePrincipal::Cast(aPrincipal)->CloneForcingOriginAttributes(attrs);
// If aPrincipal is not a ContentPrincipal, e.g. a NullPrincipal, the clone
// call will return a nullptr.
NS_ENSURE_TRUE(partitionedPrincipal, NS_ERROR_FAILURE);
partitionedPrincipal.forget(aPartitionedPrincipal);
return NS_OK;
}
// static
nsresult
StoragePrincipalHelper::PrepareEffectiveStoragePrincipalOriginAttributes(
nsIChannel* aChannel, OriginAttributes& aOriginAttributes) {
MOZ_ASSERT(aChannel);
ChooseOriginAttributes(aChannel, aOriginAttributes, false);
return NS_OK;
}
// static
bool StoragePrincipalHelper::VerifyValidStoragePrincipalInfoForPrincipalInfo(
const mozilla::ipc::PrincipalInfo& aStoragePrincipalInfo,
const mozilla::ipc::PrincipalInfo& aPrincipalInfo) {
return VerifyValidPartitionedPrincipalInfoForPrincipalInfoInternal(
aStoragePrincipalInfo, aPrincipalInfo, false, false);
}
// static
bool StoragePrincipalHelper::VerifyValidClientPrincipalInfoForPrincipalInfo(
const mozilla::ipc::PrincipalInfo& aClientPrincipalInfo,
const mozilla::ipc::PrincipalInfo& aPrincipalInfo) {
return VerifyValidPartitionedPrincipalInfoForPrincipalInfoInternal(
aClientPrincipalInfo, aPrincipalInfo, true, true);
}
// static
nsresult StoragePrincipalHelper::GetPrincipal(nsIChannel* aChannel,
PrincipalType aPrincipalType,
nsIPrincipal** aPrincipal) {
MOZ_ASSERT(aChannel);
MOZ_ASSERT(aPrincipal);
nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
nsCOMPtr<nsICookieJarSettings> cjs;
Unused << loadInfo->GetCookieJarSettings(getter_AddRefs(cjs));
nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
MOZ_DIAGNOSTIC_ASSERT(ssm);
nsCOMPtr<nsIPrincipal> principal;
nsCOMPtr<nsIPrincipal> partitionedPrincipal;
nsresult rv =
ssm->GetChannelResultPrincipals(aChannel, getter_AddRefs(principal),
getter_AddRefs(partitionedPrincipal));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
// The aChannel might not be opened in some cases, e.g. getting principal
// for the new channel during a redirect. So, the value
// `IsThirdPartyToTopWindow` is incorrect in this case because this value is
// calculated during opening a channel. And we need to know the value in order
// to get the correct principal. To fix this, we compute the value here even
// the channel hasn't been opened yet.
//
// Note that we don't need to compute the value if there is no browsing
// context ID assigned. This could happen in a GTest or XPCShell.
//
// ToDo: The AntiTrackingUtils::ComputeIsThirdPartyToTopWindow() is only
// available in the parent process. So, this can only work in the parent
// process. It's fine for now, but we should change this to also work in
// content processes. Bug 1736452 will address this.
//
if (XRE_IsParentProcess() && loadInfo->GetBrowsingContextID() != 0) {
AntiTrackingUtils::ComputeIsThirdPartyToTopWindow(aChannel);
}
nsCOMPtr<nsIPrincipal> outPrincipal = principal;
switch (aPrincipalType) {
case eRegularPrincipal:
break;
case eStorageAccessPrincipal:
if (ShouldPartitionChannel(aChannel, cjs)) {
outPrincipal = partitionedPrincipal;
}
break;
case ePartitionedPrincipal:
outPrincipal = partitionedPrincipal;
break;
case eForeignPartitionedPrincipal:
// We only support foreign partitioned principal when dFPI is enabled.
if (cjs->GetCookieBehavior() ==
nsICookieService::BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN &&
loadInfo->GetIsThirdPartyContextToTopWindow()) {
outPrincipal = partitionedPrincipal;
}
break;
}
outPrincipal.forget(aPrincipal);
return NS_OK;
}
// static
nsresult StoragePrincipalHelper::GetPrincipal(nsPIDOMWindowInner* aWindow,
PrincipalType aPrincipalType,
nsIPrincipal** aPrincipal) {
MOZ_ASSERT(aWindow);
MOZ_ASSERT(aPrincipal);
nsCOMPtr<dom::Document> doc = aWindow->GetExtantDoc();
NS_ENSURE_STATE(doc);
nsCOMPtr<nsIPrincipal> outPrincipal;
switch (aPrincipalType) {
case eRegularPrincipal:
outPrincipal = doc->NodePrincipal();
break;
case eStorageAccessPrincipal:
outPrincipal = doc->EffectiveStoragePrincipal();
break;
case ePartitionedPrincipal:
outPrincipal = doc->PartitionedPrincipal();
break;
case eForeignPartitionedPrincipal:
// We only support foreign partitioned principal when dFPI is enabled.
if (doc->CookieJarSettings()->GetCookieBehavior() ==
nsICookieService::BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN &&
AntiTrackingUtils::IsThirdPartyWindow(aWindow, nullptr)) {
outPrincipal = doc->PartitionedPrincipal();
} else {
outPrincipal = doc->NodePrincipal();
}
break;
}
outPrincipal.forget(aPrincipal);
return NS_OK;
}
// static
bool StoragePrincipalHelper::ShouldUsePartitionPrincipalForServiceWorker(
nsIDocShell* aDocShell) {
MOZ_ASSERT(aDocShell);
// We don't use the partitioned principal for service workers if it's
// disabled.
if (!StaticPrefs::privacy_partition_serviceWorkers()) {
return false;
}
RefPtr<dom::Document> document = aDocShell->GetExtantDocument();
// If we cannot get the document from the docShell, we turn to get its
// parent's document.
if (!document) {
nsCOMPtr<nsIDocShellTreeItem> parentItem;
aDocShell->GetInProcessSameTypeParent(getter_AddRefs(parentItem));
if (parentItem) {
document = parentItem->GetDocument();
}
}
nsCOMPtr<nsICookieJarSettings> cookieJarSettings;
if (document) {
cookieJarSettings = document->CookieJarSettings();
} else {
// If there was no document, we create one cookieJarSettings here in order
// to get the cookieBehavior. We don't need a real value for RFP because
// we are only using this object to check default cookie behavior.
cookieJarSettings =
net::CookieJarSettings::Create(net::CookieJarSettings::eRegular,
/* shouldResistFingerpreinting */ false);
}
// We only support partitioned service workers when dFPI is enabled.
if (cookieJarSettings->GetCookieBehavior() !=
nsICookieService::BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN) {
return false;
}
// Only the third-party context will need to use the partitioned principal. A
// first-party context is still using the regular principal for the service
// worker.
return AntiTrackingUtils::IsThirdPartyContext(
document ? document->GetBrowsingContext()
: aDocShell->GetBrowsingContext());
}
// static
bool StoragePrincipalHelper::ShouldUsePartitionPrincipalForServiceWorker(
dom::WorkerPrivate* aWorkerPrivate) {
MOZ_ASSERT(aWorkerPrivate);
// We don't use the partitioned principal for service workers if it's
// disabled.
if (!StaticPrefs::privacy_partition_serviceWorkers()) {
return false;
}
nsCOMPtr<nsICookieJarSettings> cookieJarSettings =
aWorkerPrivate->CookieJarSettings();
// We only support partitioned service workers when dFPI is enabled.
if (cookieJarSettings->GetCookieBehavior() !=
nsICookieService::BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN) {
return false;
}
return aWorkerPrivate->IsThirdPartyContextToTopWindow();
}
// static
bool StoragePrincipalHelper::GetOriginAttributes(
nsIChannel* aChannel, mozilla::OriginAttributes& aAttributes,
StoragePrincipalHelper::PrincipalType aPrincipalType) {
nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
loadInfo->GetOriginAttributes(&aAttributes);
bool isPrivate = false;
nsCOMPtr<nsIPrivateBrowsingChannel> pbChannel = do_QueryInterface(aChannel);
if (pbChannel) {
nsresult rv = pbChannel->GetIsChannelPrivate(&isPrivate);
NS_ENSURE_SUCCESS(rv, false);
} else {
// Some channels may not implement nsIPrivateBrowsingChannel
nsCOMPtr<nsILoadContext> loadContext;
NS_QueryNotificationCallbacks(aChannel, loadContext);
isPrivate = loadContext && loadContext->UsePrivateBrowsing();
}
aAttributes.SyncAttributesWithPrivateBrowsing(isPrivate);
nsCOMPtr<nsICookieJarSettings> cjs;
switch (aPrincipalType) {
case eRegularPrincipal:
break;
case eStorageAccessPrincipal:
PrepareEffectiveStoragePrincipalOriginAttributes(aChannel, aAttributes);
break;
case ePartitionedPrincipal:
ChooseOriginAttributes(aChannel, aAttributes, true);
break;
case eForeignPartitionedPrincipal:
Unused << loadInfo->GetCookieJarSettings(getter_AddRefs(cjs));
// We only support foreign partitioned principal when dFPI is enabled.
// Otherwise, we will use the regular principal.
if (cjs->GetCookieBehavior() ==
nsICookieService::BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN &&
loadInfo->GetIsThirdPartyContextToTopWindow()) {
ChooseOriginAttributes(aChannel, aAttributes, true);
}
break;
}
return true;
}
// static
bool StoragePrincipalHelper::GetRegularPrincipalOriginAttributes(
dom::Document* aDocument, OriginAttributes& aAttributes) {
aAttributes = mozilla::OriginAttributes();
if (!aDocument) {
return false;
}
nsCOMPtr<nsILoadGroup> loadGroup = aDocument->GetDocumentLoadGroup();
if (loadGroup) {
return GetRegularPrincipalOriginAttributes(loadGroup, aAttributes);
}
nsCOMPtr<nsIChannel> channel = aDocument->GetChannel();
if (!channel) {
return false;
}
return GetOriginAttributes(channel, aAttributes, eRegularPrincipal);
}
// static
bool StoragePrincipalHelper::GetRegularPrincipalOriginAttributes(
nsILoadGroup* aLoadGroup, OriginAttributes& aAttributes) {
aAttributes = mozilla::OriginAttributes();
if (!aLoadGroup) {
return false;
}
nsCOMPtr<nsIInterfaceRequestor> callbacks;
aLoadGroup->GetNotificationCallbacks(getter_AddRefs(callbacks));
if (!callbacks) {
return false;
}
nsCOMPtr<nsILoadContext> loadContext = do_GetInterface(callbacks);
if (!loadContext) {
return false;
}
loadContext->GetOriginAttributes(aAttributes);
return true;
}
// static
bool StoragePrincipalHelper::GetOriginAttributesForNetworkState(
nsIChannel* aChannel, OriginAttributes& aAttributes) {
return StoragePrincipalHelper::GetOriginAttributes(
aChannel, aAttributes,
StaticPrefs::privacy_partition_network_state() ? ePartitionedPrincipal
: eRegularPrincipal);
}
// static
void StoragePrincipalHelper::GetOriginAttributesForNetworkState(
dom::Document* aDocument, OriginAttributes& aAttributes) {
aAttributes = aDocument->NodePrincipal()->OriginAttributesRef();
if (!StaticPrefs::privacy_partition_network_state()) {
return;
}
aAttributes = aDocument->PartitionedPrincipal()->OriginAttributesRef();
}
// static
void StoragePrincipalHelper::UpdateOriginAttributesForNetworkState(
nsIURI* aFirstPartyURI, OriginAttributes& aAttributes) {
if (!StaticPrefs::privacy_partition_network_state()) {
return;
}
aAttributes.SetPartitionKey(aFirstPartyURI);
}
enum SupportedScheme { HTTP, HTTPS };
static bool GetOriginAttributesWithScheme(nsIChannel* aChannel,
OriginAttributes& aAttributes,
SupportedScheme aScheme) {
const nsString targetScheme = aScheme == HTTP ? u"http"_ns : u"https"_ns;
if (!StoragePrincipalHelper::GetOriginAttributesForNetworkState(
aChannel, aAttributes)) {
return false;
}
if (aAttributes.mPartitionKey.IsEmpty() ||
aAttributes.mPartitionKey[0] != '(') {
return true;
}
nsAString::const_iterator start, end;
aAttributes.mPartitionKey.BeginReading(start);
aAttributes.mPartitionKey.EndReading(end);
MOZ_DIAGNOSTIC_ASSERT(*start == '(');
start++;
nsAString::const_iterator iter(start);
bool ok = FindCharInReadable(',', iter, end);
MOZ_DIAGNOSTIC_ASSERT(ok);
if (!ok) {
return false;
}
nsAutoString scheme;
scheme.Assign(Substring(start, iter));
if (scheme.Equals(targetScheme)) {
return true;
}
nsAutoString key;
key += u"("_ns;
key += targetScheme;
key.Append(Substring(iter, end));
aAttributes.SetPartitionKey(key);
return true;
}
// static
bool StoragePrincipalHelper::GetOriginAttributesForHSTS(
nsIChannel* aChannel, OriginAttributes& aAttributes) {
return GetOriginAttributesWithScheme(aChannel, aAttributes, HTTP);
}
// static
bool StoragePrincipalHelper::GetOriginAttributesForHTTPSRR(
nsIChannel* aChannel, OriginAttributes& aAttributes) {
return GetOriginAttributesWithScheme(aChannel, aAttributes, HTTPS);
}
// static
bool StoragePrincipalHelper::GetOriginAttributes(
const mozilla::ipc::PrincipalInfo& aPrincipalInfo,
OriginAttributes& aAttributes) {
aAttributes = mozilla::OriginAttributes();
using Type = ipc::PrincipalInfo;
switch (aPrincipalInfo.type()) {
case Type::TContentPrincipalInfo:
aAttributes = aPrincipalInfo.get_ContentPrincipalInfo().attrs();
break;
case Type::TNullPrincipalInfo:
aAttributes = aPrincipalInfo.get_NullPrincipalInfo().attrs();
break;
case Type::TExpandedPrincipalInfo:
aAttributes = aPrincipalInfo.get_ExpandedPrincipalInfo().attrs();
break;
case Type::TSystemPrincipalInfo:
break;
default:
return false;
}
return true;
}
bool StoragePrincipalHelper::PartitionKeyHasBaseDomain(
const nsAString& aPartitionKey, const nsACString& aBaseDomain) {
return PartitionKeyHasBaseDomain(aPartitionKey,
NS_ConvertUTF8toUTF16(aBaseDomain));
}
// static
bool StoragePrincipalHelper::PartitionKeyHasBaseDomain(
const nsAString& aPartitionKey, const nsAString& aBaseDomain) {
if (aPartitionKey.IsEmpty() || aBaseDomain.IsEmpty()) {
return false;
}
nsString scheme;
nsString pkBaseDomain;
int32_t port;
bool success = OriginAttributes::ParsePartitionKey(aPartitionKey, scheme,
pkBaseDomain, port);
if (!success) {
return false;
}
return aBaseDomain.Equals(pkBaseDomain);
}
} // namespace mozilla