mirror of
https://github.com/XRPLF/rippled.git
synced 2026-08-22 14:50:54 +00:00
Memory transfer limit (#7000)
Count bytes copied across the boundaries (Wasm VM <-> Hostfunctions) and return error if limit reached (1 mb default)
This commit is contained in:
@@ -41,6 +41,8 @@ namespace xrpl {
|
||||
|
||||
using SFieldCRef = std::reference_wrapper<SField const>;
|
||||
|
||||
constexpr int64_t unalignedGas = 50;
|
||||
|
||||
static inline Expected<std::int64_t, HostFunctionError>
|
||||
checkGas(WasmRuntimeWrapper& rt, int64_t delta)
|
||||
{
|
||||
@@ -81,6 +83,21 @@ checkGas(WasmRuntimeWrapper& rt, WasmImportFunc const& impFunc)
|
||||
return *g;
|
||||
}
|
||||
|
||||
static inline Expected<std::int64_t, HostFunctionError>
|
||||
checkTransfer(WasmRuntimeWrapper& rt, int64_t delta)
|
||||
{
|
||||
auto const transLimit = rt.getTransferLimit();
|
||||
int64_t const x = transLimit >= delta ? transLimit - delta : 0;
|
||||
|
||||
if (rt.setTransferLimit(x) < 0)
|
||||
return Unexpected(HostFunctionError::Internal); // LCOV_EXCL_LINE
|
||||
|
||||
if (transLimit < delta)
|
||||
return Unexpected(HostFunctionError::OutOfTransferLimit);
|
||||
|
||||
return x;
|
||||
}
|
||||
|
||||
static Expected<std::tuple<HostFunctions&, WasmImportFunc const&>, wasm_trap_t*>
|
||||
mainCheck(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results)
|
||||
{
|
||||
@@ -146,6 +163,9 @@ setData(
|
||||
if (srcSize > dstSize)
|
||||
return hfErrorToInt(HostFunctionError::BufferTooSmall);
|
||||
|
||||
if (auto t = checkTransfer(runtime, srcSize); !t)
|
||||
return hfErrorToInt(t.error());
|
||||
|
||||
memcpy(memory.p + dst, src, srcSize);
|
||||
|
||||
return srcSize;
|
||||
@@ -255,6 +275,9 @@ getDataUInt256(WasmRuntimeWrapper& runtime, wasm_val_vec_t const* params, int32_
|
||||
if (slice->size() != uint256::size())
|
||||
return Unexpected(HostFunctionError::InvalidParams);
|
||||
|
||||
if (auto t = checkTransfer(runtime, uint256::size()); !t)
|
||||
return Unexpected(t.error());
|
||||
|
||||
return uint256::fromVoid(slice->data());
|
||||
}
|
||||
|
||||
@@ -268,6 +291,9 @@ getDataAccountID(WasmRuntimeWrapper& runtime, wasm_val_vec_t const* params, int3
|
||||
if (slice->size() != AccountID::size())
|
||||
return Unexpected(HostFunctionError::InvalidParams);
|
||||
|
||||
if (auto t = checkTransfer(runtime, AccountID::size()); !t)
|
||||
return Unexpected(t.error());
|
||||
|
||||
return AccountID::fromVoid(slice->data());
|
||||
}
|
||||
|
||||
@@ -281,6 +307,9 @@ getDataCurrency(WasmRuntimeWrapper& runtime, wasm_val_vec_t const* params, int32
|
||||
if (slice->size() != Currency::size())
|
||||
return Unexpected(HostFunctionError::InvalidParams);
|
||||
|
||||
if (auto t = checkTransfer(runtime, Currency::size()); !t)
|
||||
return Unexpected(t.error());
|
||||
|
||||
return Currency::fromVoid(slice->data());
|
||||
}
|
||||
|
||||
@@ -293,12 +322,18 @@ getDataAsset(WasmRuntimeWrapper& runtime, wasm_val_vec_t const* params, int32_t&
|
||||
|
||||
if (slice->size() == MPTID::size())
|
||||
{
|
||||
if (auto t = checkTransfer(runtime, slice->size()); !t)
|
||||
return Unexpected(t.error());
|
||||
|
||||
auto const mptid = MPTID::fromVoid(slice->data());
|
||||
return Asset{mptid};
|
||||
}
|
||||
|
||||
if (slice->size() == Currency::size())
|
||||
{
|
||||
if (auto t = checkTransfer(runtime, slice->size()); !t)
|
||||
return Unexpected(t.error());
|
||||
|
||||
auto const currency = Currency::fromVoid(slice->data());
|
||||
auto const issue = Issue{currency, xrpAccount()};
|
||||
if (!issue.native())
|
||||
@@ -309,6 +344,9 @@ getDataAsset(WasmRuntimeWrapper& runtime, wasm_val_vec_t const* params, int32_t&
|
||||
|
||||
if (slice->size() == (Currency::size() + AccountID::size()))
|
||||
{
|
||||
if (auto t = checkTransfer(runtime, slice->size()); !t)
|
||||
return Unexpected(t.error());
|
||||
|
||||
auto const issue = Issue(
|
||||
Currency::fromVoid(slice->data()),
|
||||
AccountID::fromVoid(slice->data() + Currency::size()));
|
||||
@@ -349,6 +387,12 @@ getDataLocator(WasmRuntimeWrapper& runtime, wasm_val_vec_t const* params, int32_
|
||||
if ((p & (alignof(int32_t) - 1)) != 0u)
|
||||
{ // unaligned
|
||||
|
||||
// Use gas and transfer limit for copying
|
||||
if (auto g = checkGas(runtime, unalignedGas); !g)
|
||||
return Unexpected(g.error());
|
||||
if (auto t = checkTransfer(runtime, slice->size()); !t)
|
||||
return Unexpected(t.error());
|
||||
|
||||
std::vector<int32_t> locBuf(locSize);
|
||||
memcpy(&locBuf[0], slice->data(), slice->size());
|
||||
FieldLocator locator(std::move(locBuf));
|
||||
@@ -481,6 +525,7 @@ returnResult(
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
wasm_trap_t*
|
||||
HostFuncMain_wrap(WASM_CB_PARAMS_LIST)
|
||||
{
|
||||
@@ -490,9 +535,9 @@ HostFuncMain_wrap(WASM_CB_PARAMS_LIST)
|
||||
{
|
||||
auto const mc = mainCheck(env, params, results);
|
||||
if (!mc)
|
||||
return mc.error(); // LCOV_EXCL_LINE
|
||||
auto& [hf, impFunc] = *mc;
|
||||
return mc.error();
|
||||
|
||||
auto& [hf, impFunc] = *mc;
|
||||
hfName = impFunc.name;
|
||||
auto* fWrap = reinterpret_cast<wasmSecondaryCbFuncType*>(impFunc.wrap);
|
||||
return fWrap(hf, params, results);
|
||||
@@ -1677,6 +1722,7 @@ class MockWasmRuntimeWrapper : public WasmRuntimeWrapper
|
||||
Wmem mem_;
|
||||
|
||||
std::int64_t gas_ = 1'000'000;
|
||||
std::int64_t transferLimit_ = kWasmTransferLimit;
|
||||
|
||||
public:
|
||||
MockWasmRuntimeWrapper(Wmem memory) : mem_(memory)
|
||||
@@ -1702,6 +1748,19 @@ public:
|
||||
gas_ = gas;
|
||||
return gas_;
|
||||
}
|
||||
|
||||
std::int64_t
|
||||
getTransferLimit() override
|
||||
{
|
||||
return transferLimit_;
|
||||
}
|
||||
|
||||
std::int64_t
|
||||
setTransferLimit(std::int64_t x) override
|
||||
{
|
||||
transferLimit_ = x;
|
||||
return transferLimit_;
|
||||
}
|
||||
};
|
||||
|
||||
bool
|
||||
|
||||
@@ -106,6 +106,18 @@ public:
|
||||
{
|
||||
return iw_.setGas(gas);
|
||||
}
|
||||
|
||||
std::int64_t
|
||||
getTransferLimit() override
|
||||
{
|
||||
return iw_.getTransferLimit();
|
||||
}
|
||||
|
||||
std::int64_t
|
||||
setTransferLimit(std::int64_t x) override
|
||||
{
|
||||
return iw_.setTransferLimit(x);
|
||||
}
|
||||
};
|
||||
|
||||
InstancePtr
|
||||
@@ -247,6 +259,32 @@ InstanceWrapper::setGas(std::int64_t gas) const
|
||||
return gas;
|
||||
}
|
||||
|
||||
std::int64_t
|
||||
InstanceWrapper::getTransferLimit() const
|
||||
{
|
||||
if (store_ == nullptr)
|
||||
return -1; // LCOV_EXCL_LINE
|
||||
|
||||
return transferLimit_;
|
||||
}
|
||||
|
||||
std::int64_t
|
||||
InstanceWrapper::setTransferLimit(std::int64_t x)
|
||||
{
|
||||
if (store_ == nullptr)
|
||||
return -1; // LCOV_EXCL_LINE
|
||||
if (x < 0)
|
||||
{
|
||||
transferLimit_ = std::numeric_limits<decltype(transferLimit_)>::max();
|
||||
}
|
||||
else
|
||||
{
|
||||
transferLimit_ = x;
|
||||
}
|
||||
|
||||
return transferLimit_;
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
ModulePtr
|
||||
|
||||
@@ -166,8 +166,11 @@ class VirtualRuntime : public WasmRuntimeWrapper
|
||||
{
|
||||
Bytes buffer_;
|
||||
std::int64_t gas_ = 1'000'000;
|
||||
std::int64_t transferLimit_ = kWasmTransferLimit;
|
||||
|
||||
public:
|
||||
static constexpr std::int64_t transferDiff = 1024;
|
||||
|
||||
VirtualRuntime() : buffer_(1024 * 1024)
|
||||
{
|
||||
}
|
||||
@@ -203,6 +206,37 @@ public:
|
||||
return gas_;
|
||||
}
|
||||
|
||||
std::int64_t
|
||||
getTransferLimit() override
|
||||
{
|
||||
transferLimit_ -= transferDiff;
|
||||
return transferLimit_;
|
||||
}
|
||||
|
||||
[[nodiscard]] std::int64_t
|
||||
getTestTransferLimit() const
|
||||
{
|
||||
return transferLimit_;
|
||||
}
|
||||
|
||||
std::int64_t
|
||||
setTransferLimit(std::int64_t x) override
|
||||
{
|
||||
if (x == -2)
|
||||
return -1;
|
||||
|
||||
if (x < 0)
|
||||
{
|
||||
transferLimit_ = std::numeric_limits<decltype(transferLimit_)>::max();
|
||||
}
|
||||
else
|
||||
{
|
||||
transferLimit_ = x;
|
||||
}
|
||||
|
||||
return transferLimit_;
|
||||
}
|
||||
|
||||
void
|
||||
checkIdx(WasmValVec const& params, size_t i) const
|
||||
{
|
||||
@@ -6061,6 +6095,110 @@ struct HostFuncImpl_test : public beast::unit_test::Suite
|
||||
BEAST_EXPECT(ex);
|
||||
}
|
||||
|
||||
void
|
||||
testTransferLimit()
|
||||
{
|
||||
testcase("transferLimit");
|
||||
using namespace test::jtx;
|
||||
|
||||
Env env{*this};
|
||||
OpenView ov{*env.current()};
|
||||
ApplyContext ac = createApplyContext(env, ov);
|
||||
auto const dummyEscrow = keylet::escrow(env.master, env.seq(env.master));
|
||||
VirtualRuntime vrt;
|
||||
WasmHostFunctionsImpl hfs(ac, dummyEscrow);
|
||||
|
||||
auto import = xrpl::createWasmImport(hfs);
|
||||
hfs.setRT(vrt);
|
||||
|
||||
// Test 1: Test setData() - copying FROM host TO wasm
|
||||
// Multiple calls to getLedgerSqn() which uses setData() to write result to WASM memory
|
||||
vrt.setTransferLimit(kWasmTransferLimit + 1024);
|
||||
|
||||
// hfs.getLedgerSqn();
|
||||
for (int i = 0; i < (kWasmTransferLimit / vrt.transferDiff) - 3; ++i)
|
||||
{
|
||||
WasmValVec params(2), result(1);
|
||||
|
||||
auto* trap = ww(&import.at("ldgr_index"), params, result, 0, sizeof(std::uint32_t));
|
||||
|
||||
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
|
||||
BEAST_EXPECT(result[0].of.i32 == sizeof(std::uint32_t)) &&
|
||||
BEAST_EXPECT(vrt.getUint32(params, 0) == env.current()->header().seq);
|
||||
}
|
||||
|
||||
BEAST_EXPECT((vrt.getTestTransferLimit() >= 0) && (vrt.getTestTransferLimit() < 1024));
|
||||
|
||||
// Next call should hit OutOfTransferLimit
|
||||
{
|
||||
WasmValVec params(2), result(1);
|
||||
auto* trap = ww(&import.at("ldgr_index"), params, result, 0, sizeof(std::uint32_t));
|
||||
|
||||
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
|
||||
BEAST_EXPECT(
|
||||
result[0].of.i32 == hfErrorToInt(HostFunctionError::OutOfTransferLimit));
|
||||
}
|
||||
|
||||
// After limit exhausted, all next call return OutOfTransferLimit
|
||||
{
|
||||
WasmValVec params(2), result(1);
|
||||
auto* trap = ww(&import.at("ldgr_index"), params, result, 0, sizeof(std::uint32_t));
|
||||
|
||||
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
|
||||
BEAST_EXPECT(
|
||||
result[0].of.i32 == hfErrorToInt(HostFunctionError::OutOfTransferLimit));
|
||||
}
|
||||
|
||||
// Reset transfer limit to a small value that can accommodate overhead but not AccountID
|
||||
// copy
|
||||
vrt.setTransferLimit(vrt.transferDiff + 10);
|
||||
|
||||
Account const alice("alice");
|
||||
auto const aliceID = env.master.id();
|
||||
vrt.setBytes(0, aliceID.data(), AccountID::size());
|
||||
|
||||
// This should fail because getDataAccountID() needs to copy AccountID (20 bytes)
|
||||
// After getTransferLimit() overhead (1024), we only have 10 bytes left, not enough for 20
|
||||
{
|
||||
WasmValVec params(4), result(1);
|
||||
auto* trap =
|
||||
ww(&import.at("accountroot_id"), params, result, 0, AccountID::size(), 100, 32);
|
||||
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
|
||||
BEAST_EXPECT(
|
||||
result[0].of.i32 == hfErrorToInt(HostFunctionError::OutOfTransferLimit));
|
||||
}
|
||||
|
||||
// Verify that reading slices (without copying) does NOT consume transfer limit
|
||||
vrt.setTransferLimit(vrt.transferDiff + 10);
|
||||
|
||||
// trace() uses getDataString() -> getDataSlice() which does NOT check transfer limit
|
||||
std::string testMsg = "This message is longer than 10 bytes to prove slices don't count";
|
||||
vrt.setBytes(0, testMsg.data(), testMsg.size());
|
||||
vrt.setBytes(100, (uint8_t const*)"dummy", 5); // Empty data slice for trace
|
||||
{
|
||||
WasmValVec params(5), result(1);
|
||||
// trace(msg_ptr, msg_len, data_ptr, data_len, asHex)
|
||||
auto* trap = ww(&import.at("trace"), params, result, 0, testMsg.size(), 100, 5, 0);
|
||||
|
||||
// Should succeed even though message is >10 bytes, because trace only uses slices
|
||||
// (no transfer limit check in getDataSlice)
|
||||
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
|
||||
BEAST_EXPECT(result[0].of.i32 == 0);
|
||||
}
|
||||
|
||||
// setData should return when transfer limit is exhausted
|
||||
// After trace consumed overhead (1024 bytes), we have 10 - 1024 = negative limit left
|
||||
{
|
||||
WasmValVec params(2), result(1);
|
||||
auto* trap = ww(&import.at("parent_ldgr_hash"), params, result, 500, 32);
|
||||
|
||||
// the transfer limit went negative
|
||||
BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) &&
|
||||
BEAST_EXPECT(
|
||||
result[0].of.i32 == hfErrorToInt(HostFunctionError::OutOfTransferLimit));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
run() override
|
||||
{
|
||||
@@ -6099,6 +6237,8 @@ struct HostFuncImpl_test : public beast::unit_test::Suite
|
||||
testFloats();
|
||||
|
||||
testVectorIndexes();
|
||||
|
||||
testTransferLimit();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user