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