1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "AntiTrackingLog.h"
8 #include "ContentBlockingLog.h"
10 #include "nsIEffectiveTLDService.h"
11 #include "nsITrackingDBService.h"
12 #include "nsIWebProgressListener.h"
14 #include "nsNetUtil.h"
15 #include "nsRFPService.h"
16 #include "nsServiceManagerUtils.h"
18 #include "mozilla/BasePrincipal.h"
19 #include "mozilla/ClearOnShutdown.h"
20 #include "mozilla/HashFunctions.h"
21 #include "mozilla/Preferences.h"
22 #include "mozilla/RandomNum.h"
23 #include "mozilla/ReverseIterator.h"
24 #include "mozilla/StaticPrefs_browser.h"
25 #include "mozilla/StaticPrefs_privacy.h"
26 #include "mozilla/StaticPrefs_telemetry.h"
27 #include "mozilla/StaticPtr.h"
28 #include "mozilla/Telemetry.h"
29 #include "mozilla/XorShift128PlusRNG.h"
35 StaticAutoPtr
<nsCString
> gEmailWebAppDomainsPref
;
36 static constexpr char kEmailWebAppDomainPrefName
[] =
37 "privacy.trackingprotection.emailtracking.webapp.domains";
39 void EmailWebAppDomainPrefChangeCallback(const char* aPrefName
, void*) {
40 MOZ_ASSERT(NS_IsMainThread());
41 MOZ_ASSERT(!strcmp(aPrefName
, kEmailWebAppDomainPrefName
));
42 MOZ_ASSERT(gEmailWebAppDomainsPref
);
44 Preferences::GetCString(kEmailWebAppDomainPrefName
, *gEmailWebAppDomainsPref
);
49 Maybe
<uint32_t> ContentBlockingLog::RecordLogParent(
50 const nsACString
& aOrigin
, uint32_t aType
, bool aBlocked
,
51 const Maybe
<ContentBlockingNotifier::StorageAccessPermissionGrantedReason
>&
53 const nsTArray
<nsCString
>& aTrackingFullHashes
,
54 const Maybe
<ContentBlockingNotifier::CanvasFingerprinter
>&
56 const Maybe
<bool> aCanvasFingerprinterKnownText
) {
57 MOZ_ASSERT(XRE_IsParentProcess());
59 uint32_t events
= GetContentBlockingEventsInLog();
61 bool blockedValue
= aBlocked
;
62 bool unblocked
= false;
66 case nsIWebProgressListener::STATE_COOKIES_LOADED
:
68 "We don't expected to see blocked STATE_COOKIES_LOADED");
71 case nsIWebProgressListener::STATE_COOKIES_LOADED_TRACKER
:
74 "We don't expected to see blocked STATE_COOKIES_LOADED_TRACKER");
77 case nsIWebProgressListener::STATE_COOKIES_LOADED_SOCIALTRACKER
:
79 "We don't expected to see blocked "
80 "STATE_COOKIES_LOADED_SOCIALTRACKER");
81 // Note that the logic in these branches are the logical negation of the
82 // logic in other branches, since the Document API we have is phrased
83 // in "loaded" terms as opposed to "blocked" terms.
84 blockedValue
= !aBlocked
;
87 case nsIWebProgressListener::STATE_BLOCKED_TRACKING_CONTENT
:
88 case nsIWebProgressListener::STATE_LOADED_LEVEL_1_TRACKING_CONTENT
:
89 case nsIWebProgressListener::STATE_LOADED_LEVEL_2_TRACKING_CONTENT
:
90 case nsIWebProgressListener::STATE_BLOCKED_FINGERPRINTING_CONTENT
:
91 case nsIWebProgressListener::STATE_LOADED_FINGERPRINTING_CONTENT
:
92 case nsIWebProgressListener::STATE_BLOCKED_CRYPTOMINING_CONTENT
:
93 case nsIWebProgressListener::STATE_LOADED_CRYPTOMINING_CONTENT
:
94 case nsIWebProgressListener::STATE_BLOCKED_SOCIALTRACKING_CONTENT
:
95 case nsIWebProgressListener::STATE_LOADED_SOCIALTRACKING_CONTENT
:
96 case nsIWebProgressListener::STATE_COOKIES_BLOCKED_BY_PERMISSION
:
97 case nsIWebProgressListener::STATE_COOKIES_BLOCKED_ALL
:
98 case nsIWebProgressListener::STATE_COOKIES_BLOCKED_FOREIGN
:
99 case nsIWebProgressListener::STATE_BLOCKED_EMAILTRACKING_CONTENT
:
100 case nsIWebProgressListener::STATE_LOADED_EMAILTRACKING_LEVEL_1_CONTENT
:
101 case nsIWebProgressListener::STATE_LOADED_EMAILTRACKING_LEVEL_2_CONTENT
:
102 case nsIWebProgressListener::STATE_PURGED_BOUNCETRACKER
:
103 case nsIWebProgressListener::STATE_COOKIES_PARTITIONED_TRACKER
:
104 Unused
<< RecordLogInternal(aOrigin
, aType
, blockedValue
);
107 case nsIWebProgressListener::STATE_COOKIES_BLOCKED_TRACKER
:
108 case nsIWebProgressListener::STATE_COOKIES_BLOCKED_SOCIALTRACKER
:
109 Unused
<< RecordLogInternal(aOrigin
, aType
, blockedValue
, aReason
,
110 aTrackingFullHashes
);
113 case nsIWebProgressListener::STATE_REPLACED_FINGERPRINTING_CONTENT
:
114 case nsIWebProgressListener::STATE_ALLOWED_FINGERPRINTING_CONTENT
:
115 case nsIWebProgressListener::STATE_REPLACED_TRACKING_CONTENT
:
116 case nsIWebProgressListener::STATE_ALLOWED_TRACKING_CONTENT
:
117 Unused
<< RecordLogInternal(aOrigin
, aType
, blockedValue
, aReason
,
118 aTrackingFullHashes
);
120 case nsIWebProgressListener::STATE_ALLOWED_FONT_FINGERPRINTING
:
121 MOZ_ASSERT(!aBlocked
,
122 "We don't expected to see blocked "
123 "STATE_ALLOWED_FONT_FINGERPRINTING");
124 entry
= RecordLogInternal(aOrigin
, aType
, blockedValue
);
126 // Replace the flag using the suspicious fingerprinting event so that we
127 // can report the event if we detect suspicious fingerprinting.
128 aType
= nsIWebProgressListener::STATE_BLOCKED_SUSPICIOUS_FINGERPRINTING
;
130 // Report blocking if we detect suspicious fingerprinting activity.
131 if (entry
&& entry
->mData
->mHasSuspiciousFingerprintingActivity
) {
136 case nsIWebProgressListener::STATE_ALLOWED_CANVAS_FINGERPRINTING
:
137 MOZ_ASSERT(!aBlocked
,
138 "We don't expected to see blocked "
139 "STATE_ALLOWED_CANVAS_FINGERPRINTING");
140 entry
= RecordLogInternal(aOrigin
, aType
, blockedValue
, Nothing(), {},
141 aCanvasFingerprinter
,
142 aCanvasFingerprinterKnownText
);
144 // Replace the flag using the suspicious fingerprinting event so that we
145 // can report the event if we detect suspicious fingerprinting.
146 aType
= nsIWebProgressListener::STATE_BLOCKED_SUSPICIOUS_FINGERPRINTING
;
148 // Report blocking if we detect suspicious fingerprinting activity.
149 if (entry
&& entry
->mData
->mHasSuspiciousFingerprintingActivity
) {
155 // Ignore nsIWebProgressListener::STATE_BLOCKED_UNSAFE_CONTENT;
160 unblocked
= (events
& aType
) != 0;
163 const uint32_t oldEvents
= events
;
166 } else if (unblocked
) {
170 if (events
== oldEvents
172 // GeckoView always needs to notify about blocked trackers,
173 // since the GeckoView API always needs to report the URI and
174 // type of any blocked tracker. We use a platform-dependent code
175 // path here because reporting this notification on desktop
176 // platforms isn't necessary and doing so can have a big
178 && aType
!= nsIWebProgressListener::STATE_BLOCKED_TRACKING_CONTENT
181 // Avoid dispatching repeated notifications when nothing has
189 void ContentBlockingLog::ReportLog() {
190 MOZ_ASSERT(XRE_IsParentProcess());
191 MOZ_ASSERT(NS_IsMainThread());
193 if (!StaticPrefs::browser_contentblocking_database_enabled()) {
197 if (mLog
.IsEmpty()) {
201 nsCOMPtr
<nsITrackingDBService
> trackingDBService
=
202 do_GetService("@mozilla.org/tracking-db-service;1");
203 if (NS_WARN_IF(!trackingDBService
)) {
207 trackingDBService
->RecordContentBlockingLog(Stringify());
210 void ContentBlockingLog::ReportCanvasFingerprintingLog(
211 nsIPrincipal
* aFirstPartyPrincipal
) {
212 MOZ_ASSERT(XRE_IsParentProcess());
213 MOZ_ASSERT(NS_IsMainThread());
214 MOZ_ASSERT(aFirstPartyPrincipal
);
216 // We don't need to report if the first party is not a content.
217 if (!BasePrincipal::Cast(aFirstPartyPrincipal
)->IsContentPrincipal()) {
221 bool hasCanvasFingerprinter
= false;
222 bool canvasFingerprinterKnownText
= false;
223 Maybe
<ContentBlockingNotifier::CanvasFingerprinter
> canvasFingerprinter
;
224 for (const auto& originEntry
: mLog
) {
225 if (!originEntry
.mData
) {
229 for (const auto& logEntry
: Reversed(originEntry
.mData
->mLogs
)) {
230 if (logEntry
.mType
!=
231 nsIWebProgressListener::STATE_ALLOWED_CANVAS_FINGERPRINTING
) {
235 // Select the log entry with the highest fingerprinting likelihood,
236 // that primarily means preferring those with a FingerprinterKnownText.
237 if (!hasCanvasFingerprinter
||
238 (!canvasFingerprinterKnownText
&&
239 *logEntry
.mCanvasFingerprinterKnownText
) ||
240 (!canvasFingerprinterKnownText
&& canvasFingerprinter
.isNothing() &&
241 logEntry
.mCanvasFingerprinter
.isSome())) {
242 hasCanvasFingerprinter
= true;
243 canvasFingerprinterKnownText
= *logEntry
.mCanvasFingerprinterKnownText
;
244 canvasFingerprinter
= logEntry
.mCanvasFingerprinter
;
249 if (!hasCanvasFingerprinter
) {
250 Telemetry::Accumulate(Telemetry::CANVAS_FINGERPRINTING_PER_TAB
,
253 int32_t fingerprinter
=
254 canvasFingerprinter
.isSome() ? (*canvasFingerprinter
+ 1) : 0;
255 Telemetry::Accumulate(
256 Telemetry::CANVAS_FINGERPRINTING_PER_TAB
,
257 canvasFingerprinterKnownText
? "known_text"_ns
: "unknown"_ns
,
262 void ContentBlockingLog::ReportFontFingerprintingLog(
263 nsIPrincipal
* aFirstPartyPrincipal
) {
264 MOZ_ASSERT(XRE_IsParentProcess());
265 MOZ_ASSERT(NS_IsMainThread());
266 MOZ_ASSERT(aFirstPartyPrincipal
);
268 // We don't need to report if the first party is not a content.
269 if (!BasePrincipal::Cast(aFirstPartyPrincipal
)->IsContentPrincipal()) {
273 bool hasFontFingerprinter
= false;
274 for (const auto& originEntry
: mLog
) {
275 if (!originEntry
.mData
) {
279 for (const auto& logEntry
: originEntry
.mData
->mLogs
) {
280 if (logEntry
.mType
!=
281 nsIWebProgressListener::STATE_ALLOWED_FONT_FINGERPRINTING
) {
285 hasFontFingerprinter
= true;
288 if (hasFontFingerprinter
) {
293 Telemetry::Accumulate(Telemetry::FONT_FINGERPRINTING_PER_TAB
,
294 hasFontFingerprinter
);
297 void ContentBlockingLog::ReportEmailTrackingLog(
298 nsIPrincipal
* aFirstPartyPrincipal
) {
299 MOZ_ASSERT(XRE_IsParentProcess());
300 MOZ_ASSERT(NS_IsMainThread());
301 MOZ_ASSERT(aFirstPartyPrincipal
);
303 // We don't need to report if the first party is not a content.
304 if (!BasePrincipal::Cast(aFirstPartyPrincipal
)->IsContentPrincipal()) {
308 nsCOMPtr
<nsIEffectiveTLDService
> tldService
=
309 do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID
);
315 nsTHashtable
<nsCStringHashKey
> level1SiteSet
;
316 nsTHashtable
<nsCStringHashKey
> level2SiteSet
;
318 for (const auto& originEntry
: mLog
) {
319 if (!originEntry
.mData
) {
323 bool isLevel1EmailTracker
= false;
324 bool isLevel2EmailTracker
= false;
326 for (const auto& logEntry
: Reversed(originEntry
.mData
->mLogs
)) {
327 // Check if the email tracking related event had been filed for the given
328 // origin entry. Note that we currently only block level 1 email trackers,
329 // so blocking event represents the page has embedded a level 1 tracker.
330 if (logEntry
.mType
==
331 nsIWebProgressListener::STATE_LOADED_EMAILTRACKING_LEVEL_2_CONTENT
) {
332 isLevel2EmailTracker
= true;
336 if (logEntry
.mType
==
337 nsIWebProgressListener::STATE_BLOCKED_EMAILTRACKING_CONTENT
||
338 logEntry
.mType
== nsIWebProgressListener::
339 STATE_LOADED_EMAILTRACKING_LEVEL_1_CONTENT
) {
340 isLevel1EmailTracker
= true;
345 if (isLevel1EmailTracker
|| isLevel2EmailTracker
) {
346 nsCOMPtr
<nsIURI
> uri
;
347 nsresult rv
= NS_NewURI(getter_AddRefs(uri
), originEntry
.mOrigin
);
353 nsAutoCString baseDomain
;
354 rv
= tldService
->GetBaseDomain(uri
, 0, baseDomain
);
360 if (isLevel1EmailTracker
) {
361 Unused
<< level1SiteSet
.EnsureInserted(baseDomain
);
363 Unused
<< level2SiteSet
.EnsureInserted(baseDomain
);
368 // Cache the email webapp domains pref value and register the callback
369 // function to update the cached value when the pref changes.
370 if (!gEmailWebAppDomainsPref
) {
371 gEmailWebAppDomainsPref
= new nsCString();
373 Preferences::RegisterCallbackAndCall(EmailWebAppDomainPrefChangeCallback
,
374 kEmailWebAppDomainPrefName
);
376 Preferences::UnregisterCallback(EmailWebAppDomainPrefChangeCallback
,
377 kEmailWebAppDomainPrefName
);
378 gEmailWebAppDomainsPref
= nullptr;
382 bool isTopEmailWebApp
=
383 aFirstPartyPrincipal
->IsURIInList(*gEmailWebAppDomainsPref
);
384 uint32_t level1Count
= level1SiteSet
.Count();
385 uint32_t level2Count
= level2SiteSet
.Count();
387 Telemetry::Accumulate(
388 Telemetry::EMAIL_TRACKER_EMBEDDED_PER_TAB
,
389 isTopEmailWebApp
? "base_emailapp"_ns
: "base_normal"_ns
, level1Count
);
390 Telemetry::Accumulate(
391 Telemetry::EMAIL_TRACKER_EMBEDDED_PER_TAB
,
392 isTopEmailWebApp
? "content_emailapp"_ns
: "content_normal"_ns
,
394 Telemetry::Accumulate(Telemetry::EMAIL_TRACKER_EMBEDDED_PER_TAB
,
395 isTopEmailWebApp
? "all_emailapp"_ns
: "all_normal"_ns
,
396 level1Count
+ level2Count
);
399 ContentBlockingLog::OriginEntry
* ContentBlockingLog::RecordLogInternal(
400 const nsACString
& aOrigin
, uint32_t aType
, bool aBlocked
,
401 const Maybe
<ContentBlockingNotifier::StorageAccessPermissionGrantedReason
>&
403 const nsTArray
<nsCString
>& aTrackingFullHashes
,
404 const Maybe
<ContentBlockingNotifier::CanvasFingerprinter
>&
405 aCanvasFingerprinter
,
406 const Maybe
<bool> aCanvasFingerprinterKnownText
) {
407 DebugOnly
<bool> isCookiesBlockedTracker
=
408 aType
== nsIWebProgressListener::STATE_COOKIES_BLOCKED_TRACKER
||
409 aType
== nsIWebProgressListener::STATE_COOKIES_BLOCKED_SOCIALTRACKER
;
410 MOZ_ASSERT_IF(aBlocked
, aReason
.isNothing());
411 MOZ_ASSERT_IF(!isCookiesBlockedTracker
, aReason
.isNothing());
412 MOZ_ASSERT_IF(isCookiesBlockedTracker
&& !aBlocked
, aReason
.isSome());
414 if (aOrigin
.IsVoid()) {
417 auto index
= mLog
.IndexOf(aOrigin
, 0, Comparator());
418 if (index
!= OriginDataTable::NoIndex
) {
419 OriginEntry
& entry
= mLog
[index
];
424 if (RecordLogEntryInCustomField(aType
, entry
, aBlocked
)) {
427 if (!entry
.mData
->mLogs
.IsEmpty()) {
428 auto& last
= entry
.mData
->mLogs
.LastElement();
429 if (last
.mType
== aType
&& last
.mBlocked
== aBlocked
&&
430 last
.mCanvasFingerprinter
== aCanvasFingerprinter
&&
431 last
.mCanvasFingerprinterKnownText
== aCanvasFingerprinterKnownText
) {
433 // Don't record recorded events. This helps compress our log.
434 // We don't care about if the the reason is the same, just keep the
436 // Note: {aReason, aTrackingFullHashes} are not compared here and we
437 // simply keep the first for the reason, and merge hashes to make sure
438 // they can be correctly recorded.
439 for (const auto& hash
: aTrackingFullHashes
) {
440 if (!last
.mTrackingFullHashes
.Contains(hash
)) {
441 last
.mTrackingFullHashes
.AppendElement(hash
);
447 if (entry
.mData
->mLogs
.Length() ==
448 std::max(1u, StaticPrefs::browser_contentblocking_originlog_length())) {
449 // Cap the size at the maximum length adjustable by the pref
450 entry
.mData
->mLogs
.RemoveElementAt(0);
452 entry
.mData
->mLogs
.AppendElement(
453 LogEntry
{aType
, 1u, aBlocked
, aReason
, aTrackingFullHashes
.Clone(),
454 aCanvasFingerprinter
, aCanvasFingerprinterKnownText
});
456 // Check suspicious fingerprinting activities if the origin hasn't already
458 // TODO(Bug 1864909): Moving the suspicious fingerprinting detection call
460 if ((aType
== nsIWebProgressListener::STATE_ALLOWED_CANVAS_FINGERPRINTING
||
461 aType
== nsIWebProgressListener::STATE_ALLOWED_FONT_FINGERPRINTING
) &&
462 !entry
.mData
->mHasSuspiciousFingerprintingActivity
&&
463 nsRFPService::CheckSuspiciousFingerprintingActivity(
464 entry
.mData
->mLogs
)) {
465 entry
.mData
->mHasSuspiciousFingerprintingActivity
= true;
470 // The entry has not been found.
471 OriginEntry
* entry
= mLog
.AppendElement();
472 if (NS_WARN_IF(!entry
|| !entry
->mData
)) {
476 entry
->mOrigin
= aOrigin
;
478 if (aType
== nsIWebProgressListener::STATE_LOADED_LEVEL_1_TRACKING_CONTENT
) {
479 entry
->mData
->mHasLevel1TrackingContentLoaded
= aBlocked
;
481 nsIWebProgressListener::STATE_LOADED_LEVEL_2_TRACKING_CONTENT
) {
482 entry
->mData
->mHasLevel2TrackingContentLoaded
= aBlocked
;
483 } else if (aType
== nsIWebProgressListener::STATE_COOKIES_LOADED
) {
484 MOZ_ASSERT(entry
->mData
->mHasCookiesLoaded
.isNothing());
485 entry
->mData
->mHasCookiesLoaded
.emplace(aBlocked
);
486 } else if (aType
== nsIWebProgressListener::STATE_COOKIES_LOADED_TRACKER
) {
487 MOZ_ASSERT(entry
->mData
->mHasTrackerCookiesLoaded
.isNothing());
488 entry
->mData
->mHasTrackerCookiesLoaded
.emplace(aBlocked
);
490 nsIWebProgressListener::STATE_COOKIES_LOADED_SOCIALTRACKER
) {
491 MOZ_ASSERT(entry
->mData
->mHasSocialTrackerCookiesLoaded
.isNothing());
492 entry
->mData
->mHasSocialTrackerCookiesLoaded
.emplace(aBlocked
);
494 entry
->mData
->mLogs
.AppendElement(
495 LogEntry
{aType
, 1u, aBlocked
, aReason
, aTrackingFullHashes
.Clone(),
496 aCanvasFingerprinter
, aCanvasFingerprinterKnownText
});
498 // Check suspicious fingerprinting activities if the origin hasn't been
500 // TODO(Bug 1864909): Moving the suspicious fingerprinting detection call
502 if ((aType
== nsIWebProgressListener::STATE_ALLOWED_CANVAS_FINGERPRINTING
||
503 aType
== nsIWebProgressListener::STATE_ALLOWED_FONT_FINGERPRINTING
) &&
504 nsRFPService::CheckSuspiciousFingerprintingActivity(
505 entry
->mData
->mLogs
)) {
506 entry
->mData
->mHasSuspiciousFingerprintingActivity
= true;
513 } // namespace mozilla