1
0
mirror of https://github.com/systemd/systemd.git synced 2025-03-09 12:58:26 +03:00

Merge pull request #26502 from DaanDeMeyer/chase-symlinks-additions

Several chase_symlinks() additions
This commit is contained in:
Luca Boccassi 2023-03-06 20:51:06 +00:00 committed by GitHub
commit db73b9d2ab
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 166 additions and 40 deletions

View File

@ -98,9 +98,20 @@ 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
@ -162,12 +173,14 @@ 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|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
* 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;
@ -175,9 +188,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. */
@ -213,7 +228,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;
@ -283,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;
@ -299,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)
@ -378,8 +398,21 @@ int chase_symlinks_at(
close_and_replace(fd, child);
}
if (ret_path)
if (flags & (CHASE_PARENT|CHASE_MKDIR_0755)) {
r = fd_verify_directory(fd);
if (r < 0)
return r;
}
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
@ -398,6 +431,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)
@ -487,6 +526,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);
}
@ -510,7 +555,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|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)
@ -551,7 +597,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|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)
@ -595,7 +642,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|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)
@ -637,7 +685,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|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)
@ -704,20 +753,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;
@ -735,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;
}

View File

@ -7,19 +7,21 @@
#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. */
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);
@ -37,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);

View File

@ -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,37 @@ 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 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 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) {