From 1aeba33d8ffcc4882566881420e1106c9dff61f3 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Mon, 5 Jan 2026 13:24:21 +0900 Subject: [PATCH 01/10] Bump required minimum kernel version to 5.10 and the recommended baseline to 5.14. The previous minimum required version 5.4 will be EOL on 2025-12. Let's bump the required minimum kernel version to the next LTS release 5.10 (released on 2020-12-13, EOL on 2026-12, CIP support until 2031-01). The new recommended baseline 5.14 is the version that CentOS 9 uses. CentOS 9 will EOL on 2027-05. See also #38608. --- README | 23 +++++++++++------------ src/basic/constants.h | 2 +- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/README b/README index 766863e4bc..ce25c1e3a1 100644 --- a/README +++ b/README @@ -47,25 +47,24 @@ REQUIREMENTS: ≥ 5.3 for bounded loops in BPF program, keyring namespacing, and nexthop support ≥ 5.4 for pidfd and signed Verity images - - ⛔ Kernel versions below 5.4 ("minimum baseline") are not supported at all, - and are missing required functionality as listed above. - - Linux kernel ≥ 5.6 for getrandom() GRND_INSECURE + ≥ 5.6 for getrandom() GRND_INSECURE ≥ 5.7 for CLONE_INTO_CGROUP, cgroup2fs memory_recursiveprot option, BPF links and the BPF LSM hook + ≥ 5.8 for LOOP_CONFIGURE and STATX_ATTR_MOUNT_ROOT + ≥ 5.9 for close_range() - ⚠️ Kernel versions below 5.7 ("recommended baseline") have significant gaps + ⛔ Kernel versions below 5.10 ("minimum baseline") are not supported at all, + and are missing required functionality as listed above. + + Linux kernel ≥ 5.12 for idmapped mount + ≥ 5.14 for cgroup.kill and quotactl_fd() + + ⚠️ Kernel versions below 5.14 ("recommended baseline") have significant gaps in functionality and are not recommended for use with this version of systemd. Taint flag 'old-kernel' will be set. systemd will most likely still function, but upstream support and testing are limited. - Linux kernel ≥ 5.8 for LOOP_CONFIGURE and STATX_ATTR_MOUNT_ROOT - ≥ 5.9 for close_range() - ≥ 5.12 for idmapped mount - ≥ 5.14 for cgroup.kill - ≥ 5.14 for quotactl_fd() - ≥ 6.3 for MFD_EXEC/MFD_NOEXEC_SEAL and tmpfs noswap option + Linux kernel ≥ 6.3 for MFD_EXEC/MFD_NOEXEC_SEAL and tmpfs noswap option ≥ 6.5 for name_to_handle_at() AT_HANDLE_FID, SO_PEERPIDFD/SO_PASSPIDFD, and MOVE_MOUNT_BENEATH ≥ 6.6 for quota support on tmpfs diff --git a/src/basic/constants.h b/src/basic/constants.h index a26cff4062..ab1ae59437 100644 --- a/src/basic/constants.h +++ b/src/basic/constants.h @@ -68,4 +68,4 @@ #define VARLINK_PATH_MACHINED_RESOLVE_HOOK "/run/systemd/resolve.hook/io.systemd.Machine" /* Recommended baseline - see README for details */ -#define KERNEL_BASELINE_VERSION "5.7" +#define KERNEL_BASELINE_VERSION "5.14" From ec21e5c58ebfc1ea7d76b94f8f6ec50fefe39866 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Mon, 5 Jan 2026 13:24:27 +0900 Subject: [PATCH 02/10] random-util: assume getrandom(GRND_INSECURE) works GRND_INSECURE was added in kernel 5.6, and our baseline on kernel is 5.10. Let's assume it always works. Even if it does not work, we have further fallback logics. So, this should be safe. --- src/basic/random-util.c | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/src/basic/random-util.c b/src/basic/random-util.c index ece0e41943..d05be6fa50 100644 --- a/src/basic/random-util.c +++ b/src/basic/random-util.c @@ -68,8 +68,6 @@ static void fallback_random_bytes(void *p, size_t n) { } void random_bytes(void *p, size_t n) { - static bool have_grndinsecure = true; - assert(p || n == 0); if (n == 0) @@ -78,15 +76,9 @@ void random_bytes(void *p, size_t n) { for (;;) { ssize_t l; - l = getrandom(p, n, have_grndinsecure ? GRND_INSECURE : GRND_NONBLOCK); - if (l < 0 && errno == EINVAL && have_grndinsecure) { - /* No GRND_INSECURE; fallback to GRND_NONBLOCK. */ - have_grndinsecure = false; - continue; - } + l = getrandom(p, n, GRND_INSECURE); if (l <= 0) - break; /* Will block (with GRND_NONBLOCK), or unexpected error. Give up and fallback - to /dev/urandom. */ + break; /* Unexpected error. Give up and fallback to /dev/urandom. */ if ((size_t) l == n) return; /* Done reading, success. */ From a98a6eb95cc980edab4b0f9c59e6573edc7ffe0c Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Mon, 5 Jan 2026 13:24:37 +0900 Subject: [PATCH 03/10] tree-wide: statx() supports STATX_ATTR_MOUNT_ROOT since kernel 5.8 Our baseline on kernel is 5.10, hence we can unconditionally use it. --- src/basic/mountpoint-util.c | 112 +----------------------------------- src/basic/stat-util.c | 11 ++++ src/basic/stat-util.h | 2 + src/shared/find-esp.c | 37 +++--------- src/tmpfiles/tmpfiles.c | 53 +++++------------ 5 files changed, 38 insertions(+), 177 deletions(-) diff --git a/src/basic/mountpoint-util.c b/src/basic/mountpoint-util.c index b7c4870931..b02746b14c 100644 --- a/src/basic/mountpoint-util.c +++ b/src/basic/mountpoint-util.c @@ -198,7 +198,6 @@ bool file_handle_equal(const struct file_handle *a, const struct file_handle *b) } int is_mount_point_at(int fd, const char *filename, int flags) { - bool fd_is_self; int r; assert(fd >= 0 || fd == AT_FDCWD); @@ -216,36 +215,13 @@ int is_mount_point_at(int fd, const char *filename, int flags) { filename = ""; } - fd_is_self = true; - } else if (STR_IN_SET(filename, ".", "./")) - fd_is_self = true; - else { + } else if (!STR_IN_SET(filename, ".", "./")) { /* Insist that the specified filename is actually a filename, and not a path, i.e. some inode * further up or down the tree then immediately below the specified directory fd. */ if (!filename_possibly_with_slash_suffix(filename)) return -EINVAL; - - fd_is_self = false; } - /* First we will try statx()' STATX_ATTR_MOUNT_ROOT attribute, which is our ideal API, available - * since kernel 5.8. - * - * If that fails, our second try is the name_to_handle_at() syscall, which tells us the mount id and - * an opaque file "handle". It is not supported everywhere though (kernel compile-time option, not - * all file systems are hooked up). If it works the mount id is usually good enough to tell us - * whether something is a mount point. - * - * If that didn't work we will try to read the mount id from /proc/self/fdinfo/. This is almost - * as good as name_to_handle_at(), however, does not return the opaque file handle. The opaque file - * handle is pretty useful to detect the root directory, which we should always consider a mount - * point. Hence we use this only as fallback. - * - * Note that traditionally the check is done via fstat()-based st_dev comparisons. However, various - * file systems don't guarantee same st_dev across single fs anymore, e.g. unionfs exposes file systems - * with a variety of st_dev reported. Also, btrfs subvolumes have different st_dev, even though - * they aren't real mounts of their own. */ - struct statx sx = {}; /* explicitly initialize the struct to make msan silent. */ if (statx(fd, filename, at_flags_normalize_nofollow(flags) | @@ -255,93 +231,11 @@ int is_mount_point_at(int fd, const char *filename, int flags) { &sx) < 0) return -errno; - if (FLAGS_SET(sx.stx_attributes_mask, STATX_ATTR_MOUNT_ROOT)) /* yay! */ - return FLAGS_SET(sx.stx_attributes, STATX_ATTR_MOUNT_ROOT); - - _cleanup_free_ struct file_handle *h = NULL, *h_parent = NULL; - int mount_id = -1, mount_id_parent = -1; - bool nosupp = false; - - r = name_to_handle_at_try_fid(fd, filename, &h, &mount_id, flags); - if (r < 0) { - if (is_name_to_handle_at_fatal_error(r)) - return r; - if (!ERRNO_IS_NOT_SUPPORTED(r)) - goto fallback_fdinfo; - - /* This file system does not support name_to_handle_at(), hence let's see if the upper fs - * supports it (in which case it is a mount point), otherwise fall back to the fdinfo logic. */ - nosupp = true; - } - - if (fd_is_self) - r = name_to_handle_at_try_fid(fd, "..", &h_parent, &mount_id_parent, 0); /* can't work for non-directories 😢 */ - else - r = name_to_handle_at_try_fid(fd, "", &h_parent, &mount_id_parent, AT_EMPTY_PATH); - if (r < 0) { - if (is_name_to_handle_at_fatal_error(r)) - return r; - if (!ERRNO_IS_NOT_SUPPORTED(r)) - goto fallback_fdinfo; - if (nosupp) - /* Both the parent and the directory can't do name_to_handle_at() */ - goto fallback_fdinfo; - - /* The parent can't do name_to_handle_at() but the directory we are - * interested in can? If so, it must be a mount point. */ - return 1; - } - - /* The parent can do name_to_handle_at() but the directory we are interested in can't? If - * so, it must be a mount point. */ - if (nosupp) - return 1; - - /* If the file handle for the directory we are interested in and its parent are identical, - * we assume this is the root directory, which is a mount point. */ - if (file_handle_equal(h_parent, h)) - return 1; - - return mount_id != mount_id_parent; - -fallback_fdinfo: - r = fd_fdinfo_mnt_id(fd, filename, flags, &mount_id); + r = statx_warn_mount_root(&sx, LOG_DEBUG); if (r < 0) return r; - if (fd_is_self) - r = fd_fdinfo_mnt_id(fd, "..", 0, &mount_id_parent); /* can't work for non-directories 😢 */ - else - r = fd_fdinfo_mnt_id(fd, "", AT_EMPTY_PATH, &mount_id_parent); - if (r < 0) - return r; - - if (mount_id != mount_id_parent) - return 1; - - /* Hmm, so, the mount ids are the same. This leaves one special case though for the root file - * system. For that, let's see if the parent directory has the same inode as we are interested - * in. */ - - struct stat a, b; - - /* yay for fstatat() taking a different set of flags than the other _at() above */ - if (fstatat(fd, filename, &a, at_flags_normalize_nofollow(flags)) < 0) - return -errno; - - if (fd_is_self) - r = fstatat(fd, "..", &b, 0); - else - r = fstatat(fd, "", &b, AT_EMPTY_PATH); - if (r < 0) - return -errno; - - /* A directory with same device and inode as its parent must be the root directory. Otherwise - * not a mount point. - * - * NB: we avoid inode_same_at() here because it internally attempts name_to_handle_at_try_fid() first, - * which is redundant. */ - return stat_inode_same(&a, &b); + return FLAGS_SET(sx.stx_attributes, STATX_ATTR_MOUNT_ROOT); } /* flags can be AT_SYMLINK_FOLLOW or 0 */ diff --git a/src/basic/stat-util.c b/src/basic/stat-util.c index 9f2587fadd..791e99d978 100644 --- a/src/basic/stat-util.c +++ b/src/basic/stat-util.c @@ -572,3 +572,14 @@ mode_t inode_type_from_string(const char *s) { return MODE_INVALID; } + +int statx_warn_mount_root(const struct statx *sx, int log_level) { + assert(sx); + + /* The STATX_ATTR_MOUNT_ROOT flag is supported since kernel v5.8. */ + if (!FLAGS_SET(sx->stx_attributes_mask, STATX_ATTR_MOUNT_ROOT)) + return log_full_errno(log_level, SYNTHETIC_ERRNO(ENOSYS), + "statx() did not set STATX_ATTR_MOUNT_ROOT, running on an old kernel?"); + + return 0; +} diff --git a/src/basic/stat-util.h b/src/basic/stat-util.h index 395e391786..a0a4489f74 100644 --- a/src/basic/stat-util.h +++ b/src/basic/stat-util.h @@ -117,3 +117,5 @@ static inline bool inode_type_can_hardlink(mode_t m) { * type). */ return IN_SET(m & S_IFMT, S_IFSOCK, S_IFLNK, S_IFREG, S_IFBLK, S_IFCHR, S_IFIFO); } + +int statx_warn_mount_root(const struct statx *sx, int log_level); diff --git a/src/shared/find-esp.c b/src/shared/find-esp.c index ca8d99922d..8e1737eebb 100644 --- a/src/shared/find-esp.c +++ b/src/shared/find-esp.c @@ -268,7 +268,7 @@ static int verify_fsroot_dir( bool searching = FLAGS_SET(flags, VERIFY_ESP_SEARCHING), unprivileged_mode = FLAGS_SET(flags, VERIFY_ESP_UNPRIVILEGED_MODE); _cleanup_free_ char *f = NULL; - struct statx sxa, sxb; + struct statx sx; int r; /* Checks if the specified directory is at the root of its file system, and returns device @@ -287,49 +287,30 @@ static int verify_fsroot_dir( if (statx(dir_fd, strempty(f), AT_SYMLINK_NOFOLLOW|(isempty(f) ? AT_EMPTY_PATH : 0), - STATX_TYPE|STATX_INO|STATX_MNT_ID, &sxa) < 0) + STATX_TYPE|STATX_INO|STATX_MNT_ID, &sx) < 0) return log_full_errno((searching && errno == ENOENT) || (unprivileged_mode && ERRNO_IS_PRIVILEGE(errno)) ? LOG_DEBUG : LOG_ERR, errno, "Failed to determine block device node of \"%s\": %m", path); - if (!S_ISDIR(sxa.stx_mode)) + if (!S_ISDIR(sx.stx_mode)) return log_error_errno(SYNTHETIC_ERRNO(ENOTDIR), "Path \"%s\" is not a directory", path); - if (FLAGS_SET(sxa.stx_attributes_mask, STATX_ATTR_MOUNT_ROOT)) { + r = statx_warn_mount_root(&sx, LOG_ERR); + if (r < 0) + return r; - /* If we have STATX_ATTR_MOUNT_ROOT, we are happy, that's all we need. We operate under the - * assumption that a top of a mount point is also the top of the file system. (Which of - * course is strictly speaking not always true...) */ - - if (!FLAGS_SET(sxa.stx_attributes, STATX_ATTR_MOUNT_ROOT)) - return log_full_errno(searching ? LOG_DEBUG : LOG_ERR, - SYNTHETIC_ERRNO(searching ? EADDRNOTAVAIL : ENODEV), - "Directory \"%s\" is not the root of the file system.", path); - - goto success; - } - - /* Now let's look at the parent */ - if (statx(dir_fd, "", AT_EMPTY_PATH, STATX_TYPE|STATX_INO|STATX_MNT_ID, &sxb) < 0) - return log_full_errno(unprivileged_mode && ERRNO_IS_PRIVILEGE(errno) ? LOG_DEBUG : LOG_ERR, errno, - "Failed to determine block device node of parent of \"%s\": %m", path); - - if (statx_inode_same(&sxa, &sxb)) /* for the root dir inode nr for both inodes will be the same */ - goto success; - - if (statx_mount_same(&sxa, &sxb)) + if (!FLAGS_SET(sx.stx_attributes, STATX_ATTR_MOUNT_ROOT)) return log_full_errno(searching ? LOG_DEBUG : LOG_ERR, SYNTHETIC_ERRNO(searching ? EADDRNOTAVAIL : ENODEV), "Directory \"%s\" is not the root of the file system.", path); -success: if (!ret_dev) return 0; - if (sxa.stx_dev_major == 0) /* Hmm, maybe a btrfs device, and the caller asked for the backing device? Then let's try to get it. */ + if (sx.stx_dev_major == 0) /* Hmm, maybe a btrfs device, and the caller asked for the backing device? Then let's try to get it. */ return btrfs_get_block_device_at(dir_fd, strempty(f), ret_dev); - *ret_dev = makedev(sxa.stx_dev_major, sxa.stx_dev_minor); + *ret_dev = makedev(sx.stx_dev_major, sx.stx_dev_minor); return 0; } diff --git a/src/tmpfiles/tmpfiles.c b/src/tmpfiles/tmpfiles.c index 90fe4b3d75..a2ab8ded9b 100644 --- a/src/tmpfiles/tmpfiles.c +++ b/src/tmpfiles/tmpfiles.c @@ -559,7 +559,7 @@ static int opendir_and_stat( bool *ret_mountpoint) { _cleanup_closedir_ DIR *d = NULL; - struct statx sx1; + struct statx sx; int r; assert(path); @@ -586,21 +586,16 @@ static int opendir_and_stat( return 0; } - if (statx(dirfd(d), "", AT_EMPTY_PATH, STATX_MODE|STATX_INO|STATX_ATIME|STATX_MTIME, &sx1) < 0) + if (statx(dirfd(d), "", AT_EMPTY_PATH, STATX_MODE|STATX_INO|STATX_ATIME|STATX_MTIME, &sx) < 0) return log_error_errno(errno, "statx(%s) failed: %m", path); - if (FLAGS_SET(sx1.stx_attributes_mask, STATX_ATTR_MOUNT_ROOT)) - *ret_mountpoint = FLAGS_SET(sx1.stx_attributes, STATX_ATTR_MOUNT_ROOT); - else { - struct statx sx2; - if (statx(dirfd(d), "..", 0, STATX_INO, &sx2) < 0) - return log_error_errno(errno, "statx(%s/..) failed: %m", path); - - *ret_mountpoint = !statx_mount_same(&sx1, &sx2); - } + r = statx_warn_mount_root(&sx, LOG_ERR); + if (r < 0) + return r; + *ret_mountpoint = FLAGS_SET(sx.stx_attributes, STATX_ATTR_MOUNT_ROOT); *ret = TAKE_PTR(d); - *ret_sx = sx1; + *ret_sx = sx; return 1; } @@ -713,35 +708,13 @@ static int dir_cleanup( continue; } - if (FLAGS_SET(sx.stx_attributes_mask, STATX_ATTR_MOUNT_ROOT)) { - /* Yay, we have the mount point API, use it */ - if (FLAGS_SET(sx.stx_attributes, STATX_ATTR_MOUNT_ROOT)) { - log_debug("Ignoring \"%s/%s\": different mount points.", p, de->d_name); - continue; - } - } else { - /* So we might have statx() but the STATX_ATTR_MOUNT_ROOT flag is not supported, fall - * back to traditional stx_dev checking. */ - if (sx.stx_dev_major != rootdev_major || - sx.stx_dev_minor != rootdev_minor) { - log_debug("Ignoring \"%s/%s\": different filesystem.", p, de->d_name); - continue; - } + r = statx_warn_mount_root(&sx, LOG_ERR); + if (r < 0) + return r; - /* Try to detect bind mounts of the same filesystem instance; they do not differ in - * device major/minors. This type of query is not supported on all kernels or - * filesystem types though. */ - if (S_ISDIR(sx.stx_mode)) { - int q; - - q = is_mount_point_at(dirfd(d), de->d_name, 0); - if (q < 0) - log_debug_errno(q, "Failed to determine whether \"%s/%s\" is a mount point, ignoring: %m", p, de->d_name); - else if (q > 0) { - log_debug("Ignoring \"%s/%s\": different mount of the same filesystem.", p, de->d_name); - continue; - } - } + if (FLAGS_SET(sx.stx_attributes, STATX_ATTR_MOUNT_ROOT)) { + log_debug("Ignoring \"%s/%s\": different mount points.", p, de->d_name); + continue; } atime_nsec = FLAGS_SET(sx.stx_mask, STATX_ATIME) ? statx_timestamp_load_nsec(&sx.stx_atime) : 0; From 7f6cf195130e2ad69ad8ae901bfe56feb0c9a9d8 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Mon, 5 Jan 2026 13:24:39 +0900 Subject: [PATCH 04/10] mountpoint-util: make is_mount_point_at() take usual dir_fd + path style arguments --- src/basic/mountpoint-util.c | 91 ++++++++++++--------------------- src/basic/mountpoint-util.h | 2 +- src/core/namespace.c | 2 +- src/mount/mount-tool.c | 2 +- src/nspawn/nspawn-cgroup.c | 2 +- src/test/test-mountpoint-util.c | 22 ++++---- src/validatefs/validatefs.c | 2 +- 7 files changed, 49 insertions(+), 74 deletions(-) diff --git a/src/basic/mountpoint-util.c b/src/basic/mountpoint-util.c index b02746b14c..1453340c55 100644 --- a/src/basic/mountpoint-util.c +++ b/src/basic/mountpoint-util.c @@ -163,29 +163,6 @@ static int fd_fdinfo_mnt_id(int fd, const char *filename, int flags, int *ret_mn return safe_atoi(p, ret_mnt_id); } -static bool filename_possibly_with_slash_suffix(const char *s) { - const char *slash, *copied; - - /* Checks whether the specified string is either file name, or a filename with a suffix of - * slashes. But nothing else. - * - * this is OK: foo, bar, foo/, bar/, foo//, bar/// - * this is not OK: "", "/", "/foo", "foo/bar", ".", ".." … */ - - slash = strchr(s, '/'); - if (!slash) - return filename_is_valid(s); - - if (slash - s > PATH_MAX) /* We want to allocate on the stack below, hence do a size check first */ - return false; - - if (slash[strspn(slash, "/")] != 0) /* Check that the suffix consist only of one or more slashes */ - return false; - - copied = strndupa_safe(s, slash - s); - return filename_is_valid(copied); -} - bool file_handle_equal(const struct file_handle *a, const struct file_handle *b) { if (a == b) return true; @@ -197,37 +174,30 @@ bool file_handle_equal(const struct file_handle *a, const struct file_handle *b) return memcmp_nn(a->f_handle, a->handle_bytes, b->f_handle, b->handle_bytes) == 0; } -int is_mount_point_at(int fd, const char *filename, int flags) { +int is_mount_point_at(int dir_fd, const char *path, int flags) { int r; - assert(fd >= 0 || fd == AT_FDCWD); + assert(dir_fd >= 0 || dir_fd == AT_FDCWD); assert((flags & ~AT_SYMLINK_FOLLOW) == 0); - if (isempty(filename)) { - if (fd == AT_FDCWD) - filename = "."; - else { - /* If the file name is empty we'll see if the specified 'fd' is a mount point. - * That's only supported by statx(), or if the inode specified via 'fd' refers to a - * directory. Otherwise, we'll have to fail (ENOTDIR), because we have no kernel API - * to query the information we need. */ - flags |= AT_EMPTY_PATH; - filename = ""; - } + if (path_equal(path, "/")) + return true; - } else if (!STR_IN_SET(filename, ".", "./")) { - /* Insist that the specified filename is actually a filename, and not a path, i.e. some inode - * further up or down the tree then immediately below the specified directory fd. */ - if (!filename_possibly_with_slash_suffix(filename)) - return -EINVAL; + if (isempty(path)) { + if (dir_fd == AT_FDCWD) + path = "."; + else { + flags |= AT_EMPTY_PATH; + path = ""; + } } struct statx sx = {}; /* explicitly initialize the struct to make msan silent. */ - if (statx(fd, filename, + if (statx(dir_fd, path, at_flags_normalize_nofollow(flags) | AT_NO_AUTOMOUNT | /* don't trigger automounts – mounts are a local concept, hence no need to trigger automounts to determine STATX_ATTR_MOUNT_ROOT */ AT_STATX_DONT_SYNC, /* don't go to the network for this – for similar reasons */ - STATX_TYPE, + STATX_TYPE|STATX_INO, &sx) < 0) return -errno; @@ -235,30 +205,37 @@ int is_mount_point_at(int fd, const char *filename, int flags) { if (r < 0) return r; - return FLAGS_SET(sx.stx_attributes, STATX_ATTR_MOUNT_ROOT); + if (FLAGS_SET(sx.stx_attributes, STATX_ATTR_MOUNT_ROOT)) + return true; + + /* When running on chroot environment, the root may not be a mount point, but we unconditionally + * return true when the input is "/" in the above, but the shortcut may not work e.g. when the path + * is relative. */ + struct statx sx2 = {}; /* explicitly initialize the struct to make msan silent. */ + if (statx(AT_FDCWD, "/", AT_STATX_DONT_SYNC, STATX_TYPE|STATX_INO, &sx2) < 0) + return -errno; + + return statx_inode_same(&sx, &sx2); } /* flags can be AT_SYMLINK_FOLLOW or 0 */ int path_is_mount_point_full(const char *path, const char *root, int flags) { - _cleanup_close_ int dfd = -EBADF; - _cleanup_free_ char *fn = NULL; + _cleanup_close_ int dir_fd = -EBADF; + int r; assert(path); assert((flags & ~AT_SYMLINK_FOLLOW) == 0); - if (path_equal(path, "/")) - return 1; + if (empty_or_root(root)) + return is_mount_point_at(AT_FDCWD, path, flags); - /* we need to resolve symlinks manually, we can't just rely on is_mount_point_at() to do that for us; - * if we have a structure like /bin -> /usr/bin/ and /usr is a mount point, then the parent that we - * look at needs to be /usr, not /. */ - dfd = chase_and_open_parent(path, root, - CHASE_TRAIL_SLASH|(FLAGS_SET(flags, AT_SYMLINK_FOLLOW) ? 0 : CHASE_NOFOLLOW), - &fn); - if (dfd < 0) - return dfd; + r = chase(path, root, + FLAGS_SET(flags, AT_SYMLINK_FOLLOW) ? 0 : CHASE_NOFOLLOW, + /* ret_path= */ NULL, &dir_fd); + if (r < 0) + return r; - return is_mount_point_at(dfd, fn, flags); + return is_mount_point_at(dir_fd, /* path= */ NULL, flags); } int path_get_mnt_id_at_fallback(int dir_fd, const char *path, int *ret) { diff --git a/src/basic/mountpoint-util.h b/src/basic/mountpoint-util.h index 180d75343c..206815277b 100644 --- a/src/basic/mountpoint-util.h +++ b/src/basic/mountpoint-util.h @@ -45,7 +45,7 @@ static inline int path_get_mnt_id(const char *path, int *ret) { return path_get_mnt_id_at(AT_FDCWD, path, ret); } -int is_mount_point_at(int fd, const char *filename, int flags); +int is_mount_point_at(int dir_fd, const char *path, int flags); int path_is_mount_point_full(const char *path, const char *root, int flags); static inline int path_is_mount_point(const char *path) { return path_is_mount_point_full(path, NULL, 0); diff --git a/src/core/namespace.c b/src/core/namespace.c index 422c18d238..c35226d639 100644 --- a/src/core/namespace.c +++ b/src/core/namespace.c @@ -3467,7 +3467,7 @@ static int is_extension_overlay(const char *path, int fd) { fd = dfd; } - r = is_mount_point_at(fd, /* filename= */ NULL, /* flags= */ 0); + r = is_mount_point_at(fd, /* path= */ NULL, /* flags= */ 0); if (r < 0) return log_debug_errno(r, "Unable to determine whether '%s' is a mount point: %m", path); if (r == 0) diff --git a/src/mount/mount-tool.c b/src/mount/mount-tool.c index eae5512efc..7dd45aacea 100644 --- a/src/mount/mount-tool.c +++ b/src/mount/mount-tool.c @@ -1113,7 +1113,7 @@ static int action_umount(sd_bus *bus, int argc, char **argv) { if (fstat(fd, &st) < 0) return log_error_errno(errno, "Can't stat '%s' (from %s): %m", p, argv[i]); - r = is_mount_point_at(fd, /* filename= */ NULL, /* flags= */ 0); + r = is_mount_point_at(fd, /* path= */ NULL, /* flags= */ 0); fd = safe_close(fd); /* before continuing make sure the dir is not keeping anything busy */ if (r > 0) RET_GATHER(ret, stop_mounts(bus, p)); diff --git a/src/nspawn/nspawn-cgroup.c b/src/nspawn/nspawn-cgroup.c index 55ce8e1e06..4206fe94e5 100644 --- a/src/nspawn/nspawn-cgroup.c +++ b/src/nspawn/nspawn-cgroup.c @@ -147,7 +147,7 @@ int mount_cgroups(const char *dest, bool accept_existing) { if (r < 0) return log_error_errno(r, "Failed to chase %s/sys/fs/cgroup: %m", strempty(dest)); - r = is_mount_point_at(fd, /* filename= */ NULL, /* flags= */ 0); + r = is_mount_point_at(fd, /* path= */ NULL, /* flags= */ 0); if (r < 0) return log_error_errno(r, "Failed to determine if %s is mounted already: %m", p); if (r > 0) { diff --git a/src/test/test-mountpoint-util.c b/src/test/test-mountpoint-util.c index d4c830b476..ba2ed3b347 100644 --- a/src/test/test-mountpoint-util.c +++ b/src/test/test-mountpoint-util.c @@ -283,18 +283,16 @@ TEST(is_mount_point_at) { fd = open("/", O_RDONLY|O_CLOEXEC|O_DIRECTORY|O_NOCTTY); assert_se(fd >= 0); - /* Not allowed, since "/" is a path, not a plain filename */ - assert_se(is_mount_point_at(fd, "/", 0) == -EINVAL); - assert_se(is_mount_point_at(fd, "..", 0) == -EINVAL); - assert_se(is_mount_point_at(fd, "../", 0) == -EINVAL); - assert_se(is_mount_point_at(fd, "/proc", 0) == -EINVAL); - assert_se(is_mount_point_at(fd, "/proc/", 0) == -EINVAL); - assert_se(is_mount_point_at(fd, "proc/sys", 0) == -EINVAL); - assert_se(is_mount_point_at(fd, "proc/sys/", 0) == -EINVAL); - - /* This one definitely is a mount point */ - assert_se(is_mount_point_at(fd, "proc", 0) > 0); - assert_se(is_mount_point_at(fd, "proc/", 0) > 0); + ASSERT_OK_POSITIVE(is_mount_point_at(fd, "/", /* flags= */ 0)); + ASSERT_OK_POSITIVE(is_mount_point_at(fd, "..", /* flags= */ 0)); + ASSERT_OK_POSITIVE(is_mount_point_at(fd, "../", /* flags= */ 0)); + r = ASSERT_OK(proc_mounted()); + ASSERT_OK_EQ(is_mount_point_at(fd, "/proc", /* flags= */ 0), r); + ASSERT_OK_EQ(is_mount_point_at(fd, "/proc/", /* flags= */ 0), r); + ASSERT_OK_EQ(is_mount_point_at(fd, "proc", /* flags= */ 0), r); + ASSERT_OK_EQ(is_mount_point_at(fd, "proc/", /* flags= */ 0), r); + ASSERT_OK_ZERO(is_mount_point_at(fd, "usr/lib", /* flags= */ 0)); + ASSERT_OK_ZERO(is_mount_point_at(fd, "usr/lib", /* flags= */ 0)); safe_close(fd); fd = open("/tmp", O_RDONLY|O_CLOEXEC|O_DIRECTORY|O_NOCTTY); diff --git a/src/validatefs/validatefs.c b/src/validatefs/validatefs.c index 2ac3a3072f..74a784d8ab 100644 --- a/src/validatefs/validatefs.c +++ b/src/validatefs/validatefs.c @@ -426,7 +426,7 @@ static int run(int argc, char *argv[]) { if (target_fd < 0) return log_error_errno(target_fd, "Failed to open directory '%s': %m", arg_target); - r = is_mount_point_at(target_fd, /* filename= */ NULL, /* flags= */ 0); + r = is_mount_point_at(target_fd, /* path= */ NULL, /* flags= */ 0); if (r < 0) return log_error_errno(r, "Failed to determine whether '%s' is a mount point: %m", resolved); if (!r) From 7ecfa87c56f2523141e11694577df8a380694598 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Mon, 5 Jan 2026 13:24:40 +0900 Subject: [PATCH 05/10] fd-util,mountpoint-util: STATX_MNT_ID is supported since kernel 5.10 Our baseline on kernel is 5.10, hence we can assume it works. --- README | 1 + src/basic/fd-util.c | 36 +------------------------ src/basic/mountpoint-util.c | 54 +++++-------------------------------- src/basic/mountpoint-util.h | 1 - src/basic/stat-util.c | 20 +++++++++----- src/basic/stat-util.h | 1 + 6 files changed, 23 insertions(+), 90 deletions(-) diff --git a/README b/README index ce25c1e3a1..718a02d087 100644 --- a/README +++ b/README @@ -52,6 +52,7 @@ REQUIREMENTS: BPF links and the BPF LSM hook ≥ 5.8 for LOOP_CONFIGURE and STATX_ATTR_MOUNT_ROOT ≥ 5.9 for close_range() + ≥ 5.10 for STATX_MNT_ID ⛔ Kernel versions below 5.10 ("minimum baseline") are not supported at all, and are missing required functionality as listed above. diff --git a/src/basic/fd-util.c b/src/basic/fd-util.c index 834b162c47..fb4262e62b 100644 --- a/src/basic/fd-util.c +++ b/src/basic/fd-util.c @@ -16,7 +16,6 @@ #include "format-util.h" #include "fs-util.h" #include "log.h" -#include "mountpoint-util.h" #include "parse-util.h" #include "path-util.h" #include "process-util.h" @@ -1119,7 +1118,6 @@ int path_is_root_at(int dir_fd, const char *path) { int fds_are_same_mount(int fd1, int fd2) { struct statx sx1 = {}, sx2 = {}; /* explicitly initialize the struct to make msan silent. */ - int r; assert(fd1 >= 0); assert(fd2 >= 0); @@ -1130,39 +1128,7 @@ int fds_are_same_mount(int fd1, int fd2) { if (statx(fd2, "", AT_EMPTY_PATH, STATX_TYPE|STATX_INO|STATX_MNT_ID, &sx2) < 0) return -errno; - /* First, compare inode. If these are different, the fd does not point to the root directory "/". */ - if (!statx_inode_same(&sx1, &sx2)) - return false; - - /* Note, statx() does not provide the mount ID and path_get_mnt_id_at() does not work when an old - * kernel is used. In that case, let's assume that we do not have such spurious mount points in an - * early boot stage, and silently skip the following check. */ - - if (!FLAGS_SET(sx1.stx_mask, STATX_MNT_ID)) { - int mntid; - - r = path_get_mnt_id_at_fallback(fd1, "", &mntid); - if (r < 0) - return r; - assert(mntid >= 0); - - sx1.stx_mnt_id = mntid; - sx1.stx_mask |= STATX_MNT_ID; - } - - if (!FLAGS_SET(sx2.stx_mask, STATX_MNT_ID)) { - int mntid; - - r = path_get_mnt_id_at_fallback(fd2, "", &mntid); - if (r < 0) - return r; - assert(mntid >= 0); - - sx2.stx_mnt_id = mntid; - sx2.stx_mask |= STATX_MNT_ID; - } - - return statx_mount_same(&sx1, &sx2); + return statx_inode_same(&sx1, &sx2) && statx_mount_same(&sx1, &sx2); } char* format_proc_fd_path(char buf[static PROC_FD_PATH_MAX], int fd) { diff --git a/src/basic/mountpoint-util.c b/src/basic/mountpoint-util.c index 1453340c55..35bf3bdfcc 100644 --- a/src/basic/mountpoint-util.c +++ b/src/basic/mountpoint-util.c @@ -13,10 +13,8 @@ #include "log.h" #include "mountpoint-util.h" #include "nulstr-util.h" -#include "parse-util.h" #include "path-util.h" #include "stat-util.h" -#include "stdio-util.h" #include "string-util.h" #include "strv.h" @@ -135,34 +133,6 @@ int name_to_handle_at_try_fid( return name_to_handle_at_loop(fd, path, ret_handle, ret_mnt_id, flags & ~AT_HANDLE_FID); } -static int fd_fdinfo_mnt_id(int fd, const char *filename, int flags, int *ret_mnt_id) { - char path[STRLEN("/proc/self/fdinfo/") + DECIMAL_STR_MAX(int)]; - _cleanup_close_ int subfd = -EBADF; - int r; - - assert((flags & ~(AT_SYMLINK_FOLLOW|AT_EMPTY_PATH)) == 0); - assert(ret_mnt_id); - - if ((flags & AT_EMPTY_PATH) && isempty(filename)) - xsprintf(path, "/proc/self/fdinfo/%i", fd); - else { - subfd = openat(fd, filename, O_CLOEXEC|O_PATH|(flags & AT_SYMLINK_FOLLOW ? 0 : O_NOFOLLOW)); - if (subfd < 0) - return -errno; - - xsprintf(path, "/proc/self/fdinfo/%i", subfd); - } - - _cleanup_free_ char *p = NULL; - r = get_proc_field(path, "mnt_id", &p); - if (r == -ENOENT) - return -EBADF; - if (r < 0) - return r; - - return safe_atoi(p, ret_mnt_id); -} - bool file_handle_equal(const struct file_handle *a, const struct file_handle *b) { if (a == b) return true; @@ -238,21 +208,9 @@ int path_is_mount_point_full(const char *path, const char *root, int flags) { return is_mount_point_at(dir_fd, /* path= */ NULL, flags); } -int path_get_mnt_id_at_fallback(int dir_fd, const char *path, int *ret) { - int r; - - assert(dir_fd >= 0 || dir_fd == AT_FDCWD); - assert(ret); - - r = name_to_handle_at_loop(dir_fd, path, NULL, ret, isempty(path) ? AT_EMPTY_PATH : 0); - if (r >= 0 || is_name_to_handle_at_fatal_error(r)) - return r; - - return fd_fdinfo_mnt_id(dir_fd, path, isempty(path) ? AT_EMPTY_PATH : 0, ret); -} - int path_get_mnt_id_at(int dir_fd, const char *path, int *ret) { struct statx sx; + int r; assert(dir_fd >= 0 || dir_fd == AT_FDCWD); assert(ret); @@ -266,12 +224,12 @@ int path_get_mnt_id_at(int dir_fd, const char *path, int *ret) { &sx) < 0) return -errno; - if (FLAGS_SET(sx.stx_mask, STATX_MNT_ID)) { - *ret = sx.stx_mnt_id; - return 0; - } + r = statx_warn_mount_id(&sx, LOG_DEBUG); + if (r < 0) + return r; - return path_get_mnt_id_at_fallback(dir_fd, path, ret); + *ret = sx.stx_mnt_id; + return 0; } bool fstype_is_network(const char *fstype) { diff --git a/src/basic/mountpoint-util.h b/src/basic/mountpoint-util.h index 206815277b..899e9635d1 100644 --- a/src/basic/mountpoint-util.h +++ b/src/basic/mountpoint-util.h @@ -39,7 +39,6 @@ int name_to_handle_at_try_fid(int fd, const char *path, struct file_handle **ret bool file_handle_equal(const struct file_handle *a, const struct file_handle *b); -int path_get_mnt_id_at_fallback(int dir_fd, const char *path, int *ret); int path_get_mnt_id_at(int dir_fd, const char *path, int *ret); static inline int path_get_mnt_id(const char *path, int *ret) { return path_get_mnt_id_at(AT_FDCWD, path, ret); diff --git a/src/basic/stat-util.c b/src/basic/stat-util.c index 791e99d978..b7016ed6b7 100644 --- a/src/basic/stat-util.c +++ b/src/basic/stat-util.c @@ -475,13 +475,10 @@ bool statx_mount_same(const struct statx *a, const struct statx *b) { if (!statx_is_set(a) || !statx_is_set(b)) return false; - /* if we have the mount ID, that's all we need */ - if (FLAGS_SET(a->stx_mask, STATX_MNT_ID) && FLAGS_SET(b->stx_mask, STATX_MNT_ID)) - return a->stx_mnt_id == b->stx_mnt_id; + assert(FLAGS_SET(a->stx_mask, STATX_MNT_ID)); + assert(FLAGS_SET(b->stx_mask, STATX_MNT_ID)); - /* Otherwise, major/minor of backing device must match */ - return a->stx_dev_major == b->stx_dev_major && - a->stx_dev_minor == b->stx_dev_minor; + return a->stx_mnt_id == b->stx_mnt_id; } int xstatfsat(int dir_fd, const char *path, struct statfs *ret) { @@ -583,3 +580,14 @@ int statx_warn_mount_root(const struct statx *sx, int log_level) { return 0; } + +int statx_warn_mount_id(const struct statx *sx, int log_level) { + assert(sx); + + /* The STATX_MNT_ID flag is supported since kernel v5.10. */ + if (!FLAGS_SET(sx->stx_mask, STATX_MNT_ID)) + return log_full_errno(log_level, SYNTHETIC_ERRNO(ENOSYS), + "statx() does not support STATX_MNT_ID, running on an old kernel?"); + + return 0; +} diff --git a/src/basic/stat-util.h b/src/basic/stat-util.h index a0a4489f74..11d0fdc7ff 100644 --- a/src/basic/stat-util.h +++ b/src/basic/stat-util.h @@ -119,3 +119,4 @@ static inline bool inode_type_can_hardlink(mode_t m) { } int statx_warn_mount_root(const struct statx *sx, int log_level); +int statx_warn_mount_id(const struct statx *sx, int log_level); From d377cc5244a60ecb116b5dcc435afb18a948dba5 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Mon, 5 Jan 2026 13:24:41 +0900 Subject: [PATCH 06/10] stat-util: STATX_TYPE and STATX_INO should be always set --- src/basic/stat-util.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/basic/stat-util.c b/src/basic/stat-util.c index b7016ed6b7..bb20ba50de 100644 --- a/src/basic/stat-util.c +++ b/src/basic/stat-util.c @@ -463,8 +463,13 @@ bool statx_inode_same(const struct statx *a, const struct statx *b) { /* Same as stat_inode_same() but for struct statx */ - return statx_is_set(a) && statx_is_set(b) && - FLAGS_SET(a->stx_mask, STATX_TYPE|STATX_INO) && FLAGS_SET(b->stx_mask, STATX_TYPE|STATX_INO) && + if (!statx_is_set(a) || !statx_is_set(b)) + return false; + + assert(FLAGS_SET(a->stx_mask, STATX_TYPE|STATX_INO)); + assert(FLAGS_SET(b->stx_mask, STATX_TYPE|STATX_INO)); + + return ((a->stx_mode ^ b->stx_mode) & S_IFMT) == 0 && a->stx_dev_major == b->stx_dev_major && a->stx_dev_minor == b->stx_dev_minor && From c471cca093509391682fd460a0b2eee2c203704b Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Mon, 5 Jan 2026 13:24:41 +0900 Subject: [PATCH 07/10] fd-util: close_range() is available since kernel 5.9 Our baseline on kernel is 5.10, hence we can always use it. --- src/basic/fd-util.c | 51 +++++++++++---------------------------------- 1 file changed, 12 insertions(+), 39 deletions(-) diff --git a/src/basic/fd-util.c b/src/basic/fd-util.c index fb4262e62b..7990337085 100644 --- a/src/basic/fd-util.c +++ b/src/basic/fd-util.c @@ -316,8 +316,6 @@ int close_all_fds_by_proc(const int except[], size_t n_except) { return r; } -static bool have_close_range = true; /* Assume we live in the future */ - static int close_all_fds_special_case(const int except[], size_t n_except) { assert(n_except == 0 || except); @@ -325,9 +323,6 @@ static int close_all_fds_special_case(const int except[], size_t n_except) { * nicely, since we won't need sorting for them. Returns > 0 if the special casing worked, 0 * otherwise. */ - if (!have_close_range) - return 0; - if (n_except == 1 && except[0] < 0) /* Minor optimization: if we only got one fd, and it's invalid, * we got none */ n_except = 0; @@ -336,31 +331,22 @@ static int close_all_fds_special_case(const int except[], size_t n_except) { case 0: /* Close everything. Yay! */ + if (close_range(3, INT_MAX, 0) < 0) + return -errno; - if (close_range(3, INT_MAX, 0) >= 0) - return 1; - - if (ERRNO_IS_NOT_SUPPORTED(errno) || ERRNO_IS_PRIVILEGE(errno)) { - have_close_range = false; - return 0; - } - - return -errno; + return 1; case 1: /* Close all but exactly one, then we don't need no sorting. This is a pretty common * case, hence let's handle it specially. */ - if ((except[0] <= 3 || close_range(3, except[0]-1, 0) >= 0) && - (except[0] >= INT_MAX || close_range(MAX(3, except[0]+1), -1, 0) >= 0)) - return 1; + if (except[0] > 3 && close_range(3, except[0] - 1, 0) < 0) + return -errno; - if (ERRNO_IS_NOT_SUPPORTED(errno) || ERRNO_IS_PRIVILEGE(errno)) { - have_close_range = false; - return 0; - } + if (except[0] < INT_MAX && close_range(MAX(3, except[0] + 1), -1, 0) < 0) + return -errno; - return -errno; + return 1; default: return 0; @@ -392,9 +378,6 @@ int close_all_fds(const int except[], size_t n_except) { if (r > 0) /* special case worked! */ return 0; - if (!have_close_range) - return close_all_fds_by_proc(except, n_except); - _cleanup_free_ int *sorted_malloc = NULL; size_t n_sorted; int *sorted; @@ -436,13 +419,8 @@ int close_all_fds(const int except[], size_t n_except) { continue; /* Close everything between the start and end fds (both of which shall stay open) */ - if (close_range(start + 1, end - 1, 0) < 0) { - if (!ERRNO_IS_NOT_SUPPORTED(errno) && !ERRNO_IS_PRIVILEGE(errno)) - return -errno; - - have_close_range = false; - return close_all_fds_by_proc(except, n_except); - } + if (close_range(start + 1, end - 1, 0) < 0) + return -errno; } /* The loop succeeded. Let's now close everything beyond the end */ @@ -450,13 +428,8 @@ int close_all_fds(const int except[], size_t n_except) { if (sorted[n_sorted-1] >= INT_MAX) /* Dont let the addition below overflow */ return 0; - if (close_range(sorted[n_sorted-1] + 1, INT_MAX, 0) < 0) { - if (!ERRNO_IS_NOT_SUPPORTED(errno) && !ERRNO_IS_PRIVILEGE(errno)) - return -errno; - - have_close_range = false; - return close_all_fds_by_proc(except, n_except); - } + if (close_range(sorted[n_sorted-1] + 1, INT_MAX, 0) < 0) + return -errno; return 0; } From d58c826c541598060002cfeb608f35ccd2a229e3 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Mon, 5 Jan 2026 13:24:42 +0900 Subject: [PATCH 08/10] fd-util: drop close_all_fds_by_proc() With the previous commit, now the function is used only on OOM. In that case, let's do like close_all_fds_without_malloc(). --- src/basic/fd-util.c | 45 ++++------------------------------------- src/basic/fd-util.h | 1 - src/test/test-fd-util.c | 10 ++------- 3 files changed, 6 insertions(+), 50 deletions(-) diff --git a/src/basic/fd-util.c b/src/basic/fd-util.c index 7990337085..e08ad7d324 100644 --- a/src/basic/fd-util.c +++ b/src/basic/fd-util.c @@ -251,10 +251,9 @@ int close_all_fds_frugal(const int except[], size_t n_except) { assert(except || n_except == 0); - /* This is the inner fallback core of close_all_fds(). This never calls malloc() or opendir() or so - * and hence is safe to be called in signal handler context. Most users should call close_all_fds(), - * but when we assume we are called from signal handler context, then use this simpler call - * instead. */ + /* This is the inner fallback core of close_all_fds(). This never calls malloc() or so and hence is + * safe to be called in signal handler context. Most users should call close_all_fds(), but when we + * assume we are called from signal handler context, then use this simpler call instead. */ max_fd = get_max_fd(); if (max_fd < 0) @@ -280,42 +279,6 @@ int close_all_fds_frugal(const int except[], size_t n_except) { return r; } -int close_all_fds_by_proc(const int except[], size_t n_except) { - _cleanup_closedir_ DIR *d = NULL; - int r = 0; - - d = opendir("/proc/self/fd"); - if (!d) - return close_all_fds_frugal(except, n_except); /* ultimate fallback if /proc/ is not available */ - - FOREACH_DIRENT(de, d, return -errno) { - int fd = -EBADF, q; - - if (!IN_SET(de->d_type, DT_LNK, DT_UNKNOWN)) - continue; - - fd = parse_fd(de->d_name); - if (fd < 0) - /* Let's better ignore this, just in case */ - continue; - - if (fd < 3) - continue; - - if (fd == dirfd(d)) - continue; - - if (fd_in_set(fd, except, n_except)) - continue; - - q = close_nointr(fd); - if (q != -EBADF) /* Valgrind has its own FD and doesn't want to have it closed */ - RET_GATHER(r, q); - } - - return r; -} - static int close_all_fds_special_case(const int except[], size_t n_except) { assert(n_except == 0 || except); @@ -397,7 +360,7 @@ int close_all_fds(const int except[], size_t n_except) { sorted = newa(int, n_sorted); if (!sorted) /* Fallback on OOM. */ - return close_all_fds_by_proc(except, n_except); + return close_all_fds_frugal(except, n_except); memcpy(sorted, except, n_except * sizeof(int)); diff --git a/src/basic/fd-util.h b/src/basic/fd-util.h index db4b591438..bb9669a84e 100644 --- a/src/basic/fd-util.h +++ b/src/basic/fd-util.h @@ -112,7 +112,6 @@ int get_max_fd(void); int close_all_fds(const int except[], size_t n_except); int close_all_fds_without_malloc(const int except[], size_t n_except); -int close_all_fds_by_proc(const int except[], size_t n_except); int close_all_fds_frugal(const int except[], size_t n_except); int pack_fds(int fds[], size_t n); diff --git a/src/test/test-fd-util.c b/src/test/test-fd-util.c index 5a8b58440b..d43c873516 100644 --- a/src/test/test-fd-util.c +++ b/src/test/test-fd-util.c @@ -343,19 +343,13 @@ TEST(close_all_fds) { _exit(EXIT_SUCCESS); } - ASSERT_OK(r = pidref_safe_fork("(caf-nomalloc)", flags, NULL)); + r = ASSERT_OK(pidref_safe_fork("(caf-nomalloc)", flags, NULL)); if (r == 0) { test_close_all_fds_inner(close_all_fds_without_malloc); _exit(EXIT_SUCCESS); } - ASSERT_OK(r = pidref_safe_fork("(caf-proc)", flags, NULL)); - if (r == 0) { - test_close_all_fds_inner(close_all_fds_by_proc); - _exit(EXIT_SUCCESS); - } - - ASSERT_OK(r = pidref_safe_fork("(caf-frugal)", flags, NULL)); + r = ASSERT_OK(pidref_safe_fork("(caf-frugal)", flags, NULL)); if (r == 0) { test_close_all_fds_inner(close_all_fds_frugal); _exit(EXIT_SUCCESS); From 80c5b63db5f7740cd25505e1e14251b6869b885a Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Mon, 5 Jan 2026 13:24:43 +0900 Subject: [PATCH 09/10] mount-setup: memory_recursiveprot is supported since kernel v5.7 Our baseline on kernel is 5.10. Hence we can unconditionally use it. --- src/shared/mount-setup.c | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/src/shared/mount-setup.c b/src/shared/mount-setup.c index b044c16195..9077d08691 100644 --- a/src/shared/mount-setup.c +++ b/src/shared/mount-setup.c @@ -49,19 +49,20 @@ static int cgroupfs_mount_options(int priority, const char *type, char **ret) { assert(streq(type, "cgroup2")); assert(ret); - _cleanup_free_ char *opts = NULL; - FOREACH_STRING(o, "memory_recursiveprot", "memory_hugetlb_accounting") { - r = mount_option_supported("cgroup2", o, /* value= */ NULL); + /* memory_hugetlb_accounting mount option is since kernel v6.7 (8cba9576df601c384abd334a503c3f6e1e29eefb). */ + + r = mount_option_supported("cgroup2", "memory_hugetlb_accounting", /* value= */ NULL); + if (r <= 0) { if (r < 0) - log_full_errno(priority, r, "Failed to determine whether cgroupfs supports '%s' mount option, assuming not: %m", o); - else if (r == 0) - log_debug("'%s' not supported by cgroupfs, not using mount option.", o); - else if (!strextend_with_separator(&opts, ",", o)) - return log_oom_full(priority); + log_full_errno(priority, r, "Failed to determine whether cgroupfs supports 'memory_hugetlb_accounting' mount option, assuming not: %m"); + else + log_debug("'memory_hugetlb_accounting' not supported by cgroupfs, not using mount option."); + + *ret = NULL; + return 0; } - *ret = TAKE_PTR(opts); - return 0; + return strdup_to(ret, "memory_hugetlb_accounting"); } int mount_cgroupfs(const char *path) { @@ -81,7 +82,7 @@ int mount_cgroupfs(const char *path) { return r; /* These options shall be kept in sync with those in mount_table below. */ - if (!strprepend_with_separator(&opts, ",", "nsdelegate")) + if (!strprepend_with_separator(&opts, ",", "nsdelegate,memory_recursiveprot")) return log_oom(); return mount_nofollow_verbose(LOG_ERR, "cgroup2", path, "cgroup2", MS_NOSUID|MS_NOEXEC|MS_NODEV, opts); @@ -199,7 +200,7 @@ static const MountPoint mount_table[] = { .what = "cgroup2", .where = "/sys/fs/cgroup", .type = "cgroup2", - .options = "nsdelegate", + .options = "nsdelegate,memory_recursiveprot", .options_fn = cgroupfs_mount_options, .flags = MS_NOSUID|MS_NOEXEC|MS_NODEV, .mode = MNT_FATAL|MNT_IN_CONTAINER|MNT_CHECK_WRITABLE, From fce7ad481b61e5ed3b95a2a849d07ba9519cab16 Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Mon, 5 Jan 2026 13:24:44 +0900 Subject: [PATCH 10/10] process-util: drop unnecessary E2BIG error handling E2BIG is returned when the kernel does not support CLONE_INTO_CGROUP, but the flag is supported since kernel v5.7. --- src/basic/process-util.c | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/basic/process-util.c b/src/basic/process-util.c index 1ff59dd374..50118b60d9 100644 --- a/src/basic/process-util.c +++ b/src/basic/process-util.c @@ -2119,15 +2119,14 @@ int posix_spawn_wrapper( if (ERRNO_IS_NOT_SUPPORTED(r) && FLAGS_SET(flags, POSIX_SPAWN_SETCGROUP) && cg_is_threaded(cgroup) > 0) return -EUCLEAN; /* clone3() could also return EOPNOTSUPP if the target cgroup is in threaded mode, turn that into something recognizable */ - if ((ERRNO_IS_NOT_SUPPORTED(r) || ERRNO_IS_PRIVILEGE(r) || r == E2BIG) && + if ((ERRNO_IS_NOT_SUPPORTED(r) || ERRNO_IS_PRIVILEGE(r)) && FLAGS_SET(flags, POSIX_SPAWN_SETCGROUP)) { /* Compiled on a newer host, or seccomp&friends blocking clone3()? Fallback, but * need to disable POSIX_SPAWN_SETCGROUP, which is what redirects to clone3(). - * Note that we might get E2BIG here since some kernels (e.g. 5.4) support clone3() - * but not CLONE_INTO_CGROUP. */ - - /* CLONE_INTO_CGROUP definitely won't work, hence remember the fact so that we don't - * retry every time. */ + * CLONE_INTO_CGROUP definitely won't work, hence remember the fact so that we don't + * retry every time. + * Note, CLONE_INTO_CGROUP is supported since kernel v5.7, but some architectures still + * do not support clone3(). Hence, we need to keep the fallback logic for a while. */ have_clone_into_cgroup = false; flags &= ~POSIX_SPAWN_SETCGROUP;