From d64e32c245b6710f90f746d3bf970dd17f765d4b Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 23 Jul 2020 16:49:13 +0200 Subject: [PATCH] nspawn: rework how /run/host/ is set up Let's find the right os-release file on the host side, and only mount the one that matters, i.e. /etc/os-release if it exists and /usr/lib/os-release otherwise. Use the fixed path /run/host/os-release for that. Let's also mount /run/host as a bind mount on itself before we set up /run/host, and let's mount it MS_RDONLY after we are done, so that it remains immutable as a whole. --- src/nspawn/nspawn-mount.c | 66 ++++++++++++++++++++------------------ src/nspawn/nspawn-mount.h | 1 + test/units/testsuite-13.sh | 21 +++++++++--- 3 files changed, 53 insertions(+), 35 deletions(-) diff --git a/src/nspawn/nspawn-mount.c b/src/nspawn/nspawn-mount.c index 4687ac4c18..a48c7f62da 100644 --- a/src/nspawn/nspawn-mount.c +++ b/src/nspawn/nspawn-mount.c @@ -563,15 +563,16 @@ int mount_all(const char *dest, MOUNT_FATAL|MOUNT_MKDIR }, { "tmpfs", "/run", "tmpfs", "mode=755" TMPFS_LIMITS_RUN, MS_NOSUID|MS_NODEV|MS_STRICTATIME, MOUNT_FATAL|MOUNT_MKDIR }, - { "/usr/lib/os-release", "/run/host/usr/lib/os-release", NULL, NULL, MS_BIND, - MOUNT_FATAL|MOUNT_MKDIR|MOUNT_TOUCH }, /* As per kernel interface requirements, bind mount first (creating mount points) and make read-only later */ - { NULL, "/run/host/usr/lib/os-release", NULL, NULL, MS_BIND|MS_RDONLY|MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_REMOUNT, - 0 }, - { "/etc/os-release", "/run/host/etc/os-release", NULL, NULL, MS_BIND, - MOUNT_MKDIR|MOUNT_TOUCH }, - { NULL, "/run/host/etc/os-release", NULL, NULL, MS_BIND|MS_RDONLY|MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_REMOUNT, - 0 }, - + { "/run/host", "/run/host", NULL, NULL, MS_BIND, + MOUNT_FATAL|MOUNT_MKDIR|MOUNT_PREFIX_ROOT }, /* Prepare this so that we can make it read-only when we are done */ + { "/etc/os-release", "/run/host/os-release", NULL, NULL, MS_BIND, + MOUNT_TOUCH }, /* As per kernel interface requirements, bind mount first (creating mount points) and make read-only later */ + { "/usr/lib/os-release", "/run/host/os-release", NULL, NULL, MS_BIND, + MOUNT_FATAL }, /* If /etc/os-release doesn't exist use the version in /usr/lib as fallback */ + { NULL, "/run/host/os-release", NULL, NULL, MS_BIND|MS_RDONLY|MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_REMOUNT, + MOUNT_FATAL }, + { NULL, "/run/host", NULL, NULL, MS_BIND|MS_RDONLY|MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_REMOUNT, + MOUNT_FATAL|MOUNT_IN_USERNS }, #if HAVE_SELINUX { "/sys/fs/selinux", "/sys/fs/selinux", NULL, NULL, MS_BIND, MOUNT_MKDIR }, /* Bind mount first (mkdir/chown the mount point in case /sys/ is mounted as minimal skeleton tmpfs) */ @@ -589,9 +590,9 @@ int mount_all(const char *dest, int r; for (k = 0; k < ELEMENTSOF(mount_table); k++) { - _cleanup_free_ char *where = NULL, *options = NULL; - const char *o; + _cleanup_free_ char *where = NULL, *options = NULL, *prefixed = NULL; bool fatal = FLAGS_SET(mount_table[k].mount_settings, MOUNT_FATAL); + const char *o; if (in_userns != FLAGS_SET(mount_table[k].mount_settings, MOUNT_IN_USERNS)) continue; @@ -616,20 +617,9 @@ int mount_all(const char *dest, return log_error_errno(r, "Failed to detect whether %s is a mount point: %m", where); if (r > 0) continue; - - /* Shortcut for optional bind mounts: if the source can't be found skip ahead to avoid creating - * empty and unused directories. */ - if (!fatal && FLAGS_SET(mount_table[k].mount_settings, MOUNT_MKDIR) && FLAGS_SET(mount_table[k].flags, MS_BIND)) { - r = access(mount_table[k].what, F_OK); - if (r < 0) { - if (errno == ENOENT) - continue; - return log_error_errno(errno, "Failed to stat %s: %m", mount_table[k].what); - } - } } - if (FLAGS_SET(mount_table[k].mount_settings, MOUNT_MKDIR)) { + if ((mount_table[k].mount_settings & (MOUNT_MKDIR|MOUNT_TOUCH)) != 0) { uid_t u = (use_userns && !in_userns) ? uid_shift : UID_INVALID; if (FLAGS_SET(mount_table[k].mount_settings, MOUNT_TOUCH)) @@ -647,13 +637,17 @@ int mount_all(const char *dest, if (r != -EROFS) continue; } - if (FLAGS_SET(mount_table[k].mount_settings, MOUNT_TOUCH)) { - r = touch(where); - if (r < 0 && r != -EEXIST) { - if (fatal) - return log_error_errno(r, "Failed to create mount point %s: %m", where); - log_debug_errno(r, "Failed to create mount point %s: %m", where); - } + } + + if (FLAGS_SET(mount_table[k].mount_settings, MOUNT_TOUCH)) { + r = touch(where); + if (r < 0 && r != -EEXIST) { + if (fatal && r != -EROFS) + return log_error_errno(r, "Failed to create file %s: %m", where); + + log_debug_errno(r, "Failed to create file %s: %m", where); + if (r != -EROFS) + continue; } } @@ -666,8 +660,18 @@ int mount_all(const char *dest, o = options; } + if (FLAGS_SET(mount_table[k].mount_settings, MOUNT_PREFIX_ROOT)) { + /* Optionally prefix the mount source with the root dir. This is useful in bind + * mounts to be created within the container image before we transition into it. Note + * that MOUNT_IN_USERNS is run after we transitioned hence prefixing is not ncessary + * for those. */ + r = chase_symlinks(mount_table[k].what, dest, CHASE_PREFIX_ROOT, &prefixed, NULL); + if (r < 0) + return log_error_errno(r, "Failed to resolve %s/%s: %m", dest, mount_table[k].what); + } + r = mount_verbose(fatal ? LOG_ERR : LOG_DEBUG, - mount_table[k].what, + prefixed ?: mount_table[k].what, where, mount_table[k].type, mount_table[k].flags, diff --git a/src/nspawn/nspawn-mount.h b/src/nspawn/nspawn-mount.h index 062ed8b57d..3898c74f18 100644 --- a/src/nspawn/nspawn-mount.h +++ b/src/nspawn/nspawn-mount.h @@ -18,6 +18,7 @@ typedef enum MountSettingsMask { MOUNT_NON_ROOT_ONLY = 1 << 7, /* if set, only non-root mounts are mounted */ MOUNT_MKDIR = 1 << 8, /* if set, make directory to mount over first */ MOUNT_TOUCH = 1 << 9, /* if set, touch file to mount over first */ + MOUNT_PREFIX_ROOT = 1 << 10,/* if set, prefix the source path with the container's root directory */ } MountSettingsMask; typedef enum CustomMountType { diff --git a/test/units/testsuite-13.sh b/test/units/testsuite-13.sh index 0ad75ac8b0..b8fbf00ea6 100755 --- a/test/units/testsuite-13.sh +++ b/test/units/testsuite-13.sh @@ -66,12 +66,25 @@ if [ -n "${ID:+set}" ] && [ "${ID}" != "${container_host_id}" ]; then exit 1; fi if [ -n "${VERSION_ID:+set}" ] && [ "${VERSION_ID}" != "${container_host_version_id}" ]; then exit 1; fi if [ -n "${BUILD_ID:+set}" ] && [ "${BUILD_ID}" != "${container_host_build_id}" ]; then exit 1; fi if [ -n "${VARIANT_ID:+set}" ] && [ "${VARIANT_ID}" != "${container_host_variant_id}" ]; then exit 1; fi -cd /tmp; (cd /run/host/usr/lib; md5sum os-release) | md5sum -c -if echo test >> /run/host/usr/lib/os-release; then exit 1; fi -if echo test >> /run/host/etc/os-release; then exit 1; fi +cd /tmp; (cd /run/host; md5sum os-release) | md5sum -c +if echo test >> /run/host/os-release; then exit 1; fi ' - systemd-nspawn --register=no -D /testsuite-13.nc-container --bind=/etc/os-release:/tmp/os-release /bin/sh -x -e -c "$_cmd" + local _os_release_source="/etc/os-release" + if [ ! -r "${_os_release_source}" ]; then + _os_release_source="/usr/lib/os-release" + elif [ -L "${_os_release_source}" ] && rm /etc/os-release; then + # Ensure that /etc always wins if available + cp /usr/lib/os-release /etc + echo MARKER=1 >> /etc/os-release + fi + + systemd-nspawn --register=no -D /testsuite-13.nc-container --bind="${_os_release_source}":/tmp/os-release /bin/sh -x -e -c "$_cmd" + + if grep -q MARKER /etc/os-release; then + rm /etc/os-release + ln -s ../usr/lib/os-release /etc/os-release + fi } function run {