diff --git a/bubblewrap.c b/bubblewrap.c index 027f8c9..8cdc10d 100644 --- a/bubblewrap.c +++ b/bubblewrap.c @@ -86,6 +86,8 @@ 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; #define CAP_TO_MASK_0(x) (1L << ((x) & 31)) #define CAP_TO_MASK_1(x) CAP_TO_MASK_0(x - 32) @@ -230,6 +232,8 @@ 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" + " --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" " --uid UID Custom uid in the sandbox (requires --unshare-user)\n" " --gid GID Custom gid in the sandbox (requires --unshare-user)\n" " --hostname NAME Custom hostname in the sandbox (requires --unshare-uts)\n" @@ -1886,6 +1890,40 @@ parse_args_recurse (int *argcp, opt_seccomp_fd = the_fd; + 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; } @@ -2207,14 +2245,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 @@ -2342,6 +2401,14 @@ 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"); + } + pid = raw_clone (clone_flags, NULL); if (pid == -1) { @@ -2381,7 +2448,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); @@ -2609,6 +2679,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) diff --git a/bwrap.xml b/bwrap.xml index 73ca161..b1a2b2e 100644 --- a/bwrap.xml +++ b/bwrap.xml @@ -130,6 +130,16 @@ Unshare all possible namespaces. Currently equivalent with: + + + Use an existing user namespace instead of creating a new one. The namespace must fulfil the permission requirements for setns(), which generally means that it must be a decendant of the currently active user namespace, owned by the same user. + This is incompatible with --unshare-user, and doesn't work in the setuid version of bubblewrap. + + + + After setting up the new namespace, switch into the specified namespace. For this to work the specified namespace must be a decendant of the user namespace used for the setup, so this is only useful in combination with --userns. + This is useful because sometimes bubblewrap itself creates nested user namespaces (to work around some kernel issues) and --userns2 can be used to enter these. + Use a custom user id in the sandbox (requires )