Bug 700822: Randomize JIT VirtualAlloc location on win32. (r=dmandelin)
☠☠ backed out by 6ea9d4ad53a0 ☠ ☠
authorChris Leary <cdleary@mozilla.com>
Mon, 18 Jul 2011 20:39:13 -0700
changeset 80112 1bf4c1a6412b73589e353ccad16fa74e53a4a2f8
parent 80111 ecc57a1b8b897fca77c81570e58e9238e85117f5
child 80113 6784e7fe0b3b714f9fff8c6094a8a86504ad60ef
push id323
push userrcampbell@mozilla.com
push dateTue, 15 Nov 2011 21:58:36 +0000
treeherderfx-team@3ea216303184 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdmandelin
bugs700822
milestone11.0a1
Bug 700822: Randomize JIT VirtualAlloc location on win32. (r=dmandelin)
js/src/assembler/jit/ExecutableAllocator.cpp
js/src/assembler/jit/ExecutableAllocator.h
js/src/assembler/jit/ExecutableAllocatorWin.cpp
js/src/jscntxt.cpp
js/src/jscompartment.cpp
js/src/jsmath.cpp
js/src/methodjit/MethodJIT.cpp
js/src/methodjit/MethodJIT.h
--- a/js/src/assembler/jit/ExecutableAllocator.cpp
+++ b/js/src/assembler/jit/ExecutableAllocator.cpp
@@ -22,16 +22,18 @@
  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
  */
 
 #include "ExecutableAllocator.h"
 
 #if ENABLE_ASSEMBLER
 
+#include "prmjtime.h"
+
 namespace JSC {
 
 size_t ExecutableAllocator::pageSize = 0;
 size_t ExecutableAllocator::largeAllocSize = 0;
 
 ExecutablePool::~ExecutablePool()
 {
     m_allocator->releasePoolPages(this);
--- a/js/src/assembler/jit/ExecutableAllocator.h
+++ b/js/src/assembler/jit/ExecutableAllocator.h
@@ -160,37 +160,51 @@ private:
     }
     
     size_t available() const { 
         JS_ASSERT(m_end >= m_freePtr);
         return m_end - m_freePtr;
     }
 };
 
+enum AllocationBehavior
+{
+    AllocationCanRandomize,
+    AllocationDeterministic
+};
+
 class ExecutableAllocator {
     typedef void (*DestroyCallback)(void* addr, size_t size);
     enum ProtectionSetting { Writable, Executable };
     DestroyCallback destroyCallback;
 
+    void initSeed();
+
 public:
-    ExecutableAllocator() : destroyCallback(NULL)
+    explicit ExecutableAllocator(AllocationBehavior allocBehavior)
+      : destroyCallback(NULL),
+        allocBehavior(allocBehavior)
     {
         if (!pageSize) {
             pageSize = determinePageSize();
             /*
              * On Windows, VirtualAlloc effectively allocates in 64K chunks.
              * (Technically, it allocates in page chunks, but the starting
              * address is always a multiple of 64K, so each allocation uses up
              * 64K of address space.)  So a size less than that would be
              * pointless.  But it turns out that 64KB is a reasonable size for
              * all platforms.  (This assumes 4KB pages.)
              */
             largeAllocSize = pageSize * 16;
         }
 
+#if WTF_OS_WINDOWS
+        initSeed();
+#endif
+
         JS_ASSERT(m_smallPools.empty());
     }
 
     ~ExecutableAllocator()
     {
         for (size_t i = 0; i < m_smallPools.length(); i++)
             m_smallPools[i]->release(/* willDestroy = */true);
         // XXX: temporarily disabled because it fails;  see bug 654820.
@@ -227,23 +241,31 @@ public:
         if (destroyCallback)
             destroyCallback(pool->m_allocation.pages, pool->m_allocation.size);
         systemRelease(pool->m_allocation);
         m_pools.remove(m_pools.lookup(pool));   // this asserts if |pool| is not in m_pools
     }
 
     void getCodeStats(size_t& method, size_t& regexp, size_t& unused) const;
 
+
     void setDestroyCallback(DestroyCallback destroyCallback) {
         this->destroyCallback = destroyCallback;
     }
 
+    void setRandomize(bool enabled) {
+        allocBehavior = enabled ? AllocationCanRandomize : AllocationDeterministic;
+    }
+
 private:
     static size_t pageSize;
     static size_t largeAllocSize;
+#if WTF_OS_WINDOWS
+    static int64 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.
         #ifdef _MSC_VER
@@ -256,18 +278,19 @@ private:
         // Round up to next page boundary
         size_t size = request + (granularity - 1);
         size = size & ~(granularity - 1);
         JS_ASSERT(size >= request);
         return size;
     }
 
     // On OOM, this will return an Allocation where pages is NULL.
-    static ExecutablePool::Allocation systemAlloc(size_t n);
+    ExecutablePool::Allocation systemAlloc(size_t n);
     static void systemRelease(const ExecutablePool::Allocation& alloc);
+    void *computeRandomAllocationAddress();
 
     ExecutablePool* createPool(size_t n)
     {
         size_t allocSize = roundUpAllocationSize(n, pageSize);
         if (allocSize == OVERSIZE_ALLOCATION)
             return NULL;
 
         if (!m_pools.initialized() && !m_pools.init())
@@ -461,16 +484,17 @@ private:
     SmallExecPoolVector m_smallPools;
 
     // All live pools are recorded here, just for stats purposes.  These are
     // weak references;  they don't keep pools alive.  When a pool is destroyed
     // its reference is removed from m_pools.
     typedef js::HashSet<ExecutablePool *, js::DefaultHasher<ExecutablePool *>, js::SystemAllocPolicy>
             ExecPoolHashSet;
     ExecPoolHashSet m_pools;    // All pools, just for stats purposes.
+    AllocationBehavior allocBehavior;
 
     static size_t determinePageSize();
 };
 
 }
 
 #endif // ENABLE(ASSEMBLER)
 
--- a/js/src/assembler/jit/ExecutableAllocatorWin.cpp
+++ b/js/src/assembler/jit/ExecutableAllocatorWin.cpp
@@ -15,45 +15,89 @@
  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-
 #include "ExecutableAllocator.h"
 
 #if ENABLE_ASSEMBLER && WTF_OS_WINDOWS
 
 #include "jswin.h"
+#include "prmjtime.h"
+
+extern void random_setSeed(int64 *, int64);
+extern uint64 random_next(int64 *, int);
 
 namespace JSC {
 
+int64 ExecutableAllocator::rngSeed;
+
+void ExecutableAllocator::initSeed()
+{
+    random_setSeed(&rngSeed, (PRMJ_Now() / 1000) ^ int64(this));
+}
+
 size_t ExecutableAllocator::determinePageSize()
 {
     SYSTEM_INFO system_info;
     GetSystemInfo(&system_info);
     return system_info.dwPageSize;
 }
 
+void *ExecutableAllocator::computeRandomAllocationAddress()
+{
+    /*
+     * Inspiration is V8's OS::Allocate in platform-win32.cc.
+     *
+     * VirtualAlloc takes 64K chunks out of the virtual address space, so we
+     * keep 16b alignment.
+     *
+     * x86: V8 comments say that keeping addresses in the [64MiB, 1GiB) range
+     * tries to avoid system default DLL mapping space. In the end, we get 13
+     * bits of randomness in our selection.
+     * x64: [2GiB, 4TiB), with 25 bits of randomness.
+     */
+    static const uintN chunkBits = 16;
+#if WTF_CPU_X86_64
+    static const uintptr_t base = 0x0000000080000000;
+    static const uintptr_t mask = 0x000003ffffff0000;
+#elif WTF_CPU_X86
+    static const uintptr_t base = 0x04000000;
+    static const uintptr_t mask = 0x3fff0000;
+#else
+# error "Unsupported architecture"
+#endif
+    uint64 rand = random_next(&rngSeed, 32) << chunkBits;
+    return (void *) (base | rand & mask);
+}
+
 ExecutablePool::Allocation ExecutableAllocator::systemAlloc(size_t n)
 {
-    void *allocation = VirtualAlloc(0, n, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
+    void *allocation = NULL;
+    if (allocBehavior == AllocationCanRandomize) {
+        void *randomAddress = computeRandomAllocationAddress();
+        allocation = VirtualAlloc(randomAddress, n, MEM_COMMIT | MEM_RESERVE,
+                                  PAGE_EXECUTE_READWRITE);
+    }
+    if (!allocation)
+        allocation = VirtualAlloc(0, n, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
     ExecutablePool::Allocation alloc = { reinterpret_cast<char*>(allocation), n };
     return alloc;
 }
 
 void ExecutableAllocator::systemRelease(const ExecutablePool::Allocation& alloc)
-{ 
-    VirtualFree(alloc.pages, 0, MEM_RELEASE); 
+{
+    VirtualFree(alloc.pages, 0, MEM_RELEASE);
 }
 
 #if ENABLE_ASSEMBLER_WX_EXCLUSIVE
 #error "ASSEMBLER_WX_EXCLUSIVE not yet suported on this platform."
 #endif
 
 }
 
--- a/js/src/jscntxt.cpp
+++ b/js/src/jscntxt.cpp
@@ -79,16 +79,17 @@
 #include "jspubtd.h"
 #include "jsscope.h"
 #include "jsscript.h"
 #include "jsstr.h"
 #include "jstracer.h"
 
 #ifdef JS_METHODJIT
 # include "assembler/assembler/MacroAssembler.h"
+# include "methodjit/MethodJIT.h"
 #endif
 #include "frontend/TokenStream.h"
 #include "frontend/ParseMaps.h"
 
 #include "jsatominlines.h"
 #include "jscntxtinlines.h"
 #include "jscompartment.h"
 #include "jsobjinlines.h"
@@ -1678,16 +1679,19 @@ JSContext::updateJITEnabled()
                        !IsJITBrokenHere() &&
                        compartment &&
                        !compartment->debugMode() &&
                        (debugHooks == &js_NullDebugHooks ||
                         (debugHooks == &runtime->globalDebugHooks &&
                          !runtime->debuggerInhibitsJIT())));
 #endif
 #ifdef JS_METHODJIT
+    // This allocator randomization is actually a compartment-wide option.
+    if (compartment && compartment->hasJaegerCompartment())
+        compartment->jaegerCompartment()->execAlloc()->setRandomize(hasJITHardeningOption());
     methodJitEnabled = (runOptions & JSOPTION_METHODJIT) &&
                        !IsJITBrokenHere()
 # if defined JS_CPU_X86 || defined JS_CPU_X64
                        && JSC::MacroAssemblerX86Common::getSSEState() >=
                           JSC::MacroAssemblerX86Common::HasSSE2
 # endif
                         ;
 #ifdef JS_TRACER
--- a/js/src/jscompartment.cpp
+++ b/js/src/jscompartment.cpp
@@ -155,17 +155,17 @@ bool
 JSCompartment::ensureJaegerCompartmentExists(JSContext *cx)
 {
     if (jaegerCompartment_)
         return true;
 
     mjit::JaegerCompartment *jc = cx->new_<mjit::JaegerCompartment>();
     if (!jc)
         return false;
-    if (!jc->Initialize()) {
+    if (!jc->Initialize(cx)) {
         cx->delete_(jc);
         return false;
     }
     jaegerCompartment_ = jc;
     return true;
 }
 
 void
--- a/js/src/jsmath.cpp
+++ b/js/src/jsmath.cpp
@@ -513,51 +513,49 @@ js_math_pow(JSContext *cx, uintN argc, V
 static const int64 RNG_MULTIPLIER = 0x5DEECE66DLL;
 static const int64 RNG_ADDEND = 0xBLL;
 static const int64 RNG_MASK = (1LL << 48) - 1;
 static const jsdouble RNG_DSCALE = jsdouble(1LL << 53);
 
 /*
  * Math.random() support, lifted from java.util.Random.java.
  */
-static inline void
-random_setSeed(JSContext *cx, int64 seed)
+extern void
+random_setSeed(int64 *rngSeed, int64 seed)
 {
-    cx->rngSeed = (seed ^ RNG_MULTIPLIER) & RNG_MASK;
+    *rngSeed = (seed ^ RNG_MULTIPLIER) & RNG_MASK;
 }
 
 void
 js_InitRandom(JSContext *cx)
 {
     /*
      * 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.
      */
-    random_setSeed(cx,
-                   (PRMJ_Now() / 1000) ^
-                   int64(cx) ^
-                   int64(cx->link.next));
+    random_setSeed(&cx->rngSeed, (PRMJ_Now() / 1000) ^ int64(cx) ^ int64(cx->link.next));
 }
 
-static inline uint64
-random_next(JSContext *cx, int bits)
+extern uint64
+random_next(int64 *rngSeed, int bits)
 {
-    uint64 nextseed = cx->rngSeed * RNG_MULTIPLIER;
+    uint64 nextseed = *rngSeed * RNG_MULTIPLIER;
     nextseed += RNG_ADDEND;
     nextseed &= RNG_MASK;
-    cx->rngSeed = nextseed;
+    *rngSeed = nextseed;
     return nextseed >> (48 - bits);
 }
 
 static inline jsdouble
 random_nextDouble(JSContext *cx)
 {
-    return jsdouble((random_next(cx, 26) << 27) + random_next(cx, 27)) / RNG_DSCALE;
+    return jsdouble((random_next(&cx->rngSeed, 26) << 27) + random_next(&cx->rngSeed, 27)) /
+           RNG_DSCALE;
 }
 
 static JSBool
 math_random(JSContext *cx, uintN argc, Value *vp)
 {
     jsdouble z = random_nextDouble(cx);
     vp->setDouble(z);
     return JS_TRUE;
--- a/js/src/methodjit/MethodJIT.cpp
+++ b/js/src/methodjit/MethodJIT.cpp
@@ -995,19 +995,20 @@ JS_STATIC_ASSERT(JSVAL_PAYLOAD_MASK == 0
 
 #endif                   /* _WIN64 */
 
 JaegerCompartment::JaegerCompartment()
     : orphanedNativeFrames(SystemAllocPolicy()), orphanedNativePools(SystemAllocPolicy())
 {}
 
 bool
-JaegerCompartment::Initialize()
+JaegerCompartment::Initialize(JSContext *cx)
 {
-    execAlloc_ = js::OffTheBooks::new_<JSC::ExecutableAllocator>();
+    execAlloc_ = js::OffTheBooks::new_<JSC::ExecutableAllocator>(
+        cx->hasJITHardeningOption() ? JSC::AllocationCanRandomize : JSC::AllocationDeterministic);
     if (!execAlloc_)
         return false;
     
     TrampolineCompiler tc(execAlloc_, &trampolines);
     if (!tc.compile()) {
         js::Foreground::delete_(execAlloc_);
         execAlloc_ = NULL;
         return false;
--- a/js/src/methodjit/MethodJIT.h
+++ b/js/src/methodjit/MethodJIT.h
@@ -400,17 +400,17 @@ class JaegerCompartment {
     Trampolines              trampolines;    // force-return trampolines
     VMFrame                  *activeFrame_;  // current active VMFrame
     JaegerStatus             lastUnfinished_;// result status of last VM frame,
                                              // if unfinished
 
     void Finish();
 
   public:
-    bool Initialize();
+    bool Initialize(JSContext *cx);
 
     JaegerCompartment();
     ~JaegerCompartment() { Finish(); }
 
     JSC::ExecutableAllocator *execAlloc() {
         return execAlloc_;
     }