]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
nspawn: Support idmapped mounts on homed managed home directories 38069/head
authorDaanDeMeyer <daan.j.demeyer@gmail.com>
Fri, 4 Jul 2025 18:19:26 +0000 (20:19 +0200)
committerDaanDeMeyer <daan.j.demeyer@gmail.com>
Mon, 7 Jul 2025 11:58:52 +0000 (13:58 +0200)
Christian made this possible in Linux 6.15 with a new system call
open_tree_attr() that combines open_tree() and mount_setattr().
Because idmapped mounts are (rightfully) not nested, we have to do
some extra shenanigans to make source we're putting the right source
uid in the userns for any idmapped mounts that we do in nspawn.

Of course we also add the necessary boilerplate to make open_tree_attr()
available in our code and wrap open_tree_attr() and the corresponding
fallback in a new function which we then use everywhere else.

src/nspawn/nspawn-mount.c
src/shared/mount-util.c
src/shared/mount-util.h

index d4ac649c9fb2c1ab08ea2910b9ac9bedca563c19..2c538562721c618855b54c2bb2df5ca2fb0a1a53 100644 (file)
@@ -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);
index 71ee5cf734ca8003ccb7fec357adc814597c46e7..a0db226c259c0865a910d58f85fca8573b13c2e5 100644 (file)
@@ -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);
+                /* 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,
+                                });
                 if (mount_fds[n_mounts_fds] < 0)
-                        return log_debug_errno(errno, "Failed to open tree of mounted filesystem '%s': %m", paths[i]);
+                        return mount_fds[n_mounts_fds];
 
                 n_mounts_fds++;
-
-                /* Set the user namespace mapping attribute on the cloned mount point */
-                if (mount_setattr(mntfd, "", AT_EMPTY_PATH,
-                                  &(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]);
         }
 
         for (size_t i = n; i > 0; i--) { /* Unmount the paths right-to-left */
index 92ce4c5816ce1db717875f41f7cdecfd62447c58..2f10bc896badb0788e1b544007a4bc4b4ca35fab 100644 (file)
@@ -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);