diff --git a/src/basic/chase-symlinks.c b/src/basic/chase-symlinks.c index 1b434950e6..b3bd635e86 100644 --- a/src/basic/chase-symlinks.c +++ b/src/basic/chase-symlinks.c @@ -98,20 +98,9 @@ int chase_symlinks_at( if ((flags & CHASE_STEP) && ret_fd) return -EINVAL; - if (FLAGS_SET(flags, CHASE_MKDIR_0755|CHASE_NONEXISTENT)) - return -EINVAL; - if (isempty(path)) path = "."; - if (flags & CHASE_PARENT) { - r = path_extract_directory(path, &buffer); - if (r == -EDESTADDRREQ) - path = "."; /* If we don't have a parent directory, fall back to the dir_fd directory. */ - else if (r < 0) - return r; - } - /* This function resolves symlinks of the path relative to the given directory file descriptor. If * CHASE_SYMLINKS_RESOLVE_IN_ROOT is specified and a directory file descriptor is provided, symlinks * are resolved relative to the given directory file descriptor. Otherwise, they are resolved @@ -292,6 +281,9 @@ int chase_symlinks_at( previous_stat = st; } + if (FLAGS_SET(flags, CHASE_PARENT) && isempty(todo)) + break; + close_and_replace(fd, fd_parent); continue; @@ -306,20 +298,23 @@ int chase_symlinks_at( if (!isempty(todo) && !path_is_safe(todo)) return r; - if (flags & CHASE_NONEXISTENT) { + if (FLAGS_SET(flags, CHASE_MKDIR_0755) && !isempty(todo)) { + child = open_mkdir_at(fd, first, O_CLOEXEC|O_PATH|O_EXCL, 0755); + if (child < 0) + return child; + } else if (FLAGS_SET(flags, CHASE_PARENT) && isempty(todo)) { + if (!path_extend(&done, first)) + return -ENOMEM; + + break; + } else if (flags & CHASE_NONEXISTENT) { if (!path_extend(&done, first, todo)) return -ENOMEM; exists = false; break; - } - - if (!(flags & CHASE_MKDIR_0755)) + } else return r; - - child = open_mkdir_at(fd, first, O_CLOEXEC|O_PATH|O_EXCL, 0755); - if (child < 0) - return child; } if (fstat(child, &st) < 0) @@ -394,11 +389,14 @@ int chase_symlinks_at( if (!path_extend(&done, first)) return -ENOMEM; + if (FLAGS_SET(flags, CHASE_PARENT) && isempty(todo)) + break; + /* And iterate again, but go one directory further down. */ close_and_replace(fd, child); } - if (flags & (CHASE_PARENT|CHASE_MKDIR_0755)) { + if (flags & CHASE_PARENT) { r = fd_verify_directory(fd); if (r < 0) return r; @@ -551,28 +549,38 @@ int chase_symlinks_and_open( char **ret_path) { _cleanup_close_ int path_fd = -EBADF; - _cleanup_free_ char *p = NULL; + _cleanup_free_ char *p = NULL, *fname = NULL; + mode_t mode = open_flags & O_DIRECTORY ? 0755 : 0644; + const char *q; int r; if (chase_flags & (CHASE_NONEXISTENT|CHASE_STEP)) return -EINVAL; if (empty_or_root(root) && !ret_path && - (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS|CHASE_PARENT|CHASE_MKDIR_0755)) == 0) { + (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS|CHASE_PARENT|CHASE_MKDIR_0755)) == 0) /* Shortcut this call if none of the special features of this call are requested */ - r = open(path, open_flags | (FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? O_NOFOLLOW : 0)); - if (r < 0) - return -errno; + return RET_NERRNO(xopenat(AT_FDCWD, path, + open_flags | (FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? O_NOFOLLOW : 0), + mode)); - return r; - } - - r = chase_symlinks(path, root, chase_flags, ret_path ? &p : NULL, &path_fd); + r = chase_symlinks(path, root, CHASE_PARENT|chase_flags, &p, &path_fd); if (r < 0) return r; assert(path_fd >= 0); - r = fd_reopen(path_fd, open_flags); + assert_se(q = path_startswith(p, empty_to_root(root))); + if (isempty(q)) + q = "."; + + r = path_extract_filename(q, &fname); + if (r < 0 && r != -EADDRNOTAVAIL) + return r; + + if (FLAGS_SET(chase_flags, CHASE_PARENT) || r == -EADDRNOTAVAIL) + r = fd_reopen(path_fd, open_flags); + else + r = xopenat(path_fd, fname, open_flags|O_NOFOLLOW, mode); if (r < 0) return r; @@ -748,31 +756,25 @@ int chase_symlinks_and_unlink( int unlink_flags, char **ret_path) { - _cleanup_free_ char *p = NULL, *rp = NULL, *fname = NULL; + _cleanup_free_ char *p = NULL, *fname = NULL; _cleanup_close_ int fd = -EBADF; int r; assert(path); - r = path_extract_filename(path, &fname); - if (r < 0) - return r; - - fd = chase_symlinks_and_open(path, root, chase_flags|CHASE_PARENT, O_PATH|O_DIRECTORY|O_CLOEXEC, ret_path ? &p : NULL); + fd = chase_symlinks_and_open(path, root, chase_flags|CHASE_PARENT|CHASE_NOFOLLOW, O_PATH|O_DIRECTORY|O_CLOEXEC, &p); if (fd < 0) return fd; - if (p) { - rp = path_join(p, fname); - if (!rp) - return -ENOMEM; - } + r = path_extract_filename(p, &fname); + if (r < 0) + return r; if (unlinkat(fd, fname, unlink_flags) < 0) return -errno; if (ret_path) - *ret_path = TAKE_PTR(rp); + *ret_path = TAKE_PTR(p); return 0; } @@ -785,7 +787,8 @@ int chase_symlinks_at_and_open( char **ret_path) { _cleanup_close_ int path_fd = -EBADF; - _cleanup_free_ char *p = NULL; + _cleanup_free_ char *p = NULL, *fname = NULL; + mode_t mode = open_flags & O_DIRECTORY ? 0755 : 0644; int r; if (chase_flags & (CHASE_NONEXISTENT|CHASE_STEP)) @@ -794,14 +797,22 @@ int chase_symlinks_at_and_open( if (dir_fd == AT_FDCWD && !ret_path && (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS|CHASE_PARENT|CHASE_MKDIR_0755)) == 0) /* Shortcut this call if none of the special features of this call are requested */ - return RET_NERRNO(openat(dir_fd, path, open_flags | (FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? O_NOFOLLOW : 0))); + return RET_NERRNO(xopenat(dir_fd, path, + open_flags | (FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? O_NOFOLLOW : 0), + mode)); - r = chase_symlinks_at(dir_fd, path, chase_flags, ret_path ? &p : NULL, &path_fd); + r = chase_symlinks_at(dir_fd, path, chase_flags|CHASE_PARENT, &p, &path_fd); if (r < 0) return r; - assert(path_fd >= 0); - r = fd_reopen(path_fd, open_flags); + r = path_extract_filename(p, &fname); + if (r < 0 && r != -EDESTADDRREQ) + return r; + + if (FLAGS_SET(chase_flags, CHASE_PARENT) || r == -EDESTADDRREQ) + r = fd_reopen(path_fd, open_flags); + else + r = xopenat(path_fd, fname, open_flags|O_NOFOLLOW, mode); if (r < 0) return r; diff --git a/src/basic/chase-symlinks.h b/src/basic/chase-symlinks.h index 26e8cfceca..448471fa6d 100644 --- a/src/basic/chase-symlinks.h +++ b/src/basic/chase-symlinks.h @@ -20,8 +20,10 @@ typedef enum ChaseSymlinksFlags { CHASE_AT_RESOLVE_IN_ROOT = 1 << 8, /* Same as openat2()'s RESOLVE_IN_ROOT flag, symlinks are resolved * relative to the given directory fd instead of root. */ CHASE_PROHIBIT_SYMLINKS = 1 << 9, /* Refuse all symlinks */ - CHASE_PARENT = 1 << 10, /* Chase the parent directory of the given path. */ - CHASE_MKDIR_0755 = 1 << 11, /* Create any missing directories in the given path. */ + CHASE_PARENT = 1 << 10, /* Chase the parent directory of the given path. Note that the + * full path is still stored in ret_path and only the returned + * file descriptor will point to the parent directory. */ + CHASE_MKDIR_0755 = 1 << 11, /* Create any missing parent directories in the given path. */ } ChaseSymlinksFlags; bool unsafe_transition(const struct stat *a, const struct stat *b); diff --git a/src/test/test-fs-util.c b/src/test/test-fs-util.c index 065ad3c6e7..a18dc00fc5 100644 --- a/src/test/test-fs-util.c +++ b/src/test/test-fs-util.c @@ -331,10 +331,10 @@ TEST(chase_symlinks) { } assert_se(lstat(p, &st) >= 0); - r = chase_symlinks_and_unlink(p, NULL, 0, 0, &result); + r = chase_symlinks_and_unlink(p, NULL, 0, 0, &result); + assert_se(r == 0); assert_se(path_equal(result, p)); result = mfree(result); - assert_se(r == 0); assert_se(lstat(p, &st) == -1 && errno == ENOENT); /* Test CHASE_NOFOLLOW */ @@ -426,18 +426,6 @@ TEST(chase_symlinks) { assert_se(chase_symlinks("top/dot/dot", temp, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, NULL, NULL) == -EREMCHG); assert_se(chase_symlinks("top/dot/dot", temp, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS|CHASE_WARN, NULL, NULL) == -EREMCHG); - /* Test CHASE_PARENT */ - - assert_se(chase_symlinks("/chase/parent", temp, CHASE_PREFIX_ROOT|CHASE_PARENT|CHASE_NONEXISTENT, &result, NULL) >= 0); - p = strjoina(temp, "/chase"); - assert_se(streq(p, result)); - result = mfree(result); - assert_se(chase_symlinks("/chase", temp, CHASE_PREFIX_ROOT|CHASE_PARENT|CHASE_NONEXISTENT, &result, NULL) >= 0); - assert_se(streq(temp, result)); - result = mfree(result); - assert_se(chase_symlinks("/", temp, CHASE_PREFIX_ROOT|CHASE_PARENT|CHASE_NONEXISTENT, NULL, NULL) == -EADDRNOTAVAIL); - assert_se(chase_symlinks(".", temp, CHASE_PREFIX_ROOT|CHASE_PARENT|CHASE_NONEXISTENT, NULL, NULL) == -EADDRNOTAVAIL); - cleanup: assert_se(rm_rf(temp, REMOVE_ROOT|REMOVE_PHYSICAL) >= 0); } @@ -479,23 +467,51 @@ TEST(chase_symlinks_at) { /* Test CHASE_PARENT */ - assert_se(chase_symlinks_at(tfd, "chase/parent", CHASE_NONEXISTENT|CHASE_PARENT, &result, NULL) >= 0); - assert_se(streq(result, "chase")); + assert_se((fd = open_mkdir_at(tfd, "chase", O_CLOEXEC, 0755)) >= 0); + assert_se(symlinkat("/def", fd, "parent") >= 0); + fd = safe_close(fd); + + /* Make sure that when we chase a symlink parent directory, that we chase the parent directory of the + * symlink target and not the symlink itself. But if we add CHASE_NOFOLLOW, we get the parent + * directory of the symlink itself. */ + + assert_se(chase_symlinks_at(tfd, "chase/parent", CHASE_PARENT|CHASE_AT_RESOLVE_IN_ROOT, &result, &fd) >= 0); + assert_se(faccessat(fd, "def", F_OK, 0) >= 0); + assert_se(streq(result, "def")); + fd = safe_close(fd); result = mfree(result); - assert_se(chase_symlinks_at(tfd, "chase", CHASE_NONEXISTENT|CHASE_PARENT, &result, NULL) >= 0); + assert_se(chase_symlinks_at(tfd, "chase/parent", CHASE_AT_RESOLVE_IN_ROOT|CHASE_PARENT|CHASE_NOFOLLOW, &result, &fd) >= 0); + assert_se(faccessat(fd, "parent", F_OK, AT_SYMLINK_NOFOLLOW) >= 0); + assert_se(streq(result, "chase/parent")); + fd = safe_close(fd); + result = mfree(result); + + assert_se(chase_symlinks_at(tfd, "chase", CHASE_PARENT|CHASE_AT_RESOLVE_IN_ROOT, &result, &fd) >= 0); + assert_se(faccessat(fd, "chase", F_OK, 0) >= 0); + assert_se(streq(result, "chase")); + fd = safe_close(fd); + result = mfree(result); + + assert_se(chase_symlinks_at(tfd, "/", CHASE_PARENT|CHASE_AT_RESOLVE_IN_ROOT, &result, NULL) >= 0); + assert_se(streq(result, ".")); + result = mfree(result); + + assert_se(chase_symlinks_at(tfd, ".", CHASE_PARENT|CHASE_AT_RESOLVE_IN_ROOT, &result, NULL) >= 0); assert_se(streq(result, ".")); result = mfree(result); /* Test CHASE_MKDIR_0755 */ - assert_se(chase_symlinks_at(tfd, "m/k/d/i/r", CHASE_MKDIR_0755, &result, NULL) >= 0); - assert_se(faccessat(tfd, "m/k/d/i/r", F_OK, 0) >= 0); + assert_se(chase_symlinks_at(tfd, "m/k/d/i/r", CHASE_MKDIR_0755|CHASE_NONEXISTENT, &result, NULL) >= 0); + assert_se(faccessat(tfd, "m/k/d/i", F_OK, 0) >= 0); + assert_se(RET_NERRNO(faccessat(tfd, "m/k/d/i/r", F_OK, 0)) == -ENOENT); assert_se(streq(result, "m/k/d/i/r")); result = mfree(result); - assert_se(chase_symlinks_at(tfd, "m/../q", CHASE_MKDIR_0755, &result, NULL) >= 0); - assert_se(faccessat(tfd, "q", F_OK, 0) >= 0); + assert_se(chase_symlinks_at(tfd, "m/../q", CHASE_MKDIR_0755|CHASE_NONEXISTENT, &result, NULL) >= 0); + assert_se(faccessat(tfd, "m", F_OK, 0) >= 0); + assert_se(RET_NERRNO(faccessat(tfd, "q", F_OK, 0)) == -ENOENT); assert_se(streq(result, "q")); result = mfree(result); @@ -503,8 +519,14 @@ TEST(chase_symlinks_at) { /* Test chase_symlinks_at_and_open() */ - fd = chase_symlinks_at_and_open(tfd, "o/p/e/n", CHASE_MKDIR_0755, O_CLOEXEC, NULL); + fd = chase_symlinks_at_and_open(tfd, "o/p/e/n/f/i/l/e", CHASE_MKDIR_0755, O_CREAT|O_EXCL|O_CLOEXEC, NULL); assert_se(fd >= 0); + assert_se(fd_verify_regular(fd) >= 0); + fd = safe_close(fd); + + fd = chase_symlinks_at_and_open(tfd, "o/p/e/n/d/i/r", CHASE_MKDIR_0755, O_DIRECTORY|O_CREAT|O_EXCL|O_CLOEXEC, NULL); + assert_se(fd >= 0); + assert_se(fd_verify_directory(fd) >= 0); fd = safe_close(fd); }