c62b8e
From c211b650ee5cb9934067dbba40718a4a33063e06 Mon Sep 17 00:00:00 2001
c62b8e
From: David Tardon <dtardon@redhat.com>
c62b8e
Date: Thu, 3 Jan 2019 14:44:36 +0100
c62b8e
Subject: [PATCH] fs-util: add new chase_symlinks() flag CHASE_OPEN
c62b8e
c62b8e
The new flag returns the O_PATH fd of the final component, which may be
c62b8e
converted into a proper fd by open()ing it again through the
c62b8e
/proc/self/fd/xyz path.
c62b8e
c62b8e
Together with O_SAFE this provides us with a somewhat safe way to open()
c62b8e
files in directories potentially owned by unprivileged code, where we
c62b8e
want to refuse operation if any symlink tricks are played pointing to
c62b8e
privileged files.
c62b8e
c62b8e
(cherry picked from commit 1ed34d75d4f21d2335c5625261954c848d176ae6)
c62b8e
c62b8e
Related: #1663143
c62b8e
---
c62b8e
 Makefile.am          |  1 +
c62b8e
 src/shared/util.c    | 17 +++++++++++++
c62b8e
 src/shared/util.h    |  1 +
c62b8e
 src/test/test-util.c | 59 +++++++++++++++++++++++++++++++++++++++++++-
c62b8e
 4 files changed, 77 insertions(+), 1 deletion(-)
c62b8e
c62b8e
diff --git a/Makefile.am b/Makefile.am
c62b8e
index 995c421b8b..648f54b957 100644
c62b8e
--- a/Makefile.am
c62b8e
+++ b/Makefile.am
c62b8e
@@ -1675,6 +1675,7 @@ test_util_SOURCES = \
c62b8e
 	src/test/test-util.c
c62b8e
 
c62b8e
 test_util_LDADD = \
c62b8e
+	libsystemd-internal.la \
c62b8e
 	libsystemd-shared.la
c62b8e
 
c62b8e
 test_path_lookup_SOURCES = \
c62b8e
diff --git a/src/shared/util.c b/src/shared/util.c
c62b8e
index fc4887920f..354d15ff18 100644
c62b8e
--- a/src/shared/util.c
c62b8e
+++ b/src/shared/util.c
c62b8e
@@ -9225,6 +9225,10 @@ int chase_symlinks(const char *path, const char *original_root, unsigned flags,
c62b8e
 
c62b8e
         assert(path);
c62b8e
 
c62b8e
+        /* Either the file may be missing, or we return an fd to the final object, but both make no sense */
c62b8e
+        if ((flags & (CHASE_NONEXISTENT|CHASE_OPEN)) == (CHASE_NONEXISTENT|CHASE_OPEN))
c62b8e
+                return -EINVAL;
c62b8e
+
c62b8e
         /* This is a lot like canonicalize_file_name(), but takes an additional "root" parameter, that allows following
c62b8e
          * symlinks relative to a root directory, instead of the root of the host.
c62b8e
          *
c62b8e
@@ -9476,5 +9480,18 @@ int chase_symlinks(const char *path, const char *original_root, unsigned flags,
c62b8e
                 done = NULL;
c62b8e
         }
c62b8e
 
c62b8e
+        if (flags & CHASE_OPEN) {
c62b8e
+                int q;
c62b8e
+
c62b8e
+                /* Return the O_PATH fd we currently are looking to the caller. It can translate it to a proper fd by
c62b8e
+                 * opening /proc/self/fd/xyz. */
c62b8e
+
c62b8e
+                assert(fd >= 0);
c62b8e
+                q = fd;
c62b8e
+                fd = -1;
c62b8e
+
c62b8e
+                return q;
c62b8e
+        }
c62b8e
+
c62b8e
         return exists;
c62b8e
 }
c62b8e
diff --git a/src/shared/util.h b/src/shared/util.h
c62b8e
index fa3e2e3009..d89f0d34a1 100644
c62b8e
--- a/src/shared/util.h
c62b8e
+++ b/src/shared/util.h
c62b8e
@@ -1161,6 +1161,7 @@ enum {
c62b8e
         CHASE_NONEXISTENT = 1U << 1,   /* If set, it's OK if the path doesn't actually exist. */
c62b8e
         CHASE_NO_AUTOFS   = 1U << 2,   /* If set, return -EREMOTE if autofs mount point found */
c62b8e
         CHASE_SAFE        = 1U << 3,   /* If set, return EPERM if we ever traverse from unprivileged to privileged files or directories */
c62b8e
+        CHASE_OPEN        = 1U << 4,   /* If set, return an O_PATH object to the final component */
c62b8e
 };
c62b8e
 
c62b8e
 int chase_symlinks(const char *path_with_prefix, const char *root, unsigned flags, char **ret);
c62b8e
diff --git a/src/test/test-util.c b/src/test/test-util.c
c62b8e
index e5a646ec20..8ef3850e10 100644
c62b8e
--- a/src/test/test-util.c
c62b8e
+++ b/src/test/test-util.c
c62b8e
@@ -1910,11 +1910,45 @@ static void test_acquire_data_fd(void) {
c62b8e
         test_acquire_data_fd_one(ACQUIRE_NO_DEV_NULL|ACQUIRE_NO_MEMFD|ACQUIRE_NO_PIPE|ACQUIRE_NO_TMPFILE);
c62b8e
 }
c62b8e
 
c62b8e
+static int id128_read_fd(int fd, sd_id128_t *ret) {
c62b8e
+        char buf[33];
c62b8e
+        ssize_t k;
c62b8e
+        unsigned j;
c62b8e
+        sd_id128_t t;
c62b8e
+
c62b8e
+        assert_return(fd >= 0, -EINVAL);
c62b8e
+
c62b8e
+        k = loop_read(fd, buf, 33, false);
c62b8e
+        if (k < 0)
c62b8e
+                return (int) k;
c62b8e
+
c62b8e
+        if (k != 33)
c62b8e
+                return -EIO;
c62b8e
+
c62b8e
+        if (buf[32] !='\n')
c62b8e
+                return -EIO;
c62b8e
+
c62b8e
+        for (j = 0; j < 16; j++) {
c62b8e
+                int a, b;
c62b8e
+
c62b8e
+                a = unhexchar(buf[j*2]);
c62b8e
+                b = unhexchar(buf[j*2+1]);
c62b8e
+
c62b8e
+                if (a < 0 || b < 0)
c62b8e
+                        return -EIO;
c62b8e
+
c62b8e
+                t.bytes[j] = a << 4 | b;
c62b8e
+        }
c62b8e
+
c62b8e
+        *ret = t;
c62b8e
+        return 0;
c62b8e
+}
c62b8e
+
c62b8e
 static void test_chase_symlinks(void) {
c62b8e
         _cleanup_free_ char *result = NULL;
c62b8e
         char temp[] = "/tmp/test-chase.XXXXXX";
c62b8e
         const char *top, *p, *pslash, *q, *qslash;
c62b8e
-        int r;
c62b8e
+        int r, pfd;
c62b8e
 
c62b8e
         assert_se(mkdtemp(temp));
c62b8e
 
c62b8e
@@ -2139,6 +2173,29 @@ static void test_chase_symlinks(void) {
c62b8e
                 assert_se(chase_symlinks(q, NULL, CHASE_SAFE, NULL) >= 0);
c62b8e
         }
c62b8e
 
c62b8e
+        p = strjoina(temp, "/machine-id-test");
c62b8e
+        assert_se(symlink("/usr/../etc/./machine-id", p) >= 0);
c62b8e
+
c62b8e
+        pfd = chase_symlinks(p, NULL, CHASE_OPEN, NULL);
c62b8e
+        if (pfd != -ENOENT) {
c62b8e
+                char procfs[sizeof("/proc/self/fd/") - 1 + DECIMAL_STR_MAX(pfd) + 1];
c62b8e
+                _cleanup_close_ int fd = -1;
c62b8e
+                sd_id128_t a, b;
c62b8e
+
c62b8e
+                assert_se(pfd >= 0);
c62b8e
+
c62b8e
+                xsprintf(procfs, "/proc/self/fd/%i", pfd);
c62b8e
+
c62b8e
+                fd = open(procfs, O_RDONLY|O_CLOEXEC);
c62b8e
+                assert_se(fd >= 0);
c62b8e
+
c62b8e
+                safe_close(pfd);
c62b8e
+
c62b8e
+                assert_se(id128_read_fd(fd, &a) >= 0);
c62b8e
+                assert_se(sd_id128_get_machine(&b) >= 0);
c62b8e
+                assert_se(sd_id128_equal(a, b));
c62b8e
+        }
c62b8e
+
c62b8e
         assert_se(rm_rf_dangerous(temp, false, true, false) >= 0);
c62b8e
 }
c62b8e