diff options
Diffstat (limited to 'bubblewrap.c')
-rw-r--r-- | bubblewrap.c | 173 |
1 files changed, 160 insertions, 13 deletions
diff --git a/bubblewrap.c b/bubblewrap.c index 1ec9d2b..8d0c5f7 100644 --- a/bubblewrap.c +++ b/bubblewrap.c @@ -86,6 +86,9 @@ int opt_json_status_fd = -1; int opt_seccomp_fd = -1; const char *opt_sandbox_hostname = NULL; char *opt_args_data = NULL; /* owned */ +int opt_userns_fd = -1; +int opt_userns2_fd = -1; +int opt_pidns_fd = -1; #define CAP_TO_MASK_0(x) (1L << ((x) & 31)) #define CAP_TO_MASK_1(x) CAP_TO_MASK_0(x - 32) @@ -230,8 +233,11 @@ usage (int ecode, FILE *out) " --unshare-uts Create new uts namespace\n" " --unshare-cgroup Create new cgroup namespace\n" " --unshare-cgroup-try Create new cgroup namespace if possible else continue by skipping it\n" - " --uid UID Custom uid in the sandbox (requires --unshare-user)\n" - " --gid GID Custom gid in the sandbox (requires --unshare-user)\n" + " --userns FD Use this user namespace (cannot combine with --unshare-user)\n" + " --userns2 FD After setup switch to this user namspace, only useful with --userns\n" + " --pidns FD Use this user namespace (as parent namespace if using --unshare-pid)\n" + " --uid UID Custom uid in the sandbox (requires --unshare-user or --userns)\n" + " --gid GID Custom gid in the sandbox (requires --unshare-user or --userns)\n" " --hostname NAME Custom hostname in the sandbox (requires --unshare-uts)\n" " --chdir DIR Change directory to DIR\n" " --setenv VAR VALUE Set an environment variable\n" @@ -799,9 +805,19 @@ static void switch_to_user_with_privs (void) { /* If we're in a new user namespace, we got back the bounding set, clear it again */ - if (opt_unshare_user) + if (opt_unshare_user || opt_userns_fd != -1) drop_cap_bounding_set (FALSE); + /* If we switched to a new user namespace it may allow other uids/gids, so switch to the target one */ + if (opt_userns_fd != -1) + { + if (opt_sandbox_uid != real_uid && setuid (opt_sandbox_uid) < 0) + die_with_error ("unable to switch to uid %d", opt_sandbox_uid); + + if (opt_sandbox_gid != real_gid && setgid (opt_sandbox_gid) < 0) + die_with_error ("unable to switch to gid %d", opt_sandbox_gid); + } + if (!is_privileged) return; @@ -822,10 +838,14 @@ drop_privs (bool keep_requested_caps) { assert (!keep_requested_caps || !is_privileged); /* Drop root uid */ - if (getuid () == 0 && setuid (opt_sandbox_uid) < 0) + if (geteuid () == 0 && setuid (opt_sandbox_uid) < 0) die_with_error ("unable to drop root uid"); drop_all_caps (keep_requested_caps); + + /* We don't have any privs now, so mark us dumpable which makes /proc/self be owned by the user instead of root */ + if (prctl (PR_SET_DUMPABLE, 1, 0, 0, 0) != 0) + die_with_error ("can't set dumpable"); } static char * @@ -1097,7 +1117,7 @@ setup_newroot (bool unshare_pid, if (ensure_dir (dest, 0755) != 0) die_with_error ("Can't mkdir %s", op->dest); - if (unshare_pid) + if (unshare_pid || opt_pidns_fd != -1) { /* Our own procfs */ privileged_op (privileged_op_socket, @@ -1885,6 +1905,57 @@ parse_args_recurse (int *argcp, argv += 1; argc -= 1; } + else if (strcmp (arg, "--userns") == 0) + { + int the_fd; + char *endptr; + + if (argc < 2) + die ("--userns takes an argument"); + + the_fd = strtol (argv[1], &endptr, 10); + if (argv[1][0] == 0 || endptr[0] != 0 || the_fd < 0) + die ("Invalid fd: %s", argv[1]); + + opt_userns_fd = the_fd; + + argv += 1; + argc -= 1; + } + else if (strcmp (arg, "--userns2") == 0) + { + int the_fd; + char *endptr; + + if (argc < 2) + die ("--userns2 takes an argument"); + + the_fd = strtol (argv[1], &endptr, 10); + if (argv[1][0] == 0 || endptr[0] != 0 || the_fd < 0) + die ("Invalid fd: %s", argv[1]); + + opt_userns2_fd = the_fd; + + argv += 1; + argc -= 1; + } + else if (strcmp (arg, "--pidns") == 0) + { + int the_fd; + char *endptr; + + if (argc < 2) + die ("--pidns takes an argument"); + + the_fd = strtol (argv[1], &endptr, 10); + if (argv[1][0] == 0 || endptr[0] != 0 || the_fd < 0) + die ("Invalid fd: %s", argv[1]); + + opt_pidns_fd = the_fd; + + argv += 1; + argc -= 1; + } else if (strcmp (arg, "--setenv") == 0) { if (argc < 3) @@ -2153,6 +2224,7 @@ main (int argc, size_t seccomp_len; struct sock_fprog seccomp_prog; cleanup_free char *args_data = NULL; + int intermediate_pids_sockets[2] = {-1, -1}; /* Handle --version early on before we try to acquire/drop * any capabilities so it works in a build environment; @@ -2203,14 +2275,35 @@ main (int argc, if (opt_userns_block_fd != -1 && opt_info_fd == -1) die ("--userns-block-fd requires --info-fd"); + if (opt_userns_fd != -1 && opt_unshare_user) + die ("--userns not compatible --unshare-user"); + + if (opt_userns_fd != -1 && opt_unshare_user_try) + die ("--userns not compatible --unshare-user-try"); + + /* Technically using setns() is probably safe even in the privileged + * case, because we got passed in a file descriptor to the + * namespace, and that can only be gotten if you have ptrace + * permissions against the target, and then you could do whatever to + * the namespace anyway. + * + * However, for practical reasons this isn't possible to use, + * because (as described in acquire_privs()) setuid bwrap causes + * root to own the namespaces that it creates, so you will not be + * able to access these namespaces anyway. So, best just not support + * it anway. + */ + if (opt_userns_fd != -1 && is_privileged) + die ("--userns doesn't work in setuid mode"); + /* We have to do this if we weren't installed setuid (and we're not * root), so let's just DWIM */ - if (!is_privileged && getuid () != 0) + if (!is_privileged && getuid () != 0 && opt_userns_fd == -1) opt_unshare_user = TRUE; #ifdef ENABLE_REQUIRE_USERNS /* In this build option, we require userns. */ - if (is_privileged && getuid () != 0) + if (is_privileged && getuid () != 0 && opt_userns_fd == -1) opt_unshare_user = TRUE; #endif @@ -2255,11 +2348,11 @@ main (int argc, if (opt_sandbox_gid == -1) opt_sandbox_gid = real_gid; - if (!opt_unshare_user && opt_sandbox_uid != real_uid) - die ("Specifying --uid requires --unshare-user"); + if (!opt_unshare_user && opt_userns_fd == -1 && opt_sandbox_uid != real_uid) + die ("Specifying --uid requires --unshare-user or --userns"); - if (!opt_unshare_user && opt_sandbox_gid != real_gid) - die ("Specifying --gid requires --unshare-user"); + if (!opt_unshare_user && opt_userns_fd == -1 && opt_sandbox_gid != real_gid) + die ("Specifying --gid requires --unshare-user or --userns"); if (!opt_unshare_uts && opt_sandbox_hostname != NULL) die ("Specifying --hostname requires --unshare-uts"); @@ -2299,7 +2392,7 @@ main (int argc, clone_flags = SIGCHLD | CLONE_NEWNS; if (opt_unshare_user) clone_flags |= CLONE_NEWUSER; - if (opt_unshare_pid) + if (opt_unshare_pid && opt_pidns_fd == -1) clone_flags |= CLONE_NEWPID; if (opt_unshare_net) clone_flags |= CLONE_NEWNET; @@ -2338,6 +2431,22 @@ main (int argc, die_with_error ("pipe2()"); } + /* Switch to the custom user ns before the clone, gets us privs in that ns (assuming its a child of the current and thus allowed) */ + if (opt_userns_fd > 0 && setns (opt_userns_fd, CLONE_NEWUSER) != 0) + { + if (errno == EINVAL) + die ("Joining the specified user namespace failed, it might not be a descendant of the current user namespace."); + die_with_error ("Joining specified user namespace failed"); + } + + /* Sometimes we have uninteresting intermediate pids during the setup, set up code to pass the real pid down */ + if (opt_pidns_fd != -1) + { + /* Mark us as a subreaper, this way we can get exit status from grandchildren */ + prctl (PR_SET_CHILD_SUBREAPER, 1, 0, 0, 0); + create_pid_socketpair (intermediate_pids_sockets); + } + pid = raw_clone (clone_flags, NULL); if (pid == -1) { @@ -2359,6 +2468,13 @@ main (int argc, { /* Parent, outside sandbox, privileged (initially) */ + if (intermediate_pids_sockets[0] != -1) + { + close (intermediate_pids_sockets[1]); + pid = read_pid_from_socket (intermediate_pids_sockets[0]); + close (intermediate_pids_sockets[0]); + } + /* Discover namespace ids before we drop privileges */ namespace_ids_read (pid); @@ -2377,7 +2493,10 @@ main (int argc, pid, TRUE, opt_needs_devpts); } - /* Initial launched process, wait for exec:ed command to exit */ + /* Initial launched process, wait for pid 1 or exec:ed command to exit */ + + if (opt_userns2_fd > 0 && setns (opt_userns2_fd, CLONE_NEWUSER) != 0) + die_with_error ("Setting userns2 failed"); /* We don't need any privileges in the launcher, drop them immediately. */ drop_privs (FALSE); @@ -2417,6 +2536,31 @@ main (int argc, return monitor_child (event_fd, pid, setup_finished_pipe[0]); } + if (opt_pidns_fd > 0) + { + if (setns (opt_pidns_fd, CLONE_NEWPID) != 0) + die_with_error ("Setting pidns failed"); + + /* fork to get the passed in pid ns */ + fork_intermediate_child (); + + /* We might both have specified an --pidns *and* --unshare-pid, so set up a new child pid namespace under the specified one */ + if (opt_unshare_pid) + { + if (unshare (CLONE_NEWPID)) + die_with_error ("unshare pid ns"); + + /* fork to get the new pid ns */ + fork_intermediate_child (); + } + + /* We're back, either in a child or grandchild, so message the actual pid to the monitor */ + + close (intermediate_pids_sockets[0]); + send_pid_on_socket (intermediate_pids_sockets[1]); + close (intermediate_pids_sockets[1]); + } + /* Child, in sandbox, privileged in the parent or in the user namespace (if --unshare-user). * * Note that for user namespaces we run as euid 0 during clone(), so @@ -2605,6 +2749,9 @@ main (int argc, die_with_error ("chdir /"); } + if (opt_userns2_fd > 0 && setns (opt_userns2_fd, CLONE_NEWUSER) != 0) + die_with_error ("Setting userns2 failed"); + if (opt_unshare_user && (ns_uid != opt_sandbox_uid || ns_gid != opt_sandbox_gid) && opt_userns_block_fd == -1) |