From 9f250c85d86f2e5191f5f0bfd8cd507094ba4b9c Mon Sep 17 00:00:00 2001 From: Jacky Li <86073892+JPL11@users.noreply.github.com> Date: Mon, 13 Jul 2026 14:25:31 -0700 Subject: [PATCH 1/2] Report a host limit for memories too large for the shell to allocate The fuzzer found (in #8882) that a maximal memory64 (2^48 pages, exactly 2^64 bytes) instantiated in the shell interface with the byte size wrapped to 0: instantiation "succeeded" with an empty buffer and then every memory access spuriously trapped as out of bounds, including the memory.init of active segments. Optimizations that legitimately remove such accesses (like MemoryPacking dropping a provably in-bounds zero segment) then changed observable behavior, tripping the fuzzer. Instead, report a host limit during instantiation when the byte size is unrepresentable or the allocation fails, like a real VM whose instantiation fails. The fuzzer already ignores host limits reached during instance creation, and growMemory already applies a size limit, so this brings init in line with existing policy. --- src/shell-interface.h | 18 +++++++++++++++++- test/lit/exec/memory64-max-size.wast | 16 ++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 test/lit/exec/memory64-max-size.wast diff --git a/src/shell-interface.h b/src/shell-interface.h index 6a8763c9c3f..1d8cf5e9d9e 100644 --- a/src/shell-interface.h +++ b/src/shell-interface.h @@ -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"); + } 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; }); } diff --git a/test/lit/exec/memory64-max-size.wast b/test/lit/exec/memory64-max-size.wast new file mode 100644 index 00000000000..b062d27d4fc --- /dev/null +++ b/test/lit/exec/memory64-max-size.wast @@ -0,0 +1,16 @@ +;; 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 -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] + ;; CHECK-NEXT: [host limit memory too large] + ;; CHECK-NEXT: ignoring comparison of ExecutionResults! + (memory $0 i64 281474976710656 281474976710656 shared) + (data $0 (i64.const 0) "\00") +) From 04330464e2bbd935870706a8fe808db49b3f18f4 Mon Sep 17 00:00:00 2001 From: Jacky Li <86073892+JPL11@users.noreply.github.com> Date: Mon, 13 Jul 2026 16:03:16 -0700 Subject: [PATCH 2/2] Use fuzz-exec-before in the test, which runs the module once --- test/lit/exec/memory64-max-size.wast | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/test/lit/exec/memory64-max-size.wast b/test/lit/exec/memory64-max-size.wast index b062d27d4fc..f1b5dc82dd7 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 -q -o /dev/null 2>&1 | filecheck %s +;; 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 @@ -9,8 +9,6 @@ ;; changing the spurious behavior). (module ;; CHECK: [host limit memory too large] - ;; CHECK-NEXT: [host limit memory too large] - ;; CHECK-NEXT: ignoring comparison of ExecutionResults! (memory $0 i64 281474976710656 281474976710656 shared) (data $0 (i64.const 0) "\00") )