Hello community, here is the log from the commit of package bubblewrap for openSUSE:Factory checked in at 2019-12-28 13:40:14 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/bubblewrap (Old) and /work/SRC/openSUSE:Factory/.bubblewrap.new.6675 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "bubblewrap" Sat Dec 28 13:40:14 2019 rev:10 rq:759115 version:0.4.0 Changes: -------- --- /work/SRC/openSUSE:Factory/bubblewrap/bubblewrap.changes 2019-07-08 15:04:48.662878022 +0200 +++ /work/SRC/openSUSE:Factory/.bubblewrap.new.6675/bubblewrap.changes 2019-12-28 13:40:18.562926637 +0100 @@ -1,0 +2,12 @@ +Fri Dec 20 22:59:52 UTC 2019 - Bjørn Lie <[email protected]> + +- Update to version 0.4.0: + + The biggest feature in this release is the support for joining + existing user and pid namespaces. This doesn't work in the + setuid mode (at the moment). + + Other changes: + - Stores namespace info in status json. + - In setuid mode pid 1 is now marked dumpable. + - Now builds with musl libc. + +------------------------------------------------------------------- Old: ---- bubblewrap-0.3.3.tar.xz New: ---- bubblewrap-0.4.0.tar.xz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ bubblewrap.spec ++++++ --- /var/tmp/diff_new_pack.Zz5AcS/_old 2019-12-28 13:40:19.898927311 +0100 +++ /var/tmp/diff_new_pack.Zz5AcS/_new 2019-12-28 13:40:19.934927329 +0100 @@ -1,7 +1,7 @@ # # spec file for package bubblewrap # -# Copyright (c) 2019 SUSE LINUX GmbH, Nuernberg, Germany. +# Copyright (c) 2019 SUSE LLC # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -17,13 +17,13 @@ Name: bubblewrap -Version: 0.3.3 +Version: 0.4.0 Release: 0 Summary: Core execution tool for unprivileged containers License: LGPL-2.0-or-later Group: Productivity/Security -Url: https://github.com/projectatomic/bubblewrap -Source0: https://github.com/projectatomic/bubblewrap/releases/download/v%{version}/%{name}-%{version}.tar.xz +URL: https://github.com/projectatomic/bubblewrap +Source0: %{url}/releases/download/v%{version}/%{name}-%{version}.tar.xz BuildRequires: autoconf BuildRequires: automake BuildRequires: docbook-xsl-stylesheets @@ -41,7 +41,7 @@ user namespaces. %prep -%autosetup -n %{name}-%{version} +%autosetup -p1 -n %{name}-%{version} sed -i '1d' completions/bash/bwrap %if 0%{?suse_version} < 1500 sed -i '1s,/usr/bin/env bash,/bin/bash,' demos/bubblewrap-shell.sh ++++++ bubblewrap-0.3.3.tar.xz -> bubblewrap-0.4.0.tar.xz ++++++ ++++ 15274 lines of diff (skipped) ++++ retrying with extended exclude list diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 --exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh old/bubblewrap-0.3.3/bubblewrap.c new/bubblewrap-0.4.0/bubblewrap.c --- old/bubblewrap-0.3.3/bubblewrap.c 2019-05-01 10:44:10.000000000 +0200 +++ new/bubblewrap-0.4.0/bubblewrap.c 2019-11-27 13:34:31.000000000 +0100 @@ -42,6 +42,15 @@ #define CLONE_NEWCGROUP 0x02000000 /* New cgroup namespace */ #endif +#ifndef TEMP_FAILURE_RETRY +#define TEMP_FAILURE_RETRY(expression) \ + (__extension__ \ + ({ long int __result; \ + do __result = (long int) (expression); \ + while (__result == -1L && errno == EINTR); \ + __result; })) +#endif + /* Globals to avoid having to use getuid(), since the uid/gid changes during runtime */ static uid_t real_uid; static gid_t real_gid; @@ -77,10 +86,34 @@ 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) +typedef struct _NsInfo NsInfo; + +struct _NsInfo { + const char *name; + bool *do_unshare; + ino_t id; +}; + +static NsInfo ns_infos[] = { + {"cgroup", &opt_unshare_cgroup, 0}, + {"ipc", &opt_unshare_ipc, 0}, + {"mnt", NULL, 0}, + {"net", &opt_unshare_net, 0}, + {"pid", &opt_unshare_pid, 0}, + /* user namespace info omitted because it + * is not (yet) valid when we obtain the + * namespace info (get un-shared later) */ + {"uts", &opt_unshare_uts, 0}, + {NULL, NULL, 0} +}; + typedef enum { SETUP_BIND_MOUNT, SETUP_RO_BIND_MOUNT, @@ -200,8 +233,11 @@ " --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" @@ -539,7 +575,8 @@ static uint32_t requested_caps[2] = {0, 0}; /* low 32bit caps needed */ -#define REQUIRED_CAPS_0 (CAP_TO_MASK_0 (CAP_SYS_ADMIN) | CAP_TO_MASK_0 (CAP_SYS_CHROOT) | CAP_TO_MASK_0 (CAP_NET_ADMIN) | CAP_TO_MASK_0 (CAP_SETUID) | CAP_TO_MASK_0 (CAP_SETGID)) +/* CAP_SYS_PTRACE is needed to dereference the symlinks in /proc/<pid>/ns/, see namespaces(7) */ +#define REQUIRED_CAPS_0 (CAP_TO_MASK_0 (CAP_SYS_ADMIN) | CAP_TO_MASK_0 (CAP_SYS_CHROOT) | CAP_TO_MASK_0 (CAP_NET_ADMIN) | CAP_TO_MASK_0 (CAP_SETUID) | CAP_TO_MASK_0 (CAP_SETGID) | CAP_TO_MASK_0 (CAP_SYS_PTRACE)) /* high 32bit caps needed */ #define REQUIRED_CAPS_1 0 @@ -768,9 +805,19 @@ 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; @@ -791,10 +838,14 @@ { 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 * @@ -1066,7 +1117,7 @@ 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, @@ -1854,6 +1905,57 @@ 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) @@ -2041,6 +2143,65 @@ die ("Can't parse /proc/sys/kernel/overflowgid"); } +static void +namespace_ids_read (pid_t pid) +{ + cleanup_free char *dir = NULL; + cleanup_fd int ns_fd = -1; + NsInfo *info; + + dir = xasprintf ("%d/ns", pid); + ns_fd = openat (proc_fd, dir, O_PATH); + + if (ns_fd < 0) + die_with_error ("open /proc/%s/ns failed", dir); + + for (info = ns_infos; info->name; info++) + { + bool *do_unshare = info->do_unshare; + struct stat st; + int r; + + /* if we don't unshare this ns, ignore it */ + if (do_unshare && *do_unshare == FALSE) + continue; + + r = fstatat (ns_fd, info->name, &st, 0); + + /* if we can't get the information, ignore it */ + if (r != 0) + continue; + + info->id = st.st_ino; + } +} + +static void +namespace_ids_write (int fd, + bool in_json) +{ + NsInfo *info; + + for (info = ns_infos; info->name; info++) + { + cleanup_free char *output = NULL; + const char *indent; + uintmax_t nsid; + + nsid = (uintmax_t) info->id; + + /* if we don't have the information, we don't write it */ + if (nsid == 0) + continue; + + indent = in_json ? " " : "\n "; + output = xasprintf (",%s\"%s-namespace\": %ju", + indent, info->name, nsid); + + dump_info (fd, output, TRUE); + } +} + int main (int argc, char **argv) @@ -2063,6 +2224,7 @@ 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; @@ -2113,14 +2275,35 @@ 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 @@ -2165,11 +2348,11 @@ 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"); @@ -2209,7 +2392,7 @@ 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; @@ -2229,8 +2412,11 @@ clone_flags |= CLONE_NEWCGROUP; } if (opt_unshare_cgroup_try) - if (!stat ("/proc/self/ns/cgroup", &sbuf)) - clone_flags |= CLONE_NEWCGROUP; + { + opt_unshare_cgroup = !stat ("/proc/self/ns/cgroup", &sbuf); + if (opt_unshare_cgroup) + clone_flags |= CLONE_NEWCGROUP; + } child_wait_fd = eventfd (0, EFD_CLOEXEC); if (child_wait_fd == -1) @@ -2245,6 +2431,22 @@ 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) { @@ -2266,6 +2468,16 @@ { /* 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); + if (is_privileged && opt_unshare_user && opt_userns_block_fd == -1) { /* We're running as euid 0, but the uid we want to map is @@ -2281,7 +2493,10 @@ 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); @@ -2291,14 +2506,18 @@ if (opt_info_fd != -1) { - cleanup_free char *output = xasprintf ("{\n \"child-pid\": %i\n}\n", pid); + cleanup_free char *output = xasprintf ("{\n \"child-pid\": %i", pid); dump_info (opt_info_fd, output, TRUE); + namespace_ids_write (opt_info_fd, FALSE); + dump_info (opt_info_fd, "\n}\n", TRUE); close (opt_info_fd); } if (opt_json_status_fd != -1) { - cleanup_free char *output = xasprintf ("{ \"child-pid\": %i }\n", pid); + cleanup_free char *output = xasprintf ("{ \"child-pid\": %i", pid); dump_info (opt_json_status_fd, output, TRUE); + namespace_ids_write (opt_json_status_fd, TRUE); + dump_info (opt_json_status_fd, " }\n", TRUE); } if (opt_userns_block_fd != -1) @@ -2317,6 +2536,31 @@ 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 @@ -2505,6 +2749,9 @@ 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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 --exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh old/bubblewrap-0.3.3/build-aux/compile new/bubblewrap-0.4.0/build-aux/compile --- old/bubblewrap-0.3.3/build-aux/compile 2019-05-01 10:49:13.000000000 +0200 +++ new/bubblewrap-0.4.0/build-aux/compile 2019-11-27 09:47:09.000000000 +0100 @@ -1,4 +1,4 @@ -#!/usr/bin/sh +#! /bin/sh # Wrapper for compilers which do not understand '-c -o'. scriptversion=2018-03-07.03; # UTC diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 --exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh old/bubblewrap-0.3.3/build-aux/missing new/bubblewrap-0.4.0/build-aux/missing --- old/bubblewrap-0.3.3/build-aux/missing 2019-05-01 10:49:13.000000000 +0200 +++ new/bubblewrap-0.4.0/build-aux/missing 2019-11-27 09:47:09.000000000 +0100 @@ -1,4 +1,4 @@ -#!/usr/bin/sh +#! /bin/sh # Common wrapper for a few potentially missing GNU programs. scriptversion=2018-03-07.03; # UTC diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 --exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh old/bubblewrap-0.3.3/build-aux/tap-driver.sh new/bubblewrap-0.4.0/build-aux/tap-driver.sh --- old/bubblewrap-0.3.3/build-aux/tap-driver.sh 2019-05-01 10:49:14.000000000 +0200 +++ new/bubblewrap-0.4.0/build-aux/tap-driver.sh 2019-11-27 09:47:09.000000000 +0100 @@ -1,4 +1,4 @@ -#!/usr/bin/sh +#! /bin/sh # Copyright (C) 2011-2018 Free Software Foundation, Inc. # # This program is free software; you can redistribute it and/or modify diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 --exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh old/bubblewrap-0.3.3/build-aux/test-driver new/bubblewrap-0.4.0/build-aux/test-driver --- old/bubblewrap-0.3.3/build-aux/test-driver 2019-05-01 10:49:14.000000000 +0200 +++ new/bubblewrap-0.4.0/build-aux/test-driver 2019-11-27 09:47:09.000000000 +0100 @@ -1,4 +1,4 @@ -#!/usr/bin/sh +#! /bin/sh # test-driver - basic testsuite driver script. scriptversion=2018-03-07.03; # UTC diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 --exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh old/bubblewrap-0.3.3/bwrap.xml new/bubblewrap-0.4.0/bwrap.xml --- old/bubblewrap-0.3.3/bwrap.xml 2019-03-04 14:44:18.000000000 +0100 +++ new/bubblewrap-0.4.0/bwrap.xml 2019-11-27 13:34:31.000000000 +0100 @@ -131,6 +131,21 @@ <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>--pidns <arg choice="plain">FD</arg></option></term> + <listitem><para>Use an existing pid namespace instead of creating one. This is often used with --userns, because the pid namespace must be owned by the same user namespace that bwrap uses. </para> + <para>Note that this can be combined with --unshare-pid, and in that case it means that the sandbox will be in its own pid namespace, which is a child of the passed in one.</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> diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 --exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh old/bubblewrap-0.3.3/configure.ac new/bubblewrap-0.4.0/configure.ac --- old/bubblewrap-0.3.3/configure.ac 2019-05-01 10:51:30.000000000 +0200 +++ new/bubblewrap-0.4.0/configure.ac 2019-11-27 13:40:58.000000000 +0100 @@ -1,5 +1,5 @@ AC_PREREQ([2.63]) -AC_INIT([bubblewrap], [0.3.3], [[email protected]]) +AC_INIT([bubblewrap], [0.4.0], [[email protected]]) AC_CONFIG_HEADER([config.h]) AC_CONFIG_MACRO_DIR([m4]) AC_CONFIG_AUX_DIR([build-aux]) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 --exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh old/bubblewrap-0.3.3/tests/libtest-core.sh new/bubblewrap-0.4.0/tests/libtest-core.sh --- old/bubblewrap-0.3.3/tests/libtest-core.sh 2019-03-04 14:44:18.000000000 +0100 +++ new/bubblewrap-0.4.0/tests/libtest-core.sh 2019-11-27 13:34:31.000000000 +0100 @@ -75,6 +75,18 @@ fatal "$@" } +_fatal_print_files() { + file1="$1" + shift + file2="$1" + shift + ls -al "$file1" >&2 + sed -e 's/^/# /' < "$file1" >&2 + ls -al "$file2" >&2 + sed -e 's/^/# /' < "$file2" >&2 + fatal "$@" +} + assert_not_has_file () { if test -f "$1"; then _fatal_print_file "$1" "File '$1' exists" @@ -135,8 +147,18 @@ fi } +assert_files_equal() { + if ! cmp "$1" "$2"; then + _fatal_print_files "$1" "$2" "File '$1' and '$2' is not equal" + fi +} + # Use to skip all of these tests skip() { echo "1..0 # SKIP" "$@" exit 0 } + +extract_child_pid() { + grep child-pid "$1" | sed "s/^.*: \([0-9]*\).*/\1/" +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 --exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh old/bubblewrap-0.3.3/tests/test-run.sh new/bubblewrap-0.4.0/tests/test-run.sh --- old/bubblewrap-0.3.3/tests/test-run.sh 2019-05-01 10:44:10.000000000 +0200 +++ new/bubblewrap-0.4.0/tests/test-run.sh 2019-11-27 13:34:31.000000000 +0100 @@ -80,7 +80,7 @@ skip Seems like bwrap is not working at all. Maybe setuid is not working fi -echo "1..46" +echo "1..49" # Test help ${BWRAP} --help > help.txt @@ -149,6 +149,17 @@ assert_file_has_content_literal json-status.json '"exit-code": 42' echo "ok info and json-status fd" +DATA=$($RUN --proc /proc --unshare-all --info-fd 42 --json-status-fd 43 -- bash -c 'stat -L --format "%n %i" /proc/self/ns/*' 42>info.json 43>json-status.json 2>err.txt) + +for NS in "ipc" "mnt" "net" "pid" "uts"; do + + want=$(echo "$DATA" | grep "/proc/self/ns/$NS" | awk '{print $2}') + assert_file_has_content info.json "$want" + assert_file_has_content json-status.json "$want" +done + +echo "ok namespace id info in info and json-status fd" + if ! which strace 2>/dev/null || ! strace -h | grep -v -e default | grep -e fault; then echo "ok - # SKIP no strace fault injection" else @@ -329,4 +340,40 @@ fi echo "ok - we can mount another directory inside /tmp" +# These tests need user namespaces +if test -n "${bwrap_is_suid:-}"; then + echo "ok - # SKIP no setuid support for --unshare-user" + echo "ok - # SKIP no setuid support for --unshare-user" +else + mkfifo donepipe + + $RUN --info-fd 42 --unshare-user sh -c 'readlink /proc/self/ns/user > sandbox-userns; cat < donepipe' 42>info.json & + while ! test -f sandbox-userns; do sleep 1; done + SANDBOX1PID=$(extract_child_pid info.json) + + $RUN --userns 11 readlink /proc/self/ns/user > sandbox2-userns 11< /proc/$SANDBOX1PID/ns/user + echo foo > donepipe + + assert_files_equal sandbox-userns sandbox2-userns + + rm donepipe info.json sandbox-userns + + echo "ok - Test --userns" + + mkfifo donepipe + $RUN --info-fd 42 --unshare-user --unshare-pid sh -c 'readlink /proc/self/ns/pid > sandbox-pidns; cat < donepipe' 42>info.json & + while ! test -f sandbox-pidns; do sleep 1; done + SANDBOX1PID=$(extract_child_pid info.json) + + $RUN --userns 11 --pidns 12 readlink /proc/self/ns/pid > sandbox2-pidns 11< /proc/$SANDBOX1PID/ns/user 12< /proc/$SANDBOX1PID/ns/pid + echo foo > donepipe + + assert_files_equal sandbox-pidns sandbox2-pidns + + rm donepipe info.json sandbox-pidns + + echo "ok - Test --pidns" +fi + + echo "ok - End of test" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 --exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh old/bubblewrap-0.3.3/utils.c new/bubblewrap-0.4.0/utils.c --- old/bubblewrap-0.3.3/utils.c 2019-03-04 14:44:18.000000000 +0100 +++ new/bubblewrap-0.4.0/utils.c 2019-11-27 13:34:31.000000000 +0100 @@ -19,6 +19,7 @@ #include "utils.h" #include <sys/syscall.h> +#include <sys/socket.h> #ifdef HAVE_SELINUX #include <selinux/selinux.h> #endif @@ -79,6 +80,19 @@ exit (1); } +/* Fork, return in child, exiting the previous parent */ +void +fork_intermediate_child (void) +{ + int pid = fork (); + if (pid == -1) + die_with_error ("Can't fork for --pidns"); + + /* Parent is an process not needed */ + if (pid != 0) + exit (0); +} + void * xmalloc (size_t size) { @@ -670,6 +684,86 @@ return 0; } +/* Send an ucred with current pid/uid/gid over a socket, it can be + read back with read_pid_from_socket(), and then the kernel has + translated it between namespaces as needed. */ +void +send_pid_on_socket (int socket) +{ + char buf[1] = { 0 }; + struct msghdr msg = {}; + struct iovec iov = { buf, sizeof (buf) }; + const ssize_t control_len_snd = CMSG_SPACE(sizeof(struct ucred)); + char control_buf_snd[control_len_snd]; + struct cmsghdr *cmsg; + struct ucred *cred; + + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + msg.msg_control = control_buf_snd; + msg.msg_controllen = control_len_snd; + + cmsg = CMSG_FIRSTHDR(&msg); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_CREDENTIALS; + cmsg->cmsg_len = CMSG_LEN(sizeof(struct ucred)); + cred = (struct ucred *)CMSG_DATA(cmsg); + + cred->pid = getpid (); + cred->uid = geteuid (); + cred->gid = getegid (); + + if (sendmsg (socket, &msg, 0) < 0) + die_with_error ("Can't send pid"); +} + +void +create_pid_socketpair (int sockets[2]) +{ + int enable = 1; + + if (socketpair (AF_UNIX, SOCK_SEQPACKET | SOCK_CLOEXEC, 0, sockets) != 0) + die_with_error ("Can't create intermediate pids socket"); + + if (setsockopt (sockets[0], SOL_SOCKET, SO_PASSCRED, &enable, sizeof (enable)) < 0) + die_with_error ("Can't set SO_PASSCRED"); +} + +int +read_pid_from_socket (int socket) +{ + char recv_buf[1] = { 0 }; + struct msghdr msg = {}; + struct iovec iov = { recv_buf, sizeof (recv_buf) }; + const ssize_t control_len_rcv = CMSG_SPACE(sizeof(struct ucred)); + char control_buf_rcv[control_len_rcv]; + struct cmsghdr* cmsg; + + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + msg.msg_control = control_buf_rcv; + msg.msg_controllen = control_len_rcv; + + if (recvmsg (socket, &msg, 0) < 0) + die_with_error ("Cant read pid from socket"); + + if (msg.msg_controllen <= 0) + die ("Unexpected short read from pid socket"); + + for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) + { + const unsigned payload_len = cmsg->cmsg_len - CMSG_LEN(0); + if (cmsg->cmsg_level == SOL_SOCKET && + cmsg->cmsg_type == SCM_CREDENTIALS && + payload_len == sizeof(struct ucred)) + { + struct ucred *cred = (struct ucred *)CMSG_DATA(cmsg); + return cred->pid; + } + } + die ("No pid returned on socket"); +} + int raw_clone (unsigned long flags, void *child_stack) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' --exclude Makefile.in --exclude configure --exclude config.guess --exclude '*.pot' --exclude mkinstalldirs --exclude aclocal.m4 --exclude config.sub --exclude depcomp --exclude install-sh --exclude ltmain.sh old/bubblewrap-0.3.3/utils.h new/bubblewrap-0.4.0/utils.h --- old/bubblewrap-0.3.3/utils.h 2019-03-04 14:44:18.000000000 +0100 +++ new/bubblewrap-0.4.0/utils.h 2019-11-27 13:34:31.000000000 +0100 @@ -54,6 +54,8 @@ void die_oom (void) __attribute__((__noreturn__)); void die_unless_label_valid (const char *label); +void fork_intermediate_child (void); + void *xmalloc (size_t size); void *xcalloc (size_t size); void *xrealloc (void *ptr, @@ -107,6 +109,9 @@ int mkdir_with_parents (const char *pathname, int mode, bool create_last); +void create_pid_socketpair (int sockets[2]); +void send_pid_on_socket (int socket); +int read_pid_from_socket (int socket); /* syscall wrappers */ int raw_clone (unsigned long flags,
