Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[5.0 -> main] Disable EOS VM OC's subjective compilation limits in unit tests #1874

Merged
merged 8 commits into from
Nov 7, 2023
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ class code_cache_base {
code_cache_index _cache_index;

const chainbase::database& _db;
eosvmoc::config _eosvmoc_config;

std::filesystem::path _cache_file_path;
int _cache_fd;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,24 @@

#include <fc/reflect/reflect.hpp>

#include <sys/resource.h>

namespace eosio { namespace chain { namespace eosvmoc {

struct config {
uint64_t cache_size = 1024u*1024u*1024u;
uint64_t threads = 1u;

// subjective limits for OC compilation.
// nodeos enforces the limits by the default values.
// libtester disables the limits in all tests, except enforces the limits
// in the tests in unittests/eosvmoc_limits_tests.cpp.
std::optional<rlim_t> cpu_limit {20u};
std::optional<rlim_t> vm_limit {512u*1024u*1024u};
std::optional<uint64_t> stack_size_limit {16u*1024u};
std::optional<size_t> generated_code_size_limit {16u*1024u*1024u};
};

}}}

FC_REFLECT(eosio::chain::eosvmoc::config, (cache_size)(threads)(cpu_limit)(vm_limit)(stack_size_limit)(generated_code_size_limit))
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ struct code_tuple {

struct compile_wasm_message {
code_tuple code;
eosvmoc::config eosvmoc_config;
//Two sent fd: 1) communication socket for result, 2) the wasm to compile
};

Expand Down Expand Up @@ -62,7 +63,7 @@ using eosvmoc_message = std::variant<initialize_message,
FC_REFLECT(eosio::chain::eosvmoc::initialize_message, )
FC_REFLECT(eosio::chain::eosvmoc::initalize_response_message, (error_message))
FC_REFLECT(eosio::chain::eosvmoc::code_tuple, (code_id)(vm_version))
FC_REFLECT(eosio::chain::eosvmoc::compile_wasm_message, (code))
FC_REFLECT(eosio::chain::eosvmoc::compile_wasm_message, (code)(eosvmoc_config))
FC_REFLECT(eosio::chain::eosvmoc::evict_wasms_message, (codes))
FC_REFLECT(eosio::chain::eosvmoc::code_compilation_result_message, (start)(apply_offset)(starting_memory_pages)(initdata_prologue_size))
FC_REFLECT(eosio::chain::eosvmoc::compilation_result_unknownfailure, )
Expand Down
6 changes: 3 additions & 3 deletions libraries/chain/webassembly/runtimes/eos-vm-oc/LLVMJIT.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,7 @@ namespace LLVMJIT
final_pic_code = std::move(*unitmemorymanager->code);
}

instantiated_code instantiateModule(const IR::Module& module)
instantiated_code instantiateModule(const IR::Module& module, uint64_t stack_size_limit, size_t generated_code_size_limit)
{
static bool inited;
if(!inited) {
Expand Down Expand Up @@ -315,13 +315,13 @@ namespace LLVMJIT
WAVM_ASSERT_THROW(!!c);

++num_functions_stack_size_found;
if(stack_size > 16u*1024u)
if(stack_size > stack_size_limit)
_exit(1);
}
}
if(num_functions_stack_size_found != module.functions.defs.size())
_exit(1);
if(jitModule->final_pic_code.size() >= 16u*1024u*1024u)
if(jitModule->final_pic_code.size() >= generated_code_size_limit)
_exit(1);

instantiated_code ret;
Expand Down
2 changes: 1 addition & 1 deletion libraries/chain/webassembly/runtimes/eos-vm-oc/LLVMJIT.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,6 @@ struct instantiated_code {
};

namespace LLVMJIT {
instantiated_code instantiateModule(const IR::Module& module);
instantiated_code instantiateModule(const IR::Module& module, uint64_t stack_size_limit, size_t generated_code_size_limit);
}
}}}
7 changes: 4 additions & 3 deletions libraries/chain/webassembly/runtimes/eos-vm-oc/code_cache.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ const code_descriptor* const code_cache_async::get_descriptor_for_code(bool high
_outstanding_compiles_and_poison.emplace(*nextup, false);
std::vector<wrapped_fd> fds_to_pass;
fds_to_pass.emplace_back(memfd_for_bytearray(codeobject->code));
FC_ASSERT(write_message_with_fds(_compile_monitor_write_socket, compile_wasm_message{ *nextup }, fds_to_pass), "EOS VM failed to communicate to OOP manager");
FC_ASSERT(write_message_with_fds(_compile_monitor_write_socket, compile_wasm_message{ *nextup, _eosvmoc_config }, fds_to_pass), "EOS VM failed to communicate to OOP manager");
--count_processed;
}
_queued_compiles.erase(nextup);
Expand Down Expand Up @@ -179,7 +179,7 @@ const code_descriptor* const code_cache_async::get_descriptor_for_code(bool high
_outstanding_compiles_and_poison.emplace(ct, false);
std::vector<wrapped_fd> fds_to_pass;
fds_to_pass.emplace_back(memfd_for_bytearray(codeobject->code));
write_message_with_fds(_compile_monitor_write_socket, compile_wasm_message{ ct }, fds_to_pass);
write_message_with_fds(_compile_monitor_write_socket, compile_wasm_message{ ct, _eosvmoc_config }, fds_to_pass);
failure = get_cd_failure::temporary; // Compile might not be done yet
return nullptr;
}
Expand Down Expand Up @@ -211,7 +211,7 @@ const code_descriptor* const code_cache_sync::get_descriptor_for_code_sync(const
std::vector<wrapped_fd> fds_to_pass;
fds_to_pass.emplace_back(memfd_for_bytearray(codeobject->code));

write_message_with_fds(_compile_monitor_write_socket, compile_wasm_message{ {code_id, vm_version} }, fds_to_pass);
write_message_with_fds(_compile_monitor_write_socket, compile_wasm_message{ {code_id, vm_version}, _eosvmoc_config }, fds_to_pass);
auto [success, message, fds] = read_message_with_fds(_compile_monitor_read_socket);
EOS_ASSERT(success, wasm_execution_error, "failed to read response from monitor process");
EOS_ASSERT(std::holds_alternative<wasm_compilation_result_message>(message), wasm_execution_error, "unexpected response from monitor process");
Expand All @@ -226,6 +226,7 @@ const code_descriptor* const code_cache_sync::get_descriptor_for_code_sync(const

code_cache_base::code_cache_base(const std::filesystem::path& data_dir, const eosvmoc::config& eosvmoc_config, const chainbase::database& db) :
_db(db),
_eosvmoc_config(eosvmoc_config),
_cache_file_path(data_dir/"code_cache.bin") {
static_assert(sizeof(allocator_t) <= header_offset, "header offset intersects with allocator");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ struct compile_monitor_session {
connection_dead_signal();
return;
}
kick_compile_off(compile.code, std::move(fds[0]));
kick_compile_off(compile.code, compile.eosvmoc_config, std::move(fds[0]));
},
[&](const evict_wasms_message& evict) {
for(const code_descriptor& cd : evict.codes) {
Expand All @@ -90,7 +90,7 @@ struct compile_monitor_session {
});
}

void kick_compile_off(const code_tuple& code_id, wrapped_fd&& wasm_code) {
void kick_compile_off(const code_tuple& code_id, const eosvmoc::config& eosvmoc_config, wrapped_fd&& wasm_code) {
//prepare a requst to go out to the trampoline
int socks[2];
socketpair(AF_UNIX, SOCK_SEQPACKET | SOCK_CLOEXEC, 0, socks);
Expand All @@ -100,7 +100,7 @@ struct compile_monitor_session {
fds_pass_to_trampoline.emplace_back(socks[1]);
fds_pass_to_trampoline.emplace_back(std::move(wasm_code));

eosvmoc_message trampoline_compile_request = compile_wasm_message{code_id};
eosvmoc_message trampoline_compile_request = compile_wasm_message{code_id, eosvmoc_config};
if(write_message_with_fds(_trampoline_socket, trampoline_compile_request, fds_pass_to_trampoline) == false) {
wasm_compilation_result_message reply{code_id, compilation_result_unknownfailure{}, _allocator->get_free_memory()};
write_message_with_fds(_nodeos_instance_socket, reply);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ using namespace IR;

namespace eosio { namespace chain { namespace eosvmoc {

void run_compile(wrapped_fd&& response_sock, wrapped_fd&& wasm_code) noexcept { //noexcept; we'll just blow up if anything tries to cross this boundry
void run_compile(wrapped_fd&& response_sock, wrapped_fd&& wasm_code, uint64_t stack_size_limit, size_t generated_code_size_limit) noexcept { //noexcept; we'll just blow up if anything tries to cross this boundry
std::vector<uint8_t> wasm = vector_for_memfd(wasm_code);

//ideally we catch exceptions and sent them upstream as strings for easier reporting
Expand All @@ -30,7 +30,7 @@ void run_compile(wrapped_fd&& response_sock, wrapped_fd&& wasm_code) noexcept {
wasm_injections::wasm_binary_injection injector(module);
injector.inject();

instantiated_code code = LLVMJIT::instantiateModule(module);
instantiated_code code = LLVMJIT::instantiateModule(module, stack_size_limit, generated_code_size_limit);

code_compilation_result_message result_message;

Expand Down Expand Up @@ -165,16 +165,28 @@ void run_compile_trampoline(int fd) {
prctl(PR_SET_NAME, "oc-compile");
prctl(PR_SET_PDEATHSIG, SIGKILL);

struct rlimit cpu_limits = {20u, 20u};
setrlimit(RLIMIT_CPU, &cpu_limits);
const auto& conf = std::get<compile_wasm_message>(message).eosvmoc_config;

struct rlimit vm_limits = {512u*1024u*1024u, 512u*1024u*1024u};
setrlimit(RLIMIT_AS, &vm_limits);
// enforce cpu limit only when it is set
// (libtester may disable it)
if(conf.cpu_limit) {
struct rlimit cpu_limit = {*conf.cpu_limit, *conf.cpu_limit};
setrlimit(RLIMIT_CPU, &cpu_limit);
}

// enforce vm limit only when it is set
// (libtester may disable it)
if(conf.vm_limit) {
struct rlimit vm_limit = {*conf.vm_limit, *conf.vm_limit};
setrlimit(RLIMIT_AS, &vm_limit);
}

struct rlimit core_limits = {0u, 0u};
setrlimit(RLIMIT_CORE, &core_limits);

run_compile(std::move(fds[0]), std::move(fds[1]));
uint64_t stack_size_limit = conf.stack_size_limit ? *conf.stack_size_limit : std::numeric_limits<uint64_t>::max();
size_t generated_code_size_limit = conf.generated_code_size_limit ? * conf.generated_code_size_limit : std::numeric_limits<size_t>::max();
run_compile(std::move(fds[0]), std::move(fds[1]), stack_size_limit, generated_code_size_limit);
_exit(0);
}
else if(pid == -1)
Expand Down
7 changes: 7 additions & 0 deletions libraries/testing/include/eosio/testing/tester.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -406,6 +406,13 @@ namespace eosio { namespace testing {
cfg.contracts_console = true;
cfg.eosvmoc_config.cache_size = 1024*1024*8;

// don't enforce OC compilation subject limits for tests,
// particularly EOS EVM tests may run over those limits
cfg.eosvmoc_config.cpu_limit.reset();
cfg.eosvmoc_config.vm_limit.reset();
cfg.eosvmoc_config.stack_size_limit.reset();
cfg.eosvmoc_config.generated_code_size_limit.reset();

// don't use auto tier up for tests, since the point is to test diff vms
cfg.eosvmoc_tierup = chain::wasm_interface::vm_oc_enable::oc_none;

Expand Down
135 changes: 135 additions & 0 deletions unittests/eosvmoc_limits_tests.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
#ifdef EOSIO_EOS_VM_OC_RUNTIME_ENABLED

#include <eosio/testing/tester.hpp>
#include <test_contracts.hpp>
#include <boost/test/unit_test.hpp>

using namespace eosio;
using namespace eosio::chain;
using namespace eosio::testing;
using mvo = fc::mutable_variant_object;

BOOST_AUTO_TEST_SUITE(eosvmoc_limits_tests)

// common routine to verify wasm_execution_error is raised when a resource
// limit specified in eosvmoc_config is reached
void limit_violated_test(const eosvmoc::config& eosvmoc_config) {
fc::temp_directory tempdir;

constexpr bool use_genesis = true;
validating_tester chain(
tempdir,
[&](controller::config& cfg) {
cfg.eosvmoc_config = eosvmoc_config;
},
use_genesis
);

chain.create_accounts({"eosio.token"_n});
chain.set_code("eosio.token"_n, test_contracts::eosio_token_wasm());
chain.set_abi("eosio.token"_n, test_contracts::eosio_token_abi());

if (chain.control->is_eos_vm_oc_enabled()) {
BOOST_CHECK_EXCEPTION(
chain.push_action( "eosio.token"_n, "create"_n, "eosio.token"_n, mvo()
( "issuer", "eosio.token" )
( "maximum_supply", "1000000.00 TOK" )),
eosio::chain::wasm_execution_error,
[](const eosio::chain::wasm_execution_error& e) {
return expect_assert_message(e, "failed to compile wasm");
}
);
} else {
chain.push_action( "eosio.token"_n, "create"_n, "eosio.token"_n, mvo()
( "issuer", "eosio.token" )
( "maximum_supply", "1000000.00 TOK" )
);
}
}

// common routine to verify no wasm_execution_error is raised
// because limits specified in eosvmoc_config are not reached
void limit_not_violated_test(const eosvmoc::config& eosvmoc_config) {
fc::temp_directory tempdir;

constexpr bool use_genesis = true;
validating_tester chain(
tempdir,
[&](controller::config& cfg) {
cfg.eosvmoc_config = eosvmoc_config;
},
use_genesis
);

chain.create_accounts({"eosio.token"_n});
chain.set_code("eosio.token"_n, test_contracts::eosio_token_wasm());
chain.set_abi("eosio.token"_n, test_contracts::eosio_token_abi());

chain.push_action( "eosio.token"_n, "create"_n, "eosio.token"_n, mvo()
( "issuer", "eosio.token" )
( "maximum_supply", "1000000.00 TOK" )
);
}

// test all limits are not set for tests
BOOST_AUTO_TEST_CASE( limits_not_set ) { try {
validating_tester chain;
auto& cfg = chain.get_config();

BOOST_REQUIRE(cfg.eosvmoc_config.cpu_limit == std::nullopt);
BOOST_REQUIRE(cfg.eosvmoc_config.vm_limit == std::nullopt);
BOOST_REQUIRE(cfg.eosvmoc_config.stack_size_limit == std::nullopt);
BOOST_REQUIRE(cfg.eosvmoc_config.generated_code_size_limit == std::nullopt);
} FC_LOG_AND_RETHROW() }

// test limits are not enforced unless limits in eosvmoc_config
// are modified
BOOST_AUTO_TEST_CASE( limits_not_enforced ) { try {
eosvmoc::config eosvmoc_config;
limit_not_violated_test(eosvmoc_config);
} FC_LOG_AND_RETHROW() }

// test VM limit are checked
BOOST_AUTO_TEST_CASE( vm_limit ) { try {
eosvmoc::config eosvmoc_config;

// set vm_limit to a small value such that it is exceeded
eosvmoc_config.vm_limit = 64u*1024u*1024u;
limit_violated_test(eosvmoc_config);

// set vm_limit to a large value such that it is not exceeded
eosvmoc_config.vm_limit = 128u*1024u*1024u;
limit_not_violated_test(eosvmoc_config);
} FC_LOG_AND_RETHROW() }

// test stack size limit is checked
BOOST_AUTO_TEST_CASE( stack_limit ) { try {
eosvmoc::config eosvmoc_config;

// The stack size of the compiled WASM in the test is 104.
// Set stack_size_limit one less than the actual needed stack size
eosvmoc_config.stack_size_limit = 103;
limit_violated_test(eosvmoc_config);

// set stack_size_limit to the actual needed stack size
eosvmoc_config.stack_size_limit = 104;
limit_not_violated_test(eosvmoc_config);
} FC_LOG_AND_RETHROW() }

// test generated code size limit is checked
BOOST_AUTO_TEST_CASE( generated_code_size_limit ) { try {
eosvmoc::config eosvmoc_config;

// The generated code size of the compiled WASM in the test is 36856.
// Set generated_code_size_limit to the actual generated code size
eosvmoc_config.generated_code_size_limit = 36856;
limit_violated_test(eosvmoc_config);

// Set generated_code_size_limit to one above the actual generated code size
eosvmoc_config.generated_code_size_limit = 36857;
limit_not_violated_test(eosvmoc_config);
} FC_LOG_AND_RETHROW() }

BOOST_AUTO_TEST_SUITE_END()

#endif