зеркало из https://github.com/mozilla/gecko-dev.git
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:
Родитель
1e5f4a5b17
Коммит
ef2c6c9ebc
|
@ -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
|
||||
|
|
Загрузка…
Ссылка в новой задаче