From 27468ddbcf2e8de7e559189aee8dcea97bd31af8 Mon Sep 17 00:00:00 2001 From: Olek <115580134+oleks-rip@users.noreply.github.com> Date: Thu, 19 Mar 2026 12:46:58 -0400 Subject: [PATCH] Add import / export sections test (#6497) --- src/test/app/Wasm_test.cpp | 166 +++++++++++++++++++++--- src/test/app/wasm_fixtures/fixtures.cpp | 6 + src/test/app/wasm_fixtures/fixtures.h | 2 + 3 files changed, 155 insertions(+), 19 deletions(-) diff --git a/src/test/app/Wasm_test.cpp b/src/test/app/Wasm_test.cpp index bf6bb0eb1a..041b73f0ca 100644 --- a/src/test/app/Wasm_test.cpp +++ b/src/test/app/Wasm_test.cpp @@ -32,6 +32,86 @@ hexToBytes(std::string const& hex) return Bytes(ws.begin(), ws.end()); } +template +unsigned +uleb128(IT& it, T val) +{ + unsigned count = 0; + do + { + std::uint8_t byte = val & 0x7f; + val >>= 7; + if (val) + byte |= 0x80; + *it++ = byte; + ++count; + } while (val != 0); + + return count; +} + +template +std::pair +uleb128(IT&& it) +{ + static_assert(sizeof(*it) == 1, "invalid iterator type"); + std::uint64_t val = 0; + std::uint64_t byte = 0; + unsigned shift = 0; + unsigned count = 0; + + do + { + if (shift > sizeof(std::uint64_t) * 8 - 7) + return {0, 0}; + byte = *it++; + val |= (byte & 0x7F) << shift; + shift += 7; + ++count; + } while (byte >= 0x80); + + return {val, count}; +} + +std::pair +getSection(Bytes const& module, std::uint8_t n) +{ + static std::uint8_t const hdr[] = {0x00, 0x61, 0x73, 0x6D}; + static std::uint8_t const ver[] = {0x01, 0x00, 0x00, 0x00}; + static std::uint8_t const lastSec = 12; + + // sections: + // 0: "Custom", 1: "Type", 2: "Import", 3: "Function", 4: "Table", 5: "Memory", 6: "Global", + // 7: "Export", 8: "Start", 9: "Element", 10: "Code", 11: "Data", 12: "DataCount" + + if (module.size() < sizeof(hdr) + sizeof(ver) + 2) + return {0, 0}; + if (memcmp(module.data(), hdr, sizeof(hdr)) != 0) + return {0, 0}; + if (memcmp(module.data() + sizeof(hdr), ver, sizeof(ver)) != 0) + return {0, 0}; + + unsigned pos = sizeof(hdr) + sizeof(ver); // sections start + for (; pos < module.size();) + { + auto const start = pos; + std::uint8_t const byte = module[pos++]; + if (byte > lastSec) + return {0, 0}; + + auto [sz, cnt] = uleb128(module.cbegin() + pos); + if (!cnt) + return {0, 0}; + if (pos + cnt + sz > module.size()) + return {0, 0}; + pos += cnt + sz; + + if (byte == n) + return {start, pos}; + } + return {0, 0}; +} + std::optional runFinishFunction(std::string const& code) { @@ -196,6 +276,71 @@ struct Wasm_test : public beast::unit_test::suite env.close(); } + void + testImpExp() + { + testcase("Wasm import/export functions"); + + auto impExpWasm = hexToBytes(impExpHex); + + using namespace test::jtx; + + Env env{*this}; + TestLedgerDataProvider hfs(env); + ImportVec imports; + WASM_IMPORT_FUNC2(imports, getLedgerSqn, "get_ledger_sqn", &hfs, 33); + WASM_IMPORT_FUNC2(imports, getParentLedgerHash, "get_parent_ledger_hash", &hfs, 60); + auto& engine = WasmEngine::instance(); + + // Test exp_func1() - should return 1 + auto re = engine.run(impExpWasm, hfs, 1'000'000, "exp_func1", {}, imports, env.journal); + checkResult(re, 1, 30); + + // Test exp_func2(5) - should return 2 * 5 = 10 + re = engine.run( + impExpWasm, hfs, 1'000'000, "exp_func2", wasmParams(5), imports, env.journal); + checkResult(re, 10, 52); + + // Test test_imports() - should call get_ledger_sqn and get_parent_ledger_hash + re = engine.run(impExpWasm, hfs, 1'000'000, "test_imports", {}, imports, env.journal); + // Should return the ledger sequence number (3 by default in test env) + checkResult(re, 3, 294); + + // Test corrupted import/export sections - invert each byte and expect failure + testcase("Wasm import/export section corruption"); + { + // Import section(#2): bytes [26, 79) - 53 bytes + // Export section(#7): bytes [90, 141) - 51 bytes + auto [importStart, importEnd] = getSection(impExpWasm, 2); + auto [exportStart, exportEnd] = getSection(impExpWasm, 7); + + BEAST_EXPECTS(importStart == 26, std::to_string(importStart)); + BEAST_EXPECTS(importEnd == 79, std::to_string(importEnd)); + BEAST_EXPECTS(exportStart == 90, std::to_string(exportStart)); + BEAST_EXPECTS(exportEnd == 141, std::to_string(exportEnd)); + + auto testInv = [&](unsigned i) { + auto corruptedWasm = impExpWasm; + corruptedWasm[i] = ~corruptedWasm[i]; // Invert byte + + // Try to run any function - should fail due to corruption + auto result = engine.run( + corruptedWasm, hfs, 1'000'000, "exp_func1", {}, imports, env.journal); + BEAST_EXPECT(!result); + }; + + // Test each byte in import section + for (unsigned i = importStart; i < importEnd; ++i) + testInv(i); + + // Test each byte in export section + for (unsigned i = exportStart; i < exportEnd; ++i) + testInv(i); + } + + env.close(); + } + void testWasmFib() { @@ -723,8 +868,7 @@ struct Wasm_test : public beast::unit_test::suite // i32.const 1001)) auto const wasmHex = "0061736d01000000010c0260017f017f60027f7f017f03030200010503010001071a03066d656d6f727902" - "0005746573743100" - "0005746573743200010a0d02050041e8070b050041e9070b"; + "00057465737431000005746573743200010a0d02050041e8070b050041e9070b"; auto const wasm = hexToBytes(wasmHex); // good params, module is working properly @@ -864,23 +1008,6 @@ struct Wasm_test : public beast::unit_test::suite env.close(); } - template - std::size_t - uleb128(IT& it, std::uint16_t val) - { - std::size_t count = 0; - do - { - std::uint8_t byte = val & 0x7f; - val >>= 7; - if (val) - byte |= 0x80; - *it++ = byte; - ++count; - } while (val != 0); - return count; - } - void testOpcodes() { @@ -1394,6 +1521,7 @@ struct Wasm_test : public beast::unit_test::suite testWasmLib(); testBadWasm(); testWasmLedgerSqn(); + testImpExp(); testWasmFib(); diff --git a/src/test/app/wasm_fixtures/fixtures.cpp b/src/test/app/wasm_fixtures/fixtures.cpp index eeae8ea49b..454a5a4b2c 100644 --- a/src/test/app/wasm_fixtures/fixtures.cpp +++ b/src/test/app/wasm_fixtures/fixtures.cpp @@ -8978,3 +8978,9 @@ extern std::string const opcReservedHex = "01010101010101010101010101010101010101010101010101010101010101010101010101010101010101410b0b0b" "0a010041000b04746573" "74"; + +extern std::string const impExpHex = + "0061736d0100000001100360027f7f017f6000017f60017f017f02330203656e760e6765745f6c65646765725f7371" + "6e000003656e76166765745f706172656e745f6c65646765725f686173680000030403010201050301000107310406" + "6d656d6f72790200096578705f66756e63310002096578705f66756e633200030c746573745f696d706f7274730004" + "0a2b03040041010b0700200041026c0b1c01027f4120410410001a41202802002100410041201001210120000b"; diff --git a/src/test/app/wasm_fixtures/fixtures.h b/src/test/app/wasm_fixtures/fixtures.h index ceace76fc0..b3b20a8f37 100644 --- a/src/test/app/wasm_fixtures/fixtures.h +++ b/src/test/app/wasm_fixtures/fixtures.h @@ -79,3 +79,5 @@ extern std::string const locals10kHex; extern std::string const functions5kHex; extern std::string const opcReservedHex; + +extern std::string const impExpHex;