diff --git a/src/shell-interface.h b/src/shell-interface.h index 1d8cf5e9d9e..e56ebd91d85 100644 --- a/src/shell-interface.h +++ b/src/shell-interface.h @@ -88,6 +88,10 @@ struct ShellExternalInterface : ModuleRunner::ExternalInterface { return ret; } + // Apply a reasonable limit on memory size, 1GB, to avoid DOS on the + // interpreter. + static constexpr uint64_t MaxMemorySize = 1024 * 1024 * 1024; + void init(Module& wasm, ModuleRunner& instance) override { ModuleUtils::iterDefinedMemories(wasm, [&](wasm::Memory* memory) { // Compute the initial size in bytes carefully: the byte size of a @@ -95,9 +99,8 @@ struct ShellExternalInterface : ModuleRunner::ExternalInterface { // we let it wrap, instantiation would "succeed" with an empty buffer // and every later access would spuriously trap as out of bounds. uint64_t bytes = uint64_t(memory->initial) << memory->pageSizeLog2; - if ((bytes >> memory->pageSizeLog2) != uint64_t(memory->initial)) { - // The byte size is not even representable; we certainly cannot - // allocate it, just like a real VM could not. + if ((bytes >> memory->pageSizeLog2) != uint64_t(memory->initial) || + bytes > MaxMemorySize) { hostLimit("memory too large"); } auto shellMemory = Memory(); @@ -236,10 +239,16 @@ struct ShellExternalInterface : ModuleRunner::ExternalInterface { } bool growMemory(Name memoryName, Address /*oldSize*/, Address newSize) override { - // Apply a reasonable limit on memory size, 1GB, to avoid DOS on the - // interpreter. - if (newSize > 1024 * 1024 * 1024) { - return false; + if (newSize > MaxMemorySize) { + // According to the spec, this should return -1. However, that kind of + // non-deterministic graceful failure causes false positives when doing + // differential fuzzing between implementations that vary in their ability + // to do the grow. To avoid those false positives in the fuzzer, we fail + // with an exceeded host limit here. This will cause fuzzer executions + // with large memory growths to be ignored. This limit is implemented here + // instead of in the main interpreter so other users (e.g. Precompute) get + // the standard behavior. + hostLimit("memory too large"); } auto it = memories.find(memoryName); if (it == memories.end()) { diff --git a/test/lit/exec/memory64-max-size.wast b/test/lit/exec/memory64-max-size.wast index f1b5dc82dd7..dbcafdb8f63 100644 --- a/test/lit/exec/memory64-max-size.wast +++ b/test/lit/exec/memory64-max-size.wast @@ -1,6 +1,6 @@ ;; NOTE: Assertions have been generated by update_lit_checks.py --output=fuzz-exec and should not be edited. -;; RUN: wasm-opt %s -all --fuzz-exec-before -q -o /dev/null 2>&1 | filecheck %s +;; RUN: foreach %s %t wasm-opt -all --fuzz-exec-before -q -o /dev/null 2>&1 | filecheck %s ;; A maximal memory64 declares 2^48 pages, exactly 2^64 bytes, which cannot be ;; allocated (and whose byte size does not even fit in 64 bits). Instantiation @@ -12,3 +12,38 @@ (memory $0 i64 281474976710656 281474976710656 shared) (data $0 (i64.const 0) "\00") ) + +;; An initial memory larger than the interpreter's 1 GiB (16384 64KiB pages) +;; allocation cap must also fail instantiation as a host limit rather than +;; succeeding and then failing subsequent memory.grow(0) calls. +(module + ;; CHECK: [host limit memory too large] + (memory $0 i64 16385 16385) + (func $test (export "test") (result i64) + (memory.grow + (i64.const 0) + ) + ) +) + +;; Growing a memory past the interpreter's 1 GiB cap while remaining within the +;; declared maximum size must also signal a host limit rather than returning -1 +;; so differential fuzzing ignores runs that hit the cap instead of reporting a +;; false positive when other VMs succeed. +(module + (memory $0 i64 1 16385) + ;; CHECK: [fuzz-exec] export grow-0 + ;; CHECK-NEXT: [fuzz-exec] note result: grow-0 => 1 + (func $grow-0 (export "grow-0") (result i64) + (memory.grow + (i64.const 0) + ) + ) + ;; CHECK: [fuzz-exec] export grow-past-host-limit + ;; CHECK-NEXT: [host limit memory too large] + (func $grow-past-host-limit (export "grow-past-host-limit") (result i64) + (memory.grow + (i64.const 16384) + ) + ) +) diff --git a/test/lit/passes/precompute_all-features.wast b/test/lit/passes/precompute_all-features.wast index 5ea442357db..add0e65503f 100644 --- a/test/lit/passes/precompute_all-features.wast +++ b/test/lit/passes/precompute_all-features.wast @@ -22,8 +22,8 @@ ;; CHECK: (global $global-mut (mut i32) (i32.const 2)) - ;; CHECK: (memory $m 512 512) - (memory $m 512 512) + ;; CHECK: (memory $m 512 65536) + (memory $m 512 65536) (data (memory $m) (i32.const 0) "hello!") (type $0 (func (param i32))) (global $global i32 (i32.const 1)) @@ -635,6 +635,18 @@ (i32.const 12) ) ) + ;; CHECK: (func $no-memory-grow-precompute (type $2) (result i32) + ;; CHECK-NEXT: (memory.grow + ;; CHECK-NEXT: (i32.const 32768) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $no-memory-grow-precompute (result i32) + ;; A very large memory.grow should not be precomputed and should not hit the + ;; shell interpreter's host memory limit. + (memory.grow + (i32.const 32768) + ) + ) ;; CHECK: (func $tuple-precompute (type $6) (result i32 i64) ;; CHECK-NEXT: (tuple.make 2 ;; CHECK-NEXT: (i32.const 42)