From c2595d3b02849b7baa94483f03cbc888e0c63ebd Mon Sep 17 00:00:00 2001
From: Lennart Poettering <lennart@poettering.net>
Date: Tue, 28 Jan 2020 21:02:29 +0100
Subject: [PATCH 1477/1760] fs-util: when calling chase_symlinks() with root
 path, leave root part unresolved

Previously there was a weird asymmetry: initially we'd resolve the
specified prefix path when chasing symlinks together with the actual
path we were supposed to cover, except when we hit an absolute symlink
where we'd use the root as it was. Let's unify handling here: the prefix
path is never resolved, and always left as it is.

This in particular fixes issues with symlinks in the prefix path, as
that confused the check that made sure we never left the root directory.

Fixes: #14634
Replaces: #14635
Reference: https://github.com/systemd/systemd/commit/c2595d3b02849b7baa94483f03cbc888e0c63ebd
Conflict: NA
---
 src/basic/fs-util.c | 34 +++++++++++++++++++++++++++++++---
 1 file changed, 31 insertions(+), 3 deletions(-)

diff --git a/src/basic/fs-util.c b/src/basic/fs-util.c
index 5723c84..5ec3285 100644
--- a/src/basic/fs-util.c
+++ b/src/basic/fs-util.c
@@ -810,7 +810,7 @@ int chase_symlinks(const char *path, const char *original_root, unsigned flags,
         if (r < 0)
                 return r;
 
-        fd = open("/", O_CLOEXEC|O_NOFOLLOW|O_PATH);
+        fd = open(root ?: "/", O_CLOEXEC|O_DIRECTORY|O_PATH);
         if (fd < 0)
                 return -errno;
 
@@ -819,6 +819,33 @@ int chase_symlinks(const char *path, const char *original_root, unsigned flags,
                         return -errno;
         }
 
+        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)
+                        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);
+
+                /* Make sure "done" ends without a slash */
+                done = strdup(root);
+                if (!done)
+                        return -ENOMEM;
+                delete_trailing_chars(done, "/");
+
+                /* Make sure "todo" starts with a slash */
+                absolute = strjoin("/", e);
+                if (!absolute)
+                        return -ENOMEM;
+
+                free_and_replace(buffer, absolute);
+        }
+
         todo = buffer;
         for (;;) {
                 _cleanup_free_ char *first = NULL;
@@ -930,7 +957,6 @@ int chase_symlinks(const char *path, const char *original_root, unsigned flags,
                 if (fstat(child, &st) < 0)
                         return -errno;
                 if ((flags & CHASE_SAFE) &&
-                    (empty_or_root(root) || (size_t)(todo - buffer) > strlen(root)) &&
                     unsafe_transition(&previous_stat, &st))
                         return log_unsafe_transition(fd, child, path, flags);
 
@@ -961,7 +987,7 @@ int chase_symlinks(const char *path, const char *original_root, unsigned flags,
                                  * directory as base. */
 
                                 safe_close(fd);
-                                fd = open(root ?: "/", O_CLOEXEC|O_NOFOLLOW|O_PATH);
+                                fd = open(root ?: "/", O_CLOEXEC|O_DIRECTORY|O_PATH);
                                 if (fd < 0)
                                         return -errno;
 
@@ -984,6 +1010,8 @@ int chase_symlinks(const char *path, const char *original_root, unsigned flags,
                                         done = strdup(root);
                                         if (!done)
                                                 return -ENOMEM;
+
+                                        delete_trailing_chars(done, "/");
                                 }
 
                                 /* Prefix what's left to do with what we just read, and start the loop again, but
-- 
1.8.3.1

