]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
fs-util: make chase_symlinks() use path_find_first_component()
authorYu Watanabe <watanabe.yu+github@gmail.com>
Sun, 2 May 2021 16:24:11 +0000 (01:24 +0900)
committerYu Watanabe <watanabe.yu+github@gmail.com>
Fri, 28 May 2021 04:44:19 +0000 (13:44 +0900)
The previous commit about path_compare() breaks chase_symlinks().
This commit fixes it.

src/basic/fs-util.c
src/test/test-fs-util.c

index a4d772777e1d0fa990d5b797cdd9a528434aa3d6..e9fdb75a232d92ef2f640fa2d10c18d923a47efe 100644 (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 (root) {
-                _cleanup_free_ char *absolute = NULL;
-                const char *e;
+        if (flags & CHASE_TRAIL_SLASH)
+                append_trail_slash = endswith(buffer, "/") || endswith(buffer, "/.");
 
+        if (root) {
                 /* 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;
-
-                /* 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;
-                }
-
-                m = n + strcspn(todo + n, "/");         /* The entire length of the component */
-
-                /* Extract the first component. */
-                first = strndup(todo, m);
-                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 */
+                const char *e;
 
-                        if (flags & CHASE_TRAIL_SLASH)
+                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;
                 }
 
-                /* Just a dot? Then let's eat this up. */
-                if (path_equal(first, "/."))
-                        continue;
+                first = strndup(e, r);
+                if (!first)
+                        return -ENOMEM;
 
                 /* 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))) {
-
-                                /* 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. */
+                            (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 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;
index c42c48722f9fe6b214c767532d2905b6888c0abd..63bb8303704f6e95948ed9adbe92bd896a5b3a10 100644 (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);