From 577efd82f24db83ccdaeb5cdfe8f2aaec6e18fab Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Sun, 8 Jan 2023 18:04:20 +0100 Subject: [PATCH 1/5] chase-symlinks: Skip shortcuts if CHASE_PROHIBIT_SYMLINKS is set --- src/basic/chase-symlinks.c | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/basic/chase-symlinks.c b/src/basic/chase-symlinks.c index 7cbe0b45ae..46e4e7ed7a 100644 --- a/src/basic/chase-symlinks.c +++ b/src/basic/chase-symlinks.c @@ -162,8 +162,10 @@ int chase_symlinks_at( * the mount point is emitted. CHASE_WARN cannot be used in PID 1. */ - if (!(flags & (CHASE_AT_RESOLVE_IN_ROOT|CHASE_NONEXISTENT|CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_STEP)) && - !ret_path && ret_fd) { + if (!(flags & + (CHASE_AT_RESOLVE_IN_ROOT|CHASE_NONEXISTENT|CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_STEP| + CHASE_PROHIBIT_SYMLINKS)) && + !ret_path && ret_fd) { /* Shortcut the ret_fd case if the caller isn't interested in the actual path and has no root * set and doesn't care about any of the other special features we provide either. */ @@ -510,7 +512,8 @@ int chase_symlinks_and_open( if (chase_flags & (CHASE_NONEXISTENT|CHASE_STEP)) return -EINVAL; - if (empty_or_root(root) && !ret_path && (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE)) == 0) { + if (empty_or_root(root) && !ret_path && + (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS)) == 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) @@ -551,7 +554,8 @@ int chase_symlinks_and_opendir( if (chase_flags & (CHASE_NONEXISTENT|CHASE_STEP)) return -EINVAL; - if (empty_or_root(root) && !ret_path && (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE)) == 0) { + if (empty_or_root(root) && !ret_path && + (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS)) == 0) { /* Shortcut this call if none of the special features of this call are requested */ d = opendir(path); if (!d) @@ -595,7 +599,8 @@ int chase_symlinks_and_stat( if (chase_flags & (CHASE_NONEXISTENT|CHASE_STEP)) return -EINVAL; - if (empty_or_root(root) && !ret_path && (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE)) == 0 && !ret_fd) { + if (empty_or_root(root) && !ret_path && + (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS)) == 0 && !ret_fd) { /* Shortcut this call if none of the special features of this call are requested */ if (fstatat(AT_FDCWD, path, ret_stat, FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? AT_SYMLINK_NOFOLLOW : 0) < 0) @@ -637,7 +642,8 @@ int chase_symlinks_and_access( if (chase_flags & (CHASE_NONEXISTENT|CHASE_STEP)) return -EINVAL; - if (empty_or_root(root) && !ret_path && (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE)) == 0 && !ret_fd) { + if (empty_or_root(root) && !ret_path && + (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS)) == 0 && !ret_fd) { /* Shortcut this call if none of the special features of this call are requested */ if (faccessat(AT_FDCWD, path, access_mode, FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? AT_SYMLINK_NOFOLLOW : 0) < 0) From 7bf4057d795c84392fd90520460484d2560bce09 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Thu, 23 Feb 2023 14:24:56 +0100 Subject: [PATCH 2/5] chase-symlinks: Return "." as path from chase_symlinks_at() instead of NULL --- src/basic/chase-symlinks.c | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/src/basic/chase-symlinks.c b/src/basic/chase-symlinks.c index 46e4e7ed7a..bab5a5b286 100644 --- a/src/basic/chase-symlinks.c +++ b/src/basic/chase-symlinks.c @@ -215,7 +215,7 @@ int chase_symlinks_at( return -errno; if (flags & CHASE_TRAIL_SLASH) - append_trail_slash = endswith(buffer, "/") || endswith(buffer, "/."); + append_trail_slash = ENDSWITH_SET(buffer, "/", "/."); for (todo = buffer;;) { _cleanup_free_ char *first = NULL; @@ -380,8 +380,15 @@ int chase_symlinks_at( close_and_replace(fd, child); } - if (ret_path) + if (ret_path) { + if (!done) { + done = strdup(append_trail_slash ? "./" : "."); + if (!done) + return -ENOMEM; + } + *ret_path = TAKE_PTR(done); + } if (ret_fd) { /* Return the O_PATH fd we currently are looking to the caller. It can translate it to a @@ -400,6 +407,12 @@ chased_one: if (ret_path) { const char *e; + if (!done) { + done = strdup(append_trail_slash ? "./" : "."); + if (!done) + return -ENOMEM; + } + /* todo may contain slashes at the beginning. */ r = path_find_first_component(&todo, /* accept_dot_dot= */ true, &e); if (r < 0) @@ -489,6 +502,12 @@ int chase_symlinks( if (!q) return -ENOMEM; + path_simplify(q); + + if (FLAGS_SET(flags, CHASE_TRAIL_SLASH) && ENDSWITH_SET(path, "/", "/.")) + if (!strextend(&q, "/")) + return -ENOMEM; + *ret_path = TAKE_PTR(q); } From 28aa650eb71d8381c25be805f93a2b3999cae633 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Fri, 23 Dec 2022 12:08:19 +0100 Subject: [PATCH 3/5] chase-symlinks: Add CHASE_PARENT Let's simplify chasing the parent directory of some path by adding CHASE_PARENT. --- src/basic/chase-symlinks.c | 39 +++++++++++++++++++++++++------------- src/basic/chase-symlinks.h | 27 +++++++++++++------------- src/test/test-fs-util.c | 24 ++++++++++++++++++++++- 3 files changed, 63 insertions(+), 27 deletions(-) diff --git a/src/basic/chase-symlinks.c b/src/basic/chase-symlinks.c index bab5a5b286..cc0e263852 100644 --- a/src/basic/chase-symlinks.c +++ b/src/basic/chase-symlinks.c @@ -101,6 +101,14 @@ int chase_symlinks_at( 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 @@ -169,7 +177,7 @@ int chase_symlinks_at( /* Shortcut the ret_fd case if the caller isn't interested in the actual path and has no root * set and doesn't care about any of the other special features we provide either. */ - r = openat(dir_fd, path, O_PATH|O_CLOEXEC|((flags & CHASE_NOFOLLOW) ? O_NOFOLLOW : 0)); + r = openat(dir_fd, buffer ?: path, O_PATH|O_CLOEXEC|((flags & CHASE_NOFOLLOW) ? O_NOFOLLOW : 0)); if (r < 0) return -errno; @@ -177,9 +185,11 @@ int chase_symlinks_at( return 0; } - buffer = strdup(path); - if (!buffer) - return -ENOMEM; + if (!buffer) { + buffer = strdup(path); + if (!buffer) + return -ENOMEM; + } /* If we receive an absolute path together with AT_FDCWD, we need to return an absolute path, because * a relative path would be interpreted relative to the current working directory. */ @@ -380,6 +390,12 @@ int chase_symlinks_at( close_and_replace(fd, child); } + if (flags & CHASE_PARENT) { + r = fd_verify_directory(fd); + if (r < 0) + return r; + } + if (ret_path) { if (!done) { done = strdup(append_trail_slash ? "./" : "."); @@ -532,7 +548,7 @@ int chase_symlinks_and_open( return -EINVAL; if (empty_or_root(root) && !ret_path && - (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS)) == 0) { + (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS|CHASE_PARENT)) == 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) @@ -574,7 +590,7 @@ int chase_symlinks_and_opendir( return -EINVAL; if (empty_or_root(root) && !ret_path && - (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS)) == 0) { + (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS|CHASE_PARENT)) == 0) { /* Shortcut this call if none of the special features of this call are requested */ d = opendir(path); if (!d) @@ -619,7 +635,7 @@ int chase_symlinks_and_stat( return -EINVAL; if (empty_or_root(root) && !ret_path && - (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS)) == 0 && !ret_fd) { + (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS|CHASE_PARENT)) == 0 && !ret_fd) { /* Shortcut this call if none of the special features of this call are requested */ if (fstatat(AT_FDCWD, path, ret_stat, FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? AT_SYMLINK_NOFOLLOW : 0) < 0) @@ -662,7 +678,7 @@ int chase_symlinks_and_access( return -EINVAL; if (empty_or_root(root) && !ret_path && - (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS)) == 0 && !ret_fd) { + (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS|CHASE_PARENT)) == 0 && !ret_fd) { /* Shortcut this call if none of the special features of this call are requested */ if (faccessat(AT_FDCWD, path, access_mode, FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? AT_SYMLINK_NOFOLLOW : 0) < 0) @@ -729,20 +745,17 @@ int chase_symlinks_and_unlink( int unlink_flags, char **ret_path) { - _cleanup_free_ char *p = NULL, *rp = NULL, *dir = NULL, *fname = NULL; + _cleanup_free_ char *p = NULL, *rp = NULL, *fname = NULL; _cleanup_close_ int fd = -EBADF; int r; assert(path); - r = path_extract_directory(path, &dir); - if (r < 0) - return r; r = path_extract_filename(path, &fname); if (r < 0) return r; - fd = chase_symlinks_and_open(dir, root, chase_flags, O_PATH|O_DIRECTORY|O_CLOEXEC, ret_path ? &p : NULL); + fd = chase_symlinks_and_open(path, root, chase_flags|CHASE_PARENT, O_PATH|O_DIRECTORY|O_CLOEXEC, ret_path ? &p : NULL); if (fd < 0) return fd; diff --git a/src/basic/chase-symlinks.h b/src/basic/chase-symlinks.h index be5e2bb696..6aea8d6781 100644 --- a/src/basic/chase-symlinks.h +++ b/src/basic/chase-symlinks.h @@ -7,19 +7,20 @@ #include "stat-util.h" typedef enum ChaseSymlinksFlags { - CHASE_PREFIX_ROOT = 1 << 0, /* The specified path will be prefixed by the specified root before beginning the iteration */ - CHASE_NONEXISTENT = 1 << 1, /* It's OK if the path doesn't actually exist. */ - CHASE_NO_AUTOFS = 1 << 2, /* Return -EREMOTE if autofs mount point found */ - CHASE_SAFE = 1 << 3, /* Return -EPERM if we ever traverse from unprivileged to privileged files or directories */ - CHASE_TRAIL_SLASH = 1 << 4, /* Any trailing slash will be preserved */ - CHASE_STEP = 1 << 5, /* Just execute a single step of the normalization */ - CHASE_NOFOLLOW = 1 << 6, /* Do not follow the path's right-most component. With ret_fd, when the path's - * right-most component refers to symlink, return O_PATH fd of the symlink. */ - CHASE_WARN = 1 << 7, /* Emit an appropriate warning when an error is encountered. - * Note: this may do an NSS lookup, hence this flag cannot be used in PID 1. */ - 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_PREFIX_ROOT = 1 << 0, /* The specified path will be prefixed by the specified root before beginning the iteration */ + CHASE_NONEXISTENT = 1 << 1, /* It's OK if the path doesn't actually exist. */ + CHASE_NO_AUTOFS = 1 << 2, /* Return -EREMOTE if autofs mount point found */ + CHASE_SAFE = 1 << 3, /* Return -EPERM if we ever traverse from unprivileged to privileged files or directories */ + CHASE_TRAIL_SLASH = 1 << 4, /* Any trailing slash will be preserved */ + CHASE_STEP = 1 << 5, /* Just execute a single step of the normalization */ + CHASE_NOFOLLOW = 1 << 6, /* Do not follow the path's right-most component. With ret_fd, when the path's + * right-most component refers to symlink, return O_PATH fd of the symlink. */ + CHASE_WARN = 1 << 7, /* Emit an appropriate warning when an error is encountered. + * Note: this may do an NSS lookup, hence this flag cannot be used in PID 1. */ + 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. */ } 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 3fc5f88d26..97b6bdabc8 100644 --- a/src/test/test-fs-util.c +++ b/src/test/test-fs-util.c @@ -426,6 +426,18 @@ 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); } @@ -463,7 +475,17 @@ TEST(chase_symlinks_at) { /* Valid directory file descriptor without CHASE_AT_RESOLVE_IN_ROOT should resolve symlinks against * host's root. */ - assert_se(chase_symlinks_at(tfd, "/qed", 0, &result, NULL) == -ENOENT); + assert_se(chase_symlinks_at(tfd, "/qed", 0, NULL, NULL) == -ENOENT); + + /* Test CHASE_PARENT */ + + assert_se(chase_symlinks_at(tfd, "chase/parent", CHASE_NONEXISTENT|CHASE_PARENT, &result, NULL) >= 0); + assert_se(streq(result, "chase")); + result = mfree(result); + + assert_se(chase_symlinks_at(tfd, "chase", CHASE_NONEXISTENT|CHASE_PARENT, &result, NULL) >= 0); + assert_se(streq(result, ".")); + result = mfree(result); } TEST(unlink_noerrno) { From e864dfa6717fd63d290b867f44bd1c6d661a9047 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Mon, 9 Jan 2023 12:29:12 +0100 Subject: [PATCH 4/5] chase-symlinks: Add CHASE_MKDIR_0755 --- src/basic/chase-symlinks.c | 38 +++++++++++++++++++++++--------------- src/basic/chase-symlinks.h | 1 + src/test/test-fs-util.c | 14 ++++++++++++++ 3 files changed, 38 insertions(+), 15 deletions(-) diff --git a/src/basic/chase-symlinks.c b/src/basic/chase-symlinks.c index cc0e263852..38fb582c88 100644 --- a/src/basic/chase-symlinks.c +++ b/src/basic/chase-symlinks.c @@ -98,6 +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 = "."; @@ -172,7 +175,7 @@ int chase_symlinks_at( if (!(flags & (CHASE_AT_RESOLVE_IN_ROOT|CHASE_NONEXISTENT|CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_STEP| - CHASE_PROHIBIT_SYMLINKS)) && + CHASE_PROHIBIT_SYMLINKS|CHASE_MKDIR_0755)) && !ret_path && ret_fd) { /* Shortcut the ret_fd case if the caller isn't interested in the actual path and has no root @@ -295,15 +298,15 @@ int chase_symlinks_at( } /* Otherwise let's see what this is. */ - child = openat(fd, first, O_CLOEXEC|O_NOFOLLOW|O_PATH); - if (child < 0) { - if (errno == ENOENT && - (flags & CHASE_NONEXISTENT) && - (isempty(todo) || path_is_safe(todo))) { - /* If CHASE_NONEXISTENT is set, and the path does not exist, then - * that's OK, return what we got so far. But don't allow this if the - * remaining path contains "../" or something else weird. */ + child = r = RET_NERRNO(openat(fd, first, O_CLOEXEC|O_NOFOLLOW|O_PATH)); + if (r < 0) { + if (r != -ENOENT) + return r; + if (!isempty(todo) && !path_is_safe(todo)) + return r; + + if (flags & CHASE_NONEXISTENT) { if (!path_extend(&done, first, todo)) return -ENOMEM; @@ -311,7 +314,12 @@ int chase_symlinks_at( break; } - return -errno; + if (!(flags & CHASE_MKDIR_0755)) + 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) @@ -390,7 +398,7 @@ int chase_symlinks_at( close_and_replace(fd, child); } - if (flags & CHASE_PARENT) { + if (flags & (CHASE_PARENT|CHASE_MKDIR_0755)) { r = fd_verify_directory(fd); if (r < 0) return r; @@ -548,7 +556,7 @@ int chase_symlinks_and_open( return -EINVAL; if (empty_or_root(root) && !ret_path && - (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS|CHASE_PARENT)) == 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) @@ -590,7 +598,7 @@ int chase_symlinks_and_opendir( return -EINVAL; if (empty_or_root(root) && !ret_path && - (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS|CHASE_PARENT)) == 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 */ d = opendir(path); if (!d) @@ -635,7 +643,7 @@ int chase_symlinks_and_stat( return -EINVAL; if (empty_or_root(root) && !ret_path && - (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS|CHASE_PARENT)) == 0 && !ret_fd) { + (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS|CHASE_PARENT|CHASE_MKDIR_0755)) == 0 && !ret_fd) { /* Shortcut this call if none of the special features of this call are requested */ if (fstatat(AT_FDCWD, path, ret_stat, FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? AT_SYMLINK_NOFOLLOW : 0) < 0) @@ -678,7 +686,7 @@ int chase_symlinks_and_access( return -EINVAL; if (empty_or_root(root) && !ret_path && - (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS|CHASE_PARENT)) == 0 && !ret_fd) { + (chase_flags & (CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS|CHASE_PARENT|CHASE_MKDIR_0755)) == 0 && !ret_fd) { /* Shortcut this call if none of the special features of this call are requested */ if (faccessat(AT_FDCWD, path, access_mode, FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? AT_SYMLINK_NOFOLLOW : 0) < 0) diff --git a/src/basic/chase-symlinks.h b/src/basic/chase-symlinks.h index 6aea8d6781..7308227c71 100644 --- a/src/basic/chase-symlinks.h +++ b/src/basic/chase-symlinks.h @@ -21,6 +21,7 @@ typedef enum ChaseSymlinksFlags { * 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. */ } 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 97b6bdabc8..ef7f41885a 100644 --- a/src/test/test-fs-util.c +++ b/src/test/test-fs-util.c @@ -486,6 +486,20 @@ TEST(chase_symlinks_at) { assert_se(chase_symlinks_at(tfd, "chase", CHASE_NONEXISTENT|CHASE_PARENT, &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(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(streq(result, "q")); + result = mfree(result); + + assert_se(chase_symlinks_at(tfd, "i/../p", CHASE_MKDIR_0755, NULL, NULL) == -ENOENT); } TEST(unlink_noerrno) { From 9a98c0f2b12db053a2338117dc3343f16ef6c26b Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Sun, 8 Jan 2023 18:24:38 +0100 Subject: [PATCH 5/5] chase-symlinks: Add chase_symlinks_at_and_open() --- src/basic/chase-symlinks.c | 35 +++++++++++++++++++++++++++++++++++ src/basic/chase-symlinks.h | 1 + src/test/test-fs-util.c | 6 ++++++ 3 files changed, 42 insertions(+) diff --git a/src/basic/chase-symlinks.c b/src/basic/chase-symlinks.c index 38fb582c88..8a769e08e8 100644 --- a/src/basic/chase-symlinks.c +++ b/src/basic/chase-symlinks.c @@ -781,3 +781,38 @@ int chase_symlinks_and_unlink( return 0; } + +int chase_symlinks_at_and_open( + int dir_fd, + const char *path, + ChaseSymlinksFlags chase_flags, + int open_flags, + char **ret_path) { + + _cleanup_close_ int path_fd = -EBADF; + _cleanup_free_ char *p = NULL; + int r; + + if (chase_flags & (CHASE_NONEXISTENT|CHASE_STEP)) + return -EINVAL; + + 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))); + + r = chase_symlinks_at(dir_fd, path, chase_flags, ret_path ? &p : NULL, &path_fd); + if (r < 0) + return r; + assert(path_fd >= 0); + + r = fd_reopen(path_fd, open_flags); + if (r < 0) + return r; + + if (ret_path) + *ret_path = TAKE_PTR(p); + + return r; +} + diff --git a/src/basic/chase-symlinks.h b/src/basic/chase-symlinks.h index 7308227c71..19acf9e4b5 100644 --- a/src/basic/chase-symlinks.h +++ b/src/basic/chase-symlinks.h @@ -39,3 +39,4 @@ int chase_symlinks_and_fopen_unlocked(const char *path, const char *root, ChaseS int chase_symlinks_and_unlink(const char *path, const char *root, ChaseSymlinksFlags chase_flags, int unlink_flags, char **ret_path); int chase_symlinks_at(int dir_fd, const char *path, ChaseSymlinksFlags flags, char **ret_path, int *ret_fd); +int chase_symlinks_at_and_open(int dir_fd, const char *path, ChaseSymlinksFlags chase_flags, int open_flags, char **ret_path); diff --git a/src/test/test-fs-util.c b/src/test/test-fs-util.c index ef7f41885a..c009424a2b 100644 --- a/src/test/test-fs-util.c +++ b/src/test/test-fs-util.c @@ -500,6 +500,12 @@ TEST(chase_symlinks_at) { result = mfree(result); assert_se(chase_symlinks_at(tfd, "i/../p", CHASE_MKDIR_0755, NULL, NULL) == -ENOENT); + + /* Test chase_symlinks_at_and_open() */ + + fd = chase_symlinks_at_and_open(tfd, "o/p/e/n", CHASE_MKDIR_0755, O_CLOEXEC, NULL); + assert_se(fd >= 0); + fd = safe_close(fd); } TEST(unlink_noerrno) {