From 2cbaeb786571055af464d2b9dd564e98740e36d3 Mon Sep 17 00:00:00 2001 From: Matthew Flatt Date: Wed, 10 Feb 2021 14:31:53 -0700 Subject: [PATCH] Unix: restore all signal handlers after creating a subprocess fork For each signal handler that is changed, save its state, and restore the state in a child process after a `fork` and before an `execve`. Also save and restore the signal mask. This change requires cooperation from various subsystems, which often takes the form of a callback registered with the subsystem to be called before adjusts a signal handler. Closes #3609 --- .../tests/racket/unix_check.c | 10 ++++ racket/src/ChezScheme/c/schsig.c | 30 ++++++++--- racket/src/ChezScheme/s/mkheader.ss | 1 + racket/src/bc/gc2/gc2.h | 4 ++ racket/src/bc/gc2/newgc.c | 1 + racket/src/bc/gc2/sighand.c | 4 ++ racket/src/bc/src/salloc.c | 3 ++ racket/src/cs/c/boot.c | 2 + racket/src/rktio/rktio.h | 13 ++++- racket/src/rktio/rktio_private.h | 2 + racket/src/rktio/rktio_process.c | 2 + racket/src/rktio/rktio_signal.c | 52 +++++++++++++++++++ 12 files changed, 115 insertions(+), 9 deletions(-) diff --git a/pkgs/racket-test-core/tests/racket/unix_check.c b/pkgs/racket-test-core/tests/racket/unix_check.c index 8345b14e66..f7f7ade103 100644 --- a/pkgs/racket-test-core/tests/racket/unix_check.c +++ b/pkgs/racket-test-core/tests/racket/unix_check.c @@ -7,6 +7,16 @@ int main() { sigset_t set, old_set; + struct sigaction sa; + int i; + + /* SIGPROF tends to be near the end of the range of signal IDs */ + for (i = 0; i < SIGPROF; i++) { + sigaction(i, NULL, &sa); + if (sa.sa_handler != SIG_DFL) + return 1; + } + sigemptyset(&set); sigprocmask(SIG_BLOCK, &set, &old_set); diff --git a/racket/src/ChezScheme/c/schsig.c b/racket/src/ChezScheme/c/schsig.c index 7baabb51f2..5e8f369309 100644 --- a/racket/src/ChezScheme/c/schsig.c +++ b/racket/src/ChezScheme/c/schsig.c @@ -25,6 +25,8 @@ static void handle_call_error PROTO((ptr tc, iptr type, ptr x)); static void init_signal_handlers PROTO((void)); static void keyboard_interrupt PROTO((ptr tc)); +static void (*register_modified_signal)(int); + ptr S_get_scheme_arg(tc, n) ptr tc; iptr n; { if (n <= asm_arg_reg_cnt) return REGARG(tc, n); @@ -533,6 +535,10 @@ void S_noncontinuable_interrupt() { do_error(ERROR_NONCONTINUABLE_INTERRUPT,"","",Snil); } +void Sscheme_register_signal_registerer(void (*registerer)(int)) { + register_modified_signal = registerer; +} + #ifdef WIN32 ptr S_dequeue_scheme_signals(UNUSED ptr tc) { return Snil; @@ -726,20 +732,28 @@ static void handle_signal(INT sig, UNUSED siginfo_t *si, UNUSED void *data) { } } +static void no_op_register(UNUSED int sigid) { +} + +#define SIGACTION(id, act_p, old_p) (register_modified_signal(id), sigaction(id, act_p, old_p)) + static void init_signal_handlers() { struct sigaction act; + if (register_modified_signal == NULL) + register_modified_signal = no_op_register; + sigemptyset(&act.sa_mask); /* drop pending keyboard interrupts */ act.sa_flags = 0; act.sa_handler = SIG_IGN; - sigaction(SIGINT, &act, (struct sigaction *)0); + SIGACTION(SIGINT, &act, (struct sigaction *)0); /* ignore broken pipe signals */ act.sa_flags = 0; act.sa_handler = SIG_IGN; - sigaction(SIGPIPE, &act, (struct sigaction *)0); + SIGACTION(SIGPIPE, &act, (struct sigaction *)0); /* set up to catch SIGINT w/no system call restart */ #ifdef SA_INTERRUPT @@ -748,7 +762,7 @@ static void init_signal_handlers() { act.sa_flags = SA_SIGINFO; #endif /* SA_INTERRUPT */ act.sa_sigaction = handle_signal; - sigaction(SIGINT, &act, (struct sigaction *)0); + SIGACTION(SIGINT, &act, (struct sigaction *)0); #ifdef BSDI siginterrupt(SIGINT, 1); #endif @@ -760,14 +774,14 @@ static void init_signal_handlers() { act.sa_flags |= SA_RESTART; #endif /* SA_RESTART */ #ifdef SIGQUIT - sigaction(SIGQUIT, &act, (struct sigaction *)0); + SIGACTION(SIGQUIT, &act, (struct sigaction *)0); #endif /* SIGQUIT */ - sigaction(SIGILL, &act, (struct sigaction *)0); - sigaction(SIGFPE, &act, (struct sigaction *)0); + SIGACTION(SIGILL, &act, (struct sigaction *)0); + SIGACTION(SIGFPE, &act, (struct sigaction *)0); #ifdef SIGBUS - sigaction(SIGBUS, &act, (struct sigaction *)0); + SIGACTION(SIGBUS, &act, (struct sigaction *)0); #endif /* SIGBUS */ - sigaction(SIGSEGV, &act, (struct sigaction *)0); + SIGACTION(SIGSEGV, &act, (struct sigaction *)0); } #endif /* WIN32 */ diff --git a/racket/src/ChezScheme/s/mkheader.ss b/racket/src/ChezScheme/s/mkheader.ss index 0532cff37d..400c91de04 100644 --- a/racket/src/ChezScheme/s/mkheader.ss +++ b/racket/src/ChezScheme/s/mkheader.ss @@ -445,6 +445,7 @@ (export "int" "Sscheme_script" "(const char *, int, const char *[])") (export "int" "Sscheme_program" "(const char *, int, const char *[])") (export "void" "Sscheme_deinit" "(void)") + (export "void" "Sscheme_register_signal_registerer" "(void (*f)(int))") (when-feature pthreads (nl) (comment "Thread support.") diff --git a/racket/src/bc/gc2/gc2.h b/racket/src/bc/gc2/gc2.h index 50de3453e6..c2b276c581 100644 --- a/racket/src/bc/gc2/gc2.h +++ b/racket/src/bc/gc2/gc2.h @@ -139,6 +139,10 @@ GC2_EXTERN void (*GC_report_out_of_memory)(void); Called by GC when it has to give up, maybe due to running out of memory during a collection. */ +GC2_EXTERN void (*GC_report_signal_handle_modify)(int); +/* + Called by GC just before it changes an OS signal handler. */ + GC2_EXTERN void GC_dump(void); /* Dumps memory state info to stderr. */ diff --git a/racket/src/bc/gc2/newgc.c b/racket/src/bc/gc2/newgc.c index 9b339e946c..7ce5799e5d 100644 --- a/racket/src/bc/gc2/newgc.c +++ b/racket/src/bc/gc2/newgc.c @@ -185,6 +185,7 @@ extern double scheme_get_inexact_milliseconds(void); GC_Out_Of_Memory_Proc GC_out_of_memory; void (*GC_report_out_of_memory)(void); +void (*GC_report_signal_handle_modify)(int); GC_Out_Of_Memory_Proc GC_get_out_of_memory(void) { diff --git a/racket/src/bc/gc2/sighand.c b/racket/src/bc/gc2/sighand.c index c93462d4f9..8a92883ed6 100644 --- a/racket/src/bc/gc2/sighand.c +++ b/racket/src/bc/gc2/sighand.c @@ -247,8 +247,12 @@ static void initialize_signal_handler(GCTYPE *gc) # ifdef NEED_SIGSTACK act.sa_flags |= SA_ONSTACK; # endif + if (GC_report_signal_handle_modify) + GC_report_signal_handle_modify(USE_SIGACTON_SIGNAL_KIND); sigaction(USE_SIGACTON_SIGNAL_KIND, &act, &oact); # ifdef USE_ANOTHER_SIGACTON_SIGNAL_KIND + if (GC_report_signal_handle_modify) + GC_report_signal_handle_modify(USE_ANOTHER_SIGACTON_SIGNAL_KIND); sigaction(USE_ANOTHER_SIGACTON_SIGNAL_KIND, &act, &oact); # endif } diff --git a/racket/src/bc/src/salloc.c b/racket/src/bc/src/salloc.c index 3da4647aea..7d4f5150e4 100644 --- a/racket/src/bc/src/salloc.c +++ b/racket/src/bc/src/salloc.c @@ -107,6 +107,7 @@ extern MZGC_DLLIMPORT void GC_init(); void scheme_set_stack_base(void *base, int no_auto_statics) XFORM_SKIP_PROC { #ifdef MZ_PRECISE_GC + GC_report_signal_handle_modify = rktio_will_modify_os_signal_handler; GC_init_type_tags(_scheme_last_type_, scheme_pair_type, scheme_mutable_pair_type, scheme_weak_box_type, scheme_ephemeron_type, scheme_rt_weak_array, @@ -464,6 +465,7 @@ void scheme_set_signal_handler(int sig_id, Scheme_Signal_Handler_Proc proc) XFOR sigemptyset(&sa.sa_mask); sa.sa_flags = 0; sa.sa_handler = (proc ? proc : SIG_IGN); + rktio_will_modify_os_signal_handler(sig_id); sigaction(sig_id, &sa, NULL); #endif } @@ -1655,6 +1657,7 @@ static void install_w_xor_x_handler() sigaddset(&act.sa_mask, SIGINT); sigaddset(&act.sa_mask, SIGCHLD); act.sa_flags = SA_SIGINFO; + rktio_will_modify_os_signal_handler(SIG_W_XOR_X); sigaction(SIG_W_XOR_X, &act, &oact); previous_fault_handler = oact.sa_sigaction; } diff --git a/racket/src/cs/c/boot.c b/racket/src/cs/c/boot.c index 8f2dd6f662..c1be6cb01e 100644 --- a/racket/src/cs/c/boot.c +++ b/racket/src/cs/c/boot.c @@ -109,6 +109,8 @@ void racket_boot(racket_boot_arguments_t *ba) rktio_set_dll_procs(ba->dll_open, ba->dll_find_object, ba->dll_close); #endif + Sscheme_register_signal_registerer(rktio_will_modify_os_signal_handler); + Sscheme_init(NULL); if ((ba->argc == 4) && !strcmp(ba->argv[0], "--cross-server")) diff --git a/racket/src/rktio/rktio.h b/racket/src/rktio/rktio.h index 41d7cf04e8..2474bf973b 100644 --- a/racket/src/rktio/rktio.h +++ b/racket/src/rktio/rktio.h @@ -1001,7 +1001,7 @@ RKTIO_EXTERN void rktio_flush_signals_received(rktio_t *rktio); RKTIO_EXTERN void rktio_install_os_signal_handler(rktio_t *rktio); /* Installs OS-level handlers for SIGINT, SIGTERM, and SIGHUP (or - Ctl-C on Windows) to signal the handle of `rktio` and also record + Ctl-C on Windows) to signal the handle of `rktio` and also records the signal for reporting via `rktio_poll_os_signal`. Only one `rktio` can be registered this way at a time. This function must not be called in two threads at the same time. */ @@ -1016,6 +1016,17 @@ enum { RKTIO_NUM_OS_SIGNALS }; +RKTIO_EXTERN void rktio_will_modify_os_signal_handler(int sig_id); +/* Registers with rktio that an operating-system signal handler is + about to be modified within the process but outside of rktio, where + `sig_id` is a signal identifier --- such as SIGINT or SIGTERM. This + notification allows rktio to record the current signal disposition + so that it can be restored after forking a new Unix process. + Signal registrations should happen only before multiple threads use + rktio, and registration of the signal can happen before any + `rktio_init` call. On the first `rktio_will_modify_os_signal_handler` + call, the signal mask is also recorded to be restored in a fork. */ + /*************************************************/ /* Time and date */ diff --git a/racket/src/rktio/rktio_private.h b/racket/src/rktio/rktio_private.h index f81cb41523..e36e223e89 100644 --- a/racket/src/rktio/rktio_private.h +++ b/racket/src/rktio/rktio_private.h @@ -416,9 +416,11 @@ char *rktio_strndup(char *s, intptr_t len); #ifdef RKTIO_SYSTEM_UNIX void rktio_set_signal_handler(int sig_id, void (*proc)(int)); +void rktio_restore_modified_signal_handlers(); #endif void rktio_forget_os_signal_handler(rktio_t *rktio); + #ifdef RKTIO_SYSTEM_WINDOWS int rktio_system_time_is_dst(SYSTEMTIME *st, TIME_ZONE_INFORMATION *_tz); #endif diff --git a/racket/src/rktio/rktio_process.c b/racket/src/rktio/rktio_process.c index be17d7aa2a..ae94892922 100644 --- a/racket/src/rktio/rktio_process.c +++ b/racket/src/rktio/rktio_process.c @@ -1526,6 +1526,8 @@ rktio_process_result_t *rktio_process(rktio_t *rktio, rktio_close_fds_after_fork(close_after_len, 0, 1, 2); } + rktio_restore_modified_signal_handlers(); + /* Set real CWD: */ if (!rktio_set_current_directory(rktio, current_directory)) { fprintf(stderr, "racket: chdir failed to: %s\n", current_directory); diff --git a/racket/src/rktio/rktio_signal.c b/racket/src/rktio/rktio_signal.c index caf143f6c2..73448ebd7a 100644 --- a/racket/src/rktio/rktio_signal.c +++ b/racket/src/rktio/rktio_signal.c @@ -103,6 +103,58 @@ void rktio_set_signal_handler(int sig_id, void (*proc)(int)) sigemptyset(&sa.sa_mask); sa.sa_flags = 0; sa.sa_handler = proc; + rktio_will_modify_os_signal_handler(sig_id); sigaction(sig_id, &sa, NULL); } #endif + +/*========================================================================*/ +/* Signal handler original state */ +/*========================================================================*/ + +typedef struct signal_handler_saved_disposition { + int sig_id; + struct signal_handler_saved_disposition *next; +#if defined(RKTIO_SYSTEM_UNIX) + struct sigaction sa; +#endif +} signal_handler_saved_disposition; + +static signal_handler_saved_disposition *saved_dispositions; +static sigset_t initial_procmask; + +void rktio_will_modify_os_signal_handler(int sig_id) { + signal_handler_saved_disposition *saved; + +#ifdef RKTIO_SYSTEM_UNIX + if (saved_dispositions == NULL) + sigprocmask(SIG_SETMASK, NULL, &initial_procmask); +#endif + + for (saved = saved_dispositions; saved; saved = saved->next) + if (saved->sig_id == sig_id) + return; + + saved = malloc(sizeof(signal_handler_saved_disposition)); + saved->next = saved_dispositions; + saved->sig_id = sig_id; + saved_dispositions = saved; + +#if defined(RKTIO_SYSTEM_UNIX) + sigaction(sig_id, NULL, &saved->sa); +#endif +} + +#ifdef RKTIO_SYSTEM_UNIX +/* called in a child thread after `fork */ +void rktio_restore_modified_signal_handlers() { + if (saved_dispositions) { + signal_handler_saved_disposition *saved; + + for (saved = saved_dispositions; saved; saved = saved->next) + sigaction(saved->sig_id, &saved->sa, NULL); + + sigprocmask(SIG_SETMASK, &initial_procmask, NULL); + } +} +#endif