From: Lennart Poettering Date: Wed, 25 Feb 2026 11:26:42 +0000 (+0100) Subject: chase: tighten checks on ".." once we hit the root of an CHASE_AT_RESOLVE_IN_ROOT... X-Git-Tag: v260-rc2~23^2~6 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=3538b77f9dc139f76561ec88eda17b1df6567c82;p=thirdparty%2Fsystemd.git 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. --- diff --git a/src/basic/chase.c b/src/basic/chase.c index 1d3596bb796..6e8cc15f2ef 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;