]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
chase: port to statx()
authorLennart Poettering <lennart@amutable.com>
Wed, 25 Feb 2026 11:15:14 +0000 (12:15 +0100)
committerLennart Poettering <lennart@amutable.com>
Tue, 3 Mar 2026 07:48:01 +0000 (08:48 +0100)
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
src/basic/chase.h
src/tmpfiles/tmpfiles.c

index 11cbcbe3e4d7b649e80cb805f8ecb12af5efab65..1d3596bb796e45fb09064c82c9897bcd530b0bdf 100644 (file)
          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;
                 }
index b770f45a1ecd896d443c718a1a1c08e7e27a07af..d779658ba15f856cef92d55b3a3d7213da50a04a 100644 (file)
@@ -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
index da75ffb8181278d26dfd2022f99a458bb1c8e773..090884b228303e42411dcbc8f0ef735e2ea1821d 100644 (file)
@@ -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);