Skip to content
Open
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
81 changes: 68 additions & 13 deletions bubblewrap.c
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,8 @@ 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;
static char **next_overlay_srcs = NULL;
static char *next_overlay_opt = NULL;
static bool opt_not_a_security_boundary = false;

#define CAP_TO_MASK_0(x) (1L << ((x) & 31))
Expand Down Expand Up @@ -165,6 +167,7 @@ struct _SetupOp
SetupOpFlag flags;
int perms;
size_t size; /* number of bytes, zero means unset/default */
char *overlay_opt; /* extra overlayfs mount options, owned, NULL if unset */
SetupOp *next;
};

Expand Down Expand Up @@ -329,6 +332,8 @@ usage (int ecode, FILE *out)
" --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-opt OPT Pass extra overlayfs mount options (e.g. index=off,xino=off) to the\n"
" next --overlay/--tmp-overlay/--ro-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"
Expand Down Expand Up @@ -1317,6 +1322,8 @@ setup_newroot (bool unshare_pid)
bool multi_src = false;
cleanup_fdset FdSet fds = {0};
cleanup_free char *dest_path = fd_to_proc_path (dest_fd);
/* Keep the mount op handle; `op` is advanced through the workdir/src ops below. */
SetupOp *mount_op = op;

if (op->source != NULL)
{
Expand Down Expand Up @@ -1350,16 +1357,22 @@ setup_newroot (bool unshare_pid)

strappend (&sb, ",userxattr");

if (mount_op->overlay_opt != NULL)
{
strappend (&sb, ",");
strappend (&sb, mount_op->overlay_opt);
}

if (mount ("overlay", dest_path, "overlay", MS_MGC_VAL | MS_NOSUID | MS_NODEV, sb.str) != 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",
op->dest, sb.str);
mount_op->dest, sb.str);
die_with_mount_error ("Can't make overlay mount on %s with options %s",
op->dest, sb.str);
mount_op->dest, sb.str);
}

free (sb.str);
Expand Down Expand Up @@ -1723,28 +1736,40 @@ path_argument (const char *option,
}

static void
make_setup_overlay_src_ops (const char *const *const argv)
make_setup_overlay_src_ops (SetupOp *mount_op)
{
/* 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
* 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.
*
* The source paths are stored in the next_overlay_srcs pending array at
* parse time. They can no longer be re-derived from fixed argv positions,
* because --overlay-opt may appear between the --overlay-src arguments and
* the overlay op. The pending --overlay-opt, if any, is moved onto the
* mount op.
*/
int i;
SetupOp *op;

for (i = 1; i <= next_overlay_src_count; i++)
mount_op->overlay_opt = next_overlay_opt;
next_overlay_opt = NULL;

for (i = next_overlay_src_count - 1; i >= 0; i--)
{
op = setup_op_new (SETUP_OVERLAY_SRC);
op->source = path_argument ("--overlay-src", argv[1 - 2 * i]);
op->source = next_overlay_srcs[i];
}

free (next_overlay_srcs);
next_overlay_srcs = NULL;
next_overlay_src_count = 0;
}

Expand Down Expand Up @@ -2015,7 +2040,32 @@ parse_args_recurse (int *argcp,
}
else if (strcmp (arg, "--overlay-src") == 0)
{
next_overlay_src_count++;
next_overlay_srcs = realloc (next_overlay_srcs,
(size_t) (next_overlay_src_count + 1) * sizeof (char *));
if (next_overlay_srcs == NULL)
die ("Out of memory");
next_overlay_srcs[next_overlay_src_count++] =
path_argument ("--overlay-src", argv[1]);

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

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

opt = argv[1];
if (next_overlay_opt == NULL)
next_overlay_opt = xstrdup (opt);
else
{
char *joined = xasprintf ("%s,%s", next_overlay_opt, opt);
free (next_overlay_opt);
next_overlay_opt = joined;
}

argv += 1;
argc -= 1;
Expand All @@ -2035,7 +2085,7 @@ parse_args_recurse (int *argcp,
workdir_op = setup_op_new (SETUP_OVERLAY_SRC);
workdir_op->source = path_argument (arg, argv[2]);
op->dest = path_argument (arg, argv[3]);
make_setup_overlay_src_ops (argv);
make_setup_overlay_src_ops (op);

argv += 3;
argc -= 3;
Expand All @@ -2050,7 +2100,7 @@ parse_args_recurse (int *argcp,

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

argv += 1;
Expand All @@ -2066,7 +2116,7 @@ parse_args_recurse (int *argcp,

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

argv += 1;
argc -= 1;
Expand Down Expand Up @@ -2752,8 +2802,10 @@ parse_args_recurse (int *argcp,
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");
if (strcmp (arg, "--overlay-src") != 0 &&
strcmp (arg, "--overlay-opt") != 0 &&
next_overlay_src_count > 0)
die ("--overlay-src must be followed by --overlay, --tmp-overlay, or --ro-overlay");

argv++;
argc--;
Expand All @@ -2772,7 +2824,10 @@ parse_args (int *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");
die ("--overlay-src must be followed by --overlay, --tmp-overlay, or --ro-overlay");

if (next_overlay_opt != NULL)
die ("--overlay-opt must be followed by --overlay, --tmp-overlay, or --ro-overlay");
}

static void
Expand Down
18 changes: 18 additions & 0 deletions bwrap.xml
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,24 @@
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><option>--overlay-opt <arg choice="plain">OPT</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 passes extra mount
options to the kernel's <literal>overlayfs</literal> for that
mount, for example <literal>index=off,xino=off</literal>.
</para>
<para>
This option can be used multiple times, in which case the values are
joined with a comma. It may be given before or after the
<option>--overlay-src</option> arguments, but must precede the
<option>--overlay</option>, <option>--tmp-overlay</option> or
<option>--ro-overlay</option> option it applies to.
</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>
Expand Down
1 change: 1 addition & 0 deletions completions/bash/bwrap
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ _bwrap() {
--info-fd
--lock-file
--overlay
--overlay-opt
--overlay-src
--perms
--proc
Expand Down
1 change: 1 addition & 0 deletions completions/zsh/_bwrap
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ _bwrap_args=(
'--lock-file[Take a lock on DEST while sandbox is running]:lock file:_files'
'--mqueue[Mount new mqueue on DEST]:mount point for mqueue:_files -/'
'--new-session[Create a new terminal session]'
'--overlay-opt[Pass extra overlayfs mount options (e.g. index=off,xino=off) to the next overlay option]:overlayfs options:'
'--perms[Set permissions for next action argument]: :_guard "[0-7]#" "permissions in octal": :->after_perms'
'--pidns[Use this user namespace (as parent namespace if using --unshare-pid)]: :'
'--proc[Mount new procfs on DEST]:mount point for procfs:_files -/'
Expand Down
45 changes: 45 additions & 0 deletions tests/test-sandbox.py
Original file line number Diff line number Diff line change
Expand Up @@ -531,6 +531,51 @@ def test_ro_overlay(self):
self.assertEqual(result.returncode, 0)
self.assertEqual(result.stdout, b'child content')

# ------ overlay-opt ------

def test_tmp_overlay_opt_after_src(self):
result = run_bwrap('--overlay-src', self.src.dir,
'--overlay-opt', 'index=off,xino=off',
'--tmp-overlay', '/tmp/ov',
'cat', '/tmp/ov/child')
if result.returncode != 0 and b'overlay' in result.stderr.lower():
self.skipTest('overlayfs not available')
self.assertEqual(result.returncode, 0)
self.assertEqual(result.stdout, b'child content')

def test_tmp_overlay_opt_before_src(self):
result = run_bwrap('--overlay-opt', 'index=off,xino=off',
'--overlay-src', self.src.dir,
'--tmp-overlay', '/tmp/ov',
'cat', '/tmp/ov/child')
if result.returncode != 0 and b'overlay' in result.stderr.lower():
self.skipTest('overlayfs not available')
self.assertEqual(result.returncode, 0)
self.assertEqual(result.stdout, b'child content')

def test_tmp_overlay_opt_multiple_comma_joined(self):
result = run_bwrap('--overlay-opt', 'index=off',
'--overlay-opt', 'xino=off',
'--overlay-src', self.src.dir,
'--tmp-overlay', '/tmp/ov',
'cat', '/tmp/ov/child')
if result.returncode != 0 and b'overlay' in result.stderr.lower():
self.skipTest('overlayfs not available')
self.assertEqual(result.returncode, 0)
self.assertEqual(result.stdout, b'child content')

def test_overlay_opt_dangling(self):
result = run_bwrap('--overlay-opt', 'index=off', 'true')
self.assertBwrapFailed(result)
self.assertInStderr(b'--overlay-opt must be followed', result)

def test_overlay_src_not_consumed(self):
result = run_bwrap('--overlay-src', self.src.dir,
'--overlay-opt', 'index=off',
'--ro-bind', self.src.file, '/tmp/f', 'true')
self.assertBwrapFailed(result)
self.assertInStderr(b'--overlay-src must be followed', result)

# ------ Edge cases: source path with symlinks ------

def test_bind_source_absolute_symlink_in_path(self):
Expand Down