Bug 1863280 - Part 2: Adding STATE_BLOCKED_SUSPICIOUS_FINGERPRINTING to ContentBlockingLog. r=pbz,anti-tracking-reviewers

To record whether we have detected suspicious fingerprinting activities
to the ContentBlockingLog, we introduce a new ContentBlockingLog event
called STATE_BLOCKED_SUSPICIOUS_FINGERPRINTING.

Whenever a fingerprinting content blocking event gets recorded under an
origin, we check if this origin is conducting suspicious fingerprinting
activites. Then, we store this fact under the origin entry and notify
the STATE_BLOCKED_SUSPICIOUS_FINGERPRINTING event to indicate that there
is a suspicous fingerprinitng activity happening.

I need to move ContentBlockingLog::RecordLogInternal() from header to
cpp file to avoid include file dependency issue.

Differential Revision: https://phabricator.services.mozilla.com/D193454
This commit is contained in:
Tim Huang 2023-11-16 00:22:46 +00:00
Родитель 1e5f4a5b17
Коммит ef2c6c9ebc
3 изменённых файлов: 180 добавлений и 92 удалений

Просмотреть файл

@ -12,6 +12,7 @@
#include "nsIWebProgressListener.h"
#include "nsNetCID.h"
#include "nsNetUtil.h"
#include "nsRFPService.h"
#include "nsServiceManagerUtils.h"
#include "nsTArray.h"
#include "mozilla/BasePrincipal.h"
@ -59,6 +60,7 @@ Maybe<uint32_t> ContentBlockingLog::RecordLogParent(
bool blockedValue = aBlocked;
bool unblocked = false;
OriginEntry* entry;
switch (aType) {
case nsIWebProgressListener::STATE_COOKIES_LOADED:
@ -97,26 +99,54 @@ Maybe<uint32_t> ContentBlockingLog::RecordLogParent(
case nsIWebProgressListener::STATE_BLOCKED_EMAILTRACKING_CONTENT:
case nsIWebProgressListener::STATE_LOADED_EMAILTRACKING_LEVEL_1_CONTENT:
case nsIWebProgressListener::STATE_LOADED_EMAILTRACKING_LEVEL_2_CONTENT:
RecordLogInternal(aOrigin, aType, blockedValue);
Unused << RecordLogInternal(aOrigin, aType, blockedValue);
break;
case nsIWebProgressListener::STATE_COOKIES_BLOCKED_TRACKER:
case nsIWebProgressListener::STATE_COOKIES_BLOCKED_SOCIALTRACKER:
RecordLogInternal(aOrigin, aType, blockedValue, aReason,
aTrackingFullHashes);
Unused << RecordLogInternal(aOrigin, aType, blockedValue, aReason,
aTrackingFullHashes);
break;
case nsIWebProgressListener::STATE_REPLACED_FINGERPRINTING_CONTENT:
case nsIWebProgressListener::STATE_ALLOWED_FINGERPRINTING_CONTENT:
case nsIWebProgressListener::STATE_REPLACED_TRACKING_CONTENT:
case nsIWebProgressListener::STATE_ALLOWED_TRACKING_CONTENT:
Unused << RecordLogInternal(aOrigin, aType, blockedValue, aReason,
aTrackingFullHashes);
break;
case nsIWebProgressListener::STATE_ALLOWED_FONT_FINGERPRINTING:
RecordLogInternal(aOrigin, aType, blockedValue);
MOZ_ASSERT(!aBlocked,
"We don't expected to see blocked "
"STATE_ALLOWED_FONT_FINGERPRINTING");
entry = RecordLogInternal(aOrigin, aType, blockedValue);
// Replace the flag using the suspicious fingerprinting event so that we
// can report the event if we detect suspicious fingerprinting.
aType = nsIWebProgressListener::STATE_BLOCKED_SUSPICIOUS_FINGERPRINTING;
// Report blocking if we detect suspicious fingerprinting activity.
if (entry && entry->mData->mHasSuspiciousFingerprintingActivity) {
blockedValue = true;
}
break;
case nsIWebProgressListener::STATE_ALLOWED_CANVAS_FINGERPRINTING:
RecordLogInternal(aOrigin, aType, blockedValue, Nothing(), {},
aCanvasFingerprinter, aCanvasFingerprinterKnownText);
MOZ_ASSERT(!aBlocked,
"We don't expected to see blocked "
"STATE_ALLOWED_CANVAS_FINGERPRINTING");
entry = RecordLogInternal(aOrigin, aType, blockedValue, Nothing(), {},
aCanvasFingerprinter,
aCanvasFingerprinterKnownText);
// Replace the flag using the suspicious fingerprinting event so that we
// can report the event if we detect suspicious fingerprinting.
aType = nsIWebProgressListener::STATE_BLOCKED_SUSPICIOUS_FINGERPRINTING;
// Report blocking if we detect suspicious fingerprinting activity.
if (entry && entry->mData->mHasSuspiciousFingerprintingActivity) {
blockedValue = true;
}
break;
default:
@ -365,4 +395,118 @@ void ContentBlockingLog::ReportEmailTrackingLog(
level1Count + level2Count);
}
ContentBlockingLog::OriginEntry* ContentBlockingLog::RecordLogInternal(
const nsACString& aOrigin, uint32_t aType, bool aBlocked,
const Maybe<ContentBlockingNotifier::StorageAccessPermissionGrantedReason>&
aReason,
const nsTArray<nsCString>& aTrackingFullHashes,
const Maybe<ContentBlockingNotifier::CanvasFingerprinter>&
aCanvasFingerprinter,
const Maybe<bool> aCanvasFingerprinterKnownText) {
DebugOnly<bool> isCookiesBlockedTracker =
aType == nsIWebProgressListener::STATE_COOKIES_BLOCKED_TRACKER ||
aType == nsIWebProgressListener::STATE_COOKIES_BLOCKED_SOCIALTRACKER;
MOZ_ASSERT_IF(aBlocked, aReason.isNothing());
MOZ_ASSERT_IF(!isCookiesBlockedTracker, aReason.isNothing());
MOZ_ASSERT_IF(isCookiesBlockedTracker && !aBlocked, aReason.isSome());
if (aOrigin.IsVoid()) {
return nullptr;
}
auto index = mLog.IndexOf(aOrigin, 0, Comparator());
if (index != OriginDataTable::NoIndex) {
OriginEntry& entry = mLog[index];
if (!entry.mData) {
return nullptr;
}
if (RecordLogEntryInCustomField(aType, entry, aBlocked)) {
return &entry;
}
if (!entry.mData->mLogs.IsEmpty()) {
auto& last = entry.mData->mLogs.LastElement();
if (last.mType == aType && last.mBlocked == aBlocked &&
last.mCanvasFingerprinter == aCanvasFingerprinter &&
last.mCanvasFingerprinterKnownText == aCanvasFingerprinterKnownText) {
++last.mRepeatCount;
// Don't record recorded events. This helps compress our log.
// We don't care about if the the reason is the same, just keep the
// first one.
// Note: {aReason, aTrackingFullHashes} are not compared here and we
// simply keep the first for the reason, and merge hashes to make sure
// they can be correctly recorded.
for (const auto& hash : aTrackingFullHashes) {
if (!last.mTrackingFullHashes.Contains(hash)) {
last.mTrackingFullHashes.AppendElement(hash);
}
}
return &entry;
}
}
if (entry.mData->mLogs.Length() ==
std::max(1u, StaticPrefs::browser_contentblocking_originlog_length())) {
// Cap the size at the maximum length adjustable by the pref
entry.mData->mLogs.RemoveElementAt(0);
}
entry.mData->mLogs.AppendElement(
LogEntry{aType, 1u, aBlocked, aReason, aTrackingFullHashes.Clone(),
aCanvasFingerprinter, aCanvasFingerprinterKnownText});
// Check suspicious fingerprinting activities if the origin hasn't already
// been marked.
// TODO(Bug 1864909): Moving the suspicious fingerprinting detection call
// out of here.
if ((aType == nsIWebProgressListener::STATE_ALLOWED_CANVAS_FINGERPRINTING ||
aType == nsIWebProgressListener::STATE_ALLOWED_FONT_FINGERPRINTING) &&
!entry.mData->mHasSuspiciousFingerprintingActivity &&
nsRFPService::CheckSuspiciousFingerprintingActivity(
entry.mData->mLogs)) {
entry.mData->mHasSuspiciousFingerprintingActivity = true;
}
return &entry;
}
// The entry has not been found.
OriginEntry* entry = mLog.AppendElement();
if (NS_WARN_IF(!entry || !entry->mData)) {
return nullptr;
}
entry->mOrigin = aOrigin;
if (aType == nsIWebProgressListener::STATE_LOADED_LEVEL_1_TRACKING_CONTENT) {
entry->mData->mHasLevel1TrackingContentLoaded = aBlocked;
} else if (aType ==
nsIWebProgressListener::STATE_LOADED_LEVEL_2_TRACKING_CONTENT) {
entry->mData->mHasLevel2TrackingContentLoaded = aBlocked;
} else if (aType == nsIWebProgressListener::STATE_COOKIES_LOADED) {
MOZ_ASSERT(entry->mData->mHasCookiesLoaded.isNothing());
entry->mData->mHasCookiesLoaded.emplace(aBlocked);
} else if (aType == nsIWebProgressListener::STATE_COOKIES_LOADED_TRACKER) {
MOZ_ASSERT(entry->mData->mHasTrackerCookiesLoaded.isNothing());
entry->mData->mHasTrackerCookiesLoaded.emplace(aBlocked);
} else if (aType ==
nsIWebProgressListener::STATE_COOKIES_LOADED_SOCIALTRACKER) {
MOZ_ASSERT(entry->mData->mHasSocialTrackerCookiesLoaded.isNothing());
entry->mData->mHasSocialTrackerCookiesLoaded.emplace(aBlocked);
} else {
entry->mData->mLogs.AppendElement(
LogEntry{aType, 1u, aBlocked, aReason, aTrackingFullHashes.Clone(),
aCanvasFingerprinter, aCanvasFingerprinterKnownText});
// Check suspicious fingerprinting activities if the origin hasn't been
// marked.
// TODO(Bug 1864909): Moving the suspicious fingerprinting detection call
// out of here.
if ((aType == nsIWebProgressListener::STATE_ALLOWED_CANVAS_FINGERPRINTING ||
aType == nsIWebProgressListener::STATE_ALLOWED_FONT_FINGERPRINTING) &&
nsRFPService::CheckSuspiciousFingerprintingActivity(
entry->mData->mLogs)) {
entry->mData->mHasSuspiciousFingerprintingActivity = true;
}
}
return entry;
}
} // namespace mozilla

Просмотреть файл

@ -22,10 +22,13 @@ class nsIPrincipal;
namespace mozilla {
class nsRFPService;
class ContentBlockingLog final {
typedef ContentBlockingNotifier::StorageAccessPermissionGrantedReason
StorageAccessPermissionGrantedReason;
protected:
struct LogEntry {
uint32_t mType;
uint32_t mRepeatCount;
@ -40,10 +43,12 @@ class ContentBlockingLog final {
struct OriginDataEntry {
OriginDataEntry()
: mHasLevel1TrackingContentLoaded(false),
mHasLevel2TrackingContentLoaded(false) {}
mHasLevel2TrackingContentLoaded(false),
mHasSuspiciousFingerprintingActivity(false) {}
bool mHasLevel1TrackingContentLoaded;
bool mHasLevel2TrackingContentLoaded;
bool mHasSuspiciousFingerprintingActivity;
Maybe<bool> mHasCookiesLoaded;
Maybe<bool> mHasTrackerCookiesLoaded;
Maybe<bool> mHasSocialTrackerCookiesLoaded;
@ -57,6 +62,8 @@ class ContentBlockingLog final {
UniquePtr<OriginDataEntry> mData;
};
friend class nsRFPService;
typedef nsTArray<OriginEntry> OriginDataTable;
struct Comparator {
@ -220,6 +227,11 @@ class ContentBlockingLog final {
events |= nsIWebProgressListener::STATE_LOADED_LEVEL_2_TRACKING_CONTENT;
}
if (entry.mData->mHasSuspiciousFingerprintingActivity) {
events |=
nsIWebProgressListener::STATE_BLOCKED_SUSPICIOUS_FINGERPRINTING;
}
if (entry.mData->mHasCookiesLoaded.isSome() &&
entry.mData->mHasCookiesLoaded.value()) {
events |= nsIWebProgressListener::STATE_COOKIES_LOADED;
@ -246,7 +258,7 @@ class ContentBlockingLog final {
}
private:
void RecordLogInternal(
OriginEntry* RecordLogInternal(
const nsACString& aOrigin, uint32_t aType, bool aBlocked,
const Maybe<
ContentBlockingNotifier::StorageAccessPermissionGrantedReason>&
@ -254,90 +266,7 @@ class ContentBlockingLog final {
const nsTArray<nsCString>& aTrackingFullHashes = nsTArray<nsCString>(),
const Maybe<ContentBlockingNotifier::CanvasFingerprinter>&
aCanvasFingerprinter = Nothing(),
const Maybe<bool> aCanvasFingerprinterKnownText = Nothing()) {
DebugOnly<bool> isCookiesBlockedTracker =
aType == nsIWebProgressListener::STATE_COOKIES_BLOCKED_TRACKER ||
aType == nsIWebProgressListener::STATE_COOKIES_BLOCKED_SOCIALTRACKER;
MOZ_ASSERT_IF(aBlocked, aReason.isNothing());
MOZ_ASSERT_IF(!isCookiesBlockedTracker, aReason.isNothing());
MOZ_ASSERT_IF(isCookiesBlockedTracker && !aBlocked, aReason.isSome());
if (aOrigin.IsVoid()) {
return;
}
auto index = mLog.IndexOf(aOrigin, 0, Comparator());
if (index != OriginDataTable::NoIndex) {
OriginEntry& entry = mLog[index];
if (!entry.mData) {
return;
}
if (RecordLogEntryInCustomField(aType, entry, aBlocked)) {
return;
}
if (!entry.mData->mLogs.IsEmpty()) {
auto& last = entry.mData->mLogs.LastElement();
if (last.mType == aType && last.mBlocked == aBlocked &&
last.mCanvasFingerprinter == aCanvasFingerprinter &&
last.mCanvasFingerprinterKnownText ==
aCanvasFingerprinterKnownText) {
++last.mRepeatCount;
// Don't record recorded events. This helps compress our log.
// We don't care about if the the reason is the same, just keep the
// first one.
// Note: {aReason, aTrackingFullHashes} are not compared here and we
// simply keep the first for the reason, and merge hashes to make sure
// they can be correctly recorded.
for (const auto& hash : aTrackingFullHashes) {
if (!last.mTrackingFullHashes.Contains(hash)) {
last.mTrackingFullHashes.AppendElement(hash);
}
}
return;
}
}
if (entry.mData->mLogs.Length() ==
std::max(1u,
StaticPrefs::browser_contentblocking_originlog_length())) {
// Cap the size at the maximum length adjustable by the pref
entry.mData->mLogs.RemoveElementAt(0);
}
entry.mData->mLogs.AppendElement(
LogEntry{aType, 1u, aBlocked, aReason, aTrackingFullHashes.Clone(),
aCanvasFingerprinter, aCanvasFingerprinterKnownText});
return;
}
// The entry has not been found.
OriginEntry* entry = mLog.AppendElement();
if (NS_WARN_IF(!entry || !entry->mData)) {
return;
}
entry->mOrigin = aOrigin;
if (aType ==
nsIWebProgressListener::STATE_LOADED_LEVEL_1_TRACKING_CONTENT) {
entry->mData->mHasLevel1TrackingContentLoaded = aBlocked;
} else if (aType ==
nsIWebProgressListener::STATE_LOADED_LEVEL_2_TRACKING_CONTENT) {
entry->mData->mHasLevel2TrackingContentLoaded = aBlocked;
} else if (aType == nsIWebProgressListener::STATE_COOKIES_LOADED) {
MOZ_ASSERT(entry->mData->mHasCookiesLoaded.isNothing());
entry->mData->mHasCookiesLoaded.emplace(aBlocked);
} else if (aType == nsIWebProgressListener::STATE_COOKIES_LOADED_TRACKER) {
MOZ_ASSERT(entry->mData->mHasTrackerCookiesLoaded.isNothing());
entry->mData->mHasTrackerCookiesLoaded.emplace(aBlocked);
} else if (aType ==
nsIWebProgressListener::STATE_COOKIES_LOADED_SOCIALTRACKER) {
MOZ_ASSERT(entry->mData->mHasSocialTrackerCookiesLoaded.isNothing());
entry->mData->mHasSocialTrackerCookiesLoaded.emplace(aBlocked);
} else {
entry->mData->mLogs.AppendElement(
LogEntry{aType, 1u, aBlocked, aReason, aTrackingFullHashes.Clone(),
aCanvasFingerprinter, aCanvasFingerprinterKnownText});
}
}
const Maybe<bool> aCanvasFingerprinterKnownText = Nothing());
bool RecordLogEntryInCustomField(uint32_t aType, OriginEntry& aEntry,
bool aBlocked) {
@ -431,6 +360,16 @@ class ContentBlockingLog final {
}
aWriter.EndArray();
}
if (aEntry.mData->mHasSuspiciousFingerprintingActivity) {
aWriter.StartArrayElement(aWriter.SingleLineStyle);
{
aWriter.IntElement(
nsIWebProgressListener::STATE_BLOCKED_SUSPICIOUS_FINGERPRINTING);
aWriter.BoolElement(true); // blocked
aWriter.IntElement(1); // repeat count
}
aWriter.EndArray();
}
}
private:

Просмотреть файл

@ -347,6 +347,10 @@ interface nsIWebProgressListener : nsISupports
* STATE_ALLOWED_FONT_FINGERPRINTING
* A potential attempt to fingerprint by checking the exposed fonts
* was observed.
*
* STATE_BLOCKED_SUSPICIOUS_FINGERPRINTING
* Suspicious fingerprinting activity has been blocked by the fingerprinting
* protection.
*/
const unsigned long STATE_BLOCKED_TRACKING_CONTENT = 0x00001000;
const unsigned long STATE_LOADED_LEVEL_1_TRACKING_CONTENT = 0x00002000;
@ -376,6 +380,7 @@ interface nsIWebProgressListener : nsISupports
const unsigned long STATE_LOADED_EMAILTRACKING_LEVEL_2_CONTENT = 0x00000100;
const unsigned long STATE_ALLOWED_CANVAS_FINGERPRINTING = 0x02000000;
const unsigned long STATE_ALLOWED_FONT_FINGERPRINTING = 0x04000000;
const unsigned long STATE_BLOCKED_SUSPICIOUS_FINGERPRINTING = 0x08000000;
/**
* Flags for HTTPS-Only and HTTPS-First Mode upgrades