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 Oct 3, 2024
1 parent 2a55242 commit b927140
Show file tree
Hide file tree
Showing 7 changed files with 537 additions and 0 deletions.
190 changes: 190 additions & 0 deletions bubblewrap.c
Original file line number Diff line number Diff line change
Expand Up @@ -100,8 +100,10 @@ static char *opt_args_data = NULL; /* owned */
static int opt_userns_fd = -1;
static int opt_userns2_fd = -1;
static int opt_pidns_fd = -1;
static int opt_tmp_overlay_count = 0;
static int next_perms = -1;
static size_t next_size_arg = 0;
static 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 @@ -131,6 +133,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 @@ -176,6 +182,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 @@ -345,6 +352,11 @@ usage (int ecode, FILE *out)
" --bind-fd FD DEST Bind open directory or path fd on DEST\n"
" --ro-bind-fd FD DEST Bind open directory or path fd read-only on DEST\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 @@ -1150,6 +1162,20 @@ privileged_op (int privileged_op_socket,
die_with_mount_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)
{
/* The standard message for ELOOP, "Too many levels of symbolic
* links", is not helpful here. */
if (errno == ELOOP)
die ("Can't make overlay mount on %s with options %s: "
"Overlay directories may not overlap",
arg2, arg1);
die_with_mount_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 @@ -1173,6 +1199,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 @@ -1260,6 +1287,47 @@ setup_newroot (bool unshare_pid,

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;
}

strappend (&sb, ",userxattr");

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 @@ -1523,6 +1591,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 @@ -1565,6 +1634,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 @@ -1576,6 +1647,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 @@ -1667,6 +1740,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 (directly) 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 @@ -1932,6 +2031,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 @@ -2601,6 +2770,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 @@ -2616,6 +2789,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 @@ -2721,6 +2897,7 @@ main (int argc,
cleanup_free char *args_data UNUSED = NULL;
int intermediate_pids_sockets[2] = {-1, -1};
const char *exec_path = NULL;
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 @@ -3161,6 +3338,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
80 changes: 80 additions & 0 deletions bwrap.xml
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,86 @@
<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 in the order given, with the first source on
the command line at the bottom of the stack: if a given path to be
read exists in more than one source, the file is read from the last
such source specified.
</para>
<para>
(For readers familiar with overlayfs, note that this is the
reverse of the order used by the kernel's <literal>lowerdir</literal>
mount option.)
</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>, and is used
internally by the kernel.
</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>
Due to limitations of overlayfs, no host directory given via
<arg choice="plain">--overlay-src</arg> or
<arg choice="plain">--overlay</arg> may be an ancestor of another,
after resolving symlinks. Depending on version, the Linux kernel may
or may not enforce this, but if not then overlayfs's behavior is
undefined.
</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
Loading

0 comments on commit b927140

Please sign in to comment.