From: Lennart Poettering Date: Wed, 25 Feb 2026 11:15:14 +0000 (+0100) Subject: chase: port to statx() X-Git-Tag: v260-rc2~23^2~7 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=c8031ff37eb44a1bebb4a2c3ccfae51ac09982a5;p=thirdparty%2Fsystemd.git 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. --- diff --git a/src/basic/chase.c b/src/basic/chase.c index 11cbcbe3e4d..1d3596bb796 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 b770f45a1ec..d779658ba15 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 da75ffb8181..090884b2283 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);