From 5996e485e75b19788c448145f8c1f37b77a536b7 Mon Sep 17 00:00:00 2001 From: Matt Witherspoon <32485495+spoonincode@users.noreply.github.com> Date: Mon, 18 Mar 2024 16:30:29 -0400 Subject: [PATCH] only handle signals from expected memory ranges --- include/eosio/vm/allocator.hpp | 4 ++++ include/eosio/vm/execution_context.hpp | 6 +++--- include/eosio/vm/signals.hpp | 25 ++++++++++++++++++++++--- tests/signals_tests.cpp | 8 ++++++-- 4 files changed, 35 insertions(+), 8 deletions(-) diff --git a/include/eosio/vm/allocator.hpp b/include/eosio/vm/allocator.hpp index 6ed2669a..c267ea7e 100644 --- a/include/eosio/vm/allocator.hpp +++ b/include/eosio/vm/allocator.hpp @@ -372,6 +372,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 +526,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 f0f33b8b..e6ac59ce 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 05bbdab8..f52bfe82 100644 --- a/include/eosio/vm/signals.hpp +++ b/include/eosio/vm/signals.hpp @@ -16,6 +16,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}; @@ -25,7 +28,20 @@ namespace eosio { namespace vm { inline void signal_handler(int sig, siginfo_t* info, void* uap) { sigjmp_buf* dest = std::atomic_load(&signal_dest); - if (dest) { + + auto in_protected_range = [&]() { + //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(info->si_addr >= range.data() && info->si_addr < range.data() + range.size()) + return true; + } + return false; + }; + + if (dest && in_protected_range()) { siglongjmp(*dest, sig); } else { struct sigaction* prev_action; @@ -98,7 +114,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 +132,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 15e5caf3..6dfba49f 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);