From e9f87d6c6ca1cc20dbaa3421501c33a95de9ed48 Mon Sep 17 00:00:00 2001 From: William Manley Date: Thu, 26 Jan 2017 03:35:02 +0000 Subject: [PATCH 1/2] Add --overlay and --ro-overlay command line options 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. No tests are written to exercise this new feature. --- bubblewrap.c | 150 +++++++++++++++++++++++++++++++++++++++++ bwrap.xml | 8 +++ completions/bash/bwrap | 2 + 3 files changed, 160 insertions(+) diff --git a/bubblewrap.c b/bubblewrap.c index 409d94c4..5d31ff5c 100644 --- a/bubblewrap.c +++ b/bubblewrap.c @@ -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, @@ -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; @@ -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, @@ -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" @@ -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. */ @@ -792,6 +812,93 @@ 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; +} + +/* + * "/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, realpath (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, realpath (path, buf)); + + first = 0; + } + strappend(&sb, ",upperdir=/oldroot"); + strappend(&sb, realpath (path, buf)); + strappend(&sb, ",workdir=/oldroot"); + strappend(&sb, realpath (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. @@ -846,6 +953,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); @@ -1063,6 +1182,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; } @@ -1315,6 +1440,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; } diff --git a/bwrap.xml b/bwrap.xml index b7a5c41c..4b727c72 100644 --- a/bwrap.xml +++ b/bwrap.xml @@ -195,6 +195,14 @@ Remount the path DEST as readonly. It works only on the specified mount point, without changing any other mount point under the specified path + + + Use overlayfs to bind mount the host paths LAYERS on DEST. LAYERS is a colon seperated list of paths. DEST will contain the union of all the files in all the LAYERS. All writes will go to the top layer which is the last layer in the list. These paths may not contain a comma (,) or a colon (:). WORKDIR must be an empty directory on the same filesystem as the top layer. + + + + Use overlayfs to bind mount the host paths LAYERS on DEST. LAYERS is a colon seperated list of paths. DEST will contain the union of all the files in all the LAYERS. The resulting directory will be read-only. + Mount procfs on DEST diff --git a/completions/bash/bwrap b/completions/bash/bwrap index 34780ba3..e669fd10 100644 --- a/completions/bash/bwrap +++ b/completions/bash/bwrap @@ -25,6 +25,8 @@ _bwrap() { --args --bind --bind-data + --overlay + --ro-overlay --block-fd --chdir --dev From 441d8e08863f12b6bfa6d19c3d1c7fc2d3892b41 Mon Sep 17 00:00:00 2001 From: William Manley Date: Sat, 28 Jan 2017 15:35:47 +0000 Subject: [PATCH 2/2] fixup! Add --overlay and --ro-overlay command line options --- bwrap.xml | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/bwrap.xml b/bwrap.xml index 4b727c72..6e48d65b 100644 --- a/bwrap.xml +++ b/bwrap.xml @@ -197,11 +197,38 @@ - Use overlayfs to bind mount the host paths LAYERS on DEST. LAYERS is a colon seperated list of paths. DEST will contain the union of all the files in all the LAYERS. All writes will go to the top layer which is the last layer in the list. These paths may not contain a comma (,) or a colon (:). WORKDIR must be an empty directory on the same filesystem as the top layer. - Use overlayfs to bind mount the host paths LAYERS on DEST. LAYERS is a colon seperated list of paths. DEST will contain the union of all the files in all the LAYERS. The resulting directory will be read-only. + + Use overlayfs to bind mount the host paths + LAYERS on DEST. + LAYERS is a colon seperated list of paths. + DEST will contain the union of all the files + in all the LAYERS. The paths listed in + LAYERS may not contain a comma (,) or a colon (:). + + + With --overlay all writes will go to the + top layer which is the last layer in the list. + WORKDIR must be an empty directory on the + same filesystem as the top layer. + + + With --ro-overlay the filesystem will be + mounted read-only so a WORKDIR is not needed + and shouldn't be provided. + + + Using --ro-overlay or providing more than + one layer requires a Linux kernel version of 4.0 or later. + + + For more information see the Overlay Filesystem documentation in the + Linux kernel at + https://www.kernel.org/doc/Documentation/filesystems/overlayfs.txt + +