1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3  * License, v. 2.0. If a copy of the MPL was not distributed with this
4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 
6 #include "nsRFPService.h"
7 
8 #include <algorithm>
9 #include <memory>
10 #include <time.h>
11 
12 #include "mozilla/ClearOnShutdown.h"
13 #include "mozilla/dom/Element.h"
14 #include "mozilla/Logging.h"
15 #include "mozilla/Mutex.h"
16 #include "mozilla/Preferences.h"
17 #include "mozilla/Services.h"
18 #include "mozilla/StaticPtr.h"
19 #include "mozilla/StaticPrefs_privacy.h"
20 #include "mozilla/TextEvents.h"
21 #include "mozilla/dom/KeyboardEventBinding.h"
22 
23 #include "nsCOMPtr.h"
24 #include "nsCoord.h"
25 #include "nsServiceManagerUtils.h"
26 #include "nsString.h"
27 #include "nsXULAppAPI.h"
28 #include "nsPrintfCString.h"
29 
30 #include "nsICryptoHash.h"
31 #include "nsIObserverService.h"
32 #include "nsIRandomGenerator.h"
33 #include "nsIXULAppInfo.h"
34 #include "nsJSUtils.h"
35 
36 #include "prenv.h"
37 #include "nss.h"
38 
39 #include "js/Date.h"
40 
41 using namespace mozilla;
42 
43 static mozilla::LazyLogModule gResistFingerprintingLog(
44     "nsResistFingerprinting");
45 
46 #define RESIST_FINGERPRINTING_PREF "privacy.resistFingerprinting"
47 #define RFP_TIMER_PREF "privacy.reduceTimerPrecision"
48 #define RFP_TIMER_UNCONDITIONAL_PREF \
49   "privacy.reduceTimerPrecision.unconditional"
50 #define RFP_TIMER_UNCONDITIONAL_VALUE 20
51 #define RFP_TIMER_VALUE_PREF \
52   "privacy.resistFingerprinting.reduceTimerPrecision.microseconds"
53 #define RFP_JITTER_VALUE_PREF \
54   "privacy.resistFingerprinting.reduceTimerPrecision.jitter"
55 #define PROFILE_INITIALIZED_TOPIC "profile-initial-state"
56 
57 static constexpr uint32_t kVideoFramesPerSec = 30;
58 static constexpr uint32_t kVideoDroppedRatio = 5;
59 
60 #define RFP_DEFAULT_SPOOFING_KEYBOARD_LANG KeyboardLang::EN
61 #define RFP_DEFAULT_SPOOFING_KEYBOARD_REGION KeyboardRegion::US
62 
63 NS_IMPL_ISUPPORTS(nsRFPService, nsIObserver)
64 
65 static StaticRefPtr<nsRFPService> sRFPService;
66 static bool sInitialized = false;
67 nsDataHashtable<KeyboardHashKey, const SpoofingKeyboardCode*>*
68     nsRFPService::sSpoofingKeyboardCodes = nullptr;
69 static mozilla::StaticMutex sLock;
70 
71 /* static */
GetOrCreate()72 nsRFPService* nsRFPService::GetOrCreate() {
73   if (!sInitialized) {
74     sRFPService = new nsRFPService();
75     nsresult rv = sRFPService->Init();
76 
77     if (NS_FAILED(rv)) {
78       sRFPService = nullptr;
79       return nullptr;
80     }
81 
82     ClearOnShutdown(&sRFPService);
83     sInitialized = true;
84   }
85 
86   return sRFPService;
87 }
88 
89 /* static */
TimerResolution()90 double nsRFPService::TimerResolution() {
91   double prefValue = StaticPrefs::
92       privacy_resistFingerprinting_reduceTimerPrecision_microseconds();
93   if (StaticPrefs::privacy_resistFingerprinting()) {
94     return std::max(100000.0, prefValue);
95   }
96   return prefValue;
97 }
98 
99 /*
100  * The below is a simple time-based Least Recently Used cache used to store the
101  * result of a cryptographic hash function. It has LRU_CACHE_SIZE slots, and
102  * will be used from multiple threads. It is thread-safe.
103  */
104 #define LRU_CACHE_SIZE (45)
105 #define HASH_DIGEST_SIZE_BITS (256)
106 #define HASH_DIGEST_SIZE_BYTES (HASH_DIGEST_SIZE_BITS / 8)
107 
108 class LRUCache final {
109  public:
LRUCache()110   LRUCache() : mLock("mozilla.resistFingerprinting.LRUCache") {
111     this->cache.SetLength(LRU_CACHE_SIZE);
112   }
113 
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(LRUCache)114   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(LRUCache)
115 
116   nsCString Get(long long aKeyPart1, long long aKeyPart2) {
117     for (auto& cacheEntry : this->cache) {
118       // Read optimistically befor locking
119       if (cacheEntry.keyPart1 == aKeyPart1 &&
120           cacheEntry.keyPart2 == aKeyPart2) {
121         MutexAutoLock lock(mLock);
122 
123         // Double check after we have a lock
124         if (MOZ_UNLIKELY(cacheEntry.keyPart1 != aKeyPart1 ||
125                          cacheEntry.keyPart2 != aKeyPart2)) {
126           // Got evicted in a race
127           long long tmp_keyPart1 = cacheEntry.keyPart1;
128           long long tmp_keyPart2 = cacheEntry.keyPart2;
129           MOZ_LOG(gResistFingerprintingLog, LogLevel::Verbose,
130                   ("LRU Cache HIT-MISS with %lli != %lli and %lli != %lli",
131                    aKeyPart1, tmp_keyPart1, aKeyPart2, tmp_keyPart2));
132           return EmptyCString();
133         }
134 
135         cacheEntry.accessTime = PR_Now();
136         MOZ_LOG(gResistFingerprintingLog, LogLevel::Verbose,
137                 ("LRU Cache HIT with %lli %lli", aKeyPart1, aKeyPart2));
138         return cacheEntry.data;
139       }
140     }
141 
142     return EmptyCString();
143   }
144 
Store(long long aKeyPart1,long long aKeyPart2,const nsCString & aValue)145   void Store(long long aKeyPart1, long long aKeyPart2,
146              const nsCString& aValue) {
147     MOZ_DIAGNOSTIC_ASSERT(aValue.Length() == HASH_DIGEST_SIZE_BYTES);
148     MutexAutoLock lock(mLock);
149 
150     CacheEntry* lowestKey = &this->cache[0];
151     for (auto& cacheEntry : this->cache) {
152       if (MOZ_UNLIKELY(cacheEntry.keyPart1 == aKeyPart1 &&
153                        cacheEntry.keyPart2 == aKeyPart2)) {
154         // Another thread inserted before us, don't insert twice
155         MOZ_LOG(
156             gResistFingerprintingLog, LogLevel::Verbose,
157             ("LRU Cache DOUBLE STORE with %lli %lli", aKeyPart1, aKeyPart2));
158         return;
159       }
160       if (cacheEntry.accessTime < lowestKey->accessTime) {
161         lowestKey = &cacheEntry;
162       }
163     }
164 
165     lowestKey->keyPart1 = aKeyPart1;
166     lowestKey->keyPart2 = aKeyPart2;
167     lowestKey->data = aValue;
168     lowestKey->accessTime = PR_Now();
169     MOZ_LOG(gResistFingerprintingLog, LogLevel::Verbose,
170             ("LRU Cache STORE with %lli %lli", aKeyPart1, aKeyPart2));
171   }
172 
173  private:
174   ~LRUCache() = default;
175 
176   struct CacheEntry {
177     Atomic<long long, Relaxed> keyPart1;
178     Atomic<long long, Relaxed> keyPart2;
179     PRTime accessTime = 0;
180     nsCString data;
181 
CacheEntryLRUCache::CacheEntry182     CacheEntry() {
183       this->keyPart1 = 0xFFFFFFFFFFFFFFFF;
184       this->keyPart2 = 0xFFFFFFFFFFFFFFFF;
185       this->accessTime = 0;
186       this->data = nullptr;
187     }
CacheEntryLRUCache::CacheEntry188     CacheEntry(const CacheEntry& obj) {
189       this->keyPart1.exchange(obj.keyPart1);
190       this->keyPart2.exchange(obj.keyPart2);
191       this->accessTime = obj.accessTime;
192       this->data = obj.data;
193     }
194   };
195 
196   AutoTArray<CacheEntry, LRU_CACHE_SIZE> cache;
197   mozilla::Mutex mLock;
198 };
199 
200 // We make a single LRUCache
201 static StaticRefPtr<LRUCache> sCache;
202 
203 /**
204  * The purpose of this function is to deterministicly generate a random midpoint
205  * between a lower clamped value and an upper clamped value. Assuming a clamping
206  * resolution of 100, here is an example:
207  *
208  * |---------------------------------------|--------------------------|
209  * lower clamped value (e.g. 300)          |           upper clamped value (400)
210  *                              random midpoint (e.g. 360)
211  *
212  * If our actual timestamp (e.g. 325) is below the midpoint, we keep it clamped
213  * downwards. If it were equal to or above the midpoint (e.g. 365) we would
214  * round it upwards to the largest clamped value (in this example: 400).
215  *
216  * The question is: does time go backwards?
217  *
218  * The midpoint is deterministicly random and generated from three components:
219  * a secret seed, a per-timeline (context) 'mix-in', and a clamped time.
220  *
221  * When comparing times across different seed values: time may go backwards.
222  * For a clamped time of 300, one seed may generate a midpoint of 305 and
223  * another 395. So comparing an (actual) timestamp of 325 and 351 could see the
224  * 325 clamped up to 400 and the 351 clamped down to 300. The seed is
225  * per-process, so this case occurs when one can compare timestamps
226  * cross-process. This is uncommon (because we don't have site isolation.) The
227  * circumstances this could occur are BroadcastChannel, Storage Notification,
228  * and in theory (but not yet implemented) SharedWorker. This should be an
229  * exhaustive list (at time of comment writing!).
230  *
231  * Aside from cross-process communication, derived timestamps across different
232  * time origins may go backwards. (Specifically, derived means adding two
233  * timestamps together to get an (approximate) absolute time.)
234  * Assume a page and a worker. If one calls performance.now() in the page and
235  * then triggers a call to performance.now() in the worker, the following
236  * invariant should hold true:
237  *             page.performance.timeOrigin + page.performance.now() <
238  *                      worker.performance.timeOrigin + worker.performance.now()
239  *
240  * We break this invariant.
241  *
242  * The 'Context Mix-in' is a securely generated random seed that is unique for
243  * each timeline that starts over at zero. It is needed to ensure that the
244  * sequence of midpoints (as calculated by the secret seed and clamped time)
245  * does not repeat. In RelativeTimeline.h, we define a 'RelativeTimeline' class
246  * that can be inherited by any object that has a relative timeline. The most
247  * obvious examples are Documents and Workers. An attacker could let time go
248  * forward and observe (roughly) where the random midpoints fall. Then they
249  * create a new object, time starts back over at zero, and they know
250  * (approximately) where the random midpoints are.
251  *
252  * When the timestamp given is a non-relative timestamp (e.g. it is relative to
253  * the unix epoch) it is not possible to replay a sequence of random values.
254  * Thus, providing a zero context pointer is an indicator that the timestamp
255  * given is absolute and does not need any additional randomness.
256  *
257  * @param aClampedTimeUSec [in]  The clamped input time in microseconds.
258  * @param aResolutionUSec  [in]  The current resolution for clamping in
259  *                               microseconds.
260  * @param aMidpointOut     [out] The midpoint, in microseconds, between [0,
261  *                               aResolutionUSec].
262  * @param aContextMixin    [in]  An opaque random value for relative
263  *                               timestamps. 0 for absolute timestamps
264  * @param aSecretSeed      [in]  TESTING ONLY. When provided, the current seed
265  *                               will be replaced with this value.
266  * @return                 A nsresult indicating success of failure. If the
267  *                         function failed, nothing is written to aMidpointOut
268  */
269 
270 /* static */
RandomMidpoint(long long aClampedTimeUSec,long long aResolutionUSec,int64_t aContextMixin,long long * aMidpointOut,uint8_t * aSecretSeed)271 nsresult nsRFPService::RandomMidpoint(long long aClampedTimeUSec,
272                                       long long aResolutionUSec,
273                                       int64_t aContextMixin,
274                                       long long* aMidpointOut,
275                                       uint8_t* aSecretSeed /* = nullptr */) {
276   nsresult rv;
277   const int kSeedSize = 16;
278   const int kClampTimesPerDigest = HASH_DIGEST_SIZE_BITS / 32;
279   static uint8_t* sSecretMidpointSeed = nullptr;
280 
281   if (MOZ_UNLIKELY(!aMidpointOut)) {
282     return NS_ERROR_INVALID_ARG;
283   }
284 
285   RefPtr<LRUCache> cache;
286   {
287     StaticMutexAutoLock lock(sLock);
288     cache = sCache;
289   }
290 
291   if (!cache) {
292     return NS_ERROR_FAILURE;
293   }
294 
295   /*
296    * Below, we will call a cryptographic hash function. That's expensive. We
297    * look for ways to make it more efficient.
298    *
299    * We only need as much output from the hash function as the maximum
300    * resolution we will ever support, because we will reduce the output modulo
301    * that value. The maximum resolution we think is likely is in the low seconds
302    * value, or about 1-10 million microseconds. 2**24 is 16 million, so we only
303    * need 24 bits of output. Practically speaking though, it's way easier to
304    * work with 32 bits.
305    *
306    * So we're using 32 bits of output and throwing away the other DIGEST_SIZE -
307    * 32 (in the case of SHA-256, DIGEST_SIZE is 256.)  That's a lot of waste.
308    *
309    * Instead of throwing it away, we're going to use all of it. We can handle
310    * DIGEST_SIZE / 32 Clamped Time's per hash function - call that , so we
311    * reduce aClampedTime to a multiple of kClampTimesPerDigest (just like we
312    * reduced the real time value to aClampedTime!)
313    *
314    * Then we hash _that_ value (assuming it's not in the cache) and index into
315    * the digest result the appropriate bit offset.
316    */
317   long long reducedResolution = aResolutionUSec * kClampTimesPerDigest;
318   long long extraClampedTime =
319       (aClampedTimeUSec / reducedResolution) * reducedResolution;
320 
321   nsCString hashResult = cache->Get(extraClampedTime, aContextMixin);
322 
323   if (hashResult.Length() != HASH_DIGEST_SIZE_BYTES) {  // Cache Miss =(
324     // If someone has pased in the testing-only parameter, replace our seed with
325     // it
326     if (aSecretSeed != nullptr) {
327       StaticMutexAutoLock lock(sLock);
328 
329       delete[] sSecretMidpointSeed;
330 
331       sSecretMidpointSeed = new uint8_t[kSeedSize];
332       memcpy(sSecretMidpointSeed, aSecretSeed, kSeedSize);
333     }
334 
335     // If we don't have a seed, we need to get one.
336     if (MOZ_UNLIKELY(!sSecretMidpointSeed)) {
337       nsCOMPtr<nsIRandomGenerator> randomGenerator =
338           do_GetService("@mozilla.org/security/random-generator;1", &rv);
339       if (NS_WARN_IF(NS_FAILED(rv))) {
340         return rv;
341       }
342 
343       StaticMutexAutoLock lock(sLock);
344       if (MOZ_LIKELY(!sSecretMidpointSeed)) {
345         rv = randomGenerator->GenerateRandomBytes(kSeedSize,
346                                                   &sSecretMidpointSeed);
347         if (NS_WARN_IF(NS_FAILED(rv))) {
348           return rv;
349         }
350       }
351     }
352 
353     /*
354      * Use a cryptographicly secure hash function, but do _not_ use an HMAC.
355      * Obviously we're not using this data for authentication purposes, but
356      * even still an HMAC is a perfect fit here, as we're hashing a value
357      * using a seed that never changes, and an input that does. So why not
358      * use one?
359      *
360      * Basically - we don't need to, it's two invocations of the hash function,
361      * and speed really counts here.
362      *
363      * With authentication off the table, the properties we would get by
364      * using an HMAC here would be:
365      *  - Resistence to length extension
366      *  - Resistence to collision attacks on the underlying hash function
367      *  - Resistence to chosen prefix attacks
368      *
369      * There is no threat of length extension here. Nor is there any real
370      * practical threat of collision: not only are we using a good hash
371      * function (you may mock me in 10 years if it is broken) but we don't
372      * provide the attacker much control over the input. Nor do we let them
373      * have the prefix.
374      */
375 
376     // Then hash extraClampedTime and store it in the cache
377     nsCOMPtr<nsICryptoHash> hasher =
378         do_CreateInstance("@mozilla.org/security/hash;1", &rv);
379     NS_ENSURE_SUCCESS(rv, rv);
380 
381     rv = hasher->Init(nsICryptoHash::SHA256);
382     NS_ENSURE_SUCCESS(rv, rv);
383 
384     rv = hasher->Update(sSecretMidpointSeed, kSeedSize);
385     NS_ENSURE_SUCCESS(rv, rv);
386 
387     rv = hasher->Update((const uint8_t*)&aContextMixin, sizeof(aContextMixin));
388     NS_ENSURE_SUCCESS(rv, rv);
389 
390     rv = hasher->Update((const uint8_t*)&extraClampedTime,
391                         sizeof(extraClampedTime));
392     NS_ENSURE_SUCCESS(rv, rv);
393 
394     nsAutoCStringN<HASH_DIGEST_SIZE_BYTES> derivedSecret;
395     rv = hasher->Finish(false, derivedSecret);
396     NS_ENSURE_SUCCESS(rv, rv);
397 
398     // Finally, store it in the cache
399     cache->Store(extraClampedTime, aContextMixin, derivedSecret);
400     hashResult = derivedSecret;
401   }
402 
403   // Offset the appropriate index into the hash output, and then turn it into a
404   // random midpoint between 0 and aResolutionUSec. Sometimes out input time is
405   // negative, we ride the negative out to the end until we start doing pointer
406   // math. (We also triple check we're in bounds.)
407   int byteOffset =
408       abs(((aClampedTimeUSec - extraClampedTime) / aResolutionUSec) * 4);
409   if (MOZ_UNLIKELY(byteOffset > (HASH_DIGEST_SIZE_BYTES - 4))) {
410     byteOffset = 0;
411   }
412   uint32_t deterministiclyRandomValue = *BitwiseCast<uint32_t*>(
413       PromiseFlatCString(hashResult).get() + byteOffset);
414   deterministiclyRandomValue %= aResolutionUSec;
415   *aMidpointOut = deterministiclyRandomValue;
416 
417   return NS_OK;
418 }
419 
420 /**
421  * Given a precision value, this function will reduce a given input time to the
422  * nearest multiple of that precision.
423  *
424  * It will check if it is appropriate to clamp the input time according to the
425  * values of the given TimerPrecisionType.  Note that if one desires a minimum
426  * precision for Resist Fingerprinting, it is the caller's responsibility to
427  * provide the correct value. This means you should pass TimerResolution(),
428  * which enforces a minimum value on the precision based on preferences.
429  *
430  * It ensures the given precision value is greater than zero, if it is not it
431  * returns the input time.
432  *
433  * While the correct thing to pass is TimerResolution() we expose it as an
434  * argument for testing purposes only.
435  *
436  * @param aTime           [in] The input time to be clamped.
437  * @param aTimeScale      [in] The units the input time is in (Seconds,
438  *                             Milliseconds, or Microseconds).
439  * @param aResolutionUSec [in] The precision (in microseconds) to clamp to.
440  * @param aContextMixin   [in] An opaque random value for relative timestamps.
441  *                             0 for absolute timestamps
442  * @return                 If clamping is appropriate, the clamped value of the
443  *                         input, otherwise the input.
444  */
445 /* static */
ReduceTimePrecisionImpl(double aTime,TimeScale aTimeScale,double aResolutionUSec,int64_t aContextMixin,TimerPrecisionType aType)446 double nsRFPService::ReduceTimePrecisionImpl(double aTime, TimeScale aTimeScale,
447                                              double aResolutionUSec,
448                                              int64_t aContextMixin,
449                                              TimerPrecisionType aType) {
450   if (aType == TimerPrecisionType::DangerouslyNone) {
451     return aTime;
452   }
453 
454   // This boolean will serve as a flag indicating we are clamping the time
455   // unconditionally. We do this when timer reduction preference is off; but we
456   // still want to apply 20us clamping to al timestamps to avoid leaking
457   // nano-second precision.
458   bool unconditionalClamping = false;
459   if (aType == UnconditionalAKAHighRes || aResolutionUSec <= 0) {
460     unconditionalClamping = true;
461     aResolutionUSec = RFP_TIMER_UNCONDITIONAL_VALUE;  // 20 microseconds
462     aContextMixin = 0;  // Just clarifies our logging statement at the end,
463                         // otherwise unused
464   }
465 
466   // Increase the time as needed until it is in microseconds.
467   // Note that a double can hold up to 2**53 with integer precision. This gives
468   // us only until June 5, 2255 in time-since-the-epoch with integer precision.
469   // So we will be losing microseconds precision after that date.
470   // We think this is okay, and we codify it in some tests.
471   double timeScaled = aTime * (1000000 / aTimeScale);
472   // Cut off anything less than a microsecond.
473   long long timeAsInt = timeScaled;
474 
475   // If we have a blank context mixin, this indicates we (should) have an
476   // absolute timestamp. We check the time, and if it less than a unix timestamp
477   // about 10 years in the past, we output to the log and, in debug builds,
478   // assert. This is an error case we want to understand and fix: we must have
479   // given a relative timestamp with a mixin of 0 which is incorrect. Anyone
480   // running a debug build _probably_ has an accurate clock, and if they don't,
481   // they'll hopefully find this message and understand why things are crashing.
482   const long long kFeb282008 = 1204233985000;
483   if (aContextMixin == 0 && timeAsInt < kFeb282008 && !unconditionalClamping &&
484       aType != TimerPrecisionType::RFP) {
485     nsAutoCString type;
486     TypeToText(aType, type);
487     MOZ_LOG(
488         gResistFingerprintingLog, LogLevel::Error,
489         ("About to assert. aTime=%lli<%lli aContextMixin=%" PRId64 " aType=%s",
490          timeAsInt, kFeb282008, aContextMixin, type.get()));
491     MOZ_ASSERT(
492         false,
493         "ReduceTimePrecisionImpl was given a relative time "
494         "with an empty context mix-in (or your clock is 10+ years off.) "
495         "Run this with MOZ_LOG=nsResistFingerprinting:1 to get more details.");
496   }
497 
498   // Cast the resolution (in microseconds) to an int.
499   long long resolutionAsInt = aResolutionUSec;
500   // Perform the clamping.
501   // We do a cast back to double to perform the division with doubles, then
502   // floor the result and the rest occurs with integer precision. This is
503   // because it gives consistency above and below zero. Above zero, performing
504   // the division in integers truncates decimals, taking the result closer to
505   // zero (a floor). Below zero, performing the division in integers truncates
506   // decimals, taking the result closer to zero (a ceil). The impact of this is
507   // that comparing two clamped values that should be related by a constant
508   // (e.g. 10s) that are across the zero barrier will no longer work. We need to
509   // round consistently towards positive infinity or negative infinity (we chose
510   // negative.) This can't be done with a truncation, it must be done with
511   // floor.
512   long long clamped =
513       floor(double(timeAsInt) / resolutionAsInt) * resolutionAsInt;
514 
515   long long midpoint = 0;
516   long long clampedAndJittered = clamped;
517   if (!unconditionalClamping &&
518       StaticPrefs::privacy_resistFingerprinting_reduceTimerPrecision_jitter()) {
519     if (!NS_FAILED(RandomMidpoint(clamped, resolutionAsInt, aContextMixin,
520                                   &midpoint)) &&
521         timeAsInt >= clamped + midpoint) {
522       clampedAndJittered += resolutionAsInt;
523     }
524   }
525 
526   // Cast it back to a double and reduce it to the correct units.
527   double ret = double(clampedAndJittered) / (1000000.0 / aTimeScale);
528 
529   MOZ_LOG(
530       gResistFingerprintingLog, LogLevel::Verbose,
531       ("Given: (%.*f, Scaled: %.*f, Converted: %lli), Rounding %s with (%lli, "
532        "Originally %.*f), "
533        "Intermediate: (%lli), Clamped: (%lli) Jitter: (%i Context: %" PRId64
534        " Midpoint: %lli) "
535        "Final: (%lli Converted: %.*f)",
536        DBL_DIG - 1, aTime, DBL_DIG - 1, timeScaled, timeAsInt,
537        (unconditionalClamping ? "unconditionally" : "normally"),
538        resolutionAsInt, DBL_DIG - 1, aResolutionUSec,
539        (long long)floor(double(timeAsInt) / resolutionAsInt), clamped,
540        StaticPrefs::privacy_resistFingerprinting_reduceTimerPrecision_jitter(),
541        aContextMixin, midpoint, clampedAndJittered, DBL_DIG - 1, ret));
542 
543   return ret;
544 }
545 
546 /* static */
ReduceTimePrecisionAsUSecs(double aTime,int64_t aContextMixin,bool aIsSystemPrincipal,bool aCrossOriginIsolated)547 double nsRFPService::ReduceTimePrecisionAsUSecs(double aTime,
548                                                 int64_t aContextMixin,
549                                                 bool aIsSystemPrincipal,
550                                                 bool aCrossOriginIsolated) {
551   const auto type =
552       GetTimerPrecisionType(aIsSystemPrincipal, aCrossOriginIsolated);
553   return nsRFPService::ReduceTimePrecisionImpl(
554       aTime, MicroSeconds, TimerResolution(), aContextMixin, type);
555 }
556 
557 /* static */
ReduceTimePrecisionAsMSecs(double aTime,int64_t aContextMixin,bool aIsSystemPrincipal,bool aCrossOriginIsolated)558 double nsRFPService::ReduceTimePrecisionAsMSecs(double aTime,
559                                                 int64_t aContextMixin,
560                                                 bool aIsSystemPrincipal,
561                                                 bool aCrossOriginIsolated) {
562   const auto type =
563       GetTimerPrecisionType(aIsSystemPrincipal, aCrossOriginIsolated);
564   return nsRFPService::ReduceTimePrecisionImpl(
565       aTime, MilliSeconds, TimerResolution(), aContextMixin, type);
566 }
567 
568 /* static */
ReduceTimePrecisionAsMSecsRFPOnly(double aTime,int64_t aContextMixin)569 double nsRFPService::ReduceTimePrecisionAsMSecsRFPOnly(double aTime,
570                                                        int64_t aContextMixin) {
571   return nsRFPService::ReduceTimePrecisionImpl(aTime, MilliSeconds,
572                                                TimerResolution(), aContextMixin,
573                                                GetTimerPrecisionTypeRFPOnly());
574 }
575 
576 /* static */
ReduceTimePrecisionAsSecs(double aTime,int64_t aContextMixin,bool aIsSystemPrincipal,bool aCrossOriginIsolated)577 double nsRFPService::ReduceTimePrecisionAsSecs(double aTime,
578                                                int64_t aContextMixin,
579                                                bool aIsSystemPrincipal,
580                                                bool aCrossOriginIsolated) {
581   const auto type =
582       GetTimerPrecisionType(aIsSystemPrincipal, aCrossOriginIsolated);
583   return nsRFPService::ReduceTimePrecisionImpl(
584       aTime, Seconds, TimerResolution(), aContextMixin, type);
585 }
586 
587 /* static */
ReduceTimePrecisionAsSecsRFPOnly(double aTime,int64_t aContextMixin)588 double nsRFPService::ReduceTimePrecisionAsSecsRFPOnly(double aTime,
589                                                       int64_t aContextMixin) {
590   return nsRFPService::ReduceTimePrecisionImpl(aTime, Seconds,
591                                                TimerResolution(), aContextMixin,
592                                                GetTimerPrecisionTypeRFPOnly());
593 }
594 
595 /* static */
ReduceTimePrecisionAsUSecsWrapper(double aTime,JSContext * aCx)596 double nsRFPService::ReduceTimePrecisionAsUSecsWrapper(double aTime,
597                                                        JSContext* aCx) {
598   MOZ_ASSERT(aCx);
599 
600   nsCOMPtr<nsIGlobalObject> global = xpc::CurrentNativeGlobal(aCx);
601   MOZ_ASSERT(global);
602   const auto type = GetTimerPrecisionType(/* aIsSystemPrincipal */ false,
603                                           global->CrossOriginIsolated());
604   return nsRFPService::ReduceTimePrecisionImpl(
605       aTime, MicroSeconds, TimerResolution(),
606       0, /* For absolute timestamps (all the JS engine does), supply zero
607             context mixin */
608       type);
609 }
610 
611 /* static */
CalculateTargetVideoResolution(uint32_t aVideoQuality)612 uint32_t nsRFPService::CalculateTargetVideoResolution(uint32_t aVideoQuality) {
613   return aVideoQuality * NSToIntCeil(aVideoQuality * 16 / 9.0);
614 }
615 
616 /* static */
GetSpoofedTotalFrames(double aTime)617 uint32_t nsRFPService::GetSpoofedTotalFrames(double aTime) {
618   double precision = TimerResolution() / 1000 / 1000;
619   double time = floor(aTime / precision) * precision;
620 
621   return NSToIntFloor(time * kVideoFramesPerSec);
622 }
623 
624 /* static */
GetSpoofedDroppedFrames(double aTime,uint32_t aWidth,uint32_t aHeight)625 uint32_t nsRFPService::GetSpoofedDroppedFrames(double aTime, uint32_t aWidth,
626                                                uint32_t aHeight) {
627   uint32_t targetRes = CalculateTargetVideoResolution(
628       StaticPrefs::privacy_resistFingerprinting_target_video_res());
629 
630   // The video resolution is less than or equal to the target resolution, we
631   // report a zero dropped rate for this case.
632   if (targetRes >= aWidth * aHeight) {
633     return 0;
634   }
635 
636   double precision = TimerResolution() / 1000 / 1000;
637   double time = floor(aTime / precision) * precision;
638   // Bound the dropped ratio from 0 to 100.
639   uint32_t boundedDroppedRatio = std::min(kVideoDroppedRatio, 100U);
640 
641   return NSToIntFloor(time * kVideoFramesPerSec *
642                       (boundedDroppedRatio / 100.0));
643 }
644 
645 /* static */
GetSpoofedPresentedFrames(double aTime,uint32_t aWidth,uint32_t aHeight)646 uint32_t nsRFPService::GetSpoofedPresentedFrames(double aTime, uint32_t aWidth,
647                                                  uint32_t aHeight) {
648   uint32_t targetRes = CalculateTargetVideoResolution(
649       StaticPrefs::privacy_resistFingerprinting_target_video_res());
650 
651   // The target resolution is greater than the current resolution. For this
652   // case, there will be no dropped frames, so we report total frames directly.
653   if (targetRes >= aWidth * aHeight) {
654     return GetSpoofedTotalFrames(aTime);
655   }
656 
657   double precision = TimerResolution() / 1000 / 1000;
658   double time = floor(aTime / precision) * precision;
659   // Bound the dropped ratio from 0 to 100.
660   uint32_t boundedDroppedRatio = std::min(kVideoDroppedRatio, 100U);
661 
662   return NSToIntFloor(time * kVideoFramesPerSec *
663                       ((100 - boundedDroppedRatio) / 100.0));
664 }
665 
GetSpoofedVersion()666 static uint32_t GetSpoofedVersion() {
667   // If we can't get the current Firefox version, use a hard-coded ESR version.
668   const uint32_t kKnownEsrVersion = 78;
669 
670   nsresult rv;
671   nsCOMPtr<nsIXULAppInfo> appInfo =
672       do_GetService("@mozilla.org/xre/app-info;1", &rv);
673   NS_ENSURE_SUCCESS(rv, kKnownEsrVersion);
674 
675   nsAutoCString appVersion;
676   rv = appInfo->GetVersion(appVersion);
677   NS_ENSURE_SUCCESS(rv, kKnownEsrVersion);
678 
679   // The browser version will be spoofed as the last ESR version.
680   // By doing so, the anonymity group will cover more versions instead of one
681   // version.
682   uint32_t firefoxVersion = appVersion.ToInteger(&rv);
683   NS_ENSURE_SUCCESS(rv, kKnownEsrVersion);
684 
685   // Some add-on tests set the Firefox version to low numbers like 1 or 42,
686   // which causes the spoofed version calculation's unsigned int subtraction
687   // below to wrap around zero to Firefox versions like 4294967287. This
688   // function should always return an ESR version, so return a good one now.
689   if (firefoxVersion < kKnownEsrVersion) {
690     return kKnownEsrVersion;
691   }
692 
693 #ifdef DEBUG
694   // If we are running in Firefox ESR, determine whether the formula of ESR
695   // version has changed.  Once changed, we must update the formula in this
696   // function.
697   if (!strcmp(MOZ_STRINGIFY(MOZ_UPDATE_CHANNEL), "esr")) {
698     MOZ_ASSERT(((firefoxVersion - kKnownEsrVersion) % 13) == 0,
699                "Please update ESR version formula in nsRFPService.cpp");
700   }
701 #endif  // DEBUG
702 
703   // Starting with Firefox 78, a new ESR version will be released every June.
704   // We can't accurately calculate the next ESR version, but it will be
705   // probably be every ~13 Firefox releases, assuming four-week release
706   // cycles. If this assumption is wrong, we won't need to worry about it
707   // until ESR 104±1 in 2022. :) We have a debug assert above to catch if the
708   // spoofed version doesn't match the actual ESR version then.
709   // We infer the last and closest ESR version based on this rule.
710   uint32_t spoofedVersion =
711       firefoxVersion - ((firefoxVersion - kKnownEsrVersion) % 13);
712 
713   MOZ_ASSERT(spoofedVersion >= kKnownEsrVersion &&
714              spoofedVersion <= firefoxVersion &&
715              (spoofedVersion - kKnownEsrVersion) % 13 == 0);
716 
717   return spoofedVersion;
718 }
719 
720 /* static */
GetSpoofedUserAgent(nsACString & userAgent,bool isForHTTPHeader)721 void nsRFPService::GetSpoofedUserAgent(nsACString& userAgent,
722                                        bool isForHTTPHeader) {
723   // This function generates the spoofed value of User Agent.
724   // We spoof the values of the platform and Firefox version, which could be
725   // used as fingerprinting sources to identify individuals.
726   // Reference of the format of User Agent:
727   // https://developer.mozilla.org/en-US/docs/Web/API/NavigatorID/userAgent
728   // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/User-Agent
729 
730   uint32_t spoofedVersion = GetSpoofedVersion();
731   const char* spoofedOS = isForHTTPHeader ? SPOOFED_HTTP_UA_OS : SPOOFED_UA_OS;
732   userAgent.Assign(nsPrintfCString(
733       "Mozilla/5.0 (%s; rv:%d.0) Gecko/%s Firefox/%d.0", spoofedOS,
734       spoofedVersion, LEGACY_UA_GECKO_TRAIL, spoofedVersion));
735 }
736 
737 static const char* gCallbackPrefs[] = {
738     RESIST_FINGERPRINTING_PREF,   RFP_TIMER_PREF,
739     RFP_TIMER_UNCONDITIONAL_PREF, RFP_TIMER_VALUE_PREF,
740     RFP_JITTER_VALUE_PREF,        nullptr,
741 };
742 
Init()743 nsresult nsRFPService::Init() {
744   MOZ_ASSERT(NS_IsMainThread());
745 
746   nsresult rv;
747 
748   nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
749   NS_ENSURE_TRUE(obs, NS_ERROR_NOT_AVAILABLE);
750 
751   rv = obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
752   NS_ENSURE_SUCCESS(rv, rv);
753 
754 #if defined(XP_WIN)
755   rv = obs->AddObserver(this, PROFILE_INITIALIZED_TOPIC, false);
756   NS_ENSURE_SUCCESS(rv, rv);
757 #endif
758 
759   Preferences::RegisterCallbacks(nsRFPService::PrefChanged, gCallbackPrefs,
760                                  this);
761 
762   // We backup the original TZ value here.
763   const char* tzValue = PR_GetEnv("TZ");
764   if (tzValue != nullptr) {
765     mInitialTZValue = nsCString(tzValue);
766   }
767 
768   // Call Update here to cache the values of the prefs and set the timezone.
769   UpdateRFPPref();
770 
771   // Create the LRU Cache when we initialize, to avoid accidently trying to
772   // create it (and call ClearOnShutdown) on a non-main-thread
773   if (sCache == nullptr) {
774     sCache = new LRUCache();
775   }
776 
777   return rv;
778 }
779 
780 // This function updates only timing-related fingerprinting items
UpdateTimers()781 void nsRFPService::UpdateTimers() {
782   MOZ_ASSERT(NS_IsMainThread());
783 
784   if (StaticPrefs::privacy_resistFingerprinting() ||
785       StaticPrefs::privacy_reduceTimerPrecision()) {
786     JS::SetTimeResolutionUsec(
787         TimerResolution(),
788         StaticPrefs::
789             privacy_resistFingerprinting_reduceTimerPrecision_jitter());
790     JS::SetReduceMicrosecondTimePrecisionCallback(
791         nsRFPService::ReduceTimePrecisionAsUSecsWrapper);
792   } else if (StaticPrefs::privacy_reduceTimerPrecision_unconditional()) {
793     JS::SetTimeResolutionUsec(RFP_TIMER_UNCONDITIONAL_VALUE, false);
794     JS::SetReduceMicrosecondTimePrecisionCallback(
795         nsRFPService::ReduceTimePrecisionAsUSecsWrapper);
796   } else if (sInitialized) {
797     JS::SetTimeResolutionUsec(0, false);
798   }
799 }
800 
801 // This function updates every fingerprinting item necessary except
802 // timing-related
UpdateRFPPref()803 void nsRFPService::UpdateRFPPref() {
804   MOZ_ASSERT(NS_IsMainThread());
805 
806   UpdateTimers();
807 
808   bool privacyResistFingerprinting =
809       StaticPrefs::privacy_resistFingerprinting();
810   if (privacyResistFingerprinting) {
811     PR_SetEnv("TZ=UTC");
812   } else if (sInitialized) {
813     // We will not touch the TZ value if 'privacy.resistFingerprinting' is false
814     // during the time of initialization.
815     if (!mInitialTZValue.IsEmpty()) {
816       nsAutoCString tzValue = NS_LITERAL_CSTRING("TZ=") + mInitialTZValue;
817       static char* tz = nullptr;
818 
819       // If the tz has been set before, we free it first since it will be
820       // allocated a new value later.
821       if (tz != nullptr) {
822         free(tz);
823       }
824       // PR_SetEnv() needs the input string been leaked intentionally, so
825       // we copy it here.
826       tz = ToNewCString(tzValue, mozilla::fallible);
827       if (tz != nullptr) {
828         PR_SetEnv(tz);
829       }
830     } else {
831 #if defined(XP_WIN)
832       // For Windows, we reset the TZ to an empty string. This will make Windows
833       // to use its system timezone.
834       PR_SetEnv("TZ=");
835 #else
836       // For POSIX like system, we reset the TZ to the /etc/localtime, which is
837       // the system timezone.
838       PR_SetEnv("TZ=:/etc/localtime");
839 #endif
840     }
841   }
842 
843   // If and only if the time zone was changed above, propagate the change to the
844   // <time.h> functions and the JS runtime.
845   if (privacyResistFingerprinting || sInitialized) {
846     // localtime_r (and other functions) may not call tzset, so do this here
847     // after changing TZ to ensure all <time.h> functions use the new time zone.
848 #if defined(XP_WIN)
849     _tzset();
850 #else
851     tzset();
852 #endif
853 
854     nsJSUtils::ResetTimeZone();
855   }
856 }
857 
StartShutdown()858 void nsRFPService::StartShutdown() {
859   MOZ_ASSERT(NS_IsMainThread());
860 
861   nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
862 
863   StaticMutexAutoLock lock(sLock);
864   { sCache = nullptr; }
865 
866   if (obs) {
867     obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
868   }
869   Preferences::UnregisterCallbacks(nsRFPService::PrefChanged, gCallbackPrefs,
870                                    this);
871 }
872 
873 /* static */
MaybeCreateSpoofingKeyCodes(const KeyboardLangs aLang,const KeyboardRegions aRegion)874 void nsRFPService::MaybeCreateSpoofingKeyCodes(const KeyboardLangs aLang,
875                                                const KeyboardRegions aRegion) {
876   if (sSpoofingKeyboardCodes == nullptr) {
877     sSpoofingKeyboardCodes =
878         new nsDataHashtable<KeyboardHashKey, const SpoofingKeyboardCode*>();
879   }
880 
881   if (KeyboardLang::EN == aLang) {
882     switch (aRegion) {
883       case KeyboardRegion::US:
884         MaybeCreateSpoofingKeyCodesForEnUS();
885         break;
886     }
887   }
888 }
889 
890 /* static */
MaybeCreateSpoofingKeyCodesForEnUS()891 void nsRFPService::MaybeCreateSpoofingKeyCodesForEnUS() {
892   MOZ_ASSERT(sSpoofingKeyboardCodes);
893 
894   static bool sInitialized = false;
895   const KeyboardLangs lang = KeyboardLang::EN;
896   const KeyboardRegions reg = KeyboardRegion::US;
897 
898   if (sInitialized) {
899     return;
900   }
901 
902   static const SpoofingKeyboardInfo spoofingKeyboardInfoTable[] = {
903 #define KEY(key_, _codeNameIdx, _keyCode, _modifier) \
904   {KEY_NAME_INDEX_USE_STRING,                        \
905    NS_LITERAL_STRING(key_),                          \
906    {CODE_NAME_INDEX_##_codeNameIdx, _keyCode, _modifier}},
907 #define CONTROL(keyNameIdx_, _codeNameIdx, _keyCode) \
908   {KEY_NAME_INDEX_##keyNameIdx_,                     \
909    EmptyString(),                                    \
910    {CODE_NAME_INDEX_##_codeNameIdx, _keyCode, MODIFIER_NONE}},
911 #include "KeyCodeConsensus_En_US.h"
912 #undef CONTROL
913 #undef KEY
914   };
915 
916   for (const auto& keyboardInfo : spoofingKeyboardInfoTable) {
917     KeyboardHashKey key(lang, reg, keyboardInfo.mKeyIdx, keyboardInfo.mKey);
918     MOZ_ASSERT(!sSpoofingKeyboardCodes->Lookup(key),
919                "Double-defining key code; fix your KeyCodeConsensus file");
920     sSpoofingKeyboardCodes->Put(key, &keyboardInfo.mSpoofingCode);
921   }
922 
923   sInitialized = true;
924 }
925 
926 /* static */
GetKeyboardLangAndRegion(const nsAString & aLanguage,KeyboardLangs & aLocale,KeyboardRegions & aRegion)927 void nsRFPService::GetKeyboardLangAndRegion(const nsAString& aLanguage,
928                                             KeyboardLangs& aLocale,
929                                             KeyboardRegions& aRegion) {
930   nsAutoString langStr;
931   nsAutoString regionStr;
932   uint32_t partNum = 0;
933 
934   for (const nsAString& part : aLanguage.Split('-')) {
935     if (partNum == 0) {
936       langStr = part;
937     } else {
938       regionStr = part;
939       break;
940     }
941 
942     partNum++;
943   }
944 
945   // We test each language here as well as the region. There are some cases that
946   // only the language is given, we will use the default region code when this
947   // happens. The default region should depend on the given language.
948   if (langStr.EqualsLiteral(RFP_KEYBOARD_LANG_STRING_EN)) {
949     aLocale = KeyboardLang::EN;
950     // Give default values first.
951     aRegion = KeyboardRegion::US;
952 
953     if (regionStr.EqualsLiteral(RFP_KEYBOARD_REGION_STRING_US)) {
954       aRegion = KeyboardRegion::US;
955     }
956   } else {
957     // There is no spoofed keyboard locale for the given language. We use the
958     // default one in this case.
959     aLocale = RFP_DEFAULT_SPOOFING_KEYBOARD_LANG;
960     aRegion = RFP_DEFAULT_SPOOFING_KEYBOARD_REGION;
961   }
962 }
963 
964 /* static */
GetSpoofedKeyCodeInfo(const dom::Document * aDoc,const WidgetKeyboardEvent * aKeyboardEvent,SpoofingKeyboardCode & aOut)965 bool nsRFPService::GetSpoofedKeyCodeInfo(
966     const dom::Document* aDoc, const WidgetKeyboardEvent* aKeyboardEvent,
967     SpoofingKeyboardCode& aOut) {
968   MOZ_ASSERT(aKeyboardEvent);
969 
970   KeyboardLangs keyboardLang = RFP_DEFAULT_SPOOFING_KEYBOARD_LANG;
971   KeyboardRegions keyboardRegion = RFP_DEFAULT_SPOOFING_KEYBOARD_REGION;
972   // If the document is given, we use the content language which is get from the
973   // document. Otherwise, we use the default one.
974   if (aDoc != nullptr) {
975     nsAutoString language;
976     aDoc->GetContentLanguage(language);
977 
978     // If the content-langauge is not given, we try to get langauge from the
979     // HTML lang attribute.
980     if (language.IsEmpty()) {
981       dom::Element* elm = aDoc->GetHtmlElement();
982 
983       if (elm != nullptr) {
984         elm->GetLang(language);
985       }
986     }
987 
988     // If two or more languages are given, per HTML5 spec, we should consider
989     // it as 'unknown'. So we use the default one.
990     if (!language.IsEmpty() && !language.Contains(char16_t(','))) {
991       language.StripWhitespace();
992       GetKeyboardLangAndRegion(language, keyboardLang, keyboardRegion);
993     }
994   }
995 
996   MaybeCreateSpoofingKeyCodes(keyboardLang, keyboardRegion);
997 
998   KeyNameIndex keyIdx = aKeyboardEvent->mKeyNameIndex;
999   nsAutoString keyName;
1000 
1001   if (keyIdx == KEY_NAME_INDEX_USE_STRING) {
1002     keyName = aKeyboardEvent->mKeyValue;
1003   }
1004 
1005   KeyboardHashKey key(keyboardLang, keyboardRegion, keyIdx, keyName);
1006   const SpoofingKeyboardCode* keyboardCode = sSpoofingKeyboardCodes->Get(key);
1007 
1008   if (keyboardCode != nullptr) {
1009     aOut = *keyboardCode;
1010     return true;
1011   }
1012 
1013   return false;
1014 }
1015 
1016 /* static */
GetSpoofedModifierStates(const dom::Document * aDoc,const WidgetKeyboardEvent * aKeyboardEvent,const Modifiers aModifier,bool & aOut)1017 bool nsRFPService::GetSpoofedModifierStates(
1018     const dom::Document* aDoc, const WidgetKeyboardEvent* aKeyboardEvent,
1019     const Modifiers aModifier, bool& aOut) {
1020   MOZ_ASSERT(aKeyboardEvent);
1021 
1022   // For modifier or control keys, we don't need to hide its modifier states.
1023   if (aKeyboardEvent->mKeyNameIndex != KEY_NAME_INDEX_USE_STRING) {
1024     return false;
1025   }
1026 
1027   // We will spoof the modifer state for Alt, Shift, and AltGraph.
1028   // We don't spoof the Control key, because it is often used
1029   // for command key combinations in web apps.
1030   if ((aModifier & (MODIFIER_ALT | MODIFIER_SHIFT | MODIFIER_ALTGRAPH)) != 0) {
1031     SpoofingKeyboardCode keyCodeInfo;
1032 
1033     if (GetSpoofedKeyCodeInfo(aDoc, aKeyboardEvent, keyCodeInfo)) {
1034       aOut = ((keyCodeInfo.mModifierStates & aModifier) != 0);
1035       return true;
1036     }
1037   }
1038 
1039   return false;
1040 }
1041 
1042 /* static */
GetSpoofedCode(const dom::Document * aDoc,const WidgetKeyboardEvent * aKeyboardEvent,nsAString & aOut)1043 bool nsRFPService::GetSpoofedCode(const dom::Document* aDoc,
1044                                   const WidgetKeyboardEvent* aKeyboardEvent,
1045                                   nsAString& aOut) {
1046   MOZ_ASSERT(aKeyboardEvent);
1047 
1048   SpoofingKeyboardCode keyCodeInfo;
1049 
1050   if (!GetSpoofedKeyCodeInfo(aDoc, aKeyboardEvent, keyCodeInfo)) {
1051     return false;
1052   }
1053 
1054   WidgetKeyboardEvent::GetDOMCodeName(keyCodeInfo.mCode, aOut);
1055 
1056   // We need to change the 'Left' with 'Right' if the location indicates
1057   // it's a right key.
1058   if (aKeyboardEvent->mLocation ==
1059           dom::KeyboardEvent_Binding::DOM_KEY_LOCATION_RIGHT &&
1060       StringEndsWith(aOut, NS_LITERAL_STRING("Left"))) {
1061     aOut.ReplaceLiteral(aOut.Length() - 4, 4, u"Right");
1062   }
1063 
1064   return true;
1065 }
1066 
1067 /* static */
GetSpoofedKeyCode(const dom::Document * aDoc,const WidgetKeyboardEvent * aKeyboardEvent,uint32_t & aOut)1068 bool nsRFPService::GetSpoofedKeyCode(const dom::Document* aDoc,
1069                                      const WidgetKeyboardEvent* aKeyboardEvent,
1070                                      uint32_t& aOut) {
1071   MOZ_ASSERT(aKeyboardEvent);
1072 
1073   SpoofingKeyboardCode keyCodeInfo;
1074 
1075   if (GetSpoofedKeyCodeInfo(aDoc, aKeyboardEvent, keyCodeInfo)) {
1076     aOut = keyCodeInfo.mKeyCode;
1077     return true;
1078   }
1079 
1080   return false;
1081 }
1082 
1083 /* static */
GetTimerPrecisionType(bool aIsSystemPrincipal,bool aCrossOriginIsolated)1084 TimerPrecisionType nsRFPService::GetTimerPrecisionType(
1085     bool aIsSystemPrincipal, bool aCrossOriginIsolated) {
1086   if (aIsSystemPrincipal) {
1087     return DangerouslyNone;
1088   }
1089 
1090   if (StaticPrefs::privacy_resistFingerprinting()) {
1091     return RFP;
1092   }
1093 
1094   if (StaticPrefs::privacy_reduceTimerPrecision() && aCrossOriginIsolated) {
1095     return UnconditionalAKAHighRes;
1096   }
1097 
1098   if (StaticPrefs::privacy_reduceTimerPrecision()) {
1099     return Normal;
1100   }
1101 
1102   if (StaticPrefs::privacy_reduceTimerPrecision_unconditional()) {
1103     return UnconditionalAKAHighRes;
1104   }
1105 
1106   return DangerouslyNone;
1107 }
1108 
1109 /* static */
GetTimerPrecisionTypeRFPOnly()1110 TimerPrecisionType nsRFPService::GetTimerPrecisionTypeRFPOnly() {
1111   if (StaticPrefs::privacy_resistFingerprinting()) {
1112     return RFP;
1113   }
1114 
1115   if (StaticPrefs::privacy_reduceTimerPrecision_unconditional()) {
1116     return UnconditionalAKAHighRes;
1117   }
1118 
1119   return DangerouslyNone;
1120 }
1121 
1122 /* static */
TypeToText(TimerPrecisionType aType,nsACString & aText)1123 void nsRFPService::TypeToText(TimerPrecisionType aType, nsACString& aText) {
1124   switch (aType) {
1125     case TimerPrecisionType::DangerouslyNone:
1126       aText.AssignLiteral("DangerouslyNone");
1127       return;
1128     case TimerPrecisionType::Normal:
1129       aText.AssignLiteral("Normal");
1130       return;
1131     case TimerPrecisionType::RFP:
1132       aText.AssignLiteral("RFP");
1133       return;
1134     case TimerPrecisionType::UnconditionalAKAHighRes:
1135       aText.AssignLiteral("UnconditionalAKAHighRes");
1136       return;
1137     default:
1138       MOZ_ASSERT(false, "Shouldn't go here");
1139       aText.AssignLiteral("Unknown Enum Value");
1140       return;
1141   }
1142 }
1143 
1144 // static
PrefChanged(const char * aPref,void * aSelf)1145 void nsRFPService::PrefChanged(const char* aPref, void* aSelf) {
1146   static_cast<nsRFPService*>(aSelf)->PrefChanged(aPref);
1147 }
1148 
PrefChanged(const char * aPref)1149 void nsRFPService::PrefChanged(const char* aPref) {
1150   nsDependentCString pref(aPref);
1151 
1152   if (pref.EqualsLiteral(RFP_TIMER_PREF) ||
1153       pref.EqualsLiteral(RFP_TIMER_UNCONDITIONAL_PREF) ||
1154       pref.EqualsLiteral(RFP_TIMER_VALUE_PREF) ||
1155       pref.EqualsLiteral(RFP_JITTER_VALUE_PREF)) {
1156     UpdateTimers();
1157   } else if (pref.EqualsLiteral(RESIST_FINGERPRINTING_PREF)) {
1158     UpdateRFPPref();
1159 
1160 #if defined(XP_WIN)
1161     if (!XRE_IsE10sParentProcess()) {
1162       // Windows does not follow POSIX. Updates to the TZ environment variable
1163       // are not reflected immediately on that platform as they are on UNIX
1164       // systems without this call.
1165       _tzset();
1166     }
1167 #endif
1168   }
1169 }
1170 
1171 NS_IMETHODIMP
Observe(nsISupports * aObject,const char * aTopic,const char16_t * aMessage)1172 nsRFPService::Observe(nsISupports* aObject, const char* aTopic,
1173                       const char16_t* aMessage) {
1174   if (strcmp(NS_XPCOM_SHUTDOWN_OBSERVER_ID, aTopic) == 0) {
1175     StartShutdown();
1176   }
1177 #if defined(XP_WIN)
1178   else if (!strcmp(PROFILE_INITIALIZED_TOPIC, aTopic)) {
1179     // If we're e10s, then we don't need to run this, since the child process
1180     // will simply inherit the environment variable from the parent process, in
1181     // which case it's unnecessary to call _tzset().
1182     if (XRE_IsParentProcess() && !XRE_IsE10sParentProcess()) {
1183       // Windows does not follow POSIX. Updates to the TZ environment variable
1184       // are not reflected immediately on that platform as they are on UNIX
1185       // systems without this call.
1186       _tzset();
1187     }
1188 
1189     nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
1190     NS_ENSURE_TRUE(obs, NS_ERROR_NOT_AVAILABLE);
1191 
1192     nsresult rv = obs->RemoveObserver(this, PROFILE_INITIALIZED_TOPIC);
1193     NS_ENSURE_SUCCESS(rv, rv);
1194   }
1195 #endif
1196 
1197   return NS_OK;
1198 }
1199