diff --git a/include/eosio/vm/allocator.hpp b/include/eosio/vm/allocator.hpp index 6ed2669..1943990 100644 --- a/include/eosio/vm/allocator.hpp +++ b/include/eosio/vm/allocator.hpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -372,6 +373,8 @@ namespace eosio { namespace vm { const void* get_code_start() const { return _code_base; } + std::span get_code_span() const {return {(std::byte*)_code_base, _code_size};} + /* different semantics than free, * the memory must be at the end of the most recently allocated block. */ @@ -524,5 +527,7 @@ namespace eosio { namespace vm { inline T* create_pointer(uint32_t offset) { return reinterpret_cast(raw + offset); } inline int32_t get_current_page() const { return page; } bool is_in_region(char* p) { return p >= raw && p < raw + max_memory; } + + std::span get_span() const {return {(std::byte*)raw, max_memory};} }; }} // namespace eosio::vm diff --git a/include/eosio/vm/execution_context.hpp b/include/eosio/vm/execution_context.hpp index f0f33b8..e6ac59c 100644 --- a/include/eosio/vm/execution_context.hpp +++ b/include/eosio/vm/execution_context.hpp @@ -358,11 +358,11 @@ namespace eosio { namespace vm { vm::invoke_with_signal_handler([&]() { result = execute(args_raw, fn, this, base_type::linear_memory(), stack); - }, &handle_signal); + }, &handle_signal, {_mod->allocator.get_code_span(), base_type::get_wasm_allocator()->get_span()}); } else { vm::invoke_with_signal_handler([&]() { result = execute(args_raw, fn, this, base_type::linear_memory(), stack); - }, &handle_signal); + }, &handle_signal, {_mod->allocator.get_code_span(), base_type::get_wasm_allocator()->get_span()}); } } } catch(wasm_exit_exception&) { @@ -800,7 +800,7 @@ namespace eosio { namespace vm { setup_locals(func_index); vm::invoke_with_signal_handler([&]() { execute(visitor); - }, &handle_signal); + }, &handle_signal, {_mod->allocator.get_code_span(), base_type::get_wasm_allocator()->get_span()}); } if (_mod->get_function_type(func_index).return_count && !_state.exiting) { diff --git a/include/eosio/vm/signals.hpp b/include/eosio/vm/signals.hpp index 05bbdab..b5a5312 100644 --- a/include/eosio/vm/signals.hpp +++ b/include/eosio/vm/signals.hpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include @@ -16,6 +17,9 @@ namespace eosio { namespace vm { __attribute__((visibility("default"))) inline thread_local std::atomic signal_dest{nullptr}; + __attribute__((visibility("default"))) + inline thread_local std::vector> protected_memory_ranges; + // Fixes a duplicate symbol build issue when building with `-fvisibility=hidden` __attribute__((visibility("default"))) inline thread_local std::exception_ptr saved_exception{nullptr}; @@ -23,9 +27,22 @@ namespace eosio { namespace vm { template inline struct sigaction prev_signal_handler; + inline bool in_protected_range(void* addr) { + //empty protection list means legacy catch-all behavior; useful for some of the old tests + if(protected_memory_ranges.empty()) + return true; + + for(const std::span& range : protected_memory_ranges) { + if(addr >= range.data() && addr < range.data() + range.size()) + return true; + } + return false; + } + inline void signal_handler(int sig, siginfo_t* info, void* uap) { sigjmp_buf* dest = std::atomic_load(&signal_dest); - if (dest) { + + if (dest && in_protected_range(info->si_addr)) { siglongjmp(*dest, sig); } else { struct sigaction* prev_action; @@ -98,7 +115,9 @@ namespace eosio { namespace vm { sigaddset(&sa.sa_mask, SIGPROF); sa.sa_flags = SA_NODEFER | SA_SIGINFO; sigaction(SIGSEGV, &sa, &prev_signal_handler); +#ifndef __linux__ sigaction(SIGBUS, &sa, &prev_signal_handler); +#endif sigaction(SIGFPE, &sa, &prev_signal_handler); } @@ -114,16 +133,17 @@ namespace eosio { namespace vm { /// with non-trivial destructors, then it must mask the relevant signals /// during the lifetime of these objects or the behavior is undefined. /// - /// signals handled: SIGSEGV, SIGBUS, SIGFPE + /// signals handled: SIGSEGV, SIGBUS (except on Linux), SIGFPE /// // Make this noinline to prevent possible corruption of the caller's local variables. // It's unlikely, but I'm not sure that it can definitely be ruled out if both // this and f are inlined and f modifies locals from the caller. template - [[gnu::noinline]] auto invoke_with_signal_handler(F&& f, E&& e) { + [[gnu::noinline]] auto invoke_with_signal_handler(F&& f, E&& e, const std::vector>& protect_ranges) { setup_signal_handler(); sigjmp_buf dest; sigjmp_buf* volatile old_signal_handler = nullptr; + protected_memory_ranges = protect_ranges; int sig; if((sig = sigsetjmp(dest, 1)) == 0) { // Note: Cannot use RAII, as non-trivial destructors w/ longjmp diff --git a/tests/signals_tests.cpp b/tests/signals_tests.cpp index 15e5caf..6dfba49 100644 --- a/tests/signals_tests.cpp +++ b/tests/signals_tests.cpp @@ -15,7 +15,7 @@ TEST_CASE("Testing signals", "[invoke_with_signal_handler]") { std::raise(SIGSEGV); }, [](int sig) { throw test_exception{}; - }); + }, {}); } catch(test_exception&) { okay = true; } @@ -25,7 +25,7 @@ TEST_CASE("Testing signals", "[invoke_with_signal_handler]") { TEST_CASE("Testing throw", "[signal_handler_throw]") { CHECK_THROWS_AS(eosio::vm::invoke_with_signal_handler([](){ eosio::vm::throw_( "Exiting" ); - }, [](int){}), eosio::vm::wasm_exit_exception); + }, [](int){}, {}), eosio::vm::wasm_exit_exception); } static volatile sig_atomic_t sig_handled; @@ -54,9 +54,11 @@ TEST_CASE("Test signal handler forwarding", "[signal_handler_forward]") { sig_handled = 0; std::raise(SIGSEGV); CHECK(sig_handled == 42 + SIGSEGV); +#ifndef __linux__ sig_handled = 0; std::raise(SIGBUS); CHECK(sig_handled == 42 + SIGBUS); +#endif sig_handled = 0; std::raise(SIGFPE); CHECK(sig_handled == 42 + SIGFPE); @@ -73,9 +75,11 @@ TEST_CASE("Test signal handler forwarding", "[signal_handler_forward]") { sig_handled = 0; std::raise(SIGSEGV); CHECK(sig_handled == 142 + SIGSEGV); +#ifndef __linux__ sig_handled = 0; std::raise(SIGBUS); CHECK(sig_handled == 142 + SIGBUS); +#endif sig_handled = 0; std::raise(SIGFPE); CHECK(sig_handled == 142 + SIGFPE);