Skip to content

Commit

Permalink
Add --overlay and --ro-overlay command line options
Browse files Browse the repository at this point in the history
These enable bubblewrap to create overlay mounts.  This will be useful for
an ostree-based build system we use where overlayfs ensures that none
of the ostree hard-linked files I checkout get modified.  Currently we
use a maze of bash/unshare/mount/sudo/chroot where bubblewrap will be much
nicer.

This commit contains a bit of string manipulation, which isn't
particularly fun to write in C.  Hopefully I got it right.

I've had to add some additional capabilities because otherwise overlayfs
has some difficulty writing to directories that exist in the lower
filesystem but are not yet in the upper.  It's got something to do with
the `work` directory that overlayfs uses.  overlayfs will create a `work`
directory with 0 permissions.  Ordinarily root would be able to write to
this directory because permissions checks don't apply to it, but that
requires these additional capabilities:

* `CAP_DAC_OVERRIDE`

    * Bypass file read, write, and execute permission checks.  ...

* `CAP_DAC_READ_SEARCH`

    * Bypass file read permission checks and directory read and execute
      permission checks;

* `CAP_FOWNER`
    * Bypass  permission  checks  on  operations that normally require the
      filesystem UID of the process to match the UID of the file ...

(see `man 7 capabilities`)

No tests are written to exercise this new feature.
  • Loading branch information
wmanley committed Feb 6, 2017
1 parent a26b09a commit 638bd65
Show file tree
Hide file tree
Showing 3 changed files with 198 additions and 1 deletion.
162 changes: 161 additions & 1 deletion bubblewrap.c
Expand Up @@ -77,6 +77,8 @@ typedef enum {
SETUP_BIND_MOUNT,
SETUP_RO_BIND_MOUNT,
SETUP_DEV_BIND_MOUNT,
SETUP_OVERLAY_MOUNT,
SETUP_RO_OVERLAY_MOUNT,
SETUP_MOUNT_PROC,
SETUP_MOUNT_DEV,
SETUP_MOUNT_TMPFS,
Expand All @@ -101,6 +103,12 @@ struct _SetupOp
SetupOpType type;
const char *source;
const char *dest;

/* for overlayfs: */
const char *layers;
const char *workdir;
const char *options;

int fd;
SetupOpFlag flags;
SetupOp *next;
Expand All @@ -122,6 +130,7 @@ static LockFile *last_lock_file = NULL;
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 @@ -202,6 +211,11 @@ usage (int ecode, FILE *out)
" --dev-bind SRC DEST Bind mount the host path SRC on DEST, allowing device access\n"
" --ro-bind SRC DEST Bind mount the host path SRC readonly on DEST\n"
" --remount-ro DEST Remount DEST as readonly, it doesn't recursively remount\n"
" --overlay LAYERS DEST WORKDIR Mount overlayfs on DEST. LAYERS is a colon seperated list of\n"
" directories. WORKDIR must be an empty directory on the same\n"
" filesystem as the last layer.\n"
" --overlay-ro LAYERS DEST Mount overlayfs read-only on DEST. LAYERS is a colon seperated list\n"
" of directories\n"
" --exec-label LABEL Exec Label for the sandbox\n"
" --file-label LABEL File label for temporary sandbox content\n"
" --proc DEST Mount procfs on DEST\n"
Expand Down Expand Up @@ -434,7 +448,7 @@ do_init (int event_fd, pid_t initial_pid, struct sock_fprog *seccomp_prog)
}

/* low 32bit caps needed */
#define REQUIRED_CAPS_0 (CAP_TO_MASK (CAP_SYS_ADMIN) | CAP_TO_MASK (CAP_SYS_CHROOT) | CAP_TO_MASK (CAP_NET_ADMIN) | CAP_TO_MASK (CAP_SETUID) | CAP_TO_MASK (CAP_SETGID))
#define REQUIRED_CAPS_0 (CAP_TO_MASK (CAP_SYS_ADMIN) | CAP_TO_MASK (CAP_SYS_CHROOT) | CAP_TO_MASK (CAP_NET_ADMIN) | CAP_TO_MASK (CAP_SETUID) | CAP_TO_MASK (CAP_SETGID) | CAP_TO_MASK (CAP_DAC_OVERRIDE) | CAP_TO_MASK (CAP_DAC_READ_SEARCH) | CAP_TO_MASK (CAP_FOWNER))
/* high 32bit caps needed */
#define REQUIRED_CAPS_1 0

Expand Down Expand Up @@ -778,6 +792,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 @@ -792,6 +812,103 @@ privileged_op (int privileged_op_socket,
}
}

struct _StringBuilder
{
char * str;
size_t size;
size_t offset;
};

static void
strappend(struct _StringBuilder *dest, const char *src)
{
size_t len = strlen(src);
if (dest->offset + len >= dest->size) {
dest->size = (dest->size + len) * 2;
dest->str = realloc(dest->str, dest->size);
if (dest->str == NULL)
die ("Out of memory");
}

strcpy(dest->str + dest->offset, src);
dest->offset += len;
}

static char *
realpathx(const char *path, char *resolved_path)
{
char *rp;
rp = realpath(path, resolved_path);
if (rp == NULL)
die("Failed to resolve path %s", path);
return rp;
}

/*
* "/hello:/goodbye" -> "lowerdir=/oldroot/hello:/oldroot/goodbye"
*/
static char *
ro_overlay_options(const char* layers)
{
struct _StringBuilder sb = {0};
cleanup_free char *layers_mut = strdup(layers);
char buf[PATH_MAX];
char * token;
int first = 1;

strappend(&sb, "lowerdir=");

token = strtok(layers_mut, ":");
while (token != NULL) {
if (!first)
strappend(&sb, ":");
strappend(&sb, "/oldroot");
/* Resolve absolute symlinks before we remount under /oldroot: */
strappend(&sb, realpathx (token, buf));

token = strtok(NULL, ":");
first = 0;
}
return sb.str;
}

/*
* "/hello:/goodbye", "/moo" -> "lowerdir=/oldroot/hello,upperdir=/oldroot/goodbye,workdir=/oldroot/moo"
* "/hello:/3:/goodbye", "/moo" -> "lowerdir=/oldroot/hello:/oldroot/3,upperdir=/oldroot/goodbye,workdir=/oldroot/moo"
*/
static char *
rw_overlay_options(const char* layers, const char* workdir)
{
struct _StringBuilder sb = {0};
cleanup_free char *layers_mut = strdup(layers);
char buf[PATH_MAX];
char *path, *next;
int first = 1;

strappend(&sb, "lowerdir=");

next = strtok(layers_mut, ":");
while (1) {
path = next;
next = strtok(NULL, ":");
if (next == NULL)
break;

if (!first)
strappend(&sb, ":");
strappend(&sb, "/oldroot");
/* Resolve absolute symlinks before we remount under /oldroot: */
strappend(&sb, realpathx (path, buf));

first = 0;
}
strappend(&sb, ",upperdir=/oldroot");
strappend(&sb, realpathx (path, buf));
strappend(&sb, ",workdir=/oldroot");
strappend(&sb, realpathx (workdir, buf));
return sb.str;
}

/* This is run unprivileged in the child namespace but can request
* some privileged operations (also in the child namespace) via the
* privileged_op_socket.
Expand Down Expand Up @@ -846,6 +963,18 @@ setup_newroot (bool unshare_pid,
source, dest);
break;

case SETUP_OVERLAY_MOUNT:
case SETUP_RO_OVERLAY_MOUNT:
{
cleanup_free char *options = NULL;
if (mkdir (dest, 0755) != 0 && errno != EEXIST)
die_with_error ("Can't mkdir %s", op->dest);

privileged_op (privileged_op_socket,
PRIV_SEP_OP_OVERLAY_MOUNT, 0, op->options, dest);
}
break;

case SETUP_REMOUNT_RO_NO_RECURSIVE:
privileged_op (privileged_op_socket,
PRIV_SEP_OP_REMOUNT_RO_NO_RECURSIVE, BIND_READONLY, NULL, dest);
Expand Down Expand Up @@ -1063,6 +1192,12 @@ resolve_symlinks_in_ops (void)
if (op->source == NULL)
die_with_error ("Can't find source path %s", old_source);
break;
case SETUP_RO_OVERLAY_MOUNT:
op->options = ro_overlay_options(op->layers);
break;
case SETUP_OVERLAY_MOUNT:
op->options = rw_overlay_options(op->layers, op->workdir);
break;
default:
break;
}
Expand Down Expand Up @@ -1315,6 +1450,31 @@ parse_args_recurse (int *argcp,
op->source = argv[1];
op->dest = argv[2];

argv += 2;
argc -= 2;
}
else if (strcmp (arg, "--overlay") == 0)
{
if (argc < 4)
die ("--overlay takes three arguments");

op = setup_op_new (SETUP_OVERLAY_MOUNT);
op->layers = argv[1];
op->dest = argv[2];
op->workdir = argv[3];

argv += 3;
argc -= 3;
}
else if (strcmp (arg, "--ro-overlay") == 0)
{
if (argc < 3)
die ("--ro-overlay takes two arguments");

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

argv += 2;
argc -= 2;
}
Expand Down
35 changes: 35 additions & 0 deletions bwrap.xml
Expand Up @@ -195,6 +195,41 @@
<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 <arg choice="plain">LAYERS</arg> <arg choice="plain">DEST</arg> <arg choice="plain">WORKDIR</arg></option></term>
</varlistentry>
<varlistentry>
<term><option>--ro-overlay <arg choice="plain">SRC</arg> <arg choice="plain">DEST</arg></option></term>
<listitem>
<para>Use overlayfs to bind mount the host paths
<arg choice="plain">LAYERS</arg> on <arg choice="plain">DEST</arg>.
<arg choice="plain">LAYERS</arg> is a colon seperated list of paths.
<arg choice="plain">DEST</arg> will contain the union of all the files
in all the <arg choice="plain">LAYERS</arg>. The paths listed in
LAYERS may not contain a comma (,) or a colon (:).
</para>
<para>
With <arg choice="plain">--overlay</arg> all writes will go to the
top layer which is the last layer in the list.
<arg choice="plain">WORKDIR</arg> must be an empty directory on the
same filesystem as the top layer.
</para>
<para>
With <arg choice="plain">--ro-overlay</arg> the filesystem will be
mounted read-only so a <arg choice="plain">WORKDIR</arg> is not needed
and shouldn't be provided.
</para>
<para>
Using <arg choice="plain">--ro-overlay</arg> or providing more than
one layer 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
2 changes: 2 additions & 0 deletions completions/bash/bwrap
Expand Up @@ -25,6 +25,8 @@ _bwrap() {
--args
--bind
--bind-data
--overlay
--ro-overlay
--block-fd
--chdir
--dev
Expand Down

0 comments on commit 638bd65

Please sign in to comment.