WebAssembly: limit slow memories
authorjfbastien@apple.com <jfbastien@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 19 Apr 2017 19:38:52 +0000 (19:38 +0000)
committerjfbastien@apple.com <jfbastien@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 19 Apr 2017 19:38:52 +0000 (19:38 +0000)
https://bugs.webkit.org/show_bug.cgi?id=170825

Reviewed by Saam Barati.

JSTests:

* wasm.yaml:
* wasm/stress/oom.js: Added.
(try.true.WebAssemblyMemoryMode):
(catch):

Source/JavaScriptCore:

We limits the number of fast memories, partly because ASLR. The
code then falls back to slow memories. It first tries to virtually
allocated any declared maximum (and in there, physically the
initial), and if that fails it tries to physically allocate the
initial without any extra.

This can still be used to cause a bunch of virtual
allocation. This patch imposes soft limit on slow memories as
well. The total virtual maximum for slow memories is set at the
same (theoretical) value as that for fast memories.

Anything exceeding that limit causes allocation/grow to fail.

* wasm/WasmMemory.cpp:

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@215525 268f45cc-cd09-0410-ab3c-d52691b4dbfc

JSTests/ChangeLog
JSTests/wasm.yaml
JSTests/wasm/stress/oom.js [new file with mode: 0644]
Source/JavaScriptCore/ChangeLog
Source/JavaScriptCore/wasm/WasmMemory.cpp

index 40e307a..d832238 100644 (file)
@@ -1,5 +1,17 @@
 2017-04-19  JF Bastien  <jfbastien@apple.com>
 
+        WebAssembly: limit slow memories
+        https://bugs.webkit.org/show_bug.cgi?id=170825
+
+        Reviewed by Saam Barati.
+
+        * wasm.yaml:
+        * wasm/stress/oom.js: Added.
+        (try.true.WebAssemblyMemoryMode):
+        (catch):
+
+2017-04-19  JF Bastien  <jfbastien@apple.com>
+
         WebAssembly: don't expose any WebAssembly JS object if JIT is off
         https://bugs.webkit.org/show_bug.cgi?id=170782
 
index 5bc7ff3..6488f6d 100644 (file)
@@ -31,6 +31,8 @@
   cmd: runWebAssembly unless parseRunCommands
 - path: wasm/fuzz
   cmd: runWebAssembly unless parseRunCommands
+- path: wasm/stress
+  cmd: runWebAssembly unless parseRunCommands
 
 - path: wasm/spec-tests/address.wast.js
   cmd: runWebAssemblySpecTest :normal
diff --git a/JSTests/wasm/stress/oom.js b/JSTests/wasm/stress/oom.js
new file mode 100644 (file)
index 0000000..8a6c1fd
--- /dev/null
@@ -0,0 +1,22 @@
+const verbose = true;
+
+// Use a full 4GiB so that exhaustion is likely to occur faster. We're not
+// guaranteed that we'll get that much virtually allocated to the memory so we
+// can't actually check that exhaustion occurs at a particular number of
+// memories.
+const maximumPages = 65536;
+
+let memories = [];
+try {
+    while (true) {
+        let m = new WebAssembly.Memory({ initial: 64, maximum: maximumPages });
+        memories.push(m);
+        if (verbose)
+            print(`${WebAssemblyMemoryMode(m)} ${memories.length}`);
+    }
+} catch (e) {
+    if (verbose)
+        print(`Caught: ${e}`);
+    if (e.message !== "Out of memory")
+        throw new Error(`Expected an out of memory error, got ${e} of type ${typeof e}`);
+}
index b18fb20..681b918 100644 (file)
@@ -1,5 +1,27 @@
 2017-04-19  JF Bastien  <jfbastien@apple.com>
 
+        WebAssembly: limit slow memories
+        https://bugs.webkit.org/show_bug.cgi?id=170825
+
+        Reviewed by Saam Barati.
+
+        We limits the number of fast memories, partly because ASLR. The
+        code then falls back to slow memories. It first tries to virtually
+        allocated any declared maximum (and in there, physically the
+        initial), and if that fails it tries to physically allocate the
+        initial without any extra.
+
+        This can still be used to cause a bunch of virtual
+        allocation. This patch imposes soft limit on slow memories as
+        well. The total virtual maximum for slow memories is set at the
+        same (theoretical) value as that for fast memories.
+
+        Anything exceeding that limit causes allocation/grow to fail.
+
+        * wasm/WasmMemory.cpp:
+
+2017-04-19  JF Bastien  <jfbastien@apple.com>
+
         Cannot compile JavaScriptCore/runtime/VMTraps.cpp on FreeBSD because std::pair has a non-trivial copy constructor
         https://bugs.webkit.org/show_bug.cgi?id=170875
 
index 9e8d6e0..610743d 100644 (file)
@@ -93,6 +93,12 @@ std::atomic<void*> fastMemoryCache[fastMemoryCacheHardLimit] = { ATOMIC_VAR_INIT
 std::atomic<void*> currentlyActiveFastMemories[fastMemoryAllocationSoftLimit] = { ATOMIC_VAR_INIT(nullptr) };
 std::atomic<size_t> currentlyAllocatedFastMemories = ATOMIC_VAR_INIT(0);
 std::atomic<size_t> observedMaximumFastMemory = ATOMIC_VAR_INIT(0);
+std::atomic<size_t> currentSlowMemoryCapacity = ATOMIC_VAR_INIT(0);
+
+size_t fastMemoryAllocatedBytesSoftLimit()
+{
+    return fastMemoryAllocationSoftLimit * Memory::fastMappedBytes();
+}
 
 void* tryGetCachedFastMemory()
 {
@@ -194,9 +200,27 @@ void* tryGetFastMemory(VM& vm)
     return memory;
 }
 
+bool slowMemoryCapacitySoftMaximumExceeded()
+{
+    // The limit on slow memory capacity is arbitrary. Its purpose is to limit
+    // virtual memory allocation. We choose to set the limit at the same virtual
+    // memory limit imposed on fast memories.
+    size_t maximum = fastMemoryAllocatedBytesSoftLimit();
+    size_t currentCapacity = currentSlowMemoryCapacity.load(std::memory_order_acquire);
+    if (UNLIKELY(currentCapacity > maximum)) {
+        dataLogLnIf(verbose, "Slow memory capacity limit reached");
+        return true;
+    }
+    return false;
+}
+
 void* tryGetSlowMemory(size_t bytes)
 {
+    if (slowMemoryCapacitySoftMaximumExceeded())
+        return nullptr;
     void* memory = mmapBytes(bytes);
+    if (memory)
+        currentSlowMemoryCapacity.fetch_add(bytes, std::memory_order_acq_rel);
     dataLogLnIf(memory && verbose, "Obtained slow memory ", RawPointer(memory), " with capacity ", bytes);
     dataLogLnIf(!memory && verbose, "Failed obtaining slow memory with capacity ", bytes);
     return memory;
@@ -228,6 +252,7 @@ void relinquishMemory(void* memory, size_t writableSize, size_t mappedCapacity,
     case MemoryMode::BoundsChecking:
         dataLogLnIf(verbose, "relinquishFastMemory freeing slow memory ", RawPointer(memory));
         munmapBytes(memory, mappedCapacity);
+        currentSlowMemoryCapacity.fetch_sub(mappedCapacity, std::memory_order_acq_rel);
         return;
 
     case MemoryMode::NumberOfMemoryModes: