From 3bb59e28f992bab5745fcc27a8216ab6822e25c7 Mon Sep 17 00:00:00 2001 From: DaanDeMeyer Date: Fri, 4 Jul 2025 18:56:42 +0200 Subject: [PATCH 1/4] basic: Use Static_assert() in missing_syscall_def.h Let's use Static_assert() so we don't have to include forward.h in missing_syscall_def.h. --- src/basic/missing_syscall_def.h | 16 +++++++--------- src/basic/missing_syscalls.py | 2 +- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/src/basic/missing_syscall_def.h b/src/basic/missing_syscall_def.h index fd8f3e5db5..4db051a0b0 100644 --- a/src/basic/missing_syscall_def.h +++ b/src/basic/missing_syscall_def.h @@ -6,8 +6,6 @@ */ #pragma once -#include "forward.h" - /* Note: if this code looks strange, this is because it is derived from the same * template as the per-syscall blocks below. */ # if defined(__aarch64__) @@ -100,7 +98,7 @@ /* may be an (invalid) negative number due to libseccomp, see PR 13319 */ # if defined __NR_close_range && __NR_close_range >= 0 # if defined systemd_NR_close_range -assert_cc(__NR_close_range == systemd_NR_close_range); +_Static_assert(__NR_close_range == systemd_NR_close_range, ""); # endif # else # if defined __NR_close_range @@ -168,7 +166,7 @@ assert_cc(__NR_close_range == systemd_NR_close_range); /* may be an (invalid) negative number due to libseccomp, see PR 13319 */ # if defined __NR_fchmodat2 && __NR_fchmodat2 >= 0 # if defined systemd_NR_fchmodat2 -assert_cc(__NR_fchmodat2 == systemd_NR_fchmodat2); +_Static_assert(__NR_fchmodat2 == systemd_NR_fchmodat2, ""); # endif # else # if defined __NR_fchmodat2 @@ -236,7 +234,7 @@ assert_cc(__NR_fchmodat2 == systemd_NR_fchmodat2); /* may be an (invalid) negative number due to libseccomp, see PR 13319 */ # if defined __NR_mount_setattr && __NR_mount_setattr >= 0 # if defined systemd_NR_mount_setattr -assert_cc(__NR_mount_setattr == systemd_NR_mount_setattr); +_Static_assert(__NR_mount_setattr == systemd_NR_mount_setattr, ""); # endif # else # if defined __NR_mount_setattr @@ -304,7 +302,7 @@ assert_cc(__NR_mount_setattr == systemd_NR_mount_setattr); /* may be an (invalid) negative number due to libseccomp, see PR 13319 */ # if defined __NR_openat2 && __NR_openat2 >= 0 # if defined systemd_NR_openat2 -assert_cc(__NR_openat2 == systemd_NR_openat2); +_Static_assert(__NR_openat2 == systemd_NR_openat2, ""); # endif # else # if defined __NR_openat2 @@ -372,7 +370,7 @@ assert_cc(__NR_openat2 == systemd_NR_openat2); /* may be an (invalid) negative number due to libseccomp, see PR 13319 */ # if defined __NR_quotactl_fd && __NR_quotactl_fd >= 0 # if defined systemd_NR_quotactl_fd -assert_cc(__NR_quotactl_fd == systemd_NR_quotactl_fd); +_Static_assert(__NR_quotactl_fd == systemd_NR_quotactl_fd, ""); # endif # else # if defined __NR_quotactl_fd @@ -440,7 +438,7 @@ assert_cc(__NR_quotactl_fd == systemd_NR_quotactl_fd); /* may be an (invalid) negative number due to libseccomp, see PR 13319 */ # if defined __NR_removexattrat && __NR_removexattrat >= 0 # if defined systemd_NR_removexattrat -assert_cc(__NR_removexattrat == systemd_NR_removexattrat); +_Static_assert(__NR_removexattrat == systemd_NR_removexattrat, ""); # endif # else # if defined __NR_removexattrat @@ -508,7 +506,7 @@ assert_cc(__NR_removexattrat == systemd_NR_removexattrat); /* may be an (invalid) negative number due to libseccomp, see PR 13319 */ # if defined __NR_setxattrat && __NR_setxattrat >= 0 # if defined systemd_NR_setxattrat -assert_cc(__NR_setxattrat == systemd_NR_setxattrat); +_Static_assert(__NR_setxattrat == systemd_NR_setxattrat, ""); # endif # else # if defined __NR_setxattrat diff --git a/src/basic/missing_syscalls.py b/src/basic/missing_syscalls.py index 0d351e322d..646ab4e34e 100644 --- a/src/basic/missing_syscalls.py +++ b/src/basic/missing_syscalls.py @@ -96,7 +96,7 @@ DEF_TEMPLATE_C = '''\ /* may be an (invalid) negative number due to libseccomp, see PR 13319 */ # if defined __NR_{syscall} && __NR_{syscall} >= 0 # if defined systemd_NR_{syscall} -assert_cc(__NR_{syscall} == systemd_NR_{syscall}); +_Static_assert(__NR_{syscall} == systemd_NR_{syscall}, ""); # endif # else # if defined __NR_{syscall} From 2d9759ae6afc25a8cb36154bf5a97f5dcd23ae65 Mon Sep 17 00:00:00 2001 From: DaanDeMeyer Date: Fri, 4 Jul 2025 20:18:39 +0200 Subject: [PATCH 2/4] basic: Add open_tree_attr() syscall wrapper --- meson.build | 1 + src/basic/include/sys/mount.h | 10 +++++ src/basic/missing_syscall_def.h | 68 +++++++++++++++++++++++++++++++++ src/basic/missing_syscalls.py | 1 + 4 files changed, 80 insertions(+) diff --git a/meson.build b/meson.build index 55874ee6c6..127daa419b 100644 --- a/meson.build +++ b/meson.build @@ -607,6 +607,7 @@ foreach ident : [ ['pivot_root', '''#include '''], # no known header declares pivot_root ['setxattrat', '''#include '''], # no known header declares setxattrat ['removexattrat', '''#include '''], # no known header declares removexattrat + ['open_tree_attr', '''#include '''], # no known header declares open_tree_attr ] have = cc.has_function(ident[0], prefix : ident[1], args : '-D_GNU_SOURCE') diff --git a/src/basic/include/sys/mount.h b/src/basic/include/sys/mount.h index 3f42a61233..3c9cd8ef5b 100644 --- a/src/basic/include/sys/mount.h +++ b/src/basic/include/sys/mount.h @@ -120,3 +120,13 @@ static inline int missing_mount_setattr(int dfd, const char *path, unsigned flag } # define mount_setattr missing_mount_setattr #endif + +#if HAVE_OPEN_TREE_ATTR +extern int open_tree_attr(int __dfd, const char *__filename, unsigned int __flags, struct mount_attr *__uattr, size_t __usize) __THROW; +#else +static inline int missing_open_tree_attr(int dfd, const char *filename, unsigned int flags, struct mount_attr *attr, size_t size) { + return syscall(__NR_open_tree_attr, dfd, filename, flags, attr, size); +} + +# define open_tree_attr missing_open_tree_attr +#endif diff --git a/src/basic/missing_syscall_def.h b/src/basic/missing_syscall_def.h index 4db051a0b0..a34cb36f14 100644 --- a/src/basic/missing_syscall_def.h +++ b/src/basic/missing_syscall_def.h @@ -517,3 +517,71 @@ _Static_assert(__NR_setxattrat == systemd_NR_setxattrat, ""); # endif # endif #endif + +#ifndef __IGNORE_open_tree_attr +# if defined(__aarch64__) +# define systemd_NR_open_tree_attr 467 +# elif defined(__alpha__) +# define systemd_NR_open_tree_attr 577 +# elif defined(__arc__) || defined(__tilegx__) +# define systemd_NR_open_tree_attr 467 +# elif defined(__arm__) +# define systemd_NR_open_tree_attr 467 +# elif defined(__i386__) +# define systemd_NR_open_tree_attr 467 +# elif defined(__ia64__) +# define systemd_NR_open_tree_attr -1 +# elif defined(__loongarch_lp64) +# define systemd_NR_open_tree_attr 467 +# elif defined(__m68k__) +# define systemd_NR_open_tree_attr 467 +# elif defined(_MIPS_SIM) +# if _MIPS_SIM == _MIPS_SIM_ABI32 +# define systemd_NR_open_tree_attr 4467 +# elif _MIPS_SIM == _MIPS_SIM_NABI32 +# define systemd_NR_open_tree_attr 6467 +# elif _MIPS_SIM == _MIPS_SIM_ABI64 +# define systemd_NR_open_tree_attr 5467 +# else +# error "Unknown MIPS ABI" +# endif +# elif defined(__hppa__) +# define systemd_NR_open_tree_attr 467 +# elif defined(__powerpc__) +# define systemd_NR_open_tree_attr 467 +# elif defined(__riscv) +# if __riscv_xlen == 32 +# define systemd_NR_open_tree_attr 467 +# elif __riscv_xlen == 64 +# define systemd_NR_open_tree_attr 467 +# else +# error "Unknown RISC-V ABI" +# endif +# elif defined(__s390__) +# define systemd_NR_open_tree_attr 467 +# elif defined(__sparc__) +# define systemd_NR_open_tree_attr 467 +# elif defined(__x86_64__) +# if defined(__ILP32__) +# define systemd_NR_open_tree_attr (467 | /* __X32_SYSCALL_BIT */ 0x40000000) +# else +# define systemd_NR_open_tree_attr 467 +# endif +# elif !defined(missing_arch_template) +# warning "open_tree_attr() syscall number is unknown for your architecture" +# endif + +/* may be an (invalid) negative number due to libseccomp, see PR 13319 */ +# if defined __NR_open_tree_attr && __NR_open_tree_attr >= 0 +# if defined systemd_NR_open_tree_attr +_Static_assert(__NR_open_tree_attr == systemd_NR_open_tree_attr, ""); +# endif +# else +# if defined __NR_open_tree_attr +# undef __NR_open_tree_attr +# endif +# if defined systemd_NR_open_tree_attr && systemd_NR_open_tree_attr >= 0 +# define __NR_open_tree_attr systemd_NR_open_tree_attr +# endif +# endif +#endif diff --git a/src/basic/missing_syscalls.py b/src/basic/missing_syscalls.py index 646ab4e34e..dd26b76b4c 100644 --- a/src/basic/missing_syscalls.py +++ b/src/basic/missing_syscalls.py @@ -9,6 +9,7 @@ SYSCALLS = [ 'close_range', # defined in glibc header since glibc-2.33 'fchmodat2', # defined in glibc header since glibc-2.39 'mount_setattr', # defined in glibc header since glibc-2.34 + 'open_tree_attr', 'openat2', # defined in glibc header since glibc-2.32 'quotactl_fd', # defined in glibc header since glibc-2.35 'removexattrat', From bda934d4e52794060deb204ee74fb62ab924e98f Mon Sep 17 00:00:00 2001 From: DaanDeMeyer Date: Fri, 4 Jul 2025 20:19:06 +0200 Subject: [PATCH 3/4] forward: Add struct mount_attr forward declaration --- src/basic/forward.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/basic/forward.h b/src/basic/forward.h index 6b6d68dcb4..7b8ce78b98 100644 --- a/src/basic/forward.h +++ b/src/basic/forward.h @@ -36,6 +36,7 @@ struct in_addr; struct in6_addr; struct inotify_event; struct iovec; +struct mount_attr; struct msghdr; struct passwd; struct pollfd; From 90fa161b5ba29d58953e9f08ddca49121b51efe6 Mon Sep 17 00:00:00 2001 From: DaanDeMeyer Date: Fri, 4 Jul 2025 20:19:26 +0200 Subject: [PATCH 4/4] nspawn: Support idmapped mounts on homed managed home directories 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 | 31 ++++++++++++++++++++++---- src/shared/mount-util.c | 47 +++++++++++++++++++++++++++------------ src/shared/mount-util.h | 2 ++ 3 files changed, 62 insertions(+), 18 deletions(-) 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);