Bug 1325200 - Simplify AllocateExecutableMemory, count executable memory size. r=luke, r=bhackett, a=lizzard
authorJan de Mooij <jdemooij@mozilla.com>
Thu, 12 Jan 2017 12:48:36 +0100
changeset 312573 ead08c2a6c57dcf8c3065bbee77ec1390b626896
parent 312572 0617dd4b444d79a13966d3a55b5fb271316ea49d
child 312574 c5a46afffe8dfb38d5543da16a59c2fd948dd09d
push id375
push userryanvm@gmail.com
push dateThu, 12 Jan 2017 22:08:00 +0000
treeherdermozilla-esr45@c5a46afffe8d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersluke, bhackett, lizzard
bugs1325200
milestone45.6.1
Bug 1325200 - Simplify AllocateExecutableMemory, count executable memory size. r=luke, r=bhackett, a=lizzard
js/src/asmjs/AsmJSModule.cpp
js/src/jit-test/tests/asm.js/testBug975182.js
js/src/jit/ExecutableAllocator.cpp
js/src/jit/ExecutableAllocator.h
js/src/jit/ExecutableAllocatorPosix.cpp
js/src/jit/ExecutableAllocatorWin.cpp
js/src/jsmath.cpp
js/src/jsmath.h
js/src/vm/Initialization.cpp
js/src/vm/TypeInference.cpp
--- a/js/src/asmjs/AsmJSModule.cpp
+++ b/js/src/asmjs/AsmJSModule.cpp
@@ -64,17 +64,17 @@ using JS::GenericNaN;
 static uint8_t*
 AllocateExecutableMemory(ExclusiveContext* cx, size_t bytes)
 {
     // On most platforms, this will allocate RWX memory. On iOS, or when
     // --non-writable-jitcode is used, this will allocate RW memory. In this
     // case, DynamicallyLinkModule will reprotect the code as RX.
     unsigned permissions =
         ExecutableAllocator::initialProtectionFlags(ExecutableAllocator::Writable);
-    void* p = AllocateExecutableMemory(nullptr, bytes, permissions, "asm-js-code", AsmJSPageSize);
+    void* p = AllocateExecutableMemory(bytes, permissions, "asm-js-code", AsmJSPageSize);
     if (!p)
         ReportOutOfMemory(cx);
     return (uint8_t*)p;
 }
 
 AsmJSModule::AsmJSModule(ScriptSource* scriptSource, uint32_t srcStart, uint32_t srcBodyStart,
                          bool strict, bool canUseSignalHandlers)
   : srcStart_(srcStart),
--- a/js/src/jit-test/tests/asm.js/testBug975182.js
+++ b/js/src/jit-test/tests/asm.js/testBug975182.js
@@ -8,11 +8,13 @@ Function("\
         return f\
     })(this, {\
         ff: arguments.callee\
     }, new ArrayBuffer(4096))\
 ")()
 function m(f) {
     for (var j = 0; j < 6000; ++j) {
         f();
+        if ((j % 1000) === 0)
+            gc();
     }
 }
 m(g);
--- a/js/src/jit/ExecutableAllocator.cpp
+++ b/js/src/jit/ExecutableAllocator.cpp
@@ -22,16 +22,18 @@
  * 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.
  */
 
 #include "jit/ExecutableAllocator.h"
 
+#include "mozilla/Atomics.h"
+
 #include "js/MemoryMetrics.h"
 
 #ifdef __APPLE__
 #include <TargetConditionals.h>
 #endif
 
 using namespace js::jit;
 
@@ -88,8 +90,52 @@ ExecutableAllocator::addSizeOfCode(JS::C
     }
 }
 
 #if TARGET_OS_IPHONE
 bool ExecutableAllocator::nonWritableJitCode = true;
 #else
 bool ExecutableAllocator::nonWritableJitCode = false;
 #endif
+
+// Limit on the number of bytes of executable memory to prevent JIT spraying
+// attacks.
+#if JS_BITS_PER_WORD == 32
+static const size_t MaxCodeBytesPerProcess = 128 * 1024 * 1024;
+#else
+static const size_t MaxCodeBytesPerProcess = 512 * 1024 * 1024;
+#endif
+
+static mozilla::Atomic<size_t> allocatedExecutableBytes(0);
+
+bool
+js::jit::AddAllocatedExecutableBytes(size_t bytes)
+{
+    MOZ_ASSERT(allocatedExecutableBytes <= MaxCodeBytesPerProcess);
+
+    // Multiple threads can call this concurrently. We use compareExchange to
+    // ensure allocatedExecutableBytes is always <= MaxCodeBytesPerProcess.
+    while (true) {
+        size_t bytesOld = allocatedExecutableBytes;
+        size_t bytesNew = bytesOld + bytes;
+
+        if (bytesNew > MaxCodeBytesPerProcess)
+            return false;
+
+        if (allocatedExecutableBytes.compareExchange(bytesOld, bytesNew))
+            return true;
+    }
+
+    MOZ_CRASH();
+}
+
+void
+js::jit::SubAllocatedExecutableBytes(size_t bytes)
+{
+    MOZ_ASSERT(bytes <= allocatedExecutableBytes);
+    allocatedExecutableBytes -= bytes;
+}
+
+void
+js::jit::AssertAllocatedExecutableBytesIsZero()
+{
+    MOZ_ASSERT(allocatedExecutableBytes == 0);
+}
--- a/js/src/jit/ExecutableAllocator.h
+++ b/js/src/jit/ExecutableAllocator.h
@@ -181,20 +181,16 @@ namespace jit {
     }
 };
 
 class ExecutableAllocator
 {
     typedef void (*DestroyCallback)(void* addr, size_t size);
     DestroyCallback destroyCallback;
 
-#ifdef XP_WIN
-    mozilla::Maybe<mozilla::non_crypto::XorShift128PlusRNG> randomNumberGenerator;
-#endif
-
   public:
     enum ProtectionSetting { Writable, Executable };
 
     ExecutableAllocator()
       : destroyCallback(nullptr)
     {
         MOZ_ASSERT(m_smallPools.empty());
     }
@@ -289,17 +285,16 @@ class ExecutableAllocator
         size = size & ~(granularity - 1);
         MOZ_ASSERT(size >= request);
         return size;
     }
 
     // On OOM, this will return an Allocation where pages is nullptr.
     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 nullptr;
 
         if (!m_pools.initialized() && !m_pools.init())
@@ -311,18 +306,18 @@ class ExecutableAllocator
 
         ExecutablePool* pool = js_new<ExecutablePool>(this, a);
         if (!pool) {
             systemRelease(a);
             return nullptr;
         }
 
         if (!m_pools.put(pool)) {
+            // Note: this will call |systemRelease(a)|.
             js_delete(pool);
-            systemRelease(a);
             return nullptr;
         }
 
         return pool;
     }
 
   public:
     ExecutablePool* poolForSize(size_t n)
@@ -482,18 +477,31 @@ class ExecutableAllocator
     typedef js::HashSet<ExecutablePool*, js::DefaultHasher<ExecutablePool*>, js::SystemAllocPolicy>
             ExecPoolHashSet;
     ExecPoolHashSet m_pools;    // All pools, just for stats purposes.
 
     static size_t determinePageSize();
 };
 
 extern void*
-AllocateExecutableMemory(void* addr, size_t bytes, unsigned permissions, const char* tag,
+AllocateExecutableMemory(size_t bytes, unsigned permissions, const char* tag,
                          size_t pageSize);
 
 extern void
 DeallocateExecutableMemory(void* addr, size_t bytes, size_t pageSize);
 
+// These functions are called by the platform-specific definitions of
+// (Allocate|Deallocate)ExecutableMemory and should not otherwise be
+// called directly.
+
+extern MOZ_MUST_USE bool
+AddAllocatedExecutableBytes(size_t bytes);
+
+extern void
+SubAllocatedExecutableBytes(size_t bytes);
+
+extern void
+AssertAllocatedExecutableBytesIsZero();
+
 } // namespace jit
 } // namespace js
 
 #endif /* jit_ExecutableAllocator_h */
--- a/js/src/jit/ExecutableAllocatorPosix.cpp
+++ b/js/src/jit/ExecutableAllocatorPosix.cpp
@@ -21,16 +21,17 @@
  * 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.
  */
 
 #include "mozilla/DebugOnly.h"
+#include "mozilla/ScopeExit.h"
 #include "mozilla/TaggedAnonymousMemory.h"
 
 #include <errno.h>
 #include <sys/mman.h>
 #include <unistd.h>
 
 #include "jit/ExecutableAllocator.h"
 #include "js/Utility.h"
@@ -39,36 +40,49 @@ using namespace js::jit;
 
 size_t
 ExecutableAllocator::determinePageSize()
 {
     return getpagesize();
 }
 
 void*
-js::jit::AllocateExecutableMemory(void* addr, size_t bytes, unsigned permissions, const char* tag,
+js::jit::AllocateExecutableMemory(size_t bytes, unsigned permissions, const char* tag,
                                   size_t pageSize)
 {
     MOZ_ASSERT(bytes % pageSize == 0);
-    void* p = MozTaggedAnonymousMmap(addr, bytes, permissions, MAP_PRIVATE | MAP_ANON, -1, 0, tag);
-    return p == MAP_FAILED ? nullptr : p;
+
+    if (!AddAllocatedExecutableBytes(bytes))
+        return nullptr;
+
+    auto autoSubtract = mozilla::MakeScopeExit([&] { SubAllocatedExecutableBytes(bytes); });
+
+    void* p = MozTaggedAnonymousMmap(nullptr, bytes, permissions, MAP_PRIVATE | MAP_ANON, -1, 0,
+                                     tag);
+    if (p == MAP_FAILED)
+        return nullptr;
+
+    autoSubtract.release();
+    return p;
 }
 
 void
 js::jit::DeallocateExecutableMemory(void* addr, size_t bytes, size_t pageSize)
 {
     MOZ_ASSERT(bytes % pageSize == 0);
     mozilla::DebugOnly<int> result = munmap(addr, bytes);
     MOZ_ASSERT(!result || errno == ENOMEM);
+
+    SubAllocatedExecutableBytes(bytes);
 }
 
 ExecutablePool::Allocation
 ExecutableAllocator::systemAlloc(size_t n)
 {
-    void* allocation = AllocateExecutableMemory(nullptr, n, initialProtectionFlags(Executable),
+    void* allocation = AllocateExecutableMemory(n, initialProtectionFlags(Executable),
                                                 "js-jit-code", pageSize);
     ExecutablePool::Allocation alloc = { reinterpret_cast<char*>(allocation), n };
     return alloc;
 }
 
 void
 ExecutableAllocator::systemRelease(const ExecutablePool::Allocation& alloc)
 {
--- a/js/src/jit/ExecutableAllocatorWin.cpp
+++ b/js/src/jit/ExecutableAllocatorWin.cpp
@@ -20,16 +20,17 @@
  * 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.
  */
 
+#include "mozilla/ScopeExit.h"
 #include "mozilla/WindowsVersion.h"
 
 #include "jsfriendapi.h"
 #include "jsmath.h"
 #include "jswin.h"
 
 #include "jit/ExecutableAllocator.h"
 
@@ -38,18 +39,18 @@ using namespace js::jit;
 size_t
 ExecutableAllocator::determinePageSize()
 {
     SYSTEM_INFO system_info;
     GetSystemInfo(&system_info);
     return system_info.dwPageSize;
 }
 
-void*
-ExecutableAllocator::computeRandomAllocationAddress()
+static void*
+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
@@ -62,42 +63,20 @@ ExecutableAllocator::computeRandomAlloca
     static const uintptr_t mask = 0x000003ffffff0000;
 #elif defined(JS_CPU_X86)
     static const uintptr_t base = 0x04000000;
     static const uintptr_t mask = 0x3fff0000;
 #else
 # error "Unsupported architecture"
 #endif
 
-    if (randomNumberGenerator.isNothing()) {
-        mozilla::Array<uint64_t, 2> seed;
-        js::GenerateXorShift128PlusSeed(seed);
-        randomNumberGenerator.emplace(seed[0], seed[1]);
-    }
-
-    uint64_t rand = randomNumberGenerator.ref().next();
+    uint64_t rand = js::GenerateRandomSeed();
     return (void*) (base | (rand & mask));
 }
 
-static bool
-RandomizeIsBrokenImpl()
-{
-    // We disable everything before Vista, for now.
-    return !mozilla::IsVistaOrLater();
-}
-
-static bool
-RandomizeIsBroken()
-{
-    // Use the compiler's intrinsic guards for |static type value = expr| to avoid some potential
-    // races if runtimes are created from multiple threads.
-    static int result = RandomizeIsBrokenImpl();
-    return !!result;
-}
-
 #ifdef JS_CPU_X64
 static js::JitExceptionHandler sJitExceptionHandler;
 
 JS_FRIEND_API(void)
 js::SetJitExceptionHandler(JitExceptionHandler handler)
 {
     MOZ_ASSERT(!sJitExceptionHandler);
     sJitExceptionHandler = handler;
@@ -184,73 +163,86 @@ RegisterExecutableMemory(void* p, size_t
 static void
 UnregisterExecutableMemory(void* p, size_t bytes, size_t pageSize)
 {
     ExceptionHandlerRecord* r = reinterpret_cast<ExceptionHandlerRecord*>(p);
     RtlDeleteFunctionTable(&r->runtimeFunction);
 }
 #endif
 
+static const size_t VirtualAllocGranularity = 64 * 1024;
+
 void*
-js::jit::AllocateExecutableMemory(void* addr, size_t bytes, unsigned permissions, const char* tag,
+js::jit::AllocateExecutableMemory(size_t bytes, unsigned permissions, const char* tag,
                                   size_t pageSize)
 {
     MOZ_ASSERT(bytes % pageSize == 0);
 
+    // VirtualAlloc returns 64 KB chunks, so we round the value we pass to
+    // AddAllocatedExecutableBytes up to 64 KB to account for this. See
+    // bug 1325200.
+    size_t bytesRounded = JS_ROUNDUP(bytes, VirtualAllocGranularity);
+    if (!AddAllocatedExecutableBytes(bytesRounded))
+        return nullptr;
+
+    auto autoSubtract = mozilla::MakeScopeExit([&] { SubAllocatedExecutableBytes(bytesRounded); });
+
 #ifdef JS_CPU_X64
     if (sJitExceptionHandler)
         bytes += pageSize;
 #endif
 
-    void* p = VirtualAlloc(addr, bytes, MEM_COMMIT | MEM_RESERVE, permissions);
-    if (!p)
-        return nullptr;
+    void* randomAddr = ComputeRandomAllocationAddress();
+
+    void* p = VirtualAlloc(randomAddr, bytes, MEM_COMMIT | MEM_RESERVE, permissions);
+    if (!p) {
+        // Try again without randomAddr.
+        p = VirtualAlloc(nullptr, bytes, MEM_COMMIT | MEM_RESERVE, permissions);
+        if (!p)
+            return nullptr;
+    }
 
 #ifdef JS_CPU_X64
     if (sJitExceptionHandler) {
         if (!RegisterExecutableMemory(p, bytes, pageSize)) {
             VirtualFree(p, 0, MEM_RELEASE);
             return nullptr;
         }
 
         p = (uint8_t*)p + pageSize;
     }
 #endif
 
+    autoSubtract.release();
     return p;
 }
 
 void
 js::jit::DeallocateExecutableMemory(void* addr, size_t bytes, size_t pageSize)
 {
     MOZ_ASSERT(bytes % pageSize == 0);
 
 #ifdef JS_CPU_X64
     if (sJitExceptionHandler) {
         addr = (uint8_t*)addr - pageSize;
         UnregisterExecutableMemory(addr, bytes, pageSize);
     }
 #endif
 
     VirtualFree(addr, 0, MEM_RELEASE);
+
+    SubAllocatedExecutableBytes(JS_ROUNDUP(bytes, VirtualAllocGranularity));
 }
 
 ExecutablePool::Allocation
 ExecutableAllocator::systemAlloc(size_t n)
 {
-    void* allocation = nullptr;
-    if (!RandomizeIsBroken()) {
-        void* randomAddress = computeRandomAllocationAddress();
-        allocation = AllocateExecutableMemory(randomAddress, n, initialProtectionFlags(Executable),
-                                              "js-jit-code", pageSize);
-    }
-    if (!allocation) {
-        allocation = AllocateExecutableMemory(nullptr, n, initialProtectionFlags(Executable),
-                                              "js-jit-code", pageSize);
-    }
+    unsigned flags = initialProtectionFlags(Executable);
+    void* allocation = AllocateExecutableMemory(n, flags, "js-jit-code", pageSize);
+
     ExecutablePool::Allocation alloc = { reinterpret_cast<char*>(allocation), n };
     return alloc;
 }
 
 void
 ExecutableAllocator::systemRelease(const ExecutablePool::Allocation& alloc)
 {
     DeallocateExecutableMemory(alloc.pages, alloc.size, pageSize);
--- a/js/src/jsmath.cpp
+++ b/js/src/jsmath.cpp
@@ -742,46 +742,46 @@ js::math_pow_handle(JSContext* cx, Handl
 bool
 js::math_pow(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
     return math_pow_handle(cx, args.get(0), args.get(1), args.rval());
 }
 
-static uint64_t
-GenerateSeed()
+uint64_t
+js::GenerateRandomSeed()
 {
     uint64_t seed = 0;
 
 #if defined(XP_WIN)
     MOZ_ALWAYS_TRUE(RtlGenRandom(&seed, sizeof(seed)));
 #elif defined(HAVE_ARC4RANDOM)
     seed = (static_cast<uint64_t>(arc4random()) << 32) | arc4random();
 #elif defined(XP_UNIX)
     int fd = open("/dev/urandom", O_RDONLY);
     if (fd >= 0) {
         read(fd, static_cast<void*>(&seed), sizeof(seed));
         close(fd);
     }
 #else
-# error "Platform needs to implement GenerateSeed()"
+# error "Platform needs to implement GenerateRandomSeed()"
 #endif
 
     // Also mix in PRMJ_Now() in case we couldn't read random bits from the OS.
     return seed ^ PRMJ_Now();
 }
 
 void
 js::GenerateXorShift128PlusSeed(mozilla::Array<uint64_t, 2>& seed)
 {
     // XorShift128PlusRNG must be initialized with a non-zero seed.
     do {
-        seed[0] = GenerateSeed();
-        seed[1] = GenerateSeed();
+        seed[0] = GenerateRandomSeed();
+        seed[1] = GenerateRandomSeed();
     } while (seed[0] == 0 && seed[1] == 0);
 }
 
 void
 JSCompartment::ensureRandomNumberGenerator()
 {
     if (randomNumberGenerator.isNothing()) {
         mozilla::Array<uint64_t, 2> seed;
--- a/js/src/jsmath.h
+++ b/js/src/jsmath.h
@@ -81,16 +81,19 @@ class MathCache
 
 /*
  * JS math functions.
  */
 
 extern JSObject*
 InitMathClass(JSContext* cx, HandleObject obj);
 
+extern uint64_t
+GenerateRandomSeed();
+
 // Fill |seed[0]| and |seed[1]| with random bits, suitable for
 // seeding a XorShift128+ random number generator.
 extern void
 GenerateXorShift128PlusSeed(mozilla::Array<uint64_t, 2>& seed);
 
 extern uint64_t
 random_next(uint64_t* rngState, int bits);
 
--- a/js/src/vm/Initialization.cpp
+++ b/js/src/vm/Initialization.cpp
@@ -141,16 +141,19 @@ JS_ShutDown(void)
     // so), so we really don't want to do it in JS_Init, and we really do want
     // to do it only when PRMJ_Now is eventually called.
     PRMJ_NowShutdown();
 
 #if EXPOSE_INTL_API
     u_cleanup();
 #endif // EXPOSE_INTL_API
 
+    if (!JSRuntime::hasLiveRuntimes())
+        js::jit::AssertAllocatedExecutableBytesIsZero();
+
     libraryInitState = InitState::ShutDown;
 }
 
 JS_PUBLIC_API(bool)
 JS_SetICUMemoryFunctions(JS_ICUAllocFn allocFn, JS_ICUReallocFn reallocFn, JS_ICUFreeFn freeFn)
 {
     MOZ_ASSERT(libraryInitState == InitState::Uninitialized,
                "must call JS_SetICUMemoryFunctions before any other JSAPI "
--- a/js/src/vm/TypeInference.cpp
+++ b/js/src/vm/TypeInference.cpp
@@ -4187,16 +4187,20 @@ ObjectGroup::sweep(AutoClearTypeInferenc
         // Remove unboxed layouts that are about to be finalized from the
         // compartment wide list while we are still on the main thread.
         ObjectGroup* group = this;
         if (IsAboutToBeFinalizedUnbarriered(&group))
             unboxedLayout().detachFromCompartment();
 
         if (unboxedLayout().newScript())
             unboxedLayout().newScript()->sweep();
+
+        // Discard constructor code to avoid holding onto ExecutablePools.
+        if (zone()->isGCCompacting())
+            unboxedLayout().setConstructorCode(nullptr);
     }
 
     if (maybePreliminaryObjects())
         maybePreliminaryObjects()->sweep();
 
     if (newScript())
         newScript()->sweep();