Bug 820180 - Isolate JS pseudorandom number generator state per compartment. r=luke.
☠☠ backed out by e890c7296610 ☠ ☠
authorJason Orendorff <jorendorff@mozilla.com>
Fri, 14 Dec 2012 14:27:22 -0600
changeset 125227 a567cc63a3893df175c7053736eb53bddb958764
parent 125226 96b591267cb3ac21eadce063289f81734a60498c
child 125228 8541aa1783c7a3fc9f8168ef2006d1d4682aefd2
push id2151
push userlsblakk@mozilla.com
push dateTue, 19 Feb 2013 18:06:57 +0000
treeherdermozilla-beta@4952e88741ec [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersluke
bugs820180
milestone20.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 820180 - Isolate JS pseudorandom number generator state per compartment. r=luke.
js/src/jsapi.cpp
js/src/jscntxt.cpp
js/src/jscntxt.h
js/src/jscompartment.cpp
js/src/jscompartment.h
js/src/jsmath.cpp
js/src/jsmath.h
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -883,17 +883,18 @@ JSRuntime::JSRuntime(JSUseHelperThreads 
     ionTop(NULL),
     ionJSContext(NULL),
     ionStackLimit(0),
     ionActivation(NULL),
     ionPcScriptCache(NULL),
     threadPool(this),
     ionReturnOverride_(MagicValue(JS_ARG_POISON)),
     useHelperThreads_(useHelperThreads),
-    requestedHelperThreadCount(-1)
+    requestedHelperThreadCount(-1),
+    rngNonce(0)
 {
     /* Initialize infallibly first, so we can goto bad and JS_DestroyRuntime. */
     JS_INIT_CLIST(&onNewGlobalObjectWatchers);
 
     PodZero(&debugHooks);
     PodZero(&atomState);
 
 #if JS_STACK_GROWTH_DIRECTION > 0
--- a/js/src/jscntxt.cpp
+++ b/js/src/jscntxt.cpp
@@ -257,18 +257,16 @@ js::NewContext(JSRuntime *rt, size_t sta
 
     /*
      * Here the GC lock is still held after js_InitContextThreadAndLockGC took it and
      * the GC is not running on another thread.
      */
     bool first = rt->contextList.isEmpty();
     rt->contextList.insertBack(cx);
 
-    js_InitRandom(cx);
-
     /*
      * If cx is the first context on this runtime, initialize well-known atoms,
      * keywords, numbers, strings and self-hosted scripts. If one of these
      * steps should fail, the runtime will be left in a partially initialized
      * state, with zeroes and nulls stored in the default-initialized remainder
      * of the struct. We'll clean the runtime up under DestroyContext, because
      * cx will be "last" as well as "first".
      */
@@ -1109,17 +1107,16 @@ JSContext::JSContext(JSRuntime *rt)
     errorReporter(NULL),
     operationCallback(NULL),
     data(NULL),
     data2(NULL),
 #ifdef JS_THREADSAFE
     outstandingRequests(0),
 #endif
     resolveFlags(0),
-    rngSeed(0),
     iterValue(MagicValue(JS_NO_ITER_VALUE)),
 #ifdef JS_METHODJIT
     methodJitEnabled(false),
 #endif
 #ifdef MOZ_TRACE_JSCALLS
     functionCallback(NULL),
 #endif
     enumerators(NULL),
--- a/js/src/jscntxt.h
+++ b/js/src/jscntxt.h
@@ -1214,16 +1214,26 @@ struct JSRuntime : js::RuntimeFriendFiel
 #ifdef JS_THREADSAFE
         if (requestedHelperThreadCount < 0)
             return js::GetCPUCount() - 1;
         return requestedHelperThreadCount;
 #else
         return 0;
 #endif
     }
+
+  private:
+    /*
+     * Used to ensure that compartments created at the same time get different
+     * random number sequences. See js::InitRandom.
+     */
+    uint64_t rngNonce;
+
+  public:
+    uint64_t nextRNGNonce() { return rngNonce++; }
 };
 
 /* Common macros to access thread-local caches in JSRuntime. */
 #define JS_KEEP_ATOMS(rt)   (rt)->gcKeepAtoms++;
 #define JS_UNKEEP_ATOMS(rt) (rt)->gcKeepAtoms--;
 
 namespace js {
 
@@ -1562,19 +1572,16 @@ struct JSContext : js::ContextFriendFiel
     unsigned            outstandingRequests;/* number of JS_BeginRequest calls
                                                without the corresponding
                                                JS_EndRequest. */
 #endif
 
     /* Stored here to avoid passing it around as a parameter. */
     unsigned               resolveFlags;
 
-    /* Random number generator state, used by jsmath.cpp. */
-    int64_t             rngSeed;
-
     /* Location to stash the iteration value between JSOP_MOREITER and JSOP_ITERNEXT. */
     js::Value           iterValue;
 
 #ifdef JS_METHODJIT
     bool                 methodJitEnabled;
 
     js::mjit::JaegerRuntime &jaegerRuntime() { return runtime->jaegerRuntime(); }
 #endif
--- a/js/src/jscompartment.cpp
+++ b/js/src/jscompartment.cpp
@@ -73,16 +73,17 @@ JSCompartment::JSCompartment(JSRuntime *
     gcMallocAndFreeBytes(0),
     gcTriggerMallocAndFreeBytes(0),
     gcIncomingGrayPointers(NULL),
     gcLiveArrayBuffers(NULL),
     gcWeakMapList(NULL),
     gcGrayRoots(),
     gcMallocBytes(0),
     debugModeBits(rt->debugMode ? DebugFromC : 0),
+    rngState(0),
     watchpointMap(NULL),
     scriptCountsMap(NULL),
     debugScriptMap(NULL),
     debugScopes(NULL)
 #ifdef JS_ION
     , ionCompartment_(NULL)
 #endif
 {
@@ -121,16 +122,19 @@ JSCompartment::init(JSContext *cx)
     types.init(cx);
 
     if (!crossCompartmentWrappers.init())
         return false;
 
     if (!regExps.init(cx))
         return false;
 
+    if (cx)
+        InitRandom(cx->runtime, &rngState);
+
 #ifdef JSGC_GENERATIONAL
     /*
      * If we are in the middle of post-barrier verification, we need to
      * immediately begin collecting verification data on new compartments.
      */
     if (rt->gcVerifyPostData) {
         if (!gcNursery.enable())
             return false;
--- a/js/src/jscompartment.h
+++ b/js/src/jscompartment.h
@@ -458,16 +458,19 @@ struct JSCompartment : private JS::shado
 
     void freeInCompartment(size_t nbytes) {
         JS_ASSERT(gcMallocAndFreeBytes >= nbytes);
         gcMallocAndFreeBytes -= nbytes;
     }
 
     js::DtoaCache dtoaCache;
 
+    /* Random number generator state, used by jsmath.cpp. */
+    uint64_t rngState;
+
   private:
     /*
      * Weak reference to each global in this compartment that is a debuggee.
      * Each global has its own list of debuggers.
      */
     js::GlobalObjectSet              debuggees;
 
   private:
--- a/js/src/jsmath.cpp
+++ b/js/src/jsmath.cpp
@@ -536,57 +536,58 @@ js_math_pow(JSContext *cx, unsigned argc
 
     vp->setNumber(z);
     return JS_TRUE;
 }
 #if defined(_MSC_VER)
 # pragma optimize("", on)
 #endif
 
-static const int64_t RNG_MULTIPLIER = 0x5DEECE66DLL;
-static const int64_t RNG_ADDEND = 0xBLL;
-static const int64_t RNG_MASK = (1LL << 48) - 1;
+static const uint64_t RNG_MULTIPLIER = 0x5DEECE66DLL;
+static const uint64_t RNG_ADDEND = 0xBLL;
+static const uint64_t RNG_MASK = (1LL << 48) - 1;
 static const double RNG_DSCALE = double(1LL << 53);
 
 /*
  * Math.random() support, lifted from java.util.Random.java.
  */
 extern void
-random_setSeed(int64_t *rngSeed, int64_t seed)
+random_setSeed(uint64_t *rngState, uint64_t seed)
 {
-    *rngSeed = (seed ^ RNG_MULTIPLIER) & RNG_MASK;
+    *rngState = (seed ^ RNG_MULTIPLIER) & RNG_MASK;
 }
 
 void
-js_InitRandom(JSContext *cx)
+js::InitRandom(JSRuntime *rt, uint64_t *rngState)
 {
     /*
-     * Set the seed from current time. Since we have a RNG per context and we often bring
-     * up several contexts at the same time, we xor in some additional values, namely
-     * the context and its successor. We don't just use the context because it might be
-     * possible to reverse engineer the context pointer if one guesses the time right.
+     * Set the seed from current time. Since we have a RNG per compartment and
+     * we often bring up several compartments at the same time, mix in a
+     * different integer each time. This is only meant to prevent all the new
+     * compartments from getting the same sequence of pseudo-random
+     * numbers. There's no security guarantee.
      */
-    random_setSeed(&cx->rngSeed, (PRMJ_Now() / 1000) ^ int64_t(cx) ^ int64_t(cx->getNext()));
+    random_setSeed(rngState, (uint64_t(PRMJ_Now()) << 8) ^ rt->nextRNGNonce());
 }
 
 extern uint64_t
-random_next(int64_t *rngSeed, int bits)
+random_next(uint64_t *rngState, int bits)
 {
-    uint64_t nextseed = *rngSeed * RNG_MULTIPLIER;
-    nextseed += RNG_ADDEND;
-    nextseed &= RNG_MASK;
-    *rngSeed = nextseed;
-    return nextseed >> (48 - bits);
+    uint64_t nextstate = *rngState * RNG_MULTIPLIER;
+    nextstate += RNG_ADDEND;
+    nextstate &= RNG_MASK;
+    *rngState = nextstate;
+    return nextstate >> (48 - bits);
 }
 
 static inline double
 random_nextDouble(JSContext *cx)
 {
-    return double((random_next(&cx->rngSeed, 26) << 27) + random_next(&cx->rngSeed, 27)) /
-           RNG_DSCALE;
+    uint64_t *rng = &cx->compartment->rngState;
+    return double((random_next(rng, 26) << 27) + random_next(rng, 27)) / RNG_DSCALE;
 }
 
 double
 math_random_no_outparam(JSContext *cx)
 {
     /* Calculate random without memory traffic, for use in the JITs. */
     return random_nextDouble(cx);
 }
--- a/js/src/jsmath.h
+++ b/js/src/jsmath.h
@@ -40,28 +40,28 @@ class MathCache
         e.in = x;
         e.f = f;
         return (e.out = f(x));
     }
 
     size_t sizeOfIncludingThis(JSMallocSizeOfFun mallocSizeOf);
 };
 
+extern void
+InitRandom(JSRuntime *rt, uint64_t *rngState);
+
 } /* namespace js */
 
 /*
  * JS math functions.
  */
 
 extern JSObject *
 js_InitMathClass(JSContext *cx, js::HandleObject obj);
 
-extern void
-js_InitRandom(JSContext *cx);
-
 extern double
 math_random_no_outparam(JSContext *cx);
 
 extern JSBool
 js_math_random(JSContext *cx, unsigned argc, js::Value *vp);
 
 extern JSBool
 js_math_abs(JSContext *cx, unsigned argc, js::Value *vp);