summaryrefslogtreecommitdiffstats
path: root/src/basic/chase.c
diff options
context:
space:
mode:
authorLennart Poettering <lennart@poettering.net>2023-11-01 12:46:17 +0100
committerLuca Boccassi <luca.boccassi@gmail.com>2023-11-01 15:43:24 +0100
commit9c21cfdd7d68797c6ae63ca20aa624577ba05246 (patch)
tree717a852b0b67b3f5c264d39d5e34361f82732750 /src/basic/chase.c
parentMerge pull request #29764 from dtardon/varlink-io.systemd.service (diff)
downloadsystemd-9c21cfdd7d68797c6ae63ca20aa624577ba05246.tar.xz
systemd-9c21cfdd7d68797c6ae63ca20aa624577ba05246.zip
chase: fix corner case when using CHASE_PARENT with a path ending in ".."
If we use CHASE_PARENT on a path ending in ".." then things are a bit weird, because we the last path we look at is actually the *parent* and not the *child* of the preceeding path. Hence we cannot just return the 2nd to last fd we look at. We have to correct it, by going *two* levels up, to get to the actual parent, and make sure CHASE_PARENT does what it should. Example: for the path /a/b/c chase() with CHASE_PARENT will return /a/b/c as path, and the fd returned points to /a/b. All good. But now, for the path /a/b/c/.. chase() with CHASE_PARENT would previously return /a/b as path (which is OK) but the fd would point to /a/b/c, which is *not* the parent of /a/b, after all! To get to the actual parent of /a/b we have to go *two* levels up to get to /a. Very confusing. But that's what we here for, no? @mrc0mmand ran into this in https://github.com/systemd/systemd/pull/28891#issuecomment-1782833722
Diffstat (limited to 'src/basic/chase.c')
-rw-r--r--src/basic/chase.c22
1 files changed, 21 insertions, 1 deletions
diff --git a/src/basic/chase.c b/src/basic/chase.c
index c3ecbbb3fe..b0592c538d 100644
--- a/src/basic/chase.c
+++ b/src/basic/chase.c
@@ -335,8 +335,28 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int
unsafe_transition(&st, &st_parent))
return log_unsafe_transition(fd, fd_parent, path, flags);
- if (FLAGS_SET(flags, CHASE_PARENT) && isempty(todo))
+ /* 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;
+
+ 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;
+
+ if (FLAGS_SET(flags, CHASE_SAFE) &&
+ unsafe_transition(&st_parent, &st_grandparent))
+ return log_unsafe_transition(fd_parent, fd_grandparent, path, flags);
+
+ st = st_grandparent;
+ close_and_replace(fd, fd_grandparent);
break;
+ }
/* update fd and stat */
st = st_parent;