diff --git a/cmdline.c b/cmdline.c index a2e9f91..85c77d4 100644 --- a/cmdline.c +++ b/cmdline.c @@ -95,8 +95,11 @@ void cmdlineLogParams(struct nsjconf_t *nsjconf) logYesNo(nsjconf->clone_newuts), logYesNo(nsjconf->apply_sandbox), logYesNo(nsjconf->keep_caps)); struct constchar_t *p; - LIST_FOREACH(p, &nsjconf->bindmountpts, pointers) { - LOG_I("Additional bind mount point: '%s'", p->value); + LIST_FOREACH(p, &nsjconf->robindmountpts, pointers) { + LOG_I("Additional (ro) bind mount point: '%s'", p->value); + } + LIST_FOREACH(p, &nsjconf->rwbindmountpts, pointers) { + LOG_I("Additional (rw) bind mount point: '%s'", p->value); } LIST_FOREACH(p, &nsjconf->tmpfsmountpts, pointers) { LOG_I("Additional tmpfs mount point: '%s'", p->value); @@ -181,7 +184,8 @@ bool cmdlineParse(int argc, char *argv[], struct nsjconf_t * nsjconf) /* *INDENT-OFF* */ LIST_INIT(&nsjconf->pids); - LIST_INIT(&nsjconf->bindmountpts); + LIST_INIT(&nsjconf->robindmountpts); + LIST_INIT(&nsjconf->rwbindmountpts); LIST_INIT(&nsjconf->tmpfsmountpts); const char *user = "nobody"; @@ -228,8 +232,9 @@ bool cmdlineParse(int argc, char *argv[], struct nsjconf_t * nsjconf) {{"disable_sandbox", no_argument, NULL, 0x0501}, "Don't enable the seccomp-bpf sandboxing (default: false)"}, {{"rw", no_argument, NULL, 0x0503}, "Mount / as RW (default: RO)"}, {{"silent", no_argument, NULL, 0x0504}, "Redirect child's fd:0/1/2 to /dev/null (default: false)"}, - {{"bindmount", required_argument, NULL, 'B'}, "List of mountpoints to be mounted --bind inside the container. Can be specified multiple times (default: none)"}, - {{"tmpfsmount", required_argument, NULL, 'T'}, "List of mountpoints to be mounted as RW/tmpfs inside the container. Can be specified multiple times (default: none)"}, + {{"bindmount_ro", required_argument, NULL, 0x0505}, "List of mountpoints to be mounted --bind (ro) inside the container. Can be specified multiple times. Supports 'source' syntax, or 'source:dest'. (default: none)"}, + {{"bindmount", required_argument, NULL, 'B'}, "List of mountpoints to be mounted --bind (rw) inside the container. Can be specified multiple times. Supports 'source' syntax, or 'source:dest'. (default: none)"}, + {{"tmpfsmount", required_argument, NULL, 'T'}, "List of mountpoints to be mounted as RW/tmpfs inside the container. Can be specified multiple times. Supports 'dest' syntax. (default: none)"}, {{"iface", required_argument, NULL, 'I'}, "Interface which will be cloned (MACVTAP) and put inside the subprocess' namespace"}, {{0, 0, 0, 0}, NULL}, }; @@ -350,6 +355,16 @@ bool cmdlineParse(int argc, char *argv[], struct nsjconf_t * nsjconf) case 0x0504: nsjconf->is_silent = true; break; + case 0x0505: + { + struct constchar_t *p = malloc(sizeof(struct constchar_t)); + if (p == NULL) { + PLOG_F("malloc(%zu)", sizeof(struct constchar_t)); + } + p->value = optarg; + LIST_INSERT_HEAD(&nsjconf->robindmountpts, p, pointers); + } + break; case 'B': { struct constchar_t *p = malloc(sizeof(struct constchar_t)); @@ -357,7 +372,7 @@ bool cmdlineParse(int argc, char *argv[], struct nsjconf_t * nsjconf) PLOG_F("malloc(%zu)", sizeof(struct constchar_t)); } p->value = optarg; - LIST_INSERT_HEAD(&nsjconf->bindmountpts, p, pointers); + LIST_INSERT_HEAD(&nsjconf->rwbindmountpts, p, pointers); } break; case 'T': diff --git a/common.h b/common.h index 89e56ce..facbba5 100644 --- a/common.h +++ b/common.h @@ -84,7 +84,8 @@ struct nsjconf_t { gid_t initial_gid; unsigned int max_conns_per_ip; LIST_HEAD(pidslist, pids_t) pids; - LIST_HEAD(bindmountptslist, constchar_t) bindmountpts; + LIST_HEAD(rwbindmountptslist, constchar_t) rwbindmountpts; + LIST_HEAD(robindmountptslist, constchar_t) robindmountpts; LIST_HEAD(tmpfsmountptslist, constchar_t) tmpfsmountpts; }; diff --git a/contain.c b/contain.c index 4a8951f..f5a18a1 100644 --- a/contain.c +++ b/contain.c @@ -174,6 +174,67 @@ bool containPrepareEnv(struct nsjconf_t * nsjconf) return true; } +/* findSpecDestination mutates spec (source:dest) to have a null byte instead + * of ':' in between source and dest, then returns a pointer to the dest + * string. */ +static char *findSpecDestination(char *spec) { + char *dest = spec; + while (*dest != ':' && *dest != '\0') { + dest++; + } + + switch (*dest) { + case ':': + *dest = '\0'; + return dest + 1; + case '\0': + return spec; + default: + // not reached + return spec; + } +} + +static bool bindMount(const char *newrootdir, const char *spec) { + char mount_pt[PATH_MAX]; + bool success = false; + char *source = strdup(spec); + char *dest = findSpecDestination(source); + + snprintf(mount_pt, sizeof(mount_pt), "%s/%s", newrootdir, dest); + if (mkdir(mount_pt, 0700) == -1 && errno != EEXIST) { + PLOG_E("mkdir('%s')", mount_pt); + goto cleanup; + } + LOG_D("Mounting (bind) '%s' on '%s'", source, mount_pt); + if (mount(source, mount_pt, NULL, MS_BIND | MS_REC, NULL) == -1) { + PLOG_E("mount('%s', '%s', MS_BIND|MS_REC)", source, mount_pt); + goto cleanup; + } + success = true; + +cleanup: + free(source); + return success; +} + +static bool remountBindMount(const char *spec, unsigned long flags) { + bool success = false; + char *source = strdup(spec); + char *dest = findSpecDestination(source); + + LOG_D("Remounting (bind|%lu) '%s' on '%s'", flags, dest, dest); + if (mount(dest, dest, NULL, MS_BIND | MS_NOSUID | MS_NODEV | MS_REMOUNT | MS_PRIVATE | flags, NULL) != 0) { + PLOG_E("mount('%s', '%s', MS_BIND|MS_NOSUID|MS_NODEV|MS_REMOUNT|MS_PRIVATE|%lu)", dest, dest, flags); + goto cleanup; + } + success = true; + +cleanup: + free(source); + return success; +} + bool containMountFS(struct nsjconf_t * nsjconf) { const char *destdir = "/tmp"; @@ -184,7 +245,7 @@ bool containMountFS(struct nsjconf_t * nsjconf) char newrootdir[PATH_MAX]; snprintf(newrootdir, sizeof(newrootdir), "%s/%s", destdir, "new_root"); if (mkdir(newrootdir, 0755) == -1) { - PLOG_E("mkdir(/tmp/new_root"); + PLOG_E("mkdir(/tmp/new_root)"); return false; } if (mount(nsjconf->chroot, newrootdir, NULL, MS_BIND | MS_REC, NULL) == -1) { @@ -193,16 +254,13 @@ bool containMountFS(struct nsjconf_t * nsjconf) } struct constchar_t *p; - char mount_pt[PATH_MAX]; - LIST_FOREACH(p, &nsjconf->bindmountpts, pointers) { - snprintf(mount_pt, sizeof(mount_pt), "%s/%s", newrootdir, p->value); - if (mkdir(mount_pt, 0700) == -1 && errno != EEXIST) { - PLOG_E("mkdir('%s')", mount_pt); + LIST_FOREACH(p, &nsjconf->robindmountpts, pointers) { + if (!bindMount(newrootdir, p->value)) { return false; } - LOG_D("Mounting (bind) '%s' on '%s'", p->value, mount_pt); - if (mount(p->value, mount_pt, NULL, MS_BIND | MS_REC, NULL) == -1) { - PLOG_E("mount('%s', '%s', MS_BIND|MS_REC", p->value, mount_pt); + } + LIST_FOREACH(p, &nsjconf->rwbindmountpts, pointers) { + if (!bindMount(newrootdir, p->value)) { return false; } } @@ -236,10 +294,14 @@ bool containMountFS(struct nsjconf_t * nsjconf) PLOG_E("chdir('/')"); return false; } - /* It only makes sense with "--chroot /", so don't worry about erorrs */ + /* It only makes sense with "--chroot /", so don't worry about errors */ umount2(destdir, MNT_DETACH); LIST_FOREACH(p, &nsjconf->tmpfsmountpts, pointers) { + if (strchr(p->value, ':') != NULL) { + PLOG_E("invalid tmpfs mount spec. source:dest format unsupported."); + return false; + } if (mkdir(p->value, 0700) == -1 && errno != EEXIST) { PLOG_E("mkdir('%s'); You probably need to create it in your --chroot ('%s') directory", p->value, nsjconf->chroot); @@ -261,6 +323,18 @@ bool containMountFS(struct nsjconf_t * nsjconf) } } + LIST_FOREACH(p, &nsjconf->robindmountpts, pointers) { + if (!remountBindMount(p->value, MS_RDONLY)) { + return false; + } + } + LIST_FOREACH(p, &nsjconf->rwbindmountpts, pointers) { + if (!remountBindMount(p->value, 0)) { + return false; + } + } + + return true; }