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] Disable EOS VM OC's subjective compilation limits in unit tests #1843

Merged
merged 6 commits into from
Nov 7, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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,23 @@

#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.
// nodeos uses the default values. libtester sets them as required.
heifner marked this conversation as resolved.
Show resolved Hide resolved
rlimit cpu_limits {20u, 20u};
rlimit vm_limits {512u*1024u*1024u, 512u*1024u*1024u};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't really think there is a need for both soft/hard limits here. I can't think of a reason we'd ever set them differently. It also wouldn't be appropriate if we ever ran on Windows.

uint64_t stack_size_limit {16u*1024u};
size_t generated_code_size_limit {16u*1024u*1024u};
};

}}}

FC_REFLECT(rlimit, (rlim_cur)(rlim_max))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you can remove this FC_REFLECT now

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. I missed this.

FC_REFLECT(eosio::chain::eosvmoc::config, (cache_size)(threads)(cpu_limits)(vm_limits)(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
10 changes: 7 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,17 @@ namespace LLVMJIT
WAVM_ASSERT_THROW(!!c);

++num_functions_stack_size_found;
if(stack_size > 16u*1024u)
// enforce stack_size_limit only when it is not max (libtester may
// disable it by setting it to max).
if(stack_size_limit != std::numeric_limits<uint64_t>::max() && 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)
// enforce generated_code_size_limit only when it is not max (libtester may
// disable it by setting it to max).
if(generated_code_size_limit != std::numeric_limits<size_t>::max() && jitModule->final_pic_code.size() >= generated_code_size_limit)
heifner marked this conversation as resolved.
Show resolved Hide resolved
_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,24 @@ 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_limits only when it is not RLIM_INFINITY (libtester may
// disable it by setting it to RLIM_INFINITY).
if(conf.cpu_limits.rlim_cur != RLIM_INFINITY) {
setrlimit(RLIMIT_CPU, &conf.cpu_limits);
}

// enforce vm_limits only when it is not RLIM_INFINITY (libtester may
// disable it by setting it to RLIM_INFINITY).
if(conf.vm_limits.rlim_cur != RLIM_INFINITY) {
setrlimit(RLIMIT_AS, &conf.vm_limits);
}

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

run_compile(std::move(fds[0]), std::move(fds[1]));
run_compile(std::move(fds[0]), std::move(fds[1]), conf.stack_size_limit, conf.generated_code_size_limit);
_exit(0);
}
else if(pid == -1)
Expand Down
11 changes: 11 additions & 0 deletions libraries/testing/tester.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,17 @@ namespace eosio { namespace testing {
}
}

// libtester does not enforce EOSVMOC limits for all tests except those in
// eosvmoc_limits_test.cpp, where one or more of the limits are set to
// max to indicate the limits are enforced.
if (cfg.eosvmoc_config.cpu_limits.rlim_cur != RLIM_INFINITY && cfg.eosvmoc_config.vm_limits.rlim_cur != RLIM_INFINITY && cfg.eosvmoc_config.stack_size_limit != std::numeric_limits<uint64_t>::max() && cfg.eosvmoc_config.generated_code_size_limit != std::numeric_limits<size_t>::max()) {
// set to inifinity or max such that they are not enforced.
cfg.eosvmoc_config.cpu_limits.rlim_cur = RLIM_INFINITY;
cfg.eosvmoc_config.vm_limits.rlim_cur = RLIM_INFINITY;
cfg.eosvmoc_config.stack_size_limit = std::numeric_limits<uint64_t>::max();
cfg.eosvmoc_config.generated_code_size_limit = std::numeric_limits<size_t>::max();
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't help but feel std::optional would be a better choice for these items. default_config() could be modified to simply .reset() each of them,

static std::pair<controller::config, genesis_state> default_config(const fc::temp_directory& tempdir, std::optional<uint32_t> genesis_max_inline_action_size = std::optional<uint32_t>{}) {
controller::config cfg;
cfg.blocks_dir = tempdir.path() / config::default_blocks_dir_name;
cfg.state_dir = tempdir.path() / config::default_state_dir_name;
cfg.state_size = 1024*1024*16;
cfg.state_guard_size = 0;
cfg.contracts_console = true;
cfg.eosvmoc_config.cache_size = 1024*1024*8;
// 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;

and since that occurs before validating_tester's Lambda conf_edit, eosvmoc_limits_tests can still override them back to however it wants them.

Regardless of optional usage, doing this part in default_config() feels like right spot since that's where other libtester-specific configuration items are set.

An optional should be able to be plumbed through easily, and something like

if(conf.cpu_limits.rlim_cur != RLIM_INFINITY) {
setrlimit(RLIMIT_CPU, &conf.cpu_limits);
}

becomes

if(conf.cpu_limits) {
   struct rlimit cpu_limit = {*conf.cpu_limit, *conf.cpu_limit};
   setrlimit(RLIMIT_CPU, &cpu_limit);
}


control.reset( new controller(cfg, std::move(pfs), *expected_chain_id) );
control->add_indices();
if (lambda) lambda();
Expand Down
139 changes: 139 additions & 0 deletions unittests/eosvmoc_limits_tests.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
#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) {
heifner marked this conversation as resolved.
Show resolved Hide resolved
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) {
heifner marked this conversation as resolved.
Show resolved Hide resolved
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 libtester does not enforce limits
BOOST_AUTO_TEST_CASE( default_limits ) { try {
eosvmoc::config eosvmoc_config;
limit_not_violated_test(eosvmoc_config);
} FC_LOG_AND_RETHROW() }

// test VM limits are checked
BOOST_AUTO_TEST_CASE( vm_limits ) { try {
eosvmoc::config eosvmoc_config;

// disable non-tested limits
eosvmoc_config.cpu_limits.rlim_cur = RLIM_INFINITY;
eosvmoc_config.stack_size_limit = std::numeric_limits<uint64_t>::max();
eosvmoc_config.generated_code_size_limit = std::numeric_limits<size_t>::max();

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

// set vm_limit to a large value such that it is not exceeded
eosvmoc_config.vm_limits.rlim_cur = 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;

// disable non-tested limits
eosvmoc_config.cpu_limits.rlim_cur = RLIM_INFINITY;
eosvmoc_config.vm_limits.rlim_cur = RLIM_INFINITY;
eosvmoc_config.generated_code_size_limit = std::numeric_limits<size_t>::max();

// The larget stack size in the test is 104.
// Set stack_size_limit to highest value to verify wasm_execution_error is raised
heifner marked this conversation as resolved.
Show resolved Hide resolved
eosvmoc_config.stack_size_limit = 103;
limit_violated_test(eosvmoc_config);

// set stack_size_limit to lowest value to verify wasm_execution_error is not raised
heifner marked this conversation as resolved.
Show resolved Hide resolved
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;

// disable non-tested limits
eosvmoc_config.cpu_limits.rlim_cur = RLIM_INFINITY;
eosvmoc_config.vm_limits.rlim_cur = RLIM_INFINITY;
eosvmoc_config.stack_size_limit = std::numeric_limits<uint64_t>::max();

// The sgenerated code size in the test is 36856.
// Set generated_code_size_limit to highest value to verify wasm_execution_error
// is raised
heifner marked this conversation as resolved.
Show resolved Hide resolved
eosvmoc_config.generated_code_size_limit = 36856;
limit_violated_test(eosvmoc_config);

// set generated_code_size_limit to lowest value to verify wasm_execution_error is not raised
heifner marked this conversation as resolved.
Show resolved Hide resolved
eosvmoc_config.generated_code_size_limit = 36857;
limit_not_violated_test(eosvmoc_config);
} FC_LOG_AND_RETHROW() }

BOOST_AUTO_TEST_SUITE_END()

#endif
Loading