Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 16 additions & 7 deletions src/shell-interface.h
Original file line number Diff line number Diff line change
Expand Up @@ -88,16 +88,19 @@ 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
// maximal memory64 (2^48 pages of 64KiB) does not fit in 64 bits. If
// 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();
Expand Down Expand Up @@ -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");

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting, this is technically not spec-compliant, but I guess it is nice for fuzzing...

Maybe add a comment that this is mainly for fuzzing, and it is here and not in the main interpreter code so it does not affect Precompute?

}
auto it = memories.find(memoryName);
if (it == memories.end()) {
Expand Down
37 changes: 36 additions & 1 deletion test/lit/exec/memory64-max-size.wast
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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)
)
)
)
16 changes: 14 additions & 2 deletions test/lit/passes/precompute_all-features.wast
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down Expand Up @@ -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)
Expand Down
Loading