From 1ed34d75d4f21d2335c5625261954c848d176ae6 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 4 Jan 2018 20:00:28 +0100 Subject: [PATCH] fs-util: add new chase_symlinks() flag CHASE_OPEN The new flag returns the O_PATH fd of the final component, which may be converted into a proper fd by open()ing it again through the /proc/self/fd/xyz path. Together with O_SAFE this provides us with a somewhat safe way to open() files in directories potentially owned by unprivileged code, where we want to refuse operation if any symlink tricks are played pointing to privileged files. --- src/basic/fs-util.c | 17 +++++++++++++++++ src/basic/fs-util.h | 1 + src/test/test-fs-util.c | 28 +++++++++++++++++++++++++++- 3 files changed, 45 insertions(+), 1 deletion(-) diff --git a/src/basic/fs-util.c b/src/basic/fs-util.c index a7ee785..7b76f33 100644 --- a/src/basic/fs-util.c +++ b/src/basic/fs-util.c @@ -632,6 +632,10 @@ int chase_symlinks(const char *path, const char *original_root, unsigned flags, assert(path); + /* Either the file may be missing, or we return an fd to the final object, but both make no sense */ + if ((flags & (CHASE_NONEXISTENT|CHASE_OPEN)) == (CHASE_NONEXISTENT|CHASE_OPEN)) + return -EINVAL; + /* This is a lot like canonicalize_file_name(), but takes an additional "root" parameter, that allows following * symlinks relative to a root directory, instead of the root of the host. * @@ -881,6 +885,19 @@ int chase_symlinks(const char *path, const char *original_root, unsigned flags, done = NULL; } + if (flags & CHASE_OPEN) { + int q; + + /* Return the O_PATH fd we currently are looking to the caller. It can translate it to a proper fd by + * opening /proc/self/fd/xyz. */ + + assert(fd >= 0); + q = fd; + fd = -1; + + return q; + } + return exists; } diff --git a/src/basic/fs-util.h b/src/basic/fs-util.h index 182dc63..4dba1ea 100644 --- a/src/basic/fs-util.h +++ b/src/basic/fs-util.h @@ -85,6 +85,7 @@ enum { CHASE_NONEXISTENT = 1U << 1, /* If set, it's OK if the path doesn't actually exist. */ CHASE_NO_AUTOFS = 1U << 2, /* If set, return -EREMOTE if autofs mount point found */ CHASE_SAFE = 1U << 3, /* If set, return EPERM if we ever traverse from unprivileged to privileged files or directories */ + CHASE_OPEN = 1U << 4, /* If set, return an O_PATH object to the final component */ }; int chase_symlinks(const char *path_with_prefix, const char *root, unsigned flags, char **ret); diff --git a/src/test/test-fs-util.c b/src/test/test-fs-util.c index b5ec086..cda6d9c 100644 --- a/src/test/test-fs-util.c +++ b/src/test/test-fs-util.c @@ -22,12 +22,15 @@ #include "alloc-util.h" #include "fd-util.h" +#include "fd-util.h" #include "fileio.h" #include "fs-util.h" +#include "id128-util.h" #include "macro.h" #include "mkdir.h" #include "path-util.h" #include "rm-rf.h" +#include "stdio-util.h" #include "string-util.h" #include "strv.h" #include "user-util.h" @@ -37,7 +40,7 @@ static void test_chase_symlinks(void) { _cleanup_free_ char *result = NULL; char temp[] = "/tmp/test-chase.XXXXXX"; const char *top, *p, *pslash, *q, *qslash; - int r; + int r, pfd; assert_se(mkdtemp(temp)); @@ -262,6 +265,29 @@ static void test_chase_symlinks(void) { assert_se(chase_symlinks(q, NULL, CHASE_SAFE, NULL) >= 0); } + p = strjoina(temp, "/machine-id-test"); + assert_se(symlink("/usr/../etc/./machine-id", p) >= 0); + + pfd = chase_symlinks(p, NULL, CHASE_OPEN, NULL); + if (pfd != -ENOENT) { + char procfs[STRLEN("/proc/self/fd/") + DECIMAL_STR_MAX(pfd) + 1]; + _cleanup_close_ int fd = -1; + sd_id128_t a, b; + + assert_se(pfd >= 0); + + xsprintf(procfs, "/proc/self/fd/%i", pfd); + + fd = open(procfs, O_RDONLY|O_CLOEXEC); + assert_se(fd >= 0); + + safe_close(pfd); + + assert_se(id128_read_fd(fd, ID128_PLAIN, &a) >= 0); + assert_se(sd_id128_get_machine(&b) >= 0); + assert_se(sd_id128_equal(a, b)); + } + assert_se(rm_rf(temp, REMOVE_ROOT|REMOVE_PHYSICAL) >= 0); } -- 2.7.4