From 9ca3a2b64014a8c9caf7a2cceec794241fdf77df Mon Sep 17 00:00:00 2001 From: Evgeny Zinoviev Date: Sat, 26 Dec 2020 21:59:10 +0300 Subject: 1.3: added -d option --- README.md | 15 ++++++- config.h | 3 +- utils.c | 13 ++++++ utils.h | 2 + voidnsrun.c | 143 +++++++++++++++++++++++++++++++++++++++++++++++++++--------- 5 files changed, 153 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index 4a85b9e..560103d 100644 --- a/README.md +++ b/README.md @@ -80,6 +80,7 @@ Options: VOIDNSRUN_DIR environment variable is used. -m : Add bind mount. You can add up to 50 paths. -u : Add undo bind mount. You can add up to 50 paths. + -d : Add /usr subdirectory bind mount. -U : Path to voidnsundo. When this option is not present, VOIDNSUNDO_BIN environment variable is used. -i: Don't treat missing source or target for added mounts as error. @@ -99,6 +100,12 @@ launching `xbps-install`, `xbps-remove` or `xbps-reconfigure`and using To bind something else, use the `-m` option. You can add up to 50 binds as of version 1.2. +To bind a subdirectory from the host `/usr`, use the `-d` option (available +since version 1.3). For example, instead of installing fonts into the container +and therefore duplicating them and wasting your disk space, you can bind-mount +`/usr/share/fonts` from the host. The rest of `/usr/` will be from the glibc +container. + There's also the `-u` option. It adds bind mounts of the **voidnsundo** binary inside the namespace. See more about this below in the **voidnsundo** bind mode section. Just like with the `-m` option, you can add up to 50 binds as of version @@ -251,8 +258,8 @@ A: If you installed fonts on your main system, applications that run in the moun namespace can't see them because of custom `/usr` directory. You need to install them again into the container directory. -Some workaround to bind-mount `/usr/share/fonts` from the root system to the -namespace may be introduced in future, if Linux will allow such hacks. +Since 1.3, it's possible to bind-mount `/usr/share/fonts` or other directorires +from the host to the mount namespace. Use the `-d` option for that. ## Security @@ -269,6 +276,10 @@ promise to pay $25 in Bitcoin. Contact me if you find something. ## Changelog +#### 1.3 + +- Added the `-d` option to bind mount subdirectories from the host `/usr`. + #### 1.2.1 - Minor code fixes, nothing serious. diff --git a/config.h b/config.h index b0a8c87..f339860 100644 --- a/config.h +++ b/config.h @@ -1,13 +1,14 @@ #ifndef VOIDNSRUN_CONFIG_H #define VOIDNSRUN_CONFIG_H -#define PROG_VERSION "1.2.1" +#define PROG_VERSION "1.3" #define USER_LISTS_MAX 50 #define CONTAINER_DIR_VAR "VOIDNSRUN_DIR" #define UNDO_BIN_VAR "VOIDNSUNDO_BIN" #define VOIDNSUNDO_NAME "voidnsundo" +#define OLDROOT "/oldroot" /* This path has not been made configurable and is hardcoded * here for security purposes. If you want to change it, change it diff --git a/utils.c b/utils.c index 233a6e5..e6dd37f 100644 --- a/utils.c +++ b/utils.c @@ -43,6 +43,19 @@ bool mkfile(const char *s) return true; } +mode_t getmode(const char *s) +{ + struct stat st; + if (stat(s, &st) == -1) + return 0; + return st.st_mode; +} + +bool startswith(const char *haystack, const char *needle) +{ + return strncmp(haystack, needle, strlen(needle)) == 0; +} + int send_fd(int sock, int fd) { struct msghdr msg = {0}; diff --git a/utils.h b/utils.h index cc9f610..e25c4a4 100644 --- a/utils.h +++ b/utils.h @@ -20,6 +20,8 @@ bool isdir(const char *s); bool isexe(const char *s); bool exists(const char *s); bool mkfile(const char *s); +bool startswith(const char *haystack, const char *needle); +mode_t getmode(const char *s); int send_fd(int sock, int fd); int recv_fd(int sock); diff --git a/voidnsrun.c b/voidnsrun.c index 6a70caa..e895d13 100644 --- a/voidnsrun.c +++ b/voidnsrun.c @@ -35,6 +35,7 @@ void usage(const char *progname) " " CONTAINER_DIR_VAR " environment variable is used.\n" " -m : Add bind mount. You can add up to %d paths.\n" " -u : Add undo bind mount. You can add up to %d paths.\n" + " -d : Add /usr subdirectory bind mount.\n" " -U : Path to " VOIDNSUNDO_NAME ". When this option is not present,\n" " " UNDO_BIN_VAR " environment variable is used.\n" " -i: Don't treat missing source or target for added mounts as error.\n" @@ -44,10 +45,14 @@ void usage(const char *progname) USER_LISTS_MAX, USER_LISTS_MAX); } -size_t mount_dirs(const char *source_prefix, size_t source_prefix_len, struct strarray *targets) +size_t mount_dirs(const char *source_prefix, + size_t source_prefix_len, + struct strarray *targets, + struct intarray *created) { char buf[PATH_MAX]; int successful = 0; + mode_t mode; for (size_t i = 0; i < targets->end; i++) { /* Check if it's safe to proceed. */ if (source_prefix_len + strlen(targets->list[i]) >= PATH_MAX) { @@ -65,8 +70,28 @@ size_t mount_dirs(const char *source_prefix, size_t source_prefix_len, struct st continue; } + if (!exists(targets->list[i])) { + if (created != NULL) { + mode = getmode(buf); + if (mode == 0) { + ERROR("error: can't get mode for %s.\n", buf); + continue; + } + + if (mkdir(targets->list[i], mode) == -1) { + ERROR("error: failed to create mountpotint at %s: %s.\n", + targets->list[i], strerror(errno)); + continue; + } else + intarray_append(created, i); + } else { + ERROR("error: mount dir %s does not exists.\n", buf); + continue; + } + } + if (!isdir(targets->list[i])) { - ERROR("error: mount point %s does not exists.\n", targets->list[i]); + ERROR("error: mount point %s is not a directory.\n", targets->list[i]); continue; } @@ -78,7 +103,9 @@ size_t mount_dirs(const char *source_prefix, size_t source_prefix_len, struct st return successful; } -size_t mount_undo(const char *source, const struct strarray *targets, struct intarray *created) +size_t mount_undo(const char *source, + const struct strarray *targets, + struct intarray *created) { int successful = 0; for (size_t i = 0; i < targets->end; i++) { @@ -135,12 +162,20 @@ int main(int argc, char **argv) struct strarray undo_mounts; strarray_alloc(&undo_mounts, USER_LISTS_MAX); + /* List of user-specified /usr subdirectories to mount. */ + struct strarray dir_mounts; + strarray_alloc(&dir_mounts, USER_LISTS_MAX); + /* List of indexes of items in the undo_mounts array. See comments in * mount_undo() function for more info. */ - struct intarray to_unlink; - intarray_alloc(&to_unlink, USER_LISTS_MAX); + struct intarray created_undos; + intarray_alloc(&created_undos, USER_LISTS_MAX); + + /* List of indexes of items in the dir_mounts array. */ + struct intarray created_dirs; + intarray_alloc(&created_dirs, USER_LISTS_MAX); - while ((c = getopt(argc, argv, "vhm:r:u:U:iV")) != -1) { + while ((c = getopt(argc, argv, "vhm:r:u:U:iVd:")) != -1) { switch (c) { case 'v': printf("%s\n", PROG_VERSION); @@ -170,6 +205,13 @@ int main(int argc, char **argv) ERROR_EXIT("error: only up to %lu user mounts allowed.\n", undo_mounts.size); break; + case 'd': + if (!startswith(optarg, "/usr/")) + ERROR_EXIT("only subdirectories of /usr are allowed for bind mounting this way.\n"); + if (!strarray_append(&dir_mounts, optarg)) + ERROR_EXIT("error: only up to %lu dir mounts allowed.\n", + dir_mounts.size); + break; case '?': return 1; } @@ -261,9 +303,28 @@ int main(int argc, char **argv) /* Mount stuff from the container to the namespace. */ /* First, mount what user asked us to mount. */ - if (mount_dirs(dir, dirlen, &user_mounts) < user_mounts.end && !ignore_missing) + if (mount_dirs(dir, dirlen, &user_mounts, NULL) < user_mounts.end && !ignore_missing) ERROR_EXIT("error: some mounts failed.\n"); + /* Then preserve original /usr at /oldroot if needed. */ + if (dir_mounts.end > 0) { + mode_t mode = getmode("/usr"); + if (mode == 0) + ERROR_EXIT("error: failed to get mode of /usr.\n"); + + if (mount("tmpfs", OLDROOT, "tmpfs", 0, "size=4k,mode=0700,uid=0,gid=0") == -1) + ERROR_EXIT("mount: error mounting tmpfs in %s.\n", OLDROOT); + + strcpy(buf, OLDROOT); + strcat(buf, "/usr"); + if (mkdir(buf, mode) == -1) + ERROR_EXIT("error: failed to mkdir %s: %s.\n", buf, strerror(errno)); + + if (mount("/usr", buf, NULL, MS_BIND|MS_REC, NULL) == -1) + ERROR_EXIT("error: failed to mount /usr at %s: %s.", + buf, strerror(errno)); + } + /* Then the necessary stuff. */ struct strarray default_mounts; strarray_alloc(&default_mounts, 3); @@ -272,11 +333,16 @@ int main(int argc, char **argv) strarray_append(&default_mounts, "/var"); strarray_append(&default_mounts, "/etc"); } - if (mount_dirs(dir, dirlen, &default_mounts) < default_mounts.end) + if (mount_dirs(dir, dirlen, &default_mounts, NULL) < default_mounts.end) ERROR_EXIT("error: some necessary mounts failed.\n"); + /* Mount /usr subdirectories if needed. */ + if (dir_mounts.end > 0 + && mount_dirs(OLDROOT, strlen(OLDROOT), &dir_mounts, &created_dirs) < dir_mounts.end) + ERROR_EXIT("error: some dir mounts failed.\n"); + /* Now lets do bind mounts of voidnsundo (if needed). */ - if (mount_undo(undo_bin, &undo_mounts, &to_unlink) < undo_mounts.end + if (mount_undo(undo_bin, &undo_mounts, &created_undos) < undo_mounts.end && !ignore_missing) ERROR_EXIT("error: some undo mounts failed.\n"); @@ -393,17 +459,54 @@ end: if (dirptr != NULL) closedir(dirptr); - /* If we created some empty files to bind the voidnsundo utility, - * delete them here. */ - if (to_unlink.end > 0 && (!forked || pid == 0)) { - for (size_t i = 0; i < to_unlink.end; i++) { - char *path = undo_mounts.list[to_unlink.list[i]]; - if (umount(path) == -1) - DEBUG("umount(%s): %s\n", path, strerror(errno)); - if (unlink(path) == -1) - ERROR("unlink(%s): %s\n", path, strerror(errno)); - else - DEBUG("unlink(%s)\n", path); + if (!forked || pid == 0) { + /* If we created some empty files to bind the voidnsundo utility, + * delete them here. */ + if (created_undos.end > 0) { + for (size_t i = 0; i < created_undos.end; i++) { + char *path = undo_mounts.list[created_undos.list[i]]; + if (umount(path) == -1) + DEBUG("umount(%s): %s\n", path, strerror(errno)); + if (unlink(path) == -1) + ERROR("unlink(%s): %s\n", path, strerror(errno)); + else + DEBUG("unlink(%s)\n", path); + } + } + + /* If we had to create mount tmpfs to /oldroot and do other + * dirty hacks related to /usr subdirs bind mounting, clean up here. */ + if (dir_mounts.end > 0) { + for (size_t i = 0; i < dir_mounts.end; i++) { + char *path = dir_mounts.list[i]; + if (umount(path) == -1) + ERROR("umount(%s): %s\n", path, strerror(errno)); + } + + /* If we created some empty dirs to use them as mountpoints for + * bind mounts, delete them here. */ + if (created_dirs.end > 0) { + for (size_t i = 0; i < created_dirs.end; i++) { + char *path = dir_mounts.list[created_dirs.list[i]]; + if (rmdir(path) == -1) + ERROR("rmdir(%s): %s\n", path, strerror(errno)); + else + DEBUG("rmdir(%s)\n", path); + } + } + + strcpy(buf, OLDROOT); + strcat(buf, "/usr"); + if (umount(buf) == -1) + ERROR("umount(%s): %s\n", buf, strerror(errno)); + + /* This call always fails with EBUSY and I don't know why. + * We can safely ignore any errors here (I hope) because + * the mount namespace will be destroyed as soon as there + * will be no more processes attached to it. */ + umount(OLDROOT); + /*if (umount(OLDROOT) == -1) + ERROR("umount(%s): %s\n", OLDROOT, strerror(errno));*/ } } -- cgit v1.2.3