Skip to content

Commit

Permalink
Bug 1876579 - Separate BounceTrackingProtection global state objects …
Browse files Browse the repository at this point in the history
…by OriginAttributes. r=bvandersloot,anti-tracking-reviewers

Differential Revision: https://phabricator.services.mozilla.com/D200818
  • Loading branch information
Trikolon committed Feb 14, 2024
1 parent e823842 commit f70fe0a
Show file tree
Hide file tree
Showing 7 changed files with 215 additions and 71 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include "BounceTrackingRecord.h"

#include "ErrorList.h"
#include "mozilla/AlreadyAddRefed.h"
#include "mozilla/ClearOnShutdown.h"
#include "mozilla/Logging.h"
#include "mozilla/Services.h"
Expand Down Expand Up @@ -101,6 +102,23 @@ BounceTrackingProtection::BounceTrackingProtection() {
"Failed to schedule timer for RunPurgeBounceTrackers.");
}

BounceTrackingStateGlobal* BounceTrackingProtection::GetOrCreateStateGlobal(
nsIPrincipal* aPrincipal) {
MOZ_ASSERT(aPrincipal);
return GetOrCreateStateGlobal(aPrincipal->OriginAttributesRef());
}

BounceTrackingStateGlobal* BounceTrackingProtection::GetOrCreateStateGlobal(
BounceTrackingState* aBounceTrackingState) {
MOZ_ASSERT(aBounceTrackingState);
return GetOrCreateStateGlobal(aBounceTrackingState->OriginAttributesRef());
}

BounceTrackingStateGlobal* BounceTrackingProtection::GetOrCreateStateGlobal(
const OriginAttributes& aOriginAttributes) {
return mStateGlobal.GetOrInsertNew(aOriginAttributes);
}

nsresult BounceTrackingProtection::RecordStatefulBounces(
BounceTrackingState* aBounceTrackingState) {
NS_ENSURE_ARG_POINTER(aBounceTrackingState);
Expand All @@ -114,6 +132,11 @@ nsresult BounceTrackingProtection::RecordStatefulBounces(
aBounceTrackingState->GetBounceTrackingRecord();
NS_ENSURE_TRUE(record, NS_ERROR_FAILURE);

// Get the bounce tracker map and the user activation map.
BounceTrackingStateGlobal* globalState =
GetOrCreateStateGlobal(aBounceTrackingState);
MOZ_ASSERT(globalState);

// For each host in navigable’s bounce tracking record's bounce set:
for (const nsACString& host : record->GetBounceHosts()) {
// If host equals navigable’s bounce tracking record's initial host,
Expand All @@ -133,15 +156,15 @@ nsresult BounceTrackingProtection::RecordStatefulBounces(
}

// If user activation map contains host, continue.
if (mUserActivation.Contains(host)) {
if (globalState->mUserActivation.Contains(host)) {
MOZ_LOG(gBounceTrackingProtectionLog, LogLevel::Debug,
("%s: Skip host with recent user activation: %s", __FUNCTION__,
PromiseFlatCString(host).get()));
continue;
}

// If stateful bounce tracking map contains host, continue.
if (mBounceTrackers.Contains(host)) {
if (globalState->mBounceTrackers.Contains(host)) {
MOZ_LOG(gBounceTrackingProtectionLog, LogLevel::Debug,
("%s: Skip already existing host: %s", __FUNCTION__,
PromiseFlatCString(host).get()));
Expand All @@ -162,8 +185,8 @@ nsresult BounceTrackingProtection::RecordStatefulBounces(
// Set stateful bounce tracking map[host] to topDocument’s relevant settings
// object's current wall time.
PRTime now = PR_Now();
MOZ_ASSERT(!mBounceTrackers.Contains(host));
mBounceTrackers.InsertOrUpdate(host, now);
MOZ_ASSERT(!globalState->mBounceTrackers.Contains(host));
globalState->mBounceTrackers.InsertOrUpdate(host, now);

MOZ_LOG(gBounceTrackingProtectionLog, LogLevel::Info,
("%s: Added candidate to mBounceTrackers: %s, Time: %" PRIu64,
Expand Down Expand Up @@ -212,33 +235,40 @@ nsresult BounceTrackingProtection::RecordUserActivation(
MOZ_LOG(gBounceTrackingProtectionLog, LogLevel::Info,
("%s: siteHost: %s", __FUNCTION__, siteHost.get()));

bool hasRemoved = mBounceTrackers.Remove(siteHost);
BounceTrackingStateGlobal* globalState = GetOrCreateStateGlobal(aPrincipal);
MOZ_ASSERT(globalState);

bool hasRemoved = globalState->mBounceTrackers.Remove(siteHost);
if (hasRemoved) {
MOZ_LOG(gBounceTrackingProtectionLog, LogLevel::Debug,
("%s: Removed bounce tracking candidate due to user activation: %s",
__FUNCTION__, siteHost.get()));
}
MOZ_ASSERT(!mBounceTrackers.Contains(siteHost));
MOZ_ASSERT(!globalState->mBounceTrackers.Contains(siteHost));

mUserActivation.InsertOrUpdate(siteHost, PR_Now());
globalState->mUserActivation.InsertOrUpdate(siteHost, PR_Now());

return NS_OK;
}

NS_IMETHODIMP
BounceTrackingProtection::GetBounceTrackerCandidateHosts(
nsTArray<nsCString>& aCandidates) {
for (const nsACString& host : mBounceTrackers.Keys()) {
aCandidates.AppendElement(host);
for (BounceTrackingStateGlobal* globalState : mStateGlobal.Values()) {
for (const nsACString& host : globalState->mBounceTrackers.Keys()) {
aCandidates.AppendElement(host);
}
}

return NS_OK;
}

NS_IMETHODIMP
BounceTrackingProtection::GetUserActivationHosts(nsTArray<nsCString>& aHosts) {
for (const nsACString& host : mUserActivation.Keys()) {
aHosts.AppendElement(host);
for (BounceTrackingStateGlobal* globalState : mStateGlobal.Values()) {
for (const nsACString& host : globalState->mUserActivation.Keys()) {
aHosts.AppendElement(host);
}
}

return NS_OK;
Expand All @@ -247,9 +277,7 @@ BounceTrackingProtection::GetUserActivationHosts(nsTArray<nsCString>& aHosts) {
NS_IMETHODIMP
BounceTrackingProtection::Reset() {
BounceTrackingState::ResetAll();

mBounceTrackers.Clear();
mUserActivation.Clear();
mStateGlobal.Clear();

return NS_OK;
}
Expand Down Expand Up @@ -288,33 +316,94 @@ BounceTrackingProtection::TestRunPurgeBounceTrackers(
NS_IMETHODIMP
BounceTrackingProtection::TestAddBounceTrackerCandidate(
const nsACString& aHost, const PRTime aBounceTime) {
const OriginAttributes oa;
BounceTrackingStateGlobal* stateGlobal = GetOrCreateStateGlobal(oa);
MOZ_ASSERT(stateGlobal);

// Can not have a host in both maps.
mUserActivation.Remove(aHost);
mBounceTrackers.InsertOrUpdate(aHost, aBounceTime);
stateGlobal->mUserActivation.Remove(aHost);
stateGlobal->mBounceTrackers.InsertOrUpdate(aHost, aBounceTime);
return NS_OK;
}

NS_IMETHODIMP
BounceTrackingProtection::TestAddUserActivation(const nsACString& aHost,
const PRTime aActivationTime) {
const OriginAttributes oa;
BounceTrackingStateGlobal* stateGlobal = GetOrCreateStateGlobal(oa);
MOZ_ASSERT(stateGlobal);

// Can not have a host in both maps.
mBounceTrackers.Remove(aHost);
mUserActivation.InsertOrUpdate(aHost, aActivationTime);
stateGlobal->mBounceTrackers.Remove(aHost);
stateGlobal->mUserActivation.InsertOrUpdate(aHost, aActivationTime);
return NS_OK;
}

RefPtr<BounceTrackingProtection::PurgeBounceTrackersMozPromise>
BounceTrackingProtection::PurgeBounceTrackers() {
// Run the purging algorithm for all global state objects.
for (auto& entry : mStateGlobal) {
const OriginAttributes& originAttributes = entry.GetKey();
BounceTrackingStateGlobal* stateGlobal = entry.GetData();
MOZ_ASSERT(stateGlobal);

if (MOZ_LOG_TEST(gBounceTrackingProtectionLog, LogLevel::Debug)) {
nsAutoCString oaSuffix;
originAttributes.CreateSuffix(oaSuffix);
MOZ_LOG(gBounceTrackingProtectionLog, LogLevel::Debug,
("%s: Running purge algorithm for OA: '%s'", __FUNCTION__,
oaSuffix.get()));
}

PurgeBounceTrackersForStateGlobal(stateGlobal, originAttributes);
}

// Wait for all data clearing operations to complete. mClearPromises contains
// one promise per host / clear task.
return ClearDataMozPromise::AllSettled(GetCurrentSerialEventTarget(),
mClearPromises)
->Then(
GetCurrentSerialEventTarget(), __func__,
[&](ClearDataMozPromise::AllSettledPromiseType::ResolveOrRejectValue&&
aResults) {
MOZ_ASSERT(aResults.IsResolve(), "AllSettled never rejects");

MOZ_LOG(gBounceTrackingProtectionLog, LogLevel::Info,
("%s: Done. Cleared %zu hosts.", __FUNCTION__,
aResults.ResolveValue().Length()));

nsTArray<nsCString> purgedSiteHosts;
// If any clear call failed reject.
for (auto& result : aResults.ResolveValue()) {
if (result.IsReject()) {
mClearPromises.Clear();
return PurgeBounceTrackersMozPromise::CreateAndReject(
NS_ERROR_FAILURE, __func__);
}
purgedSiteHosts.AppendElement(result.ResolveValue());
}

// No clearing errors, resolve.
mClearPromises.Clear();
return PurgeBounceTrackersMozPromise::CreateAndResolve(
std::move(purgedSiteHosts), __func__);
});
}

nsresult BounceTrackingProtection::PurgeBounceTrackersForStateGlobal(
BounceTrackingStateGlobal* aStateGlobal,
const OriginAttributes& aOriginAttributes) {
MOZ_ASSERT(aStateGlobal);
MOZ_LOG(gBounceTrackingProtectionLog, LogLevel::Debug,
("%s: #mUserActivation: %d, #mBounceTrackers: %d", __FUNCTION__,
mUserActivation.Count(), mBounceTrackers.Count()));
aStateGlobal->mUserActivation.Count(),
aStateGlobal->mBounceTrackers.Count()));

// Purge already in progress.
if (!mClearPromises.IsEmpty()) {
MOZ_LOG(gBounceTrackingProtectionLog, LogLevel::Debug,
("%s: Skip: Purge already in progress.", __FUNCTION__));
return PurgeBounceTrackersMozPromise::CreateAndReject(
NS_ERROR_NOT_AVAILABLE, __func__);
return NS_ERROR_NOT_AVAILABLE;
}

const PRTime now = PR_Now();
Expand All @@ -329,13 +418,13 @@ BounceTrackingProtection::PurgeBounceTrackers() {

// 1. Remove hosts from the user activation map whose user activation flag has
// expired.
for (auto hostIter = mUserActivation.Iter(); !hostIter.Done();
for (auto hostIter = aStateGlobal->mUserActivation.Iter(); !hostIter.Done();
hostIter.Next()) {
const nsACString& host = hostIter.Key();

// Ensure that mBounceTrackers and mUserActivation maps are disjoint. A host
// can never be in both maps.
MOZ_ASSERT(!mBounceTrackers.Contains(host));
MOZ_ASSERT(!aStateGlobal->mBounceTrackers.Contains(host));

// If activationTime + bounce tracking activation lifetime is before now,
// then remove host from user activation map.
Expand All @@ -352,14 +441,12 @@ BounceTrackingProtection::PurgeBounceTrackers() {
nsresult rv = NS_OK;
nsCOMPtr<nsIClearDataService> clearDataService =
do_GetService("@mozilla.org/clear-data-service;1", &rv);
if (NS_FAILED(rv)) {
return PurgeBounceTrackersMozPromise::CreateAndReject(rv, __func__);
}
NS_ENSURE_SUCCESS(rv, rv);

mClearPromises.Clear();
nsTArray<nsCString> purgedSiteHosts;

for (auto hostIter = mBounceTrackers.Iter(); !hostIter.Done();
for (auto hostIter = aStateGlobal->mBounceTrackers.Iter(); !hostIter.Done();
hostIter.Next()) {
const nsACString& host = hostIter.Key();
const PRTime& bounceTime = hostIter.Data();
Expand Down Expand Up @@ -405,9 +492,7 @@ BounceTrackingProtection::PurgeBounceTrackers() {
("%s: Purge state for host: %s", __FUNCTION__,
PromiseFlatCString(host).get()));

// TODO: Bug 1842067: DeleteDataFromBaseDomain clears the whole cookie jar,
// including state partitioned under `host` as the top level. Consider only
// clearing unpartitioned state.
// TODO: Bug 1842067: Clear by site + OA.
rv = clearDataService->DeleteDataFromBaseDomain(host, false,
TRACKER_PURGE_FLAGS, cb);
if (NS_WARN_IF(NS_FAILED(rv))) {
Expand All @@ -422,36 +507,7 @@ BounceTrackingProtection::PurgeBounceTrackers() {
hostIter.Remove();
}

// Wait for all data clearing operations to complete. mClearPromises contains
// one promise per host / clear task.
return ClearDataMozPromise::AllSettled(GetCurrentSerialEventTarget(),
mClearPromises)
->Then(
GetCurrentSerialEventTarget(), __func__,
[&](ClearDataMozPromise::AllSettledPromiseType::ResolveOrRejectValue&&
aResults) {
MOZ_ASSERT(aResults.IsResolve(), "AllSettled never rejects");

MOZ_LOG(gBounceTrackingProtectionLog, LogLevel::Info,
("%s: Done. Cleared %zu hosts.", __FUNCTION__,
aResults.ResolveValue().Length()));

nsTArray<nsCString> purgedSiteHosts;
// If any clear call failed reject.
for (auto& result : aResults.ResolveValue()) {
if (result.IsReject()) {
mClearPromises.Clear();
return PurgeBounceTrackersMozPromise::CreateAndReject(
NS_ERROR_FAILURE, __func__);
}
purgedSiteHosts.AppendElement(result.ResolveValue());
}

// No clearing errors, resolve.
mClearPromises.Clear();
return PurgeBounceTrackersMozPromise::CreateAndResolve(
std::move(purgedSiteHosts), __func__);
});
return NS_OK;
}

// ClearDataCallback
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,13 @@
#include "mozilla/Logging.h"
#include "mozilla/MozPromise.h"
#include "nsIBounceTrackingProtection.h"
#include "BounceTrackingStateGlobal.h"
#include "nsIClearDataService.h"
#include "nsTHashMap.h"

#include "mozilla/OriginAttributes.h"
#include "mozilla/OriginAttributesHashKey.h"

class nsIPrincipal;
class nsITimer;

Expand Down Expand Up @@ -40,16 +44,18 @@ class BounceTrackingProtection final : public nsIBounceTrackingProtection {
BounceTrackingProtection();
~BounceTrackingProtection() = default;

// Map of site hosts to moments. The moments represent the most recent wall
// clock time at which the user activated a top-level document on the
// associated site host.
nsTHashMap<nsCStringHashKey, PRTime> mUserActivation{};
// Map of origin attributes to global state object. This enables us to track
// bounce tracking state per OA, e.g. to separate private browsing from normal
// browsing.
nsTHashMap<OriginAttributesHashKey, RefPtr<BounceTrackingStateGlobal>>
mStateGlobal{};

// Map of site hosts to moments. The moments represent the first wall clock
// time since the last execution of the bounce tracking timer at which a page
// on the given site host performed an action that could indicate stateful
// bounce tracking took place.
nsTHashMap<nsCStringHashKey, PRTime> mBounceTrackers{};
// Getters for mStateGlobal.
BounceTrackingStateGlobal* GetOrCreateStateGlobal(
const OriginAttributes& aOriginAttributes);
BounceTrackingStateGlobal* GetOrCreateStateGlobal(nsIPrincipal* aPrincipal);
BounceTrackingStateGlobal* GetOrCreateStateGlobal(
BounceTrackingState* aBounceTrackingState);

// Timer which periodically runs PurgeBounceTrackers.
nsCOMPtr<nsITimer> mBounceTrackingPurgeTimer;
Expand All @@ -59,6 +65,10 @@ class BounceTrackingProtection final : public nsIBounceTrackingProtection {
MozPromise<nsTArray<nsCString>, nsresult, true>;
RefPtr<PurgeBounceTrackersMozPromise> PurgeBounceTrackers();

nsresult PurgeBounceTrackersForStateGlobal(
BounceTrackingStateGlobal* aStateGlobal,
const OriginAttributes& aOriginAttributes);

// Pending clear operations are stored as ClearDataMozPromise, one per host.
using ClearDataMozPromise = MozPromise<nsCString, uint32_t, true>;
nsTArray<RefPtr<ClearDataMozPromise>> mClearPromises;
Expand Down
Loading

0 comments on commit f70fe0a

Please sign in to comment.