Bug 1954188: bFPP Part 2 - Implement granular overrides. r=tjr

Differential Revision: https://phabricator.services.mozilla.com/D241852
This commit is contained in:
Fatih Kilic
2025-04-03 17:30:18 +00:00
parent b0162ee293
commit aba2cdec5d
5 changed files with 116 additions and 56 deletions

View File

@@ -570,6 +570,10 @@ nsresult ServiceWorkerPrivate::Initialize() {
nsCOMPtr<nsIURI> firstPartyURI;
bool foreignByAncestorContext = false;
bool isOn3PCBExceptionList = false;
// Firefox doesn't support service workers in PBM,
// but we add this just so that when we do,
// we can handle it correctly.
bool isPBM = principal->GetIsInPrivateBrowsing();
if (!principal->OriginAttributesRef().mPartitionKey.IsEmpty()) {
net::CookieJarSettings::Cast(cookieJarSettings)
->SetPartitionKey(principal->OriginAttributesRef().mPartitionKey);
@@ -592,7 +596,7 @@ nsresult ServiceWorkerPrivate::Initialize() {
if (NS_SUCCEEDED(rv)) {
overriddenFingerprintingSettings =
nsRFPService::GetOverriddenFingerprintingSettingsForURI(
firstPartyURI, uri);
firstPartyURI, uri, isPBM);
if (overriddenFingerprintingSettings.isSome()) {
overriddenFingerprintingSettingsArg.emplace(
overriddenFingerprintingSettings.ref());
@@ -625,9 +629,9 @@ nsresult ServiceWorkerPrivate::Initialize() {
overriddenFingerprintingSettings =
isThirdParty
? nsRFPService::GetOverriddenFingerprintingSettingsForURI(
firstPartyURI, uri)
firstPartyURI, uri, isPBM)
: nsRFPService::GetOverriddenFingerprintingSettingsForURI(
uri, nullptr);
uri, nullptr, isPBM);
RefPtr<net::CookieService> csSingleton =
net::CookieService::GetSingleton();
@@ -650,7 +654,8 @@ nsresult ServiceWorkerPrivate::Initialize() {
// the service worker as the first-party domain to get the fingerprinting
// protection overrides.
overriddenFingerprintingSettings =
nsRFPService::GetOverriddenFingerprintingSettingsForURI(uri, nullptr);
nsRFPService::GetOverriddenFingerprintingSettingsForURI(uri, nullptr,
isPBM);
if (overriddenFingerprintingSettings.isSome()) {
overriddenFingerprintingSettingsArg.emplace(
@@ -658,7 +663,6 @@ nsresult ServiceWorkerPrivate::Initialize() {
}
}
bool isPBM = principal->GetIsInPrivateBrowsing();
if (ContentBlockingAllowList::Check(principal, isPBM)) {
net::CookieJarSettings::Cast(cookieJarSettings)
->SetIsOnContentBlockingAllowList(true);

View File

@@ -49,6 +49,8 @@ const SCHEMA = `{
const COLLECTION_NAME = "fingerprinting-protection-overrides";
const PREF_GRANULAR_OVERRIDES =
"privacy.fingerprintingProtection.granularOverrides";
const PREF_GRANULAR_OVERRIDES_BASELINE =
"privacy.baselineFingerprintingProtection.granularOverrides";
const PREF_REMOTE_OVERRIDES_ENABLED =
"privacy.fingerprintingProtection.remoteOverrides.enabled";
@@ -58,6 +60,12 @@ XPCOMUtils.defineLazyPreferenceGetter(
PREF_GRANULAR_OVERRIDES
);
XPCOMUtils.defineLazyPreferenceGetter(
lazy,
"baselineGranularOverridesPref",
PREF_GRANULAR_OVERRIDES_BASELINE
);
XPCOMUtils.defineLazyPreferenceGetter(
lazy,
"remoteOverridesEnabled",
@@ -71,10 +79,11 @@ export class FingerprintingOverride {
classID = Components.ID("{07f45442-1806-44be-9230-12eb79de9bac}");
QueryInterface = ChromeUtils.generateQI(["nsIFingerprintingOverride"]);
constructor(firstPartyDomain, thirdPartyDomain, overrides) {
constructor(firstPartyDomain, thirdPartyDomain, overrides, isBaseline) {
this.firstPartyDomain = firstPartyDomain;
this.thirdPartyDomain = thirdPartyDomain;
this.overrides = overrides;
this.isBaseline = isBaseline;
}
}
@@ -125,6 +134,7 @@ export class FingerprintingWebCompatService {
// Register listener to import overrides when the overrides pref changes.
Services.prefs.addObserver(PREF_GRANULAR_OVERRIDES, this);
Services.prefs.addObserver(PREF_GRANULAR_OVERRIDES_BASELINE, this);
// Register the sync event for the remote settings updates.
this.#rs.on("sync", event => {
@@ -155,37 +165,42 @@ export class FingerprintingWebCompatService {
// Clear overrides before we update.
this.#granularOverrides.clear();
let overrides;
try {
overrides = JSON.parse(lazy.granularOverridesPref || "[]");
} catch (error) {
lazy.logConsole.error(
`Failed to parse granular override JSON string: Not a valid JSON.`,
error
);
return;
}
// Ensure we have an array we can iterate over and not an object.
if (!Array.isArray(overrides)) {
lazy.logConsole.error(
"Failed to parse granular overrides JSON String: Not an array."
);
return;
}
for (let override of overrides) {
// Validate the override.
let { valid, errors } = this.#validator.validate(override);
if (!valid) {
lazy.logConsole.debug("Override validation error", override, errors);
continue;
for (const [pref, isBaseline] of [
[lazy.baselineGranularOverridesPref, true],
[lazy.granularOverridesPref, false],
]) {
let overrides;
try {
overrides = JSON.parse(pref || "[]");
} catch (error) {
lazy.logConsole.error(
`Failed to parse granular override JSON string: Not a valid JSON.`,
error
);
return;
}
this.#granularOverrides.add(
this.#createFingerprintingOverrideFrom(override)
);
// Ensure we have an array we can iterate over and not an object.
if (!Array.isArray(overrides)) {
lazy.logConsole.error(
"Failed to parse granular overrides JSON String: Not an array."
);
return;
}
for (let override of overrides) {
// Validate the override.
let { valid, errors } = this.#validator.validate(override);
if (!valid) {
lazy.logConsole.debug("Override validation error", override, errors);
continue;
}
this.#granularOverrides.add(
this.#createFingerprintingOverrideFrom(override, isBaseline)
);
}
}
}
@@ -217,11 +232,18 @@ export class FingerprintingWebCompatService {
}
}
#createFingerprintingOverrideFrom(entry) {
#createFingerprintingOverrideFrom(entry, isBaselineOverride = null) {
// If the isBaselineOverride is not provided, we will use the value from the
// entry. If the entry doesn't have the isBaseline field, we will default to
// false.
// We default to false because we didn't have the baseline mode in the past,
// and the existing entries don't have the isBaseline field.
const isBaseline = isBaselineOverride ?? entry.isBaseline ?? false;
return new FingerprintingOverride(
entry.firstPartyDomain,
entry.thirdPartyDomain,
entry.overrides
entry.overrides,
isBaseline
);
}
@@ -240,7 +262,10 @@ export class FingerprintingWebCompatService {
}
observe(subject, topic, prefName) {
if (prefName != PREF_GRANULAR_OVERRIDES) {
if (
prefName != PREF_GRANULAR_OVERRIDES &&
prefName != PREF_GRANULAR_OVERRIDES_BASELINE
) {
return;
}
@@ -252,5 +277,6 @@ export class FingerprintingWebCompatService {
lazy.logConsole.debug("shutdown");
Services.prefs.removeObserver(PREF_GRANULAR_OVERRIDES, this);
Services.prefs.removeObserver(PREF_GRANULAR_OVERRIDES_BASELINE, this);
}
}

View File

@@ -21,6 +21,11 @@ interface nsIFingerprintingOverride : nsISupports
* fingerprinting override pref "privacy.fingerprintingProtection.overrides".
*/
readonly attribute ACString overrides;
/**
* Whether the override is baseline or FPP.
*/
readonly attribute boolean isBaseline;
};
/**

View File

@@ -2072,6 +2072,10 @@ nsresult nsRFPService::CreateOverrideDomainKey(
nsresult rv = aOverride->GetFirstPartyDomain(firstPartyDomain);
NS_ENSURE_SUCCESS(rv, rv);
bool isBaseline = false;
rv = aOverride->GetIsBaseline(&isBaseline);
NS_ENSURE_SUCCESS(rv, rv);
// The first party domain shouldn't be empty. And it shouldn't contain a comma
// because we use a comma as a delimiter.
if (firstPartyDomain.IsEmpty() ||
@@ -2102,6 +2106,9 @@ nsresult nsRFPService::CreateOverrideDomainKey(
aDomainKey.Append(thirdPartyDomain);
}
aDomainKey.Append(FP_OVERRIDES_DOMAIN_KEY_DELIMITER);
aDomainKey.Append(isBaseline ? "1" : "0");
return NS_OK;
}
@@ -2162,8 +2169,9 @@ nsRFPService::SetFingerprintingOverrides(
const nsTArray<RefPtr<nsIFingerprintingOverride>>& aOverrides) {
MOZ_ASSERT(XRE_IsParentProcess());
// Clear all overrides before importing.
mFingerprintingOverrides.Clear();
CleanAllOverrides();
StaticMutexAutoLock lock(sEnabledFingerprintingProtectionsMutex);
for (const auto& fpOverride : aOverrides) {
nsAutoCString domainKey;
@@ -2177,14 +2185,18 @@ nsRFPService::SetFingerprintingOverrides(
rv = fpOverride->GetOverrides(overridesText);
NS_ENSURE_SUCCESS(rv, rv);
StaticMutexAutoLock lock(sEnabledFingerprintingProtectionsMutex);
bool isBaseline = false;
rv = fpOverride->GetIsBaseline(&isBaseline);
NS_ENSURE_SUCCESS(rv, rv);
RFPTargetSet baseOverrides = isBaseline
? sEnabledFingerprintingProtectionsBase
: sEnabledFingerprintingProtections;
RFPTargetSet targets = nsRFPService::CreateOverridesFromText(
NS_ConvertUTF8toUTF16(overridesText),
mFingerprintingOverrides.Contains(domainKey)
? mFingerprintingOverrides.Get(domainKey)
// TODO(fkilic): We will handle granular overrides in the following
// patches.
: sEnabledFingerprintingProtections);
: baseOverrides);
// The newly added one will replace the existing one for the given domain
// key.
@@ -2273,9 +2285,11 @@ Maybe<RFPTargetSet> nsRFPService::GetOverriddenFingerprintingSettingsForChannel(
return Nothing();
}
bool isPrivate = loadInfo->GetOriginAttributes().IsPrivateBrowsing();
// The channel is for the first-party load.
if (!AntiTrackingUtils::IsThirdPartyChannel(aChannel)) {
return GetOverriddenFingerprintingSettingsForURI(uri, nullptr);
return GetOverriddenFingerprintingSettingsForURI(uri, nullptr, isPrivate);
}
// The channel is for the third-party load. We get the first-party URI from
@@ -2328,7 +2342,7 @@ Maybe<RFPTargetSet> nsRFPService::GetOverriddenFingerprintingSettingsForChannel(
rv = NS_NewURI(getter_AddRefs(topURI), scheme + u"://"_ns + domain);
MOZ_ASSERT(NS_SUCCEEDED(rv));
return GetOverriddenFingerprintingSettingsForURI(topURI, uri);
return GetOverriddenFingerprintingSettingsForURI(topURI, uri, isPrivate);
}
nsCOMPtr<nsIPrincipal> topPrincipal = topWGP->DocumentPrincipal();
@@ -2380,12 +2394,12 @@ Maybe<RFPTargetSet> nsRFPService::GetOverriddenFingerprintingSettingsForChannel(
attrsForeignByAncestor.mPartitionKey.Equals(partitionKey));
#endif
return GetOverriddenFingerprintingSettingsForURI(topURI, uri);
return GetOverriddenFingerprintingSettingsForURI(topURI, uri, isPrivate);
}
/* static */
Maybe<RFPTargetSet> nsRFPService::GetOverriddenFingerprintingSettingsForURI(
nsIURI* aFirstPartyURI, nsIURI* aThirdPartyURI) {
nsIURI* aFirstPartyURI, nsIURI* aThirdPartyURI, bool aIsPrivate) {
MOZ_ASSERT(aFirstPartyURI);
MOZ_ASSERT(XRE_IsParentProcess());
@@ -2399,9 +2413,17 @@ Maybe<RFPTargetSet> nsRFPService::GetOverriddenFingerprintingSettingsForURI(
// will take over {first-party domain, *} because the latter one has a smaller
// scope.
bool isBaseline = !IsFPPEnabled(aIsPrivate);
auto addIsBaseline = [](nsAutoCString& aKey, bool aIsBaseline) {
aKey.Append(FP_OVERRIDES_DOMAIN_KEY_DELIMITER);
aKey.Append(aIsBaseline ? "1" : "0");
};
// First, we get the overrides that applies to every context.
Maybe<RFPTargetSet> result =
service->mFingerprintingOverrides.MaybeGet("*"_ns);
nsAutoCString key;
key.Assign("*"_ns);
addIsBaseline(key, isBaseline);
Maybe<RFPTargetSet> result = service->mFingerprintingOverrides.MaybeGet(key);
nsCOMPtr<nsIEffectiveTLDService> eTLDService =
mozilla::components::EffectiveTLD::Service();
@@ -2424,11 +2446,10 @@ Maybe<RFPTargetSet> nsRFPService::GetOverriddenFingerprintingSettingsForURI(
// first-party domain.
if (!aThirdPartyURI) {
// Test the {first-party domain, *} scope.
nsAutoCString key;
key.Assign(firstPartyDomain);
key.Append(FP_OVERRIDES_DOMAIN_KEY_DELIMITER);
key.Append("*");
key.Append("*"_ns);
addIsBaseline(key, isBaseline);
Maybe<RFPTargetSet> fpOverrides =
service->mFingerprintingOverrides.MaybeGet(key);
if (fpOverrides) {
@@ -2436,7 +2457,9 @@ Maybe<RFPTargetSet> nsRFPService::GetOverriddenFingerprintingSettingsForURI(
}
// Test the {first-party domain} scope.
fpOverrides = service->mFingerprintingOverrides.MaybeGet(firstPartyDomain);
key.Assign(firstPartyDomain);
addIsBaseline(key, isBaseline);
fpOverrides = service->mFingerprintingOverrides.MaybeGet(key);
if (fpOverrides) {
result = fpOverrides;
}
@@ -2460,10 +2483,10 @@ Maybe<RFPTargetSet> nsRFPService::GetOverriddenFingerprintingSettingsForURI(
}
// Test {first-party domain, *} scope.
nsAutoCString key;
key.Assign(firstPartyDomain);
key.Append(FP_OVERRIDES_DOMAIN_KEY_DELIMITER);
key.Append("*");
key.Append("*"_ns);
addIsBaseline(key, isBaseline);
Maybe<RFPTargetSet> fpOverrides =
service->mFingerprintingOverrides.MaybeGet(key);
if (fpOverrides) {
@@ -2474,6 +2497,7 @@ Maybe<RFPTargetSet> nsRFPService::GetOverriddenFingerprintingSettingsForURI(
key.Assign("*");
key.Append(FP_OVERRIDES_DOMAIN_KEY_DELIMITER);
key.Append(thirdPartyDomain);
addIsBaseline(key, isBaseline);
fpOverrides = service->mFingerprintingOverrides.MaybeGet(key);
if (fpOverrides) {
result = fpOverrides;
@@ -2483,6 +2507,7 @@ Maybe<RFPTargetSet> nsRFPService::GetOverriddenFingerprintingSettingsForURI(
key.Assign(firstPartyDomain);
key.Append(FP_OVERRIDES_DOMAIN_KEY_DELIMITER);
key.Append(thirdPartyDomain);
addIsBaseline(key, isBaseline);
fpOverrides = service->mFingerprintingOverrides.MaybeGet(key);
if (fpOverrides) {
result = fpOverrides;

View File

@@ -355,7 +355,7 @@ class nsRFPService final : public nsIObserver, public nsIRFPService {
// and third-party URI. Otherwise, it will return Nothing() to indicate using
// the default RFPTargets.
static Maybe<RFPTargetSet> GetOverriddenFingerprintingSettingsForURI(
nsIURI* aFirstPartyURI, nsIURI* aThirdPartyURI);
nsIURI* aFirstPartyURI, nsIURI* aThirdPartyURI, bool aIsPrivate);
// --------------------------------------------------------------------------