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.
This commit is contained in:
Matthew Flatt 2018-03-26 12:33:57 -06:00
parent a70e6bdfc9
commit 9d0ab74e9e
3 changed files with 93 additions and 3 deletions

View File

@ -249,6 +249,7 @@
# define USE_IEEE_FP_PREDS # define USE_IEEE_FP_PREDS
# define USE_MAP_ANON # define USE_MAP_ANON
# define IMPLEMENT_WRITE_XOR_EXECUTE_BY_SIGNAL_HANDLER
# if defined(__x86_64__) # if defined(__x86_64__)
# define MZ_USE_JIT_X86_64 # define MZ_USE_JIT_X86_64

View File

@ -972,6 +972,10 @@ static void *mmap_sector(int count, int executable)
fd = open("/dev/zero", O_RDWR); fd = open("/dev/zero", O_RDWR);
#endif #endif
#ifdef IMPLEMENT_WRITE_XOR_EXECUTE_BY_SIGNAL_HANDLER
executable = 0;
#endif
p = mmap(NULL, (count + 1) << LOG_SECTOR_SEGMENT_SIZE, p = mmap(NULL, (count + 1) << LOG_SECTOR_SEGMENT_SIZE,
PROT_READ | PROT_WRITE | (executable ? PROT_EXEC : 0), PROT_READ | PROT_WRITE | (executable ? PROT_EXEC : 0),
MAP_PRIVATE | flags, fd, 0); MAP_PRIVATE | flags, fd, 0);

View File

@ -109,6 +109,10 @@ extern MZGC_DLLIMPORT void GC_register_indirect_disappearing_link(void **link, v
static void init_allocation_callback(void); static void init_allocation_callback(void);
#endif #endif
#ifdef IMPLEMENT_WRITE_XOR_EXECUTE_BY_SIGNAL_HANDLER
static void install_w_xor_x_handler();
#endif
SHARED_OK static int use_registered_statics; 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(); init_allocation_callback();
# endif # endif
#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) 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; static int fd, fd_created;
#endif #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 LOG_CODE_MALLOC(lvl, s) /* if (lvl > 1) s */
#define CODE_PAGE_OF(p) ((void *)(((uintptr_t)p) & ~(page_size - 1))) #define CODE_PAGE_OF(p) ((void *)(((uintptr_t)p) & ~(page_size - 1)))
@ -1010,13 +1024,13 @@ static void *malloc_page(intptr_t size)
} }
#else #else
# ifdef MAP_ANON # 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 # else
if (!fd_created) { if (!fd_created) {
fd_created = 1; fd_created = 1;
fd = open("/dev/zero", O_RDWR); 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 # endif
if (r == (void *)-1) if (r == (void *)-1)
r = NULL; r = NULL;
@ -1379,7 +1393,7 @@ void *scheme_malloc_gcable_code(intptr_t size)
# else # else
{ {
int r; 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) { if (r == -1) {
scheme_log_abort("mprotect for generate-code page failed; aborting"); scheme_log_abort("mprotect for generate-code page failed; aborting");
} }
@ -1417,6 +1431,77 @@ void scheme_notify_code_gc()
} }
#endif #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 <signal.h>
# include <sys/param.h>
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 #ifdef MZ_PRECISE_GC
END_XFORM_SKIP; END_XFORM_SKIP;
#endif #endif