Skip to content
Merged
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
18 changes: 17 additions & 1 deletion src/shell-interface.h
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,24 @@ struct ShellExternalInterface : ModuleRunner::ExternalInterface {

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.
hostLimit("memory too large");
}
Comment on lines +97 to +102

This comment was marked as resolved.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

That works for validated modules: as we established on #8882, under the validator's bound the only value the shift can wrap to is exactly 0. The round trip form additionally stays correct for modules that skipped validation (--no-validation), where an out of range initial can wrap to a nonzero value that !bytes would silently accept as a real size. Since both compile to the same couple of instructions I kept the self contained one, but happy to switch if @kripken prefers the shorter form.

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.

We can assume that modules being executed are valid. The only place a module might be invalid is during parsing, basically. But I think the current code is clearer as it stands, as it is self-contained - the short form would need a longer comment, in my opinion.

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.

Also, the validation limit might change if the spec changes, in theory...

auto shellMemory = Memory();
shellMemory.resize(memory->initial << memory->pageSizeLog2);
try {
shellMemory.resize(bytes);
} catch (const std::exception&) {
// bad_alloc or length_error from the underlying vector: the memory
// is representable but too large to actually allocate.
hostLimit("memory too large");
}
memories[memory->name] = shellMemory;
});
}
Expand Down
14 changes: 14 additions & 0 deletions test/lit/exec/memory64-max-size.wast
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
;; 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

;; 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
;; must fail as a host limit, not wrap the size to zero and then spuriously
;; trap on the active segment (which optimizations may legitimately remove,
;; changing the spurious behavior).
(module
;; CHECK: [host limit memory too large]
(memory $0 i64 281474976710656 281474976710656 shared)
(data $0 (i64.const 0) "\00")
)
Loading