From 57c10a5650f6bb7180f3bec31a3f24239a81be39 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Thu, 24 Nov 2022 10:45:24 +0100 Subject: [PATCH 1/3] nspawn: support pivot_root() In order to support pivot_root() we need to move mount propagation changes after the pivot_root(). While MS_MOVE requires the source mount to not be a shared mount pivot_root() also requires the target mount to not be a shared mount. This guarantees that pivot_root() doesn't leak any mounts. Signed-off-by: Christian Brauner (Microsoft) --- src/core/namespace.c | 4 +- src/nspawn/nspawn.c | 24 +++++----- src/shared/mount-util.c | 97 +++++++++++++++++++++++++++++------------ src/shared/mount-util.h | 17 +++++++- 4 files changed, 96 insertions(+), 46 deletions(-) diff --git a/src/core/namespace.c b/src/core/namespace.c index c0d0cc9715..c9c2132b8a 100644 --- a/src/core/namespace.c +++ b/src/core/namespace.c @@ -2486,7 +2486,7 @@ int setup_namespace( goto finish; /* MS_MOVE does not work on MS_SHARED so the remount MS_SHARED will be done later */ - r = mount_pivot_root(root); + r = mount_switch_root(root, MOUNT_ATTR_PROPAGATION_INHERIT); if (r == -EINVAL && root_directory) { /* If we are using root_directory and we don't have privileges (ie: user manager in a user * namespace) and the root_directory is already a mount point in the parent namespace, @@ -2496,7 +2496,7 @@ int setup_namespace( r = mount_nofollow_verbose(LOG_DEBUG, root, root, NULL, MS_BIND|MS_REC, NULL); if (r < 0) goto finish; - r = mount_pivot_root(root); + r = mount_switch_root(root, MOUNT_ATTR_PROPAGATION_INHERIT); } if (r < 0) { log_debug_errno(r, "Failed to mount root with MS_MOVE: %m"); diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c index d7b636209e..5844674d95 100644 --- a/src/nspawn/nspawn.c +++ b/src/nspawn/nspawn.c @@ -3858,19 +3858,6 @@ static int outer_child( unified_cgroup_hierarchy_socket = safe_close(unified_cgroup_hierarchy_socket); } - /* Mark everything as shared so our mounts get propagated down. This is required to make new bind - * mounts available in systemd services inside the container that create a new mount namespace. See - * https://github.com/systemd/systemd/issues/3860 Further submounts (such as /dev) done after this - * will inherit the shared propagation mode. - * - * IMPORTANT: Do not overmount the root directory anymore from now on to enable moving the root - * directory mount to root later on. - * https://github.com/systemd/systemd/issues/3847#issuecomment-562735251 - */ - r = mount_nofollow_verbose(LOG_ERR, NULL, directory, NULL, MS_SHARED|MS_REC, NULL); - if (r < 0) - return r; - r = recursive_chown(directory, arg_uid_shift, arg_uid_range); if (r < 0) return r; @@ -3974,7 +3961,16 @@ static int outer_child( return r; } - r = mount_move_root(directory); + /* Mark everything as shared so our mounts get propagated down. This is required to make new bind + * mounts available in systemd services inside the container that create a new mount namespace. See + * https://github.com/systemd/systemd/issues/3860 Further submounts (such as /dev) done after this + * will inherit the shared propagation mode. + * + * IMPORTANT: Do not overmount the root directory anymore from now on to enable moving the root + * directory mount to root later on. + * https://github.com/systemd/systemd/issues/3847#issuecomment-562735251 + */ + r = mount_switch_root(directory, MOUNT_ATTR_PROPAGATION_SHARED); if (r < 0) return log_error_errno(r, "Failed to move root directory: %m"); diff --git a/src/shared/mount-util.c b/src/shared/mount-util.c index 681d698800..adb6b6dd27 100644 --- a/src/shared/mount-util.c +++ b/src/shared/mount-util.c @@ -36,6 +36,7 @@ #include "set.h" #include "stat-util.h" #include "stdio-util.h" +#include "string-table.h" #include "string-util.h" #include "strv.h" #include "tmpfile-util.h" @@ -475,47 +476,41 @@ int bind_remount_one_with_mountinfo( return 0; } -int mount_move_root(const char *path) { - assert(path); +static const char *const mount_attr_propagation_type_table[_MOUNT_ATTR_PROPAGATION_TYPE_MAX] = { + [MOUNT_ATTR_PROPAGATION_INHERIT] = "inherited", + [MOUNT_ATTR_PROPAGATION_PRIVATE] = "private", + [MOUNT_ATTR_PROPAGATION_DEPENDENT] = "dependent", + [MOUNT_ATTR_PROPAGATION_SHARED] = "shared", +}; - if (chdir(path) < 0) - return -errno; +DEFINE_STRING_TABLE_LOOKUP(mount_attr_propagation_type, MountAttrPropagationType); - if (mount(path, "/", NULL, MS_MOVE, NULL) < 0) - return -errno; - - if (chroot(".") < 0) - return -errno; - - return RET_NERRNO(chdir("/")); +unsigned int mount_attr_propagation_type_to_flag(MountAttrPropagationType t) { + switch (t) { + case MOUNT_ATTR_PROPAGATION_INHERIT: + return 0; + case MOUNT_ATTR_PROPAGATION_PRIVATE: + return MS_PRIVATE; + case MOUNT_ATTR_PROPAGATION_DEPENDENT: + return MS_SLAVE; + case MOUNT_ATTR_PROPAGATION_SHARED: + return MS_SHARED; + default: + assert_not_reached(); + } } -int mount_pivot_root(const char *path) { - _cleanup_close_ int fd_oldroot = -EBADF, fd_newroot = -EBADF; - - assert(path); - - /* pivot_root() isn't currently supported in the initramfs. */ - if (in_initrd()) - return mount_move_root(path); +static inline int mount_switch_root_pivot(const char *path, int fd_newroot) { + _cleanup_close_ int fd_oldroot = -EBADF; fd_oldroot = open("/", O_PATH|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW); if (fd_oldroot < 0) return log_debug_errno(errno, "Failed to open old rootfs"); - fd_newroot = open(path, O_PATH|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW); - if (fd_newroot < 0) - return log_debug_errno(errno, "Failed to open new rootfs '%s': %m", path); - - /* Change into the new rootfs. */ - if (fchdir(fd_newroot) < 0) - return log_debug_errno(errno, "Failed to change into new rootfs '%s': %m", path); - /* Let the kernel tuck the new root under the old one. */ if (pivot_root(".", ".") < 0) return log_debug_errno(errno, "Failed to pivot root to new rootfs '%s': %m", path); - /* At this point the new root is tucked under the old root. If we want * to unmount it we cannot be fchdir()ed into it. So escape back to the * old root. */ @@ -535,6 +530,52 @@ int mount_pivot_root(const char *path) { return 0; } +static inline int mount_switch_root_move(const char *path) { + if (mount(path, "/", NULL, MS_MOVE, NULL) < 0) + return log_debug_errno(errno, "Failed to move new rootfs '%s': %m", path); + + if (chroot(".") < 0) + return log_debug_errno(errno, "Failed to chroot to new rootfs '%s': %m", path); + + if (chdir("/")) + return log_debug_errno(errno, "Failed to chdir to new rootfs '%s': %m", path); + + return 0; +} + +int mount_switch_root(const char *path, MountAttrPropagationType type) { + int r; + _cleanup_close_ int fd_newroot = -EBADF; + unsigned int flags; + + assert(path); + + fd_newroot = open(path, O_PATH|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW); + if (fd_newroot < 0) + return log_debug_errno(errno, "Failed to open new rootfs '%s': %m", path); + + /* Change into the new rootfs. */ + if (fchdir(fd_newroot) < 0) + return log_debug_errno(errno, "Failed to change into new rootfs '%s': %m", path); + + r = mount_switch_root_pivot(path, fd_newroot); + if (r < 0) { + /* Failed to pivot_root() fallback to MS_MOVE. For example, this may happen if the + * rootfs is an initramfs in which case pivot_root() isn't supported. */ + log_debug_errno(r, "Failed to pivot into new rootfs '%s': %m", path); + r = mount_switch_root_move(path); + } + if (r < 0) + return log_debug_errno(r, "Failed to switch to new rootfs '%s': %m", path); + + /* Finally, let's establish the requested propagation type. */ + flags = mount_attr_propagation_type_to_flag(type); + if ((flags != 0) && mount(NULL, ".", NULL, flags|MS_REC, 0) < 0) + return log_debug_errno(errno, "Failed to turn new rootfs '%s' into %s mount: %m", + mount_attr_propagation_type_to_string(type), path); + + return 0; +} int repeat_unmount(const char *path, int flags) { bool done = false; diff --git a/src/shared/mount-util.h b/src/shared/mount-util.h index 29b9ed02f7..56b1c3669c 100644 --- a/src/shared/mount-util.h +++ b/src/shared/mount-util.h @@ -11,6 +11,20 @@ #include "errno-util.h" #include "macro.h" +typedef enum MountAttrPropagationType { + MOUNT_ATTR_PROPAGATION_INHERIT, /* no special MS_* propagation flags */ + MOUNT_ATTR_PROPAGATION_PRIVATE, /* MS_PRIVATE */ + MOUNT_ATTR_PROPAGATION_DEPENDENT, /* MS_SLAVE */ + MOUNT_ATTR_PROPAGATION_SHARED, /* MS_SHARE */ + + _MOUNT_ATTR_PROPAGATION_TYPE_MAX, + _MOUNT_ATTR_PROPAGATION_TYPE_INVALID = -EINVAL, +} MountAttrPropagationType; + +const char* mount_attr_propagation_type_to_string(MountAttrPropagationType t) _const_; +MountAttrPropagationType mount_attr_propagation_type_from_string(const char *s) _pure_; +unsigned int mount_attr_propagation_type_to_flag(MountAttrPropagationType t); + /* The limit used for /dev itself. 4MB should be enough since device nodes and symlinks don't * consume any space and udev isn't supposed to create regular file either. There's no limit on the * max number of inodes since such limit is hard to guess especially on large storage array @@ -54,8 +68,7 @@ static inline int bind_remount_recursive(const char *prefix, unsigned long new_f int bind_remount_one_with_mountinfo(const char *path, unsigned long new_flags, unsigned long flags_mask, FILE *proc_self_mountinfo); -int mount_move_root(const char *path); -int mount_pivot_root(const char *path); +int mount_switch_root(const char *path, MountAttrPropagationType type); DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(FILE*, endmntent, NULL); #define _cleanup_endmntent_ _cleanup_(endmntentp) From b71a0192c040f585397cfc6fc2ca025bf839733d Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Mon, 28 Nov 2022 12:36:47 +0100 Subject: [PATCH 2/3] nspawn: mount temporary visible procfs and sysfs instance In order to mount procfs and sysfs in an unprivileged container the kernel requires that a fully visible instance is already present in the target mount namespace. Mount one here so the inner child can mount its own instances. Later we umount the temporary instances created here before we actually exec the payload. Since the rootfs is shared the umount will propagate into the container. Note, the inner child wouldn't be able to unmount the instances on its own since it doesn't own the originating mount namespace. IOW, the outer child needs to do this. So far nspawn didn't run into this issue because it used MS_MOVE which meant that the shadow mount tree pinned a procfs and sysfs instance which the kernel would find. The shadow mount tree is gone with proper pivot_root() semantics. Signed-off-by: Christian Brauner (Microsoft) --- src/nspawn/nspawn-mount.c | 65 +++++++++++++++++++++++++++++++++++++-- src/nspawn/nspawn-mount.h | 2 ++ src/nspawn/nspawn.c | 50 +++++++++++++++++++++++++----- 3 files changed, 107 insertions(+), 10 deletions(-) diff --git a/src/nspawn/nspawn-mount.c b/src/nspawn/nspawn-mount.c index a54f1464ba..0e8aaa1e3c 100644 --- a/src/nspawn/nspawn-mount.c +++ b/src/nspawn/nspawn-mount.c @@ -13,6 +13,7 @@ #include "mkdir-label.h" #include "mount-util.h" #include "mountpoint-util.h" +#include "namespace-util.h" #include "nspawn-mount.h" #include "parse-util.h" #include "path-util.h" @@ -510,6 +511,9 @@ int mount_sysfs(const char *dest, MountSettingsMask mount_settings) { MS_BIND|MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_REMOUNT|extra_flags, NULL); } +#define PROC_DEFAULT_MOUNT_FLAGS (MS_NOSUID|MS_NOEXEC|MS_NODEV) +#define SYS_DEFAULT_MOUNT_FLAGS (MS_RDONLY|MS_NOSUID|MS_NOEXEC|MS_NODEV) + int mount_all(const char *dest, MountSettingsMask mount_settings, uid_t uid_shift, @@ -538,7 +542,7 @@ int mount_all(const char *dest, static const MountPoint mount_table[] = { /* First we list inner child mounts (i.e. mounts applied *after* entering user namespacing) */ - { "proc", "/proc", "proc", NULL, MS_NOSUID|MS_NOEXEC|MS_NODEV, + { "proc", "/proc", "proc", NULL, PROC_DEFAULT_MOUNT_FLAGS, MOUNT_FATAL|MOUNT_IN_USERNS|MOUNT_MKDIR|MOUNT_FOLLOW_SYMLINKS }, /* we follow symlinks here since not following them requires /proc/ already being mounted, which we don't have here. */ { "/proc/sys", "/proc/sys", NULL, NULL, MS_BIND, @@ -576,7 +580,7 @@ int mount_all(const char *dest, MOUNT_FATAL|MOUNT_APPLY_TMPFS_TMP|MOUNT_MKDIR }, { "tmpfs", "/sys", "tmpfs", "mode=555" TMPFS_LIMITS_SYS, MS_NOSUID|MS_NOEXEC|MS_NODEV, MOUNT_FATAL|MOUNT_APPLY_APIVFS_NETNS|MOUNT_MKDIR }, - { "sysfs", "/sys", "sysfs", NULL, MS_RDONLY|MS_NOSUID|MS_NOEXEC|MS_NODEV, + { "sysfs", "/sys", "sysfs", NULL, SYS_DEFAULT_MOUNT_FLAGS, MOUNT_FATAL|MOUNT_APPLY_APIVFS_RO|MOUNT_MKDIR }, /* skipped if above was mounted */ { "sysfs", "/sys", "sysfs", NULL, MS_NOSUID|MS_NOEXEC|MS_NODEV, MOUNT_FATAL|MOUNT_MKDIR }, /* skipped if above was mounted */ @@ -1336,3 +1340,60 @@ done: return r; } + +#define NSPAWN_PRIVATE_FULLY_VISIBLE_PROCFS "/run/host/proc" +#define NSPAWN_PRIVATE_FULLY_VISIBLE_SYSFS "/run/host/sys" + +int pin_fully_visible_fs(void) { + int r; + + (void) mkdir_p(NSPAWN_PRIVATE_FULLY_VISIBLE_PROCFS, 0755); + (void) mkdir_p(NSPAWN_PRIVATE_FULLY_VISIBLE_SYSFS, 0755); + + r = mount_follow_verbose(LOG_ERR, "proc", NSPAWN_PRIVATE_FULLY_VISIBLE_PROCFS, "proc", PROC_DEFAULT_MOUNT_FLAGS, NULL); + if (r < 0) + return r; + + r = mount_follow_verbose(LOG_ERR, "sysfs", NSPAWN_PRIVATE_FULLY_VISIBLE_SYSFS, "sysfs", SYS_DEFAULT_MOUNT_FLAGS, NULL); + if (r < 0) + return r; + + return 0; +} + +static int do_wipe_fully_visible_fs(void) { + if (umount2(NSPAWN_PRIVATE_FULLY_VISIBLE_PROCFS, MNT_DETACH) < 0) + return log_error_errno(errno, "Failed to unmount temporary proc: %m"); + + if (rmdir(NSPAWN_PRIVATE_FULLY_VISIBLE_PROCFS) < 0) + return log_error_errno(errno, "Failed to remove temporary proc mountpoint: %m"); + + if (umount2(NSPAWN_PRIVATE_FULLY_VISIBLE_SYSFS, MNT_DETACH) < 0) + return log_error_errno(errno, "Failed to unmount temporary sys: %m"); + + if (rmdir(NSPAWN_PRIVATE_FULLY_VISIBLE_SYSFS) < 0) + return log_error_errno(errno, "Failed to remove temporary sys mountpoint: %m"); + + return 0; +} + +int wipe_fully_visible_fs(int mntns_fd) { + _cleanup_close_ int orig_mntns_fd = -EBADF; + int r, rr; + + r = namespace_open(0, NULL, &orig_mntns_fd, NULL, NULL, NULL); + if (r < 0) + return log_error_errno(r, "Failed to pin originating mount namespace: %m"); + + r = namespace_enter(-EBADF, mntns_fd, -EBADF, -EBADF, -EBADF); + if (r < 0) + return log_error_errno(r, "Failed to enter mount namespace: %m"); + + rr = do_wipe_fully_visible_fs(); + + r = namespace_enter(-EBADF, orig_mntns_fd, -EBADF, -EBADF, -EBADF); + if (r < 0) + return log_error_errno(r, "Failed to enter original mount namespace: %m"); + + return rr; +} diff --git a/src/nspawn/nspawn-mount.h b/src/nspawn/nspawn-mount.h index 6bedbf9b3f..bf5e47dce4 100644 --- a/src/nspawn/nspawn-mount.h +++ b/src/nspawn/nspawn-mount.h @@ -67,3 +67,5 @@ int pivot_root_parse(char **pivot_root_new, char **pivot_root_old, const char *s int setup_pivot_root(const char *directory, const char *pivot_root_new, const char *pivot_root_old); int tmpfs_patch_options(const char *options,uid_t uid_shift, const char *selinux_apifs_context, char **ret); +int pin_fully_visible_fs(void); +int wipe_fully_visible_fs(int mntns_fd); diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c index 5844674d95..392336dfa5 100644 --- a/src/nspawn/nspawn.c +++ b/src/nspawn/nspawn.c @@ -3632,7 +3632,7 @@ static int outer_child( _cleanup_(bind_user_context_freep) BindUserContext *bind_user_context = NULL; _cleanup_strv_free_ char **os_release_pairs = NULL; - _cleanup_close_ int fd = -1; + _cleanup_close_ int fd = -1, mntns_fd = -EBADF; bool idmap = false; const char *p; pid_t pid; @@ -3697,6 +3697,15 @@ static int outer_child( return r; if (arg_userns_mode != USER_NAMESPACE_NO) { + r = namespace_open(0, NULL, &mntns_fd, NULL, NULL, NULL); + if (r < 0) + return log_error_errno(r, "Failed to pin outer mount namespace: %m"); + + l = send_one_fd(notify_socket, mntns_fd, 0); + if (l < 0) + return log_error_errno(l, "Failed to send outer mount namespace fd: %m"); + mntns_fd = safe_close(mntns_fd); + /* Let the parent know which UID shift we read from the image */ l = send(uid_shift_socket, &arg_uid_shift, sizeof(arg_uid_shift), MSG_NOSIGNAL); if (l < 0) @@ -3974,6 +3983,20 @@ static int outer_child( if (r < 0) return log_error_errno(r, "Failed to move root directory: %m"); + if (arg_userns_mode != USER_NAMESPACE_NO) { + /* In order to mount procfs and sysfs in an unprivileged container the kernel + * requires that a fully visible instance is already present in the target mount + * namespace. Mount one here so the inner child can mount its own instances. Later + * we umount the temporary instances created here before we actually exec the + * payload. Since the rootfs is shared the umount will propagate into the container. + * Note, the inner child wouldn't be able to unmount the instances on its own since + * it doesn't own the originating mount namespace. IOW, the outer child needs to do + * this. */ + r = pin_fully_visible_fs(); + if (r < 0) + return r; + } + fd = setup_notify_child(); if (fd < 0) return fd; @@ -4731,12 +4754,12 @@ static int run_container( rtnl_socket_pair[2] = { -1, -1 }, pid_socket_pair[2] = { -1, -1 }, uuid_socket_pair[2] = { -1, -1 }, - notify_socket_pair[2] = { -1, -1 }, + fd_socket_pair[2] = { -EBADF, -EBADF }, uid_shift_socket_pair[2] = { -1, -1 }, master_pty_socket_pair[2] = { -1, -1 }, unified_cgroup_hierarchy_socket_pair[2] = { -1, -1}; - _cleanup_close_ int notify_socket = -1; + _cleanup_close_ int notify_socket = -1, mntns_fd = -EBADF; _cleanup_(barrier_destroy) Barrier barrier = BARRIER_NULL; _cleanup_(sd_event_source_unrefp) sd_event_source *notify_event_source = NULL; _cleanup_(sd_event_unrefp) sd_event *event = NULL; @@ -4783,7 +4806,7 @@ static int run_container( if (socketpair(AF_UNIX, SOCK_SEQPACKET|SOCK_CLOEXEC, 0, uuid_socket_pair) < 0) return log_error_errno(errno, "Failed to create id socket pair: %m"); - if (socketpair(AF_UNIX, SOCK_SEQPACKET|SOCK_CLOEXEC, 0, notify_socket_pair) < 0) + if (socketpair(AF_UNIX, SOCK_SEQPACKET|SOCK_CLOEXEC, 0, fd_socket_pair) < 0) return log_error_errno(errno, "Failed to create notify socket pair: %m"); if (socketpair(AF_UNIX, SOCK_SEQPACKET|SOCK_CLOEXEC, 0, master_pty_socket_pair) < 0) @@ -4836,7 +4859,7 @@ static int run_container( rtnl_socket_pair[0] = safe_close(rtnl_socket_pair[0]); pid_socket_pair[0] = safe_close(pid_socket_pair[0]); uuid_socket_pair[0] = safe_close(uuid_socket_pair[0]); - notify_socket_pair[0] = safe_close(notify_socket_pair[0]); + fd_socket_pair[0] = safe_close(fd_socket_pair[0]); master_pty_socket_pair[0] = safe_close(master_pty_socket_pair[0]); uid_shift_socket_pair[0] = safe_close(uid_shift_socket_pair[0]); unified_cgroup_hierarchy_socket_pair[0] = safe_close(unified_cgroup_hierarchy_socket_pair[0]); @@ -4850,7 +4873,7 @@ static int run_container( secondary, pid_socket_pair[1], uuid_socket_pair[1], - notify_socket_pair[1], + fd_socket_pair[1], kmsg_socket_pair[1], rtnl_socket_pair[1], uid_shift_socket_pair[1], @@ -4872,12 +4895,16 @@ static int run_container( rtnl_socket_pair[1] = safe_close(rtnl_socket_pair[1]); pid_socket_pair[1] = safe_close(pid_socket_pair[1]); uuid_socket_pair[1] = safe_close(uuid_socket_pair[1]); - notify_socket_pair[1] = safe_close(notify_socket_pair[1]); + fd_socket_pair[1] = safe_close(fd_socket_pair[1]); master_pty_socket_pair[1] = safe_close(master_pty_socket_pair[1]); uid_shift_socket_pair[1] = safe_close(uid_shift_socket_pair[1]); unified_cgroup_hierarchy_socket_pair[1] = safe_close(unified_cgroup_hierarchy_socket_pair[1]); if (arg_userns_mode != USER_NAMESPACE_NO) { + mntns_fd = receive_one_fd(fd_socket_pair[0], 0); + if (mntns_fd < 0) + return log_error_errno(mntns_fd, "Failed to receive mount namespace fd from outer child: %m"); + /* The child just let us know the UID shift it might have read from the image. */ l = recv(uid_shift_socket_pair[0], &arg_uid_shift, sizeof arg_uid_shift, 0); if (l < 0) @@ -4954,7 +4981,7 @@ static int run_container( return log_error_errno(SYNTHETIC_ERRNO(EIO), "Short read while reading container machined ID."); /* We also retrieve the socket used for notifications generated by outer child */ - notify_socket = receive_one_fd(notify_socket_pair[0], 0); + notify_socket = receive_one_fd(fd_socket_pair[0], 0); if (notify_socket < 0) return log_error_errno(notify_socket, "Failed to receive notification socket from the outer child: %m"); @@ -5139,6 +5166,13 @@ static int run_container( if (r < 0) return r; + if (arg_userns_mode != USER_NAMESPACE_NO) { + r = wipe_fully_visible_fs(mntns_fd); + if (r < 0) + return r; + mntns_fd = safe_close(mntns_fd); + } + /* Let the child know that we are ready and wait that the child is completely ready now. */ if (!barrier_place_and_sync(&barrier)) /* #5 */ return log_error_errno(SYNTHETIC_ERRNO(ESRCH), "Child died too early."); From e79581ddfefd5d565bb3b497f1e6adae688c8810 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Mon, 5 Dec 2022 18:34:00 +0100 Subject: [PATCH 3/3] nspawn: split mount tunnel setup Before we supported pivot_root() nspawn used to make the rootfs shared before setting up the mount tunnel. So it was safe for it to just turn it into a dependent mount during setup. However, we cannot do this anymore because of the requirements pivot_root() has. After the pivot_root() we will make the rootfs shared recursively. If we turned the mount tunnel into dependent mount before mount_switch_root() this will have the consequence that it becomes a shared mount within the same peer group as the rootfs. So no mounts will propagate into the container from the host anymore. To fix this we split setting up the mount tunnel and making it active into two steps. Setting up the mount tunnel is performed before mount_switch_root() and activating it afterwards. Note that this works because turning a shared mount into a shared mount is a nop. IOW, no new peer group will be allocated. Signed-off-by: Christian Brauner (Microsoft) --- src/nspawn/nspawn.c | 31 ++++++++++++++++++++++++------- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c index 392336dfa5..0d025804fa 100644 --- a/src/nspawn/nspawn.c +++ b/src/nspawn/nspawn.c @@ -114,6 +114,7 @@ /* The notify socket inside the container it can use to talk to nspawn using the sd_notify(3) protocol */ #define NSPAWN_NOTIFY_SOCKET_PATH "/run/host/notify" +#define NSPAWN_MOUNT_TUNNEL "/run/host/incoming" #define EXIT_FORCE_RESTART 133 @@ -2776,7 +2777,7 @@ static int reset_audit_loginuid(void) { return 0; } -static int setup_propagate(const char *root) { +static int mount_tunnel_dig(const char *root) { const char *p, *q; int r; @@ -2789,11 +2790,11 @@ static int setup_propagate(const char *root) { if (r < 0) return log_error_errno(r, "Failed to create /run/host: %m"); - r = userns_mkdir(root, "/run/host/incoming", 0600, 0, 0); + r = userns_mkdir(root, NSPAWN_MOUNT_TUNNEL, 0600, 0, 0); if (r < 0) - return log_error_errno(r, "Failed to create /run/host/incoming: %m"); + return log_error_errno(r, "Failed to create "NSPAWN_MOUNT_TUNNEL": %m"); - q = prefix_roota(root, "/run/host/incoming"); + q = prefix_roota(root, NSPAWN_MOUNT_TUNNEL); r = mount_nofollow_verbose(LOG_ERR, p, q, NULL, MS_BIND, NULL); if (r < 0) return r; @@ -2802,8 +2803,17 @@ static int setup_propagate(const char *root) { if (r < 0) return r; - /* machined will MS_MOVE into that directory, and that's only supported for non-shared mounts. */ - return mount_nofollow_verbose(LOG_ERR, NULL, q, NULL, MS_SLAVE, NULL); + return 0; +} + +static int mount_tunnel_open(void) { + int r; + + r = mount_follow_verbose(LOG_ERR, NULL, NSPAWN_MOUNT_TUNNEL, NULL, MS_SLAVE, NULL); + if (r < 0) + return r; + + return 0; } static int setup_machine_id(const char *directory) { @@ -3906,7 +3916,7 @@ static int outer_child( if (r < 0) return r; - r = setup_propagate(directory); + r = mount_tunnel_dig(directory); if (r < 0) return r; @@ -3983,6 +3993,13 @@ static int outer_child( if (r < 0) return log_error_errno(r, "Failed to move root directory: %m"); + /* We finished setting up the rootfs which is a shared mount. The mount tunnel needs to be a + * dependent mount otherwise we can't MS_MOVE mounts that were propagated from the host into + * the container. */ + r = mount_tunnel_open(); + if (r < 0) + return r; + if (arg_userns_mode != USER_NAMESPACE_NO) { /* In order to mount procfs and sysfs in an unprivileged container the kernel * requires that a fully visible instance is already present in the target mount