Bug 1360039 - Spoof navigator.hardwareConcurrency = 2 when privacy.resistFingerprinting = true. r=qdot
authorChris Peterson <cpeterson@mozilla.com>
Tue, 02 May 2017 14:03:08 -0700
changeset 356865 a88c73f843dc199a6f584cdc2829d05369de1ce9
parent 356864 a717a61128accd351be6554bb747ef34f779c59b
child 356866 cbd3b49c0c2912567d66421bce8d106611aea598
push id31775
push userihsiao@mozilla.com
push dateMon, 08 May 2017 03:10:38 +0000
treeherdermozilla-central@22aaf8bad4df [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersqdot
bugs1360039, 1345322
milestone55.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1360039 - Spoof navigator.hardwareConcurrency = 2 when privacy.resistFingerprinting = true. r=qdot Trackers use navigator.hardwareConcurrency as yet another source of entropy to fingerprint users. The Firefox Hardware Report says 70% of Firefox users have exactly 2 cores. When the privacy.resistFingerprinting pref is set, we want to blend into the crowd so spoof navigator.hardwareConcurrency = 2 to reduce user uniqueness. This pref was added in bug 1345322 for the Tor uplift project. https://metrics.mozilla.com/firefox-hardware-report/#goto-cpu-and-memory MozReview-Commit-ID: CDWAaxjRpqe
dom/base/test/test_navigator_hardwareConcurrency.html
dom/workers/RuntimeService.cpp
dom/workers/test/test_navigator_workers_hardwareConcurrency.html
--- a/dom/base/test/test_navigator_hardwareConcurrency.html
+++ b/dom/base/test/test_navigator_hardwareConcurrency.html
@@ -1,21 +1,39 @@
 <!DOCTYPE HTML>
 <html>
 <head>
   <meta charset="utf-8">
   <title>Test for Navigator.hardwareConcurrency</title>
   <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
   <script type="application/javascript">
+  "use strict";
+
+  function resistFingerprinting(value) {
+    return SpecialPowers.pushPrefEnv({"set": [["privacy.resistFingerprinting", value]]});
+  }
 
   var x = navigator.hardwareConcurrency;
   is(typeof x, "number", "hardwareConcurrency should be a number.");
   ok(x > 0, "hardwareConcurrency should be greater than 0.");
 
+  SimpleTest.waitForExplicitFinish();
+
+  resistFingerprinting(true).then(() => {
+    const y = navigator.hardwareConcurrency;
+    ok(y === 2, "hardwareConcurrency should always be 2 when we're resisting fingerprinting.");
+
+    resistFingerprinting(false).then(() => {
+      const z = navigator.hardwareConcurrency;
+      ok(z === x, "hardwareConcurrency should be the same as before we were resisting fingerprinting.");
+      SimpleTest.finish();
+    });
+  });
+
   </script>
 </head>
 <body>
 <p id="display"></p>
 <div id="content" style="display: none">
 
 </div>
 <pre id="test">
--- a/dom/workers/RuntimeService.cpp
+++ b/dom/workers/RuntimeService.cpp
@@ -2704,16 +2704,23 @@ void
 RuntimeService::MemoryPressureAllWorkers()
 {
   BROADCAST_ALL_WORKERS(MemoryPressure, /* dummy = */ false);
 }
 
 uint32_t
 RuntimeService::ClampedHardwareConcurrency() const
 {
+  // The Firefox Hardware Report says 70% of Firefox users have exactly 2 cores.
+  // When the resistFingerprinting pref is set, we want to blend into the crowd
+  // so spoof navigator.hardwareConcurrency = 2 to reduce user uniqueness.
+  if (MOZ_UNLIKELY(nsContentUtils::ShouldResistFingerprinting())) {
+    return 2;
+  }
+
   // This needs to be atomic, because multiple workers, and even mainthread,
   // could race to initialize it at once.
   static Atomic<uint32_t> clampedHardwareConcurrency;
 
   // No need to loop here: if compareExchange fails, that just means that some
   // other worker has initialized numberOfProcessors, so we're good to go.
   if (!clampedHardwareConcurrency) {
     int32_t numberOfProcessors = PR_GetNumberOfProcessors();
--- a/dom/workers/test/test_navigator_workers_hardwareConcurrency.html
+++ b/dom/workers/test/test_navigator_workers_hardwareConcurrency.html
@@ -1,27 +1,53 @@
 <!DOCTYPE HTML>
 <html>
 <head>
   <meta charset="utf-8">
   <title>Test for Navigator.hardwareConcurrency</title>
   <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
   <script type="application/javascript">
+  "use strict";
 
   SimpleTest.waitForExplicitFinish();
-  var script = "postMessage(navigator.hardwareConcurrency)";
-  var url = URL.createObjectURL(new Blob([script]));
-  var w = new Worker(url);
-  w.onmessage = function(e) {
+
+  function getWorkerHardwareConcurrency(onmessage) {
+    var script = "postMessage(navigator.hardwareConcurrency)";
+    var url = URL.createObjectURL(new Blob([script]));
+    var w = new Worker(url);
+    w.onmessage = onmessage;
+  }
+
+  function resistFingerprinting(value) {
+    return SpecialPowers.pushPrefEnv({"set": [["privacy.resistFingerprinting", value]]});
+  }
+
+  getWorkerHardwareConcurrency(e => {
     var x = e.data;
     is(typeof x, "number", "hardwareConcurrency should be a number.");
     ok(x > 0, "hardwareConcurrency should be greater than 0.");
-    SimpleTest.finish();
-  }
+
+    resistFingerprinting(true).then(() => {
+      getWorkerHardwareConcurrency(e => {
+        const y = e.data;
+        ok(y === 2, "hardwareConcurrency should always be 2 when we're resisting fingerprinting.");
+
+        resistFingerprinting(false).then(() => {
+          getWorkerHardwareConcurrency(e => {
+            const z = e.data;
+            ok(z === x, "hardwareConcurrency should be the same as before we were resisting fingerprinting.");
+
+            SimpleTest.finish();
+          });
+        });
+      });
+    });
+  });
+
   </script>
 </head>
 <body>
 <p id="display"></p>
 <div id="content" style="display: none">
 
 </div>
 <pre id="test">