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
9 changes: 9 additions & 0 deletions bind-mount.c
Original file line number Diff line number Diff line change
Expand Up @@ -476,6 +476,15 @@ bind_mount (int proc_fd,
be safe to ignore because its not something the user can access. */
if (errno != EACCES)
{
/* And if we don't need a security boundary, we can also
* ignore other remount errors for submounts. */
if (options & BIND_FAIL_OPEN)
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I verified that having the check under the for loop that start with i = 1 seem to be enough:

  if (recursive)
    {
      for (i = 1; mount_tab[i].mountpoint != NULL; i++)
        {
           ...

This seems to cover also host mount points like /hdd directly under /, because even when --bind / / is used mount_tab[0].mountpoint will be /newroot, and so the code will handle /newroot/hdd

And we can assume that /newroot is not on an automount?

So we shouldn't nee to add the check also for mount_tab[0].mountpoint.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And we can assume that /newroot is not on an automount?

Normally, yes: it's on a tmpfs, look for comment /* Create a tmpfs which we will use as / in the namespace */.

If we're mounting an automount onto the root (like --bind /mnt/am / where /mnt/am is an automount) then I think mount_tab[0].mountpoint might be the automounter? But in that case, I think mount(2) would trigger the automount, mount the underlying filesystem, and either succeed or fail as appropriate. Please could you confirm that?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's reasonable to say that if /mnt/bad is a broken automount, then bwrap --not-a-security-boundary ... --bind /mnt/bad $DEST ... should still fail: even though we're not imposing a security boundary, we still asked to mount /mnt/bad onto $DEST, and if we're unable to do so, we should fail.

Conversely, if /mnt/bad is a broken automount and we mount /mnt onto $DEST successfully, it's a lot more reasonable to succeed even though were unable to remount /mnt/bad as nodev.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, I confirm that mount seems to trigger access to the automount in case it's /newroot, and its succeeds or fail regardless of -not-a-security-boundary:

$ ./_build/bwrap --not-a-security-boundary --bind /mnt/bad / true
bwrap: Can't bind mount /oldroot/mnt/bad on /newroot/: Unable to mount source on destination: No such device
$ ./_build/bwrap --bind /mnt/bad / true
bwrap: Can't bind mount /oldroot/mnt/bad on /newroot/: Unable to mount source on destination: No such device

and

$ ./_build/bwrap --not-a-security-boundary --bind /mnt/good / true
bwrap: execvp true: No such file or directory
$ ./_build/bwrap --bind /mnt/good / true
bwrap: execvp true: No such file or directory

The results about /mnt/good tell us that bwrap is able to mount the filesystem, but it just happens to be an empty.

{
warn ("Can't remount %s submount (%s), ignoring error",
mount_tab[i].mountpoint, strerror (errno));
continue;
}

if (failing_path != NULL)
*failing_path = xstrdup (mount_tab[i].mountpoint);

Expand Down
1 change: 1 addition & 0 deletions bind-mount.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ typedef enum {
BIND_READONLY = (1 << 0),
BIND_DEVICES = (1 << 2),
BIND_RECURSIVE = (1 << 3),
BIND_FAIL_OPEN = (1 << 4),
} bind_option_t;

typedef enum
Expand Down
22 changes: 19 additions & 3 deletions bubblewrap.c
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ 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 bool opt_not_a_security_boundary = false;

#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 @@ -350,6 +351,8 @@ usage (int ecode, FILE *out)
" --perms OCTAL Set permissions of next argument (--bind-data, --file, etc.)\n"
" --size BYTES Set size of next argument (only for --tmpfs)\n"
" --chmod OCTAL PATH Change permissions of PATH (must already exist)\n"
" --not-a-security-boundary Do not fail hard when some sandbox setup steps fail;\n"
" use only when the sandbox is not a security boundary\n"
);
exit (ecode);
}
Expand Down Expand Up @@ -1003,9 +1006,18 @@ setup_newroot (bool unshare_pid)
else if (ensure_file (dest, 0444) != 0)
die_with_error ("Can't create file at %s", op->dest);

setup_op_bind_mount ((op->type == SETUP_RO_BIND_MOUNT ? BIND_READONLY : 0) |
(op->type == SETUP_DEV_BIND_MOUNT ? BIND_DEVICES : 0),
source, dest);
bind_option_t bind_flags = 0;

if (opt_not_a_security_boundary)
bind_flags |= BIND_FAIL_OPEN;

if (op->type == SETUP_RO_BIND_MOUNT)
bind_flags |= BIND_READONLY;

if (op->type == SETUP_DEV_BIND_MOUNT)
bind_flags |= BIND_DEVICES;

setup_op_bind_mount (bind_flags, source, dest);

if (op->fd >= 0)
{
Expand Down Expand Up @@ -2427,6 +2439,10 @@ parse_args_recurse (int *argcp,
argv += 2;
argc -= 2;
}
else if (strcmp (arg, "--not-a-security-boundary") == 0)
{
opt_not_a_security_boundary = true;
}
else if (strcmp (arg, "--") == 0)
{
argv += 1;
Expand Down
32 changes: 32 additions & 0 deletions bwrap.xml
Original file line number Diff line number Diff line change
Expand Up @@ -614,6 +614,38 @@
command line. Please be careful to the order they are specified.
</para></listitem>
</varlistentry>
<varlistentry>
<term><option>--not-a-security-boundary</option></term>
<listitem>
<para>
Declare that this invocation of <command>bwrap</command>
is not intended to create a security boundary
between the sandbox and the host system.
When this option is given,
certain non-fatal sandbox setup failures
(such as a subdirectory remount failing
because an automounter did not respond in time)
will produce a warning and will be skipped,
rather than causing <command>bwrap</command> to exit with an error.
Comment thread
smcv marked this conversation as resolved.
In future releases of bubblewrap
the effect of this option might be extended
to make other sandbox setup operations non-fatal
</para>
<para>
This option is intended for callers
such as <application>xdg-dbus-proxy</application> or Steam
that use <command>bwrap</command>
to adjust the filesystem layout for a process,
but do not rely on it to create a security boundary.
</para>
<para>
Other operations that are fundamental to establishing the sandbox
(creating namespaces, <function>pivot_root</function>,
dropping capabilities)
will still cause a hard failure regardless of this option.
</para>
</listitem>
</varlistentry>
</variablelist>
</refsect1>

Expand Down
9 changes: 9 additions & 0 deletions tests/test-run.sh
Original file line number Diff line number Diff line change
Expand Up @@ -689,4 +689,13 @@ else
fi
fi

# Smoke-test --not-a-security-boundary
#
# Setting up an unavailable automount and triggering the right conditions is
# complicated to do here, but we can at least check that the option is there,
# and that it stays there.

$RUN --not-a-security-boundary true
ok "Accepts --not-a-security-boundary"

done_testing
Loading