Bug 1431455 Fix a regression for ResistFingerprinting: use the larger of the reduceTimerPrecision pref and the constant 100ms r?bkelly draft
authorTom Ritter <tom@mozilla.com>
Thu, 18 Jan 2018 11:25:59 -0600
changeset 722507 01bf4623cb5ac83e847af90edef525184fe86ee0
parent 722215 77534d1286e8dd2fa04b30be6edcbdb02ac47d35
child 724406 e10ac8afe94ceec0e9b39ba761bdc7e903003aa7
push id96161
push userbmo:tom@mozilla.com
push dateFri, 19 Jan 2018 03:02:17 +0000
reviewersbkelly
bugs1431455
milestone59.0a1
Bug 1431455 Fix a regression for ResistFingerprinting: use the larger of the reduceTimerPrecision pref and the constant 100ms r?bkelly MozReview-Commit-ID: 73MpmfEKoQG
browser/components/resistfingerprinting/test/browser/browser_performanceAPI.js
browser/components/resistfingerprinting/test/mochitest/test_animation_api.html
browser/components/resistfingerprinting/test/mochitest/test_reduce_time_precision.html
toolkit/components/resistfingerprinting/nsRFPService.cpp
--- a/browser/components/resistfingerprinting/test/browser/browser_performanceAPI.js
+++ b/browser/components/resistfingerprinting/test/browser/browser_performanceAPI.js
@@ -57,22 +57,26 @@ let isRounded = (x, expectedPrecision) =
     " Rounded Vaue: " + rounded + " Fuzzy1: " + Math.abs(rounded - x + expectedPrecision) +
     " Fuzzy 2: " + Math.abs(rounded - x));
 
   return false;
 };
 
 let setupTest = async function(tab, resistFingerprinting, reduceTimerPrecision, expectedPrecision, runTests, workerCall) {
   await SpecialPowers.pushPrefEnv({"set":
-    // Run one set of tests with both true to confirm p.rP overrides p.rTP
     [["privacy.resistFingerprinting", resistFingerprinting],
      ["privacy.reduceTimerPrecision", reduceTimerPrecision],
      ["privacy.resistFingerprinting.reduceTimerPrecision.microseconds", expectedPrecision * 1000]
      ]
   });
+  // No matter what we set the precision to, if we're in ResistFingerprinting mode
+  // we use the larger of the precision pref and the constant 100ms
+  if (resistFingerprinting) {
+    expectedPrecision = expectedPrecision < 100 ? 100 : expectedPrecision;
+  }
   await ContentTask.spawn(tab.linkedBrowser, {
       list: PERFORMANCE_TIMINGS,
       precision: expectedPrecision,
       isRoundedFunc: isRounded.toString(),
       workerCall
     },
     runTests);
 };
@@ -186,17 +190,17 @@ let runWorkerTest = async function(data)
   };
 
 add_task(async function runRPTestsForWorker() {
   let tab = await BrowserTestUtils.openNewForegroundTab(
     gBrowser, TEST_PATH + "file_dummy.html");
 
   await setupTest(tab, true, true, 100, runWorkerTest, "runRPTests");
   await setupTest(tab, true, false, 13, runWorkerTest, "runRPTests");
-  await setupTest(tab, true, false, .13, runWorkerTest, "runRPTests");
+  await setupTest(tab, true, true, .13, runWorkerTest, "runRPTests");
 
   await BrowserTestUtils.removeTab(tab);
   });
 
 add_task(async function runRTPTestsForWorker() {
   let tab = await BrowserTestUtils.openNewForegroundTab(
     gBrowser, TEST_PATH + "file_dummy.html");
 
--- a/browser/components/resistfingerprinting/test/mochitest/test_animation_api.html
+++ b/browser/components/resistfingerprinting/test/mochitest/test_animation_api.html
@@ -11,47 +11,56 @@ https://bugzilla.mozilla.org/show_bug.cg
   <script type="application/javascript">
 
   /** Test for Bug 1382545 **/
   SimpleTest.waitForExplicitFinish();
 
   // Used by file_animation_api.html
   var prefName = "";
   var expectedPrecision = 0;
+  var resistFingerprinting = false;
+  var reduceTimerPrecision = false;
 
   function runTest() {
+    // No matter what we set the precision to, if we're in ResistFingerprinting mode
+    // we use the larger of the precision pref and the constant 100ms
+    if (resistFingerprinting) {
+      expectedPrecision = expectedPrecision < 100000 ? 100000 : expectedPrecision;
+    }
     window.open("file_animation_api.html");
   }
 
-  function setupTest(resistFingerprinting, reduceTimerPrecision, ep) {
+  function setupTest(rfp, rtp, ep) {
     // Set globals
     expectedPrecision = ep;
+    resistFingerprinting = rfp;
+    reduceTimerPrecision = rtp;
     prefName = "";
     prefName += resistFingerprinting ? "privacy.resistFingerprinting " : "";
     prefName += reduceTimerPrecision ? "privacy.reduceTimerPrecision " : "";
     SpecialPowers.pushPrefEnv({"set":
       [
+        ["dom.animations-api.core.enabled", true],
         ["privacy.resistFingerprinting", resistFingerprinting],
-        ["dom.animations-api.core.enabled", true],
         ["privacy.reduceTimerPrecision", reduceTimerPrecision],
         ["privacy.resistFingerprinting.reduceTimerPrecision.microseconds", expectedPrecision]
       ]
     }, runTest);
 
   }
 
   var testIndx = 0;
   var testSequence = [
     [true, false, 100000],
     [false, true, 100000],
     [true, false, 50000],
     [false, true, 50000],
     [true, false, 100],
     [false, true, 100],
-    [true, false, 13],
+    [true, true, 13],
     [false, true, 13],
   ];
 
   window.onload = () => {
     setupTest(testSequence[testIndx][0], testSequence[testIndx][1], testSequence[testIndx][2]);
   };
 
   function done() {
--- a/browser/components/resistfingerprinting/test/mochitest/test_reduce_time_precision.html
+++ b/browser/components/resistfingerprinting/test/mochitest/test_reduce_time_precision.html
@@ -83,17 +83,17 @@ https://trac.torproject.org/projects/tor
         worker.removeEventListener("message", onMessage);
 
         let timeStamps = event.data;
         for (let i = 0; i < timeStampCodes.length; i++) {
           let timeStamp = timeStamps[i];
           ok(isRounded(timeStamp, expectedPrecision),
             "pref: " + prefname + " - '" +
              "'" + timeStampCodes[i] +
-             "' should be rounded to nearest " + expectedPrecision + " us in workers; saw " +
+             "' should be rounded to nearest " + expectedPrecision + " ms in workers; saw " +
              timeStamp);
         }
         resolve();
       };
       worker.addEventListener("message", onMessage);
     });
 
     // Send the codes to its child worker.
@@ -115,16 +115,23 @@ https://trac.torproject.org/projects/tor
     // check that the resolution is updated whether or not the worker was
     // already started
     let worker1 = new Worker("worker_child.js");
     await SpecialPowers.pushPrefEnv({
       "set": [["privacy.resistFingerprinting", resistFingerprinting],
               ["privacy.reduceTimerPrecision", reduceTimerPrecision],
               ["privacy.resistFingerprinting.reduceTimerPrecision.microseconds", expectedPrecision * 1000]
               ]});
+
+    // No matter what we set the precision to, if we're in ResistFingerprinting mode
+    // we use the larger of the precision pref and the constant 100ms
+    if (resistFingerprinting) {
+      expectedPrecision = expectedPrecision < 100 ? 100 : expectedPrecision;
+    }
+
     let worker2 = new Worker("worker_child.js");
     // Allow ~550 ms to elapse, so we can get non-zero
     // time values for all elements.
     await new Promise(resolve => window.setTimeout(resolve, 550));
     await checkWorker(worker1, prefname, expectedPrecision);
     await checkWorker(worker2, prefname, expectedPrecision);
   }
 
@@ -148,31 +155,37 @@ https://trac.torproject.org/projects/tor
     prefname += reduceTimerPrecision ? "privacy.reduceTimerPrecision " : "";
 
     await SpecialPowers.pushPrefEnv({
       "set": [["privacy.resistFingerprinting", resistFingerprinting],
               ["privacy.reduceTimerPrecision", reduceTimerPrecision],
               ["privacy.resistFingerprinting.reduceTimerPrecision.microseconds", expectedPrecision * 1000]
               ]});
 
+    // No matter what we set the precision to, if we're in ResistFingerprinting mode
+    // we use the larger of the precision pref and the constant 100ms
+    if (resistFingerprinting) {
+      expectedPrecision = expectedPrecision < 100 ? 100 : expectedPrecision;
+    }
+
     // Loop through each timeStampCode, evaluate it,
     // and check if it is rounded
     for (let timeStampCode of timeStampCodesDOM) {
       let timeStamp = eval(timeStampCode);
       ok(isRounded(timeStamp, expectedPrecision),
         "pref: " + prefname + " - '" +
          "'" + timeStampCode +
          "' should be rounded to nearest " +
-         expectedPrecision + " us; saw " +
+         expectedPrecision + " ms; saw " +
          timeStamp);
     }
   }
 
   add_task(async function testDOMRFP() {
-    await testDOM(true, false, 100);
+    await testDOM(true, true, 100);
     await testDOM(true, false, 13);
     await testDOM(true, false, .13);
   });
 
   add_task(async function testDOMRTP() {
     await testDOM(false, true, 100);
     await testDOM(false, true, 13);
     await testDOM(false, true, .13);
--- a/toolkit/components/resistfingerprinting/nsRFPService.cpp
+++ b/toolkit/components/resistfingerprinting/nsRFPService.cpp
@@ -56,16 +56,17 @@ static mozilla::LazyLogModule gResistFin
 #define RFP_DEFAULT_SPOOFING_KEYBOARD_REGION KeyboardRegion::US
 
 NS_IMPL_ISUPPORTS(nsRFPService, nsIObserver)
 
 static StaticRefPtr<nsRFPService> sRFPService;
 static bool sInitialized = false;
 Atomic<bool, ReleaseAcquire> nsRFPService::sPrivacyResistFingerprinting;
 Atomic<bool, ReleaseAcquire> nsRFPService::sPrivacyTimerPrecisionReduction;
+// Note: anytime you want to use this variable, you should probably use TimerResolution() instead
 Atomic<uint32_t, ReleaseAcquire> sResolutionUSec;
 static uint32_t sVideoFramesPerSec;
 static uint32_t sVideoDroppedRatio;
 static uint32_t sTargetVideoRes;
 nsDataHashtable<KeyboardHashKey, const SpoofingKeyboardCode*>*
   nsRFPService::sSpoofingKeyboardCodes = nullptr;
 
 /* static */
@@ -83,58 +84,68 @@ nsRFPService::GetOrCreate()
 
     ClearOnShutdown(&sRFPService);
     sInitialized = true;
   }
 
   return sRFPService;
 }
 
+inline double
+TimerResolution()
+{
+  if(nsRFPService::IsResistFingerprintingEnabled()) {
+    return max(100000.0, (double)sResolutionUSec);
+  }
+  return sResolutionUSec;
+}
+
 /* static */
 bool
 nsRFPService::IsResistFingerprintingEnabled()
 {
   return sPrivacyResistFingerprinting;
 }
 
 /* static */
 bool
 nsRFPService::IsTimerPrecisionReductionEnabled()
 {
   return (sPrivacyTimerPrecisionReduction || IsResistFingerprintingEnabled()) &&
-         sResolutionUSec != 0;
+         TimerResolution() != 0;
 }
 
 /* static */
 double
 nsRFPService::ReduceTimePrecisionAsMSecs(double aTime)
 {
   if (!IsTimerPrecisionReductionEnabled()) {
     return aTime;
   }
-  const double resolutionMSec = sResolutionUSec / 1000.0;
+  const double resolutionMSec = TimerResolution() / 1000.0;
   double ret = floor(aTime / resolutionMSec) * resolutionMSec;
 #if defined(DEBUG)
   MOZ_LOG(gResistFingerprintingLog, LogLevel::Verbose,
     ("Given: %.*f, Rounding with %.*f, Intermediate: %.*f, Got: %.*f",
       DBL_DIG-1, aTime, DBL_DIG-1, resolutionMSec, DBL_DIG-1, floor(aTime / resolutionMSec), DBL_DIG-1, ret));
 #endif
   return ret;
 }
 
 /* static */
 double
 nsRFPService::ReduceTimePrecisionAsUSecs(double aTime)
 {
   if (!IsTimerPrecisionReductionEnabled()) {
     return aTime;
   }
-  double ret = floor(aTime / sResolutionUSec) * sResolutionUSec;
+  double resolutionUSec = TimerResolution();
+  double ret = floor(aTime / resolutionUSec) * resolutionUSec;
 #if defined(DEBUG)
-  double tmp_sResolutionUSec = sResolutionUSec;
+  double tmp_sResolutionUSec = resolutionUSec;
   MOZ_LOG(gResistFingerprintingLog, LogLevel::Verbose,
     ("Given: %.*f, Rounding with %.*f, Intermediate: %.*f, Got: %.*f",
       DBL_DIG-1, aTime, DBL_DIG-1, tmp_sResolutionUSec, DBL_DIG-1, floor(aTime / tmp_sResolutionUSec), DBL_DIG-1, ret));
 #endif
   return ret;
 }
 
 /* static */
@@ -146,29 +157,30 @@ nsRFPService::CalculateTargetVideoResolu
 
 /* static */
 double
 nsRFPService::ReduceTimePrecisionAsSecs(double aTime)
 {
   if (!IsTimerPrecisionReductionEnabled()) {
     return aTime;
   }
-  if (sResolutionUSec < 1000000) {
+  double resolutionUSec = TimerResolution();
+  if (TimerResolution() < 1000000) {
     // The resolution is smaller than one sec.  Use the reciprocal to avoid
     // floating point error.
-    const double resolutionSecReciprocal = 1000000.0 / sResolutionUSec;
+    const double resolutionSecReciprocal = 1000000.0 / resolutionUSec;
     double ret = floor(aTime * resolutionSecReciprocal) / resolutionSecReciprocal;
 #if defined(DEBUG)
   MOZ_LOG(gResistFingerprintingLog, LogLevel::Verbose,
     ("Given: %.*f, Reciprocal Rounding with %.*f, Intermediate: %.*f, Got: %.*f",
       DBL_DIG-1, aTime, DBL_DIG-1, resolutionSecReciprocal, DBL_DIG-1, floor(aTime * resolutionSecReciprocal), DBL_DIG-1, ret));
 #endif
     return ret;
   }
-  const double resolutionSec = sResolutionUSec / 1000000.0;
+  const double resolutionSec = resolutionUSec / 1000000.0;
   double ret = floor(aTime / resolutionSec) * resolutionSec;
 #if defined(DEBUG)
   MOZ_LOG(gResistFingerprintingLog, LogLevel::Verbose,
     ("Given: %.*f, Rounding with %.*f, Intermediate: %.*f, Got: %.*f",
       DBL_DIG-1, aTime, DBL_DIG-1, resolutionSec, DBL_DIG-1, floor(aTime / resolutionSec), DBL_DIG-1, ret));
 #endif
   return ret;
 }
@@ -333,17 +345,17 @@ nsRFPService::Init()
 }
 
 // This function updates only timing-related fingerprinting items
 void
 nsRFPService::UpdateTimers() {
   MOZ_ASSERT(NS_IsMainThread());
 
   if (sPrivacyResistFingerprinting || sPrivacyTimerPrecisionReduction) {
-    JS::SetTimeResolutionUsec(sResolutionUSec);
+    JS::SetTimeResolutionUsec(TimerResolution());
   } else if (sInitialized) {
     JS::SetTimeResolutionUsec(0);
   }
 }
 
 
 // This function updates every fingerprinting item necessary except timing-related
 void