Bug 1330890 - Part 1: Spoofing the time zone as UTC when fingerprinting resistance is enabled (adopt from Tor #16622). r?ehsan,arthuredelstein draft
authorTim Huang <tihuang@mozilla.com>
Tue, 02 May 2017 15:21:04 +0800
changeset 573639 e7216934b1ea9955a783655d60e9732a4e07c38f
parent 572425 4a6a71f4aa22e4dc3961884ce505ce34bdd799a2
child 573640 5793c801c24bf6bc44993d1ff4d8ad5e68a29383
push id57442
push userbmo:tihuang@mozilla.com
push dateSat, 06 May 2017 00:25:33 +0000
reviewersehsan, arthuredelstein
bugs1330890, 16622
milestone55.0a1
Bug 1330890 - Part 1: Spoofing the time zone as UTC when fingerprinting resistance is enabled (adopt from Tor #16622). r?ehsan,arthuredelstein This patch adds a new service for fingerprinting resistance, which is called nsRFPService. This service will be put in /toolkit/components/resistfingerprinting. This service is responsible for observing the change of pref 'privacy.resistfingerprinting' and doing underlying jobs. And it also in charge of caching pref setting of 'privacy.resistfingerprinting' and changing environment value 'TZ'. This service will be initialized within nsContentUtils::Init(). During initialization, it will store the original TZ value and set the value according to 'privacy.resistfingerprinting'. It also changes environment value 'TZ' and calls nsJSUtils::ResetTimeZone() in response to the change of the pref. This service is only a nsIObserver for now. In the future, however, it will be responsible for more fingerprinting resistance jobs, like changing prefs after 'privacy.resistfingerprinting' is changed. The environment variable 'TZ' will be set to 'UTC' when 'privacy.resistFingerprinting' is true. By doing so, Firefox will use UTC as its local time zone instead of the default local time zone. This prevents a browser be fingerprinted through the local time zone. After the 'privacy.resistFingerprinting' is turned off, the service will restore 'TZ' back to the original TZ setting, the user's setting or the default system timezone. MozReview-Commit-ID: 8V47ZATgrKE
dom/base/nsContentUtils.cpp
dom/base/nsContentUtils.h
toolkit/components/moz.build
toolkit/components/resistfingerprinting/moz.build
toolkit/components/resistfingerprinting/nsRFPService.cpp
toolkit/components/resistfingerprinting/nsRFPService.h
--- a/dom/base/nsContentUtils.cpp
+++ b/dom/base/nsContentUtils.cpp
@@ -286,17 +286,16 @@ bool nsContentUtils::sTrustedFullScreenO
 bool nsContentUtils::sIsCutCopyAllowed = true;
 bool nsContentUtils::sIsFrameTimingPrefEnabled = false;
 bool nsContentUtils::sIsPerformanceTimingEnabled = false;
 bool nsContentUtils::sIsResourceTimingEnabled = false;
 bool nsContentUtils::sIsUserTimingLoggingEnabled = false;
 bool nsContentUtils::sIsExperimentalAutocompleteEnabled = false;
 bool nsContentUtils::sIsWebComponentsEnabled = false;
 bool nsContentUtils::sIsCustomElementsEnabled = false;
-bool nsContentUtils::sPrivacyResistFingerprinting = false;
 bool nsContentUtils::sSendPerformanceTimingNotifications = false;
 bool nsContentUtils::sUseActivityCursor = false;
 bool nsContentUtils::sAnimationsAPICoreEnabled = false;
 bool nsContentUtils::sAnimationsAPIElementAnimateEnabled = false;
 bool nsContentUtils::sGetBoxQuadsEnabled = false;
 bool nsContentUtils::sSkipCursorMoveForSameValueSet = false;
 bool nsContentUtils::sRequestIdleCallbackEnabled = false;
 
@@ -596,19 +595,16 @@ nsContentUtils::Init()
                                "dom.forms.autocomplete.experimental", false);
 
   Preferences::AddBoolVarCache(&sIsWebComponentsEnabled,
                                "dom.webcomponents.enabled", false);
 
   Preferences::AddBoolVarCache(&sIsCustomElementsEnabled,
                                "dom.webcomponents.customelements.enabled", false);
 
-  Preferences::AddBoolVarCache(&sPrivacyResistFingerprinting,
-                               "privacy.resistFingerprinting", false);
-
   Preferences::AddIntVarCache(&sPrivacyMaxInnerWidth,
                               "privacy.window.maxInnerWidth",
                               1000);
 
   Preferences::AddIntVarCache(&sPrivacyMaxInnerHeight,
                               "privacy.window.maxInnerHeight",
                               1000);
 
@@ -651,16 +647,18 @@ nsContentUtils::Init()
                                "dom.input.skip_cursor_move_for_same_value_set",
                                true);
 
   Preferences::AddBoolVarCache(&sRequestIdleCallbackEnabled,
                                "dom.requestIdleCallback.enabled", false);
 
   Element::InitCCCallbacks();
 
+  Unused << nsRFPService::GetOrCreate();
+
   nsCOMPtr<nsIUUIDGenerator> uuidGenerator =
     do_GetService("@mozilla.org/uuid-generator;1", &rv);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
   uuidGenerator.forget(&sUUIDGenerator);
 
   sInitialized = true;
@@ -2188,27 +2186,27 @@ nsContentUtils::IsCallerChrome()
   // If the check failed, look for UniversalXPConnect on the cx compartment.
   return xpc::IsUniversalXPConnectEnabled(GetCurrentJSContext());
 }
 
 /* static */
 bool
 nsContentUtils::ShouldResistFingerprinting()
 {
-  return sPrivacyResistFingerprinting;
+  return nsRFPService::IsResistFingerprintingEnabled();
 }
 
 bool
 nsContentUtils::ShouldResistFingerprinting(nsIDocShell* aDocShell)
 {
   if (!aDocShell) {
     return false;
   }
   bool isChrome = nsContentUtils::IsChromeDoc(aDocShell->GetDocument());
-  return !isChrome && sPrivacyResistFingerprinting;
+  return !isChrome && nsRFPService::IsResistFingerprintingEnabled();
 }
 
 /* static */
 void
 nsContentUtils::CalcRoundedWindowSizeForResistingFingerprinting(int32_t  aChromeWidth,
                                                                 int32_t  aChromeHeight,
                                                                 int32_t  aScreenWidth,
                                                                 int32_t  aScreenHeight,
--- a/dom/base/nsContentUtils.h
+++ b/dom/base/nsContentUtils.h
@@ -33,16 +33,17 @@
 #include "mozilla/dom/ScriptSettings.h"
 #include "mozilla/FloatingPoint.h"
 #include "mozilla/net/ReferrerPolicy.h"
 #include "mozilla/Logging.h"
 #include "mozilla/NotNull.h"
 #include "nsIContentPolicy.h"
 #include "nsIDocument.h"
 #include "nsPIDOMWindow.h"
+#include "nsRFPService.h"
 
 #if defined(XP_WIN)
 // Undefine LoadImage to prevent naming conflict with Windows.
 #undef LoadImage
 #endif
 
 class imgICache;
 class imgIContainer;
@@ -2142,17 +2143,17 @@ public:
   /*
    * Returns true if the browser should attempt to prevent the given caller type
    * from collecting distinctive information about the browser that could
    * be used to "fingerprint" and track the user across websites.
    */
   static bool ResistFingerprinting(mozilla::dom::CallerType aCallerType)
   {
     return aCallerType != mozilla::dom::CallerType::System &&
-           sPrivacyResistFingerprinting;
+           mozilla::nsRFPService::IsResistFingerprintingEnabled();
   }
 
   /**
    * Returns true if the browser should show busy cursor when loading page.
    */
   static bool UseActivityCursor()
   {
     return sUseActivityCursor;
@@ -3016,17 +3017,16 @@ private:
   static uint32_t sHandlingInputTimeout;
   static bool sIsPerformanceTimingEnabled;
   static bool sIsResourceTimingEnabled;
   static bool sIsUserTimingLoggingEnabled;
   static bool sIsFrameTimingPrefEnabled;
   static bool sIsExperimentalAutocompleteEnabled;
   static bool sIsWebComponentsEnabled;
   static bool sIsCustomElementsEnabled;
-  static bool sPrivacyResistFingerprinting;
   static bool sSendPerformanceTimingNotifications;
   static bool sUseActivityCursor;
   static bool sAnimationsAPICoreEnabled;
   static bool sAnimationsAPIElementAnimateEnabled;
   static bool sGetBoxQuadsEnabled;
   static bool sSkipCursorMoveForSameValueSet;
   static bool sRequestIdleCallbackEnabled;
   static uint32_t sCookiesLifetimePolicy;
--- a/toolkit/components/moz.build
+++ b/toolkit/components/moz.build
@@ -48,16 +48,17 @@ DIRS += [
     'privatebrowsing',
     'processsingleton',
     'promiseworker',
     'prompts',
     'protobuf',
     'reader',
     'remotebrowserutils',
     'reflect',
+    'resistfingerprinting',
     'securityreporter',
     'startup',
     'statusfilter',
     'telemetry',
     'thumbnails',
     'timermanager',
     'tooltiptext',
     'typeaheadfind',
new file mode 100644
--- /dev/null
+++ b/toolkit/components/resistfingerprinting/moz.build
@@ -0,0 +1,15 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+UNIFIED_SOURCES += [
+    'nsRFPService.cpp',
+]
+
+FINAL_LIBRARY = 'xul'
+
+EXPORTS += [
+    'nsRFPService.h',
+]
new file mode 100644
--- /dev/null
+++ b/toolkit/components/resistfingerprinting/nsRFPService.cpp
@@ -0,0 +1,151 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsRFPService.h"
+
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/Services.h"
+#include "mozilla/StaticPtr.h"
+
+#include "nsCOMPtr.h"
+#include "nsServiceManagerUtils.h"
+#include "nsString.h"
+
+#include "nsIObserverService.h"
+#include "nsIPrefBranch.h"
+#include "nsIPrefService.h"
+#include "nsJSUtils.h"
+
+#include "prenv.h"
+
+using namespace mozilla;
+
+#define RESIST_FINGERPRINTING_PREF "privacy.resistFingerprinting"
+
+NS_IMPL_ISUPPORTS(nsRFPService, nsIObserver)
+
+static StaticRefPtr<nsRFPService> sRFPService;
+static bool sInitialized = false;
+bool nsRFPService::sPrivacyResistFingerprinting = false;
+
+/* static */
+nsRFPService*
+nsRFPService::GetOrCreate()
+{
+  if (!sInitialized) {
+    sRFPService = new nsRFPService();
+    nsresult rv = sRFPService->Init();
+
+    if (NS_FAILED(rv)) {
+      sRFPService = nullptr;
+      return nullptr;
+    }
+
+    ClearOnShutdown(&sRFPService);
+    sInitialized = true;
+  }
+
+  return sRFPService;
+}
+
+nsresult
+nsRFPService::Init()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  nsresult rv;
+
+  nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+  NS_ENSURE_TRUE(obs, NS_ERROR_NOT_AVAILABLE);
+
+  rv = obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
+  NS_ENSURE_TRUE(prefs, NS_ERROR_NOT_AVAILABLE);
+
+  rv = prefs->AddObserver(RESIST_FINGERPRINTING_PREF, this, false);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // We backup the original TZ value here.
+  const char* tzValue = PR_GetEnv("TZ");
+  if (tzValue) {
+    mInitialTZValue = nsCString(tzValue);
+  }
+
+  // Call UpdatePref() here to cache the value of 'privacy.resistFingerprinting'
+  // and set the timezone.
+  UpdatePref();
+  return rv;
+}
+
+void
+nsRFPService::UpdatePref()
+{
+  sPrivacyResistFingerprinting = Preferences::GetBool(RESIST_FINGERPRINTING_PREF);
+
+  if (sPrivacyResistFingerprinting) {
+    PR_SetEnv("TZ=UTC");
+  } else if (sInitialized) {
+    // We will not touch the TZ value if 'privacy.resistFingerprinting' is false during
+    // the time of initialization.
+    if (!mInitialTZValue.IsEmpty()) {
+      nsAutoCString tzValue = NS_LITERAL_CSTRING("TZ=") + mInitialTZValue;
+      PR_SetEnv(tzValue.get());
+    } else {
+#if defined(XP_LINUX) || defined (XP_MACOSX)
+      // For POSIX like system, we reset the TZ to the /etc/localtime, which is the
+      // system timezone.
+      PR_SetEnv("TZ=:/etc/localtime");
+#else
+      // For Windows, we reset the TZ to an empty string. This will make Windows to use
+      // its system timezone.
+      PR_SetEnv("TZ=");
+#endif
+    }
+  }
+
+  // We don't have to call _tzset() here for Windows since the following
+  // function nsJSUtils::ResetTimeZone() will call it for us.
+  nsJSUtils::ResetTimeZone();
+}
+
+void
+nsRFPService::StartShutdown()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+
+  if (obs) {
+    obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
+
+    nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
+
+    if (prefs) {
+      prefs->RemoveObserver(RESIST_FINGERPRINTING_PREF, this);
+    }
+  }
+}
+
+NS_IMETHODIMP
+nsRFPService::Observe(nsISupports* aObject, const char* aTopic,
+                      const char16_t* aMessage)
+{
+  if (!strcmp(NS_PREFBRANCH_PREFCHANGE_TOPIC_ID, aTopic)) {
+    NS_ConvertUTF16toUTF8 pref(aMessage);
+
+    if (pref.EqualsLiteral(RESIST_FINGERPRINTING_PREF)) {
+      UpdatePref();
+    }
+  }
+
+  if (!strcmp(NS_XPCOM_SHUTDOWN_OBSERVER_ID, aTopic)) {
+    StartShutdown();
+  }
+
+  return NS_OK;
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/components/resistfingerprinting/nsRFPService.h
@@ -0,0 +1,44 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef __nsRFPService_h__
+#define __nsRFPService_h__
+
+#include "nsIObserver.h"
+
+#include "nsString.h"
+
+namespace mozilla {
+
+class nsRFPService final : public nsIObserver
+{
+public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIOBSERVER
+
+  static nsRFPService* GetOrCreate();
+  static bool IsResistFingerprintingEnabled()
+  {
+    return sPrivacyResistFingerprinting;
+  }
+
+private:
+  nsresult Init();
+
+  nsRFPService() {}
+
+  ~nsRFPService() {}
+
+  void UpdatePref();
+  void StartShutdown();
+
+  static bool sPrivacyResistFingerprinting;
+
+  nsCString mInitialTZValue;
+};
+
+} // mozilla namespace
+
+#endif /* __nsRFPService_h__ */