Hello community,

here is the log from the commit of package bubblewrap for openSUSE:Factory 
checked in at 2017-10-13 14:09:16
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/bubblewrap (Old)
 and      /work/SRC/openSUSE:Factory/.bubblewrap.new (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "bubblewrap"

Fri Oct 13 14:09:16 2017 rev:4 rq:532853 version:0.2.0

Changes:
--------
--- /work/SRC/openSUSE:Factory/bubblewrap/bubblewrap.changes    2017-09-21 
12:32:47.996533808 +0200
+++ /work/SRC/openSUSE:Factory/.bubblewrap.new/bubblewrap.changes       
2017-10-13 14:09:17.578155078 +0200
@@ -1,0 +2,17 @@
+Mon Oct  9 17:53:37 UTC 2017 - [email protected]
+
+- update to version 0.2.0
+ - bwrap now automatically detects the new
+   user namespace restrictions in Red Hat Enterprise Linux 7.4:
+   bubblewrap: check for max_user_namespaces == 0.
+ - The most notable features are new arguments --as-pid1, and
+   --cap-add/--cap-drop. These were added for running systemd (or in general a
+   "full" init system) inside bubblewrap. But the capability options are also
+   useful for unprivileged callers to potentially retain capbilities inside the
+   sandbox (for example CAP_NET_ADMIN), when user namespaces are enabled.
+   Conversely, privileged callers (uid 0) can conversely drop capabilities 
(without
+   user namespaces). Contributed by Giuseppe Scrivano.
+ - With --dev, add /dev/fd and /dev/core symlinks
+   which should improve compatibility with older software.
+
+-------------------------------------------------------------------

Old:
----
  v0.1.8.tar.gz

New:
----
  v0.2.0.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ bubblewrap.spec ++++++
--- /var/tmp/diff_new_pack.oNiZK5/_old  2017-10-13 14:09:18.454116559 +0200
+++ /var/tmp/diff_new_pack.oNiZK5/_new  2017-10-13 14:09:18.458116383 +0200
@@ -17,7 +17,7 @@
 
 
 Name:           bubblewrap
-Version:        0.1.8
+Version:        0.2.0
 Release:        0
 Summary:        Core execution tool for unprivileged containers
 License:        LGPL-2.0+

++++++ v0.1.8.tar.gz -> v0.2.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/bubblewrap-0.1.8/.papr.yml 
new/bubblewrap-0.2.0/.papr.yml
--- old/bubblewrap-0.1.8/.papr.yml      1970-01-01 01:00:00.000000000 +0100
+++ new/bubblewrap-0.2.0/.papr.yml      2017-10-09 16:11:41.000000000 +0200
@@ -0,0 +1,28 @@
+context: centos7
+required: true
+
+branches:
+    - master
+    - auto
+    - try
+
+host:
+    distro: centos/7/atomic
+
+tests:
+  - env BWRAP_SUID=true ./ci/papr.sh centos:7
+
+timeout: 30m
+
+---
+
+inherit: true
+
+host:
+    distro: fedora/26/atomic
+
+context: f26-sanitizer
+required: true
+
+tests:
+  - env CFLAGS='-g -Og -fsanitize=undefined -fsanitize=address -O2 
-Wp,-D_FORTIFY_SOURCE=2' ./ci/papr.sh registry.fedoraproject.org/fedora:26
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/bubblewrap-0.1.8/.redhat-ci.yml 
new/bubblewrap-0.2.0/.redhat-ci.yml
--- old/bubblewrap-0.1.8/.redhat-ci.yml 2017-03-28 16:26:53.000000000 +0200
+++ new/bubblewrap-0.2.0/.redhat-ci.yml 1970-01-01 01:00:00.000000000 +0100
@@ -1,25 +0,0 @@
-context: centos7
-required: true
-
-branches:
-    - master
-    - auto
-    - try
-
-host:
-    distro: centos/7/atomic
-
-tests:
-  - env BWRAP_SUID=true ./ci/redhat-ci.sh centos:7
-
-timeout: 30m
-
----
-
-inherit: true
-
-context: f25-asan-ubsan
-required: true
-
-tests:
-  - env CFLAGS='-g -Og -fsanitize=undefined -fsanitize=address' 
./ci/redhat-ci.sh fedora:25
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/bubblewrap-0.1.8/Makefile.am 
new/bubblewrap-0.2.0/Makefile.am
--- old/bubblewrap-0.1.8/Makefile.am    2017-03-28 16:26:53.000000000 +0200
+++ new/bubblewrap-0.2.0/Makefile.am    2017-10-09 16:11:41.000000000 +0200
@@ -29,10 +29,13 @@
 
 include Makefile-docs.am
 
+LOG_DRIVER = env AM_TAP_AWK='$(AWK)' $(SHELL) 
$(top_srcdir)/build-aux/tap-driver.sh
+LOG_COMPILER =
 TESTS = tests/test-run.sh
 TESTS_ENVIRONMENT = BWRAP=$(abs_top_builddir)/test-bwrap
 
 EXTRA_DIST += $(TESTS)
+EXTRA_DIST += tests/libtest-core.sh
 
 if ENABLE_BASH_COMPLETION
 bashcompletiondir = $(BASH_COMPLETION_DIR)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/bubblewrap-0.1.8/README.md 
new/bubblewrap-0.2.0/README.md
--- old/bubblewrap-0.1.8/README.md      2017-03-28 16:26:53.000000000 +0200
+++ new/bubblewrap-0.2.0/README.md      2017-10-09 16:11:41.000000000 +0200
@@ -55,6 +55,7 @@
 
  - [Flatpak](http://www.flatpak.org)
  - [rpm-ostree 
unprivileged](https://github.com/projectatomic/rpm-ostree/pull/209)
+ - [bwrap-oci](https://github.com/projectatomic/bwrap-oci)
 
 We would also like to see this be available in Kubernetes/OpenShift
 clusters.  Having the ability for unprivileged users to use container
@@ -173,7 +174,3 @@
 The name bubblewrap was chosen to convey that this
 tool runs as the parent of the application (so wraps it in some sense) and 
creates
 a protective layer (the sandbox) around it.
-
-![](bubblewrap.jpg)
-
-(Bubblewrap cat by 
[dancing_stupidity](https://www.flickr.com/photos/27549668@N03/))
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/bubblewrap-0.1.8/bind-mount.c 
new/bubblewrap-0.2.0/bind-mount.c
--- old/bubblewrap-0.1.8/bind-mount.c   2017-03-28 16:26:53.000000000 +0200
+++ new/bubblewrap-0.2.0/bind-mount.c   2017-10-09 16:11:41.000000000 +0200
@@ -389,7 +389,7 @@
 
   if (src)
     {
-      if (mount (src, dest, NULL, MS_MGC_VAL | MS_BIND | (recursive ? MS_REC : 
0), NULL) != 0)
+      if (mount (src, dest, NULL, MS_BIND | (recursive ? MS_REC : 0), NULL) != 
0)
         return 1;
     }
 
@@ -411,7 +411,7 @@
   new_flags = current_flags | (devices ? 0 : MS_NODEV) | MS_NOSUID | (readonly 
? MS_RDONLY : 0);
   if (new_flags != current_flags &&
       mount ("none", resolved_dest,
-             NULL, MS_MGC_VAL | MS_BIND | MS_REMOUNT | new_flags, NULL) != 0)
+             NULL, MS_BIND | MS_REMOUNT | new_flags, NULL) != 0)
     return 3;
 
   /* We need to work around the fact that a bind mount does not apply the 
flags, so we need to manually
@@ -426,7 +426,7 @@
           new_flags = current_flags | (devices ? 0 : MS_NODEV) | MS_NOSUID | 
(readonly ? MS_RDONLY : 0);
           if (new_flags != current_flags &&
               mount ("none", mount_tab[i].mountpoint,
-                     NULL, MS_MGC_VAL | MS_BIND | MS_REMOUNT | new_flags, 
NULL) != 0)
+                     NULL, MS_BIND | MS_REMOUNT | new_flags, NULL) != 0)
             {
               /* If we can't read the mountpoint we can't remount it, but that 
should
                  be safe to ignore because its not something the user can 
access. */
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/bubblewrap-0.1.8/bubblewrap.c 
new/bubblewrap-0.2.0/bubblewrap.c
--- old/bubblewrap-0.1.8/bubblewrap.c   2017-03-28 16:26:53.000000000 +0200
+++ new/bubblewrap-0.2.0/bubblewrap.c   2017-10-09 16:11:41.000000000 +0200
@@ -47,14 +47,15 @@
 static gid_t real_gid;
 static uid_t overflow_uid;
 static gid_t overflow_gid;
-static bool is_privileged;
+static bool is_privileged; /* See acquire_privs() */
 static const char *argv0;
 static const char *host_tty_dev;
 static int proc_fd = -1;
-static char *opt_exec_label = NULL;
-static char *opt_file_label = NULL;
+static const char *opt_exec_label = NULL;
+static const char *opt_file_label = NULL;
+static bool opt_as_pid_1;
 
-char *opt_chdir_path = NULL;
+const char *opt_chdir_path = NULL;
 bool opt_unshare_user = FALSE;
 bool opt_unshare_user_try = FALSE;
 bool opt_unshare_pid = FALSE;
@@ -70,9 +71,13 @@
 gid_t opt_sandbox_gid = -1;
 int opt_sync_fd = -1;
 int opt_block_fd = -1;
+int opt_userns_block_fd = -1;
 int opt_info_fd = -1;
 int opt_seccomp_fd = -1;
-char *opt_sandbox_hostname = NULL;
+const char *opt_sandbox_hostname = NULL;
+
+#define CAP_TO_MASK_0(x) (1L << ((x) & 31))
+#define CAP_TO_MASK_1(x) CAP_TO_MASK_0(x - 32)
 
 typedef enum {
   SETUP_BIND_MOUNT,
@@ -112,6 +117,7 @@
 struct _LockFile
 {
   const char *path;
+  int         fd;
   LockFile   *next;
 };
 
@@ -180,7 +186,7 @@
   fprintf (out,
            "    --help                       Print this help\n"
            "    --version                    Print version\n"
-           "    --args FD                    Parse nul-separated args from 
FD\n"
+           "    --args FD                    Parse NUL-separated args from 
FD\n"
            "    --unshare-all                Unshare every namespace we 
support by default\n"
            "    --share-net                  Retain the network namespace (can 
only combine with --unshare-all)\n"
            "    --unshare-user               Create new user namespace (may be 
automatically implied if not setuid)\n"
@@ -192,7 +198,7 @@
            "    --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                    Custon gid 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"
            "    --chdir DIR                  Change directory to DIR\n"
            "    --setenv VAR VALUE           Set an environment variable\n"
@@ -202,23 +208,27 @@
            "    --bind SRC DEST              Bind mount the host path SRC on 
DEST\n"
            "    --dev-bind SRC DEST          Bind mount the host path SRC on 
DEST, allowing device access\n"
            "    --ro-bind SRC DEST           Bind mount the host path SRC 
readonly on DEST\n"
-           "    --remount-ro DEST            Remount DEST as readonly, it 
doesn't recursively remount\n"
-           "    --exec-label LABEL           Exec Label for the sandbox\n"
+           "    --remount-ro DEST            Remount DEST as readonly; does 
not recursively remount\n"
+           "    --exec-label LABEL           Exec label for the sandbox\n"
            "    --file-label LABEL           File label for temporary sandbox 
content\n"
-           "    --proc DEST                  Mount procfs on DEST\n"
+           "    --proc DEST                  Mount new procfs on DEST\n"
            "    --dev DEST                   Mount new dev on DEST\n"
            "    --tmpfs DEST                 Mount new tmpfs on DEST\n"
            "    --mqueue DEST                Mount new mqueue on DEST\n"
            "    --dir DEST                   Create dir at DEST\n"
-           "    --file FD DEST               Copy from FD to dest DEST\n"
+           "    --file FD DEST               Copy from FD to destination 
DEST\n"
            "    --bind-data FD DEST          Copy from FD to file which is 
bind-mounted on DEST\n"
            "    --ro-bind-data FD DEST       Copy from FD to file which is 
readonly bind-mounted on DEST\n"
            "    --symlink SRC DEST           Create symlink at DEST with 
target SRC\n"
            "    --seccomp FD                 Load and use seccomp rules from 
FD\n"
            "    --block-fd FD                Block on FD until some data to 
read is available\n"
+           "    --userns-block-fd FD         Block on FD until the user 
namespace is ready\n"
            "    --info-fd FD                 Write information about the 
running container to FD\n"
            "    --new-session                Create a new terminal session\n"
            "    --die-with-parent            Kills with SIGKILL child process 
(COMMAND) when bwrap or bwrap's parent dies.\n"
+           "    --as-pid-1                   Do not install a reaper process 
with PID=1\n"
+           "    --cap-add CAP                Add cap CAP when running as 
privileged user\n"
+           "    --cap-drop CAP               Drop cap CAP when running as 
privileged user\n"
           );
   exit (ecode);
 }
@@ -409,6 +419,7 @@
         die_with_error ("Unable to lock file %s", lock->path);
 
       /* Keep fd open to hang on to lock */
+      lock->fd = fd;
     }
 
   /* Optionally bind our lifecycle to that of the caller */
@@ -445,11 +456,29 @@
         }
     }
 
+  /* Close FDs. */
+  for (lock = lock_files; lock != NULL; lock = lock->next)
+    {
+      if (lock->fd >= 0)
+        {
+          close (lock->fd);
+          lock->fd = -1;
+        }
+    }
+
   return initial_exit_status;
 }
 
+#define CAP_TO_MASK_0(x) (1L << ((x) & 31))
+#define CAP_TO_MASK_1(x) CAP_TO_MASK_0(x - 32)
+
+/* Set if --cap-add or --cap-drop were used */
+static bool opt_cap_add_or_drop_used;
+/* The capability set we'll target, used if above is true */
+static uint32_t requested_caps[2] = {0, 0};
+
 /* low 32bit caps needed */
-#define REQUIRED_CAPS_0 (CAP_TO_MASK (CAP_SYS_ADMIN) | CAP_TO_MASK 
(CAP_SYS_CHROOT) | CAP_TO_MASK (CAP_NET_ADMIN) | CAP_TO_MASK (CAP_SETUID) | 
CAP_TO_MASK (CAP_SETGID))
+#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))
 /* high 32bit caps needed */
 #define REQUIRED_CAPS_1 0
 
@@ -471,13 +500,43 @@
 }
 
 static void
-drop_all_caps (void)
+drop_all_caps (bool keep_requested_caps)
 {
   struct __user_cap_header_struct hdr = { _LINUX_CAPABILITY_VERSION_3, 0 };
   struct __user_cap_data_struct data[2] = { { 0 } };
 
+  if (keep_requested_caps)
+    {
+      /* Avoid calling capset() unless we need to; currently
+       * systemd-nspawn at least is known to install a seccomp
+       * policy denying capset() for dubious reasons.
+       * <https://github.com/projectatomic/bubblewrap/pull/122>
+       */
+      if (!opt_cap_add_or_drop_used && real_uid == 0)
+        {
+          assert (!is_privileged);
+          return;
+        }
+      data[0].effective = requested_caps[0];
+      data[0].permitted = requested_caps[0];
+      data[0].inheritable = requested_caps[0];
+      data[1].effective = requested_caps[1];
+      data[1].permitted = requested_caps[1];
+      data[1].inheritable = requested_caps[1];
+    }
+
   if (capset (&hdr, data) < 0)
-    die_with_error ("capset failed");
+    {
+      /* While the above logic ensures we don't call capset() for the primary
+       * process unless configured to do so, we still try to drop privileges 
for
+       * the init process unconditionally. Since due to the systemd seccomp
+       * filter that will fail, let's just ignore it.
+       */
+      if (errno == EPERM && real_uid == 0 && !is_privileged)
+        return;
+      else
+        die_with_error ("capset failed");
+    }
 }
 
 static bool
@@ -492,8 +551,12 @@
   return data[0].permitted != 0 || data[1].permitted != 0;
 }
 
+/* Most of the code here is used both to add caps to the ambient capabilities
+ * and drop caps from the bounding set.  Handle both cases here and add
+ * drop_cap_bounding_set/set_ambient_capabilities wrappers to facilitate its 
usage.
+ */
 static void
-drop_cap_bounding_set (void)
+prctl_caps (uint32_t *caps, bool do_cap_bounding, bool do_set_ambient)
 {
   unsigned long cap;
 
@@ -504,14 +567,56 @@
    *  
https://github.com/projectatomic/bubblewrap/pull/175#issuecomment-278051373
    *  
https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/commit/security/commoncap.c?id=160da84dbb39443fdade7151bc63a88f8e953077
    */
-  for (cap = 0; cap <= 63; cap++)
+  for (cap = 0; cap <= CAP_LAST_CAP; cap++)
     {
-      int res = prctl (PR_CAPBSET_DROP, cap, 0, 0, 0);
-      if (res == -1 && !(errno == EINVAL || errno == EPERM))
-        die_with_error ("Dropping capability %ld from bounds", cap);
+      bool keep = FALSE;
+      if (cap < 32)
+        {
+          if (CAP_TO_MASK_0 (cap) & caps[0])
+            keep = TRUE;
+        }
+      else
+        {
+          if (CAP_TO_MASK_1 (cap) & caps[1])
+            keep = TRUE;
+        }
+
+      if (keep && do_set_ambient)
+        {
+          int res = prctl (PR_CAP_AMBIENT, PR_CAP_AMBIENT_RAISE, cap, 0, 0);
+          if (res == -1 && !(errno == EINVAL || errno == EPERM))
+            die_with_error ("Adding ambient capability %ld", cap);
+        }
+
+      if (!keep && do_cap_bounding)
+        {
+          int res = prctl (PR_CAPBSET_DROP, cap, 0, 0, 0);
+          if (res == -1 && !(errno == EINVAL || errno == EPERM))
+            die_with_error ("Dropping capability %ld from bounds", cap);
+        }
     }
 }
 
+static void
+drop_cap_bounding_set (bool drop_all)
+{
+  if (!drop_all)
+    prctl_caps (requested_caps, TRUE, FALSE);
+  else
+    {
+      uint32_t no_caps[2] = {0, 0};
+      prctl_caps (no_caps, TRUE, FALSE);
+    }
+}
+
+static void
+set_ambient_capabilities (void)
+{
+  if (is_privileged)
+    return;
+  prctl_caps (requested_caps, FALSE, TRUE);
+}
+
 /* This acquires the privileges that the bwrap will need it to work.
  * If bwrap is not setuid, then this does nothing, and it relies on
  * unprivileged user namespaces to be used. This case is
@@ -536,11 +641,10 @@
   /* Are we setuid ? */
   if (real_uid != euid)
     {
-      if (euid == 0)
-        is_privileged = TRUE;
-      else
+      if (euid != 0)
         die ("Unexpected setuid user %d, should be 0", euid);
 
+      is_privileged = TRUE;
       /* We want to keep running as euid=0 until at the clone()
        * operation because doing so will make the user namespace be
        * owned by root, which makes it not ptrace:able by the user as
@@ -560,8 +664,8 @@
       if (new_fsuid != real_uid)
         die ("Unable to set fsuid (was %d)", (int)new_fsuid);
 
-      /* We never need capabilies after execve(), so lets drop everything from 
the bounding set */
-      drop_cap_bounding_set ();
+      /* We never need capabilities after execve(), so lets drop everything 
from the bounding set */
+      drop_cap_bounding_set (TRUE);
 
       /* Keep only the required capabilities for setup */
       set_required_caps ();
@@ -573,6 +677,21 @@
          don't support anymore */
       die ("Unexpected capabilities but not setuid, old file caps config?");
     }
+  else if (real_uid == 0)
+    {
+      /* If our uid is 0, default to inheriting all caps; the caller
+       * can drop them via --cap-drop.  This is used by at least rpm-ostree.
+       * Note this needs to happen before the argument parsing of --cap-drop.
+       */
+      struct __user_cap_header_struct hdr = { _LINUX_CAPABILITY_VERSION_3, 0 };
+      struct __user_cap_data_struct data[2] = { { 0 } };
+
+      if (capget (&hdr, data) < 0)
+        die_with_error ("capget (for uid == 0) failed");
+
+      requested_caps[0] = data[0].effective;
+      requested_caps[1] = data[1].effective;
+    }
 
   /* Else, we try unprivileged user namespaces */
 }
@@ -583,7 +702,7 @@
 {
   /* If we're in a new user namespace, we got back the bounding set, clear it 
again */
   if (opt_unshare_user)
-    drop_cap_bounding_set ();
+    drop_cap_bounding_set (FALSE);
 
   if (!is_privileged)
     return;
@@ -599,17 +718,16 @@
   set_required_caps ();
 }
 
+/* Call setuid() and use capset() to adjust capabilities */
 static void
-drop_privs (void)
+drop_privs (bool keep_requested_caps)
 {
-  if (!is_privileged)
-    return;
-
+  assert (!keep_requested_caps || !is_privileged);
   /* Drop root uid */
-  if (setuid (opt_sandbox_uid) < 0)
+  if (getuid () == 0 && setuid (opt_sandbox_uid) < 0)
     die_with_error ("unable to drop root uid");
 
-  drop_all_caps ();
+  drop_all_caps (keep_requested_caps);
 }
 
 static char *
@@ -777,20 +895,20 @@
       break;
 
     case PRIV_SEP_OP_PROC_MOUNT:
-      if (mount ("proc", arg1, "proc", MS_MGC_VAL | MS_NOSUID | MS_NOEXEC | 
MS_NODEV, NULL) != 0)
+      if (mount ("proc", arg1, "proc", MS_NOSUID | MS_NOEXEC | MS_NODEV, NULL) 
!= 0)
         die_with_error ("Can't mount proc on %s", arg1);
       break;
 
     case PRIV_SEP_OP_TMPFS_MOUNT:
       {
         cleanup_free char *opt = label_mount ("mode=0755", opt_file_label);
-        if (mount ("tmpfs", arg1, "tmpfs", MS_MGC_VAL | MS_NOSUID | MS_NODEV, 
opt) != 0)
+        if (mount ("tmpfs", arg1, "tmpfs", MS_NOSUID | MS_NODEV, opt) != 0)
           die_with_error ("Can't mount tmpfs on %s", arg1);
         break;
       }
 
     case PRIV_SEP_OP_DEVPTS_MOUNT:
-      if (mount ("devpts", arg1, "devpts", MS_MGC_VAL | MS_NOSUID | MS_NOEXEC,
+      if (mount ("devpts", arg1, "devpts", MS_NOSUID | MS_NOEXEC,
                  "newinstance,ptmxmode=0666,mode=620") != 0)
         die_with_error ("Can't mount devpts on %s", arg1);
       break;
@@ -939,6 +1057,16 @@
                 die_with_error ("Can't create symlink %s/%s", op->dest, 
stdionodes[i]);
             }
 
+          /* /dev/fd and /dev/core - legacy, but both nspawn and docker do 
these */
+          { cleanup_free char *dev_fd = strconcat (dest, "/fd");
+            if (symlink ("/proc/self/fd", dev_fd) < 0)
+              die_with_error ("Can't create symlink %s", dev_fd);
+          }
+          { cleanup_free char *dev_core = strconcat (dest, "/core");
+            if (symlink ("/proc/kcore", dev_core) < 0)
+              die_with_error ("Can't create symlink %s", dev_core);
+          }
+
           {
             cleanup_free char *pts = strconcat (dest, "/pts");
             cleanup_free char *ptmx = strconcat (dest, "/ptmx");
@@ -1030,6 +1158,8 @@
 
             close (op->fd);
 
+            assert (dest != NULL);
+
             if (ensure_file (dest, 0666) != 0)
               die_with_error ("Can't create file at %s", op->dest);
 
@@ -1046,11 +1176,13 @@
           break;
 
         case SETUP_MAKE_SYMLINK:
+          assert (op->source != NULL);  /* guaranteed by the constructor */
           if (symlink (op->source, dest) != 0)
             die_with_error ("Can't make symlink at %s", op->dest);
           break;
 
         case SETUP_SET_HOSTNAME:
+          assert (op->dest != NULL);  /* guaranteed by the constructor */
           privileged_op (privileged_op_socket,
                          PRIV_SEP_OP_SET_HOSTNAME, 0,
                          op->dest, NULL);
@@ -1150,14 +1282,14 @@
 }
 
 static void
-parse_args_recurse (int    *argcp,
-                    char ***argvp,
-                    bool    in_file,
-                    int    *total_parsed_argc_p)
+parse_args_recurse (int          *argcp,
+                    const char ***argvp,
+                    bool          in_file,
+                    int          *total_parsed_argc_p)
 {
   SetupOp *op;
   int argc = *argcp;
-  char **argv = *argvp;
+  const char **argv = *argvp;
   /* I can't imagine a case where someone wants more than this.
    * If you do...you should be able to pass multiple files
    * via a single tmpfs and linking them there, etc.
@@ -1189,11 +1321,11 @@
         {
           int the_fd;
           char *endptr;
-          char *data, *p;
-          char *data_end;
+          char *data = NULL;
+          const char *p, *data_end;
           size_t data_len;
-          cleanup_free char **data_argv = NULL;
-          char **data_argv_copy;
+          cleanup_free const char **data_argv = NULL;
+          const char **data_argv_copy;
           int data_argc;
           int i;
 
@@ -1210,6 +1342,7 @@
           data = load_file_data (the_fd, &data_len);
           if (data == NULL)
             die_with_error ("Can't read --args data");
+          (void) close (the_fd);
 
           data_end = data + data_len;
           data_argc = 0;
@@ -1542,6 +1675,23 @@
           argv += 1;
           argc -= 1;
         }
+      else if (strcmp (arg, "--userns-block-fd") == 0)
+        {
+          int the_fd;
+          char *endptr;
+
+          if (argc < 2)
+            die ("--userns-block-fd 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_block_fd = the_fd;
+
+          argv += 1;
+          argc -= 1;
+        }
       else if (strcmp (arg, "--info-fd") == 0)
         {
           int the_fd;
@@ -1652,6 +1802,62 @@
         {
           opt_die_with_parent = TRUE;
         }
+      else if (strcmp (arg, "--as-pid-1") == 0)
+        {
+          opt_as_pid_1 = TRUE;
+        }
+      else if (strcmp (arg, "--cap-add") == 0)
+        {
+          cap_value_t cap;
+          if (argc < 2)
+            die ("--cap-add takes an argument");
+
+          opt_cap_add_or_drop_used = TRUE;
+
+          if (strcasecmp (argv[1], "ALL") == 0)
+            {
+              requested_caps[0] = requested_caps[1] = 0xFFFFFFFF;
+            }
+          else
+            {
+              if (cap_from_name (argv[1], &cap) < 0)
+                die ("unknown cap: %s", argv[1]);
+
+              if (cap < 32)
+                requested_caps[0] |= CAP_TO_MASK_0 (cap);
+              else
+                requested_caps[1] |= CAP_TO_MASK_1 (cap - 32);
+            }
+
+          argv += 1;
+          argc -= 1;
+        }
+      else if (strcmp (arg, "--cap-drop") == 0)
+        {
+          cap_value_t cap;
+          if (argc < 2)
+            die ("--cap-drop takes an argument");
+
+          opt_cap_add_or_drop_used = TRUE;
+
+          if (strcasecmp (argv[1], "ALL") == 0)
+            {
+              requested_caps[0] = requested_caps[1] = 0;
+            }
+          else
+            {
+              if (cap_from_name (argv[1], &cap) < 0)
+                die ("unknown cap: %s", argv[1]);
+
+              if (cap < 32)
+                requested_caps[0] &= ~CAP_TO_MASK_0 (cap);
+              else
+                requested_caps[1] &= ~CAP_TO_MASK_1 (cap - 32);
+            }
+
+          argv += 1;
+          argc -= 1;
+        }
       else if (*arg == '-')
         {
           die ("Unknown option %s", arg);
@@ -1670,8 +1876,8 @@
 }
 
 static void
-parse_args (int    *argcp,
-            char ***argvp)
+parse_args (int          *argcp,
+            const char ***argvp)
 {
   int total_parsed_argc = *argcp;
 
@@ -1756,7 +1962,16 @@
   if (argc == 0)
     usage (EXIT_FAILURE, stderr);
 
-  parse_args (&argc, &argv);
+  parse_args (&argc, (const char ***) &argv);
+
+  if ((requested_caps[0] || requested_caps[1]) && is_privileged)
+    die ("--cap-add in setuid mode can be used only by root");
+
+  if (opt_userns_block_fd != -1 && !opt_unshare_user)
+    die ("--userns-block-fd requires --unshare-user");
+
+  if (opt_userns_block_fd != -1 && opt_info_fd == -1)
+    die ("--userns-block-fd requires --info-fd");
 
   /* We have to do this if we weren't installed setuid (and we're not
    * root), so let's just DWIM */
@@ -1783,6 +1998,15 @@
             disabled = TRUE;
         }
 
+      /* Check for max_user_namespaces */
+      if (stat ("/proc/sys/user/max_user_namespaces", &sbuf) == 0)
+        {
+          cleanup_free char *max_user_ns = NULL;
+          max_user_ns = load_file_at (AT_FDCWD, 
"/proc/sys/user/max_user_namespaces");
+          if (max_user_ns != NULL && strcmp(max_user_ns, "0\n") == 0)
+            disabled = TRUE;
+        }
+
       /* Debian lets you disable *unprivileged* user namespaces. However this 
is not
          a problem if we're privileged, and if we're not opt_unshare_user is 
TRUE
          already, and there is not much we can do, its just a non-working 
setup. */
@@ -1810,6 +2034,12 @@
   if (!opt_unshare_uts && opt_sandbox_hostname != NULL)
     die ("Specifying --hostname requires --unshare-uts");
 
+  if (opt_as_pid_1 && !opt_unshare_pid)
+    die ("Specifying --as-pid-1 requires --unshare-pid");
+
+  if (opt_as_pid_1 && lock_files != NULL)
+    die ("Specifying --as-pid-1 and --lock-file is not permitted");
+
   /* We need to read stuff from proc during the pivot_root dance, etc.
      Lets keep a fd to it open */
   proc_fd = open ("/proc", O_RDONLY | O_PATH);
@@ -1829,7 +2059,7 @@
 
   __debug__ (("creating new namespace\n"));
 
-  if (opt_unshare_pid)
+  if (opt_unshare_pid && !opt_as_pid_1)
     {
       event_fd = eventfd (0, EFD_CLOEXEC | EFD_NONBLOCK);
       if (event_fd == -1)
@@ -1890,7 +2120,7 @@
     {
       /* Parent, outside sandbox, privileged (initially) */
 
-      if (is_privileged && opt_unshare_user)
+      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
            * not 0. This means we're not allowed to write this from
@@ -1908,17 +2138,11 @@
       /* Initial launched process, wait for exec:ed command to exit */
 
       /* We don't need any privileges in the launcher, drop them immediately. 
*/
-      drop_privs ();
+      drop_privs (FALSE);
 
       /* Optionally bind our lifecycle to that of the parent */
       handle_die_with_parent ();
 
-      /* Let child run now that the uid maps are set up */
-      val = 1;
-      res = write (child_wait_fd, &val, 8);
-      /* Ignore res, if e.g. the child died and closed child_wait_fd we don't 
want to error out here */
-      close (child_wait_fd);
-
       if (opt_info_fd != -1)
         {
           cleanup_free char *output = xasprintf ("{\n    \"child-pid\": 
%i\n}\n", pid);
@@ -1928,6 +2152,19 @@
           close (opt_info_fd);
         }
 
+      if (opt_userns_block_fd != -1)
+        {
+          char b[1];
+          (void) TEMP_FAILURE_RETRY (read (opt_userns_block_fd, b, 1));
+          close (opt_userns_block_fd);
+        }
+
+      /* Let child run now that the uid maps are set up */
+      val = 1;
+      res = write (child_wait_fd, &val, 8);
+      /* Ignore res, if e.g. the child died and closed child_wait_fd we don't 
want to error out here */
+      close (child_wait_fd);
+
       monitor_child (event_fd, pid);
       exit (0); /* Should not be reached, but better safe... */
     }
@@ -1964,7 +2201,7 @@
 
   ns_uid = opt_sandbox_uid;
   ns_gid = opt_sandbox_gid;
-  if (!is_privileged && opt_unshare_user)
+  if (!is_privileged && opt_unshare_user && opt_userns_block_fd == -1)
     {
       /* In the unprivileged case we have to write the uid/gid maps in
        * the child, because we have no caps in the parent */
@@ -2038,7 +2275,7 @@
       if (child == 0)
         {
           /* Unprivileged setup process */
-          drop_privs ();
+          drop_privs (FALSE);
           close (privsep_sockets[0]);
           setup_newroot (opt_unshare_pid, privsep_sockets[1]);
           exit (0);
@@ -2081,7 +2318,8 @@
     die_with_error ("unmount old root");
 
   if (opt_unshare_user &&
-      (ns_uid != opt_sandbox_uid || ns_gid != opt_sandbox_gid))
+      (ns_uid != opt_sandbox_uid || ns_gid != opt_sandbox_gid) &&
+      opt_userns_block_fd == -1)
     {
       /* Now that devpts is mounted and we've no need for mount
          permissions we can create a new userspace and map our uid
@@ -2103,13 +2341,13 @@
   if (chdir ("/") != 0)
     die_with_error ("chdir /");
 
-  /* All privileged ops are done now, so drop it */
-  drop_privs ();
+  /* All privileged ops are done now, so drop caps we don't need */
+  drop_privs (!is_privileged);
 
   if (opt_block_fd != -1)
     {
       char b[1];
-      read (opt_block_fd, b, 1);
+      (void) TEMP_FAILURE_RETRY (read (opt_block_fd, b, 1));
       close (opt_block_fd);
     }
 
@@ -2162,7 +2400,7 @@
 
   __debug__ (("forking for child\n"));
 
-  if (opt_unshare_pid || lock_files != NULL || opt_sync_fd != -1)
+  if (!opt_as_pid_1 && (opt_unshare_pid || lock_files != NULL || opt_sync_fd 
!= -1))
     {
       /* We have to have a pid 1 in the pid namespace, because
        * otherwise we'll get a bunch of zombies as nothing reaps
@@ -2176,6 +2414,8 @@
 
       if (pid != 0)
         {
+          drop_all_caps (FALSE);
+
           /* Close fds in pid 1, except stdio and optionally event_fd
              (for syncing pid 2 lifetime with monitor_child) and
              opt_sync_fd (for syncing sandbox lifetime with outside
@@ -2201,8 +2441,13 @@
   if (proc_fd != -1)
     close (proc_fd);
 
-  if (opt_sync_fd != -1)
-    close (opt_sync_fd);
+  /* If we are using --as-pid-1 leak the sync fd into the sandbox.
+     --sync-fd will still work unless the container process doesn't close this 
file.  */
+  if (!opt_as_pid_1)
+    {
+      if (opt_sync_fd != -1)
+        close (opt_sync_fd);
+    }
 
   /* We want sigchild in the child */
   unblock_sigchild ();
@@ -2210,6 +2455,9 @@
   /* Optionally bind our lifecycle */
   handle_die_with_parent ();
 
+  if (!is_privileged)
+    set_ambient_capabilities ();
+
   /* Should be the last thing before execve() so that filters don't
    * need to handle anything above */
   if (seccomp_data != NULL &&
Binary files old/bubblewrap-0.1.8/bubblewrap.jpg and 
new/bubblewrap-0.2.0/bubblewrap.jpg differ
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/bubblewrap-0.1.8/bwrap.xml 
new/bubblewrap-0.2.0/bwrap.xml
--- old/bubblewrap-0.1.8/bwrap.xml      2017-03-28 16:26:53.000000000 +0200
+++ new/bubblewrap-0.2.0/bwrap.xml      2017-10-09 16:11:41.000000000 +0200
@@ -263,6 +263,14 @@
       </para></listitem>
     </varlistentry>
     <varlistentry>
+      <term><option>--userns-block-fd <arg 
choice="plain">FD</arg></option></term>
+      <listitem><para>
+       Do not initialize the user namespace but wait on FD until it is ready.  
This allow
+        external processes (like newuidmap/newgidmap) to setup the user 
namespace before it
+        is used by the sandbox process.
+      </para></listitem>
+    </varlistentry>
+    <varlistentry>
       <term><option>--info-fd <arg choice="plain">FD</arg></option></term>
       <listitem><para>
        Write information in JSON format about the sandbox to FD.
@@ -289,6 +297,31 @@
         See prctl, PR_SET_PDEATHSIG.
       </para></listitem>
     </varlistentry>
+    <varlistentry>
+      <term><option>--as-pid-1</option></term>
+      <listitem><para>
+       Do not create a process with PID=1 in the sandbox to reap child 
processes.
+      </para></listitem>
+    </varlistentry>
+    <varlistentry>
+      <term><option>--cap-add <arg choice="plain">CAP</arg></option></term>
+      <listitem><para>
+        Add the specified capability when running as privileged user.  It 
accepts
+        the special value ALL to add all the permitted caps.
+      </para></listitem>
+    </varlistentry>
+    <varlistentry>
+      <term><option>--cap-drop <arg choice="plain">CAP</arg></option></term>
+      <listitem><para>
+        Drop the specified capability when running as privileged user.  It 
accepts
+        the special value ALL to drop all the caps.
+
+        By default no caps are left in the sandboxed process.  The
+        <option>--cap-add</option> and <option>--cap-drop</option>
+        options are processed in the order they are specified on the
+        command line.  Please be careful to the order they are specified.
+      </para></listitem>
+    </varlistentry>
   </variablelist>
 </refsect1>
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/bubblewrap-0.1.8/ci/papr.sh 
new/bubblewrap-0.2.0/ci/papr.sh
--- old/bubblewrap-0.1.8/ci/papr.sh     1970-01-01 01:00:00.000000000 +0100
+++ new/bubblewrap-0.2.0/ci/papr.sh     2017-10-09 16:11:41.000000000 +0200
@@ -0,0 +1,48 @@
+#!/usr/bin/env bash
+
+set -xeuo pipefail
+
+distro=$1
+
+runcontainer() {
+    docker run --rm --env=container=true --env=BWRAP_SUID=${BWRAP_SUID:-} 
--env CFLAGS="${CFLAGS:-}" --net=host --privileged -v /usr:/host/usr -v 
$(pwd):/srv/code -w /srv/code $distro ./ci/papr.sh $distro
+}
+
+buildinstall_to_host() {
+
+    yum -y install git autoconf automake libtool make gcc redhat-rpm-config \
+        libcap-devel  'pkgconfig(libselinux)' 'libxslt' 'docbook-style-xsl' \
+        lib{a,ub,t}san /usr/bin/eu-readelf rsync
+
+    echo testing: $(git describe --tags --always --abbrev=42)
+
+    env NOCONFIGURE=1 ./autogen.sh
+    ./configure --prefix=/usr --libdir=/usr/lib64
+    make -j 8
+    tmpd=$(mktemp -d)
+    make install DESTDIR=${tmpd}
+    for san in a t ub; do
+        if eu-readelf -d ${tmpd}/usr/bin/bwrap | grep -q 
"NEEDED.*lib${san}san"; then
+            for x in /usr/lib64/lib${san}san*.so.*; do
+                install -D $x ${tmpd}${x}
+            done
+        fi
+    done
+    rsync -rlv ${tmpd}/usr/ /host/usr/
+    if ${BWRAP_SUID}; then
+        chmod u+s /host/usr/bin/bwrap
+    fi
+    rm ${tmpd} -rf
+}
+
+if test -z "${container:-}"; then
+    ostree admin unlock
+    useradd bwrap-tester
+    runcontainer
+    # Unprivileged invocation
+    runuser -u bwrap-tester env ASAN_OPTIONS=detect_leaks=false 
./tests/test-run.sh
+    # Privileged invocation
+    env ASAN_OPTIONS=detect_leaks=false ./tests/test-run.sh
+else
+    buildinstall_to_host
+fi
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/bubblewrap-0.1.8/ci/redhat-ci.sh 
new/bubblewrap-0.2.0/ci/redhat-ci.sh
--- old/bubblewrap-0.1.8/ci/redhat-ci.sh        2017-03-28 16:26:53.000000000 
+0200
+++ new/bubblewrap-0.2.0/ci/redhat-ci.sh        1970-01-01 01:00:00.000000000 
+0100
@@ -1,47 +0,0 @@
-#!/usr/bin/env bash
-
-set -xeuo pipefail
-
-distro=$1
-
-runcontainer() {
-    docker run --rm --env=container=true --env=BWRAP_SUID=${BWRAP_SUID:-} 
--env CFLAGS="${CFLAGS:-}" --net=host --privileged -v /usr:/host/usr -v 
$(pwd):/srv/code -w /srv/code $distro ./ci/redhat-ci.sh $distro
-}
-
-buildinstall_to_host() {
-
-    yum -y install git autoconf automake libtool make gcc redhat-rpm-config \
-        libcap-devel  'pkgconfig(libselinux)' 'libxslt' 'docbook-style-xsl' \
-        lib{a,ub,t}san /usr/bin/eu-readelf
-
-    echo testing: $(git describe --tags --always --abbrev=42)
-
-    env NOCONFIGURE=1 ./autogen.sh
-    ./configure --prefix=/usr --libdir=/usr/lib64
-    make -j 8
-    tmpd=$(mktemp -d)
-    make install DESTDIR=${tmpd}
-    for san in a t ub; do
-        if eu-readelf -d ${tmpd}/usr/bin/bwrap | grep -q 
"NEEDED.*lib${san}san"; then
-            for x in /usr/lib64/lib${san}san*.so.*; do
-                install -D $x ${tmpd}${x}
-            done
-        fi
-    done
-    rsync -rlv ${tmpd}/usr/ /host/usr/
-    if ${BWRAP_SUID}; then
-        chmod u+s /host/usr/bin/bwrap
-    fi
-    rm ${tmpd} -rf
-}
-
-if test -z "${container:-}"; then
-    ostree admin unlock
-    # Hack until the host tree is updated in rhci
-    rpm -Uvh 
https://kojipkgs.fedoraproject.org//packages/glibc/2.24/4.fc25/x86_64/{libcrypt-nss,glibc,glibc-common,glibc-all-langpacks}-2.24-4.fc25.x86_64.rpm
-    useradd bwrap-tester
-    runcontainer
-    runuser -u bwrap-tester env ASAN_OPTIONS=detect_leaks=false 
./tests/test-run.sh
-else
-    buildinstall_to_host
-fi
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/bubblewrap-0.1.8/completions/bash/bwrap 
new/bubblewrap-0.2.0/completions/bash/bwrap
--- old/bubblewrap-0.1.8/completions/bash/bwrap 2017-03-28 16:26:53.000000000 
+0200
+++ new/bubblewrap-0.2.0/completions/bash/bwrap 2017-10-09 16:11:41.000000000 
+0200
@@ -8,11 +8,14 @@
     _init_completion || return
 
        local boolean_options="
+               --as-pid-1
                --help
+               --new-session
                --unshare-cgroup
                --unshare-cgroup-try
                --unshare-user
                --unshare-user-try
+               --unshare-all
                --unshare-ipc
                --unshare-net
                --unshare-pid
@@ -26,9 +29,12 @@
                --bind
                --bind-data
                --block-fd
+               --cap-add
+               --cap-drop
                --chdir
                --dev
                --dev-bind
+               --die-with-parent
                --dir
                --exec-label
                --file
@@ -38,17 +44,15 @@
                --info-fd
                --lock-file
                --proc
-               --ro-bind
                --remount-ro
+               --ro-bind
                --seccomp
                --setenv
                --symlink
                --sync-fd
                --uid
                --unsetenv
-               --seccomp
-               --symlink
-               --die-with-parent
+               --userns-block-fd
        "
 
        if [[ "$cur" == -* ]]; then
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/bubblewrap-0.1.8/configure.ac 
new/bubblewrap-0.2.0/configure.ac
--- old/bubblewrap-0.1.8/configure.ac   2017-03-28 16:26:53.000000000 +0200
+++ new/bubblewrap-0.2.0/configure.ac   2017-10-09 16:11:41.000000000 +0200
@@ -1,5 +1,5 @@
 AC_PREREQ([2.63])
-AC_INIT([bubblewrap], [0.1.8], [[email protected]])
+AC_INIT([bubblewrap], [0.2.0], [[email protected]])
 AC_CONFIG_HEADER([config.h])
 AC_CONFIG_MACRO_DIR([m4])
 AC_CONFIG_AUX_DIR([build-aux])
@@ -87,6 +87,12 @@
 ])
 AC_SUBST(WARN_CFLAGS)
 
+AC_CHECK_LIB(cap, cap_from_text)
+
+if test "$ac_cv_lib_cap_cap_from_text" != "yes"; then
+            AC_MSG_ERROR([*** libcap requested but not found])
+fi
+
 AC_ARG_WITH(priv-mode,
             AS_HELP_STRING([--with-priv-mode=setuid/none],
                            [How to set privilege-raising during make install]),
@@ -110,6 +116,9 @@
         AC_DEFINE(ENABLE_REQUIRE_USERNS, 1, [Define if userns should be used 
by default in suid mode])
      ])
 
+AC_PROG_AWK
+AC_REQUIRE_AUX_FILE([tap-driver.sh])
+
 AC_CONFIG_FILES([
 Makefile
 ])
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/bubblewrap-0.1.8/demos/bubblewrap-shell.sh 
new/bubblewrap-0.2.0/demos/bubblewrap-shell.sh
--- old/bubblewrap-0.1.8/demos/bubblewrap-shell.sh      2017-03-28 
16:26:53.000000000 +0200
+++ new/bubblewrap-0.2.0/demos/bubblewrap-shell.sh      2017-10-09 
16:11:41.000000000 +0200
@@ -23,6 +23,7 @@
       --chdir / \
       --unshare-all \
       --share-net \
+      --die-with-parent \
       --dir /run/user/$(id -u) \
       --setenv XDG_RUNTIME_DIR "/run/user/`id -u`" \
       --setenv PS1 "bwrap-demo$ " \
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/bubblewrap-0.1.8/demos/userns-block-fd.py 
new/bubblewrap-0.2.0/demos/userns-block-fd.py
--- old/bubblewrap-0.1.8/demos/userns-block-fd.py       1970-01-01 
01:00:00.000000000 +0100
+++ new/bubblewrap-0.2.0/demos/userns-block-fd.py       2017-10-09 
16:11:41.000000000 +0200
@@ -0,0 +1,36 @@
+#!/bin/python
+
+import os, select, subprocess, json
+
+pipe_info = os.pipe()
+userns_block = os.pipe()
+
+pid = os.fork()
+
+if pid != 0:
+    os.close(pipe_info[1])
+    os.close(userns_block[0])
+
+    select.select([pipe_info[0]], [], [])
+
+    data = json.load(os.fdopen(pipe_info[0]))
+    child_pid = str(data['child-pid'])
+
+    subprocess.call(["newuidmap", child_pid, "0", str(os.getuid()), "1"])
+    subprocess.call(["newgidmap", child_pid, "0", str(os.getgid()), "1"])
+
+    os.write(userns_block[1], '1')
+else:
+    os.close(pipe_info[0])
+    os.close(userns_block[1])
+
+    args = ["bwrap",
+            "bwrap",
+            "--unshare-all",
+            "--unshare-user",
+            "--userns-block-fd", "%i" % userns_block[0],
+            "--info-fd", "%i" % pipe_info[1],
+            "--bind", "/", "/",
+            "cat", "/proc/self/uid_map"]
+
+    os.execl(*args)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/bubblewrap-0.1.8/tests/libtest-core.sh 
new/bubblewrap-0.2.0/tests/libtest-core.sh
--- old/bubblewrap-0.1.8/tests/libtest-core.sh  1970-01-01 01:00:00.000000000 
+0100
+++ new/bubblewrap-0.2.0/tests/libtest-core.sh  2017-10-09 16:11:41.000000000 
+0200
@@ -0,0 +1,142 @@
+# Core source library for shell script tests; the
+# canonical version lives in:
+#
+#   https://github.com/ostreedev/ostree
+#
+# Known copies are in the following repos:
+#
+# - https://github.com/projectatomic/rpm-ostree
+#
+# Copyright (C) 2017 Colin Walters <[email protected]>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the
+# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+# Boston, MA 02111-1307, USA.
+
+fatal() {
+    echo $@ 1>&2; exit 1
+}
+# fatal() is shorter to type, but retain this alias
+assert_not_reached () {
+    fatal "$@"
+}
+
+# Some tests look for specific English strings. Use a UTF-8 version
+# of the C (POSIX) locale if we have one, or fall back to POSIX
+# (https://sourceware.org/glibc/wiki/Proposals/C.UTF-8)
+if locale -a | grep C.UTF-8 >/dev/null; then
+    export LC_ALL=C.UTF-8
+else
+    export LC_ALL=C
+fi
+
+# This should really be the default IMO
+export G_DEBUG=fatal-warnings
+
+assert_streq () {
+    test "$1" = "$2" || fatal "$1 != $2"
+}
+
+assert_str_match () {
+    if ! echo "$1" | grep -E -q "$2"; then
+             fatal "$1 does not match regexp $2"
+    fi
+}
+
+assert_not_streq () {
+    (! test "$1" = "$2") || fatal "$1 == $2"
+}
+
+assert_has_file () {
+    test -f "$1" || fatal "Couldn't find '$1'"
+}
+
+assert_has_dir () {
+    test -d "$1" || fatal "Couldn't find '$1'"
+}
+
+# Dump ls -al + file contents to stderr, then fatal()
+_fatal_print_file() {
+    file="$1"
+    shift
+    ls -al "$file" >&2
+    sed -e 's/^/# /' < "$file" >&2
+    fatal "$@"
+}
+
+assert_not_has_file () {
+    if test -f "$1"; then
+        _fatal_print_file "$1" "File '$1' exists"
+    fi
+}
+
+assert_not_file_has_content () {
+    fpath=$1
+    shift
+    for re in "$@"; do
+        if grep -q -e "$re" "$fpath"; then
+            _fatal_print_file "$fpath" "File '$fpath' matches regexp '$re'"
+        fi
+    done
+}
+
+assert_not_has_dir () {
+    if test -d "$1"; then
+             fatal "Directory '$1' exists"
+    fi
+}
+
+assert_file_has_content () {
+    fpath=$1
+    shift
+    for re in "$@"; do
+        if ! grep -q -e "$re" "$fpath"; then
+            _fatal_print_file "$fpath" "File '$fpath' doesn't match regexp 
'$re'"
+        fi
+    done
+}
+
+assert_file_has_content_literal () {
+    if ! grep -q -F -e "$2" "$1"; then
+        _fatal_print_file "$1" "File '$1' doesn't match fixed string list '$2'"
+    fi
+}
+
+assert_file_has_mode () {
+    mode=$(stat -c '%a' $1)
+    if [ "$mode" != "$2" ]; then
+        fatal "File '$1' has wrong mode: expected $2, but got $mode"
+    fi
+}
+
+assert_symlink_has_content () {
+    if ! test -L "$1"; then
+        fatal "File '$1' is not a symbolic link"
+    fi
+    if ! readlink "$1" | grep -q -e "$2"; then
+        _fatal_print_file "$1" "Symbolic link '$1' doesn't match regexp '$2'"
+    fi
+}
+
+assert_file_empty() {
+    if test -s "$1"; then
+        _fatal_print_file "$1" "File '$1' is not empty"
+    fi
+}
+
+# Use to skip all of these tests
+skip() {
+    echo "1..0 # SKIP" "$@"
+    exit 0
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/bubblewrap-0.1.8/tests/test-run.sh 
new/bubblewrap-0.2.0/tests/test-run.sh
--- old/bubblewrap-0.1.8/tests/test-run.sh      2017-03-28 16:26:53.000000000 
+0200
+++ new/bubblewrap-0.2.0/tests/test-run.sh      2017-10-09 16:11:41.000000000 
+0200
@@ -2,7 +2,13 @@
 
 set -xeuo pipefail
 
+# Make sure /sbin/getpcaps etc. are in our PATH even if non-root
+PATH="$PATH:/usr/sbin:/sbin"
+
 srcd=$(cd $(dirname $0) && pwd)
+
+. ${srcd}/libtest-core.sh
+
 bn=$(basename $0)
 tempdir=$(mktemp -d /var/tmp/tap-test.XXXXXX)
 touch ${tempdir}/.testtmp
@@ -19,20 +25,6 @@
 
 : "${BWRAP:=bwrap}"
 
-skip () {
-    echo $@ 1>&2; exit 77
-}
-
-assert_not_reached () {
-    echo $@ 1>&2; exit 1
-}
-
-assert_file_has_content () {
-    if ! grep -q -e "$2" "$1"; then
-        echo 1>&2 "File '$1' doesn't match regexp '$2'"; exit 1
-    fi
-}
-
 FUSE_DIR=
 for mp in $(cat /proc/self/mounts | grep " fuse[. ]" | grep user_id=$(id -u) | 
awk '{print $2}'); do
     if test -d $mp; then
@@ -42,9 +34,15 @@
     fi
 done
 
+if test "$(id -u)" = "0"; then
+    is_uidzero=true
+else
+    is_uidzero=false
+fi
+
 # This is supposed to be an otherwise readable file in an unreadable (by the 
user) dir
 UNREADABLE=/root/.bashrc
-if test -x `dirname $UNREADABLE`; then
+if ${is_uidzero} || test -x `dirname $UNREADABLE`; then
     UNREADABLE=
 fi
 
@@ -55,36 +53,101 @@
     skip Seems like bwrap is not working at all. Maybe setuid is not working
 fi
 
+echo "1..32"
+
 # Test help
 ${BWRAP} --help > help.txt
 assert_file_has_content help.txt "usage: ${BWRAP}"
+echo "ok - Help works"
 
 for ALT in "" "--unshare-user-try"  "--unshare-pid" "--unshare-user-try 
--unshare-pid"; do
     # Test fuse fs as bind source
     if [ x$FUSE_DIR != x ]; then
         $RUN $ALT  --proc /proc --dev /dev --bind $FUSE_DIR /tmp/foo true
+        echo "ok - can bind-mount a FUSE directory with $ALT"
+    else
+        echo "ok # SKIP no FUSE support"
     fi
     # no --dev => no devpts => no map_root workaround
     $RUN $ALT --proc /proc true
+    echo "ok - can mount /proc with $ALT"
     # No network
     $RUN $ALT --unshare-net --proc /proc --dev /dev true
+    echo "ok - can unshare network, create new /dev with $ALT"
     # Unreadable file
-    echo -n "expect EPERM: "
-    if $RUN $ALT --unshare-net --proc /proc --bind /etc/shadow  /tmp/foo cat 
/etc/shadow; then
+    echo -n "expect EPERM: " >&2
+
+    # Test caps when bwrap is not setuid
+    if ! test -u ${BWRAP}; then
+        CAP="--cap-add ALL"
+    else
+        CAP=""
+    fi
+
+    if ! ${is_uidzero} && $RUN $CAP $ALT --unshare-net --proc /proc --bind 
/etc/shadow  /tmp/foo cat /etc/shadow; then
         assert_not_reached Could read /etc/shadow
     fi
+    echo "ok - cannot read /etc/shadow with $ALT"
     # Unreadable dir
     if [ x$UNREADABLE != x ]; then
-        echo -n "expect EPERM: "
+        echo -n "expect EPERM: " >&2
         if $RUN $ALT --unshare-net --proc /proc --dev /dev --bind $UNREADABLE  
/tmp/foo cat /tmp/foo ; then
             assert_not_reached Could read $UNREADABLE
         fi
+        echo "ok - cannot read $UNREADABLE with $ALT"
+    else
+        echo "ok # SKIP not sure what unreadable file to use"
     fi
 
     # bind dest in symlink 
(https://github.com/projectatomic/bubblewrap/pull/119)
     $RUN $ALT --dir /tmp/dir --symlink dir /tmp/link --bind /etc /tmp/link true
+    echo "ok - can bind a destination over a symlink"
 done
 
+# Test devices
+$RUN --unshare-pid --dev /dev ls -al 
/dev/{stdin,stdout,stderr,null,random,urandom,fd,core} >/dev/null
+echo "ok - all expected devices were created"
+
+# Test --as-pid-1
+$RUN --unshare-pid --as-pid-1 --bind / / bash -c 'echo $$' > as_pid_1.txt
+assert_file_has_content as_pid_1.txt "1"
+echo "ok - can run as pid 1"
+
+# Test error prefixing
+if $RUN --unshare-pid  --bind /source-enoent /dest true 2>err.txt; then
+    assert_not_reached "bound nonexistent source"
+fi
+assert_file_has_content err.txt "^bwrap: Can't find source path.*source-enoent"
+echo "ok error prefxing"
+
+if ! ${is_uidzero}; then
+    # When invoked as non-root, check that by default we have no caps left
+    for OPT in "" "--unshare-user-try --as-pid-1" "--unshare-user-try" 
"--as-pid-1"; do
+        e=0
+        $RUN $OPT --unshare-pid getpcaps 1 2> caps.test || e=$?
+        sed -e 's/^/# /' < caps.test >&2
+        test "$e" = 0
+        assert_not_file_has_content caps.test ': =.*cap'
+    done
+    echo "ok - we have no caps as uid != 0"
+else
+    capsh --print > caps.orig
+    for OPT in "" "--as-pid-1"; do
+        $RUN $OPT --unshare-pid capsh --print >caps.test
+        diff -u caps.orig caps.test
+    done
+    # And test that we can drop all, as well as specific caps
+    $RUN $OPT --cap-drop ALL --unshare-pid capsh --print >caps.test
+    assert_file_has_content caps.test 'Current: =$'
+    # Check for dropping kill/fowner (we assume all uid 0 callers have this)
+    $RUN $OPT --cap-drop CAP_KILL --cap-drop CAP_FOWNER --unshare-pid capsh 
--print >caps.test
+    assert_not_file_has_content caps.test '^Current: =.*cap_kill'
+    assert_not_file_has_content caps.test '^Current: =.*cap_fowner'
+    # But we should still have net_bind_service for example
+    assert_file_has_content caps.test '^Current: =.*cap_net_bind_service'
+    echo "ok - we have the expected caps as uid 0"
+fi
+
 # Test --die-with-parent
 
 cat >lockf-n.py <<EOF
@@ -107,7 +170,10 @@
 touch lock
 
 for die_with_parent_argv in "--die-with-parent" "--die-with-parent 
--unshare-pid"; do
-    /bin/bash -c "$RUN ${die_with_parent_argv} --lock-file $(pwd)/lock sleep 
1h && true" &
+    # We have to loop here, because bwrap doesn't wait for the lock if
+    # another process is holding it. If we're unlucky, lockf-n.py will
+    # be holding it.
+    /bin/bash -c "while true; do $RUN ${die_with_parent_argv} --lock-file 
$(pwd)/lock sleep 1h; done" &
     childshellpid=$!
 
     # Wait for lock to be taken (yes hacky)
@@ -129,4 +195,8 @@
     echo "ok die with parent ${die_with_parent_argv}"
 done
 
-echo OK
+printf '%s--dir\0/tmp/hello/world\0' '' > test.args
+$RUN --args 3 test -d /tmp/hello/world 3<test.args
+echo "ok - we can parse arguments from a fd"
+
+echo "ok - End of test"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/bubblewrap-0.1.8/utils.c new/bubblewrap-0.2.0/utils.c
--- old/bubblewrap-0.1.8/utils.c        2017-03-28 16:26:53.000000000 +0200
+++ new/bubblewrap-0.2.0/utils.c        2017-10-09 16:11:41.000000000 +0200
@@ -29,6 +29,8 @@
   va_list args;
   int errsv;
 
+  fprintf (stderr, "bwrap: ");
+
   errsv = errno;
 
   va_start (args, format);
@@ -45,6 +47,8 @@
 {
   va_list args;
 
+  fprintf (stderr, "bwrap: ");
+
   va_start (args, format);
   vfprintf (stderr, format, args);
   va_end (args);


Reply via email to