未验证 提交 25c6bcd9 编写于 作者: W wanderingbort 提交者: GitHub

Merge pull request #1287 from EOSIO/more_eosio_wasm_constraints

Add more constraints on WASM execution environment
......@@ -9,6 +9,7 @@ add_library( eosio_chain
block.cpp
wast_to_wasm.cpp
wasm_interface.cpp
wasm_eosio_constraints.cpp
apply_context.cpp
rate_limiting.cpp
......
#pragma once
namespace IR {
struct Module;
};
namespace eosio { namespace chain {
//Throws if something in the module violates
void validate_eosio_wasm_constraints(const IR::Module& m);
}}
#include <eosio/chain/wasm_eosio_constraints.hpp>
#include <fc/exception/exception.hpp>
#include <eosio/chain/exceptions.hpp>
#include "IR/Module.h"
#include "IR/Operators.h"
namespace eosio { namespace chain {
using namespace IR;
struct nop_opcode_visitor {
typedef void Result;
#define VISIT_OPCODE(opcode,name,nameString,Imm,...) \
virtual void name(Imm) {}
ENUM_OPERATORS(VISIT_OPCODE)
#undef VISIT_OPCODE
void unknown(Opcode) {
FC_THROW_EXCEPTION(wasm_execution_error, "Smart contract encountered unknown opcode");
}
};
struct eosio_constraints_visitor : public nop_opcode_visitor {
///Make this some sort of visitor enum to reduce chance of copy pasta errors (but
// the override declaration makes it somewhat safe)
//While it's possible to access beyond 1MiB by giving an offset that's 1MiB-1 and
// 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)
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); }
void i64_load (LoadOrStoreImm<3> imm) override { fail_large_offset(imm.offset); }
void i32_load8_s (LoadOrStoreImm<0> imm) override { fail_large_offset(imm.offset); }
void i32_load8_u (LoadOrStoreImm<0> imm) override { fail_large_offset(imm.offset); }
void i32_load16_s (LoadOrStoreImm<1> imm) override { fail_large_offset(imm.offset); }
void i32_load16_u (LoadOrStoreImm<1> imm) override { fail_large_offset(imm.offset); }
void i64_load8_s (LoadOrStoreImm<0> imm) override { fail_large_offset(imm.offset); }
void i64_load8_u (LoadOrStoreImm<0> imm) override { fail_large_offset(imm.offset); }
void i64_load16_s (LoadOrStoreImm<1> imm) override { fail_large_offset(imm.offset); }
void i64_load16_u (LoadOrStoreImm<1> imm) override { fail_large_offset(imm.offset); }
void i64_load32_s (LoadOrStoreImm<2> imm) override { fail_large_offset(imm.offset); }
void i64_load32_u (LoadOrStoreImm<2> imm) override { fail_large_offset(imm.offset); }
void i32_store (LoadOrStoreImm<2> imm) override { fail_large_offset(imm.offset); }
void i64_store (LoadOrStoreImm<3> imm) override { fail_large_offset(imm.offset); }
void i32_store8 (LoadOrStoreImm<0> imm) override { fail_large_offset(imm.offset); }
void i32_store16 (LoadOrStoreImm<1> imm) override { fail_large_offset(imm.offset); }
void i64_store8 (LoadOrStoreImm<0> imm) override { fail_large_offset(imm.offset); }
void i64_store16 (LoadOrStoreImm<1> imm) override { fail_large_offset(imm.offset); }
void i64_store32 (LoadOrStoreImm<2> imm) override { fail_large_offset(imm.offset); }
void f32_load (LoadOrStoreImm<2> imm) override { fail_large_offset(imm.offset); }
void f64_load (LoadOrStoreImm<3> imm) override { fail_large_offset(imm.offset); }
void f32_store (LoadOrStoreImm<2> imm) override { fail_large_offset(imm.offset); }
void f64_store (LoadOrStoreImm<3> imm) override { fail_large_offset(imm.offset); }
#define VISIT_OPCODE(opcode,name,nameString,Imm,...) \
void name(Imm) override { FC_THROW_EXCEPTION(wasm_execution_error, "Smart contracts may not use WASM memory operators"); }
ENUM_MEMORY_OPERATORS(VISIT_OPCODE);
#undef VISIT_OPCODE
};
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");
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(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");
unsigned mutable_globals_total_size = 0;
for(const GlobalDef& global_def : m.globals.defs) {
if(!global_def.type.isMutable)
continue;
switch(global_def.type.valueType) {
case ValueType::any:
case ValueType::num:
FC_THROW_EXCEPTION(wasm_execution_error, "Smart contract has unexpected global definition value type");
case ValueType::i64:
case ValueType::f64:
mutable_globals_total_size += 4;
case ValueType::i32:
case ValueType::f32:
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");
//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).
// Not doing that here, yet, since it's not clear it's required for the purpose of the validation
eosio_constraints_visitor visitor;
for(const FunctionDef& fd : m.functions.defs) {
OperatorDecoderStream decoder(fd.code);
while(decoder) {
decoder.decodeOp(visitor);
}
}
}
}}
\ No newline at end of file
......@@ -4,6 +4,7 @@
#include <eosio/chain/exceptions.hpp>
#include <boost/core/ignore_unused.hpp>
#include <eosio/chain/wasm_interface_private.hpp>
#include <eosio/chain/wasm_eosio_constraints.hpp>
#include <fc/exception/exception.hpp>
#include <fc/crypto/sha1.hpp>
#include <fc/io/raw.hpp>
......@@ -221,6 +222,7 @@ namespace eosio { namespace chain {
Serialization::MemoryInputStream stream((const U8 *) wasm_binary, wasm_binary_size);
#warning TODO: restore checktime injection?
WASM::serializeWithInjection(stream, *module);
validate_eosio_wasm_constraints(*module);
root_resolver resolver;
LinkResult link_result = linkModule(*module, resolver);
......
......@@ -620,13 +620,6 @@ namespace WASM
Opcode opcode;
serialize(bodyStream,opcode);
////disallow memory operations
#define VISIT_OPCODE(_,name,...) \
if(opcode == Opcode::name) \
throw FatalSerializationException("memory instructions not allowed");
ENUM_MEMORY_OPERATORS(VISIT_OPCODE)
#undef VISIT_OPCODE
switch(opcode)
{
#define VISIT_OPCODE(_,name,nameString,Imm,...) \
......
......@@ -96,4 +96,78 @@ static const char grow_memory_wast[] = R"=====(
)
)
)
)=====";
\ No newline at end of file
)=====";
static const char biggest_memory_wast[] = R"=====(
(module
(import "env" "sbrk" (func $sbrk (param i32) (result i32)))
(table 0 anyfunc)
(memory $0 16)
(export "memory" (memory $0))
(export "apply" (func $apply))
(func $apply (param $0 i64) (param $1 i64)
(drop
(call $sbrk
(i32.const 1)
)
)
)
)
)=====";
static const char too_big_memory_wast[] = R"=====(
(module
(table 0 anyfunc)
(memory $0 17)
(export "memory" (memory $0))
(export "apply" (func $apply))
(func $apply (param $0 i64) (param $1 i64))
)
)=====";
static const char valid_sparse_table[] = R"=====(
(module
(table 1024 anyfunc)
(func $apply (param $0 i64) (param $1 i64))
(elem (i32.const 0) $apply)
(elem (i32.const 1022) $apply $apply)
)
)=====";
static const char too_big_table[] = R"=====(
(module
(table 1025 anyfunc)
(func $apply (param $0 i64) (param $1 i64))
(elem (i32.const 0) $apply)
(elem (i32.const 1022) $apply $apply)
)
)=====";
static const char memory_init_borderline[] = R"=====(
(module
(memory $0 16)
(data (i32.const 65532) "sup!")
)
)=====";
static const char memory_init_toolong[] = R"=====(
(module
(memory $0 16)
(data (i32.const 65533) "sup!")
)
)=====";
static const char memory_init_negative[] = R"=====(
(module
(memory $0 16)
(data (i32.const -1) "sup!")
)
)=====";
static const char memory_table_import[] = R"=====(
(module
(table (import "foo" "table") 10 anyfunc)
(memory (import "nom" "memory") 0)
)
)=====";
#include <boost/test/unit_test.hpp>
#include <eosio/testing/tester.hpp>
#include <eosio/chain/contracts/abi_serializer.hpp>
#include <eosio/chain/exceptions.hpp>
#include <asserter/asserter.wast.hpp>
#include <asserter/asserter.abi.hpp>
......@@ -519,7 +520,7 @@ BOOST_FIXTURE_TEST_CASE( memory_operators, tester ) try {
transfer( N(inita), N(current_memory), "10.0000 EOS", "memo" );
produce_block();
set_code(N(current_memory), current_memory_wast);
BOOST_CHECK_THROW(set_code(N(current_memory), current_memory_wast), eosio::chain::wasm_execution_error);
produce_blocks(1);
{
signed_transaction trx;
......@@ -551,6 +552,173 @@ BOOST_FIXTURE_TEST_CASE( memory_operators, tester ) try {
} FC_LOG_AND_RETHROW()
//Make sure we can create a wasm with 16 pages, but not grow it any
BOOST_FIXTURE_TEST_CASE( big_memory, tester ) try {
produce_blocks(2);
create_accounts( {N(bigmem)}, asset::from_string("1000.0000 EOS") );
transfer( N(inita), N(bigmem), "10.0000 EOS", "memo" );
produce_block();
set_code(N(bigmem), biggest_memory_wast); //should pass, 16 pages is fine
produce_blocks(1);
signed_transaction trx;
action act;
act.account = N(bigmem);
act.name = N();
act.authorization = vector<permission_level>{{N(bigmem),config::active_name}};
trx.actions.push_back(act);
set_tapos(trx);
trx.sign(get_private_key( N(bigmem), "active" ), chain_id_type());
//but should not be able to grow beyond 16th page
BOOST_CHECK_THROW(push_transaction(trx), fc::exception);
produce_blocks(1);
//should fail, 17 blocks is no no
BOOST_CHECK_THROW(set_code(N(bigmem), too_big_memory_wast), eosio::chain::wasm_execution_error);
} FC_LOG_AND_RETHROW()
BOOST_FIXTURE_TEST_CASE( table_init_tests, tester ) try {
produce_blocks(2);
create_accounts( {N(tableinit)}, asset::from_string("1000.0000 EOS") );
transfer( N(inita), N(tableinit), "10.0000 EOS", "memo" );
produce_block();
set_code(N(tableinit), valid_sparse_table);
produce_blocks(1);
BOOST_CHECK_THROW(set_code(N(tableinit), too_big_table), eosio::chain::wasm_execution_error);
} FC_LOG_AND_RETHROW()
BOOST_FIXTURE_TEST_CASE( memory_init_border, tester ) try {
produce_blocks(2);
create_accounts( {N(memoryborder)}, asset::from_string("1000.0000 EOS") );
transfer( N(inita), N(memoryborder), "10.0000 EOS", "memo" );
produce_block();
set_code(N(memoryborder), memory_init_borderline);
produce_blocks(1);
BOOST_CHECK_THROW(set_code(N(memoryborder), memory_init_toolong), eosio::chain::wasm_execution_error);
BOOST_CHECK_THROW(set_code(N(memoryborder), memory_init_negative), eosio::chain::wasm_execution_error);
} FC_LOG_AND_RETHROW()
BOOST_FIXTURE_TEST_CASE( imports, tester ) try {
produce_blocks(2);
create_accounts( {N(imports)}, asset::from_string("1000.0000 EOS") );
transfer( N(inita), N(imports), "10.0000 EOS", "memo" );
produce_block();
//this will fail to link but that's okay; mainly looking to make sure that the constraint
// system doesn't choke when memories and tables exist only as imports
BOOST_CHECK_THROW(set_code(N(imports), memory_table_import), fc::exception);
} FC_LOG_AND_RETHROW()
BOOST_FIXTURE_TEST_CASE( lotso_globals, tester ) try {
produce_blocks(2);
create_accounts( {N(globals)}, asset::from_string("1000.0000 EOS") );
transfer( N(inita), N(globals), "10.0000 EOS", "memo" );
produce_block();
std::stringstream ss;
ss << "(module ";
for(unsigned int i = 0; i < 85; ++i)
ss << "(global $g" << i << " (mut i32) (i32.const 0))" << "(global $g" << i+100 << " (mut i64) (i64.const 0))";
//that gives us 1020 bytes of mutable globals
//add a few immutable ones for good measure
for(unsigned int i = 0; i < 10; ++i)
ss << "(global $g" << i+200 << " i32 (i32.const 0))";
set_code(N(globals),
string(ss.str() + ")")
.c_str());
//1024 should pass
set_code(N(globals),
string(ss.str() + "(global $z (mut i32) (i32.const -12)))")
.c_str());
//1028 should fail
BOOST_CHECK_THROW(set_code(N(globals),
string(ss.str() + "(global $z (mut i64) (i64.const -12)))")
.c_str()), eosio::chain::wasm_execution_error);;
} FC_LOG_AND_RETHROW()
BOOST_FIXTURE_TEST_CASE( offset_check, tester ) try {
produce_blocks(2);
create_accounts( {N(offsets)}, asset::from_string("1000.0000 EOS") );
transfer( N(inita), N(offsets), "10.0000 EOS", "memo" );
produce_block();
//floats not tested since they are blocked in the serializer before eosio_constraints
vector<string> loadops = {
"i32.load", "i64.load", /* "f32.load", "f64.load",*/ "i32.load8_s", "i32.load8_u",
"i32.load16_s", "i32.load16_u", "i64.load8_s", "i64.load8_u", "i64.load16_s",
"i64.load16_u", "i64.load32_s", "i64.load32_u"
};
vector<vector<string>> storeops = {
{"i32.store", "i32"},
{"i64.store", "i64"},
/*{"f32.store", "f32"},
{"f64.store", "f64"},*/
{"i32.store8", "i32"},
{"i32.store16", "i32"},
{"i64.store8", "i64"},
{"i64.store16", "i64"},
{"i64.store32", "i64"},
};
for(const string& s : loadops) {
std::stringstream ss;
ss << "(module (memory $0 16) (func $apply (param $0 i64) (param $1 i64) ";
ss << "(drop (" << s << " offset=1048574 (i32.const 0)))";
ss << "))";
set_code(N(offsets), ss.str().c_str());
produce_block();
}
for(const vector<string>& o : storeops) {
std::stringstream ss;
ss << "(module (memory $0 16) (func $apply (param $0 i64) (param $1 i64) ";
ss << "(" << o[0] << " offset=1048574 (i32.const 0) (" << o[1] << ".const 0))";
ss << "))";
set_code(N(offsets), ss.str().c_str());
produce_block();
}
for(const string& s : loadops) {
std::stringstream ss;
ss << "(module (memory $0 16) (func $apply (param $0 i64) (param $1 i64) ";
ss << "(drop (" << s << " offset=1048580 (i32.const 0)))";
ss << "))";
BOOST_CHECK_THROW(set_code(N(offsets), ss.str().c_str()), eosio::chain::wasm_execution_error);
produce_block();
}
for(const vector<string>& o : storeops) {
std::stringstream ss;
ss << "(module (memory $0 16) (func $apply (param $0 i64) (param $1 i64) ";
ss << "(" << o[0] << " offset=1048580 (i32.const 0) (" << o[1] << ".const 0))";
ss << "))";
BOOST_CHECK_THROW(set_code(N(offsets), ss.str().c_str()), eosio::chain::wasm_execution_error);
produce_block();
}
} FC_LOG_AND_RETHROW()
BOOST_FIXTURE_TEST_CASE(noop, tester) try {
produce_blocks(2);
create_accounts( {N(noop), N(alice)}, asset::from_string("1000.0000 EOS") );
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册