summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGiuseppe Scrivano <gscrivan@redhat.com>2016-09-23 16:09:30 +0200
committerAtomic Bot <atomic-devel@projectatomic.io>2017-06-29 23:02:31 +0000
commit71660f41016bf67b3669c921c31235747a78c976 (patch)
tree6736a7b732a95caa95d869191c22c20acbf6f457
parenta4709b6547caf438e41cb478b0b9faded7e4b941 (diff)
downloadbubblewrap-71660f41016bf67b3669c921c31235747a78c976.tar.gz
bubblewrap: add --cap-add and --cap-drop
When using namespaces, permit to leave some capabilities in the sandbox. This can be helpful to run a system instance of systemd. Signed-off-by: Giuseppe Scrivano <gscrivan@redhat.com> Closes: #101 Approved by: alexlarsson
-rw-r--r--bubblewrap.c128
-rw-r--r--bwrap.xml19
-rw-r--r--completions/bash/bwrap2
-rw-r--r--configure.ac6
4 files changed, 146 insertions, 9 deletions
diff --git a/bubblewrap.c b/bubblewrap.c
index ed94923..0a5f25b 100644
--- a/bubblewrap.c
+++ b/bubblewrap.c
@@ -75,6 +75,9 @@ int opt_info_fd = -1;
int opt_seccomp_fd = -1;
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,
SETUP_RO_BIND_MOUNT,
@@ -221,6 +224,8 @@ usage (int ecode, FILE *out)
" --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);
}
@@ -450,8 +455,13 @@ do_init (int event_fd, pid_t initial_pid, struct sock_fprog *seccomp_prog)
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)
+
+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
@@ -494,8 +504,12 @@ has_caps (void)
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;
@@ -506,14 +520,56 @@ drop_cap_bounding_set (void)
* 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
@@ -563,7 +619,7 @@ acquire_privs (void)
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 ();
+ drop_cap_bounding_set (TRUE);
/* Keep only the required capabilities for setup */
set_required_caps ();
@@ -585,7 +641,7 @@ 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)
- drop_cap_bounding_set ();
+ drop_cap_bounding_set (FALSE);
if (!is_privileged)
return;
@@ -1658,6 +1714,54 @@ parse_args_recurse (int *argcp,
{
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");
+
+ 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");
+
+ 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);
@@ -1764,6 +1868,9 @@ main (int argc,
parse_args (&argc, &argv);
+ if ((requested_caps[0] || requested_caps[1]) && is_privileged)
+ die ("--cap-add in setuid mode can be used only by root");
+
/* 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)
@@ -2115,7 +2222,7 @@ main (int argc,
if (chdir ("/") != 0)
die_with_error ("chdir /");
- /* All privileged ops are done now, so drop it */
+ /* All privileged ops are done now, so drop caps we don't need */
drop_privs ();
if (opt_block_fd != -1)
@@ -2227,6 +2334,9 @@ main (int argc,
/* 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 &&
diff --git a/bwrap.xml b/bwrap.xml
index e16d259..7596892 100644
--- a/bwrap.xml
+++ b/bwrap.xml
@@ -295,6 +295,25 @@
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 --git a/completions/bash/bwrap b/completions/bash/bwrap
index 6378164..04d0e2c 100644
--- a/completions/bash/bwrap
+++ b/completions/bash/bwrap
@@ -50,6 +50,8 @@ _bwrap() {
--seccomp
--symlink
--die-with-parent
+ --cap-add
+ --cap-drop
"
if [[ "$cur" == -* ]]; then
diff --git a/configure.ac b/configure.ac
index 2203b22..b96b5e2 100644
--- a/configure.ac
+++ b/configure.ac
@@ -87,6 +87,12 @@ CC_CHECK_FLAGS_APPEND([WARN_CFLAGS], [CFLAGS], [\
])
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]),