Bug 820180 - Isolate JS pseudorandom number generator state per compartment. r=luke.
authorJason Orendorff <jorendorff@mozilla.com>
Fri, 14 Dec 2012 14:27:22 -0600
changeset 118332 7702047b003fb540f0ed4a3d9d2ff545a762fe75
parent 118331 cb938ddc272e4628a84a79df0dbf5543ab0f1109
child 118333 ee16318ac67b2dd2f9095e103e0b34ae0c68a42e
push id270
push userpvanderbeken@mozilla.com
push dateThu, 06 Mar 2014 09:24:21 +0000
reviewersluke
bugs820180
milestone20.0a1
Bug 820180 - Isolate JS pseudorandom number generator state per compartment. r=luke.
js/src/assembler/jit/ExecutableAllocator.h
js/src/assembler/jit/ExecutableAllocatorWin.cpp
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/assembler/jit/ExecutableAllocator.h
+++ b/js/src/assembler/jit/ExecutableAllocator.h
@@ -262,17 +262,17 @@ public:
     void setRandomize(bool enabled) {
         allocBehavior = enabled ? AllocationCanRandomize : AllocationDeterministic;
     }
 
 private:
     static size_t pageSize;
     static size_t largeAllocSize;
 #if WTF_OS_WINDOWS
-    static int64_t rngSeed;
+    static uint64_t rngSeed;
 #endif
 
     static const size_t OVERSIZE_ALLOCATION = size_t(-1);
 
     static size_t roundUpAllocationSize(size_t request, size_t granularity)
     {
         // Something included via windows.h defines a macro with this name,
         // which causes the function below to fail to compile.
--- a/js/src/assembler/jit/ExecutableAllocatorWin.cpp
+++ b/js/src/assembler/jit/ExecutableAllocatorWin.cpp
@@ -25,22 +25,22 @@
 
 #include "ExecutableAllocator.h"
 
 #if ENABLE_ASSEMBLER && WTF_OS_WINDOWS
 
 #include "jswin.h"
 #include "prmjtime.h"
 
-extern void random_setSeed(int64_t *, int64_t);
-extern uint64_t random_next(int64_t *, int);
+extern void random_setSeed(uint64_t *, uint64_t);
+extern uint64_t random_next(uint64_t *, int);
 
 namespace JSC {
 
-int64_t ExecutableAllocator::rngSeed;
+uint64_t ExecutableAllocator::rngSeed;
 
 void ExecutableAllocator::initSeed()
 {
     random_setSeed(&rngSeed, (PRMJ_Now() / 1000) ^ int64_t(this));
 }
 
 size_t ExecutableAllocator::determinePageSize()
 {
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -885,17 +885,18 @@ JSRuntime::JSRuntime(JSUseHelperThreads 
     ionJSContext(NULL),
     ionStackLimit(0),
     ionActivation(NULL),
     ionPcScriptCache(NULL),
     threadPool(this),
     ctypesActivityCallback(NULL),
     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
@@ -260,18 +260,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".
      */
@@ -1112,17 +1110,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
@@ -1215,16 +1215,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 {
 
@@ -1578,19 +1588,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
@@ -75,16 +75,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
 {
@@ -123,16 +124,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
@@ -465,16 +465,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
@@ -512,57 +512,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);