From 04289fa326b790c1a4abb236d1f9d913bacfc8c6 Mon Sep 17 00:00:00 2001 From: Jameson Nash Date: Thu, 29 Jul 2021 12:09:51 -0400 Subject: [PATCH] unix: protect fork in uv_spawn from signals Years ago, we found that various kernels (linux, macOS) were known to fail if they try to deliver a signal during this syscall, so we prevent that from happening. They may have fixed those issues, but it is generally just a bad time for signals to arrive (glibc blocks them here, for example, including some more internal ones that it won't let us touch here). We try to be a bit conservative, and leave many signals unblocked which could happen during normal execution and should terminate the process if they do. There is a small race window after the child starts before we clear the old handlers, if the user was to send an fake signal from elsewhere, but that should be quite unlikely. PR-URL: https://github.com/libuv/libuv/pull/3251 Reviewed-By: Ben Noordhuis --- src/unix/process.c | 97 +++++++++++++++++++++++++++------------------- 1 file changed, 57 insertions(+), 40 deletions(-) diff --git a/src/unix/process.c b/src/unix/process.c index f4aebb04..91bf3c50 100644 --- a/src/unix/process.c +++ b/src/unix/process.c @@ -26,6 +26,7 @@ #include #include #include +#include #include #include @@ -216,13 +217,32 @@ static void uv__process_child_init(const uv_process_options_t* options, int stdio_count, int (*pipes)[2], int error_fd) { - sigset_t set; + sigset_t signewset; int close_fd; int use_fd; - int err; int fd; int n; + /* Reset signal disposition first. Use a hard-coded limit because NSIG is not + * fixed on Linux: it's either 32, 34 or 64, depending on whether RT signals + * are enabled. We are not allowed to touch RT signal handlers, glibc uses + * them internally. + */ + for (n = 1; n < 32; n += 1) { + if (n == SIGKILL || n == SIGSTOP) + continue; /* Can't be changed. */ + +#if defined(__HAIKU__) + if (n == SIGKILLTHR) + continue; /* Can't be changed. */ +#endif + + if (SIG_ERR != signal(n, SIG_DFL)) + continue; + + uv__write_errno(error_fd); + } + if (options->flags & UV_PROCESS_DETACHED) setsid(); @@ -304,32 +324,10 @@ static void uv__process_child_init(const uv_process_options_t* options, environ = options->env; } - /* Reset signal disposition. Use a hard-coded limit because NSIG - * is not fixed on Linux: it's either 32, 34 or 64, depending on - * whether RT signals are enabled. We are not allowed to touch - * RT signal handlers, glibc uses them internally. - */ - for (n = 1; n < 32; n += 1) { - if (n == SIGKILL || n == SIGSTOP) - continue; /* Can't be changed. */ - -#if defined(__HAIKU__) - if (n == SIGKILLTHR) - continue; /* Can't be changed. */ -#endif - - if (SIG_ERR != signal(n, SIG_DFL)) - continue; - - uv__write_errno(error_fd); - } - - /* Reset signal mask. */ - sigemptyset(&set); - err = pthread_sigmask(SIG_SETMASK, &set, NULL); - - if (err != 0) - uv__write_errno(error_fd); + /* Reset signal mask just before exec. */ + sigemptyset(&signewset); + if (sigprocmask(SIG_SETMASK, &signewset, NULL) != 0) + abort(); #ifdef __MVS__ execvpe(options->file, options->args, environ); @@ -338,6 +336,7 @@ static void uv__process_child_init(const uv_process_options_t* options, #endif uv__write_errno(error_fd); + abort(); } #endif @@ -349,6 +348,8 @@ int uv_spawn(uv_loop_t* loop, /* fork is marked __WATCHOS_PROHIBITED __TVOS_PROHIBITED. */ return UV_ENOSYS; #else + sigset_t signewset; + sigset_t sigoldset; int signal_pipe[2] = { -1, -1 }; int pipes_storage[8][2]; int (*pipes)[2]; @@ -423,25 +424,41 @@ int uv_spawn(uv_loop_t* loop, /* Acquire write lock to prevent opening new fds in worker threads */ uv_rwlock_wrlock(&loop->cloexec_lock); - pid = fork(); - if (pid == -1) { - err = UV__ERR(errno); - uv_rwlock_wrunlock(&loop->cloexec_lock); - uv__close(signal_pipe[0]); - uv__close(signal_pipe[1]); - goto error; - } - - if (pid == 0) { - uv__process_child_init(options, stdio_count, pipes, signal_pipe[1]); + /* Start the child with most signals blocked, to avoid any issues before we + * can reset them, but allow program failures to exit (and not hang). */ + sigfillset(&signewset); + sigdelset(&signewset, SIGKILL); + sigdelset(&signewset, SIGSTOP); + sigdelset(&signewset, SIGTRAP); + sigdelset(&signewset, SIGSEGV); + sigdelset(&signewset, SIGBUS); + sigdelset(&signewset, SIGILL); + sigdelset(&signewset, SIGSYS); + sigdelset(&signewset, SIGABRT); + if (pthread_sigmask(SIG_BLOCK, &signewset, &sigoldset) != 0) + abort(); + + pid = fork(); + if (pid == -1) + err = UV__ERR(errno); + + if (pid == 0) + uv__process_child_init(options, stdio_count, pipes, signal_pipe[1]); + + if (pthread_sigmask(SIG_SETMASK, &sigoldset, NULL) != 0) abort(); - } /* Release lock in parent process */ uv_rwlock_wrunlock(&loop->cloexec_lock); + uv__close(signal_pipe[1]); + if (pid == -1) { + uv__close(signal_pipe[0]); + goto error; + } + process->status = 0; exec_errorno = 0; do