Bug 1325200 part 2 - Count executable memory size. r=luke
authorJan de Mooij <jdemooij@mozilla.com>
Thu, 12 Jan 2017 13:00:19 +0100
changeset 374139 21dab2e6926ae09b8a63dc7653dcf4e7c6f4efa9
parent 374138 ab1fd5b320a457e8313dfcdb2d90c99850f3f1d9
child 374140 165b23f87d0818cd103f096df06e0505ab7f9b45
push id6996
push userjlorenzo@mozilla.com
push dateMon, 06 Mar 2017 20:48:21 +0000
treeherdermozilla-beta@d89512dab048 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersluke
bugs1325200
milestone53.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 1325200 part 2 - Count executable memory size. r=luke
js/src/jit-test/tests/asm.js/testBug1301191.js
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/vm/Initialization.cpp
--- a/js/src/jit-test/tests/asm.js/testBug1301191.js
+++ b/js/src/jit-test/tests/asm.js/testBug1301191.js
@@ -9,13 +9,16 @@ timeout(1);
             ff()
         }
         return f
     })(this, {
         ff: arguments.callee
     })
 })()
 function m(f) {
+    var i = 0;
     while (true) {
         f();
+        if ((i++ % 1000) === 0)
+            gc();
     }
 }
 m(g);
--- 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 "jit/JitCompartment.h"
 #include "js/MemoryMetrics.h"
 
 #ifdef __APPLE__
 #include <TargetConditionals.h>
 #endif
 
 using namespace js::jit;
@@ -231,18 +233,18 @@ ExecutableAllocator::createPool(size_t n
 
     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;
 }
 
 void*
 ExecutableAllocator::alloc(size_t n, ExecutablePool** poolp, CodeKind type)
@@ -402,8 +404,52 @@ ExecutableAllocator::poisonCode(JSRuntim
         ExecutablePool* pool = ranges[i].pool;
         if (pool->isMarked()) {
             reprotectPool(rt, pool, Executable);
             pool->unmark();
         }
         pool->release();
     }
 }
+
+// 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
@@ -335,12 +335,25 @@ class ExecutableAllocator
 
 extern void*
 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"
@@ -43,27 +44,39 @@ ExecutableAllocator::determinePageSize()
     return getpagesize();
 }
 
 void*
 js::jit::AllocateExecutableMemory(size_t bytes, unsigned permissions, const char* tag,
                                   size_t pageSize)
 {
     MOZ_ASSERT(bytes % pageSize == 0);
+
+    if (!AddAllocatedExecutableBytes(bytes))
+        return nullptr;
+
+    auto autoSubtract = mozilla::MakeScopeExit([&] { SubAllocatedExecutableBytes(bytes); });
+
     void* p = MozTaggedAnonymousMmap(nullptr, bytes, permissions, MAP_PRIVATE | MAP_ANON, -1, 0,
                                      tag);
-    return p == MAP_FAILED ? nullptr : p;
+    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(n, initialProtectionFlags(Executable),
                                                 "js-jit-code", pageSize);
     ExecutablePool::Allocation alloc = { reinterpret_cast<char*>(allocation), n };
--- a/js/src/jit/ExecutableAllocatorWin.cpp
+++ b/js/src/jit/ExecutableAllocatorWin.cpp
@@ -22,16 +22,17 @@
  * 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/StackWalk_windows.h"
 
+#include "mozilla/ScopeExit.h"
 #include "mozilla/WindowsVersion.h"
 
 #include "jsfriendapi.h"
 #include "jsmath.h"
 #include "jswin.h"
 
 #include "jit/ExecutableAllocator.h"
 
@@ -182,22 +183,33 @@ UnregisterExecutableMemory(void* p, size
     AcquireStackWalkWorkaroundLock();
 
     RtlDeleteFunctionTable(&r->runtimeFunction);
 
     ReleaseStackWalkWorkaroundLock();
 }
 #endif
 
+static const size_t VirtualAllocGranularity = 64 * 1024;
+
 void*
 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 HAVE_64BIT_BUILD
     if (sJitExceptionHandler)
         bytes += pageSize;
 #endif
 
     void* randomAddr = ComputeRandomAllocationAddress();
 
     void* p = VirtualAlloc(randomAddr, bytes, MEM_COMMIT | MEM_RESERVE, permissions);
@@ -214,32 +226,35 @@ js::jit::AllocateExecutableMemory(size_t
             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 HAVE_64BIT_BUILD
     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)
 {
     unsigned flags = initialProtectionFlags(Executable);
     void* allocation = AllocateExecutableMemory(n, flags, "js-jit-code", pageSize);
 
--- a/js/src/vm/Initialization.cpp
+++ b/js/src/vm/Initialization.cpp
@@ -171,16 +171,19 @@ JS_ShutDown(void)
     PRMJ_NowShutdown();
 
 #if EXPOSE_INTL_API
     u_cleanup();
 #endif // EXPOSE_INTL_API
 
     js::FinishDateTimeState();
 
+    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 "