Projects
openEuler:22.03:LTS:LoongArch
libunwind
_service:tar_scm_kernel_repo:backport-Port-memo...
Sign Up
Log In
Username
Password
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
File _service:tar_scm_kernel_repo:backport-Port-memory-address-checks-from-x86_64-to-Aarch64.patch of Package libunwind
From f8327f50be0413357816c2a41348455af476b9e5 Mon Sep 17 00:00:00 2001 From: Mikhail Durnev <mikhail_durnev@mentor.com> Date: Fri, 5 Mar 2021 21:41:31 +1000 Subject: [PATCH] Port memory address checks from x86/x86_64 to Aarch64 Signed-off-by: Mikhail Durnev <mikhail_durnev@mentor.com> --- include/tdep-aarch64/libunwind_i.h | 14 +- src/aarch64/Gglobal.c | 2 + src/aarch64/Ginit.c | 248 ++++++++++++++++++++++++++++- src/aarch64/Ginit_local.c | 4 +- src/aarch64/Ginit_remote.c | 11 +- src/aarch64/Gresume.c | 2 +- src/aarch64/Gstep.c | 20 ++- 7 files changed, 291 insertions(+), 10 deletions(-) diff --git a/include/tdep-aarch64/libunwind_i.h b/include/tdep-aarch64/libunwind_i.h index fc4416069..d96833a21 100644 --- a/include/tdep-aarch64/libunwind_i.h +++ b/include/tdep-aarch64/libunwind_i.h @@ -105,8 +105,16 @@ struct cursor unw_word_t sigcontext_sp; unw_word_t sigcontext_pc; int validate; + ucontext_t *uc; }; +static inline ucontext_t * +dwarf_get_uc(const struct dwarf_cursor *cursor) +{ + const struct cursor *c = (struct cursor *) cursor->as_arg; + return c->uc; +} + #define DWARF_GET_LOC(l) ((l).val) #ifdef UNW_LOCAL_ONLY @@ -115,10 +123,10 @@ struct cursor # define DWARF_LOC(r, t) ((dwarf_loc_t) { .val = (r) }) # define DWARF_IS_REG_LOC(l) 0 # define DWARF_REG_LOC(c,r) (DWARF_LOC((unw_word_t) \ - tdep_uc_addr((c)->as_arg, (r)), 0)) + tdep_uc_addr(dwarf_get_uc(c), (r)), 0)) # define DWARF_MEM_LOC(c,m) DWARF_LOC ((m), 0) # define DWARF_FPREG_LOC(c,r) (DWARF_LOC((unw_word_t) \ - tdep_uc_addr((c)->as_arg, (r)), 0)) + tdep_uc_addr(dwarf_get_uc(c), (r)), 0)) static inline int dwarf_getfp (struct dwarf_cursor *c, dwarf_loc_t loc, unw_fpreg_t *val) @@ -262,6 +270,7 @@ dwarf_put (struct dwarf_cursor *c, dwarf_loc_t loc, unw_word_t val) #define tdep_getcontext_trace UNW_ARCH_OBJ(getcontext_trace) #define tdep_init_done UNW_OBJ(init_done) +#define tdep_init_mem_validate UNW_OBJ(init_mem_validate) #define tdep_init UNW_OBJ(init) /* Platforms that support UNW_INFO_FORMAT_TABLE need to define tdep_search_unwind_table. */ @@ -300,6 +309,7 @@ dwarf_put (struct dwarf_cursor *c, dwarf_loc_t loc, unw_word_t val) extern int tdep_init_done; extern void tdep_init (void); +extern void tdep_init_mem_validate (void); extern int tdep_search_unwind_table (unw_addr_space_t as, unw_word_t ip, unw_dyn_info_t *di, unw_proc_info_t *pi, int need_unwind_info, void *arg); diff --git a/src/aarch64/Gglobal.c b/src/aarch64/Gglobal.c index 854b54914..2987f2aff 100644 --- a/src/aarch64/Gglobal.c +++ b/src/aarch64/Gglobal.c @@ -47,6 +47,8 @@ tdep_init (void) dwarf_init (); + tdep_init_mem_validate (); + #ifndef UNW_REMOTE_ONLY aarch64_local_addr_space_init (); #endif diff --git a/src/aarch64/Ginit.c b/src/aarch64/Ginit.c index a3b933b6d..2b08feb36 100644 --- a/src/aarch64/Ginit.c +++ b/src/aarch64/Ginit.c @@ -24,8 +24,11 @@ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +#include <errno.h> #include <stdlib.h> #include <string.h> +#include <sys/mman.h> +#include <stdatomic.h> #include "unwind_i.h" @@ -81,17 +84,256 @@ get_dyn_info_list_addr (unw_addr_space_t as, unw_word_t *dyn_info_list_addr, return 0; } +#define PAGE_SIZE 4096 +#define PAGE_START(a) ((a) & ~(PAGE_SIZE-1)) + +static int mem_validate_pipe[2] = {-1, -1}; + +#ifdef HAVE_PIPE2 +static inline void +do_pipe2 (int pipefd[2]) +{ + pipe2 (pipefd, O_CLOEXEC | O_NONBLOCK); +} +#else +static inline void +set_pipe_flags (int fd) +{ + int fd_flags = fcntl (fd, F_GETFD, 0); + int status_flags = fcntl (fd, F_GETFL, 0); + + fd_flags |= FD_CLOEXEC; + fcntl (fd, F_SETFD, fd_flags); + + status_flags |= O_NONBLOCK; + fcntl (fd, F_SETFL, status_flags); +} + +static inline void +do_pipe2 (int pipefd[2]) +{ + pipe (pipefd); + set_pipe_flags(pipefd[0]); + set_pipe_flags(pipefd[1]); +} +#endif + +static inline void +open_pipe (void) +{ + if (mem_validate_pipe[0] != -1) + close (mem_validate_pipe[0]); + if (mem_validate_pipe[1] != -1) + close (mem_validate_pipe[1]); + + do_pipe2 (mem_validate_pipe); +} + +ALWAYS_INLINE +static int +write_validate (void *addr) +{ + int ret = -1; + ssize_t bytes = 0; + + do + { + char buf; + bytes = read (mem_validate_pipe[0], &buf, 1); + } + while ( errno == EINTR ); + + int valid_read = (bytes > 0 || errno == EAGAIN || errno == EWOULDBLOCK); + if (!valid_read) + { + // re-open closed pipe + open_pipe (); + } + + do + { + ret = write (mem_validate_pipe[1], addr, 1); + } + while ( errno == EINTR ); + + return ret; +} + +static int (*mem_validate_func) (void *addr, size_t len); +static int msync_validate (void *addr, size_t len) +{ + if (msync (addr, len, MS_ASYNC) != 0) + { + return -1; + } + + return write_validate (addr); +} + +#ifdef HAVE_MINCORE +static int mincore_validate (void *addr, size_t len) +{ + unsigned char mvec[2]; /* Unaligned access may cross page boundary */ + + /* mincore could fail with EAGAIN but we conservatively return -1 + instead of looping. */ + if (mincore (addr, len, (unsigned char *)mvec) != 0) + { + return -1; + } + + return write_validate (addr); +} +#endif + +/* Initialise memory validation method. On linux kernels <2.6.21, + mincore() returns incorrect value for MAP_PRIVATE mappings, + such as stacks. If mincore() was available at compile time, + check if we can actually use it. If not, use msync() instead. */ +HIDDEN void +tdep_init_mem_validate (void) +{ + open_pipe (); + +#ifdef HAVE_MINCORE + unsigned char present = 1; + unw_word_t addr = PAGE_START((unw_word_t)&present); + unsigned char mvec[1]; + int ret; + while ((ret = mincore ((void*)addr, PAGE_SIZE, (unsigned char *)mvec)) == -1 && + errno == EAGAIN) {} + if (ret == 0) + { + Debug(1, "using mincore to validate memory\n"); + mem_validate_func = mincore_validate; + } + else +#endif + { + Debug(1, "using msync to validate memory\n"); + mem_validate_func = msync_validate; + } +} + +/* Cache of already validated addresses */ +#define NLGA 4 +#if defined(HAVE___CACHE_PER_THREAD) && HAVE___CACHE_PER_THREAD +// thread-local variant +static _Thread_local unw_word_t last_good_addr[NLGA]; +static _Thread_local int lga_victim; + +static int +is_cached_valid_mem(unw_word_t addr) +{ + int i; + for (i = 0; i < NLGA; i++) + { + if (addr == last_good_addr[i]) + return 1; + } + return 0; +} + +static void +cache_valid_mem(unw_word_t addr) +{ + int i, victim; + victim = lga_victim; + for (i = 0; i < NLGA; i++) { + if (last_good_addr[victim] == 0) { + last_good_addr[victim] = addr; + return; + } + victim = (victim + 1) % NLGA; + } + + /* All slots full. Evict the victim. */ + last_good_addr[victim] = addr; + victim = (victim + 1) % NLGA; + lga_victim = victim; +} + +#else +// global, thread safe variant +static _Atomic unw_word_t last_good_addr[NLGA]; +static _Atomic int lga_victim; + +static int +is_cached_valid_mem(unw_word_t addr) +{ + int i; + for (i = 0; i < NLGA; i++) + { + if (addr == atomic_load(&last_good_addr[i])) + return 1; + } + return 0; +} + +static void +cache_valid_mem(unw_word_t addr) +{ + int i, victim; + victim = atomic_load(&lga_victim); + unw_word_t zero = 0; + for (i = 0; i < NLGA; i++) { + if (atomic_compare_exchange_strong(&last_good_addr[victim], &zero, addr)) { + return; + } + victim = (victim + 1) % NLGA; + } + + /* All slots full. Evict the victim. */ + atomic_store(&last_good_addr[victim], addr); + victim = (victim + 1) % NLGA; + atomic_store(&lga_victim, victim); +} +#endif + +static int +validate_mem (unw_word_t addr) +{ + size_t len; + + if (PAGE_START(addr + sizeof (unw_word_t) - 1) == PAGE_START(addr)) + len = PAGE_SIZE; + else + len = PAGE_SIZE * 2; + + addr = PAGE_START(addr); + + if (addr == 0) + return -1; + + if (is_cached_valid_mem(addr)) + return 0; + + if (mem_validate_func ((void *) addr, len) == -1) + return -1; + + cache_valid_mem(addr); + + return 0; +} + static int access_mem (unw_addr_space_t as, unw_word_t addr, unw_word_t *val, int write, void *arg) { - if (write) + if (unlikely (write)) { Debug (16, "mem[%lx] <- %lx\n", addr, *val); *(unw_word_t *) addr = *val; } else { + /* validate address */ + const struct cursor *c = (const struct cursor *)arg; + if (likely (c != NULL) && unlikely (c->validate) + && unlikely (validate_mem (addr))) { + Debug (16, "mem[%016lx] -> invalid\n", addr); + return -1; + } *val = *(unw_word_t *) addr; Debug (16, "mem[%lx] -> %lx\n", addr, *val); } @@ -103,7 +345,7 @@ access_reg (unw_addr_space_t as, unw_regnum_t reg, unw_word_t *val, int write, void *arg) { unw_word_t *addr; - unw_tdep_context_t *uc = arg; + unw_tdep_context_t *uc = ((struct cursor *)arg)->uc; if (unw_is_fpreg (reg)) goto badreg; @@ -132,7 +374,7 @@ static int access_fpreg (unw_addr_space_t as, unw_regnum_t reg, unw_fpreg_t *val, int write, void *arg) { - unw_tdep_context_t *uc = arg; + unw_tdep_context_t *uc = ((struct cursor *)arg)->uc; unw_fpreg_t *addr; if (!unw_is_fpreg (reg)) diff --git a/src/aarch64/Ginit_local.c b/src/aarch64/Ginit_local.c index 3f0080ade..4a055fb09 100644 --- a/src/aarch64/Ginit_local.c +++ b/src/aarch64/Ginit_local.c @@ -47,7 +47,9 @@ unw_init_local_common (unw_cursor_t *cursor, unw_context_t *uc, unsigned use_pre Debug (1, "(cursor=%p)\n", c); c->dwarf.as = unw_local_addr_space; - c->dwarf.as_arg = uc; + c->dwarf.as_arg = c; + c->uc = uc; + c->validate = 0; return common_init (c, use_prev_instr); } diff --git a/src/aarch64/Ginit_remote.c b/src/aarch64/Ginit_remote.c index 26d11ba94..e300173ca 100644 --- a/src/aarch64/Ginit_remote.c +++ b/src/aarch64/Ginit_remote.c @@ -39,7 +39,16 @@ unw_init_remote (unw_cursor_t *cursor, unw_addr_space_t as, void *as_arg) Debug (1, "(cursor=%p)\n", c); c->dwarf.as = as; - c->dwarf.as_arg = as_arg; + if (as == unw_local_addr_space) + { + c->dwarf.as_arg = c; + c->uc = as_arg; + } + else + { + c->dwarf.as_arg = as_arg; + c->uc = 0; + } return common_init (c, 0); #endif /* !UNW_LOCAL_ONLY */ } diff --git a/src/aarch64/Gresume.c b/src/aarch64/Gresume.c index 2cc161360..445bac70f 100644 --- a/src/aarch64/Gresume.c +++ b/src/aarch64/Gresume.c @@ -34,7 +34,7 @@ aarch64_local_resume (unw_addr_space_t as, unw_cursor_t *cursor, void *arg) { #ifdef __linux__ struct cursor *c = (struct cursor *) cursor; - unw_tdep_context_t *uc = c->dwarf.as_arg; + unw_tdep_context_t *uc = c->uc; if (c->sigcontext_format == AARCH64_SCF_NONE) { diff --git a/src/aarch64/Gstep.c b/src/aarch64/Gstep.c index fdf64a73f..92e2a6663 100644 --- a/src/aarch64/Gstep.c +++ b/src/aarch64/Gstep.c @@ -70,7 +70,7 @@ aarch64_handle_signal_frame (unw_cursor_t *cursor) c->sigcontext_sp = c->dwarf.cfa; c->sigcontext_pc = c->dwarf.ip; - if (ret) + if (ret > 0) { c->sigcontext_format = AARCH64_SCF_LINUX_RT_SIGFRAME; sc_addr = sp_addr + sizeof (siginfo_t) + LINUX_UC_MCONTEXT_OFF; @@ -134,14 +134,30 @@ int unw_step (unw_cursor_t *cursor) { struct cursor *c = (struct cursor *) cursor; + int validate = c->validate; int ret; Debug (1, "(cursor=%p, ip=0x%016lx, cfa=0x%016lx))\n", c, c->dwarf.ip, c->dwarf.cfa); + /* Validate all addresses before dereferencing. */ + c->validate = 1; + /* Check if this is a signal frame. */ - if (unw_is_signal_frame (cursor) > 0) + ret = unw_is_signal_frame (cursor); + if (ret > 0) return aarch64_handle_signal_frame (cursor); + else if (unlikely (ret < 0)) + { + /* IP points to non-mapped memory. */ + /* This is probably SIGBUS. */ + /* Try to load LR in IP to recover. */ + Debug(1, "Invalid address found in the call stack: 0x%lx\n", c->dwarf.ip); + dwarf_get (&c->dwarf, c->dwarf.loc[UNW_AARCH64_X30], &c->dwarf.ip); + } + + /* Restore default memory validation state */ + c->validate = validate; ret = dwarf_step (&c->dwarf); Debug(1, "dwarf_step()=%d\n", ret);
Locations
Projects
Search
Status Monitor
Help
Open Build Service
OBS Manuals
API Documentation
OBS Portal
Reporting a Bug
Contact
Mailing List
Forums
Chat (IRC)
Twitter
Open Build Service (OBS)
is an
openSUSE project
.