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/. */
6
7 #include "AntiTrackingLog.h"
8 #include "ContentBlockingLog.h"
9
10 #include "nsITrackingDBService.h"
11 #include "nsServiceManagerUtils.h"
12 #include "nsTArray.h"
13 #include "mozilla/HashFunctions.h"
14 #include "mozilla/Preferences.h"
15 #include "mozilla/RandomNum.h"
16 #include "mozilla/ReverseIterator.h"
17 #include "mozilla/StaticPrefs_browser.h"
18 #include "mozilla/StaticPrefs_privacy.h"
19 #include "mozilla/StaticPrefs_telemetry.h"
20 #include "mozilla/Telemetry.h"
21 #include "mozilla/XorShift128PlusRNG.h"
22
23 namespace mozilla {
24
25 using ipc::AutoIPCStream;
26
27 typedef Telemetry::OriginMetricID OriginMetricID;
28
29 // sync with TelemetryOriginData.inc
30 const nsLiteralCString ContentBlockingLog::kDummyOriginHash = "PAGELOAD"_ns;
31
32 // randomly choose 1% users included in the content blocking measurement
33 // based on their client id.
34 static constexpr double kRatioReportUser = 0.01;
35
36 // randomly choose 0.14% documents when the page is unload.
37 static constexpr double kRatioReportDocument = 0.0014;
38
IsReportingPerUserEnabled()39 static bool IsReportingPerUserEnabled() {
40 MOZ_ASSERT(NS_IsMainThread());
41
42 static Maybe<bool> sIsReportingEnabled;
43
44 if (sIsReportingEnabled.isSome()) {
45 return sIsReportingEnabled.value();
46 }
47
48 nsAutoCString cachedClientId;
49 if (NS_FAILED(Preferences::GetCString("toolkit.telemetry.cachedClientID",
50 cachedClientId))) {
51 return false;
52 }
53
54 nsID clientId;
55 if (!clientId.Parse(cachedClientId.get())) {
56 return false;
57 }
58
59 /**
60 * UUID might not be uniform-distributed (although some part of it could be).
61 * In order to generate more random result, usually we use a hash function,
62 * but here we hope it's fast and doesn't have to be cryptographic-safe.
63 * |XorShift128PlusRNG| looks like a good alternative because it takes a
64 * 128-bit data as its seed and always generate identical sequence if the
65 * initial seed is the same.
66 */
67 static_assert(sizeof(nsID) == 16, "nsID is 128-bit");
68 uint64_t* init = reinterpret_cast<uint64_t*>(&clientId);
69 non_crypto::XorShift128PlusRNG rng(init[0], init[1]);
70 sIsReportingEnabled.emplace(rng.nextDouble() <= kRatioReportUser);
71
72 return sIsReportingEnabled.value();
73 }
74
IsReportingPerDocumentEnabled()75 static bool IsReportingPerDocumentEnabled() {
76 constexpr double boundary =
77 kRatioReportDocument * double(std::numeric_limits<uint64_t>::max());
78 Maybe<uint64_t> randomNum = RandomUint64();
79 return randomNum.isSome() && randomNum.value() <= boundary;
80 }
81
IsReportingEnabled()82 static bool IsReportingEnabled() {
83 if (StaticPrefs::telemetry_origin_telemetry_test_mode_enabled()) {
84 return true;
85 } else if (!StaticPrefs::
86 privacy_trackingprotection_origin_telemetry_enabled()) {
87 return false;
88 }
89
90 return IsReportingPerUserEnabled() && IsReportingPerDocumentEnabled();
91 }
92
ReportOriginSingleHash(OriginMetricID aId,const nsACString & aOrigin)93 static void ReportOriginSingleHash(OriginMetricID aId,
94 const nsACString& aOrigin) {
95 MOZ_ASSERT(XRE_IsParentProcess());
96 MOZ_ASSERT(NS_IsMainThread());
97
98 LOG(("ReportOriginSingleHash metric=%s",
99 Telemetry::MetricIDToString[static_cast<uint32_t>(aId)]));
100 LOG(("ReportOriginSingleHash origin=%s", PromiseFlatCString(aOrigin).get()));
101
102 Telemetry::RecordOrigin(aId, aOrigin);
103 }
104
RecordLogParent(const nsACString & aOrigin,uint32_t aType,bool aBlocked,const Maybe<ContentBlockingNotifier::StorageAccessPermissionGrantedReason> & aReason,const nsTArray<nsCString> & aTrackingFullHashes)105 Maybe<uint32_t> ContentBlockingLog::RecordLogParent(
106 const nsACString& aOrigin, uint32_t aType, bool aBlocked,
107 const Maybe<ContentBlockingNotifier::StorageAccessPermissionGrantedReason>&
108 aReason,
109 const nsTArray<nsCString>& aTrackingFullHashes) {
110 MOZ_ASSERT(XRE_IsParentProcess());
111
112 uint32_t events = GetContentBlockingEventsInLog();
113
114 bool blockedValue = aBlocked;
115 bool unblocked = false;
116
117 switch (aType) {
118 case nsIWebProgressListener::STATE_COOKIES_LOADED:
119 MOZ_ASSERT(!aBlocked,
120 "We don't expected to see blocked STATE_COOKIES_LOADED");
121 [[fallthrough]];
122
123 case nsIWebProgressListener::STATE_COOKIES_LOADED_TRACKER:
124 MOZ_ASSERT(
125 !aBlocked,
126 "We don't expected to see blocked STATE_COOKIES_LOADED_TRACKER");
127 [[fallthrough]];
128
129 case nsIWebProgressListener::STATE_COOKIES_LOADED_SOCIALTRACKER:
130 MOZ_ASSERT(!aBlocked,
131 "We don't expected to see blocked "
132 "STATE_COOKIES_LOADED_SOCIALTRACKER");
133 // Note that the logic in these branches are the logical negation of the
134 // logic in other branches, since the Document API we have is phrased
135 // in "loaded" terms as opposed to "blocked" terms.
136 blockedValue = !aBlocked;
137 [[fallthrough]];
138
139 case nsIWebProgressListener::STATE_BLOCKED_TRACKING_CONTENT:
140 case nsIWebProgressListener::STATE_LOADED_LEVEL_1_TRACKING_CONTENT:
141 case nsIWebProgressListener::STATE_LOADED_LEVEL_2_TRACKING_CONTENT:
142 case nsIWebProgressListener::STATE_BLOCKED_FINGERPRINTING_CONTENT:
143 case nsIWebProgressListener::STATE_LOADED_FINGERPRINTING_CONTENT:
144 case nsIWebProgressListener::STATE_BLOCKED_CRYPTOMINING_CONTENT:
145 case nsIWebProgressListener::STATE_LOADED_CRYPTOMINING_CONTENT:
146 case nsIWebProgressListener::STATE_BLOCKED_SOCIALTRACKING_CONTENT:
147 case nsIWebProgressListener::STATE_LOADED_SOCIALTRACKING_CONTENT:
148 case nsIWebProgressListener::STATE_COOKIES_BLOCKED_BY_PERMISSION:
149 case nsIWebProgressListener::STATE_COOKIES_BLOCKED_ALL:
150 case nsIWebProgressListener::STATE_COOKIES_BLOCKED_FOREIGN:
151 RecordLogInternal(aOrigin, aType, blockedValue);
152 break;
153
154 case nsIWebProgressListener::STATE_COOKIES_BLOCKED_TRACKER:
155 case nsIWebProgressListener::STATE_COOKIES_BLOCKED_SOCIALTRACKER:
156 RecordLogInternal(aOrigin, aType, blockedValue, aReason,
157 aTrackingFullHashes);
158 break;
159
160 case nsIWebProgressListener::STATE_REPLACED_TRACKING_CONTENT:
161 case nsIWebProgressListener::STATE_ALLOWED_TRACKING_CONTENT:
162 RecordLogInternal(aOrigin, aType, blockedValue);
163 break;
164
165 default:
166 // Ignore nsIWebProgressListener::STATE_BLOCKED_UNSAFE_CONTENT;
167 break;
168 }
169
170 if (!aBlocked) {
171 unblocked = (events & aType) != 0;
172 }
173
174 const uint32_t oldEvents = events;
175 if (blockedValue) {
176 events |= aType;
177 } else if (unblocked) {
178 events &= ~aType;
179 }
180
181 if (events == oldEvents
182 #ifdef ANDROID
183 // GeckoView always needs to notify about blocked trackers,
184 // since the GeckoView API always needs to report the URI and
185 // type of any blocked tracker. We use a platform-dependent code
186 // path here because reporting this notification on desktop
187 // platforms isn't necessary and doing so can have a big
188 // performance cost.
189 && aType != nsIWebProgressListener::STATE_BLOCKED_TRACKING_CONTENT
190 #endif
191 ) {
192 // Avoid dispatching repeated notifications when nothing has
193 // changed
194 return Nothing();
195 }
196
197 return Some(events);
198 }
199
ReportLog(nsIPrincipal * aFirstPartyPrincipal)200 void ContentBlockingLog::ReportLog(nsIPrincipal* aFirstPartyPrincipal) {
201 MOZ_ASSERT(XRE_IsParentProcess());
202 MOZ_ASSERT(NS_IsMainThread());
203 MOZ_ASSERT(aFirstPartyPrincipal);
204
205 if (!StaticPrefs::browser_contentblocking_database_enabled()) {
206 return;
207 }
208
209 if (mLog.IsEmpty()) {
210 return;
211 }
212
213 nsCOMPtr<nsITrackingDBService> trackingDBService =
214 do_GetService("@mozilla.org/tracking-db-service;1");
215 if (NS_WARN_IF(!trackingDBService)) {
216 return;
217 }
218
219 trackingDBService->RecordContentBlockingLog(Stringify());
220 }
221
ReportOrigins()222 void ContentBlockingLog::ReportOrigins() {
223 if (!IsReportingEnabled()) {
224 return;
225 }
226 LOG(("ContentBlockingLog::ReportOrigins [this=%p]", this));
227 const bool testMode =
228 StaticPrefs::telemetry_origin_telemetry_test_mode_enabled();
229 OriginMetricID metricId =
230 testMode ? OriginMetricID::ContentBlocking_Blocked_TestOnly
231 : OriginMetricID::ContentBlocking_Blocked;
232 ReportOriginSingleHash(metricId, kDummyOriginHash);
233
234 nsTArray<HashNumber> lookupTable;
235 for (const auto& originEntry : mLog) {
236 if (!originEntry.mData) {
237 continue;
238 }
239
240 for (const auto& logEntry : Reversed(originEntry.mData->mLogs)) {
241 if ((logEntry.mType !=
242 nsIWebProgressListener::STATE_COOKIES_BLOCKED_TRACKER &&
243 logEntry.mType !=
244 nsIWebProgressListener::STATE_COOKIES_BLOCKED_SOCIALTRACKER) ||
245 logEntry.mTrackingFullHashes.IsEmpty()) {
246 continue;
247 }
248
249 const bool isBlocked = logEntry.mBlocked;
250 Maybe<StorageAccessPermissionGrantedReason> reason = logEntry.mReason;
251
252 metricId = testMode ? OriginMetricID::ContentBlocking_Blocked_TestOnly
253 : OriginMetricID::ContentBlocking_Blocked;
254 if (!isBlocked) {
255 MOZ_ASSERT(reason.isSome());
256 switch (reason.value()) {
257 case StorageAccessPermissionGrantedReason::eStorageAccessAPI:
258 metricId =
259 testMode
260 ? OriginMetricID::
261 ContentBlocking_StorageAccessAPIExempt_TestOnly
262 : OriginMetricID::ContentBlocking_StorageAccessAPIExempt;
263 break;
264 case StorageAccessPermissionGrantedReason::
265 eOpenerAfterUserInteraction:
266 metricId =
267 testMode
268 ? OriginMetricID::
269 ContentBlocking_OpenerAfterUserInteractionExempt_TestOnly
270 : OriginMetricID::
271 ContentBlocking_OpenerAfterUserInteractionExempt;
272 break;
273 case StorageAccessPermissionGrantedReason::eOpener:
274 metricId =
275 testMode ? OriginMetricID::ContentBlocking_OpenerExempt_TestOnly
276 : OriginMetricID::ContentBlocking_OpenerExempt;
277 break;
278 default:
279 MOZ_ASSERT_UNREACHABLE(
280 "Unknown StorageAccessPermissionGrantedReason");
281 }
282 }
283
284 for (const auto& hash : logEntry.mTrackingFullHashes) {
285 HashNumber key = AddToHash(HashString(hash.get(), hash.Length()),
286 static_cast<uint32_t>(metricId));
287 if (lookupTable.Contains(key)) {
288 continue;
289 }
290 lookupTable.AppendElement(key);
291 ReportOriginSingleHash(metricId, hash);
292 }
293 break;
294 }
295 }
296 }
297
298 } // namespace mozilla
299