Merge pull request #28474 from yuwata/chase-fix

chase: handle ".." more carefully
This commit is contained in:
Daan De Meyer
2023-07-31 10:46:19 +02:00
committed by GitHub
3 changed files with 101 additions and 22 deletions
+93 -22
View File
@@ -72,6 +72,13 @@ static int log_prohibited_symlink(int fd, ChaseFlags flags) {
strna(n1));
}
static int chaseat_needs_absolute(int dir_fd, const char *path) {
if (dir_fd < 0)
return path_is_absolute(path);
return dir_fd_is_root(dir_fd);
}
int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int *ret_fd) {
_cleanup_free_ char *buffer = NULL, *done = NULL;
_cleanup_close_ int fd = -EBADF, root_fd = -EBADF;
@@ -205,13 +212,28 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int
/* 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. Also, let's make
* the result absolute when the file descriptor of the root directory is specified. */
bool need_absolute = (dir_fd == AT_FDCWD && path_is_absolute(path)) || (dir_fd >= 0 && dir_fd_is_root(dir_fd) > 0);
r = chaseat_needs_absolute(dir_fd, path);
if (r < 0)
return r;
bool need_absolute = r;
if (need_absolute) {
done = strdup("/");
if (!done)
return -ENOMEM;
}
/* If a positive directory file descriptor is provided, always resolve the given path relative to it,
* regardless of whether it is absolute or not. If we get AT_FDCWD, follow regular openat()
* semantics, if the path is relative, resolve against the current working directory. Otherwise,
* resolve against root. */
fd = openat(dir_fd, done ?: ".", O_CLOEXEC|O_DIRECTORY|O_PATH);
if (fd < 0)
return -errno;
if (fstat(fd, &st) < 0)
return -errno;
/* If we get AT_FDCWD, we always resolve symlinks relative to the host's root. Only if a positive
* directory file descriptor is provided we will look at CHASE_AT_RESOLVE_IN_ROOT to determine
* whether to resolve symlinks in it or not. */
@@ -222,20 +244,6 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int
if (root_fd < 0)
return -errno;
/* If a positive directory file descriptor is provided, always resolve the given path relative to it,
* regardless of whether it is absolute or not. If we get AT_FDCWD, follow regular openat()
* semantics, if the path is relative, resolve against the current working directory. Otherwise,
* resolve against root. */
if (dir_fd >= 0 || !path_is_absolute(path))
fd = openat(dir_fd, ".", O_CLOEXEC|O_DIRECTORY|O_PATH);
else
fd = open("/", O_CLOEXEC|O_DIRECTORY|O_PATH);
if (fd < 0)
return -errno;
if (fstat(fd, &st) < 0)
return -errno;
if (FLAGS_SET(flags, CHASE_TRAIL_SLASH))
append_trail_slash = ENDSWITH_SET(buffer, "/", "/.");
@@ -267,8 +275,11 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int
/* If we already are at the top, then going up will not change anything. This is
* in-line with how the kernel handles this. */
if (empty_or_root(done) && FLAGS_SET(flags, CHASE_AT_RESOLVE_IN_ROOT))
if (empty_or_root(done) && FLAGS_SET(flags, CHASE_AT_RESOLVE_IN_ROOT)) {
if (FLAGS_SET(flags, CHASE_STEP))
goto chased_one;
continue;
}
fd_parent = openat(fd, "..", O_CLOEXEC|O_NOFOLLOW|O_PATH|O_DIRECTORY);
if (fd_parent < 0)
@@ -284,14 +295,33 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int
r = dir_fd_is_root(fd);
if (r < 0)
return r;
if (r > 0)
if (r > 0) {
if (FLAGS_SET(flags, CHASE_STEP))
goto chased_one;
continue;
}
}
r = path_extract_directory(done, &parent);
if (r >= 0 || r == -EDESTADDRREQ)
if (r >= 0) {
assert(!need_absolute || path_is_absolute(parent));
free_and_replace(done, parent);
else if (IN_SET(r, -EINVAL, -EADDRNOTAVAIL)) {
} else if (r == -EDESTADDRREQ) {
/* 'done' contains filename only (i.e. no slash). */
assert(!need_absolute);
done = mfree(done);
} else if (r == -EADDRNOTAVAIL) {
/* 'done' is "/". This branch should be already handled in the above. */
assert(!FLAGS_SET(flags, CHASE_AT_RESOLVE_IN_ROOT));
assert_not_reached();
} else if (r == -EINVAL) {
/* 'done' is an empty string, ends with '..', or an invalid path. */
assert(!need_absolute);
assert(!FLAGS_SET(flags, CHASE_AT_RESOLVE_IN_ROOT));
if (!path_is_valid(done))
return -EINVAL;
/* If we're at the top of "dir_fd", start appending ".." to "done". */
if (!path_extend(&done, ".."))
return -ENOMEM;
@@ -446,6 +476,7 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int
}
if (!done) {
assert(!need_absolute || FLAGS_SET(flags, CHASE_EXTRACT_FILENAME));
done = strdup(append_trail_slash ? "./" : ".");
if (!done)
return -ENOMEM;
@@ -472,6 +503,7 @@ chased_one:
const char *e;
if (!done) {
assert(!need_absolute);
done = strdup(append_trail_slash ? "./" : ".");
if (!done)
return -ENOMEM;
@@ -497,6 +529,27 @@ chased_one:
return 0;
}
static int empty_or_root_to_null(const char **path) {
int r;
assert(path);
/* This nullifies the input path when the path is empty or points to "/". */
if (empty_or_root(*path)) {
*path = NULL;
return 0;
}
r = path_is_root(*path);
if (r < 0)
return r;
if (r > 0)
*path = NULL;
return 0;
}
int chase(const char *path, const char *root, ChaseFlags flags, char **ret_path, int *ret_fd) {
_cleanup_free_ char *root_abs = NULL, *absolute = NULL, *p = NULL;
_cleanup_close_ int fd = -EBADF, pfd = -EBADF;
@@ -507,10 +560,19 @@ int chase(const char *path, const char *root, ChaseFlags flags, char **ret_path,
if (isempty(path))
return -EINVAL;
r = empty_or_root_to_null(&root);
if (r < 0)
return r;
/* A root directory of "/" or "" is identical to "/". */
if (empty_or_root(root))
if (empty_or_root(root)) {
root = "/";
else {
/* When the root directory is "/", we will drop CHASE_AT_RESOLVE_IN_ROOT in chaseat(),
* hence below is not necessary, but let's shortcut. */
flags &= ~CHASE_AT_RESOLVE_IN_ROOT;
} else {
r = path_make_absolute_cwd(root, &root_abs);
if (r < 0)
return r;
@@ -598,8 +660,13 @@ int chaseat_prefix_root(const char *path, const char *root, char **ret) {
if (!path_is_absolute(path)) {
_cleanup_free_ char *root_abs = NULL;
r = empty_or_root_to_null(&root);
if (r < 0 && r != -ENOENT)
return r;
/* If the dir_fd points to the root directory, chaseat() always returns an absolute path. */
assert(!empty_or_root(root));
if (empty_or_root(root))
return -EINVAL;
r = path_make_absolute_cwd(root, &root_abs);
if (r < 0)
@@ -632,6 +699,10 @@ int chase_extract_filename(const char *path, const char *root, char **ret) {
if (!path_is_absolute(path))
return -EINVAL;
r = empty_or_root_to_null(&root);
if (r < 0 && r != -ENOENT)
return r;
if (!empty_or_root(root)) {
_cleanup_free_ char *root_abs = NULL;
+3
View File
@@ -102,6 +102,9 @@ int read_nr_open(void);
int fd_get_diskseq(int fd, uint64_t *ret);
int path_is_root_at(int dir_fd, const char *path);
static inline int path_is_root(const char *path) {
return path_is_root_at(AT_FDCWD, path);
}
static inline int dir_fd_is_root(int dir_fd) {
return path_is_root_at(dir_fd, NULL);
}
+5
View File
@@ -73,6 +73,11 @@ TEST(chase) {
assert_se(path_equal(result, "/usr"));
result = mfree(result);
r = chase(p, "/.//../../../", 0, &result, NULL);
assert_se(r > 0);
assert_se(path_equal(result, "/usr"));
result = mfree(result);
pslash = strjoina(p, "/");
r = chase(pslash, NULL, 0, &result, NULL);
assert_se(r > 0);