From 36d7e177ad0814eb85bcafb603d622794e51fe10 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 2 Mar 2026 18:09:23 +0100 Subject: [PATCH 01/11] fd-util: make use of XAT_FDROOT in path_is_root_at() --- src/basic/fd-util.c | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/basic/fd-util.c b/src/basic/fd-util.c index 99eff99874..d043927a2c 100644 --- a/src/basic/fd-util.c +++ b/src/basic/fd-util.c @@ -1061,10 +1061,6 @@ int path_is_root_at(int dir_fd, const char *path) { dir_fd = fd; } - _cleanup_close_ int root_fd = open("/", O_PATH|O_DIRECTORY|O_CLOEXEC); - if (root_fd < 0) - return -errno; - /* Even if the root directory has the same inode as our fd, the fd may not point to the root * directory "/", and we also need to check that the mount ids are the same. Otherwise, a construct * like the following could be used to trick us: @@ -1073,7 +1069,7 @@ int path_is_root_at(int dir_fd, const char *path) { * $ mount --bind / /tmp/x */ - return fds_are_same_mount(dir_fd, root_fd); + return fds_are_same_mount(dir_fd, XAT_FDROOT); } int fds_are_same_mount(int fd1, int fd2) { From 97fe03e12faa4e50d25a3ca8999967801c7e2da9 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 25 Feb 2026 12:13:31 +0100 Subject: [PATCH 02/11] stat-util: add statx() flavours of stat_verify_regular() + stat_verify_socket() --- src/basic/stat-util.c | 71 ++++++++++++++++++++++++++++--------------- src/basic/stat-util.h | 2 ++ 2 files changed, 48 insertions(+), 25 deletions(-) diff --git a/src/basic/stat-util.c b/src/basic/stat-util.c index 43f82dd926..c5702ca6ab 100644 --- a/src/basic/stat-util.c +++ b/src/basic/stat-util.c @@ -49,22 +49,35 @@ static int verify_stat_at( return verify ? r : r >= 0; } +static int mode_verify_regular(mode_t mode) { + if (S_ISDIR(mode)) + return -EISDIR; + + if (S_ISLNK(mode)) + return -ELOOP; + + if (!S_ISREG(mode)) + return -EBADFD; + + return 0; +} + int stat_verify_regular(const struct stat *st) { assert(st); /* Checks whether the specified stat() structure refers to a regular file. If not returns an * appropriate error code. */ - if (S_ISDIR(st->st_mode)) - return -EISDIR; + return mode_verify_regular(st->st_mode); +} - if (S_ISLNK(st->st_mode)) - return -ELOOP; +int statx_verify_regular(const struct statx *stx) { + assert(stx); - if (!S_ISREG(st->st_mode)) - return -EBADFD; + if (!FLAGS_SET(stx->stx_mask, STATX_TYPE)) + return -ENODATA; - return 0; + return mode_verify_regular(stx->stx_mode); } int verify_regular_at(int fd, const char *path, bool follow) { @@ -78,31 +91,29 @@ int fd_verify_regular(int fd) { return verify_regular_at(fd, /* path= */ NULL, /* follow= */ false); } -int stat_verify_directory(const struct stat *st) { - assert(st); - - if (S_ISLNK(st->st_mode)) +static int mode_verify_directory(mode_t mode) { + if (S_ISLNK(mode)) return -ELOOP; - if (!S_ISDIR(st->st_mode)) + if (!S_ISDIR(mode)) return -ENOTDIR; return 0; } +int stat_verify_directory(const struct stat *st) { + assert(st); + + return mode_verify_directory(st->st_mode); +} + int statx_verify_directory(const struct statx *stx) { assert(stx); if (!FLAGS_SET(stx->stx_mask, STATX_TYPE)) return -ENODATA; - if (S_ISLNK(stx->stx_mode)) - return -ELOOP; - - if (!S_ISDIR(stx->stx_mode)) - return -ENOTDIR; - - return 0; + return mode_verify_directory(stx->stx_mode); } int fd_verify_directory(int fd) { @@ -142,21 +153,31 @@ int is_symlink(const char *path) { return verify_stat_at(AT_FDCWD, path, false, stat_verify_symlink, false); } -int stat_verify_socket(const struct stat *st) { - assert(st); - - if (S_ISLNK(st->st_mode)) +static mode_t mode_verify_socket(mode_t mode) { + if (S_ISLNK(mode)) return -ELOOP; - if (S_ISDIR(st->st_mode)) + if (S_ISDIR(mode)) return -EISDIR; - if (!S_ISSOCK(st->st_mode)) + if (!S_ISSOCK(mode)) return -ENOTSOCK; return 0; } +int stat_verify_socket(const struct stat *st) { + assert(st); + + return mode_verify_socket(st->st_mode); +} + +int statx_verify_socket(const struct statx *stx) { + assert(stx); + + return mode_verify_socket(stx->stx_mode); +} + int is_socket(const char *path) { assert(!isempty(path)); return verify_stat_at(AT_FDCWD, path, /* follow= */ true, stat_verify_socket, /* verify= */ false); diff --git a/src/basic/stat-util.h b/src/basic/stat-util.h index 0e4bec513b..2336c775e9 100644 --- a/src/basic/stat-util.h +++ b/src/basic/stat-util.h @@ -7,6 +7,7 @@ #include "basic-forward.h" int stat_verify_regular(const struct stat *st); +int statx_verify_regular(const struct statx *stx); int verify_regular_at(int fd, const char *path, bool follow); int fd_verify_regular(int fd); @@ -21,6 +22,7 @@ int fd_verify_symlink(int fd); int is_symlink(const char *path); int stat_verify_socket(const struct stat *st); +int statx_verify_socket(const struct statx *stx); int is_socket(const char *path); int stat_verify_linked(const struct stat *st); From 004e60e0fca861db6a924c5e0e7c6cb02ea02fd2 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 25 Feb 2026 11:43:11 +0100 Subject: [PATCH 03/11] chase: put limit on overall chase cycles Let's add some protections in case we deal with inodes owned by an untrusted person, with concurrent access: let's put a limit on how long we traverse, and fail eventually so that live changes cannot send us in circles indefinitely. This reworks the current CHASE_MAX logic so that it not only applies to symlinks transitions, but to any transitions. This also bumps CHASE_MAX a bit, given that it's now bumped on every single iteration of the loop. --- src/basic/chase.c | 14 +++++++------- src/basic/chase.h | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/basic/chase.c b/src/basic/chase.c index abe85a2a08..11cbcbe3e4 100644 --- a/src/basic/chase.c +++ b/src/basic/chase.c @@ -128,7 +128,6 @@ static int chaseat_needs_absolute(int dir_fd, const char *path) { int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int *ret_fd) { _cleanup_free_ char *buffer = NULL, *done = NULL; _cleanup_close_ int fd = -EBADF, root_fd = -EBADF; - unsigned max_follow = CHASE_MAX; /* how many symlinks to follow before giving up and returning ELOOP */ bool exists = true, append_trail_slash = false; struct stat st; /* stat obtained from fd */ bool need_absolute = false; /* allocate early to avoid compiler warnings around goto */ @@ -334,12 +333,18 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int if (FLAGS_SET(flags, CHASE_MUST_BE_DIRECTORY) + FLAGS_SET(flags, CHASE_MUST_BE_REGULAR) + FLAGS_SET(flags, CHASE_MUST_BE_SOCKET) > 1) return -EBADSLT; - for (todo = buffer;;) { + todo = buffer; + for (unsigned n_steps = 0;; n_steps++) { _cleanup_free_ char *first = NULL; _cleanup_close_ int child = -EBADF; struct stat st_child; const char *e; + /* If people change our tree behind our back, they might send us in circles. Put a limit on + * things */ + if (n_steps > CHASE_MAX) + return -ELOOP; + r = path_find_first_component(&todo, /* accept_dot_dot= */ true, &e); if (r < 0) return r; @@ -495,11 +500,6 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int if (FLAGS_SET(flags, CHASE_PROHIBIT_SYMLINKS)) return log_prohibited_symlink(child, flags); - /* This is a symlink, in this case read the destination. But let's make sure we - * don't follow symlinks without bounds. */ - if (--max_follow <= 0) - return -ELOOP; - r = readlinkat_malloc(fd, first, &destination); if (r < 0) return r; diff --git a/src/basic/chase.h b/src/basic/chase.h index d0674aae73..b770f45a1e 100644 --- a/src/basic/chase.h +++ b/src/basic/chase.h @@ -35,7 +35,7 @@ typedef enum ChaseFlags { bool unsafe_transition(const struct stat *a, const struct stat *b); /* How many iterations to execute before returning -ELOOP */ -#define CHASE_MAX 32 +#define CHASE_MAX 128U int chase(const char *path_with_prefix, const char *root, ChaseFlags flags, char **ret_path, int *ret_fd); From c8031ff37eb44a1bebb4a2c3ccfae51ac09982a5 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 25 Feb 2026 12:15:14 +0100 Subject: [PATCH 04/11] chase: port to statx() In one of the next commits we want to acquire .stx_mnt_id from statx() for each inode we traverse (plain fstat() doesn't provide that field). Hence let's port chase() over to statx() as preparation for that. No change in behaviour. --- src/basic/chase.c | 125 ++++++++++++++++++++++++++-------------- src/basic/chase.h | 3 +- src/tmpfiles/tmpfiles.c | 2 +- 3 files changed, 86 insertions(+), 44 deletions(-) diff --git a/src/basic/chase.c b/src/basic/chase.c index 11cbcbe3e4..1d3596bb79 100644 --- a/src/basic/chase.c +++ b/src/basic/chase.c @@ -31,15 +31,33 @@ CHASE_MUST_BE_REGULAR | \ CHASE_MUST_BE_SOCKET) -bool unsafe_transition(const struct stat *a, const struct stat *b) { - /* Returns true if the transition from a to b is safe, i.e. that we never transition from unprivileged to - * privileged files or directories. Why bother? So that unprivileged code can't symlink to privileged files - * making us believe we read something safe even though it isn't safe in the specific context we open it in. */ +static bool uid_unsafe_transition(uid_t a, uid_t b) { + /* Returns true if the transition from a to b is safe, i.e. that we never transition from + * unprivileged to privileged files or directories. Why bother? So that unprivileged code can't + * symlink to privileged files making us believe we read something safe even though it isn't safe in + * the specific context we open it in. */ - if (a->st_uid == 0) /* Transitioning from privileged to unprivileged is always fine */ + if (a == 0) /* Transitioning from privileged to unprivileged is always fine */ return false; - return a->st_uid != b->st_uid; /* Otherwise we need to stay within the same UID */ + return a != b; /* Otherwise we need to stay within the same UID */ +} + +int statx_unsafe_transition(const struct statx *a, const struct statx *b) { + assert(a); + assert(b); + + if (!FLAGS_SET(a->stx_mask, STATX_UID) || !FLAGS_SET(b->stx_mask, STATX_UID)) + return -ENODATA; + + return uid_unsafe_transition(a->stx_uid, b->stx_uid); +} + +bool stat_unsafe_transition(const struct stat *a, const struct stat *b) { + assert(a); + assert(b); + + return uid_unsafe_transition(a->st_uid, b->st_uid); } static int log_unsafe_transition(int a, int b, const char *path, ChaseFlags flags) { @@ -129,9 +147,10 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int _cleanup_free_ char *buffer = NULL, *done = NULL; _cleanup_close_ int fd = -EBADF, root_fd = -EBADF; bool exists = true, append_trail_slash = false; - struct stat st; /* stat obtained from fd */ + struct statx stx; /* statx obtained from fd */ bool need_absolute = false; /* allocate early to avoid compiler warnings around goto */ const char *todo; + unsigned mask = STATX_TYPE|STATX_UID|STATX_INO; int r; assert(!FLAGS_SET(flags, CHASE_PREFIX_ROOT)); @@ -245,8 +264,9 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int if (fd < 0) return -errno; - if (fstat(fd, &st) < 0) - return -errno; + r = xstatx(fd, /* path= */ NULL, /* flags= */ 0, mask, &stx); + if (r < 0) + return r; exists = true; goto success; @@ -306,8 +326,9 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int if (fd < 0) return -errno; - if (fstat(fd, &st) < 0) - return -errno; + r = xstatx(fd, /* path= */ NULL, /* flags= */ 0, mask, &stx); + if (r < 0) + return r; /* If we get AT_FDCWD, we always resolve symlinks relative to the host's root. Only if a positive * directory file descriptor is provided we will look at CHASE_AT_RESOLVE_IN_ROOT to determine @@ -337,7 +358,7 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int for (unsigned n_steps = 0;; n_steps++) { _cleanup_free_ char *first = NULL; _cleanup_close_ int child = -EBADF; - struct stat st_child; + struct statx stx_child; const char *e; /* If people change our tree behind our back, they might send us in circles. Put a limit on @@ -359,7 +380,7 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int if (streq(first, "..")) { _cleanup_free_ char *parent = NULL; _cleanup_close_ int fd_parent = -EBADF; - struct stat st_parent; + struct statx stx_parent; /* If we already are at the top, then going up will not change anything. This is * in-line with how the kernel handles this. */ @@ -373,13 +394,14 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int if (fd_parent < 0) return -errno; - if (fstat(fd_parent, &st_parent) < 0) - return -errno; + r = xstatx(fd_parent, /* path= */ NULL, /* flags= */ 0, mask, &stx_parent); + if (r < 0) + return r; /* If we opened the same directory, that _may_ indicate that we're at the host root * directory. Let's confirm that in more detail with dir_fd_is_root(). And if so, * going up won't change anything. */ - if (stat_inode_same(&st_parent, &st)) { + if (statx_inode_same(&stx_parent, &stx)) { r = dir_fd_is_root(fd); if (r < 0) return r; @@ -419,35 +441,44 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int if (FLAGS_SET(flags, CHASE_STEP)) goto chased_one; - if (FLAGS_SET(flags, CHASE_SAFE) && - unsafe_transition(&st, &st_parent)) - return log_unsafe_transition(fd, fd_parent, path, flags); + if (FLAGS_SET(flags, CHASE_SAFE)) { + r = statx_unsafe_transition(&stx, &stx_parent); + if (r < 0) + return r; + if (r > 0) + return log_unsafe_transition(fd, fd_parent, path, flags); + } /* If the path ends on a "..", and CHASE_PARENT is specified then our current 'fd' is * the child of the returned normalized path, not the parent as requested. To correct * this we have to go *two* levels up. */ if (FLAGS_SET(flags, CHASE_PARENT) && isempty(todo)) { _cleanup_close_ int fd_grandparent = -EBADF; - struct stat st_grandparent; + struct statx stx_grandparent; fd_grandparent = openat(fd_parent, "..", O_CLOEXEC|O_NOFOLLOW|O_PATH|O_DIRECTORY); if (fd_grandparent < 0) return -errno; - if (fstat(fd_grandparent, &st_grandparent) < 0) - return -errno; + r = xstatx(fd_grandparent, /* path= */ NULL, /* flags= */ 0, mask, &stx_grandparent); + if (r < 0) + return r; - if (FLAGS_SET(flags, CHASE_SAFE) && - unsafe_transition(&st_parent, &st_grandparent)) - return log_unsafe_transition(fd_parent, fd_grandparent, path, flags); + if (FLAGS_SET(flags, CHASE_SAFE)) { + r = statx_unsafe_transition(&stx_parent, &stx_grandparent); + if (r < 0) + return r; + if (r > 0) + return log_unsafe_transition(fd_parent, fd_grandparent, path, flags); + } - st = st_grandparent; + stx = stx_grandparent; close_and_replace(fd, fd_grandparent); break; } /* update fd and stat */ - st = st_parent; + stx = stx_parent; close_and_replace(fd, fd_parent); continue; } @@ -483,18 +514,23 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int } /* ... and then check what it actually is. */ - if (fstat(child, &st_child) < 0) - return -errno; + r = xstatx(child, /* path= */ NULL, /* flags= */ 0, mask, &stx_child); + if (r < 0) + return r; - if (FLAGS_SET(flags, CHASE_SAFE) && - unsafe_transition(&st, &st_child)) - return log_unsafe_transition(fd, child, path, flags); + if (FLAGS_SET(flags, CHASE_SAFE)) { + r = statx_unsafe_transition(&stx, &stx_child); + if (r < 0) + return r; + if (r > 0) + return log_unsafe_transition(fd, child, path, flags); + } if (FLAGS_SET(flags, CHASE_NO_AUTOFS) && fd_is_fs_type(child, AUTOFS_SUPER_MAGIC) > 0) return log_autofs_mount_point(child, path, flags); - if (S_ISLNK(st_child.st_mode) && !(FLAGS_SET(flags, CHASE_NOFOLLOW) && isempty(todo))) { + if (S_ISLNK(stx_child.stx_mode) && !(FLAGS_SET(flags, CHASE_NOFOLLOW) && isempty(todo))) { _cleanup_free_ char *destination = NULL; if (FLAGS_SET(flags, CHASE_PROHIBIT_SYMLINKS)) @@ -516,12 +552,17 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int if (fd < 0) return fd; - if (fstat(fd, &st) < 0) - return -errno; + r = xstatx(fd, /* path= */ NULL, /* flags= */ 0, mask, &stx); + if (r < 0) + return r; - if (FLAGS_SET(flags, CHASE_SAFE) && - unsafe_transition(&st_child, &st)) - return log_unsafe_transition(child, fd, path, flags); + if (FLAGS_SET(flags, CHASE_SAFE)) { + r = statx_unsafe_transition(&stx_child, &stx); + if (r < 0) + return r; + if (r > 0) + return log_unsafe_transition(child, fd, path, flags); + } /* When CHASE_AT_RESOLVE_IN_ROOT is not set, now the chased path may be * outside of the specified dir_fd. Let's make the result absolute. */ @@ -555,26 +596,26 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int break; /* And iterate again, but go one directory further down. */ - st = st_child; + stx = stx_child; close_and_replace(fd, child); } success: if (exists) { if (FLAGS_SET(flags, CHASE_MUST_BE_DIRECTORY)) { - r = stat_verify_directory(&st); + r = statx_verify_directory(&stx); if (r < 0) return r; } if (FLAGS_SET(flags, CHASE_MUST_BE_REGULAR)) { - r = stat_verify_regular(&st); + r = statx_verify_regular(&stx); if (r < 0) return r; } if (FLAGS_SET(flags, CHASE_MUST_BE_SOCKET)) { - r = stat_verify_socket(&st); + r = statx_verify_socket(&stx); if (r < 0) return r; } diff --git a/src/basic/chase.h b/src/basic/chase.h index b770f45a1e..d779658ba1 100644 --- a/src/basic/chase.h +++ b/src/basic/chase.h @@ -32,7 +32,8 @@ typedef enum ChaseFlags { CHASE_MUST_BE_SOCKET = 1 << 16, /* Fail if returned inode fd is not a socket */ } ChaseFlags; -bool unsafe_transition(const struct stat *a, const struct stat *b); +int statx_unsafe_transition(const struct statx *a, const struct statx *b); +bool stat_unsafe_transition(const struct stat *a, const struct stat *b); /* How many iterations to execute before returning -ELOOP */ #define CHASE_MAX 128U diff --git a/src/tmpfiles/tmpfiles.c b/src/tmpfiles/tmpfiles.c index da75ffb818..090884b228 100644 --- a/src/tmpfiles/tmpfiles.c +++ b/src/tmpfiles/tmpfiles.c @@ -2633,7 +2633,7 @@ static int rm_if_wrong_type_safe( } /* Fail before removing anything if this is an unsafe transition. */ - if (follow_links && unsafe_transition(parent_st, &st)) { + if (follow_links && stat_unsafe_transition(parent_st, &st)) { (void) fd_get_path(parent_fd, &parent_name); return log_error_errno(SYNTHETIC_ERRNO(ENOLINK), "Unsafe transition from \"%s\" to \"%s\".", parent_name ?: "...", name); From 3538b77f9dc139f76561ec88eda17b1df6567c82 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 25 Feb 2026 12:26:42 +0100 Subject: [PATCH 05/11] chase: tighten checks on ".." once we hit the root of an CHASE_AT_RESOLVE_IN_ROOT root tree Let's harden things in case concurrent access is allowed to a root tree passed via CHASE_AT_RESOLVE_IN_ROOT: let's not just validate via the path if we hit the root of the tree, but also by comparing inodes + mount ids. Hardening opportunity reported by Sebastian Wick. --- src/basic/chase.c | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/basic/chase.c b/src/basic/chase.c index 1d3596bb79..6e8cc15f2e 100644 --- a/src/basic/chase.c +++ b/src/basic/chase.c @@ -147,10 +147,10 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int _cleanup_free_ char *buffer = NULL, *done = NULL; _cleanup_close_ int fd = -EBADF, root_fd = -EBADF; bool exists = true, append_trail_slash = false; - struct statx stx; /* statx obtained from fd */ + struct statx root_stx, stx; bool need_absolute = false; /* allocate early to avoid compiler warnings around goto */ const char *todo; - unsigned mask = STATX_TYPE|STATX_UID|STATX_INO; + unsigned mask = STATX_TYPE|STATX_UID|STATX_INO|STATX_MNT_ID; int r; assert(!FLAGS_SET(flags, CHASE_PREFIX_ROOT)); @@ -244,7 +244,6 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int if (r < 0) return r; if (r > 0) { - /* Shortcut the common case where no root dir is specified, and no special flags are given to * a regular open() */ if (!ret_path && @@ -329,6 +328,7 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int r = xstatx(fd, /* path= */ NULL, /* flags= */ 0, mask, &stx); if (r < 0) return r; + root_stx = stx; /* remember stat data of the root, so that we can recognize it later */ /* If we get AT_FDCWD, we always resolve symlinks relative to the host's root. Only if a positive * directory file descriptor is provided we will look at CHASE_AT_RESOLVE_IN_ROOT to determine @@ -383,8 +383,13 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int struct statx stx_parent; /* If we already are at the top, then going up will not change anything. This is - * in-line with how the kernel handles this. */ - if (empty_or_root(done) && FLAGS_SET(flags, CHASE_AT_RESOLVE_IN_ROOT)) { + * in-line with how the kernel handles this. We check this both by path and by + * inode/mount identity check. The latter is load-bearing if concurrent access of the + * root tree we operate in is allowed, where an inode is moved up the tree while we + * look at it, and thus get the current path wrong and think we are deeper down than + * we actually are. */ + if (FLAGS_SET(flags, CHASE_AT_RESOLVE_IN_ROOT) && + (empty_or_root(done) || (statx_inode_same(&stx, &root_stx) && statx_mount_same(&stx, &root_stx)))) { if (FLAGS_SET(flags, CHASE_STEP)) goto chased_one; continue; From 0384875582fca36ced71eea1655b644fa00179e4 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 25 Feb 2026 12:51:29 +0100 Subject: [PATCH 06/11] chase: drop wrong optimization MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The same optimization is already applied in the very similar dir_fd_is_root() check a few lines up – with the exception that it doesn't accept AT_FCWD there. And frankly turning off CHASE_AT_RESOLVE_IN_ROOT if we operate on AT_FCWD is simply wrong. Hence remove this code. --- src/basic/chase.c | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/basic/chase.c b/src/basic/chase.c index 6e8cc15f2e..8030c74079 100644 --- a/src/basic/chase.c +++ b/src/basic/chase.c @@ -277,15 +277,6 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int dir_fd = _dir_fd; flags &= ~CHASE_AT_RESOLVE_IN_ROOT; - } else if (FLAGS_SET(flags, CHASE_AT_RESOLVE_IN_ROOT)) { - /* If we get AT_FDCWD or dir_fd points to "/", then we always resolve symlinks relative to - * the host's root. Hence, CHASE_AT_RESOLVE_IN_ROOT is meaningless. */ - - r = dir_fd_is_root_or_cwd(dir_fd); - if (r < 0) - return r; - if (r > 0) - flags &= ~CHASE_AT_RESOLVE_IN_ROOT; } if (!ret_path && ret_fd && (flags & (CHASE_AT_RESOLVE_IN_ROOT|CHASE_NO_SHORTCUT_MASK)) == 0) { From 4023840ffa263d29132f7417dea674fff01f44af Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 25 Feb 2026 13:06:32 +0100 Subject: [PATCH 07/11] fd-util: rename fds_are_same_mount() fds_inode_and_mount_same() The old name suggested this would only check if the two inodes are on the same mount, but it actually checks if they are the same inodes too. Let's rename it to make this clearer, in particular as we have both statx_inode_same() and statx_mount_same() already, and they are even used here, and hence very confusing. This also drops two checks from the test case, which are simply wrong. Given they apparently weren't load bearing (since no CI tripped up), let's just drop them. --- src/basic/fd-util.c | 4 ++-- src/basic/fd-util.h | 2 +- src/shared/switch-root.c | 2 +- src/test/test-fd-util.c | 9 +++------ src/test/test-fs-util.c | 8 ++++---- 5 files changed, 11 insertions(+), 14 deletions(-) diff --git a/src/basic/fd-util.c b/src/basic/fd-util.c index d043927a2c..4d720e014e 100644 --- a/src/basic/fd-util.c +++ b/src/basic/fd-util.c @@ -1069,10 +1069,10 @@ int path_is_root_at(int dir_fd, const char *path) { * $ mount --bind / /tmp/x */ - return fds_are_same_mount(dir_fd, XAT_FDROOT); + return fds_inode_and_mount_same(dir_fd, XAT_FDROOT); } -int fds_are_same_mount(int fd1, int fd2) { +int fds_inode_and_mount_same(int fd1, int fd2) { struct statx sx1, sx2; int r; diff --git a/src/basic/fd-util.h b/src/basic/fd-util.h index ee1dc870df..60caa424b4 100644 --- a/src/basic/fd-util.h +++ b/src/basic/fd-util.h @@ -179,7 +179,7 @@ static inline int dir_fd_is_root_or_cwd(int dir_fd) { return IN_SET(dir_fd, AT_FDCWD, XAT_FDROOT) ? true : path_is_root_at(dir_fd, NULL); } -int fds_are_same_mount(int fd1, int fd2); +int fds_inode_and_mount_same(int fd1, int fd2); int resolve_xat_fdroot(int *fd, const char **path, char **ret_buffer); diff --git a/src/shared/switch-root.c b/src/shared/switch-root.c index 6017200d79..08710a8966 100644 --- a/src/shared/switch-root.c +++ b/src/shared/switch-root.c @@ -54,7 +54,7 @@ int switch_root(const char *new_root, if (new_root_fd < 0) return log_error_errno(errno, "Failed to open target directory '%s': %m", new_root); - r = fds_are_same_mount(old_root_fd, new_root_fd); /* checks if referenced inodes and mounts match */ + r = fds_inode_and_mount_same(old_root_fd, new_root_fd); /* checks if referenced inodes and mounts match */ if (r < 0) return log_error_errno(r, "Failed to check if old and new root directory/mount are the same: %m"); if (r > 0) { diff --git a/src/test/test-fd-util.c b/src/test/test-fd-util.c index d43c873516..e99d7d0472 100644 --- a/src/test/test-fd-util.c +++ b/src/test/test-fd-util.c @@ -738,7 +738,7 @@ TEST(path_is_root_at) { test_path_is_root_at_one(true); } -TEST(fds_are_same_mount) { +TEST(fds_inode_and_mount_same) { _cleanup_close_ int fd1 = -EBADF, fd2 = -EBADF, fd3 = -EBADF, fd4 = -EBADF; fd1 = open("/sys", O_CLOEXEC|O_PATH|O_DIRECTORY|O_NOFOLLOW); @@ -749,11 +749,8 @@ TEST(fds_are_same_mount) { if (fd1 < 0 || fd2 < 0 || fd3 < 0 || fd4 < 0) return (void) log_tests_skipped_errno(errno, "Failed to open /sys or /proc or /"); - if (fds_are_same_mount(fd1, fd4) > 0 && fds_are_same_mount(fd2, fd4) > 0) - return (void) log_tests_skipped("Cannot test fds_are_same_mount() as /sys and /proc are not mounted"); - - assert_se(fds_are_same_mount(fd1, fd2) == 0); - assert_se(fds_are_same_mount(fd2, fd3) > 0); + assert_se(fds_inode_and_mount_same(fd1, fd2) == 0); + assert_se(fds_inode_and_mount_same(fd2, fd3) > 0); } TEST(fd_get_path) { diff --git a/src/test/test-fs-util.c b/src/test/test-fs-util.c index a56f8f92ad..7cb720938c 100644 --- a/src/test/test-fs-util.c +++ b/src/test/test-fs-util.c @@ -871,10 +871,10 @@ TEST(xat_fdroot) { ASSERT_OK_POSITIVE(path_is_root_at(fd, ".")); ASSERT_OK_POSITIVE(path_is_root_at(fd, "/")); - ASSERT_OK_POSITIVE(fds_are_same_mount(fd, fd)); - ASSERT_OK_POSITIVE(fds_are_same_mount(XAT_FDROOT, XAT_FDROOT)); - ASSERT_OK_POSITIVE(fds_are_same_mount(fd, XAT_FDROOT)); - ASSERT_OK_POSITIVE(fds_are_same_mount(XAT_FDROOT, fd)); + ASSERT_OK_POSITIVE(fds_inode_and_mount_same(fd, fd)); + ASSERT_OK_POSITIVE(fds_inode_and_mount_same(XAT_FDROOT, XAT_FDROOT)); + ASSERT_OK_POSITIVE(fds_inode_and_mount_same(fd, XAT_FDROOT)); + ASSERT_OK_POSITIVE(fds_inode_and_mount_same(XAT_FDROOT, fd)); ASSERT_OK_POSITIVE(dir_fd_is_root(XAT_FDROOT)); ASSERT_OK_POSITIVE(dir_fd_is_root(fd)); From e4206858ad29b8e9ea9b26381a08453efe7f309e Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 25 Feb 2026 13:08:34 +0100 Subject: [PATCH 08/11] fd-util: minor shortcut --- src/basic/fd-util.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/basic/fd-util.c b/src/basic/fd-util.c index 4d720e014e..e032bb62ac 100644 --- a/src/basic/fd-util.c +++ b/src/basic/fd-util.c @@ -1085,6 +1085,9 @@ int fds_inode_and_mount_same(int fd1, int fd2) { if (r < 0) return r; + if (fd1 == fd2) /* Shortcut things if fds are the same (only after validating the fd) */ + return true; + r = xstatx(fd2, /* path = */ NULL, AT_EMPTY_PATH, STATX_TYPE|STATX_INO|STATX_MNT_ID, &sx2); From 6d61bb671de3b096a5b3eee7e5f1de50e2bc1a41 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 25 Feb 2026 13:08:48 +0100 Subject: [PATCH 09/11] mountwork: use statx_mount_same() where appropriate --- src/mountfsd/mountwork.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mountfsd/mountwork.c b/src/mountfsd/mountwork.c index 7115f33e36..2e75ac532f 100644 --- a/src/mountfsd/mountwork.c +++ b/src/mountfsd/mountwork.c @@ -947,7 +947,7 @@ static DirectoryOwnership validate_directory_fd( return DIRECTORY_IS_OTHERWISE_OWNED; } - if (stx.stx_mnt_id != new_stx.stx_mnt_id) { + if (!statx_mount_same(&stx, &new_stx)) { /* NB, this check is probably redundant, given we also check * STATX_ATTR_MOUNT_ROOT. The only reason we have it here is to provide extra safety * in case the mount tree is rearranged concurrently with our traversal, so that From 2e9208199f98fa07f09c7d01828688c52d955651 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 25 Feb 2026 15:10:12 +0100 Subject: [PATCH 10/11] stat-util: teach statx_mount_same() STATX_MNT_ID_UNIQUE So far statx_mount_same() assumed STATX_MNT_ID_UNIQUE didn't exist. However it does exist, hence do something useful if we see it set. Note that this creates a certain ambiguity: if we compare one statx struct with STATX_MNT_ID_UNIQUE and one without it (but with the regular mnt id), then we cnanot really come to a clear conclusion, hence need to introduce a third, unknown state. Note that we don't request STATX_MNT_ID_UNIQUE yet wherever we call statx_mount_same(). THis will be added in a later commit. --- src/basic/chase.c | 19 ++++++++++++++----- src/basic/fd-util.c | 6 +++++- src/basic/stat-util.c | 9 +++++---- src/basic/stat-util.h | 2 +- src/mountfsd/mountwork.c | 5 ++++- 5 files changed, 29 insertions(+), 12 deletions(-) diff --git a/src/basic/chase.c b/src/basic/chase.c index 8030c74079..2243b12972 100644 --- a/src/basic/chase.c +++ b/src/basic/chase.c @@ -379,11 +379,20 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int * root tree we operate in is allowed, where an inode is moved up the tree while we * look at it, and thus get the current path wrong and think we are deeper down than * we actually are. */ - if (FLAGS_SET(flags, CHASE_AT_RESOLVE_IN_ROOT) && - (empty_or_root(done) || (statx_inode_same(&stx, &root_stx) && statx_mount_same(&stx, &root_stx)))) { - if (FLAGS_SET(flags, CHASE_STEP)) - goto chased_one; - continue; + if (FLAGS_SET(flags, CHASE_AT_RESOLVE_IN_ROOT)) { + bool is_root = empty_or_root(done); + if (!is_root && statx_inode_same(&stx, &root_stx)) { + r = statx_mount_same(&stx, &root_stx); + if (r < 0) + return r; + + is_root = r > 0; + } + if (is_root) { + if (FLAGS_SET(flags, CHASE_STEP)) + goto chased_one; + continue; + } } fd_parent = openat(fd, "..", O_CLOEXEC|O_NOFOLLOW|O_PATH|O_DIRECTORY); diff --git a/src/basic/fd-util.c b/src/basic/fd-util.c index e032bb62ac..6fb4e78c2f 100644 --- a/src/basic/fd-util.c +++ b/src/basic/fd-util.c @@ -1094,7 +1094,11 @@ int fds_inode_and_mount_same(int fd1, int fd2) { if (r < 0) return r; - return statx_inode_same(&sx1, &sx2) && statx_mount_same(&sx1, &sx2); + r = statx_mount_same(&sx1, &sx2); + if (r <= 0) + return r; + + return statx_inode_same(&sx1, &sx2); } int resolve_xat_fdroot(int *fd, const char **path, char **ret_buffer) { diff --git a/src/basic/stat-util.c b/src/basic/stat-util.c index c5702ca6ab..7f065b5c7f 100644 --- a/src/basic/stat-util.c +++ b/src/basic/stat-util.c @@ -675,14 +675,15 @@ bool statx_inode_same(const struct statx *a, const struct statx *b) { a->stx_ino == b->stx_ino; } -bool statx_mount_same(const struct statx *a, const struct statx *b) { +int statx_mount_same(const struct statx *a, const struct statx *b) { if (!statx_is_set(a) || !statx_is_set(b)) return false; - assert(FLAGS_SET(a->stx_mask, STATX_MNT_ID)); - assert(FLAGS_SET(b->stx_mask, STATX_MNT_ID)); + if ((FLAGS_SET(a->stx_mask, STATX_MNT_ID) && FLAGS_SET(b->stx_mask, STATX_MNT_ID)) || + (FLAGS_SET(a->stx_mask, STATX_MNT_ID_UNIQUE) && FLAGS_SET(b->stx_mask, STATX_MNT_ID_UNIQUE))) + return a->stx_mnt_id == b->stx_mnt_id; - return a->stx_mnt_id == b->stx_mnt_id; + return -ENODATA; } usec_t statx_timestamp_load(const struct statx_timestamp *ts) { diff --git a/src/basic/stat-util.h b/src/basic/stat-util.h index 2336c775e9..230535aad4 100644 --- a/src/basic/stat-util.h +++ b/src/basic/stat-util.h @@ -110,7 +110,7 @@ bool stat_inode_same(const struct stat *a, const struct stat *b); bool stat_inode_unmodified(const struct stat *a, const struct stat *b); bool statx_inode_same(const struct statx *a, const struct statx *b); -bool statx_mount_same(const struct statx *a, const struct statx *b); +int statx_mount_same(const struct statx *a, const struct statx *b); int xstatfsat(int dir_fd, const char *path, struct statfs *ret); diff --git a/src/mountfsd/mountwork.c b/src/mountfsd/mountwork.c index 2e75ac532f..54d92b5585 100644 --- a/src/mountfsd/mountwork.c +++ b/src/mountfsd/mountwork.c @@ -947,7 +947,10 @@ static DirectoryOwnership validate_directory_fd( return DIRECTORY_IS_OTHERWISE_OWNED; } - if (!statx_mount_same(&stx, &new_stx)) { + r = statx_mount_same(&stx, &new_stx); + if (r < 0) + return log_debug_errno(r, "Failed to compare mount IDs: %m"); + if (!r) { /* NB, this check is probably redundant, given we also check * STATX_ATTR_MOUNT_ROOT. The only reason we have it here is to provide extra safety * in case the mount tree is rearranged concurrently with our traversal, so that From d2b27a7a8c894052e0847d8905cfb52575d59a1a Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 25 Feb 2026 14:16:47 +0100 Subject: [PATCH 11/11] xstatx_full(): add flag to acquire STATX_MNT_ID_UNIQUE if we can, with fallback. --- src/basic/chase.c | 56 ++++++++++++++++++++++++++++++++----- src/basic/dirent-util.c | 1 + src/basic/mountpoint-util.c | 1 + src/basic/stat-util.c | 21 +++++++++++--- src/basic/stat-util.h | 11 ++++++-- src/basic/xattr-util.c | 8 ++++-- src/mountfsd/mountwork.c | 6 ++-- src/shared/find-esp.c | 1 + src/tmpfiles/tmpfiles.c | 8 ++++-- 9 files changed, 92 insertions(+), 21 deletions(-) diff --git a/src/basic/chase.c b/src/basic/chase.c index 2243b12972..82946eae5f 100644 --- a/src/basic/chase.c +++ b/src/basic/chase.c @@ -150,7 +150,7 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int struct statx root_stx, stx; bool need_absolute = false; /* allocate early to avoid compiler warnings around goto */ const char *todo; - unsigned mask = STATX_TYPE|STATX_UID|STATX_INO|STATX_MNT_ID; + unsigned mask = STATX_TYPE|STATX_UID|STATX_INO; int r; assert(!FLAGS_SET(flags, CHASE_PREFIX_ROOT)); @@ -263,7 +263,14 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int if (fd < 0) return -errno; - r = xstatx(fd, /* path= */ NULL, /* flags= */ 0, mask, &stx); + r = xstatx_full(fd, + /* path= */ NULL, + /* statx_flags= */ 0, + XSTATX_MNT_ID_BEST, + mask, + /* optional_mask= */ 0, + /* mandatory_attributes= */ 0, + &stx); if (r < 0) return r; @@ -316,7 +323,14 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int if (fd < 0) return -errno; - r = xstatx(fd, /* path= */ NULL, /* flags= */ 0, mask, &stx); + r = xstatx_full(fd, + /* path= */ NULL, + /* statx_flags= */ 0, + XSTATX_MNT_ID_BEST, + mask, + /* optional_mask= */ 0, + /* mandatory_attributes= */ 0, + &stx); if (r < 0) return r; root_stx = stx; /* remember stat data of the root, so that we can recognize it later */ @@ -399,7 +413,14 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int if (fd_parent < 0) return -errno; - r = xstatx(fd_parent, /* path= */ NULL, /* flags= */ 0, mask, &stx_parent); + r = xstatx_full(fd_parent, + /* path= */ NULL, + /* statx_flags= */ 0, + XSTATX_MNT_ID_BEST, + mask, + /* optional_mask= */ 0, + /* mandatory_attributes= */ 0, + &stx_parent); if (r < 0) return r; @@ -465,7 +486,14 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int if (fd_grandparent < 0) return -errno; - r = xstatx(fd_grandparent, /* path= */ NULL, /* flags= */ 0, mask, &stx_grandparent); + r = xstatx_full(fd_grandparent, + /* path= */ NULL, + /* statx_flags= */ 0, + XSTATX_MNT_ID_BEST, + mask, + /* optional_mask= */ 0, + /* mandatory_attributes= */ 0, + &stx_grandparent); if (r < 0) return r; @@ -519,7 +547,14 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int } /* ... and then check what it actually is. */ - r = xstatx(child, /* path= */ NULL, /* flags= */ 0, mask, &stx_child); + r = xstatx_full(child, + /* path= */ NULL, + /* statx_flags= */ 0, + XSTATX_MNT_ID_BEST, + mask, + /* optional_mask= */ 0, + /* mandatory_attributes= */ 0, + &stx_child); if (r < 0) return r; @@ -557,7 +592,14 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int if (fd < 0) return fd; - r = xstatx(fd, /* path= */ NULL, /* flags= */ 0, mask, &stx); + r = xstatx_full(fd, + /* path= */ NULL, + /* statx_flags= */ 0, + XSTATX_MNT_ID_BEST, + mask, + /* optional_mask= */ 0, + /* mandatory_attributes= */ 0, + &stx); if (r < 0) return r; diff --git a/src/basic/dirent-util.c b/src/basic/dirent-util.c index a150874777..91a7040e40 100644 --- a/src/basic/dirent-util.c +++ b/src/basic/dirent-util.c @@ -27,6 +27,7 @@ int dirent_ensure_type(int dir_fd, struct dirent *de) { r = xstatx_full(dir_fd, de->d_name, AT_SYMLINK_NOFOLLOW|AT_NO_AUTOMOUNT, + /* xstatx_flags= */ 0, /* mandatory_mask= */ STATX_TYPE, /* optional_mask= */ STATX_INO, /* mandatory_attributes= */ 0, diff --git a/src/basic/mountpoint-util.c b/src/basic/mountpoint-util.c index 06d4d4450a..79b51cb099 100644 --- a/src/basic/mountpoint-util.c +++ b/src/basic/mountpoint-util.c @@ -250,6 +250,7 @@ int is_mount_point_at(int dir_fd, const char *path, int flags) { 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 */ + /* xstatx_flags = */ 0, STATX_TYPE|STATX_INO, /* optional_mask = */ 0, STATX_ATTR_MOUNT_ROOT, diff --git a/src/basic/stat-util.c b/src/basic/stat-util.c index 7f065b5c7f..ef39562992 100644 --- a/src/basic/stat-util.c +++ b/src/basic/stat-util.c @@ -328,7 +328,8 @@ DEFINE_STATX_BITS_TO_STRING(statx_attributes, uint64_t, statx_attribute_to_name, int xstatx_full(int fd, const char *path, - int flags, + int statx_flags, + XStatXFlags xstatx_flags, unsigned mandatory_mask, unsigned optional_mask, uint64_t mandatory_attributes, @@ -344,10 +345,13 @@ int xstatx_full(int fd, * 3. Takes separate mandatory and optional mask params, plus mandatory attributes. * Returns -EUNATCH if statx() does not return all masks specified as mandatory, * > 0 if all optional masks are supported, 0 otherwise. + * 4. Supports a new flag XSTATX_MNT_ID_BEST which acquires STATX_MNT_ID_UNIQUE if available and + * STATX_MNT_ID if not. */ assert(fd >= 0 || IN_SET(fd, AT_FDCWD, XAT_FDROOT)); assert((mandatory_mask & optional_mask) == 0); + assert(!FLAGS_SET(xstatx_flags, XSTATX_MNT_ID_BEST) || !((mandatory_mask|optional_mask) & (STATX_MNT_ID|STATX_MNT_ID_UNIQUE))); assert(ret); _cleanup_free_ char *p = NULL; @@ -355,12 +359,21 @@ int xstatx_full(int fd, if (r < 0) return r; - if (statx(fd, strempty(path), - flags|(isempty(path) ? AT_EMPTY_PATH : 0), - mandatory_mask|optional_mask, + unsigned request_mask = mandatory_mask|optional_mask; + if (FLAGS_SET(xstatx_flags, XSTATX_MNT_ID_BEST)) + request_mask |= STATX_MNT_ID|STATX_MNT_ID_UNIQUE; + + if (statx(fd, + strempty(path), + statx_flags|(isempty(path) ? AT_EMPTY_PATH : 0), + request_mask, &sx) < 0) return negative_errno(); + if (FLAGS_SET(xstatx_flags, XSTATX_MNT_ID_BEST) && + !(sx.stx_mask & (STATX_MNT_ID|STATX_MNT_ID_UNIQUE))) + return log_debug_errno(SYNTHETIC_ERRNO(EUNATCH), "statx() did not return either STATX_MNT_ID or STATX_MNT_ID_UNIQUE."); + if (!FLAGS_SET(sx.stx_mask, mandatory_mask)) { if (DEBUG_LOGGING) { _cleanup_free_ char *mask_str = statx_mask_to_string(mandatory_mask & ~sx.stx_mask); diff --git a/src/basic/stat-util.h b/src/basic/stat-util.h index 230535aad4..c261014cd2 100644 --- a/src/basic/stat-util.h +++ b/src/basic/stat-util.h @@ -47,9 +47,14 @@ static inline int null_or_empty_path(const char *fn) { return null_or_empty_path_with_root(fn, NULL); } +typedef enum XStatXFlags { + XSTATX_MNT_ID_BEST = 1 << 0, /* Like STATX_MNT_ID_UNIQUE if available, STATX_MNT_ID otherwise */ +} XStatXFlags; + int xstatx_full(int fd, const char *path, - int flags, + int statx_flags, + XStatXFlags xstatx_flags, unsigned mandatory_mask, unsigned optional_mask, uint64_t mandatory_attributes, @@ -58,11 +63,11 @@ int xstatx_full(int fd, static inline int xstatx( int fd, const char *path, - int flags, + int statx_flags, unsigned mandatory_mask, struct statx *ret) { - return xstatx_full(fd, path, flags, mandatory_mask, 0, 0, ret); + return xstatx_full(fd, path, statx_flags, 0, mandatory_mask, 0, 0, ret); } int fd_is_read_only_fs(int fd); diff --git a/src/basic/xattr-util.c b/src/basic/xattr-util.c index 68ee83d899..e77d0bc8e8 100644 --- a/src/basic/xattr-util.c +++ b/src/basic/xattr-util.c @@ -429,11 +429,13 @@ int getcrtime_at( * concept is useful for determining how "old" a file really is, and hence using the older of the two makes * most sense. */ - r = xstatx_full(fd, path, + r = xstatx_full(fd, + path, at_flags_normalize_nofollow(at_flags)|AT_STATX_DONT_SYNC, - /* mandatory_mask = */ 0, + /* xstatx_flags= */ 0, + /* mandatory_mask= */ 0, STATX_BTIME, - /* mandatory_attributes = */ 0, + /* mandatory_attributes= */ 0, &sx); if (r > 0 && sx.stx_btime.tv_sec != 0) /* > 0: all optional masks are supported */ a = statx_timestamp_load(&sx.stx_btime); diff --git a/src/mountfsd/mountwork.c b/src/mountfsd/mountwork.c index 54d92b5585..922bcb1b18 100644 --- a/src/mountfsd/mountwork.c +++ b/src/mountfsd/mountwork.c @@ -866,7 +866,8 @@ static DirectoryOwnership validate_directory_fd( r = xstatx_full(fd, /* path= */ NULL, AT_EMPTY_PATH, - /* mandatory_mask= */ STATX_TYPE|STATX_UID|STATX_MNT_ID|STATX_INO, + /* xstatx_flags= */ XSTATX_MNT_ID_BEST, + /* mandatory_mask= */ STATX_TYPE|STATX_UID|STATX_INO, /* optional_mask= */ 0, /* mandatory_attributes= */ STATX_ATTR_MOUNT_ROOT, &stx); @@ -933,7 +934,8 @@ static DirectoryOwnership validate_directory_fd( r = xstatx_full(new_parent_fd, /* path= */ NULL, AT_EMPTY_PATH, - /* mandatory_mask= */ STATX_UID|STATX_MNT_ID|STATX_INO, + /* xstatx_flags= */ XSTATX_MNT_ID_BEST, + /* mandatory_mask= */ STATX_UID|STATX_INO, /* optional_mask= */ 0, /* mandatory_attributes= */ STATX_ATTR_MOUNT_ROOT, &new_stx); diff --git a/src/shared/find-esp.c b/src/shared/find-esp.c index 3ba9b8a4b6..a2a2093faf 100644 --- a/src/shared/find-esp.c +++ b/src/shared/find-esp.c @@ -287,6 +287,7 @@ static int verify_fsroot_dir( r = xstatx_full(dir_fd, f, AT_SYMLINK_NOFOLLOW, + /* xstatx_flags= */ 0, STATX_TYPE|STATX_INO, /* optional_mask = */ 0, STATX_ATTR_MOUNT_ROOT, diff --git a/src/tmpfiles/tmpfiles.c b/src/tmpfiles/tmpfiles.c index 090884b228..0d62b847b6 100644 --- a/src/tmpfiles/tmpfiles.c +++ b/src/tmpfiles/tmpfiles.c @@ -586,9 +586,12 @@ static int opendir_and_stat( return 0; } - r = xstatx_full(dirfd(d), /* path = */ NULL, AT_EMPTY_PATH, + r = xstatx_full(dirfd(d), + /* path= */ NULL, + AT_EMPTY_PATH, + /* xstatx_flags= */ 0, STATX_MODE|STATX_INO|STATX_ATIME|STATX_MTIME, - /* optional_mask = */ 0, + /* optional_mask= */ 0, STATX_ATTR_MOUNT_ROOT, &sx); if (r < 0) @@ -687,6 +690,7 @@ static int dir_cleanup( struct statx sx; r = xstatx_full(dirfd(d), de->d_name, AT_SYMLINK_NOFOLLOW|AT_NO_AUTOMOUNT, + /* xstatx_flags= */ 0, STATX_TYPE|STATX_MODE|STATX_UID, STATX_ATIME|STATX_MTIME|STATX_CTIME|STATX_BTIME, STATX_ATTR_MOUNT_ROOT,