diff --git a/clang_format.sh b/clang_format.sh new file mode 100755 index 0000000000..f9aeb0af66 --- /dev/null +++ b/clang_format.sh @@ -0,0 +1,18 @@ +#!/bin/bash +modified=$1 +dir=`pwd` +clangf=clang-format-10 +clangf=clang-format +if [ "$1" = "--all" ] +then + modified=`git status|egrep "modified|new file"|egrep "(cpp|h)$" | sed -E -e 's/modified://' -e 's/new file://' -e 's/^[[:space:]]+//'` +fi +for i in $modified +do + basedir=$(dirname "$i") + file=$(basename "$i") + echo "$basedir $file" + cd $basedir + $clangf -style=file -i "$file" + cd $dir +done diff --git a/src/test/app/Wasm_test.cpp b/src/test/app/Wasm_test.cpp index c22054fac0..e6e3901958 100644 --- a/src/test/app/Wasm_test.cpp +++ b/src/test/app/Wasm_test.cpp @@ -23,6 +23,23 @@ Add(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results) return nullptr; } +std::optional +runFinishFunction(std::string const& code) +{ + auto& engine = WasmEngine::instance(); + auto const ws = boost::algorithm::unhex(code); + Bytes const wasm(ws.begin(), ws.end()); + auto const re = engine.run(wasm, "finish"); + if (re.has_value()) + { + return std::optional(re->result); + } + else + { + return std::nullopt; + } +} + struct Wasm_test : public beast::unit_test::suite { void @@ -715,6 +732,88 @@ struct Wasm_test : public beast::unit_test::suite } } + void + testWasmMemory() + { + testcase("Wasm additional memory limit tests"); + BEAST_EXPECT(runFinishFunction(memoryPointerAtLimitHex).value() == 1); + BEAST_EXPECT( + runFinishFunction(memoryPointerOverLimitHex).has_value() == false); + BEAST_EXPECT( + runFinishFunction(memoryOffsetOverLimitHex).has_value() == false); + BEAST_EXPECT( + runFinishFunction(memoryEndOfWordOverLimitHex).has_value() == + false); + BEAST_EXPECT(runFinishFunction(memoryGrow0To1PageHex).value() == 1); + BEAST_EXPECT(runFinishFunction(memoryGrow1To0PageHex).value() == -1); + BEAST_EXPECT(runFinishFunction(memoryLastByteOf8MBHex).value() == 1); + BEAST_EXPECT( + runFinishFunction(memoryGrow1MoreThan8MBHex).value() == -1); + BEAST_EXPECT(runFinishFunction(memoryGrow0MoreThan8MBHex).value() == 1); + BEAST_EXPECT( + runFinishFunction(memoryInit1MoreThan8MBHex).has_value() == false); + BEAST_EXPECT( + runFinishFunction(memoryNegativeAddressHex).has_value() == false); + } + + void + testWasmTable() + { + testcase("Wasm table limit tests"); + BEAST_EXPECT(runFinishFunction(table64ElementsHex).value() == 1); + BEAST_EXPECT( + runFinishFunction(table65ElementsHex).has_value() == false); + BEAST_EXPECT(runFinishFunction(table2TablesHex).has_value() == false); + BEAST_EXPECT(runFinishFunction(table0ElementsHex).value() == 1); + BEAST_EXPECT(runFinishFunction(tableUintMaxHex).has_value() == false); + } + + void + testWasmProposal() + { + testcase("Wasm disabled proposal tests"); + BEAST_EXPECT( + runFinishFunction(proposalMutableGlobalHex).has_value() == false); + BEAST_EXPECT( + runFinishFunction(proposalGcStructNewHex).has_value() == false); + BEAST_EXPECT( + runFinishFunction(proposalMultiValueHex).has_value() == false); + BEAST_EXPECT( + runFinishFunction(proposalSignExtHex).has_value() == false); + BEAST_EXPECT( + runFinishFunction(proposalFloatToIntHex).has_value() == false); + BEAST_EXPECT( + runFinishFunction(proposalBulkMemoryHex).has_value() == false); + BEAST_EXPECT( + runFinishFunction(proposalRefTypesHex).has_value() == false); + BEAST_EXPECT( + runFinishFunction(proposalTailCallHex).has_value() == false); + BEAST_EXPECT( + runFinishFunction(proposalExtendedConstHex).has_value() == false); + } + + void + testWasmTrap() + { + testcase("Wasm trap tests"); + BEAST_EXPECT(runFinishFunction(trapDivideBy0Hex).has_value() == false); + BEAST_EXPECT( + runFinishFunction(trapIntOverflowHex).has_value() == false); + BEAST_EXPECT( + runFinishFunction(trapUnreachableHex).has_value() == false); + BEAST_EXPECT(runFinishFunction(trapNullCallHex).has_value() == false); + BEAST_EXPECT( + runFinishFunction(trapFuncSigMismatchHex).has_value() == false); + } + + void + testWasmWasi() + { + testcase("Wasm Wasi tests"); + BEAST_EXPECT(runFinishFunction(wasiGetTimeHex).has_value() == false); + BEAST_EXPECT(runFinishFunction(wasiPrintHex).has_value() == false); + } + void run() override { @@ -736,6 +835,11 @@ struct Wasm_test : public beast::unit_test::suite testCodecovWasm(); testDisabledFloat(); + testWasmMemory(); + testWasmTable(); + testWasmProposal(); + testWasmTrap(); + testWasmWasi(); // perfTest(); } }; diff --git a/src/test/app/wasm_fixtures/fixtures.cpp b/src/test/app/wasm_fixtures/fixtures.cpp index 54584364f1..6a6ae2e686 100644 --- a/src/test/app/wasm_fixtures/fixtures.cpp +++ b/src/test/app/wasm_fixtures/fixtures.cpp @@ -1069,3 +1069,142 @@ extern std::string const disabledFloatHex = "6503050b5f5f686561705f6261736503060a5f5f686561705f656e640307" "0d5f5f6d656d6f72795f6261736503080c5f5f7461626c655f6261736503" "090a150202000b100043000000c54300200045921a41010b"; + +extern std::string const memoryPointerAtLimitHex = + "0061736d010000000105016000017f030201000503010001070a010666696e69736800000a" + "0e010c0041ffff032d00001a41010b"; + +extern std::string const memoryPointerOverLimitHex = + "0061736d010000000105016000017f030201000503010001070a010666696e69736800000a" + "0e010c00418080042d00001a41010b"; + +extern std::string const memoryOffsetOverLimitHex = + "0061736d010000000105016000017f030201000503010001071302066d656d6f7279020006" + "66696e69736800000a0e010c00410028028080041a41010b"; + +extern std::string const memoryEndOfWordOverLimitHex = + "0061736d010000000105016000017f030201000503010001071302066d656d6f7279020006" + "66696e69736800000a0e010c0041feff032802001a41010b"; + +extern std::string const memoryGrow0To1PageHex = + "0061736d010000000105016000017f030201000503010000071302066d656d6f7279020006" + "66696e69736800000a0b010900410140001a41010b"; + +extern std::string const memoryGrow1To0PageHex = + "0061736d010000000105016000017f030201000503010001071302066d656d6f7279020006" + "66696e69736800000a13011100417f4000417f460440417f0f0b41010b"; + +extern std::string const memoryLastByteOf8MBHex = + "0061736d010000000105016000017f030201000506010180018001071302066d656d6f7279" + "02000666696e69736800000a0f010d0041ffffff032d00001a41010b"; + +extern std::string const memoryGrow1MoreThan8MBHex = + "0061736d010000000105016000017f03020100050401008001071302066d656d6f72790200" + "0666696e69736800000a1301110041014000417f460440417f0f0b41010b"; + +extern std::string const memoryGrow0MoreThan8MBHex = + "0061736d010000000105016000017f03020100050401008001071302066d656d6f72790200" + "0666696e69736800000a1301110041004000417f460440417f0f0b41010b"; + +extern std::string const memoryInit1MoreThan8MBHex = + "0061736d010000000105016000017f030201000506010181018101071302066d656d6f7279" + "02000666696e69736800000a0f010d0041ffffff032d00001a41010b"; + +extern std::string const memoryNegativeAddressHex = + "0061736d010000000105016000017f030201000506010180018001071302066d656d6f7279" + "02000666696e69736800000a0c010a00417f2d00001a41010b"; + +extern std::string const table64ElementsHex = + "0061736d010000000108026000006000017f0303020001040401700040070a010666696e69" + "736800010946010041000b4000000000000000000000000000000000000000000000000000" + "00000000000000000000000000000000000000000000000000000000000000000000000000" + "00000a090202000b040041010b"; + +extern std::string const table65ElementsHex = + "0061736d010000000108026000006000017f0303020001040401700041070a010666696e69" + "736800010947010041000b4100000000000000000000000000000000000000000000000000" + "00000000000000000000000000000000000000000000000000000000000000000000000000" + "0000000a090202000b040041010b"; + +extern std::string const table2TablesHex = + "0061736d010000000108026000006000017f03030200010409027001010170010101070a01" + "0666696e6973680001090f020041000b0100020141000b0001000a090202000b040041010" + "b"; + +extern std::string const table0ElementsHex = + "0061736d010000000105016000017f03020100040401700000070a010666696e6973680000" + "0a0601040041010b"; + +extern std::string const tableUintMaxHex = + "0061736d010000000105016000017f030201000408017000ffffffff0f070a010666696e69" + "736800000a0601040041010b"; + +extern std::string const proposalMutableGlobalHex = + "0061736d010000000105016000017f030201000606017f0141000b07140207636f756e7465" + "7203000666696e69736800000a0d010b00230041016a240041010b"; + +extern std::string const proposalGcStructNewHex = + "0061736d01000000010b026000017f5f027f017f0103020100070a010666696e6973680000" + "0a0a010800fb01011a41010b"; + +extern std::string const proposalMultiValueHex = + "0061736d010000000110036000027f7f6000017f60027f7f017f0303020001070a01066669" + "6e69736800010a14020600410a41140b0b00100002026a411e460b0b"; + +extern std::string const proposalSignExtHex = + "0061736d010000000105016000017f03020100070a010666696e69736800000a0b01090041" + "ff01c0417f460b"; + +extern std::string const proposalFloatToIntHex = + "0061736d010000000105016000017f03020100070a010666696e69736800000a1201100043" + "f9021550fc0041ffffffff07460b"; + +extern std::string const proposalBulkMemoryHex = + "0061736d010000000105016000017f030201000503010001071302066d656d6f7279020006" + "66696e69736800000a1f011d004100412a3a000041e40041004101fc0a000041e4002d0000" + "412a460b"; + +extern std::string const proposalRefTypesHex = + "0061736d010000000105016000017f020f0103656e76057461626c65016f00010302010007" + "0a010666696e69736800000a0c010a004100d06f260041010b"; + +extern std::string const proposalTailCallHex = + "0061736d010000000105016000017f0303020000070a010666696e69736800010a0b020400" + "41010b040012000b"; + +extern std::string const proposalExtendedConstHex = + "0061736d010000000105016000017f030201000609017f00410a41206a0b070a010666696e" + "69736800000a090107002300412a460b"; + +extern std::string const trapDivideBy0Hex = + "0061736d010000000105016000017f03020100070a010666696e69736800000a0c010a0041" + "2a41006d1a41010b"; + +extern std::string const trapIntOverflowHex = + "0061736d010000000105016000017f03020100070a010666696e69736800000a0d010b0041" + "8080808078417f6d0b"; + +extern std::string const trapUnreachableHex = + "0061736d010000000105016000017f03020100070a010666696e69736800000a0701050000" + "41010b"; + +extern std::string const trapNullCallHex = + "0061736d010000000105016000017f03020100040401700001070a010666696e6973680000" + "0a0901070041001100000b"; + +extern std::string const trapFuncSigMismatchHex = + "0061736d010000000108026000006000017f0303020001040401700001070a010666696e69" + "736800010907010041000b01000a0d020300010b070041001101000b"; + +extern std::string const wasiGetTimeHex = + "0061736d01000000010c0260037f7e7f017f6000017f02290116776173695f736e61707368" + "6f745f70726576696577310e636c6f636b5f74696d655f6765740000030201010503010001" + "071302066d656d6f727902000666696e69736800010a16011400410042e807410010004504" + "7f410105417f0b0b"; + +extern std::string const wasiPrintHex = + "0061736d01000000010d0260047f7f7f7f017f6000017f02230116776173695f736e617073" + "686f745f70726576696577310866645f77726974650000030201010503010001071302066d" + "656d6f727902000666696e69736800010a1d011b01017f4118210041014100410120001000" + "45047f410105417f0b0b0b1e030041100b0648656c6c6f0a0041000b04100000000041040b" + "0406000000"; diff --git a/src/test/app/wasm_fixtures/fixtures.h b/src/test/app/wasm_fixtures/fixtures.h index abde376763..cb4f6e69d6 100644 --- a/src/test/app/wasm_fixtures/fixtures.h +++ b/src/test/app/wasm_fixtures/fixtures.h @@ -27,3 +27,40 @@ extern std::string const floatTestsWasmHex; extern std::string const float0Hex; extern std::string const disabledFloatHex; + +extern std::string const memoryPointerAtLimitHex; +extern std::string const memoryPointerOverLimitHex; +extern std::string const memoryOffsetOverLimitHex; +extern std::string const memoryEndOfWordOverLimitHex; +extern std::string const memoryGrow0To1PageHex; +extern std::string const memoryGrow1To0PageHex; +extern std::string const memoryLastByteOf8MBHex; +extern std::string const memoryGrow1MoreThan8MBHex; +extern std::string const memoryGrow0MoreThan8MBHex; +extern std::string const memoryInit1MoreThan8MBHex; +extern std::string const memoryNegativeAddressHex; + +extern std::string const table64ElementsHex; +extern std::string const table65ElementsHex; +extern std::string const table2TablesHex; +extern std::string const table0ElementsHex; +extern std::string const tableUintMaxHex; + +extern std::string const proposalMutableGlobalHex; +extern std::string const proposalGcStructNewHex; +extern std::string const proposalMultiValueHex; +extern std::string const proposalSignExtHex; +extern std::string const proposalFloatToIntHex; +extern std::string const proposalBulkMemoryHex; +extern std::string const proposalRefTypesHex; +extern std::string const proposalTailCallHex; +extern std::string const proposalExtendedConstHex; + +extern std::string const trapDivideBy0Hex; +extern std::string const trapIntOverflowHex; +extern std::string const trapUnreachableHex; +extern std::string const trapNullCallHex; +extern std::string const trapFuncSigMismatchHex; + +extern std::string const wasiGetTimeHex; +extern std::string const wasiPrintHex; diff --git a/src/test/app/wasm_fixtures/wat/memory_end_of_word_over_limit.wat b/src/test/app/wasm_fixtures/wat/memory_end_of_word_over_limit.wat new file mode 100644 index 0000000000..dc5427563f --- /dev/null +++ b/src/test/app/wasm_fixtures/wat/memory_end_of_word_over_limit.wat @@ -0,0 +1,28 @@ +(module + ;; 1. Define Memory: 1 Page = 64KB = 65,536 bytes + (memory 1) + + ;; Export memory so the host can inspect it if needed + (export "memory" (memory 0)) + + (func $test_straddle (result i32) + ;; Push the address onto the stack. + ;; 65534 is valid, but it is only 2 bytes away from the end. + i32.const 65534 + + ;; Attempt to load an i32 (4 bytes) from that address. + ;; This requires bytes 65534, 65535, 65536, and 65537. + ;; Since 65536 is the first invalid byte, this MUST trap. + i32.load + + ;; Clean up the stack. + ;; The load pushed a value, but we don't care what it is. + drop + + ;; Return 1 to signal "I survived the memory access" + i32.const 1 + ) + + ;; Export the function so you can call it from your host (JS, Python, etc.) + (export "finish" (func $test_straddle)) +) diff --git a/src/test/app/wasm_fixtures/wat/memory_grow_0_page_more_than_8MB.wat b/src/test/app/wasm_fixtures/wat/memory_grow_0_page_more_than_8MB.wat new file mode 100644 index 0000000000..d9c149530f --- /dev/null +++ b/src/test/app/wasm_fixtures/wat/memory_grow_0_page_more_than_8MB.wat @@ -0,0 +1,29 @@ +(module + ;; Start at your limit: 128 pages (8MB) + (memory 128) + (export "memory" (memory 0)) + + (func $try_grow_beyond_limit (result i32) + ;; Attempt to grow by 0 page + i32.const 0 + memory.grow + + ;; memory.grow returns: + ;; -1 if the growth failed (Correct behavior for your limit) + ;; 128 (old size) if growth succeeded (Means limit was bypassed) + + ;; Check if result == -1 + i32.const -1 + i32.eq + if + ;; Growth FAILED (Host blocked it). Return -1. + i32.const -1 + return + end + + ;; Growth SUCCEEDED (Host allowed it). Return 1. + i32.const 1 + ) + + (export "finish" (func $try_grow_beyond_limit)) +) diff --git a/src/test/app/wasm_fixtures/wat/memory_grow_0_to_1.wat b/src/test/app/wasm_fixtures/wat/memory_grow_0_to_1.wat new file mode 100644 index 0000000000..ff822ffbdc --- /dev/null +++ b/src/test/app/wasm_fixtures/wat/memory_grow_0_to_1.wat @@ -0,0 +1,26 @@ +(module + ;; 1. Define Memory: Start with 0 pages + (memory 0) + + ;; Export memory to host + (export "memory" (memory 0)) + + (func $grow_from_zero (result i32) + ;; We have 0 pages. We want to add 1 page. + ;; Push delta (1) onto stack. + i32.const 1 + + ;; Grow the memory. + ;; If successful: memory becomes 64KB, returns old size (0). + ;; If failed: memory stays 0, returns -1. + memory.grow + + ;; Drop the return value of memory.grow + drop + + ;; Return 1 (as requested) + i32.const 1 + ) + + (export "finish" (func $grow_from_zero)) +) diff --git a/src/test/app/wasm_fixtures/wat/memory_grow_1_page_more_than_8MB.wat b/src/test/app/wasm_fixtures/wat/memory_grow_1_page_more_than_8MB.wat new file mode 100644 index 0000000000..4a245a521c --- /dev/null +++ b/src/test/app/wasm_fixtures/wat/memory_grow_1_page_more_than_8MB.wat @@ -0,0 +1,29 @@ +(module + ;; Start at your limit: 128 pages (8MB) + (memory 128) + (export "memory" (memory 0)) + + (func $try_grow_beyond_limit (result i32) + ;; Attempt to grow by 1 page + i32.const 1 + memory.grow + + ;; memory.grow returns: + ;; -1 if the growth failed (Correct behavior for your limit) + ;; 128 (old size) if growth succeeded (Means limit was bypassed) + + ;; Check if result == -1 + i32.const -1 + i32.eq + if + ;; Growth FAILED (Host blocked it). Return -1. + i32.const -1 + return + end + + ;; Growth SUCCEEDED (Host allowed it). Return 1. + i32.const 1 + ) + + (export "finish" (func $try_grow_beyond_limit)) +) diff --git a/src/test/app/wasm_fixtures/wat/memory_grow_1_to_0.wat b/src/test/app/wasm_fixtures/wat/memory_grow_1_to_0.wat new file mode 100644 index 0000000000..fa25154f09 --- /dev/null +++ b/src/test/app/wasm_fixtures/wat/memory_grow_1_to_0.wat @@ -0,0 +1,33 @@ +(module + ;; 1. Define Memory: Start with 1 page (64KB) + (memory 1) + + ;; Export memory to host + (export "memory" (memory 0)) + + (func $grow_negative (result i32) + ;; The user pushed -1. In Wasm, this is interpreted as unsigned MAX_UINT32. + ;; This is requesting to add 4,294,967,295 pages (approx 256 TB). + ;; A secure runtime MUST fail this request (return -1) without crashing. + i32.const -1 + + ;; Grow the memory. + ;; Returns: old_size if success, -1 if failure. + memory.grow + + ;; Check if result == -1 (Failure) + i32.const -1 + i32.eq + if + ;; If memory.grow returned -1, we return -1 to signal "Correctly failed". + i32.const -1 + return + end + + ;; If we are here, memory.grow somehow SUCCEEDED (Vulnerability). + ;; We return 1 to signal "Unexpected Success". + i32.const 1 + ) + + (export "finish" (func $grow_negative)) +) diff --git a/src/test/app/wasm_fixtures/wat/memory_init_1_page_more_than_8MB.wat b/src/test/app/wasm_fixtures/wat/memory_init_1_page_more_than_8MB.wat new file mode 100644 index 0000000000..5fd1d09ed1 --- /dev/null +++ b/src/test/app/wasm_fixtures/wat/memory_init_1_page_more_than_8MB.wat @@ -0,0 +1,27 @@ +(module + ;; Define memory: 129 pages (> 8MB limit) min, 129 pages max + (memory 129 129) + + ;; Export memory so host can verify size + (export "memory" (memory 0)) + + ;; access last byte of 8MB limit + (func $access_last_byte (result i32) + ;; Math: 128 pages * 64,536 bytes/page = 8,388,608 bytes + ;; Valid indices: 0 to 8,388,607 + + ;; Push the address of the LAST valid byte + i32.const 8388607 + + ;; Load byte from that address + i32.load8_u + + ;; Drop the value (we don't care what it is, just that we could read it) + drop + + ;; Return 1 to indicate success + i32.const 1 + ) + + (export "finish" (func $access_last_byte)) +) diff --git a/src/test/app/wasm_fixtures/wat/memory_last_byte_of_8MB.wat b/src/test/app/wasm_fixtures/wat/memory_last_byte_of_8MB.wat new file mode 100644 index 0000000000..a59ec06294 --- /dev/null +++ b/src/test/app/wasm_fixtures/wat/memory_last_byte_of_8MB.wat @@ -0,0 +1,26 @@ +(module + ;; Define memory: 128 pages (8MB) min, 128 pages max + (memory 128 128) + + ;; Export memory so host can verify size + (export "memory" (memory 0)) + + (func $access_last_byte (result i32) + ;; Math: 128 pages * 64,536 bytes/page = 8,388,608 bytes + ;; Valid indices: 0 to 8,388,607 + + ;; Push the address of the LAST valid byte + i32.const 8388607 + + ;; Load byte from that address + i32.load8_u + + ;; Drop the value (we don't care what it is, just that we could read it) + drop + + ;; Return 1 to indicate success + i32.const 1 + ) + + (export "finish" (func $access_last_byte)) +) diff --git a/src/test/app/wasm_fixtures/wat/memory_negative_address.wat b/src/test/app/wasm_fixtures/wat/memory_negative_address.wat new file mode 100644 index 0000000000..02c9cb5a3b --- /dev/null +++ b/src/test/app/wasm_fixtures/wat/memory_negative_address.wat @@ -0,0 +1,23 @@ +(module + ;; Define memory: 128 pages (8MB) min, 128 pages max + (memory 128 128) + + ;; Export memory so host can verify size + (export "memory" (memory 0)) + + (func $access_last_byte (result i32) + ;; Push a negative address + i32.const -1 + + ;; Load byte from that address + i32.load8_u + + ;; Drop the value + drop + + ;; Return 1 to indicate success + i32.const 1 + ) + + (export "finish" (func $access_last_byte)) +) diff --git a/src/test/app/wasm_fixtures/wat/memory_offset_over_limit.wat b/src/test/app/wasm_fixtures/wat/memory_offset_over_limit.wat new file mode 100644 index 0000000000..d502ddf291 --- /dev/null +++ b/src/test/app/wasm_fixtures/wat/memory_offset_over_limit.wat @@ -0,0 +1,27 @@ +(module + ;; 1. Define Memory: 1 Page = 64KB + (memory 1) + + (export "memory" (memory 0)) + + (func $test_offset_overflow (result i32) + ;; 1. Push the base address onto the stack. + ;; We use '0', which is the safest, most valid address possible. + i32.const 0 + + ;; 2. Attempt to load using a static offset. + ;; syntax: i32.load offset=N align=N + ;; We set the offset to 65536 (the size of the memory). + ;; The effective address becomes 0 + 65536 = 65536. + i32.load offset=65536 + + ;; Clean up the stack. + ;; The load pushed a value, but we don't care what it is. + drop + + ;; Return 1 to signal "I survived the memory access" + i32.const 1 + ) + + (export "finish" (func $test_offset_overflow)) +) diff --git a/src/test/app/wasm_fixtures/wat/memory_pointer_at_limit.wat b/src/test/app/wasm_fixtures/wat/memory_pointer_at_limit.wat new file mode 100644 index 0000000000..c72cd43739 --- /dev/null +++ b/src/test/app/wasm_fixtures/wat/memory_pointer_at_limit.wat @@ -0,0 +1,22 @@ +(module + ;; Define 1 page of memory (64KB = 65,536 bytes) + (memory 1) + + (func $read_edge (result i32) + ;; Push the index of the LAST valid byte + i32.const 65535 + + ;; Load 1 byte (unsigned) + i32.load8_u + + ;; Clean up the stack. + ;; The load pushed a value, but we don't care what it is. + drop + + ;; Return 1 to signal "I survived the memory access" + i32.const 1 + ) + + ;; Export as "finish" as requested + (export "finish" (func $read_edge)) +) diff --git a/src/test/app/wasm_fixtures/wat/memory_pointer_over_limit.wat b/src/test/app/wasm_fixtures/wat/memory_pointer_over_limit.wat new file mode 100644 index 0000000000..12f6ea01a2 --- /dev/null +++ b/src/test/app/wasm_fixtures/wat/memory_pointer_over_limit.wat @@ -0,0 +1,23 @@ +(module + ;; Define 1 page of memory (64KB = 65,536 bytes) + (memory 1) + + (func $read_overflow (result i32) + ;; Push the index of the FIRST invalid byte + ;; Memory is 0..65535, so 65536 is out of bounds. + i32.const 65536 + + ;; Load 1 byte (unsigned) + i32.load8_u + + ;; Clean up the stack. + ;; The load pushed a value, but we don't care what it is. + drop + + ;; Return 1 to signal "I survived the memory access" + i32.const 1 + ) + + ;; Export as "finish" as requested + (export "finish" (func $read_overflow)) + ) diff --git a/src/test/app/wasm_fixtures/wat/proposal_bulk_memory.wat b/src/test/app/wasm_fixtures/wat/proposal_bulk_memory.wat new file mode 100644 index 0000000000..1010c60a61 --- /dev/null +++ b/src/test/app/wasm_fixtures/wat/proposal_bulk_memory.wat @@ -0,0 +1,25 @@ +(module + ;; Define 1 page of memory + (memory 1) + (export "memory" (memory 0)) + + (func $test_bulk_ops (result i32) + ;; Setup: Write value 42 at index 0 so we have something to copy + (i32.store8 (i32.const 0) (i32.const 42)) + + ;; Test memory.copy (Opcode 0xFC 0x0A) + ;; Copy 1 byte from offset 0 to offset 100 + (memory.copy + (i32.const 100) ;; Destination Offset + (i32.const 0) ;; Source Offset + (i32.const 1) ;; Size (bytes) + ) + + ;; Verify: Read byte at offset 100. Should be 42. + (i32.load8_u (i32.const 100)) + (i32.const 42) + i32.eq + ) + + (export "finish" (func $test_bulk_ops)) +) diff --git a/src/test/app/wasm_fixtures/wat/proposal_extended_const.wat b/src/test/app/wasm_fixtures/wat/proposal_extended_const.wat new file mode 100644 index 0000000000..1b24f8ff45 --- /dev/null +++ b/src/test/app/wasm_fixtures/wat/proposal_extended_const.wat @@ -0,0 +1,15 @@ +(module + ;; 1. Define a global using an EXTENDED constant expression. + ;; MVP only allows (i32.const X). + ;; This proposal allows (i32.add (i32.const X) (i32.const Y)). + (global $g i32 (i32.add (i32.const 10) (i32.const 32))) + + (func $finish (result i32) + ;; 2. verify the global equals 42 + global.get $g + i32.const 42 + i32.eq + ) + + (export "finish" (func $finish)) +) diff --git a/src/test/app/wasm_fixtures/wat/proposal_float_to_int.wat b/src/test/app/wasm_fixtures/wat/proposal_float_to_int.wat new file mode 100644 index 0000000000..3d52bfa399 --- /dev/null +++ b/src/test/app/wasm_fixtures/wat/proposal_float_to_int.wat @@ -0,0 +1,18 @@ +(module + (func $test_saturation (result i32) + ;; 1. Push a float that is too big for a 32-bit integer + ;; 1e10 (10 billion) > 2.14 billion (Max i32) + f32.const 1.0e10 + + ;; 2. Attempt saturating conversion (Opcode 0xFC 0x00) + ;; If supported: Clamps to MAX_I32. + ;; If disabled: Validation error (unknown instruction). + i32.trunc_sat_f32_s + + ;; 3. Check if result is MAX_I32 (2147483647) + i32.const 2147483647 + i32.eq + ) + + (export "finish" (func $test_saturation)) +) diff --git a/src/test/app/wasm_fixtures/wat/proposal_gc_struct_new.wat b/src/test/app/wasm_fixtures/wat/proposal_gc_struct_new.wat new file mode 100644 index 0000000000..b9a1e5c519 --- /dev/null +++ b/src/test/app/wasm_fixtures/wat/proposal_gc_struct_new.wat @@ -0,0 +1,12 @@ +;; generated by wasm-tools print gc_test.wasm that has the following hex +;; 0061736d01000000010b026000017f5f027f017f0103020100070a010666696e69736800000a0a010800fb01011a41010b +(module + (type (;0;) (func (result i32))) + (type (;1;) (struct (field (mut i32)) (field (mut i32)))) + (export "finish" (func 0)) + (func (;0;) (type 0) (result i32) + struct.new_default 1 + drop + i32.const 1 + ) +) diff --git a/src/test/app/wasm_fixtures/wat/proposal_multi_value.wat b/src/test/app/wasm_fixtures/wat/proposal_multi_value.wat new file mode 100644 index 0000000000..30dda4ce12 --- /dev/null +++ b/src/test/app/wasm_fixtures/wat/proposal_multi_value.wat @@ -0,0 +1,22 @@ +(module + ;; 1. Function returning TWO values (Multi-Value feature) + (func $get_numbers (result i32 i32) + i32.const 10 + i32.const 20 + ) + + (func $finish (result i32) + ;; Call pushes [10, 20] onto the stack + call $get_numbers + + ;; 2. Block taking TWO parameters (Multi-Value feature) + ;; It consumes the [10, 20] from the stack. + block (param i32 i32) (result i32) + i32.add ;; 10 + 20 = 30 + i32.const 30 ;; Expected result + i32.eq ;; Compare: returns 1 if equal + end + ) + + (export "finish" (func $finish)) +) diff --git a/src/test/app/wasm_fixtures/wat/proposal_mutable_global.wat b/src/test/app/wasm_fixtures/wat/proposal_mutable_global.wat new file mode 100644 index 0000000000..2b1b5ff648 --- /dev/null +++ b/src/test/app/wasm_fixtures/wat/proposal_mutable_global.wat @@ -0,0 +1,25 @@ +(module + ;; Define a mutable global initialized to 0 + (global $counter (mut i32) (i32.const 0)) + + ;; EXPORTING a mutable global is the key feature of this proposal. + ;; In strict MVP, exported globals had to be immutable (const). + (export "counter" (global $counter)) + + (func $finish (result i32) + ;; 1. Get current value + global.get $counter + + ;; 2. Add 1 + i32.const 1 + i32.add + + ;; 3. Set new value (Mutation) + global.set $counter + + ;; 4. Return 1 for success + i32.const 1 + ) + + (export "finish" (func $finish)) +) diff --git a/src/test/app/wasm_fixtures/wat/proposal_ref_types.wat b/src/test/app/wasm_fixtures/wat/proposal_ref_types.wat new file mode 100644 index 0000000000..c339b9176f --- /dev/null +++ b/src/test/app/wasm_fixtures/wat/proposal_ref_types.wat @@ -0,0 +1,18 @@ +(module + ;; Import a table from the host that holds externrefs + (import "env" "table" (table 1 externref)) + + (func $test_ref_types (result i32) + ;; Store a null externref into the table at index 0 + ;; If reference_types is disabled, 'externref' and 'ref.null' will fail parsing. + (table.set + (i32.const 0) ;; Index + (ref.null extern) ;; Value (Null External Reference) + ) + + ;; Return 1 (Success) + i32.const 1 + ) + + (export "finish" (func $test_ref_types)) +) diff --git a/src/test/app/wasm_fixtures/wat/proposal_sign_ext.wat b/src/test/app/wasm_fixtures/wat/proposal_sign_ext.wat new file mode 100644 index 0000000000..ab9061fbb1 --- /dev/null +++ b/src/test/app/wasm_fixtures/wat/proposal_sign_ext.wat @@ -0,0 +1,18 @@ +(module + (func $test_sign_ext (result i32) + ;; Push 255 (0x000000FF) onto the stack + i32.const 255 + + ;; Sign-extend from 8-bit to 32-bit + ;; If 255 is treated as an i8, it is -1. + ;; Result should be -1 (0xFFFFFFFF). + ;; Without this proposal, this opcode (0xC0) causes a validation error. + i32.extend8_s + + ;; Check if result is -1 + i32.const -1 + i32.eq + ) + + (export "finish" (func $test_sign_ext)) +) diff --git a/src/test/app/wasm_fixtures/wat/proposal_stringref.wat b/src/test/app/wasm_fixtures/wat/proposal_stringref.wat new file mode 100644 index 0000000000..c9f8030faf --- /dev/null +++ b/src/test/app/wasm_fixtures/wat/proposal_stringref.wat @@ -0,0 +1 @@ +;;hard to generate diff --git a/src/test/app/wasm_fixtures/wat/proposal_tail_call.wat b/src/test/app/wasm_fixtures/wat/proposal_tail_call.wat new file mode 100644 index 0000000000..f2c883931a --- /dev/null +++ b/src/test/app/wasm_fixtures/wat/proposal_tail_call.wat @@ -0,0 +1,15 @@ +(module + ;; Define a simple function we can tail-call + (func $target (result i32) + i32.const 1 + ) + + (func $finish (result i32) + ;; Try to use the 'return_call' instruction (Opcode 0x12) + ;; If Tail Call proposal is disabled, this fails to Compile/Validate. + ;; If enabled, it jumps to $target, which returns 1. + return_call $target + ) + + (export "finish" (func $finish)) +) diff --git a/src/test/app/wasm_fixtures/wat/table_0_elements.wat b/src/test/app/wasm_fixtures/wat/table_0_elements.wat new file mode 100644 index 0000000000..7f2d1abc64 --- /dev/null +++ b/src/test/app/wasm_fixtures/wat/table_0_elements.wat @@ -0,0 +1,10 @@ +(module + ;; Define a table with exactly 0 entries + (table 0 funcref) + + ;; Standard finish function + (func $finish (result i32) + i32.const 1 + ) + (export "finish" (func $finish)) +) diff --git a/src/test/app/wasm_fixtures/wat/table_2_tables.wat b/src/test/app/wasm_fixtures/wat/table_2_tables.wat new file mode 100644 index 0000000000..fd007f410a --- /dev/null +++ b/src/test/app/wasm_fixtures/wat/table_2_tables.wat @@ -0,0 +1,24 @@ +(module + ;; Define a dummy function to put in the tables + (func $dummy) + + ;; TABLE 0: The default table (allowed in MVP) + ;; Size: 1 initial, 1 max + (table $t0 1 1 funcref) + + ;; Initialize Table 0 at index 0 + (elem (table $t0) (i32.const 0) $dummy) + + ;; TABLE 1: The second table (Requires Reference Types proposal) + ;; If strict MVP is enforced, the parser should error here. + (table $t1 1 1 funcref) + + ;; Initialize Table 1 at index 0 + (elem (table $t1) (i32.const 0) $dummy) + + (func $finish (result i32) + ;; If we successfully loaded a module with 2 tables, return 1. + i32.const 1 + ) + (export "finish" (func $finish)) +) diff --git a/src/test/app/wasm_fixtures/wat/table_64_elements.wat b/src/test/app/wasm_fixtures/wat/table_64_elements.wat new file mode 100644 index 0000000000..3ff990b228 --- /dev/null +++ b/src/test/app/wasm_fixtures/wat/table_64_elements.wat @@ -0,0 +1,25 @@ +(module + ;; Define a table with exactly 64 entries + (table 64 funcref) + + ;; A dummy function to reference + (func $dummy) + + ;; Initialize the table at offset 0 with 64 references to $dummy + (elem (i32.const 0) + $dummy $dummy $dummy $dummy $dummy $dummy $dummy $dummy ;; 8 + $dummy $dummy $dummy $dummy $dummy $dummy $dummy $dummy ;; 16 + $dummy $dummy $dummy $dummy $dummy $dummy $dummy $dummy ;; 24 + $dummy $dummy $dummy $dummy $dummy $dummy $dummy $dummy ;; 32 + $dummy $dummy $dummy $dummy $dummy $dummy $dummy $dummy ;; 40 + $dummy $dummy $dummy $dummy $dummy $dummy $dummy $dummy ;; 48 + $dummy $dummy $dummy $dummy $dummy $dummy $dummy $dummy ;; 56 + $dummy $dummy $dummy $dummy $dummy $dummy $dummy $dummy ;; 64 + ) + + ;; Standard finish function + (func $finish (result i32) + i32.const 1 + ) + (export "finish" (func $finish)) +) diff --git a/src/test/app/wasm_fixtures/wat/table_65_elements.wat b/src/test/app/wasm_fixtures/wat/table_65_elements.wat new file mode 100644 index 0000000000..db3efa0a18 --- /dev/null +++ b/src/test/app/wasm_fixtures/wat/table_65_elements.wat @@ -0,0 +1,25 @@ +(module + ;; Define a table with exactly 65 entries + (table 65 funcref) + + ;; A dummy function to reference + (func $dummy) + + ;; Initialize the table at offset 0 with 65 references to $dummy + (elem (i32.const 0) + $dummy $dummy $dummy $dummy $dummy $dummy $dummy $dummy ;; 8 + $dummy $dummy $dummy $dummy $dummy $dummy $dummy $dummy ;; 16 + $dummy $dummy $dummy $dummy $dummy $dummy $dummy $dummy ;; 24 + $dummy $dummy $dummy $dummy $dummy $dummy $dummy $dummy ;; 32 + $dummy $dummy $dummy $dummy $dummy $dummy $dummy $dummy ;; 40 + $dummy $dummy $dummy $dummy $dummy $dummy $dummy $dummy ;; 48 + $dummy $dummy $dummy $dummy $dummy $dummy $dummy $dummy ;; 56 + $dummy $dummy $dummy $dummy $dummy $dummy $dummy $dummy ;; 64 + $dummy ;; 65 (The one that breaks the camel's back) + ) + + (func $finish (result i32) + i32.const 1 + ) + (export "finish" (func $finish)) +) diff --git a/src/test/app/wasm_fixtures/wat/table_uint_max.wat b/src/test/app/wasm_fixtures/wat/table_uint_max.wat new file mode 100644 index 0000000000..31c15b8be2 --- /dev/null +++ b/src/test/app/wasm_fixtures/wat/table_uint_max.wat @@ -0,0 +1,15 @@ +(module + ;; Definition: (table ) + ;; We use 0xFFFFFFFF (4,294,967,295), which is the unsigned equivalent of -1. + ;; This tests if the runtime handles the maximum possible u32 value + ;; without integer overflows or attempting a massive allocation. + ;; + ;; Note that using -1 as the table size cannot be parsed by wasm-tools or wat2wasm + (table 0xFFFFFFFF funcref) + + (func $finish (result i32) + ;; If the module loads despite the massive table, return 1. + i32.const 1 + ) + (export "finish" (func $finish)) +) diff --git a/src/test/app/wasm_fixtures/wat/trap_divide_by_0.wat b/src/test/app/wasm_fixtures/wat/trap_divide_by_0.wat new file mode 100644 index 0000000000..8fda7bdae0 --- /dev/null +++ b/src/test/app/wasm_fixtures/wat/trap_divide_by_0.wat @@ -0,0 +1,15 @@ +(module + (func $finish (export "finish") (result i32) + ;; Setup for Requirement 2: Divide an i32 by 0 + i32.const 42 ;; Push numerator + i32.const 0 ;; Push denominator (0) + i32.div_s ;; Perform signed division (42 / 0) + + ;; --- NOTE: Execution usually traps (crashes) at the line above --- + + ;; Logic to satisfy Requirement 1: Return i32 = 1 + ;; If execution continued, we would drop the division result and return 1 + drop ;; Clear the stack + i32.const 1 ;; Push the return value + ) +) diff --git a/src/test/app/wasm_fixtures/wat/trap_func_signature_mismatch.wat b/src/test/app/wasm_fixtures/wat/trap_func_signature_mismatch.wat new file mode 100644 index 0000000000..a712d1ac8d --- /dev/null +++ b/src/test/app/wasm_fixtures/wat/trap_func_signature_mismatch.wat @@ -0,0 +1,33 @@ +(module + ;; Define a table with 1 slot + (table 1 funcref) + + ;; Define Type A: Takes nothing, returns nothing + (type $type_void (func)) + + ;; Define Type B: Takes nothing, returns i32 + (type $type_i32 (func (result i32))) + + ;; Define a function of Type A + (func $void_func (type $type_void) + nop + ) + + ;; Put Type A function into Table[0] + (elem (i32.const 0) $void_func) + + (func $finish (result i32) + ;; Attempt to call Index 0, but CLAIM we expect Type B (result i32). + ;; The function at Index 0 matches Type A. + ;; TRAP: "indirect call type mismatch" + + ;; 1. Push the table index (0) onto the stack + i32.const 0 + + ;; 2. Call indirect using Type B signature. + ;; This pops the index (0) from the stack. + call_indirect (type $type_i32) + ) + + (export "finish" (func $finish)) +) diff --git a/src/test/app/wasm_fixtures/wat/trap_int_overflow.wat b/src/test/app/wasm_fixtures/wat/trap_int_overflow.wat new file mode 100644 index 0000000000..810236df79 --- /dev/null +++ b/src/test/app/wasm_fixtures/wat/trap_int_overflow.wat @@ -0,0 +1,18 @@ +(module + (func $test_int_overflow (result i32) + ;; 1. Push INT_MIN (-2147483648) + ;; In Hex: 0x80000000 + i32.const -2147483648 + + ;; 2. Push -1 + i32.const -1 + + ;; 3. Signed Division + ;; This specific case is the ONLY integer arithmetic operation + ;; (besides divide by zero) that traps in the spec. + ;; Result would be +2147483648, which is too big for signed i32. + i32.div_s + ) + + (export "finish" (func $test_int_overflow)) +) diff --git a/src/test/app/wasm_fixtures/wat/trap_null_call.wat b/src/test/app/wasm_fixtures/wat/trap_null_call.wat new file mode 100644 index 0000000000..f806d922f7 --- /dev/null +++ b/src/test/app/wasm_fixtures/wat/trap_null_call.wat @@ -0,0 +1,22 @@ +(module + ;; Table size is 1, so Index 0 is VALID bounds. + ;; However, we do NOT initialize it, so it contains 'ref.null'. + (table 1 funcref) + + (type $t (func (result i32))) + + (func $finish (result i32) + ;; Call Index 0. + ;; Bounds check passes (0 < 1). + ;; Null check fails. + ;; TRAP: "uninitialized element" or "undefined element" + + ;; 1. Push the index (0) onto the stack first + i32.const 0 + + ;; 2. Perform the call. This pops the index. + call_indirect (type $t) + ) + + (export "finish" (func $finish)) +) diff --git a/src/test/app/wasm_fixtures/wat/trap_unreachable.wat b/src/test/app/wasm_fixtures/wat/trap_unreachable.wat new file mode 100644 index 0000000000..4bc15569fc --- /dev/null +++ b/src/test/app/wasm_fixtures/wat/trap_unreachable.wat @@ -0,0 +1,12 @@ +(module + (func $finish (result i32) + ;; This instruction explicitly causes a trap. + ;; It consumes no fuel (beyond the instruction itself) and stops execution. + unreachable + + ;; This code is dead and never reached + i32.const 1 + ) + + (export "finish" (func $finish)) +) diff --git a/src/test/app/wasm_fixtures/wat/wasi_get_time.wat b/src/test/app/wasm_fixtures/wat/wasi_get_time.wat new file mode 100644 index 0000000000..049ecf0be1 --- /dev/null +++ b/src/test/app/wasm_fixtures/wat/wasi_get_time.wat @@ -0,0 +1,38 @@ +(module + ;; Import clock_time_get from WASI + ;; Signature: (param clock_id precision return_ptr) (result errno) + (import "wasi_snapshot_preview1" "clock_time_get" + (func $clock_time_get (param i32 i64 i32) (result i32)) + ) + + (memory 1) + (export "memory" (memory 0)) + + (func $finish (result i32) + ;; We will store the timestamp (a 64-bit integer) at address 0. + ;; No setup required in memory beforehand! + + ;; Call the function + (call $clock_time_get + (i32.const 0) ;; clock_id: 0 = Realtime (Wallclock) + (i64.const 1000) ;; precision: 1000ns (hint to OS) + (i32.const 0) ;; result_ptr: Write the time to address 0 + ) + + ;; The function returns an 'errno' (error code). + ;; 0 = Success. Anything else = Error. + + ;; Check if errno (top of stack) is 0 + i32.eqz + if (result i32) + ;; Success! The time is now stored in heap[0..8]. + ;; We return 1 as requested. + i32.const 1 + else + ;; Failed (maybe WASI is disabled or clock is missing) + i32.const -1 + end + ) + + (export "finish" (func $finish)) +) diff --git a/src/test/app/wasm_fixtures/wat/wasi_print.wat b/src/test/app/wasm_fixtures/wat/wasi_print.wat new file mode 100644 index 0000000000..18e8a088f4 --- /dev/null +++ b/src/test/app/wasm_fixtures/wat/wasi_print.wat @@ -0,0 +1,59 @@ +(module + ;; Import WASI fd_write + ;; Signature: (fd, iovs_ptr, iovs_len, nwritten_ptr) -> errno + (import "wasi_snapshot_preview1" "fd_write" + (func $fd_write (param i32 i32 i32 i32) (result i32)) + ) + + (memory 1) + (export "memory" (memory 0)) + + ;; --- DATA SEGMENTS --- + + ;; 1. The String Data "Hello\n" placed at offset 16 + ;; We assume offset 0-16 is reserved for the IOVec struct + (data (i32.const 16) "Hello\n") + + ;; 2. The IO Vector (struct iovec) placed at offset 0 + ;; Structure: { buf_ptr: u32, buf_len: u32 } + + ;; Field 1: buf_ptr = 16 (Location of "Hello\n") + ;; Encoded in little-endian: 10 00 00 00 + (data (i32.const 0) "\10\00\00\00") + + ;; Field 2: buf_len = 6 (Length of "Hello\n") + ;; Encoded in little-endian: 06 00 00 00 + (data (i32.const 4) "\06\00\00\00") + + (func $finish (result i32) + (local $nwritten_ptr i32) + + ;; We will ask WASI to write the "number of bytes written" to address 24 + ;; (safely after our string data) + i32.const 24 + local.set $nwritten_ptr + + ;; Call fd_write + (call $fd_write + (i32.const 1) ;; fd: 1 = STDOUT + (i32.const 0) ;; iovs_ptr: Address 0 (where we defined the struct) + (i32.const 1) ;; iovs_len: We are passing 1 vector + (local.get $nwritten_ptr) ;; nwritten_ptr: Address 24 + ) + + ;; The function returns an 'errno' (i32). + ;; 0 means Success. + + ;; Check if errno == 0 + i32.eqz + if (result i32) + ;; Success: Return 1 + i32.const 1 + else + ;; Failure: Return -1 + i32.const -1 + end + ) + + (export "finish" (func $finish)) +)