valeriyvdovin / rpms / systemd

Forked from rpms/systemd 4 years ago
Clone

Blame SOURCES/0722-backport-chase_symlinks.patch

Pablo Greco 48fc63
From a221a65ad0563d1bfe8770e928b221efc6ba8c88 Mon Sep 17 00:00:00 2001
Pablo Greco 48fc63
From: David Tardon <dtardon@redhat.com>
Pablo Greco 48fc63
Date: Thu, 3 Jan 2019 13:09:43 +0100
Pablo Greco 48fc63
Subject: [PATCH] backport chase_symlinks
Pablo Greco 48fc63
Pablo Greco 48fc63
Related: #1663143
Pablo Greco 48fc63
---
Pablo Greco 48fc63
 src/shared/util.c    | 233 +++++++++++++++++++++++++++++++++++++++++++
Pablo Greco 48fc63
 src/shared/util.h    |   8 ++
Pablo Greco 48fc63
 src/test/test-util.c | 208 ++++++++++++++++++++++++++++++++++++++
Pablo Greco 48fc63
 3 files changed, 449 insertions(+)
Pablo Greco 48fc63
Pablo Greco 48fc63
diff --git a/src/shared/util.c b/src/shared/util.c
Pablo Greco 48fc63
index 2838d50f6f..385551f2b3 100644
Pablo Greco 48fc63
--- a/src/shared/util.c
Pablo Greco 48fc63
+++ b/src/shared/util.c
Pablo Greco 48fc63
@@ -9202,3 +9202,236 @@ int fd_is_fs_type(int fd, statfs_f_type_t magic_value) {
Pablo Greco 48fc63
 
Pablo Greco 48fc63
         return is_fs_type(&s, magic_value);
Pablo Greco 48fc63
 }
Pablo Greco 48fc63
+
Pablo Greco 48fc63
+int chase_symlinks(const char *path, const char *original_root, unsigned flags, char **ret) {
Pablo Greco 48fc63
+        _cleanup_free_ char *buffer = NULL, *done = NULL, *root = NULL;
Pablo Greco 48fc63
+        _cleanup_close_ int fd = -1;
Pablo Greco 48fc63
+        unsigned max_follow = 32; /* how many symlinks to follow before giving up and returning ELOOP */
Pablo Greco 48fc63
+        bool exists = true;
Pablo Greco 48fc63
+        char *todo;
Pablo Greco 48fc63
+        int r;
Pablo Greco 48fc63
+
Pablo Greco 48fc63
+        assert(path);
Pablo Greco 48fc63
+
Pablo Greco 48fc63
+        /* This is a lot like canonicalize_file_name(), but takes an additional "root" parameter, that allows following
Pablo Greco 48fc63
+         * symlinks relative to a root directory, instead of the root of the host.
Pablo Greco 48fc63
+         *
Pablo Greco 48fc63
+         * Note that "root" primarily matters if we encounter an absolute symlink. It is also used when following
Pablo Greco 48fc63
+         * relative symlinks to ensure they cannot be used to "escape" the root directory. The path parameter passed is
Pablo Greco 48fc63
+         * assumed to be already prefixed by it, except if the CHASE_PREFIX_ROOT flag is set, in which case it is first
Pablo Greco 48fc63
+         * prefixed accordingly.
Pablo Greco 48fc63
+         *
Pablo Greco 48fc63
+         * Algorithmically this operates on two path buffers: "done" are the components of the path we already
Pablo Greco 48fc63
+         * processed and resolved symlinks, "." and ".." of. "todo" are the components of the path we still need to
Pablo Greco 48fc63
+         * process. On each iteration, we move one component from "todo" to "done", processing it's special meaning
Pablo Greco 48fc63
+         * each time. The "todo" path always starts with at least one slash, the "done" path always ends in no
Pablo Greco 48fc63
+         * slash. We always keep an O_PATH fd to the component we are currently processing, thus keeping lookup races
Pablo Greco 48fc63
+         * at a minimum.
Pablo Greco 48fc63
+         *
Pablo Greco 48fc63
+         * Suggested usage: whenever you want to canonicalize a path, use this function. Pass the absolute path you got
Pablo Greco 48fc63
+         * as-is: fully qualified and relative to your host's root. Optionally, specify the root parameter to tell this
Pablo Greco 48fc63
+         * function what to do when encountering a symlink with an absolute path as directory: prefix it by the
Pablo Greco 48fc63
+         * specified path. */
Pablo Greco 48fc63
+
Pablo Greco 48fc63
+        if (original_root) {
Pablo Greco 48fc63
+                root = path_make_absolute_cwd(original_root);
Pablo Greco 48fc63
+                if (root == NULL)
Pablo Greco 48fc63
+                        return -ENOENT;
Pablo Greco 48fc63
+
Pablo Greco 48fc63
+                if (flags & CHASE_PREFIX_ROOT)
Pablo Greco 48fc63
+                        path = prefix_roota(root, path);
Pablo Greco 48fc63
+        }
Pablo Greco 48fc63
+
Pablo Greco 48fc63
+        buffer = path_make_absolute_cwd(path);
Pablo Greco 48fc63
+        if (buffer == NULL)
Pablo Greco 48fc63
+                return -ENOENT;
Pablo Greco 48fc63
+
Pablo Greco 48fc63
+        fd = open("/", O_CLOEXEC|O_NOFOLLOW|O_PATH);
Pablo Greco 48fc63
+        if (fd < 0)
Pablo Greco 48fc63
+                return -errno;
Pablo Greco 48fc63
+
Pablo Greco 48fc63
+        todo = buffer;
Pablo Greco 48fc63
+        for (;;) {
Pablo Greco 48fc63
+                _cleanup_free_ char *first = NULL;
Pablo Greco 48fc63
+                _cleanup_close_ int child = -1;
Pablo Greco 48fc63
+                struct stat st;
Pablo Greco 48fc63
+                size_t n, m;
Pablo Greco 48fc63
+
Pablo Greco 48fc63
+                /* Determine length of first component in the path */
Pablo Greco 48fc63
+                n = strspn(todo, "/");                  /* The slashes */
Pablo Greco 48fc63
+                m = n + strcspn(todo + n, "/");         /* The entire length of the component */
Pablo Greco 48fc63
+
Pablo Greco 48fc63
+                /* Extract the first component. */
Pablo Greco 48fc63
+                first = strndup(todo, m);
Pablo Greco 48fc63
+                if (!first)
Pablo Greco 48fc63
+                        return -ENOMEM;
Pablo Greco 48fc63
+
Pablo Greco 48fc63
+                todo += m;
Pablo Greco 48fc63
+
Pablo Greco 48fc63
+                /* Empty? Then we reached the end. */
Pablo Greco 48fc63
+                if (isempty(first))
Pablo Greco 48fc63
+                        break;
Pablo Greco 48fc63
+
Pablo Greco 48fc63
+                /* Just a single slash? Then we reached the end. */
Pablo Greco 48fc63
+                if (path_equal(first, "/")) {
Pablo Greco 48fc63
+                        /* Preserve the trailing slash */
Pablo Greco 48fc63
+                        if (!strextend(&done, "/", NULL))
Pablo Greco 48fc63
+                                return -ENOMEM;
Pablo Greco 48fc63
+
Pablo Greco 48fc63
+                        break;
Pablo Greco 48fc63
+                }
Pablo Greco 48fc63
+
Pablo Greco 48fc63
+                /* Just a dot? Then let's eat this up. */
Pablo Greco 48fc63
+                if (path_equal(first, "/."))
Pablo Greco 48fc63
+                        continue;
Pablo Greco 48fc63
+
Pablo Greco 48fc63
+                /* Two dots? Then chop off the last bit of what we already found out. */
Pablo Greco 48fc63
+                if (path_equal(first, "/..")) {
Pablo Greco 48fc63
+                        _cleanup_free_ char *parent = NULL;
Pablo Greco 48fc63
+                        int fd_parent = -1;
Pablo Greco 48fc63
+
Pablo Greco 48fc63
+                        /* If we already are at the top, then going up will not change anything. This is in-line with
Pablo Greco 48fc63
+                         * how the kernel handles this. */
Pablo Greco 48fc63
+                        if (isempty(done) || path_equal(done, "/"))
Pablo Greco 48fc63
+                                continue;
Pablo Greco 48fc63
+
Pablo Greco 48fc63
+                        parent = dirname_malloc(done);
Pablo Greco 48fc63
+                        if (!parent)
Pablo Greco 48fc63
+                                return -ENOMEM;
Pablo Greco 48fc63
+
Pablo Greco 48fc63
+                        /* Don't allow this to leave the root dir.  */
Pablo Greco 48fc63
+                        if (root &&
Pablo Greco 48fc63
+                            path_startswith(done, root) &&
Pablo Greco 48fc63
+                            !path_startswith(parent, root))
Pablo Greco 48fc63
+                                continue;
Pablo Greco 48fc63
+
Pablo Greco 48fc63
+                        free(done);
Pablo Greco 48fc63
+                        done = parent;
Pablo Greco 48fc63
+                        parent = NULL;
Pablo Greco 48fc63
+
Pablo Greco 48fc63
+                        fd_parent = openat(fd, "..", O_CLOEXEC|O_NOFOLLOW|O_PATH);
Pablo Greco 48fc63
+                        if (fd_parent < 0)
Pablo Greco 48fc63
+                                return -errno;
Pablo Greco 48fc63
+
Pablo Greco 48fc63
+                        safe_close(fd);
Pablo Greco 48fc63
+                        fd = fd_parent;
Pablo Greco 48fc63
+
Pablo Greco 48fc63
+                        continue;
Pablo Greco 48fc63
+                }
Pablo Greco 48fc63
+
Pablo Greco 48fc63
+                /* Otherwise let's see what this is. */
Pablo Greco 48fc63
+                child = openat(fd, first + n, O_CLOEXEC|O_NOFOLLOW|O_PATH);
Pablo Greco 48fc63
+                if (child < 0) {
Pablo Greco 48fc63
+
Pablo Greco 48fc63
+                        if (errno == ENOENT &&
Pablo Greco 48fc63
+                            (flags & CHASE_NONEXISTENT) &&
Pablo Greco 48fc63
+                            (isempty(todo) || path_is_safe(todo))) {
Pablo Greco 48fc63
+
Pablo Greco 48fc63
+                                /* If CHASE_NONEXISTENT is set, and the path does not exist, then that's OK, return
Pablo Greco 48fc63
+                                 * what we got so far. But don't allow this if the remaining path contains "../ or "./"
Pablo Greco 48fc63
+                                 * or something else weird. */
Pablo Greco 48fc63
+
Pablo Greco 48fc63
+                                /* If done is "/", as first also contains slash at the head, then remove this redundant slash. */
Pablo Greco 48fc63
+                                if (streq_ptr(done, "/"))
Pablo Greco 48fc63
+                                        *done = '\0';
Pablo Greco 48fc63
+
Pablo Greco 48fc63
+                                if (!strextend(&done, first, todo, NULL))
Pablo Greco 48fc63
+                                        return -ENOMEM;
Pablo Greco 48fc63
+
Pablo Greco 48fc63
+                                exists = false;
Pablo Greco 48fc63
+                                break;
Pablo Greco 48fc63
+                        }
Pablo Greco 48fc63
+
Pablo Greco 48fc63
+                        return -errno;
Pablo Greco 48fc63
+                }
Pablo Greco 48fc63
+
Pablo Greco 48fc63
+                if (fstat(child, &st) < 0)
Pablo Greco 48fc63
+                        return -errno;
Pablo Greco 48fc63
+                if ((flags & CHASE_NO_AUTOFS) &&
Pablo Greco 48fc63
+                    fd_is_fs_type(child, AUTOFS_SUPER_MAGIC) > 0)
Pablo Greco 48fc63
+                        return -EREMOTE;
Pablo Greco 48fc63
+
Pablo Greco 48fc63
+                if (S_ISLNK(st.st_mode)) {
Pablo Greco 48fc63
+                        char *joined;
Pablo Greco 48fc63
+
Pablo Greco 48fc63
+                        _cleanup_free_ char *destination = NULL;
Pablo Greco 48fc63
+
Pablo Greco 48fc63
+                        /* This is a symlink, in this case read the destination. But let's make sure we don't follow
Pablo Greco 48fc63
+                         * symlinks without bounds. */
Pablo Greco 48fc63
+                        if (--max_follow <= 0)
Pablo Greco 48fc63
+                                return -ELOOP;
Pablo Greco 48fc63
+
Pablo Greco 48fc63
+                        r = readlinkat_malloc(fd, first + n, &destination);
Pablo Greco 48fc63
+                        if (r < 0)
Pablo Greco 48fc63
+                                return r;
Pablo Greco 48fc63
+                        if (isempty(destination))
Pablo Greco 48fc63
+                                return -EINVAL;
Pablo Greco 48fc63
+
Pablo Greco 48fc63
+                        if (path_is_absolute(destination)) {
Pablo Greco 48fc63
+
Pablo Greco 48fc63
+                                /* An absolute destination. Start the loop from the beginning, but use the root
Pablo Greco 48fc63
+                                 * directory as base. */
Pablo Greco 48fc63
+
Pablo Greco 48fc63
+                                safe_close(fd);
Pablo Greco 48fc63
+                                fd = open(root ?: "/", O_CLOEXEC|O_NOFOLLOW|O_PATH);
Pablo Greco 48fc63
+                                if (fd < 0)
Pablo Greco 48fc63
+                                        return -errno;
Pablo Greco 48fc63
+
Pablo Greco 48fc63
+                                free(done);
Pablo Greco 48fc63
+
Pablo Greco 48fc63
+                                /* Note that we do not revalidate the root, we take it as is. */
Pablo Greco 48fc63
+                                if (isempty(root))
Pablo Greco 48fc63
+                                        done = NULL;
Pablo Greco 48fc63
+                                else {
Pablo Greco 48fc63
+                                        done = strdup(root);
Pablo Greco 48fc63
+                                        if (!done)
Pablo Greco 48fc63
+                                                return -ENOMEM;
Pablo Greco 48fc63
+                                }
Pablo Greco 48fc63
+
Pablo Greco 48fc63
+                                /* Prefix what's left to do with what we just read, and start the loop again, but
Pablo Greco 48fc63
+                                 * remain in the current directory. */
Pablo Greco 48fc63
+                                joined = strjoin(destination, todo, NULL);
Pablo Greco 48fc63
+                        } else
Pablo Greco 48fc63
+                                joined = strjoin("/", destination, todo, NULL);
Pablo Greco 48fc63
+                        if (!joined)
Pablo Greco 48fc63
+                                return -ENOMEM;
Pablo Greco 48fc63
+
Pablo Greco 48fc63
+                        free(buffer);
Pablo Greco 48fc63
+                        todo = buffer = joined;
Pablo Greco 48fc63
+
Pablo Greco 48fc63
+                        continue;
Pablo Greco 48fc63
+                }
Pablo Greco 48fc63
+
Pablo Greco 48fc63
+                /* If this is not a symlink, then let's just add the name we read to what we already verified. */
Pablo Greco 48fc63
+                if (!done) {
Pablo Greco 48fc63
+                        done = first;
Pablo Greco 48fc63
+                        first = NULL;
Pablo Greco 48fc63
+                } else {
Pablo Greco 48fc63
+                        /* If done is "/", as first also contains slash at the head, then remove this redundant slash. */
Pablo Greco 48fc63
+                        if (streq(done, "/"))
Pablo Greco 48fc63
+                                *done = '\0';
Pablo Greco 48fc63
+
Pablo Greco 48fc63
+                        if (!strextend(&done, first, NULL))
Pablo Greco 48fc63
+                                return -ENOMEM;
Pablo Greco 48fc63
+                }
Pablo Greco 48fc63
+
Pablo Greco 48fc63
+                /* And iterate again, but go one directory further down. */
Pablo Greco 48fc63
+                safe_close(fd);
Pablo Greco 48fc63
+                fd = child;
Pablo Greco 48fc63
+                child = -1;
Pablo Greco 48fc63
+        }
Pablo Greco 48fc63
+
Pablo Greco 48fc63
+        if (!done) {
Pablo Greco 48fc63
+                /* Special case, turn the empty string into "/", to indicate the root directory. */
Pablo Greco 48fc63
+                done = strdup("/");
Pablo Greco 48fc63
+                if (!done)
Pablo Greco 48fc63
+                        return -ENOMEM;
Pablo Greco 48fc63
+        }
Pablo Greco 48fc63
+
Pablo Greco 48fc63
+        if (ret) {
Pablo Greco 48fc63
+                *ret = done;
Pablo Greco 48fc63
+                done = NULL;
Pablo Greco 48fc63
+        }
Pablo Greco 48fc63
+
Pablo Greco 48fc63
+        return exists;
Pablo Greco 48fc63
+}
Pablo Greco 48fc63
diff --git a/src/shared/util.h b/src/shared/util.h
Pablo Greco 48fc63
index f768936ab1..915c7439e8 100644
Pablo Greco 48fc63
--- a/src/shared/util.h
Pablo Greco 48fc63
+++ b/src/shared/util.h
Pablo Greco 48fc63
@@ -1155,3 +1155,11 @@ typedef typeof(((struct statfs*)NULL)->f_type) statfs_f_type_t;
Pablo Greco 48fc63
 
Pablo Greco 48fc63
 bool is_fs_type(const struct statfs *s, statfs_f_type_t magic_value) _pure_;
Pablo Greco 48fc63
 int fd_is_fs_type(int fd, statfs_f_type_t magic_value);
Pablo Greco 48fc63
+
Pablo Greco 48fc63
+enum {
Pablo Greco 48fc63
+        CHASE_PREFIX_ROOT = 1,   /* If set, the specified path will be prefixed by the specified root before beginning the iteration */
Pablo Greco 48fc63
+        CHASE_NONEXISTENT = 2,   /* If set, it's OK if the path doesn't actually exist. */
Pablo Greco 48fc63
+        CHASE_NO_AUTOFS = 4,     /* If set, return -EREMOTE if autofs mount point found */
Pablo Greco 48fc63
+};
Pablo Greco 48fc63
+
Pablo Greco 48fc63
+int chase_symlinks(const char *path_with_prefix, const char *root, unsigned flags, char **ret);
Pablo Greco 48fc63
diff --git a/src/test/test-util.c b/src/test/test-util.c
Pablo Greco 48fc63
index efb02ff530..397c45a9f4 100644
Pablo Greco 48fc63
--- a/src/test/test-util.c
Pablo Greco 48fc63
+++ b/src/test/test-util.c
Pablo Greco 48fc63
@@ -36,6 +36,7 @@
Pablo Greco 48fc63
 #include "fileio.h"
Pablo Greco 48fc63
 #include "conf-parser.h"
Pablo Greco 48fc63
 #include "virt.h"
Pablo Greco 48fc63
+#include "path-util.h"
Pablo Greco 48fc63
 
Pablo Greco 48fc63
 static void test_streq_ptr(void) {
Pablo Greco 48fc63
         assert_se(streq_ptr(NULL, NULL));
Pablo Greco 48fc63
@@ -1909,6 +1910,212 @@ static void test_acquire_data_fd(void) {
Pablo Greco 48fc63
         test_acquire_data_fd_one(ACQUIRE_NO_DEV_NULL|ACQUIRE_NO_MEMFD|ACQUIRE_NO_PIPE|ACQUIRE_NO_TMPFILE);
Pablo Greco 48fc63
 }
Pablo Greco 48fc63
 
Pablo Greco 48fc63
+static void test_chase_symlinks(void) {
Pablo Greco 48fc63
+        _cleanup_free_ char *result = NULL;
Pablo Greco 48fc63
+        char temp[] = "/tmp/test-chase.XXXXXX";
Pablo Greco 48fc63
+        const char *top, *p, *pslash, *q, *qslash;
Pablo Greco 48fc63
+        int r;
Pablo Greco 48fc63
+
Pablo Greco 48fc63
+        assert_se(mkdtemp(temp));
Pablo Greco 48fc63
+
Pablo Greco 48fc63
+        top = strjoina(temp, "/top");
Pablo Greco 48fc63
+        assert_se(mkdir(top, 0700) >= 0);
Pablo Greco 48fc63
+
Pablo Greco 48fc63
+        p = strjoina(top, "/dot");
Pablo Greco 48fc63
+        assert_se(symlink(".", p) >= 0);
Pablo Greco 48fc63
+
Pablo Greco 48fc63
+        p = strjoina(top, "/dotdot");
Pablo Greco 48fc63
+        assert_se(symlink("..", p) >= 0);
Pablo Greco 48fc63
+
Pablo Greco 48fc63
+        p = strjoina(top, "/dotdota");
Pablo Greco 48fc63
+        assert_se(symlink("../a", p) >= 0);
Pablo Greco 48fc63
+
Pablo Greco 48fc63
+        p = strjoina(temp, "/a");
Pablo Greco 48fc63
+        assert_se(symlink("b", p) >= 0);
Pablo Greco 48fc63
+
Pablo Greco 48fc63
+        p = strjoina(temp, "/b");
Pablo Greco 48fc63
+        assert_se(symlink("/usr", p) >= 0);
Pablo Greco 48fc63
+
Pablo Greco 48fc63
+        p = strjoina(temp, "/start");
Pablo Greco 48fc63
+        assert_se(symlink("top/dot/dotdota", p) >= 0);
Pablo Greco 48fc63
+
Pablo Greco 48fc63
+        /* Paths that use symlinks underneath the "root" */
Pablo Greco 48fc63
+
Pablo Greco 48fc63
+        r = chase_symlinks(p, NULL, 0, &result);
Pablo Greco 48fc63
+        assert_se(r > 0);
Pablo Greco 48fc63
+        assert_se(path_equal(result, "/usr"));
Pablo Greco 48fc63
+        result = mfree(result);
Pablo Greco 48fc63
+
Pablo Greco 48fc63
+        pslash = strjoina(p, "/");
Pablo Greco 48fc63
+        r = chase_symlinks(pslash, NULL, 0, &result);
Pablo Greco 48fc63
+        assert_se(r > 0);
Pablo Greco 48fc63
+        assert_se(path_equal(result, "/usr/"));
Pablo Greco 48fc63
+        result = mfree(result);
Pablo Greco 48fc63
+
Pablo Greco 48fc63
+        r = chase_symlinks(p, temp, 0, &result);
Pablo Greco 48fc63
+        assert_se(r == -ENOENT);
Pablo Greco 48fc63
+
Pablo Greco 48fc63
+        r = chase_symlinks(pslash, temp, 0, &result);
Pablo Greco 48fc63
+        assert_se(r == -ENOENT);
Pablo Greco 48fc63
+
Pablo Greco 48fc63
+        q = strjoina(temp, "/usr");
Pablo Greco 48fc63
+
Pablo Greco 48fc63
+        r = chase_symlinks(p, temp, CHASE_NONEXISTENT, &result);
Pablo Greco 48fc63
+        assert_se(r == 0);
Pablo Greco 48fc63
+        assert_se(path_equal(result, q));
Pablo Greco 48fc63
+        result = mfree(result);
Pablo Greco 48fc63
+
Pablo Greco 48fc63
+        qslash = strjoina(q, "/");
Pablo Greco 48fc63
+
Pablo Greco 48fc63
+        r = chase_symlinks(pslash, temp, CHASE_NONEXISTENT, &result);
Pablo Greco 48fc63
+        assert_se(r == 0);
Pablo Greco 48fc63
+        assert_se(path_equal(result, qslash));
Pablo Greco 48fc63
+        result = mfree(result);
Pablo Greco 48fc63
+
Pablo Greco 48fc63
+        assert_se(mkdir(q, 0700) >= 0);
Pablo Greco 48fc63
+
Pablo Greco 48fc63
+        r = chase_symlinks(p, temp, 0, &result);
Pablo Greco 48fc63
+        assert_se(r > 0);
Pablo Greco 48fc63
+        assert_se(path_equal(result, q));
Pablo Greco 48fc63
+        result = mfree(result);
Pablo Greco 48fc63
+
Pablo Greco 48fc63
+        r = chase_symlinks(pslash, temp, 0, &result);
Pablo Greco 48fc63
+        assert_se(r > 0);
Pablo Greco 48fc63
+        assert_se(path_equal(result, qslash));
Pablo Greco 48fc63
+        result = mfree(result);
Pablo Greco 48fc63
+
Pablo Greco 48fc63
+        p = strjoina(temp, "/slash");
Pablo Greco 48fc63
+        assert_se(symlink("/", p) >= 0);
Pablo Greco 48fc63
+
Pablo Greco 48fc63
+        r = chase_symlinks(p, NULL, 0, &result);
Pablo Greco 48fc63
+        assert_se(r > 0);
Pablo Greco 48fc63
+        assert_se(path_equal(result, "/"));
Pablo Greco 48fc63
+        result = mfree(result);
Pablo Greco 48fc63
+
Pablo Greco 48fc63
+        r = chase_symlinks(p, temp, 0, &result);
Pablo Greco 48fc63
+        assert_se(r > 0);
Pablo Greco 48fc63
+        assert_se(path_equal(result, temp));
Pablo Greco 48fc63
+        result = mfree(result);
Pablo Greco 48fc63
+
Pablo Greco 48fc63
+        /* Paths that would "escape" outside of the "root" */
Pablo Greco 48fc63
+
Pablo Greco 48fc63
+        p = strjoina(temp, "/6dots");
Pablo Greco 48fc63
+        assert_se(symlink("../../..", p) >= 0);
Pablo Greco 48fc63
+
Pablo Greco 48fc63
+        r = chase_symlinks(p, temp, 0, &result);
Pablo Greco 48fc63
+        assert_se(r > 0 && path_equal(result, temp));
Pablo Greco 48fc63
+        result = mfree(result);
Pablo Greco 48fc63
+
Pablo Greco 48fc63
+        p = strjoina(temp, "/6dotsusr");
Pablo Greco 48fc63
+        assert_se(symlink("../../../usr", p) >= 0);
Pablo Greco 48fc63
+
Pablo Greco 48fc63
+        r = chase_symlinks(p, temp, 0, &result);
Pablo Greco 48fc63
+        assert_se(r > 0 && path_equal(result, q));
Pablo Greco 48fc63
+        result = mfree(result);
Pablo Greco 48fc63
+
Pablo Greco 48fc63
+        p = strjoina(temp, "/top/8dotsusr");
Pablo Greco 48fc63
+        assert_se(symlink("../../../../usr", p) >= 0);
Pablo Greco 48fc63
+
Pablo Greco 48fc63
+        r = chase_symlinks(p, temp, 0, &result);
Pablo Greco 48fc63
+        assert_se(r > 0 && path_equal(result, q));
Pablo Greco 48fc63
+        result = mfree(result);
Pablo Greco 48fc63
+
Pablo Greco 48fc63
+        /* Paths that contain repeated slashes */
Pablo Greco 48fc63
+
Pablo Greco 48fc63
+        p = strjoina(temp, "/slashslash");
Pablo Greco 48fc63
+        assert_se(symlink("///usr///", p) >= 0);
Pablo Greco 48fc63
+
Pablo Greco 48fc63
+        r = chase_symlinks(p, NULL, 0, &result);
Pablo Greco 48fc63
+        assert_se(r > 0);
Pablo Greco 48fc63
+        assert_se(path_equal(result, "/usr"));
Pablo Greco 48fc63
+        result = mfree(result);
Pablo Greco 48fc63
+
Pablo Greco 48fc63
+        r = chase_symlinks(p, temp, 0, &result);
Pablo Greco 48fc63
+        assert_se(r > 0);
Pablo Greco 48fc63
+        assert_se(path_equal(result, q));
Pablo Greco 48fc63
+        result = mfree(result);
Pablo Greco 48fc63
+
Pablo Greco 48fc63
+        /* Paths using . */
Pablo Greco 48fc63
+
Pablo Greco 48fc63
+        r = chase_symlinks("/etc/./.././", NULL, 0, &result);
Pablo Greco 48fc63
+        assert_se(r > 0);
Pablo Greco 48fc63
+        assert_se(path_equal(result, "/"));
Pablo Greco 48fc63
+        result = mfree(result);
Pablo Greco 48fc63
+
Pablo Greco 48fc63
+        r = chase_symlinks("/etc/./.././", "/etc", 0, &result);
Pablo Greco 48fc63
+        assert_se(r > 0 && path_equal(result, "/etc"));
Pablo Greco 48fc63
+        result = mfree(result);
Pablo Greco 48fc63
+
Pablo Greco 48fc63
+        r = chase_symlinks("/../.././//../../etc", NULL, 0, &result);
Pablo Greco 48fc63
+        assert_se(r > 0);
Pablo Greco 48fc63
+        assert_se(streq(result, "/etc"));
Pablo Greco 48fc63
+        result = mfree(result);
Pablo Greco 48fc63
+
Pablo Greco 48fc63
+        r = chase_symlinks("/../.././//../../test-chase.fsldajfl", NULL, CHASE_NONEXISTENT, &result);
Pablo Greco 48fc63
+        assert_se(r == 0);
Pablo Greco 48fc63
+        assert_se(streq(result, "/test-chase.fsldajfl"));
Pablo Greco 48fc63
+        result = mfree(result);
Pablo Greco 48fc63
+
Pablo Greco 48fc63
+        r = chase_symlinks("/../.././//../../etc", "/", CHASE_PREFIX_ROOT, &result);
Pablo Greco 48fc63
+        assert_se(r > 0);
Pablo Greco 48fc63
+        assert_se(streq(result, "/etc"));
Pablo Greco 48fc63
+        result = mfree(result);
Pablo Greco 48fc63
+
Pablo Greco 48fc63
+        r = chase_symlinks("/../.././//../../test-chase.fsldajfl", "/", CHASE_PREFIX_ROOT|CHASE_NONEXISTENT, &result);
Pablo Greco 48fc63
+        assert_se(r == 0);
Pablo Greco 48fc63
+        assert_se(streq(result, "/test-chase.fsldajfl"));
Pablo Greco 48fc63
+        result = mfree(result);
Pablo Greco 48fc63
+
Pablo Greco 48fc63
+        r = chase_symlinks("/etc/machine-id/foo", NULL, 0, &result);
Pablo Greco 48fc63
+        assert_se(r == -ENOTDIR);
Pablo Greco 48fc63
+        result = mfree(result);
Pablo Greco 48fc63
+
Pablo Greco 48fc63
+        /* Path that loops back to self */
Pablo Greco 48fc63
+
Pablo Greco 48fc63
+        p = strjoina(temp, "/recursive-symlink");
Pablo Greco 48fc63
+        assert_se(symlink("recursive-symlink", p) >= 0);
Pablo Greco 48fc63
+        r = chase_symlinks(p, NULL, 0, &result);
Pablo Greco 48fc63
+        assert_se(r == -ELOOP);
Pablo Greco 48fc63
+
Pablo Greco 48fc63
+        /* Path which doesn't exist */
Pablo Greco 48fc63
+
Pablo Greco 48fc63
+        p = strjoina(temp, "/idontexist");
Pablo Greco 48fc63
+        r = chase_symlinks(p, NULL, 0, &result);
Pablo Greco 48fc63
+        assert_se(r == -ENOENT);
Pablo Greco 48fc63
+
Pablo Greco 48fc63
+        r = chase_symlinks(p, NULL, CHASE_NONEXISTENT, &result);
Pablo Greco 48fc63
+        assert_se(r == 0);
Pablo Greco 48fc63
+        assert_se(path_equal(result, p));
Pablo Greco 48fc63
+        result = mfree(result);
Pablo Greco 48fc63
+
Pablo Greco 48fc63
+        p = strjoina(temp, "/idontexist/meneither");
Pablo Greco 48fc63
+        r = chase_symlinks(p, NULL, 0, &result);
Pablo Greco 48fc63
+        assert_se(r == -ENOENT);
Pablo Greco 48fc63
+
Pablo Greco 48fc63
+        r = chase_symlinks(p, NULL, CHASE_NONEXISTENT, &result);
Pablo Greco 48fc63
+        assert_se(r == 0);
Pablo Greco 48fc63
+        assert_se(path_equal(result, p));
Pablo Greco 48fc63
+        result = mfree(result);
Pablo Greco 48fc63
+
Pablo Greco 48fc63
+        /* Path which doesn't exist, but contains weird stuff */
Pablo Greco 48fc63
+
Pablo Greco 48fc63
+        p = strjoina(temp, "/idontexist/..");
Pablo Greco 48fc63
+        r = chase_symlinks(p, NULL, 0, &result);
Pablo Greco 48fc63
+        assert_se(r == -ENOENT);
Pablo Greco 48fc63
+
Pablo Greco 48fc63
+        r = chase_symlinks(p, NULL, CHASE_NONEXISTENT, &result);
Pablo Greco 48fc63
+        assert_se(r == -ENOENT);
Pablo Greco 48fc63
+
Pablo Greco 48fc63
+        p = strjoina(temp, "/target");
Pablo Greco 48fc63
+        q = strjoina(temp, "/top");
Pablo Greco 48fc63
+        assert_se(symlink(q, p) >= 0);
Pablo Greco 48fc63
+        p = strjoina(temp, "/target/idontexist");
Pablo Greco 48fc63
+        r = chase_symlinks(p, NULL, 0, &result);
Pablo Greco 48fc63
+        assert_se(r == -ENOENT);
Pablo Greco 48fc63
+
Pablo Greco 48fc63
+        assert_se(rm_rf_dangerous(temp, false, true, false) >= 0);
Pablo Greco 48fc63
+}
Pablo Greco 48fc63
+
Pablo Greco 48fc63
 int main(int argc, char *argv[]) {
Pablo Greco 48fc63
         log_parse_environment();
Pablo Greco 48fc63
         log_open();
Pablo Greco 48fc63
@@ -1992,6 +2199,7 @@ int main(int argc, char *argv[]) {
Pablo Greco 48fc63
         test_system_tasks_max();
Pablo Greco 48fc63
         test_system_tasks_max_scale();
Pablo Greco 48fc63
         test_acquire_data_fd();
Pablo Greco 48fc63
+        test_chase_symlinks();
Pablo Greco 48fc63
 
Pablo Greco 48fc63
         return 0;
Pablo Greco 48fc63
 }