Bug 1440539 Support time jitter in the JS Shell, and expose a function to enable it r?luke draft
authorTom Ritter <tom@mozilla.com>
Fri, 02 Mar 2018 13:47:44 -0600
changeset 762703 b9e7f2535f16a0105957479c0ec497250c76c412
parent 762577 0979a1279bca516c2a7c9869ef6f14fab84199e5
push id101238
push userbmo:tom@mozilla.com
push dateFri, 02 Mar 2018 21:22:26 +0000
reviewersluke
bugs1440539
milestone60.0a1
Bug 1440539 Support time jitter in the JS Shell, and expose a function to enable it r?luke This adds jittering to the already existing logic for time clamping. It also exposes a testing function allowing those interested to enable time clamping or time clamping and jittering. Neither (clamping nor jittering) is enabled by default. MozReview-Commit-ID: JcHCEwRQPch
js/src/builtin/TestingFunctions.cpp
js/src/jsdate.cpp
--- a/js/src/builtin/TestingFunctions.cpp
+++ b/js/src/builtin/TestingFunctions.cpp
@@ -4927,16 +4927,43 @@ IsLegacyIterator(JSContext* cx, unsigned
     if (args.length() < 1)
         args.rval().setBoolean(false);
     else
         args.rval().setBoolean(IsPropertyIterator(args[0]));
     return true;
 }
 
 static bool
+SetTimeResolution(JSContext* cx, unsigned argc, Value* vp)
+{
+   CallArgs args = CallArgsFromVp(argc, vp);
+   RootedObject callee(cx, &args.callee());
+
+   if (!args.requireAtLeast(cx, "setTimeResolution", 2))
+        return false;
+
+   if (!args[0].isInt32()) {
+      ReportUsageErrorASCII(cx, callee, "First argument must be an Int32.");
+      return false;
+   }
+   int32_t resolution = args[0].toInt32();
+
+   if (!args[1].isBoolean()) {
+       ReportUsageErrorASCII(cx, callee, "Second argument must be a Boolean");
+       return false;
+   }
+   bool jitter = args[1].toBoolean();
+
+   JS::SetTimeResolutionUsec(resolution, jitter);
+
+   args.rval().setUndefined();
+   return true;
+}
+
+static bool
 EnableExpressionClosures(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     JS::ContextOptionsRef(cx).setExpressionClosures(true);
     args.rval().setUndefined();
     return true;
 }
 
@@ -5688,16 +5715,21 @@ gc::ZealModeHelpText),
     JS_FN_HELP("isLegacyIterator", IsLegacyIterator, 1, 0,
 "isLegacyIterator(value)",
 "  Returns whether the value is considered is a legacy iterator.\n"),
 
     JS_FN_HELP("getTimeZone", GetTimeZone, 0, 0,
 "getTimeZone()",
 "  Get the current time zone.\n"),
 
+    JS_FN_HELP("setTimeResolution", SetTimeResolution, 2, 0,
+"setTimeResolution(resolution, jitter)",
+"  Enables time clamping and jittering. Specify a time resolution in\n"
+"  microseconds and whether or not to jitter\n"),
+
     JS_FN_HELP("enableExpressionClosures", EnableExpressionClosures, 0, 0,
 "enableExpressionClosures()",
 "  Enables the deprecated, non-standard expression closures.\n"),
 
     JS_FN_HELP("disableExpressionClosures", DisableExpressionClosures, 0, 0,
 "disableExpressionClosures()",
 "  Disables the deprecated, non-standard expression closures.\n"),
 
old mode 100644
new mode 100755
--- a/js/src/jsdate.cpp
+++ b/js/src/jsdate.cpp
@@ -1305,18 +1305,47 @@ date_parse(JSContext* cx, unsigned argc,
 }
 
 static ClippedTime
 NowAsMillis()
 {
     double now = PRMJ_Now();
     if (sReduceMicrosecondTimePrecisionCallback)
         now = sReduceMicrosecondTimePrecisionCallback(now);
-    else if (sResolutionUsec)
-        now = floor(now / sResolutionUsec) * sResolutionUsec;
+    else if (sResolutionUsec) {
+        double clamped = floor(now / sResolutionUsec) * sResolutionUsec;
+
+        if (sJitter) {
+            // Calculate a random midpoint for jittering. In the browser, we are adversarial:
+            // Web Content may try to calculate the midpoint themselves and use that to bypass
+            // it's security. In the JS Shell, we are not adversarial, we want to jitter the
+            // time to recreate the operating environment, but we do not concern ourselves
+            // with trying to prevent an attacker from calculating the midpoint themselves.
+            // So we use a very simple, very fast CRC with a hardcoded seed.
+
+            uint64_t midpoint = *((uint64_t*)&clamped);
+            midpoint ^= 0x0F00DD1E2BAD2DED; // XOR in a 'secret'
+            // MurmurHash3 internal component from
+            //   https://searchfox.org/mozilla-central/rev/61d400da1c692453c2dc2c1cf37b616ce13dea5b/dom/canvas/MurmurHash3.cpp#85
+            midpoint ^= midpoint >> 33;
+            midpoint *= uint64_t{0xFF51AFD7ED558CCD};
+            midpoint ^= midpoint >> 33;
+            midpoint *= uint64_t{0xC4CEB9FE1A85EC53};
+            midpoint ^= midpoint >> 33;
+            midpoint %= sResolutionUsec;
+
+            if (now > clamped + midpoint) { // We're jittering up to the next step
+                now = clamped + sResolutionUsec;
+            } else { // We're staying at the clamped value
+                now = clamped;
+            }
+        } else { //No jitter, only clamping
+            now = clamped;
+        }
+    }
 
     return TimeClip(now / PRMJ_USEC_PER_MSEC);
 }
 
 bool
 js::date_now(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);