Skip to content

Commit b32aa1d

Browse files
aaruni96earlchewq3cpma
committed
Add option to propagate common signals to sandboxed process
This fixes, for example, pressing CTRL+C to send a SIGINT during interactive use and also allows for things like passing SIGWINCH to neovim running inside the bubblewrap to resize the window. Extends [bubblewrap] Propagate SIGTERM and SIGINT to child #402 Borrows test from q3cpma Co-authored-by: Earl Chew <earl_chew@yahoo.com> Co-authored-by: q3cpma <q3cpma@posteo.net> Signed-off-by: Aaruni Kaushik <akaushik@mathematik.uni-kl.de>
1 parent c1bfc72 commit b32aa1d

3 files changed

Lines changed: 105 additions & 1 deletion

File tree

bubblewrap.c

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ static bool opt_unshare_cgroup_try = false;
7878
static bool opt_needs_devpts = false;
7979
static bool opt_new_session = false;
8080
static bool opt_die_with_parent = false;
81+
static bool opt_forward_signals = false;
8182
static uid_t opt_sandbox_uid = -1;
8283
static gid_t opt_sandbox_gid = -1;
8384
static int opt_sync_fd = -1;
@@ -373,6 +374,7 @@ usage (int ecode, FILE *out)
373374
" --perms OCTAL Set permissions of next argument (--bind-data, --file, etc.)\n"
374375
" --size BYTES Set size of next argument (only for --tmpfs)\n"
375376
" --chmod OCTAL PATH Change permissions of PATH (must already exist)\n"
377+
" --forward-signals Forward various commonly used signals to the child process.\n"
376378
);
377379
exit (ecode);
378380
}
@@ -387,6 +389,33 @@ handle_die_with_parent (void)
387389
die_with_error ("prctl");
388390
}
389391

392+
static int forwarded_signals[] =
393+
{
394+
SIGINT,
395+
SIGTERM,
396+
SIGCONT,
397+
SIGHUP,
398+
SIGQUIT,
399+
SIGUSR1,
400+
SIGUSR2,
401+
SIGWINCH,
402+
};
403+
404+
static void
405+
block_forwarded_signals (sigset_t *prevmask)
406+
{
407+
sigset_t mask;
408+
size_t i;
409+
410+
sigemptyset (&mask);
411+
412+
for (i = 0; i < N_ELEMENTS (forwarded_signals); i++)
413+
sigaddset (&mask, forwarded_signals[i]);
414+
415+
if (sigprocmask (SIG_BLOCK, &mask, prevmask) == -1)
416+
die_with_error ("sigprocmask");
417+
}
418+
390419
static void
391420
block_sigchild (void)
392421
{
@@ -508,6 +537,7 @@ monitor_child (int event_fd, pid_t child_pid, int setup_finished_fd)
508537
int exitc;
509538
pid_t died_pid;
510539
int died_status;
540+
size_t i;
511541

512542
/* Close all extra fds in the monitoring process.
513543
Any passed in fds have been passed on to the child anyway. */
@@ -523,6 +553,9 @@ monitor_child (int event_fd, pid_t child_pid, int setup_finished_fd)
523553
sigemptyset (&mask);
524554
sigaddset (&mask, SIGCHLD);
525555

556+
for (i = 0; i < N_ELEMENTS (forwarded_signals); i++)
557+
sigaddset (&mask, forwarded_signals[i]);
558+
526559
signal_fd = signalfd (-1, &mask, SFD_CLOEXEC | SFD_NONBLOCK);
527560
if (signal_fd == -1)
528561
die_with_error ("Can't create signalfd");
@@ -561,12 +594,21 @@ monitor_child (int event_fd, pid_t child_pid, int setup_finished_fd)
561594
}
562595

563596
/* We need to read the signal_fd, or it will keep polling as read,
564-
* however we ignore the details as we get them from waitpid
597+
* however we ignore the details for SIGCHLD as we get them from waitpid
565598
* below anyway */
566599
s = read (signal_fd, &fdsi, sizeof (struct signalfd_siginfo));
567600
if (s == -1 && errno != EINTR && errno != EAGAIN)
568601
die_with_error ("read signalfd");
569602

603+
/* Propagate signal to child so that it will take the correct
604+
* action. This avoids the parent terminating, leaving an orphan. */
605+
if (fdsi.ssi_signo != SIGCHLD)
606+
{
607+
s = kill (child_pid, fdsi.ssi_signo);
608+
if (s != 0 && s != ESRCH)
609+
die_with_error("kill child");
610+
}
611+
570612
/* We may actually get several sigchld compressed into one
571613
SIGCHLD, so we have to handle all of them. */
572614
while ((died_pid = waitpid (-1, &died_status, WNOHANG)) > 0)
@@ -2742,6 +2784,10 @@ parse_args_recurse (int *argcp,
27422784
argc -= 1;
27432785
break;
27442786
}
2787+
else if (strcmp (arg, "--forward-signals") == 0)
2788+
{
2789+
opt_forward_signals = true;
2790+
}
27452791
else if (*arg == '-')
27462792
{
27472793
die ("Unknown option %s", arg);
@@ -2889,6 +2935,8 @@ main (int argc,
28892935
int intermediate_pids_sockets[2] = {-1, -1};
28902936
const char *exec_path = NULL;
28912937
int i;
2938+
sigset_t sigmask_before_forwarding;
2939+
sigemptyset (&sigmask_before_forwarding);
28922940

28932941
/* Handle --version early on before we try to acquire/drop
28942942
* any capabilities so it works in a build environment;
@@ -3062,6 +3110,10 @@ main (int argc,
30623110
/* We block sigchild here so that we can use signalfd in the monitor. */
30633111
block_sigchild ();
30643112

3113+
/* We block other signals here to avoid leaving an orphan. */
3114+
if (opt_forward_signals)
3115+
block_forwarded_signals (&sigmask_before_forwarding);
3116+
30653117
clone_flags = SIGCHLD | CLONE_NEWNS;
30663118
if (opt_unshare_user)
30673119
clone_flags |= CLONE_NEWUSER;
@@ -3212,6 +3264,13 @@ main (int argc,
32123264
return monitor_child (event_fd, pid, setup_finished_pipe[0]);
32133265
}
32143266

3267+
/* Restore the state of sigmask from before the blocking. */
3268+
if (opt_forward_signals)
3269+
{
3270+
if (sigprocmask (SIG_SETMASK, &sigmask_before_forwarding, NULL) != 0)
3271+
die_with_error ("sigprocmask");
3272+
}
3273+
32153274
if (opt_pidns_fd > 0)
32163275
{
32173276
if (setns (opt_pidns_fd, CLONE_NEWPID) != 0)

tests/meson.build

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ test_scripts = [
2020
'test-seccomp.py',
2121
'test-specifying-pidns.sh',
2222
'test-specifying-userns.sh',
23+
'test-forward-signals.sh'
2324
]
2425

2526
test_env = environment()

tests/test-forward-signals.sh

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
#!/usr/bin/env bash
2+
3+
set -xeuo pipefail
4+
5+
srcd=$(cd $(dirname "$0") && pwd)
6+
. ${srcd}/libtest.sh
7+
test_count=0
8+
ok () {
9+
test_count=$((test_count + 1))
10+
echo ok $test_count "$@"
11+
}
12+
ok_skip () {
13+
ok "# SKIP" "$@"
14+
}
15+
done_testing () {
16+
echo "1..$test_count"
17+
}
18+
19+
sh_path=/bin/sh
20+
21+
out=$(
22+
$RUN \
23+
"$sh_path" -c 'trap "echo USR1; exit" USR1; sleep 0.5' &
24+
sleep 0.1
25+
kill -USR1 $!
26+
)
27+
if [ -z "$out" ]; then
28+
ok "No signals forwarded without --forward-signal"
29+
else
30+
false
31+
fi
32+
33+
out=$(
34+
$RUN --forward-signals \
35+
"$sh_path" -c 'trap "echo USR1; exit" USR1; sleep 0.5' &
36+
sleep 0.1
37+
kill -USR1 $!
38+
)
39+
40+
if [ "$out" = USR1 ]; then
41+
ok "Successfully forwarded signals with --forward-signals"
42+
else
43+
false
44+
fi

0 commit comments

Comments
 (0)