Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add --overlay and related options #547

Merged
merged 1 commit into from
Oct 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
190 changes: 190 additions & 0 deletions bubblewrap.c
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,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 @@ -122,6 +124,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 @@ -167,6 +173,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 @@ -336,6 +343,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"
rhendric marked this conversation as resolved.
Show resolved Hide resolved
" --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 @@ -1141,6 +1153,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 @@ -1164,6 +1190,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 @@ -1251,6 +1278,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 (ensure_dir (dest, 0755) != 0)
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");
rhendric marked this conversation as resolved.
Show resolved Hide resolved

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 @@ -1514,6 +1582,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 @@ -1556,6 +1625,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 @@ -1567,6 +1638,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 @@ -1658,6 +1731,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];
rhendric marked this conversation as resolved.
Show resolved Hide resolved
}
next_overlay_src_count = 0;
}

static void
parse_args_recurse (int *argcp,
const char ***argvp,
Expand Down Expand Up @@ -1923,6 +2022,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 @@ -2592,6 +2761,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 @@ -2607,6 +2780,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 @@ -2712,6 +2888,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 @@ -3152,6 +3329,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
Loading