diff options
author | Daan De Meyer <daan.j.demeyer@gmail.com> | 2023-04-20 09:19:22 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-04-20 09:19:22 +0200 |
commit | 59e4eeed7819c05c57d52e268ab459a00a28ea27 (patch) | |
tree | bd909ec00a3b6798078ce7071465d502e819291f | |
parent | 47041a2b91034b5cce19cb87a5c9a43b25691b23 (diff) | |
parent | c19f1cc9a5ef20f37e890df65fb9b8b95a0b18fa (diff) | |
download | systemd-59e4eeed7819c05c57d52e268ab459a00a28ea27.tar.gz |
Merge pull request #27299 from yuwata/chase-absolute
chase: return absolute path when dir_fd points to the root directory
-rw-r--r-- | src/basic/chase.c | 114 | ||||
-rw-r--r-- | src/basic/chase.h | 2 | ||||
-rw-r--r-- | src/basic/fd-util.c | 22 | ||||
-rw-r--r-- | src/basic/mountpoint-util.c | 6 | ||||
-rw-r--r-- | src/basic/os-util.c | 4 | ||||
-rw-r--r-- | src/basic/path-util.c | 28 | ||||
-rw-r--r-- | src/basic/path-util.h | 1 | ||||
-rw-r--r-- | src/shared/find-esp.c | 4 | ||||
-rw-r--r-- | src/test/test-chase.c | 74 | ||||
-rw-r--r-- | src/test/test-path-util.c | 39 | ||||
-rwxr-xr-x | test/test-bootctl-json.sh | 9 |
11 files changed, 194 insertions, 109 deletions
diff --git a/src/basic/chase.c b/src/basic/chase.c index eb4bda07a6..373252b645 100644 --- a/src/basic/chase.c +++ b/src/basic/chase.c @@ -111,12 +111,26 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int * given directory file descriptor, even if it is absolute. If the given directory file descriptor is * AT_FDCWD and "path" is absolute, it is interpreted relative to the root directory of the host. * - * If "dir_fd" is a valid directory fd, "path" is an absolute path and "ret_path" is not NULL, this - * functions returns a relative path in "ret_path" because openat() like functions generally ignore - * the directory fd if they are provided with an absolute path. On the other hand, if "dir_fd" is - * AT_FDCWD and "path" is an absolute path, we return an absolute path in "ret_path" because - * otherwise, if the caller passes the returned relative path to another openat() like function, it - * would be resolved relative to the current working directory instead of to "/". + * When "dir_fd" points to a non-root directory and CHASE_AT_RESOLVE_IN_ROOT is set, this function + * always returns a relative path in "ret_path", even if "path" is an absolute path, because openat() + * like functions generally ignore the directory fd if they are provided with an absolute path. When + * CHASE_AT_RESOLVE_IN_ROOT is not set, then this returns relative path to the specified file + * descriptor if all resolved symlinks are relative, otherwise absolute path will be returned. When + * "dir_fd" is AT_FDCWD and "path" is an absolute path, we return an absolute path in "ret_path" + * because otherwise, if the caller passes the returned relative path to another openat() like + * function, it would be resolved relative to the current working directory instead of to "/". + * + * Summary about the result path: + * - "dir_fd" points to the root directory + * → result will be absolute + * - "dir_fd" points to a non-root directory, and CHASE_AT_RESOLVE_IN_ROOT is set + * → relative + * - "dir_fd" points to a non-root directory, and CHASE_AT_RESOLVE_IN_ROOT is not set + * → relative when all resolved symlinks are relative, otherwise absolute + * - "dir_fd" is AT_FDCWD, and "path" is absolute + * → absolute + * - "dir_fd" is AT_FDCWD, and "path" is relative + * → relative when all resolved symlinks are relative, otherwise absolute * * Algorithmically this operates on two path buffers: "done" are the components of the path we * already processed and resolved symlinks, "." and ".." of. "todo" are the components of the path we @@ -190,8 +204,9 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int return -ENOMEM; /* If we receive an absolute path together with AT_FDCWD, we need to return an absolute path, because - * a relative path would be interpreted relative to the current working directory. */ - bool need_absolute = dir_fd == AT_FDCWD && path_is_absolute(path); + * a relative path would be interpreted relative to the current working directory. Also, let's make + * the result absolute when the file descriptor of the root directory is specified. */ + bool need_absolute = (dir_fd == AT_FDCWD && path_is_absolute(path)) || dir_fd_is_root(dir_fd) > 0; if (need_absolute) { done = strdup("/"); if (!done) @@ -373,6 +388,11 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int unsafe_transition(&st_child, &st)) return log_unsafe_transition(child, fd, path, flags); + /* When CHASE_AT_RESOLVE_IN_ROOT is not set, now the chased path may be + * outside of the specified dir_fd. Let's make the result absolute. */ + if (!FLAGS_SET(flags, CHASE_AT_RESOLVE_IN_ROOT)) + need_absolute = true; + r = free_and_strdup(&done, need_absolute ? "/" : NULL); if (r < 0) return r; @@ -474,8 +494,8 @@ chased_one: return 0; } -int chase(const char *path, const char *original_root, ChaseFlags flags, char **ret_path, int *ret_fd) { - _cleanup_free_ char *root = NULL, *absolute = NULL, *p = NULL; +int chase(const char *path, const char *root, ChaseFlags flags, char **ret_path, int *ret_fd) { + _cleanup_free_ char *root_abs = NULL, *absolute = NULL, *p = NULL; _cleanup_close_ int fd = -EBADF, pfd = -EBADF; int r; @@ -484,18 +504,17 @@ int chase(const char *path, const char *original_root, ChaseFlags flags, char ** if (isempty(path)) return -EINVAL; - /* A root directory of "/" or "" is identical to none */ - if (empty_or_root(original_root)) - original_root = NULL; - - if (original_root) { - r = path_make_absolute_cwd(original_root, &root); + /* A root directory of "/" or "" is identical to "/". */ + if (empty_or_root(root)) + root = "/"; + else { + r = path_make_absolute_cwd(root, &root_abs); if (r < 0) return r; /* Simplify the root directory, so that it has no duplicate slashes and nothing at the * end. While we won't resolve the root path we still simplify it. */ - path_simplify(root); + root = path_simplify(root_abs); assert(path_is_absolute(root)); assert(!empty_or_root(root)); @@ -515,14 +534,14 @@ int chase(const char *path, const char *original_root, ChaseFlags flags, char ** return r; } - path = path_startswith(absolute, empty_to_root(root)); + path = path_startswith(absolute, root); if (!path) return log_full_errno(FLAGS_SET(flags, CHASE_WARN) ? LOG_WARNING : LOG_DEBUG, SYNTHETIC_ERRNO(ECHRNG), "Specified path '%s' is outside of specified root directory '%s', refusing to resolve.", - absolute, empty_to_root(root)); + absolute, root); - fd = open(empty_to_root(root), O_CLOEXEC|O_DIRECTORY|O_PATH); + fd = open(root, O_CLOEXEC|O_DIRECTORY|O_PATH); if (fd < 0) return -errno; @@ -532,19 +551,27 @@ int chase(const char *path, const char *original_root, ChaseFlags flags, char ** if (ret_path) { if (!FLAGS_SET(flags, CHASE_EXTRACT_FILENAME)) { - _cleanup_free_ char *q = NULL; - q = path_join(empty_to_root(root), p); - if (!q) - return -ENOMEM; + /* When "root" points to the root directory, the result of chaseat() is always + * absolute, hence it is not necessary to prefix with the root. When "root" points to + * a non-root directory, the result path is always normalized and relative, hence + * we can simply call path_join() and not necessary to call path_simplify(). + * Note that the result of chaseat() may start with "." (more specifically, it may be + * "." or "./"), and we need to drop "." in that case. */ + + if (empty_or_root(root)) + assert(path_is_absolute(p)); + else { + char *q; - path_simplify(q); + assert(!path_is_absolute(p)); - if (FLAGS_SET(flags, CHASE_TRAIL_SLASH) && ENDSWITH_SET(path, "/", "/.")) - if (!strextend(&q, "/")) + q = path_join(root, p + (*p == '.')); + if (!q) return -ENOMEM; - free_and_replace(p, q); + free_and_replace(p, q); + } } *ret_path = TAKE_PTR(p); @@ -556,6 +583,37 @@ int chase(const char *path, const char *original_root, ChaseFlags flags, char ** return r; } +int chaseat_prefix_root(const char *path, const char *root, char **ret) { + char *q; + int r; + + assert(path); + assert(ret); + + /* This is mostly for prefixing the result of chaseat(). */ + + if (!path_is_absolute(path)) { + _cleanup_free_ char *root_abs = NULL; + + /* If the dir_fd points to the root directory, chaseat() always returns an absolute path. */ + assert(!empty_or_root(root)); + + r = path_make_absolute_cwd(root, &root_abs); + if (r < 0) + return r; + + root = path_simplify(root_abs); + + q = path_join(root, path + (path[0] == '.' && IN_SET(path[1], '/', '\0'))); + } else + q = strdup(path); + if (!q) + return -ENOMEM; + + *ret = q; + return 0; +} + int chase_and_open(const char *path, const char *root, ChaseFlags chase_flags, int open_flags, char **ret_path) { _cleanup_close_ int path_fd = -EBADF; _cleanup_free_ char *p = NULL, *fname = NULL; diff --git a/src/basic/chase.h b/src/basic/chase.h index 40121f7d70..f37e836822 100644 --- a/src/basic/chase.h +++ b/src/basic/chase.h @@ -42,6 +42,8 @@ bool unsafe_transition(const struct stat *a, const struct stat *b); int chase(const char *path_with_prefix, const char *root, ChaseFlags chase_flags, char **ret_path, int *ret_fd); +int chaseat_prefix_root(const char *path, const char *root, char **ret); + int chase_and_open(const char *path, const char *root, ChaseFlags chase_flags, int open_flags, char **ret_path); int chase_and_opendir(const char *path, const char *root, ChaseFlags chase_flags, char **ret_path, DIR **ret_dir); int chase_and_stat(const char *path, const char *root, ChaseFlags chase_flags, char **ret_path, struct stat *ret_stat); diff --git a/src/basic/fd-util.c b/src/basic/fd-util.c index 7125e28e1b..974a7aac65 100644 --- a/src/basic/fd-util.c +++ b/src/basic/fd-util.c @@ -911,10 +911,23 @@ int dir_fd_is_root(int dir_fd) { if (!statx_inode_same(&st.sx, &pst.sx)) return false; + /* Even if the parent directory has the same inode, the fd may not point to the root directory "/", + * and we also need to check that the mount ids are the same. Otherwise, a construct like the + * following could be used to trick us: + * + * $ mkdir /tmp/x /tmp/x/y + * $ mount --bind /tmp/x /tmp/x/y + * + * Note, statx() does not provide the mount ID and path_get_mnt_id_at() does not work when an old + * kernel is used without /proc mounted. In that case, let's assume that we do not have such spurious + * mount points in an early boot stage, and silently skip the following check. */ + if (!FLAGS_SET(st.nsx.stx_mask, STATX_MNT_ID)) { int mntid; r = path_get_mnt_id_at(dir_fd, "", &mntid); + if (r == -ENOSYS) + return true; /* skip the mount ID check */ if (r < 0) return r; assert(mntid >= 0); @@ -927,6 +940,8 @@ int dir_fd_is_root(int dir_fd) { int mntid; r = path_get_mnt_id_at(dir_fd, "..", &mntid); + if (r == -ENOSYS) + return true; /* skip the mount ID check */ if (r < 0) return r; assert(mntid >= 0); @@ -935,13 +950,6 @@ int dir_fd_is_root(int dir_fd) { pst.nsx.stx_mask |= STATX_MNT_ID; } - /* Even if the parent directory has the same inode, the fd may not point to the root directory "/", - * and we also need to check that the mount ids are the same. Otherwise, a construct like the - * following could be used to trick us: - * - * $ mkdir /tmp/x /tmp/x/y - * $ mount --bind /tmp/x /tmp/x/y - */ return statx_mount_same(&st.nsx, &pst.nsx); } diff --git a/src/basic/mountpoint-util.c b/src/basic/mountpoint-util.c index db36b2f2f6..e28ca825b3 100644 --- a/src/basic/mountpoint-util.c +++ b/src/basic/mountpoint-util.c @@ -123,7 +123,7 @@ static int fd_fdinfo_mnt_id(int fd, const char *filename, int flags, int *ret_mn r = read_full_virtual_file(path, &fdinfo, NULL); if (r == -ENOENT) /* The fdinfo directory is a relatively new addition */ - return -EOPNOTSUPP; + return proc_mounted() > 0 ? -EOPNOTSUPP : -ENOSYS; if (r < 0) return r; @@ -280,7 +280,7 @@ int fd_is_mount_point(int fd, const char *filename, int flags) { fallback_fdinfo: r = fd_fdinfo_mnt_id(fd, filename, flags, &mount_id); - if (IN_SET(r, -EOPNOTSUPP, -EACCES, -EPERM)) + if (IN_SET(r, -EOPNOTSUPP, -EACCES, -EPERM, -ENOSYS)) goto fallback_fstat; if (r < 0) return r; @@ -549,6 +549,8 @@ int dev_is_devtmpfs(void) { return r; r = fopen_unlocked("/proc/self/mountinfo", "re", &proc_self_mountinfo); + if (r == -ENOENT) + return proc_mounted() > 0 ? -ENOENT : -ENOSYS; if (r < 0) return r; diff --git a/src/basic/os-util.c b/src/basic/os-util.c index dd8faf2376..5d06e20871 100644 --- a/src/basic/os-util.c +++ b/src/basic/os-util.c @@ -155,7 +155,7 @@ int open_os_release(const char *root, char **ret_path, int *ret_fd) { return r; if (ret_path) { - r = path_prefix_root_cwd(p, root, ret_path); + r = chaseat_prefix_root(p, root, ret_path); if (r < 0) return r; } @@ -292,7 +292,7 @@ int open_extension_release( return r; if (ret_path) { - r = path_prefix_root_cwd(p, root, ret_path); + r = chaseat_prefix_root(p, root, ret_path); if (r < 0) return r; } diff --git a/src/basic/path-util.c b/src/basic/path-util.c index fa2e26789f..0b0f0da760 100644 --- a/src/basic/path-util.c +++ b/src/basic/path-util.c @@ -100,34 +100,6 @@ int path_make_absolute_cwd(const char *p, char **ret) { return 0; } -int path_prefix_root_cwd(const char *p, const char *root, char **ret) { - _cleanup_free_ char *root_abs = NULL; - char *c; - int r; - - assert(p); - assert(ret); - - /* Unlike path_make_absolute(), this always prefixes root path if specified. - * The root path is always simplified, but the provided path will not. - * This is useful for prefixing the result of chaseat(). */ - - if (empty_or_root(root)) - return path_make_absolute_cwd(p, ret); - - r = path_make_absolute_cwd(root, &root_abs); - if (r < 0) - return r; - - path_simplify(root_abs); - c = path_join(root_abs, p); - if (!c) - return -ENOMEM; - - *ret = c; - return 0; -} - int path_make_relative(const char *from, const char *to, char **ret) { _cleanup_free_ char *result = NULL; unsigned n_parents; diff --git a/src/basic/path-util.h b/src/basic/path-util.h index a0af9de674..7843599816 100644 --- a/src/basic/path-util.h +++ b/src/basic/path-util.h @@ -60,7 +60,6 @@ int path_split_and_make_absolute(const char *p, char ***ret); char* path_make_absolute(const char *p, const char *prefix); int safe_getcwd(char **ret); int path_make_absolute_cwd(const char *p, char **ret); -int path_prefix_root_cwd(const char *p, const char *root, char **ret); int path_make_relative(const char *from, const char *to, char **ret); int path_make_relative_parent(const char *from_child, const char *to, char **ret); char *path_startswith_full(const char *path, const char *prefix, bool accept_dot_dot) _pure_; diff --git a/src/shared/find-esp.c b/src/shared/find-esp.c index 6a0002a2bd..c4cf508517 100644 --- a/src/shared/find-esp.c +++ b/src/shared/find-esp.c @@ -540,7 +540,7 @@ int find_esp_and_warn( return r; if (ret_path) { - r = path_prefix_root_cwd(p, root, ret_path); + r = chaseat_prefix_root(p, root, ret_path); if (r < 0) return r; } @@ -859,7 +859,7 @@ int find_xbootldr_and_warn( return r; if (ret_path) { - r = path_prefix_root_cwd(p, root, ret_path); + r = chaseat_prefix_root(p, root, ret_path); if (r < 0) return r; } diff --git a/src/test/test-chase.c b/src/test/test-chase.c index 1e98f5c6ed..558f4109e3 100644 --- a/src/test/test-chase.c +++ b/src/test/test-chase.c @@ -442,6 +442,43 @@ TEST(chaseat) { assert_se(streq(result, "/usr")); result = mfree(result); + /* If the file descriptor points to the root directory, the result will be absolute. */ + + fd = open("/", O_CLOEXEC | O_DIRECTORY | O_PATH); + assert_se(fd >= 0); + + assert_se(chaseat(fd, p, 0, &result, NULL) >= 0); + assert_se(streq(result, "/usr")); + result = mfree(result); + + assert_se(chaseat(fd, p, CHASE_AT_RESOLVE_IN_ROOT, &result, NULL) >= 0); + assert_se(streq(result, "/usr")); + result = mfree(result); + + fd = safe_close(fd); + + /* If the file descriptor does not point to the root directory, the result will be relative + * unless the result is outside of the specified file descriptor. */ + + assert_se(chaseat(tfd, "abc", 0, &result, NULL) >= 0); + assert_se(streq(result, "/usr")); + result = mfree(result); + + assert_se(chaseat(tfd, "/abc", 0, &result, NULL) >= 0); + assert_se(streq(result, "/usr")); + result = mfree(result); + + assert_se(chaseat(tfd, "abc", CHASE_AT_RESOLVE_IN_ROOT, NULL, NULL) == -ENOENT); + assert_se(chaseat(tfd, "/abc", CHASE_AT_RESOLVE_IN_ROOT, NULL, NULL) == -ENOENT); + + assert_se(chaseat(tfd, "abc", CHASE_AT_RESOLVE_IN_ROOT | CHASE_NONEXISTENT, &result, NULL) >= 0); + assert_se(streq(result, "usr")); + result = mfree(result); + + assert_se(chaseat(tfd, "/abc", CHASE_AT_RESOLVE_IN_ROOT | CHASE_NONEXISTENT, &result, NULL) >= 0); + assert_se(streq(result, "usr")); + result = mfree(result); + /* Test that absolute path or not are the same when resolving relative to a directory file * descriptor and that we always get a relative path back. */ @@ -611,4 +648,41 @@ static int intro(void) { return EXIT_SUCCESS; } +TEST(chaseat_prefix_root) { + _cleanup_free_ char *cwd = NULL, *ret = NULL, *expected = NULL; + + assert_se(safe_getcwd(&cwd) >= 0); + + assert_se(chaseat_prefix_root("/hoge", NULL, &ret) >= 0); + assert_se(streq(ret, "/hoge")); + + ret = mfree(ret); + + assert_se(chaseat_prefix_root("/hoge", "a/b/c", &ret) >= 0); + assert_se(streq(ret, "/hoge")); + + ret = mfree(ret); + + assert_se(chaseat_prefix_root("hoge", "/a/b//./c///", &ret) >= 0); + assert_se(streq(ret, "/a/b/c/hoge")); + + ret = mfree(ret); + + assert_se(chaseat_prefix_root("hoge", "a/b//./c///", &ret) >= 0); + assert_se(expected = path_join(cwd, "a/b/c/hoge")); + assert_se(streq(ret, expected)); + + ret = mfree(ret); + expected = mfree(expected); + + assert_se(chaseat_prefix_root("./hoge/aaa/../././b", "/a/b//./c///", &ret) >= 0); + assert_se(streq(ret, "/a/b/c/hoge/aaa/../././b")); + + ret = mfree(ret); + + assert_se(chaseat_prefix_root("./hoge/aaa/../././b", "a/b//./c///", &ret) >= 0); + assert_se(expected = path_join(cwd, "a/b/c/hoge/aaa/../././b")); + assert_se(streq(ret, expected)); +} + DEFINE_TEST_MAIN_WITH_INTRO(LOG_INFO, intro); diff --git a/src/test/test-path-util.c b/src/test/test-path-util.c index 22e8f3481a..e40ffea4d5 100644 --- a/src/test/test-path-util.c +++ b/src/test/test-path-util.c @@ -494,45 +494,6 @@ TEST(fsck_exists) { assert_se(fsck_exists_for_fstype("/../bin/") == 0); } -TEST(path_prefix_root_cwd) { - _cleanup_free_ char *cwd = NULL, *ret = NULL, *expected = NULL; - - assert_se(safe_getcwd(&cwd) >= 0); - - assert_se(path_prefix_root_cwd("hoge", NULL, &ret) >= 0); - assert_se(expected = path_join(cwd, "hoge")); - assert_se(streq(ret, expected)); - - ret = mfree(ret); - expected = mfree(expected); - - assert_se(path_prefix_root_cwd("/hoge", NULL, &ret) >= 0); - assert_se(streq(ret, "/hoge")); - - ret = mfree(ret); - - assert_se(path_prefix_root_cwd("hoge", "/a/b//./c///", &ret) >= 0); - assert_se(streq(ret, "/a/b/c/hoge")); - - ret = mfree(ret); - - assert_se(path_prefix_root_cwd("hoge", "a/b//./c///", &ret) >= 0); - assert_se(expected = path_join(cwd, "a/b/c/hoge")); - assert_se(streq(ret, expected)); - - ret = mfree(ret); - expected = mfree(expected); - - assert_se(path_prefix_root_cwd("/../hoge/aaa/../././b", "/a/b//./c///", &ret) >= 0); - assert_se(streq(ret, "/a/b/c/../hoge/aaa/../././b")); - - ret = mfree(ret); - - assert_se(path_prefix_root_cwd("/../hoge/aaa/../././b", "a/b//./c///", &ret) >= 0); - assert_se(expected = path_join(cwd, "a/b/c/../hoge/aaa/../././b")); - assert_se(streq(ret, expected)); -} - static void test_path_make_relative_one(const char *from, const char *to, const char *expected) { _cleanup_free_ char *z = NULL; int r; diff --git a/test/test-bootctl-json.sh b/test/test-bootctl-json.sh index fde5fbd1de..4d7c468241 100755 --- a/test/test-bootctl-json.sh +++ b/test/test-bootctl-json.sh @@ -28,6 +28,15 @@ command -v jq >/dev/null || { "$bootctl" -R || test "$?" -eq 80 "$bootctl" -RR || test "$?" -eq 80 +# regression tests for +# https://github.com/systemd/systemd/pull/27199#issuecomment-1511387731 +if ret=$("$bootctl" --print-esp-path); then + test "$ret" = "/efi" -o "$ret" = "/boot" -o "$ret" = "/boot/efi" +fi +if ret=$("bootctl" --print-boot-path); then + test "$ret" = "/efi" -o "$ret" = "/boot" -o "$ret" = "/boot/efi" +fi + if "$bootctl" -R > /dev/null ; then P=$("$bootctl" -R) PP=$("$bootctl" -RR) |