Skip to content

Commit

Permalink
Add --overlay and related options
Browse files Browse the repository at this point in the history
This commit adds --overlay, --tmp-overlay, --ro-overlay, and
--overlay-src options to enable bubblewrap to create overlay mounts.
These options are only permitted when bubblewrap is not installed
setuid.

Resolves: #412
Co-authored-by: William Manley <[email protected]>
Signed-off-by: Ryan Hendrickson <[email protected]>
  • Loading branch information
rhendric and wmanley committed Mar 5, 2023
1 parent da63f2b commit bc8044a
Show file tree
Hide file tree
Showing 7 changed files with 475 additions and 1 deletion.
180 changes: 180 additions & 0 deletions bubblewrap.c
Original file line number Diff line number Diff line change
Expand Up @@ -99,8 +99,10 @@ char *opt_args_data = NULL; /* owned */
int opt_userns_fd = -1;
int opt_userns2_fd = -1;
int opt_pidns_fd = -1;
int opt_tmp_overlay_count = 0;
int next_perms = -1;
size_t next_size_arg = 0;
int next_overlay_src_count = 0;

#define CAP_TO_MASK_0(x) (1L << ((x) & 31))
#define CAP_TO_MASK_1(x) CAP_TO_MASK_0(x - 32)
Expand Down Expand Up @@ -130,6 +132,10 @@ typedef enum {
SETUP_BIND_MOUNT,
SETUP_RO_BIND_MOUNT,
SETUP_DEV_BIND_MOUNT,
SETUP_OVERLAY_MOUNT,
SETUP_TMP_OVERLAY_MOUNT,
SETUP_RO_OVERLAY_MOUNT,
SETUP_OVERLAY_SRC,
SETUP_MOUNT_PROC,
SETUP_MOUNT_DEV,
SETUP_MOUNT_TMPFS,
Expand Down Expand Up @@ -175,6 +181,7 @@ struct _LockFile
enum {
PRIV_SEP_OP_DONE,
PRIV_SEP_OP_BIND_MOUNT,
PRIV_SEP_OP_OVERLAY_MOUNT,
PRIV_SEP_OP_PROC_MOUNT,
PRIV_SEP_OP_TMPFS_MOUNT,
PRIV_SEP_OP_DEVPTS_MOUNT,
Expand Down Expand Up @@ -340,6 +347,11 @@ usage (int ecode, FILE *out)
" --ro-bind SRC DEST Bind mount the host path SRC readonly on DEST\n"
" --ro-bind-try SRC DEST Equal to --ro-bind but ignores non-existent SRC\n"
" --remount-ro DEST Remount DEST as readonly; does not recursively remount\n"
" --overlay-src SRC Read files from SRC in the following overlay\n"
" --overlay RWSRC WORKDIR DEST Mount overlayfs on DEST, with RWSRC as the host path for writes and\n"
" WORKDIR an empty directory on the same filesystem as RWSRC\n"
" --tmp-overlay DEST Mount overlayfs on DEST, with writes going to an invisible tmpfs\n"
" --ro-overlay DEST Mount overlayfs read-only on DEST\n"
" --exec-label LABEL Exec label for the sandbox\n"
" --file-label LABEL File label for temporary sandbox content\n"
" --proc DEST Mount new procfs on DEST\n"
Expand Down Expand Up @@ -1153,6 +1165,12 @@ privileged_op (int privileged_op_socket,
die_with_error ("Can't mount mqueue on %s", arg1);
break;

case PRIV_SEP_OP_OVERLAY_MOUNT:
if (mount ("overlay", arg2, "overlay", MS_MGC_VAL, arg1) != 0)
die_with_error ("Can't make overlay mount on %s with options %s",
arg2, arg1);
break;

case PRIV_SEP_OP_SET_HOSTNAME:
/* This is checked at the start, but lets verify it here in case
something manages to send hacked priv-sep operation requests. */
Expand All @@ -1176,6 +1194,7 @@ setup_newroot (bool unshare_pid,
int privileged_op_socket)
{
SetupOp *op;
int tmp_overlay_idx = 0;

for (op = ops; op != NULL; op = op->next)
{
Expand Down Expand Up @@ -1239,6 +1258,45 @@ setup_newroot (bool unshare_pid,
0, 0, source, dest);
break;

case SETUP_OVERLAY_MOUNT:
case SETUP_RO_OVERLAY_MOUNT:
case SETUP_TMP_OVERLAY_MOUNT:
{
StringBuilder sb = {0};
bool multi_src = FALSE;

if (mkdir (dest, 0755) != 0 && errno != EEXIST)
die_with_error ("Can't mkdir %s", op->dest);

if (op->source != NULL)
{
strappend (&sb, "upperdir=/oldroot");
strappend_escape_for_mount_options (&sb, op->source);
strappend (&sb, ",workdir=/oldroot");
op = op->next;
strappend_escape_for_mount_options (&sb, op->source);
strappend (&sb, ",");
}
else if (op->type == SETUP_TMP_OVERLAY_MOUNT)
strappendf (&sb, "upperdir=/tmp-overlay-upper-%1$d,workdir=/tmp-overlay-work-%1$d,",
tmp_overlay_idx++);

strappend (&sb, "lowerdir=/oldroot");
while (op->next != NULL && op->next->type == SETUP_OVERLAY_SRC)
{
op = op->next;
if (multi_src)
strappend (&sb, ":/oldroot");
strappend_escape_for_mount_options (&sb, op->source);
multi_src = TRUE;
}

privileged_op (privileged_op_socket,
PRIV_SEP_OP_OVERLAY_MOUNT, 0, 0, 0, sb.str, dest);
free (sb.str);
}
break;

case SETUP_REMOUNT_RO_NO_RECURSIVE:
privileged_op (privileged_op_socket,
PRIV_SEP_OP_REMOUNT_RO_NO_RECURSIVE, 0, 0, 0, NULL, dest);
Expand Down Expand Up @@ -1484,6 +1542,7 @@ setup_newroot (bool unshare_pid,
op->dest, NULL);
break;

case SETUP_OVERLAY_SRC: /* handled by SETUP_OVERLAY_MOUNT */
default:
die ("Unexpected type %d", op->type);
}
Expand Down Expand Up @@ -1526,6 +1585,8 @@ resolve_symlinks_in_ops (void)
case SETUP_RO_BIND_MOUNT:
case SETUP_DEV_BIND_MOUNT:
case SETUP_BIND_MOUNT:
case SETUP_OVERLAY_SRC:
case SETUP_OVERLAY_MOUNT:
old_source = op->source;
op->source = realpath (old_source, NULL);
if (op->source == NULL)
Expand All @@ -1537,6 +1598,8 @@ resolve_symlinks_in_ops (void)
}
break;

case SETUP_RO_OVERLAY_MOUNT:
case SETUP_TMP_OVERLAY_MOUNT:
case SETUP_MOUNT_PROC:
case SETUP_MOUNT_DEV:
case SETUP_MOUNT_TMPFS:
Expand Down Expand Up @@ -1628,6 +1691,32 @@ warn_only_last_option (const char *name)
warn ("Only the last %s option will take effect", name);
}

static void
make_setup_overlay_src_ops (const char *const *const argv)
{
/* SETUP_OVERLAY_SRC is unlike other SETUP_* ops in that it exists to hold
* data for SETUP_{,TMP_,RO_}OVERLAY_MOUNT ops, not to be its own operation.
* This lets us reuse existing code paths to handle resolving the realpaths
* of each source, as no other operations involve multiple sources the way
* the *_OVERLAY_MOUNT ops do.
*
* While the --overlay-src arguments are expected to precede the
* --overlay argument, in bottom-to-top order, the SETUP_OVERLAY_SRC ops
* follow their corresponding *_OVERLAY_MOUNT op, in top-to-bottom order
* (the order in which overlayfs will want them). They are handled specially
* in setup_new_root () during the processing of *_OVERLAY_MOUNT.
*/
int i;
SetupOp *op;

for (i = 1; i <= next_overlay_src_count; i++)
{
op = setup_op_new (SETUP_OVERLAY_SRC);
op->source = argv[1 - 2 * i];
}
next_overlay_src_count = 0;
}

static void
parse_args_recurse (int *argcp,
const char ***argvp,
Expand Down Expand Up @@ -1853,6 +1942,76 @@ parse_args_recurse (int *argcp,
argv += 2;
argc -= 2;
}
else if (strcmp (arg, "--overlay-src") == 0)
{
if (is_privileged)
die ("The --overlay-src option is not permitted in setuid mode");

next_overlay_src_count++;

argv += 1;
argc -= 1;
}
else if (strcmp (arg, "--overlay") == 0)
{
SetupOp *workdir_op;

if (is_privileged)
die ("The --overlay option is not permitted in setuid mode");

if (argc < 4)
die ("--overlay takes three arguments");

if (next_overlay_src_count < 1)
die ("--overlay requires at least one --overlay-src");

op = setup_op_new (SETUP_OVERLAY_MOUNT);
op->source = argv[1];
workdir_op = setup_op_new (SETUP_OVERLAY_SRC);
workdir_op->source = argv[2];
op->dest = argv[3];
make_setup_overlay_src_ops (argv);

argv += 3;
argc -= 3;
}
else if (strcmp (arg, "--tmp-overlay") == 0)
{
if (is_privileged)
die ("The --tmp-overlay option is not permitted in setuid mode");

if (argc < 2)
die ("--tmp-overlay takes an argument");

if (next_overlay_src_count < 1)
die ("--tmp-overlay requires at least one --overlay-src");

op = setup_op_new (SETUP_TMP_OVERLAY_MOUNT);
op->dest = argv[1];
make_setup_overlay_src_ops (argv);
opt_tmp_overlay_count++;

argv += 1;
argc -= 1;
}
else if (strcmp (arg, "--ro-overlay") == 0)
{
if (is_privileged)
die ("The --ro-overlay option is not permitted in setuid mode");

if (argc < 2)
die ("--ro-overlay takes an argument");

if (next_overlay_src_count < 2)
die ("--ro-overlay requires at least two --overlay-src");

op = setup_op_new (SETUP_RO_OVERLAY_MOUNT);
op->dest = argv[1];
make_setup_overlay_src_ops (argv);

argv += 1;
argc -= 1;
}
else if (strcmp (arg, "--proc") == 0)
{
if (argc < 2)
Expand Down Expand Up @@ -2522,6 +2681,10 @@ parse_args_recurse (int *argcp,
if (!is_modifier_option(arg) && next_size_arg != 0)
die ("--size must be followed by --tmpfs");

/* Similarly for --overlay-src. */
if (strcmp (arg, "--overlay-src") != 0 && next_overlay_src_count > 0)
die ("--overlay-src must be followed by another --overlay-src or one of --overlay, --tmp-overlay, or --ro-overlay");

argv++;
argc--;
}
Expand All @@ -2537,6 +2700,9 @@ parse_args (int *argcp,
int total_parsed_argc = *argcp;

parse_args_recurse (argcp, argvp, FALSE, &total_parsed_argc);

if (next_overlay_src_count > 0)
die ("--overlay-src must be followed by another --overlay-src or one of --overlay, --tmp-overlay, or --ro-overlay");
}

static void
Expand Down Expand Up @@ -2641,6 +2807,7 @@ main (int argc,
int res UNUSED;
cleanup_free char *args_data UNUSED = NULL;
int intermediate_pids_sockets[2] = {-1, -1};
int i;

/* Handle --version early on before we try to acquire/drop
* any capabilities so it works in a build environment;
Expand Down Expand Up @@ -3081,6 +3248,19 @@ main (int argc,
if (mkdir ("oldroot", 0755))
die_with_error ("Creating oldroot failed");

for (i = 0; i < opt_tmp_overlay_count; i++)
{
char *dirname;
dirname = xasprintf ("tmp-overlay-upper-%d", i);
if (mkdir (dirname, 0755))
die_with_error ("Creating --tmp-overlay upperdir failed");
free (dirname);
dirname = xasprintf ("tmp-overlay-work-%d", i);
if (mkdir (dirname, 0755))
die_with_error ("Creating --tmp-overlay workdir failed");
free (dirname);
}

if (pivot_root (base_path, "oldroot"))
die_with_error ("pivot_root");

Expand Down
65 changes: 65 additions & 0 deletions bwrap.xml
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,71 @@
<term><option>--remount-ro <arg choice="plain">DEST</arg></option></term>
<listitem><para>Remount the path <arg choice="plain">DEST</arg> as readonly. It works only on the specified mount point, without changing any other mount point under the specified path</para></listitem>
</varlistentry>
<varlistentry>
<term><option>--overlay-src <arg choice="plain">SRC</arg></option></term>
<listitem>
<para>
This option does nothing on its own, and must be followed by one of
the other <literal>overlay</literal> options. It specifies a host
path from which files should be read if they aren't present in a
higher layer.
</para>
<para>
This option can be used multiple times to provide multiple sources.
The sources are overlaid from bottom to top: if a given path to be
read exists in more than one source, the file is read from the last
such source specified.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><option>--overlay <arg choice="plain">RWSRC</arg> <arg choice="plain">WORKDIR</arg> <arg choice="plain">DEST</arg></option></term>
</varlistentry>
<varlistentry>
<term><option>--tmp-overlay <arg choice="plain">DEST</arg></option></term>
</varlistentry>
<varlistentry>
<term><option>--ro-overlay <arg choice="plain">DEST</arg></option></term>
<listitem>
<para>
Use overlayfs to mount the host paths specified by
<arg choice="plain">RWSRC</arg> and all immediately preceding
<option>--overlay-src</option> on <arg choice="plain">DEST</arg>.
<arg choice="plain">DEST</arg> will contain the union of all the files
in all the layers.
</para>
<para>
With <arg choice="plain">--overlay</arg> all writes will go to
<arg choice="plain">RWSRC</arg>. Reads will come preferentially from
<arg choice="plain">RWSRC</arg>, and then from any
<option>--overlay-src</option> paths.
<arg choice="plain">WORKDIR</arg> must be an empty directory on the
same filesystem as <arg choice="plain">RWSRC</arg>.
</para>
<para>
With <arg choice="plain">--tmp-overlay</arg> all writes will go to
the tmpfs that hosts the sandbox root, in a location not accessible
from either the host or the child process. Writes will therefore not
be persisted across multiple runs.
</para>
<para>
With <arg choice="plain">--ro-overlay</arg> the filesystem will be
mounted read-only. This option requires at least two
<option>--overlay-src</option> to precede it.
</para>
<para>
None of these options are available in the setuid version of
bubblewrap. Using <arg choice="plain">--ro-overlay</arg> or providing
more than one <option>--overlay-src</option> requires a Linux kernel
version of 4.0 or later.
</para>
<para>
For more information see the Overlay Filesystem documentation in the
Linux kernel at
https://www.kernel.org/doc/Documentation/filesystems/overlayfs.txt
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><option>--proc <arg choice="plain">DEST</arg></option></term>
<listitem><para>Mount procfs on <arg choice="plain">DEST</arg></para></listitem>
Expand Down
4 changes: 4 additions & 0 deletions completions/bash/bwrap
Original file line number Diff line number Diff line change
Expand Up @@ -50,15 +50,19 @@ _bwrap() {
--hostname
--info-fd
--lock-file
--overlay
--overlay-src
--perms
--proc
--remount-ro
--ro-bind
--ro-overlay
--seccomp
--setenv
--size
--symlink
--sync-fd
--tmp-overlay
--uid
--unsetenv
--userns-block-fd
Expand Down
Loading

0 comments on commit bc8044a

Please sign in to comment.