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