1
0
mirror of https://github.com/systemd/systemd.git synced 2025-01-28 21:47:38 +03:00

fs-util: make chase_symlinks() use path_find_first_component()

The previous commit about path_compare() breaks chase_symlinks().
This commit fixes it.
This commit is contained in:
Yu Watanabe 2021-05-03 01:24:11 +09:00
parent 353df4438e
commit 39d7af99c2
2 changed files with 59 additions and 112 deletions

View File

@ -775,9 +775,9 @@ int chase_symlinks(const char *path, const char *original_root, unsigned flags,
_cleanup_free_ char *buffer = NULL, *done = NULL, *root = NULL;
_cleanup_close_ int fd = -1;
unsigned max_follow = CHASE_SYMLINKS_MAX; /* how many symlinks to follow before giving up and returning ELOOP */
bool exists = true, append_trail_slash = false;
struct stat previous_stat;
bool exists = true;
char *todo;
const char *todo;
int r;
assert(path);
@ -885,84 +885,51 @@ int chase_symlinks(const char *path, const char *original_root, unsigned flags,
if (fd < 0)
return -errno;
if (flags & CHASE_SAFE) {
if (flags & CHASE_SAFE)
if (fstat(fd, &previous_stat) < 0)
return -errno;
}
if (flags & CHASE_TRAIL_SLASH)
append_trail_slash = endswith(buffer, "/") || endswith(buffer, "/.");
if (root) {
_cleanup_free_ char *absolute = NULL;
const char *e;
/* If we are operating on a root directory, let's take the root directory as it is. */
e = path_startswith(buffer, root);
if (!e)
todo = path_startswith(buffer, root);
if (!todo)
return log_full_errno(flags & CHASE_WARN ? LOG_WARNING : LOG_DEBUG,
SYNTHETIC_ERRNO(ECHRNG),
"Specified path '%s' is outside of specified root directory '%s', refusing to resolve.",
path, root);
done = strdup(root);
if (!done)
return -ENOMEM;
/* Make sure "todo" starts with a slash */
absolute = strjoin("/", e);
if (!absolute)
return -ENOMEM;
free_and_replace(buffer, absolute);
} else {
todo = buffer;
done = strdup("/");
}
todo = buffer;
for (;;) {
_cleanup_free_ char *first = NULL;
_cleanup_close_ int child = -1;
struct stat st;
size_t n, m;
const char *e;
/* Determine length of first component in the path */
n = strspn(todo, "/"); /* The slashes */
if (n > 1) {
/* If we are looking at more than a single slash then skip all but one, so that when
* we are done with everything we have a normalized path with only single slashes
* separating the path components. */
todo += n - 1;
n = 1;
r = path_find_first_component(&todo, true, &e);
if (r < 0)
return r;
if (r == 0) { /* We reached the end. */
if (append_trail_slash)
if (!strextend(&done, "/"))
return -ENOMEM;
break;
}
m = n + strcspn(todo + n, "/"); /* The entire length of the component */
/* Extract the first component. */
first = strndup(todo, m);
first = strndup(e, r);
if (!first)
return -ENOMEM;
todo += m;
/* Empty? Then we reached the end. */
if (isempty(first))
break;
/* Just a single slash? Then we reached the end. */
if (path_equal(first, "/")) {
/* Preserve the trailing slash */
if (flags & CHASE_TRAIL_SLASH)
if (!strextend(&done, "/"))
return -ENOMEM;
break;
}
/* Just a dot? Then let's eat this up. */
if (path_equal(first, "/."))
continue;
/* Two dots? Then chop off the last bit of what we already found out. */
if (path_equal(first, "/..")) {
if (path_equal(first, "..")) {
_cleanup_free_ char *parent = NULL;
_cleanup_close_ int fd_parent = -1;
@ -1007,22 +974,16 @@ int chase_symlinks(const char *path, const char *original_root, unsigned flags,
}
/* Otherwise let's see what this is. */
child = openat(fd, first + n, O_CLOEXEC|O_NOFOLLOW|O_PATH);
child = openat(fd, first, O_CLOEXEC|O_NOFOLLOW|O_PATH);
if (child < 0) {
if (errno == ENOENT &&
(flags & CHASE_NONEXISTENT) &&
(isempty(todo) || path_is_normalized(todo))) {
(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. */
/* 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 "./"
* or something else weird. */
/* If done is "/", as first also contains slash at the head, then remove this redundant slash. */
if (streq_ptr(done, "/"))
*done = '\0';
if (!strextend(&done, first, todo))
if (!path_extend(&done, first, todo))
return -ENOMEM;
exists = false;
@ -1045,15 +1006,14 @@ int chase_symlinks(const char *path, const char *original_root, unsigned flags,
return log_autofs_mount_point(child, path, flags);
if (S_ISLNK(st.st_mode) && !((flags & CHASE_NOFOLLOW) && isempty(todo))) {
char *joined;
_cleanup_free_ char *destination = NULL;
/* This is a symlink, in this case read the destination. But let's make sure we don't follow
* symlinks without bounds. */
/* This is a symlink, in this case read the destination. But let's make sure we
* don't follow symlinks without bounds. */
if (--max_follow <= 0)
return -ELOOP;
r = readlinkat_malloc(fd, first + n, &destination);
r = readlinkat_malloc(fd, first, &destination);
if (r < 0)
return r;
if (isempty(destination))
@ -1079,27 +1039,19 @@ int chase_symlinks(const char *path, const char *original_root, unsigned flags,
previous_stat = st;
}
free(done);
/* Note that we do not revalidate the root, we take it as is. */
if (isempty(root))
done = NULL;
else {
done = strdup(root);
if (!done)
return -ENOMEM;
}
r = free_and_strdup(&done, empty_to_root(root));
if (r < 0)
return r;
}
/* Prefix what's left to do with what we just read, and start the loop again, but
* remain in the current directory. */
joined = path_join(destination, todo);
} else
joined = path_join("/", destination, todo);
if (!joined)
/* Prefix what's left to do with what we just read, and start the loop again, but
* remain in the current directory. */
if (!path_extend(&destination, todo))
return -ENOMEM;
free(buffer);
todo = buffer = joined;
free_and_replace(buffer, destination);
todo = buffer;
if (flags & CHASE_STEP)
goto chased_one;
@ -1108,29 +1060,14 @@ int chase_symlinks(const char *path, const char *original_root, unsigned flags,
}
/* If this is not a symlink, then let's just add the name we read to what we already verified. */
if (!done)
done = TAKE_PTR(first);
else {
/* If done is "/", as first also contains slash at the head, then remove this redundant slash. */
if (streq(done, "/"))
*done = '\0';
if (!strextend(&done, first))
return -ENOMEM;
}
if (!path_extend(&done, first))
return -ENOMEM;
/* And iterate again, but go one directory further down. */
safe_close(fd);
fd = TAKE_FD(child);
}
if (!done) {
/* Special case, turn the empty string into "/", to indicate the root directory. */
done = strdup("/");
if (!done)
return -ENOMEM;
}
if (ret_path)
*ret_path = TAKE_PTR(done);
@ -1149,13 +1086,23 @@ int chase_symlinks(const char *path, const char *original_root, unsigned flags,
chased_one:
if (ret_path) {
char *c;
const char *e;
c = strjoin(strempty(done), todo);
if (!c)
return -ENOMEM;
/* todo may contain slashes at the beginning. */
r = path_find_first_component(&todo, true, &e);
if (r < 0)
return r;
if (r == 0)
*ret_path = TAKE_PTR(done);
else {
char *c;
*ret_path = c;
c = path_join(done, e);
if (!c)
return -ENOMEM;
*ret_path = c;
}
}
return 0;

View File

@ -332,7 +332,7 @@ static void test_chase_symlinks(void) {
assert_se(S_ISLNK(st.st_mode));
result = mfree(result);
/* Test CHASE_ONE */
/* Test CHASE_STEP */
p = strjoina(temp, "/start");
r = chase_symlinks(p, NULL, CHASE_STEP, &result, NULL);
@ -343,7 +343,7 @@ static void test_chase_symlinks(void) {
r = chase_symlinks(p, NULL, CHASE_STEP, &result, NULL);
assert_se(r == 0);
p = strjoina(temp, "/top/./dotdota");
p = strjoina(temp, "/top/dotdota");
assert_se(streq(p, result));
result = mfree(result);