diff --git a/src/nspawn/nspawn-mount.c b/src/nspawn/nspawn-mount.c index d4ac649c9f..2c53856272 100644 --- a/src/nspawn/nspawn-mount.c +++ b/src/nspawn/nspawn-mount.c @@ -6,6 +6,7 @@ #include "alloc-util.h" #include "chase.h" +#include "errno-util.h" #include "escape.h" #include "extract-word.h" #include "fd-util.h" @@ -814,7 +815,28 @@ static int mount_bind(const char *dest, CustomMount *m, uid_t uid_shift, uid_t u if (m->rm_rf_tmpdir && chown(m->source, uid_shift, uid_shift) < 0) return log_error_errno(errno, "Failed to chown %s: %m", m->source); - if (stat(m->source, &source_st) < 0) + /* UID/GIDs of idmapped mounts are always resolved in the caller's user namespace. In other + * words, they're not nested. If we're doing an idmapped mount from a bind mount that's + * already idmapped itself, the old idmap is replaced with the new one. This means that the + * source uid which we put in the idmap userns has to be the uid of mount source in the + * caller's userns *without* any mount idmapping in place. To get that uid, we clone the + * mount source tree and clear any existing idmapping and temporarily mount that tree over + * the mount source before we stat the mount source to figure out the source uid. */ + _cleanup_close_ int fd_clone = open_tree_attr_fallback( + AT_FDCWD, + m->source, + OPEN_TREE_CLONE|OPEN_TREE_CLOEXEC, + &(struct mount_attr) { + .attr_clr = MOUNT_ATTR_IDMAP, + }); + if (ERRNO_IS_NEG_NOT_SUPPORTED(fd_clone)) + /* We can only clear idmapped mounts with open_tree_attr(), but there might not be one in + * the first place, so we keep going if we get a not supported error. */ + fd_clone = open_tree(AT_FDCWD, m->source, OPEN_TREE_CLONE|OPEN_TREE_CLOEXEC); + if (fd_clone < 0) + return log_error_errno(errno, "Failed to clone %s: %m", m->source); + + if (fstat(fd_clone, &source_st) < 0) return log_error_errno(errno, "Failed to stat %s: %m", m->source); r = chase(m->destination, dest, CHASE_PREFIX_ROOT|CHASE_NONEXISTENT, &where, NULL); @@ -859,9 +881,10 @@ static int mount_bind(const char *dest, CustomMount *m, uid_t uid_shift, uid_t u dest_uid = uid_shift; } - r = mount_nofollow_verbose(LOG_ERR, m->source, where, NULL, mount_flags, mount_opts); - if (r < 0) - return r; + if (move_mount(fd_clone, "", AT_FDCWD, where, MOVE_MOUNT_F_EMPTY_PATH) < 0) + return log_error_errno(errno, "Failed to mount %s to %s: %m", m->source, where); + + fd_clone = safe_close(fd_clone); if (m->read_only) { r = bind_remount_recursive(where, MS_RDONLY, MS_RDONLY, NULL); diff --git a/src/shared/mount-util.c b/src/shared/mount-util.c index 71ee5cf734..a0db226c25 100644 --- a/src/shared/mount-util.c +++ b/src/shared/mount-util.c @@ -1441,6 +1441,28 @@ int make_userns(uid_t uid_shift, return TAKE_FD(userns_fd); } +int open_tree_attr_fallback(int dir_fd, const char *path, unsigned int flags, struct mount_attr *attr) { + assert(attr); + + _cleanup_close_ int fd = open_tree_attr(dir_fd, path, flags, attr, sizeof(struct mount_attr)); + if (fd >= 0) + return TAKE_FD(fd); + if (!ERRNO_IS_NOT_SUPPORTED(errno)) + return log_debug_errno(errno, "Failed to open tree and set mount attributes: %m"); + + if (attr->attr_clr & MOUNT_ATTR_IDMAP) + return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Cannot clear idmap from mount without open_tree_attr()"); + + fd = open_tree(dir_fd, path, flags); + if (fd < 0) + return log_debug_errno(errno, "Failed to open tree: %m"); + + if (mount_setattr(fd, "", AT_EMPTY_PATH | (flags & AT_RECURSIVE), attr, sizeof(struct mount_attr)) < 0) + return log_debug_errno(errno, "Failed to change mount attributes: %m"); + + return TAKE_FD(fd); +} + int remount_idmap_fd( char **paths, int userns_fd, @@ -1469,22 +1491,19 @@ int remount_idmap_fd( CLEANUP_ARRAY(mount_fds, n_mounts_fds, close_many_and_free); for (size_t i = 0; i < n; i++) { - int mntfd; - - /* Clone the mount point */ - mntfd = mount_fds[n_mounts_fds] = open_tree(-EBADF, paths[i], OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC); - if (mount_fds[n_mounts_fds] < 0) - return log_debug_errno(errno, "Failed to open tree of mounted filesystem '%s': %m", paths[i]); - - n_mounts_fds++; - - /* Set the user namespace mapping attribute on the cloned mount point */ - if (mount_setattr(mntfd, "", AT_EMPTY_PATH, - &(struct mount_attr) { + /* Clone the mount point and et the user namespace mapping attribute on the cloned mount point. */ + mount_fds[n_mounts_fds] = open_tree_attr_fallback( + /* dir_fd= */ -EBADF, + paths[i], + OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC, + &(struct mount_attr) { .attr_set = MOUNT_ATTR_IDMAP | extra_mount_attr_set, .userns_fd = userns_fd, - }, sizeof(struct mount_attr)) < 0) - return log_debug_errno(errno, "Failed to change bind mount attributes for clone of '%s': %m", paths[i]); + }); + if (mount_fds[n_mounts_fds] < 0) + return mount_fds[n_mounts_fds]; + + n_mounts_fds++; } for (size_t i = n; i > 0; i--) { /* Unmount the paths right-to-left */ diff --git a/src/shared/mount-util.h b/src/shared/mount-util.h index 92ce4c5816..2f10bc896b 100644 --- a/src/shared/mount-util.h +++ b/src/shared/mount-util.h @@ -148,6 +148,8 @@ typedef enum RemountIdmapping { _REMOUNT_IDMAPPING_INVALID = -EINVAL, } RemountIdmapping; +int open_tree_attr_fallback(int dir_fd, const char *path, unsigned int flags, struct mount_attr *attr); + int make_userns(uid_t uid_shift, uid_t uid_range, uid_t host_owner, uid_t dest_owner, RemountIdmapping idmapping); int remount_idmap_fd(char **p, int userns_fd, uint64_t extra_mount_attr_set); int remount_idmap(char **p, uid_t uid_shift, uid_t uid_range, uid_t host_owner, uid_t dest_owner, RemountIdmapping idmapping);