]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
chase: tighten checks on ".." once we hit the root of an CHASE_AT_RESOLVE_IN_ROOT...
authorLennart Poettering <lennart@amutable.com>
Wed, 25 Feb 2026 11:26:42 +0000 (12:26 +0100)
committerLennart Poettering <lennart@amutable.com>
Tue, 3 Mar 2026 07:48:01 +0000 (08:48 +0100)
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

index 1d3596bb796e45fb09064c82c9897bcd530b0bdf..6e8cc15f2efe7de8d843b104744081ec309ae4ad 100644 (file)
@@ -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;