未验证 提交 48c762f2 编写于 作者: W wanderingbort 提交者: GitHub

Merge pull request #1328 from EOSIO/rein_in_table_vmem_usuage

Reduce wasm table vmem usage
......@@ -6,6 +6,14 @@ namespace IR {
namespace eosio { namespace chain {
namespace wasm_constraints {
//Be aware that some of these are required to be a multiple of some internal number
constexpr unsigned maximum_linear_memory = 1024*1024; //bytes
constexpr unsigned maximum_mutable_globals = 1024; //bytes
constexpr unsigned maximum_table_elements = 1024; //elements
constexpr unsigned maximum_linear_memory_init = 64*1024; //bytes
}
//Throws if something in the module violates
void validate_eosio_wasm_constraints(const IR::Module& m);
......
......@@ -29,7 +29,7 @@ struct eosio_constraints_visitor : public nop_opcode_visitor {
// an 8 byte data type, that's fine. There will be enough of a guard on the end
// of 1MiB where it's not a problem
void fail_large_offset(U32 offset) {
if(offset >= 1024*1024)
if(offset >= wasm_constraints::maximum_linear_memory)
FC_THROW_EXCEPTION(wasm_execution_error, "Smart contract used an invalid large memory store/load offset");
}
void i32_load (LoadOrStoreImm<2> imm) override { fail_large_offset(imm.offset); }
......@@ -65,18 +65,18 @@ struct eosio_constraints_visitor : public nop_opcode_visitor {
};
void validate_eosio_wasm_constraints(const Module& m) {
if(m.memories.defs.size() && m.memories.defs[0].type.size.min > 16)
FC_THROW_EXCEPTION(wasm_execution_error, "Smart contract initial memory size must be less than or equal to 1MiB");
if(m.memories.defs.size() && m.memories.defs[0].type.size.min > wasm_constraints::maximum_linear_memory/(64*1024))
FC_THROW_EXCEPTION(wasm_execution_error, "Smart contract initial memory size must be less than or equal to ${k}KiB", ("k", wasm_constraints::maximum_linear_memory/1024));
for(const DataSegment& ds : m.dataSegments) {
if(ds.baseOffset.type != InitializerExpression::Type::i32_const)
FC_THROW_EXCEPTION(wasm_execution_error, "Smart contract has unexpected memory base offset type");
if(static_cast<uint32_t>(ds.baseOffset.i32) + ds.data.size() > 64*1024)
FC_THROW_EXCEPTION(wasm_execution_error, "Smart contract data segments must lie in first 64KiB");
if(static_cast<uint32_t>(ds.baseOffset.i32) + ds.data.size() > wasm_constraints::maximum_linear_memory_init)
FC_THROW_EXCEPTION(wasm_execution_error, "Smart contract data segments must lie in first ${k}KiB", ("k", wasm_constraints::maximum_linear_memory_init/1024));
}
if(m.tables.defs.size() && m.tables.defs[0].type.size.min > 1024)
FC_THROW_EXCEPTION(wasm_execution_error, "Smart contract table limited to 1024 elements");
if(m.tables.defs.size() && m.tables.defs[0].type.size.min > wasm_constraints::maximum_table_elements)
FC_THROW_EXCEPTION(wasm_execution_error, "Smart contract table limited to ${t} elements", ("t", wasm_constraints::maximum_table_elements));
unsigned mutable_globals_total_size = 0;
for(const GlobalDef& global_def : m.globals.defs) {
......@@ -94,8 +94,8 @@ void validate_eosio_wasm_constraints(const Module& m) {
mutable_globals_total_size += 4;
}
}
if(mutable_globals_total_size > 1024)
FC_THROW_EXCEPTION(wasm_execution_error, "Smart contract has more than 1KiB of mutable globals");
if(mutable_globals_total_size > wasm_constraints::maximum_mutable_globals)
FC_THROW_EXCEPTION(wasm_execution_error, "Smart contract has more than ${k} bytes of mutable globals", ("k", wasm_constraints::maximum_mutable_globals));
//Some of the OperatorDecoderStream users inside of WAVM track the control stack and quit parsing from
// OperatorDecoderStream when the control stack is empty (since that would indicate unreachable code).
......
......@@ -31,6 +31,8 @@ add_definitions(${LLVM_DEFINITIONS})
add_definitions(-DRUNTIME_API=DLL_EXPORT)
target_include_directories( Runtime PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/../../../chain/include )
# Link against the LLVM libraries
llvm_map_components_to_libnames(LLVM_LIBS support core passes mcjit native DebugInfoDWARF)
target_link_libraries(Runtime Platform Logging IR ${LLVM_LIBS})
......@@ -23,7 +23,7 @@ namespace LLVMJIT
std::vector<llvm::Constant*> importedFunctionPointers;
std::vector<llvm::Constant*> globalPointers;
llvm::Constant* defaultTablePointer;
llvm::Constant* defaultTableEndOffset;
llvm::Constant* defaultTableMaxElementIndex;
llvm::Constant* defaultMemoryBase;
llvm::Constant* defaultMemoryEndOffset;
......@@ -679,7 +679,7 @@ namespace LLVMJIT
// If the function index is larger than the function table size, trap.
emitConditionalTrapIntrinsic(
irBuilder.CreateICmpUGE(functionIndexZExt,moduleContext.defaultTableEndOffset),
irBuilder.CreateICmpUGE(functionIndexZExt,moduleContext.defaultTableMaxElementIndex),
"wavmIntrinsics.indirectCallIndexOutOfBounds",FunctionType::get(),{});
// Load the type for this table entry.
......@@ -1592,11 +1592,11 @@ namespace LLVMJIT
llvmI8PtrType
});
defaultTablePointer = emitLiteralPointer(moduleInstance->defaultTable->baseAddress,tableElementType->getPointerTo());
defaultTableEndOffset = emitLiteral((Uptr)moduleInstance->defaultTable->endOffset);
defaultTableMaxElementIndex = emitLiteral(((Uptr)moduleInstance->defaultTable->endOffset)/sizeof(TableInstance::FunctionElement));
}
else
{
defaultTablePointer = defaultTableEndOffset = nullptr;
defaultTablePointer = defaultTableMaxElementIndex = nullptr;
}
// Create LLVM pointer constants for the module's imported functions.
......
......@@ -2,6 +2,7 @@
#include "Runtime.h"
#include "Platform/Platform.h"
#include "RuntimePrivate.h"
#include <eosio/chain/wasm_eosio_constraints.hpp>
namespace Runtime
{
......@@ -17,13 +18,9 @@ namespace Runtime
{
TableInstance* table = new TableInstance(type);
// In 64-bit, allocate enough address-space to safely access 32-bit table indices without bounds checking, or 16MB (4M elements) if the host is 32-bit.
const Uptr tableMaxBytes = HAS_64BIT_ADDRESS_SPACE ? Uptr(U64(sizeof(TableInstance::FunctionElement)) << 32) : 16*1024*1024;
const Uptr tableMaxBytes = sizeof(TableInstance::FunctionElement)*eosio::chain::wasm_constraints::maximum_table_elements;
// On a 64 bit runtime, align the table base to a 4GB boundary, so the lower 32-bits will all be zero. Maybe it will allow better code generation?
// Note that this reserves a full extra 4GB, but only uses (4GB-1 page) for alignment, so there will always be a guard page at the end to
// protect against unaligned loads/stores that straddle the end of the address-space.
const Uptr alignmentBytes = HAS_64BIT_ADDRESS_SPACE ? Uptr(4ull*1024*1024*1024) : (Uptr(1) << Platform::getPageSizeLog2());
const Uptr alignmentBytes = 1U << Platform::getPageSizeLog2();
table->baseAddress = (TableInstance::FunctionElement*)allocateVirtualPagesAligned(tableMaxBytes,alignmentBytes,table->reservedBaseAddress,table->reservedNumPlatformPages);
table->endOffset = tableMaxBytes;
if(!table->baseAddress) { delete table; return nullptr; }
......
......@@ -171,3 +171,77 @@ static const char memory_table_import[] = R"=====(
(memory (import "nom" "memory") 0)
)
)=====";
static const char table_checker_wast[] = R"=====(
(module
(import "env" "assert" (func $assert (param i32 i32)))
(import "env" "printi" (func $printi (param i64)))
(type $SIG$vj (func (param i64)))
(table 1024 anyfunc)
(memory $0 1)
(export "apply" (func $apply))
(func $apply (param $0 i64) (param $1 i64)
(call_indirect $SIG$vj
(i64.shr_u
(get_local $1)
(i64.const 32)
)
(i32.wrap/i64
(get_local $1)
)
)
)
(func $apple (type $SIG$vj) (param $0 i64)
(call $assert
(i64.eq
(get_local $0)
(i64.const 555)
)
(i32.const 0)
)
)
(func $bannna (type $SIG$vj) (param $0 i64)
(call $assert
(i64.eq
(get_local $0)
(i64.const 7777)
)
(i32.const 0)
)
)
(elem (i32.const 0) $apple)
(elem (i32.const 1022) $apple $bannna)
)
)=====";
static const char table_checker_small_wast[] = R"=====(
(module
(import "env" "assert" (func $assert (param i32 i32)))
(import "env" "printi" (func $printi (param i64)))
(type $SIG$vj (func (param i64)))
(table 128 anyfunc)
(memory $0 1)
(export "apply" (func $apply))
(func $apply (param $0 i64) (param $1 i64)
(call_indirect $SIG$vj
(i64.shr_u
(get_local $1)
(i64.const 32)
)
(i32.wrap/i64
(get_local $1)
)
)
)
(func $apple (type $SIG$vj) (param $0 i64)
(call $assert
(i64.eq
(get_local $0)
(i64.const 555)
)
(i32.const 0)
)
)
(elem (i32.const 0) $apple)
)
)=====";
\ No newline at end of file
#include <boost/test/unit_test.hpp>
#include <eosio/testing/tester.hpp>
#include <eosio/chain/contracts/abi_serializer.hpp>
#include <eosio/chain/wasm_eosio_constraints.hpp>
#include <eosio/chain/exceptions.hpp>
#include <asserter/asserter.wast.hpp>
#include <asserter/asserter.abi.hpp>
......@@ -757,4 +758,126 @@ BOOST_FIXTURE_TEST_CASE(noop, tester) try {
} FC_LOG_AND_RETHROW()
//busted because of checktime, disable for now
#if 0
BOOST_FIXTURE_TEST_CASE( check_table_maximum, tester ) try {
produce_blocks(2);
create_accounts( {N(tbl)}, asset::from_string("1000.0000 EOS") );
transfer( N(inita), N(tbl), "10.0000 EOS", "memo" );
produce_block();
set_code(N(tbl), table_checker_wast);
produce_blocks(1);
{
signed_transaction trx;
action act;
act.name = 555ULL<<32 | 0ULL; //top 32 is what we assert against, bottom 32 is indirect call index
act.account = N(tbl);
act.authorization = vector<permission_level>{{N(tbl),config::active_name}};
trx.actions.push_back(act);
set_tapos(trx);
trx.sign(get_private_key( N(tbl), "active" ), chain_id_type());
push_transaction(trx);
}
produce_blocks(1);
{
signed_transaction trx;
action act;
act.name = 555ULL<<32 | 1022ULL; //top 32 is what we assert against, bottom 32 is indirect call index
act.account = N(tbl);
act.authorization = vector<permission_level>{{N(tbl),config::active_name}};
trx.actions.push_back(act);
set_tapos(trx);
trx.sign(get_private_key( N(tbl), "active" ), chain_id_type());
push_transaction(trx);
}
produce_blocks(1);
{
signed_transaction trx;
action act;
act.name = 7777ULL<<32 | 1023ULL; //top 32 is what we assert against, bottom 32 is indirect call index
act.account = N(tbl);
act.authorization = vector<permission_level>{{N(tbl),config::active_name}};
trx.actions.push_back(act);
set_tapos(trx);
trx.sign(get_private_key( N(tbl), "active" ), chain_id_type());
push_transaction(trx);
}
produce_blocks(1);
{
signed_transaction trx;
action act;
act.name = 7778ULL<<32 | 1023ULL; //top 32 is what we assert against, bottom 32 is indirect call index
act.account = N(tbl);
act.authorization = vector<permission_level>{{N(tbl),config::active_name}};
trx.actions.push_back(act);
set_tapos(trx);
trx.sign(get_private_key( N(tbl), "active" ), chain_id_type());
//should fail, a check to make sure assert() in wasm is being evaluated correctly
BOOST_CHECK_THROW(push_transaction(trx), fc::assert_exception);
}
produce_blocks(1);
{
signed_transaction trx;
action act;
act.name = 133ULL<<32 | 5ULL; //top 32 is what we assert against, bottom 32 is indirect call index
act.account = N(tbl);
act.authorization = vector<permission_level>{{N(tbl),config::active_name}};
trx.actions.push_back(act);
set_tapos(trx);
trx.sign(get_private_key( N(tbl), "active" ), chain_id_type());
//should fail, this element index (5) does not exist
BOOST_CHECK_THROW(push_transaction(trx), eosio::chain::wasm_execution_error);
}
produce_blocks(1);
{
signed_transaction trx;
action act;
act.name = eosio::chain::wasm_constraints::maximum_table_elements+54334;
act.account = N(tbl);
act.authorization = vector<permission_level>{{N(tbl),config::active_name}};
trx.actions.push_back(act);
set_tapos(trx);
trx.sign(get_private_key( N(tbl), "active" ), chain_id_type());
//should fail, this element index is out of range
BOOST_CHECK_THROW(push_transaction(trx), eosio::chain::wasm_execution_error);
}
produce_blocks(1);
set_code(N(tbl), table_checker_small_wast);
produce_blocks(1);
{
signed_transaction trx;
action act;
act.name = 888ULL;
act.account = N(tbl);
act.authorization = vector<permission_level>{{N(tbl),config::active_name}};
trx.actions.push_back(act);
set_tapos(trx);
trx.sign(get_private_key( N(tbl), "active" ), chain_id_type());
//an element that is out of range and has no mmap access permission either (should be a trapped segv)
BOOST_CHECK_THROW(push_transaction(trx), eosio::chain::wasm_execution_error);
}
} FC_LOG_AND_RETHROW()
#endif
BOOST_AUTO_TEST_SUITE_END()
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册