diff --git a/bubblewrap.c b/bubblewrap.c index 9b78a9ae..125ff385 100644 --- a/bubblewrap.c +++ b/bubblewrap.c @@ -87,6 +87,7 @@ static bool opt_unshare_cgroup_try = FALSE; static bool opt_needs_devpts = FALSE; static bool opt_new_session = FALSE; static bool opt_die_with_parent = FALSE; +static bool opt_forward_signals = FALSE; static uid_t opt_sandbox_uid = -1; static gid_t opt_sandbox_gid = -1; static int opt_sync_fd = -1; @@ -367,6 +368,7 @@ usage (int ecode, FILE *out) " --perms OCTAL Set permissions of next argument (--bind-data, --file, etc.)\n" " --size BYTES Set size of next argument (only for --tmpfs)\n" " --chmod OCTAL PATH Change permissions of PATH (must already exist)\n" + " --forward-signals Forward SIGNALs to the child process.\n" ); exit (ecode); } @@ -381,6 +383,32 @@ handle_die_with_parent (void) die_with_error ("prctl"); } +static int forwarded_signals[] = +{ + SIGINT, + SIGTERM, + SIGCONT, + SIGHUP, + SIGQUIT, + SIGUSR1, + SIGUSR2, + SIGWINCH, +}; + +static void +block_forwarded_signals (sigset_t *prevmask) +{ + sigset_t mask; + size_t i; + + sigemptyset (&mask); + + for (i = 0; i < N_ELEMENTS (forwarded_signals); i++) + { + sigaddset (&mask, forwarded_signals[i]); + } +} + static void block_sigchild (void) { @@ -502,6 +530,7 @@ monitor_child (int event_fd, pid_t child_pid, int setup_finished_fd) int exitc; pid_t died_pid; int died_status; + size_t i; /* Close all extra fds in the monitoring process. Any passed in fds have been passed on to the child anyway. */ @@ -517,6 +546,11 @@ monitor_child (int event_fd, pid_t child_pid, int setup_finished_fd) sigemptyset (&mask); sigaddset (&mask, SIGCHLD); + for (i = 0; i < N_ELEMENTS(forwarded_signals); i++) + { + sigaddset(&mask, forwarded_signals[i]); + } + signal_fd = signalfd (-1, &mask, SFD_CLOEXEC | SFD_NONBLOCK); if (signal_fd == -1) die_with_error ("Can't create signalfd"); @@ -555,12 +589,17 @@ monitor_child (int event_fd, pid_t child_pid, int setup_finished_fd) } /* We need to read the signal_fd, or it will keep polling as read, - * however we ignore the details as we get them from waitpid + * however we ignore the details for SIGCHLD as we get them from waitpid * below anyway */ s = read (signal_fd, &fdsi, sizeof (struct signalfd_siginfo)); if (s == -1 && errno != EINTR && errno != EAGAIN) die_with_error ("read signalfd"); + /* Propagate signal to child so that it will take the correct + * action. This avoids the parent terminating, leaving an orphan. */ + if (fdsi.ssi_signo != SIGCHLD && kill (child_pid, fdsi.ssi_signo)) + die_with_error ("kill child"); + /* We may actually get several sigchld compressed into one SIGCHLD, so we have to handle all of them. */ while ((died_pid = waitpid (-1, &died_status, WNOHANG)) > 0) @@ -2527,6 +2566,10 @@ parse_args_recurse (int *argcp, argc -= 1; break; } + else if (strcmp (arg, "--forward-signals") == 0) + { + opt_forward_signals = TRUE; + } else if (*arg == '-') { die ("Unknown option %s", arg); @@ -2666,6 +2709,8 @@ main (int argc, cleanup_free char *args_data UNUSED = NULL; int intermediate_pids_sockets[2] = {-1, -1}; const char *exec_path = NULL; + sigset_t sigmask_before_forwarding; + sigemptyset (&sigmask_before_forwarding); /* Handle --version early on before we try to acquire/drop * any capabilities so it works in a build environment; @@ -2839,6 +2884,10 @@ main (int argc, /* We block sigchild here so that we can use signalfd in the monitor. */ block_sigchild (); + /* We block other signals here to avoid leaving an orphan. */ + if (opt_forward_signals) + block_forwarded_signals (&sigmask_before_forwarding); + clone_flags = SIGCHLD | CLONE_NEWNS; if (opt_unshare_user) clone_flags |= CLONE_NEWUSER; @@ -2989,6 +3038,13 @@ main (int argc, return monitor_child (event_fd, pid, setup_finished_pipe[0]); } + /* Restore the state of sigmask from before the blocking. */ + if (opt_forward_signals) + { + if (sigprocmask (SIG_SETMASK, &sigmask_before_forwarding, NULL) != 0) + die_with_error ("sigprocmask"); + } + if (opt_pidns_fd > 0) { if (setns (opt_pidns_fd, CLONE_NEWPID) != 0)