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;
assert_se(streq(ret, expected));
}
+TEST(trailing_dot_dot) {
+ _cleanup_free_ char *path = NULL, *fdpath = NULL;
+ _cleanup_close_ int fd = -EBADF;
+
+ assert_se(chase("/usr/..", NULL, CHASE_PARENT, &path, &fd) >= 0);
+ assert_se(path_equal(path, "/"));
+ assert_se(fd_get_path(fd, &fdpath) >= 0);
+ assert_se(path_equal(fdpath, "/"));
+
+ path = mfree(path);
+ fdpath = mfree(fdpath);
+ fd = safe_close(fd);
+
+ _cleanup_(rm_rf_physical_and_freep) char *t = NULL;
+ assert_se(mkdtemp_malloc(NULL, &t) >= 0);
+ _cleanup_free_ char *sub = ASSERT_PTR(path_join(t, "a/b/c/d"));
+ assert_se(mkdir_p(sub, 0700) >= 0);
+ _cleanup_free_ char *suffixed = ASSERT_PTR(path_join(sub, ".."));
+ assert_se(chase(suffixed, NULL, CHASE_PARENT, &path, &fd) >= 0);
+ _cleanup_free_ char *expected1 = ASSERT_PTR(path_join(t, "a/b/c"));
+ _cleanup_free_ char *expected2 = ASSERT_PTR(path_join(t, "a/b"));
+
+ assert_se(path_equal(path, expected1));
+ assert_se(fd_get_path(fd, &fdpath) >= 0);
+ assert_se(path_equal(fdpath, expected2));
+}
+
static int intro(void) {
arg_test_dir = saved_argv[1];
return EXIT_SUCCESS;