summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlexander Larsson <alexl@redhat.com>2019-11-21 15:30:03 +0100
committerAlexander Larsson <alexl@redhat.com>2019-11-22 11:11:32 +0100
commit75c2d94de8a6a3f13619aecf3d5a2a5276942a88 (patch)
tree3d264bcc207bd96da752db87a16cd3ac4dcb6911
parent23d3b639242efa1f4626eb07aa04698ba2e0354e (diff)
downloadbubblewrap-75c2d94de8a6a3f13619aecf3d5a2a5276942a88.tar.gz
Add support for --userns and --userns2
This allows you to reuse an existing user namespace to set up all the other namespaces, entering that instead of creating a new one. The reason you want to do this is that you can then also reuse other namespaces that are owned by the user namespace. Typically you use this to partially re-enter a previoulsy created bubblewrap sandbox. This also adds --userns2 which is similar to --userns, but this is switched into at the end instead of the start. Bubblewrap sometimes creates nested such user namespaces[1], and to be able to reuse such a setup we need to similarly reuse both namespaces via --userns2. 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 useable for bwrap, because (as described in a comment in acquire_privs()) setuid mode causes root to own the namespaces that it creates. So as you will not be able to access these namespaces for reuse anyway, its best to disable it (in case of unexpected security issues). [1] This is to work around an issue with mounting devpts without uid 0 mapped in the user namespace, where the outer namespace owns all the other namespaces but the inner one has the right mappings.
-rw-r--r--bubblewrap.c79
-rw-r--r--bwrap.xml10
2 files changed, 86 insertions, 3 deletions
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"
@@ -1889,6 +1893,40 @@ 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, "--setenv") == 0)
{
if (argc < 3)
@@ -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
@@ -131,6 +131,16 @@
<listitem><para>Unshare all possible namespaces. Currently equivalent with: <option>--unshare-user-try</option> <option>--unshare-ipc</option> <option>--unshare-pid</option> <option>--unshare-net</option> <option>--unshare-uts</option> <option>--unshare-cgroup-try</option></para></listitem>
</varlistentry>
<varlistentry>
+ <term><option>--userns <arg choice="plain">FD</arg></option></term>
+ <listitem><para>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. </para>
+ <para>This is incompatible with --unshare-user, and doesn't work in the setuid version of bubblewrap.</para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><option>--userns2 <arg choice="plain">FD</arg></option></term>
+ <listitem><para>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.</para>
+ <para>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.</para></listitem>
+ </varlistentry>
+ <varlistentry>
<term><option>--uid <arg choice="plain">UID</arg></option></term>
<listitem><para>Use a custom user id in the sandbox (requires <option>--unshare-user</option>)</para></listitem>
</varlistentry>