]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
chase: put limit on overall chase cycles
authorLennart Poettering <lennart@amutable.com>
Wed, 25 Feb 2026 10:43:11 +0000 (11:43 +0100)
committerLennart Poettering <lennart@amutable.com>
Tue, 3 Mar 2026 07:48:01 +0000 (08:48 +0100)
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
src/basic/chase.h

index abe85a2a0892b1fdd57d748f089f67abb8421b23..11cbcbe3e4d7b649e80cb805f8ecb12af5efab65 100644 (file)
@@ -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;
index d0674aae73c990b378c83b7910b7f4f8a8055ce6..b770f45a1ecd896d443c718a1a1c08e7e27a07af 100644 (file)
@@ -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);