diff --git a/runtime/include/software_interrupt.h b/runtime/include/software_interrupt.h index e1f048c5..a2069387 100644 --- a/runtime/include/software_interrupt.h +++ b/runtime/include/software_interrupt.h @@ -92,6 +92,11 @@ software_interrupt_deferred_sigalrm_replay() * Exports from software_interrupt.c * ************************/ +/* Size of each worker's alternate signal stack. The SA_ONSTACK handlers (SIGSEGV, SIGFPE) only siglongjmp out, + * so this needs little space; keep it comfortably above MINSIGSTKSZ. */ +#define SOFTWARE_INTERRUPT_ALT_STACK_SIZE (64 * 1024) + +void software_interrupt_alt_stack_initialize(void); void software_interrupt_arm_timer(void); void software_interrupt_cleanup(void); void software_interrupt_disarm_timer(void); diff --git a/runtime/src/software_interrupt.c b/runtime/src/software_interrupt.c index 14c1ab1d..cca38f00 100644 --- a/runtime/src/software_interrupt.c +++ b/runtime/src/software_interrupt.c @@ -278,6 +278,19 @@ software_interrupt_initialize(void) /* Mask this signal on the listener thread */ software_interrupt_mask_signal(signal); + /* Deliver the synchronous fault signals (SIGSEGV, SIGFPE) on the per-worker alternate signal + * stack registered by software_interrupt_alt_stack_initialize(). A sandbox stack overflow faults + * on the stack's guard page, leaving no room to deliver the signal on the overflowed stack; running + * the handler on the alternate stack keeps that overflow recoverable (the handler siglongjmps back + * to the sandbox entry on a valid part of the stack). The asynchronous preemption signals (SIGALRM, + * SIGUSR1) stay on the normal stack: they context-switch away rather than returning, which does not + * compose with an alternate stack. */ + if (signal == SIGSEGV || signal == SIGFPE) { + signal_action.sa_flags |= SA_ONSTACK; + } else { + signal_action.sa_flags &= ~SA_ONSTACK; + } + /* But register the handler for this signal for the process */ if (sigaction(signal, &signal_action, NULL)) { perror("sigaction"); @@ -288,6 +301,28 @@ software_interrupt_initialize(void) software_interrupt_counts_alloc(); } +/** + * Registers a per-thread alternate signal stack so that handlers flagged SA_ONSTACK (SIGSEGV, SIGFPE) can run + * even when the active sandbox stack is exhausted. Must be called by each worker before it unmasks those signals. + */ +void +software_interrupt_alt_stack_initialize(void) +{ + stack_t alt_stack = { 0 }; + alt_stack.ss_flags = 0; + alt_stack.ss_size = SOFTWARE_INTERRUPT_ALT_STACK_SIZE; + alt_stack.ss_sp = malloc(alt_stack.ss_size); + if (alt_stack.ss_sp == NULL) { + perror("malloc alternate signal stack"); + exit(1); + } + + if (sigaltstack(&alt_stack, NULL) != 0) { + perror("sigaltstack"); + exit(1); + } +} + void software_interrupt_cleanup() { diff --git a/runtime/src/worker_thread.c b/runtime/src/worker_thread.c index ae993ec4..a96407e0 100644 --- a/runtime/src/worker_thread.c +++ b/runtime/src/worker_thread.c @@ -62,6 +62,10 @@ worker_thread_main(void *argument) tenant_timeout_get_priority); } + /* Register this worker's alternate signal stack before unmasking the fault signals, so a sandbox stack + * overflow (which faults on the stack guard page) can still be delivered and recovered from. */ + software_interrupt_alt_stack_initialize(); + software_interrupt_unmask_signal(SIGFPE); software_interrupt_unmask_signal(SIGSEGV);