From 9d0ab74e9e46cbdb7e95d1398fdbc985a9527b61 Mon Sep 17 00:00:00 2001 From: Matthew Flatt Date: Mon, 26 Mar 2018 12:33:57 -0600 Subject: [PATCH] OpenBSD: follow the letter of W^X Conform to W^X by using a signal handler that switches between W and X mode on any fault. That's not the spirit of W^X, certainly, but it should make Racket work without special configuration. Beware that this change can turn some crashes into infinite loops. It may be possible to detect those loops, but I didn't find a good and portable way, so far. --- racket/src/racket/sconfig.h | 1 + racket/src/racket/sgc/sgc.c | 4 ++ racket/src/racket/src/salloc.c | 91 ++++++++++++++++++++++++++++++++-- 3 files changed, 93 insertions(+), 3 deletions(-) diff --git a/racket/src/racket/sconfig.h b/racket/src/racket/sconfig.h index 4e442fd7a7..dbca851dbb 100644 --- a/racket/src/racket/sconfig.h +++ b/racket/src/racket/sconfig.h @@ -249,6 +249,7 @@ # define USE_IEEE_FP_PREDS # define USE_MAP_ANON +# define IMPLEMENT_WRITE_XOR_EXECUTE_BY_SIGNAL_HANDLER # if defined(__x86_64__) # define MZ_USE_JIT_X86_64 diff --git a/racket/src/racket/sgc/sgc.c b/racket/src/racket/sgc/sgc.c index b47631b486..92e0ed3027 100644 --- a/racket/src/racket/sgc/sgc.c +++ b/racket/src/racket/sgc/sgc.c @@ -972,6 +972,10 @@ static void *mmap_sector(int count, int executable) fd = open("/dev/zero", O_RDWR); #endif +#ifdef IMPLEMENT_WRITE_XOR_EXECUTE_BY_SIGNAL_HANDLER + executable = 0; +#endif + p = mmap(NULL, (count + 1) << LOG_SECTOR_SEGMENT_SIZE, PROT_READ | PROT_WRITE | (executable ? PROT_EXEC : 0), MAP_PRIVATE | flags, fd, 0); diff --git a/racket/src/racket/src/salloc.c b/racket/src/racket/src/salloc.c index 4f45b0fe18..54f8ae932c 100644 --- a/racket/src/racket/src/salloc.c +++ b/racket/src/racket/src/salloc.c @@ -109,6 +109,10 @@ extern MZGC_DLLIMPORT void GC_register_indirect_disappearing_link(void **link, v static void init_allocation_callback(void); #endif +#ifdef IMPLEMENT_WRITE_XOR_EXECUTE_BY_SIGNAL_HANDLER +static void install_w_xor_x_handler(); +#endif + SHARED_OK static int use_registered_statics; /************************************************************************/ @@ -155,6 +159,10 @@ void scheme_set_stack_base(void *base, int no_auto_statics) XFORM_SKIP_PROC init_allocation_callback(); # endif #endif + +#ifdef IMPLEMENT_WRITE_XOR_EXECUTE_BY_SIGNAL_HANDLER + install_w_xor_x_handler(); +#endif } void scheme_set_current_os_thread_stack_base(void *base) @@ -936,6 +944,12 @@ THREAD_LOCAL_DECL(intptr_t scheme_code_page_total); static int fd, fd_created; #endif +#ifdef IMPLEMENT_WRITE_XOR_EXECUTE_BY_SIGNAL_HANDLER +# define MAYBE_PROT_EXEC 0 +#else +# define MAYBE_PROT_EXEC PROT_EXEC +#endif + #define LOG_CODE_MALLOC(lvl, s) /* if (lvl > 1) s */ #define CODE_PAGE_OF(p) ((void *)(((uintptr_t)p) & ~(page_size - 1))) @@ -1010,13 +1024,13 @@ static void *malloc_page(intptr_t size) } #else # ifdef MAP_ANON - r = mmap(NULL, size, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANON, -1, 0); + r = mmap(NULL, size, PROT_READ | PROT_WRITE | MAYBE_PROT_EXEC, MAP_PRIVATE | MAP_ANON, -1, 0); # else if (!fd_created) { fd_created = 1; fd = open("/dev/zero", O_RDWR); } - r = mmap(NULL, size, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE, fd, 0); + r = mmap(NULL, size, PROT_READ | PROT_WRITE | MAYBE_PROT_EXEC, MAP_PRIVATE, fd, 0); # endif if (r == (void *)-1) r = NULL; @@ -1379,7 +1393,7 @@ void *scheme_malloc_gcable_code(intptr_t size) # else { int r; - r = mprotect ((void *) page, length, PROT_READ | PROT_WRITE | PROT_EXEC); + r = mprotect ((void *) page, length, PROT_READ | PROT_WRITE | MAYBE_PROT_EXEC); if (r == -1) { scheme_log_abort("mprotect for generate-code page failed; aborting"); } @@ -1417,6 +1431,77 @@ void scheme_notify_code_gc() } #endif +#ifdef IMPLEMENT_WRITE_XOR_EXECUTE_BY_SIGNAL_HANDLER + +/* We abide by W^X by following the letter of the law, but not the + sprirt. Pages are mapped without execute mode. When the process + crashes by trying to execute code from those pages, we switch the + page from writable to executable --- or vice versa if the process + changes back to writing. */ + +# include +# include +static intptr_t wx_page_size; +static int try_x = 0; +static void (*previous_fault_handler)(int sn, siginfo_t *si, void *ctx); + +static void fault_handler(int sn, siginfo_t *si, void *ctx) +{ + void *addr = si->si_addr; + int fail = 0; + +#ifdef MZ_PRECISE_GC + /* For precise GC, defer to its handler for GC-managed pages, which + are never intended to be executable pages */ + if (GC_is_on_allocated_page(addr)) { + previous_fault_handler(sn, si, ctx); + return; + } +#endif + + addr = (char *)addr - ((intptr_t)addr & (wx_page_size - 1)); + + if (!addr) { + /* Always fail on the first page, such as for a NULL pointer + misuse */ + fail = 1; + } else if (try_x) { + /* Maybe switching to execute mode will help. If not, we'll + get called again, and we'll try allowing writes */ + if (mprotect(addr, wx_page_size, PROT_READ | PROT_EXEC)) + fail = 1; + try_x = 0; + } else { + /* Maybe switching to write mode will help... */ + if (mprotect(addr, wx_page_size, PROT_READ | PROT_WRITE)) + fail = 1; + try_x = 1; + } + + if (fail) { + fprintf(stderr, "SIGSEGV at %p\n", si->si_addr); + abort(); + } +} + +static void install_w_xor_x_handler() +{ + wx_page_size = sysconf (_SC_PAGESIZE); + { + struct sigaction act, oact; + memset(&act, 0, sizeof(act)); + act.sa_sigaction = fault_handler; + sigemptyset(&act.sa_mask); + sigaddset(&act.sa_mask, SIGINT); + sigaddset(&act.sa_mask, SIGCHLD); + act.sa_flags = SA_SIGINFO; + sigaction(SIGSEGV, &act, &oact); + previous_fault_handler = oact.sa_sigaction; + } +} + +#endif + #ifdef MZ_PRECISE_GC END_XFORM_SKIP; #endif