diff --git a/src/basic/chase.c b/src/basic/chase.c index 5abb4bc430..50ab4e3c47 100644 --- a/src/basic/chase.c +++ b/src/basic/chase.c @@ -1,7 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include -#include #include #include "alloc-util.h" @@ -18,18 +17,59 @@ #include "strv.h" #include "user-util.h" +/* Flags that prevent us from taking any of the early shortcuts: either they change the path resolution + * semantics (e.g. CHASE_NONEXISTENT, CHASE_PARENT, CHASE_STEP) or ask for per-component validation that a + * single open() cannot provide (e.g. CHASE_SAFE, CHASE_NO_AUTOFS, CHASE_PROHIBIT_SYMLINKS). + * + * Notably, the following are *not* listed here: + * - CHASE_TRIGGER_AUTOFS: plain open() already triggers automounts, and O_PATH shortcuts can use + * XO_TRIGGER_AUTOMOUNT to tell xopenat_full() to use open_tree() instead. + * - CHASE_MUST_BE_{DIRECTORY,REGULAR,SOCKET}: xopenat_full() can enforce these via O_DIRECTORY, + * XO_REGULAR and XO_SOCKET. Shortcut callers that don't go through xopenat_full() (stat/access + * paths) must include CHASE_MUST_BE_ANY in their local mask to still bail on these. */ #define CHASE_NO_SHORTCUT_MASK \ (CHASE_NONEXISTENT | \ CHASE_NO_AUTOFS | \ - CHASE_TRIGGER_AUTOFS | \ CHASE_SAFE | \ CHASE_STEP | \ CHASE_PROHIBIT_SYMLINKS | \ CHASE_PARENT | \ - CHASE_MKDIR_0755 | \ - CHASE_MUST_BE_DIRECTORY | \ - CHASE_MUST_BE_REGULAR | \ - CHASE_MUST_BE_SOCKET) + CHASE_MKDIR_0755) + +#define CHASE_MUST_BE_ANY \ + (CHASE_MUST_BE_DIRECTORY|CHASE_MUST_BE_REGULAR|CHASE_MUST_BE_SOCKET) + +static int chase_statx(int fd, struct statx *ret) { + return xstatx_full(fd, + /* path= */ NULL, + /* statx_flags= */ 0, + XSTATX_MNT_ID_BEST, + STATX_TYPE|STATX_UID|STATX_INO, + /* optional_mask= */ 0, + /* mandatory_attributes= */ 0, + ret); +} + +static int chase_xopenat(int dir_fd, const char *path, ChaseFlags chase_flags, int open_flags, XOpenFlags xopen_flags) { + /* Wrapper around xopenat_full() that translates CHASE_NOFOLLOW, CHASE_MUST_BE_* and + * CHASE_TRIGGER_AUTOFS into their xopenat_full() counterparts. Used by shortcuts that want to open + * the final target of a chase operation: they all want O_NOFOLLOW honoured, MUST_BE_* verified on + * the opened inode, and automounts triggered if requested. */ + + if (FLAGS_SET(chase_flags, CHASE_NOFOLLOW)) + open_flags |= O_NOFOLLOW; + if (FLAGS_SET(chase_flags, CHASE_MUST_BE_DIRECTORY)) + open_flags |= O_DIRECTORY; + if (FLAGS_SET(chase_flags, CHASE_MUST_BE_REGULAR)) + xopen_flags |= XO_REGULAR; + if (FLAGS_SET(chase_flags, CHASE_MUST_BE_SOCKET)) + xopen_flags |= XO_SOCKET; + /* Only needed for O_PATH since plain open() already triggers automounts */ + if (FLAGS_SET(chase_flags, CHASE_TRIGGER_AUTOFS) && FLAGS_SET(open_flags, O_PATH)) + xopen_flags |= XO_TRIGGER_AUTOMOUNT; + + return xopenat_full(dir_fd, path, open_flags, xopen_flags, MODE_INVALID); +} static bool uid_unsafe_transition(uid_t a, uid_t b) { /* Returns true if the transition from a to b is safe, i.e. that we never transition from @@ -108,95 +148,40 @@ static int log_prohibited_symlink(int fd, ChaseFlags flags) { strna(n1)); } -static int openat_opath_with_automount(int dir_fd, const char *path, bool automount) { - static bool can_open_tree = true; - int r; - - /* Pin an inode via O_PATH semantics. Sounds pretty obvious to do this, right? You just do open() - * with O_PATH, and there you go. But uh, it's not that easy. open() via O_PATH does not trigger - * automounts, but we may want that when CHASE_TRIGGER_AUTOFS is set. But thankfully there's - * a way out: the newer open_tree() call, when specified without OPEN_TREE_CLONE actually is fully - * equivalent to open() with O_PATH – except for one thing: it triggers automounts. - * - * As it turns out some sandboxes prohibit open_tree(), and return EPERM or ENOSYS if we call it. - * But since autofs does not work inside of mount namespace anyway, let's simply handle this - * as gracefully as we can, and fall back to classic openat() if we see EPERM/ENOSYS. */ - - assert(dir_fd >= 0 || dir_fd == AT_FDCWD); - assert(path); - - if (automount && can_open_tree) { - r = RET_NERRNO(open_tree(dir_fd, path, AT_SYMLINK_NOFOLLOW|OPEN_TREE_CLOEXEC)); - if (r >= 0 || (r != -EPERM && !ERRNO_IS_NEG_NOT_SUPPORTED(r))) - return r; - - can_open_tree = false; - } - - return RET_NERRNO(openat(dir_fd, path, O_PATH|O_NOFOLLOW|O_CLOEXEC)); -} - -static int chaseat_needs_absolute(int dir_fd, const char *path) { - if (dir_fd < 0) - return path_is_absolute(path); - - return dir_fd_is_root(dir_fd); -} - -int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int *ret_fd) { - _cleanup_free_ char *buffer = NULL, *done = NULL; - _cleanup_close_ int fd = -EBADF, root_fd = -EBADF; - bool exists = true, append_trail_slash = false; - struct statx root_stx, stx; - bool need_absolute = false; /* allocate early to avoid compiler warnings around goto */ - const char *todo; - unsigned mask = STATX_TYPE|STATX_UID|STATX_INO; +int chaseat(int root_fd, int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int *ret_fd) { int r; assert(!FLAGS_SET(flags, CHASE_PREFIX_ROOT)); assert(!FLAGS_SET(flags, CHASE_STEP|CHASE_EXTRACT_FILENAME)); assert(!FLAGS_SET(flags, CHASE_NO_AUTOFS|CHASE_TRIGGER_AUTOFS)); assert(dir_fd >= 0 || IN_SET(dir_fd, AT_FDCWD, XAT_FDROOT)); + assert(root_fd >= 0 || IN_SET(root_fd, AT_FDCWD, XAT_FDROOT)); + /* AT_FDCWD for dir_fd is only allowed when there is no chroot boundary: otherwise the current + * working directory might live outside root_fd's subtree. */ + assert(dir_fd != AT_FDCWD || IN_SET(root_fd, AT_FDCWD, XAT_FDROOT)); if (FLAGS_SET(flags, CHASE_STEP)) assert(!ret_fd); - if (isempty(path)) - path = "."; - - /* This function resolves symlinks of the path relative to the given directory file descriptor. If - * CHASE_AT_RESOLVE_IN_ROOT is specified and a directory file descriptor is provided, symlinks - * are resolved relative to the given directory file descriptor. Otherwise, they are resolved - * relative to the root directory of the host. + /* This function resolves symlinks of the path relative to the given directory file descriptor. + * The root directory file descriptor sets the chroot boundary: symlinks may not escape it, and + * absolute symlinks encountered during resolution are resolved relative to it. When the root fd is + * XAT_FDROOT, symlinks are resolved relative to the host's root directory with no containment. * - * Note that when a positive directory file descriptor is provided and CHASE_AT_RESOLVE_IN_ROOT is - * specified and we find an absolute symlink, it is resolved relative to given directory file - * descriptor and not the root of the host. Also, when following relative symlinks, this functions - * ensures they cannot be used to "escape" the given directory file descriptor. If a positive - * directory file descriptor is provided, the "path" parameter is always interpreted relative to the - * 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. + * The given path is always resolved starting at dir_fd, regardless of whether it is absolute or + * relative. The leading slashes of an absolute path are ignored. The only exceptions are + * dir_fd == XAT_FDROOT (which starts resolution at root_fd) and dir_fd == AT_FDCWD with an absolute + * path (which starts resolution at "/" rather than the current working directory). * - * 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 "/". + * Note that we do not verify that dir_fd actually points to a descendant of root_fd. If dir_fd + * lies outside the root_fd subtree, ".." traversal and absolute symlinks may still be clamped to + * root_fd, leading to surprising results. Callers must ensure the relationship themselves. * - * 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 + * Absolute paths returned by this function are relative to the given root file descriptor. Relative + * paths returned by this function are relative to the given directory file descriptor. The result is + * absolute when root_fd is XAT_FDROOT (i.e. there is no chroot boundary, so openat()-like callers + * need an absolute path to reach the host inode), or when an absolute symlink made us jump to a + * different subtree than the one dir_fd points into. Otherwise the result is relative. * * 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 @@ -204,11 +189,6 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int * its special meaning each time. We always keep an O_PATH fd to the component we are currently * processing, thus keeping lookup races to a minimum. * - * Suggested usage: whenever you want to canonicalize a path, use this function. Pass the absolute - * path you got as-is: fully qualified and relative to your host's root. Optionally, specify the - * "dir_fd" parameter to tell this function what to do when encountering a symlink with an absolute - * path as directory: resolve it relative to the given directory file descriptor. - * * There are five ways to invoke this function: * * 1. Without CHASE_STEP or ret_fd: in this case the path is resolved and the normalized path is @@ -239,117 +219,40 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int * the mount point is emitted. CHASE_WARN cannot be used in PID 1. */ - _cleanup_close_ int _dir_fd = -EBADF; + /* We treat AT_FDCWD as XAT_FDROOT for a more seamless migration for all callers of chaseat() before + * it was reworked to support separate root_fd and dir_fd arguments. */ + if (root_fd == AT_FDCWD) + root_fd = XAT_FDROOT; + else { + r = dir_fd_is_root(root_fd); + if (r < 0) + return r; + if (r > 0) + root_fd = XAT_FDROOT; + } + + /* If dir_fd points to the host's root directory and there is no chroot boundary, normalize it + * to XAT_FDROOT so the shortcut path can kick in. */ r = dir_fd_is_root(dir_fd); if (r < 0) return r; - if (r > 0) { - /* Shortcut the common case where no root dir is specified, and no special flags are given to - * a regular open() */ - if (!ret_path && - (flags & (CHASE_STEP|CHASE_NO_AUTOFS|CHASE_NONEXISTENT|CHASE_SAFE|CHASE_WARN|CHASE_PROHIBIT_SYMLINKS|CHASE_PARENT|CHASE_MKDIR_0755)) == 0) { - _cleanup_free_ char *slash_path = NULL; + if (r > 0 && root_fd == XAT_FDROOT) + dir_fd = XAT_FDROOT; - if (!path_is_absolute(path)) { - slash_path = strjoin("/", path); - if (!slash_path) - return -ENOMEM; - } + /* dir_fd == XAT_FDROOT means "start at root_fd". An absolute path is always resolved relative to + * root_fd, regardless of what dir_fd points to. */ + if (dir_fd == XAT_FDROOT || path_is_absolute(path)) + dir_fd = root_fd; - /* We use open_tree() rather than regular open() here, because it gives us direct - * control over automount behaviour, and otherwise is equivalent to open() with - * O_PATH */ - fd = open_tree(-EBADF, slash_path ?: path, OPEN_TREE_CLOEXEC|(FLAGS_SET(flags, CHASE_TRIGGER_AUTOFS) ? 0 : AT_NO_AUTOMOUNT)); - if (fd < 0) - return -errno; + if (isempty(path)) + path = "."; - r = xstatx_full(fd, - /* path= */ NULL, - /* statx_flags= */ 0, - XSTATX_MNT_ID_BEST, - mask, - /* optional_mask= */ 0, - /* mandatory_attributes= */ 0, - &stx); - if (r < 0) - return r; - - exists = true; - goto success; - } - - _dir_fd = open("/", O_DIRECTORY|O_RDONLY|O_CLOEXEC); - if (_dir_fd < 0) - return -errno; - - dir_fd = _dir_fd; - flags &= ~CHASE_AT_RESOLVE_IN_ROOT; - } - - if (!ret_path && ret_fd && (flags & (CHASE_AT_RESOLVE_IN_ROOT|CHASE_NO_SHORTCUT_MASK)) == 0) { - /* Shortcut the ret_fd case if the caller isn't interested in the actual path and has no root - * set and doesn't care about any of the other special features we provide either. */ - r = openat(dir_fd, path, O_PATH|O_CLOEXEC|(FLAGS_SET(flags, CHASE_NOFOLLOW) ? O_NOFOLLOW : 0)); - if (r < 0) - return -errno; - - *ret_fd = r; - return 0; - } - - buffer = strdup(path); - if (!buffer) - 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. Also, let's make - * the result absolute when the file descriptor of the root directory is specified. */ - r = chaseat_needs_absolute(dir_fd, path); - if (r < 0) - return r; - - need_absolute = r; - if (need_absolute) { - done = strdup("/"); - if (!done) - return -ENOMEM; - } - - /* If a positive directory file descriptor is provided, always resolve the given path relative to it, - * regardless of whether it is absolute or not. If we get AT_FDCWD, follow regular openat() - * semantics, if the path is relative, resolve against the current working directory. Otherwise, - * resolve against root. */ - fd = openat(dir_fd, done ?: ".", O_CLOEXEC|O_DIRECTORY|O_PATH); - if (fd < 0) - return -errno; - - r = xstatx_full(fd, - /* path= */ NULL, - /* statx_flags= */ 0, - XSTATX_MNT_ID_BEST, - mask, - /* optional_mask= */ 0, - /* mandatory_attributes= */ 0, - &stx); - if (r < 0) - return r; - root_stx = stx; /* remember stat data of the root, so that we can recognize it later */ - - /* If we get AT_FDCWD, we always resolve symlinks relative to the host's root. Only if a positive - * directory file descriptor is provided we will look at CHASE_AT_RESOLVE_IN_ROOT to determine - * whether to resolve symlinks in it or not. */ - if (dir_fd >= 0 && FLAGS_SET(flags, CHASE_AT_RESOLVE_IN_ROOT)) - root_fd = openat(dir_fd, ".", O_CLOEXEC|O_DIRECTORY|O_PATH); - else - root_fd = open("/", O_CLOEXEC|O_DIRECTORY|O_PATH); - if (root_fd < 0) - return -errno; - - if (ENDSWITH_SET(buffer, "/", "/.")) { + bool append_trail_slash = false; + if (ENDSWITH_SET(path, "/", "/.")) { flags |= CHASE_MUST_BE_DIRECTORY; if (FLAGS_SET(flags, CHASE_TRAIL_SLASH)) append_trail_slash = true; - } else if (dot_or_dot_dot(buffer) || endswith(buffer, "/..")) + } else if (dot_or_dot_dot(path) || endswith(path, "/..")) flags |= CHASE_MUST_BE_DIRECTORY; if (FLAGS_SET(flags, CHASE_PARENT)) @@ -359,7 +262,73 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int if (FLAGS_SET(flags, CHASE_MUST_BE_DIRECTORY) + FLAGS_SET(flags, CHASE_MUST_BE_REGULAR) + FLAGS_SET(flags, CHASE_MUST_BE_SOCKET) > 1) return -EBADSLT; - todo = buffer; + if (root_fd == XAT_FDROOT && !ret_path && (flags & CHASE_NO_SHORTCUT_MASK) == 0) { + /* Shortcut the common case where we don't have a real root boundary and no fancy features + * are requested: open the target directly via xopenat_full() which applies any MUST_BE_* + * verification and automount triggering for us. */ + + r = chase_xopenat(dir_fd, path, flags, O_PATH|O_CLOEXEC, /* xopen_flags= */ 0); + if (r < 0) + return r; + + if (ret_fd) + *ret_fd = r; + else + safe_close(r); + + return 1; + } + + /* Decide whether to return an absolute or relative path. + * + * We return an absolute path only when there is no chroot boundary (root_fd == XAT_FDROOT) + * and resolution starts from root — i.e. either dir_fd was XAT_FDROOT or path is absolute, + * both of which caused dir_fd = root_fd above. In every other case we return a relative + * path so the result keeps working when fed to an openat()-style call against dir_fd, + * which would ignore dir_fd if handed an absolute path. + * + * When root_fd != XAT_FDROOT and an absolute symlink later causes resolution to escape + * dir_fd, the loop below rebases onto root_fd and switches to an absolute result at that + * point — it is not handled here. + */ + bool need_absolute = (root_fd == XAT_FDROOT || dir_fd != root_fd) && (dir_fd == XAT_FDROOT || path_is_absolute(path)); + + _cleanup_free_ char *done = NULL; + if (need_absolute) { + done = strdup("/"); + if (!done) + return -ENOMEM; + } + + _cleanup_close_ int fd = xopenat(dir_fd, NULL, O_CLOEXEC|O_DIRECTORY|O_PATH); + if (fd < 0) + return fd; + + struct statx stx; + r = chase_statx(fd, &stx); + if (r < 0) + return r; + + /* Remember stat data of the root, so that we can recognize it later during .. handling. Only + * needed when there is an actual chroot boundary — with root_fd == XAT_FDROOT the boundary + * check in the .. loop below is skipped and root_stx is never consulted. */ + struct statx root_stx; + if (root_fd != XAT_FDROOT) { + if (root_fd == dir_fd) + root_stx = stx; + else { + r = chase_statx(root_fd, &root_stx); + if (r < 0) + return r; + } + } + + _cleanup_free_ char *buffer = strdup(path); + if (!buffer) + return -ENOMEM; + + const char *todo = buffer; + bool exists = true; for (unsigned n_steps = 0;; n_steps++) { _cleanup_free_ char *first = NULL; _cleanup_close_ int child = -EBADF; @@ -392,9 +361,13 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int * inode/mount identity check. The latter is load-bearing if concurrent access of the * root tree we operate in is allowed, where an inode is moved up the tree while we * look at it, and thus get the current path wrong and think we are deeper down than - * we actually are. */ - if (FLAGS_SET(flags, CHASE_AT_RESOLVE_IN_ROOT)) { - bool is_root = empty_or_root(done); + * we actually are. + * + * The path-based fast path is only valid when the caller started at the root fd: + * otherwise 'done' being empty just means we haven't descended past the starting + * dir_fd, not that we're at the chroot boundary. */ + if (root_fd != XAT_FDROOT) { + bool is_root = root_fd == dir_fd && empty_or_root(done); if (!is_root && statx_inode_same(&stx, &root_stx)) { r = statx_mount_same(&stx, &root_stx); if (r < 0) @@ -413,14 +386,7 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int if (fd_parent < 0) return -errno; - r = xstatx_full(fd_parent, - /* path= */ NULL, - /* statx_flags= */ 0, - XSTATX_MNT_ID_BEST, - mask, - /* optional_mask= */ 0, - /* mandatory_attributes= */ 0, - &stx_parent); + r = chase_statx(fd_parent, &stx_parent); if (r < 0) return r; @@ -447,18 +413,19 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int assert(!need_absolute); done = mfree(done); } else if (r == -EADDRNOTAVAIL) { - /* 'done' is "/". This branch should be already handled in the above. */ - assert(!FLAGS_SET(flags, CHASE_AT_RESOLVE_IN_ROOT)); + /* 'done' is "/". This branch should already be handled above via the + * is_root check. */ assert_not_reached(); } else if (r == -EINVAL) { - /* 'done' is an empty string, ends with '..', or an invalid path. */ + /* 'done' is empty (we haven't descended past the starting dir_fd yet), or + * ends with '..'. In both cases we're traversing above the starting point + * (valid when root_fd is XAT_FDROOT, or when dir_fd was below root_fd to + * start with), so record another '..' in 'done'. */ assert(!need_absolute); - assert(!FLAGS_SET(flags, CHASE_AT_RESOLVE_IN_ROOT)); - if (!path_is_valid(done)) + if (!isempty(done) && !path_is_valid(done)) return -EINVAL; - /* If we're at the top of "dir_fd", start appending ".." to "done". */ if (!path_extend(&done, "..")) return -ENOMEM; } else @@ -486,14 +453,7 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int if (fd_grandparent < 0) return -errno; - r = xstatx_full(fd_grandparent, - /* path= */ NULL, - /* statx_flags= */ 0, - XSTATX_MNT_ID_BEST, - mask, - /* optional_mask= */ 0, - /* mandatory_attributes= */ 0, - &stx_grandparent); + r = chase_statx(fd_grandparent, &stx_grandparent); if (r < 0) return r; @@ -517,7 +477,10 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int } /* Otherwise let's pin it by file descriptor, via O_PATH. */ - child = r = openat_opath_with_automount(fd, first, /* automount= */ FLAGS_SET(flags, CHASE_TRIGGER_AUTOFS)); + child = r = xopenat_full(fd, first, + O_PATH|O_NOFOLLOW|O_CLOEXEC, + FLAGS_SET(flags, CHASE_TRIGGER_AUTOFS) ? XO_TRIGGER_AUTOMOUNT : 0, + MODE_INVALID); if (r < 0) { if (r != -ENOENT) return r; @@ -546,15 +509,7 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int return r; } - /* ... and then check what it actually is. */ - r = xstatx_full(child, - /* path= */ NULL, - /* statx_flags= */ 0, - XSTATX_MNT_ID_BEST, - mask, - /* optional_mask= */ 0, - /* mandatory_attributes= */ 0, - &stx_child); + r = chase_statx(child, &stx_child); if (r < 0) return r; @@ -592,14 +547,7 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int if (fd < 0) return fd; - r = xstatx_full(fd, - /* path= */ NULL, - /* statx_flags= */ 0, - XSTATX_MNT_ID_BEST, - mask, - /* optional_mask= */ 0, - /* mandatory_attributes= */ 0, - &stx); + r = chase_statx(fd, &stx); if (r < 0) return r; @@ -611,9 +559,7 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int 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)) + if (dir_fd != root_fd) need_absolute = true; r = free_and_strdup(&done, need_absolute ? "/" : NULL); @@ -647,7 +593,6 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int close_and_replace(fd, child); } -success: if (exists) { if (FLAGS_SET(flags, CHASE_MUST_BE_DIRECTORY)) { r = statx_verify_directory(&stx); @@ -755,14 +700,9 @@ int chase(const char *path, const char *root, ChaseFlags flags, char **ret_path, return r; /* A root directory of "/" or "" is identical to "/". */ - if (empty_or_root(root)) { + if (empty_or_root(root)) root = "/"; - - /* When the root directory is "/", we will drop CHASE_AT_RESOLVE_IN_ROOT in chaseat(), - * hence below is not necessary, but let's shortcut. */ - flags &= ~CHASE_AT_RESOLVE_IN_ROOT; - - } else { + else { r = path_make_absolute_cwd(root, &root_abs); if (r < 0) return r; @@ -779,8 +719,6 @@ int chase(const char *path, const char *root, ChaseFlags flags, char **ret_path, if (!absolute) return -ENOMEM; } - - flags |= CHASE_AT_RESOLVE_IN_ROOT; } if (!absolute) { @@ -804,7 +742,7 @@ int chase(const char *path, const char *root, ChaseFlags flags, char **ret_path, return -errno; } - r = chaseat(fd, path, flags & ~CHASE_PREFIX_ROOT, ret_path ? &p : NULL, ret_fd ? &pfd : NULL); + r = chaseat(fd, fd, path, flags & ~CHASE_PREFIX_ROOT, ret_path ? &p : NULL, ret_fd ? &pfd : NULL); if (r < 0) return r; @@ -927,36 +865,34 @@ int chase_and_open( _cleanup_close_ int path_fd = -EBADF; _cleanup_free_ char *p = NULL, *fname = NULL; + const char *open_name = NULL; int r; assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP))); - XOpenFlags xopen_flags = 0; - if (FLAGS_SET(chase_flags, CHASE_MUST_BE_DIRECTORY)) - open_flags |= O_DIRECTORY; - if (FLAGS_SET(chase_flags, CHASE_MUST_BE_REGULAR)) - xopen_flags |= XO_REGULAR; - if (empty_or_root(root) && !ret_path && (chase_flags & CHASE_NO_SHORTCUT_MASK) == 0) /* Shortcut this call if none of the special features of this call are requested */ - return xopenat_full(AT_FDCWD, path, - open_flags | (FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? O_NOFOLLOW : 0), - xopen_flags, - MODE_INVALID); + return chase_xopenat(AT_FDCWD, path, chase_flags, open_flags, /* xopen_flags= */ 0); r = chase(path, root, (CHASE_PARENT|chase_flags)&~CHASE_MUST_BE_REGULAR, &p, &path_fd); if (r < 0) return r; assert(path_fd >= 0); - if (!FLAGS_SET(chase_flags, CHASE_PARENT) && - !FLAGS_SET(chase_flags, CHASE_EXTRACT_FILENAME)) { - r = chase_extract_filename(p, root, &fname); - if (r < 0) - return r; + if (!FLAGS_SET(chase_flags, CHASE_PARENT)) { + if (FLAGS_SET(chase_flags, CHASE_EXTRACT_FILENAME)) + /* chase() with CHASE_EXTRACT_FILENAME already returns just the filename in + * p — use it directly without redundant extraction. */ + open_name = p; + else { + r = chase_extract_filename(p, root, &fname); + if (r < 0) + return r; + open_name = fname; + } } - r = xopenat_full(path_fd, strempty(fname), open_flags|O_NOFOLLOW, xopen_flags, MODE_INVALID); + r = chase_xopenat(path_fd, strempty(open_name), chase_flags, open_flags|O_NOFOLLOW, /* xopen_flags= */ 0); if (r < 0) return r; @@ -1010,8 +946,10 @@ int chase_and_stat(const char *path, const char *root, ChaseFlags chase_flags, c assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP))); assert(ret_stat); - if (empty_or_root(root) && !ret_path && (chase_flags & CHASE_NO_SHORTCUT_MASK) == 0) - /* Shortcut this call if none of the special features of this call are requested */ + if (empty_or_root(root) && !ret_path && (chase_flags & (CHASE_NO_SHORTCUT_MASK|CHASE_MUST_BE_ANY)) == 0) + /* Shortcut this call if none of the special features of this call are requested. We can't + * take the shortcut if CHASE_MUST_BE_* is set because fstatat() alone does not verify the + * inode type. */ return RET_NERRNO(fstatat(AT_FDCWD, path, ret_stat, FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? AT_SYMLINK_NOFOLLOW : 0)); @@ -1037,8 +975,8 @@ int chase_and_access(const char *path, const char *root, ChaseFlags chase_flags, assert(path); assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP))); - if (empty_or_root(root) && !ret_path && (chase_flags & CHASE_NO_SHORTCUT_MASK) == 0) - /* Shortcut this call if none of the special features of this call are requested */ + if (empty_or_root(root) && !ret_path && (chase_flags & (CHASE_NO_SHORTCUT_MASK|CHASE_MUST_BE_ANY)) == 0) + /* Shortcut this call if none of the special features of this call are requested. */ return RET_NERRNO(faccessat(AT_FDCWD, path, access_mode, FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? AT_SYMLINK_NOFOLLOW : 0)); @@ -1130,6 +1068,7 @@ int chase_and_open_parent(const char *path, const char *root, ChaseFlags chase_f } int chase_and_openat( + int root_fd, int dir_fd, const char *path, ChaseFlags chase_flags, @@ -1138,39 +1077,33 @@ int chase_and_openat( _cleanup_close_ int path_fd = -EBADF; _cleanup_free_ char *p = NULL, *fname = NULL; + const char *open_name = NULL; int r; assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP|CHASE_MUST_BE_SOCKET))); - XOpenFlags xopen_flags = 0; - if (FLAGS_SET(chase_flags, CHASE_MUST_BE_DIRECTORY)) - open_flags |= O_DIRECTORY; - if (FLAGS_SET(chase_flags, CHASE_MUST_BE_REGULAR)) - xopen_flags |= XO_REGULAR; - - if (dir_fd == AT_FDCWD && !ret_path && (chase_flags & CHASE_NO_SHORTCUT_MASK) == 0) + if (root_fd == XAT_FDROOT && dir_fd == AT_FDCWD && !ret_path && (chase_flags & CHASE_NO_SHORTCUT_MASK) == 0) /* Shortcut this call if none of the special features of this call are requested */ - return xopenat_full(dir_fd, path, - open_flags | (FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? O_NOFOLLOW : 0), - xopen_flags, - MODE_INVALID); + return chase_xopenat(dir_fd, path, chase_flags, open_flags, /* xopen_flags= */ 0); - r = chaseat(dir_fd, path, (chase_flags|CHASE_PARENT)&~CHASE_MUST_BE_REGULAR, &p, &path_fd); + r = chaseat(root_fd, dir_fd, path, (chase_flags|CHASE_PARENT)&~CHASE_MUST_BE_REGULAR, &p, &path_fd); if (r < 0) return r; if (!FLAGS_SET(chase_flags, CHASE_PARENT)) { - r = path_extract_filename(p, &fname); - if (r < 0 && r != -EADDRNOTAVAIL) - return r; + if (FLAGS_SET(chase_flags, CHASE_EXTRACT_FILENAME)) + /* chaseat() with CHASE_EXTRACT_FILENAME already returns just the filename in + * p — use it directly without redundant extraction. */ + open_name = p; + else { + r = path_extract_filename(p, &fname); + if (r < 0 && r != -EADDRNOTAVAIL) + return r; + open_name = fname; + } } - r = xopenat_full( - path_fd, - strempty(fname), - open_flags|O_NOFOLLOW, - xopen_flags, - MODE_INVALID); + r = chase_xopenat(path_fd, strempty(open_name), chase_flags, open_flags|O_NOFOLLOW, /* xopen_flags= */ 0); if (r < 0) return r; @@ -1180,7 +1113,7 @@ int chase_and_openat( return r; } -int chase_and_opendirat(int dir_fd, const char *path, ChaseFlags chase_flags, char **ret_path, DIR **ret_dir) { +int chase_and_opendirat(int root_fd, int dir_fd, const char *path, ChaseFlags chase_flags, char **ret_path, DIR **ret_dir) { _cleanup_close_ int path_fd = -EBADF; _cleanup_free_ char *p = NULL; DIR *d; @@ -1189,7 +1122,7 @@ int chase_and_opendirat(int dir_fd, const char *path, ChaseFlags chase_flags, ch assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP|CHASE_MUST_BE_REGULAR|CHASE_MUST_BE_SOCKET))); assert(ret_dir); - if (dir_fd == AT_FDCWD && !ret_path && (chase_flags & CHASE_NO_SHORTCUT_MASK) == 0) { + if (root_fd == XAT_FDROOT && dir_fd == AT_FDCWD && !ret_path && (chase_flags & CHASE_NO_SHORTCUT_MASK) == 0) { /* Shortcut this call if none of the special features of this call are requested */ d = opendir(path); if (!d) @@ -1199,7 +1132,7 @@ int chase_and_opendirat(int dir_fd, const char *path, ChaseFlags chase_flags, ch return 0; } - r = chaseat(dir_fd, path, chase_flags, ret_path ? &p : NULL, &path_fd); + r = chaseat(root_fd, dir_fd, path, chase_flags, ret_path ? &p : NULL, &path_fd); if (r < 0) return r; assert(path_fd >= 0); @@ -1215,7 +1148,7 @@ int chase_and_opendirat(int dir_fd, const char *path, ChaseFlags chase_flags, ch return 0; } -int chase_and_statat(int dir_fd, const char *path, ChaseFlags chase_flags, char **ret_path, struct stat *ret_stat) { +int chase_and_statat(int root_fd, int dir_fd, const char *path, ChaseFlags chase_flags, char **ret_path, struct stat *ret_stat) { _cleanup_close_ int path_fd = -EBADF; _cleanup_free_ char *p = NULL; int r; @@ -1224,12 +1157,14 @@ int chase_and_statat(int dir_fd, const char *path, ChaseFlags chase_flags, char assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP))); assert(ret_stat); - if (dir_fd == AT_FDCWD && !ret_path && (chase_flags & CHASE_NO_SHORTCUT_MASK) == 0) - /* Shortcut this call if none of the special features of this call are requested */ + if (root_fd == XAT_FDROOT && dir_fd == AT_FDCWD && !ret_path && (chase_flags & (CHASE_NO_SHORTCUT_MASK|CHASE_MUST_BE_ANY)) == 0) + /* Shortcut this call if none of the special features of this call are requested. We can't + * take the shortcut if CHASE_MUST_BE_* is set because fstatat() alone does not verify the + * inode type. */ return RET_NERRNO(fstatat(AT_FDCWD, path, ret_stat, FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? AT_SYMLINK_NOFOLLOW : 0)); - r = chaseat(dir_fd, path, chase_flags, ret_path ? &p : NULL, &path_fd); + r = chaseat(root_fd, dir_fd, path, chase_flags, ret_path ? &p : NULL, &path_fd); if (r < 0) return r; assert(path_fd >= 0); @@ -1243,7 +1178,7 @@ int chase_and_statat(int dir_fd, const char *path, ChaseFlags chase_flags, char return 0; } -int chase_and_accessat(int dir_fd, const char *path, ChaseFlags chase_flags, int access_mode, char **ret_path) { +int chase_and_accessat(int root_fd, int dir_fd, const char *path, ChaseFlags chase_flags, int access_mode, char **ret_path) { _cleanup_close_ int path_fd = -EBADF; _cleanup_free_ char *p = NULL; int r; @@ -1251,12 +1186,12 @@ int chase_and_accessat(int dir_fd, const char *path, ChaseFlags chase_flags, int assert(path); assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP))); - if (dir_fd == AT_FDCWD && !ret_path && (chase_flags & CHASE_NO_SHORTCUT_MASK) == 0) - /* Shortcut this call if none of the special features of this call are requested */ + if (root_fd == XAT_FDROOT && dir_fd == AT_FDCWD && !ret_path && (chase_flags & (CHASE_NO_SHORTCUT_MASK|CHASE_MUST_BE_ANY)) == 0) + /* Shortcut this call if none of the special features of this call are requested. */ return RET_NERRNO(faccessat(AT_FDCWD, path, access_mode, FLAGS_SET(chase_flags, CHASE_NOFOLLOW) ? AT_SYMLINK_NOFOLLOW : 0)); - r = chaseat(dir_fd, path, chase_flags, ret_path ? &p : NULL, &path_fd); + r = chaseat(root_fd, dir_fd, path, chase_flags, ret_path ? &p : NULL, &path_fd); if (r < 0) return r; assert(path_fd >= 0); @@ -1272,6 +1207,7 @@ int chase_and_accessat(int dir_fd, const char *path, ChaseFlags chase_flags, int } int chase_and_fopenat_unlocked( + int root_fd, int dir_fd, const char *path, ChaseFlags chase_flags, @@ -1292,7 +1228,7 @@ int chase_and_fopenat_unlocked( if (mode_flags < 0) return mode_flags; - fd = chase_and_openat(dir_fd, path, chase_flags, mode_flags, ret_path ? &final_path : NULL); + fd = chase_and_openat(root_fd, dir_fd, path, chase_flags, mode_flags, ret_path ? &final_path : NULL); if (fd < 0) return fd; @@ -1306,7 +1242,7 @@ int chase_and_fopenat_unlocked( return 0; } -int chase_and_unlinkat(int dir_fd, const char *path, ChaseFlags chase_flags, int unlink_flags, char **ret_path) { +int chase_and_unlinkat(int root_fd, int dir_fd, const char *path, ChaseFlags chase_flags, int unlink_flags, char **ret_path) { _cleanup_free_ char *p = NULL, *fname = NULL; _cleanup_close_ int fd = -EBADF; int r; @@ -1314,7 +1250,7 @@ int chase_and_unlinkat(int dir_fd, const char *path, ChaseFlags chase_flags, int assert(path); assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP|CHASE_PARENT|CHASE_MUST_BE_SOCKET|CHASE_MUST_BE_REGULAR|CHASE_MUST_BE_DIRECTORY|CHASE_EXTRACT_FILENAME|CHASE_MKDIR_0755))); - fd = chase_and_openat(dir_fd, path, chase_flags|CHASE_PARENT|CHASE_NOFOLLOW, O_PATH|O_DIRECTORY|O_CLOEXEC, &p); + fd = chase_and_openat(root_fd, dir_fd, path, chase_flags|CHASE_PARENT|CHASE_NOFOLLOW, O_PATH|O_DIRECTORY|O_CLOEXEC, &p); if (fd < 0) return fd; @@ -1331,12 +1267,12 @@ int chase_and_unlinkat(int dir_fd, const char *path, ChaseFlags chase_flags, int return 0; } -int chase_and_open_parent_at(int dir_fd, const char *path, ChaseFlags chase_flags, char **ret_filename) { +int chase_and_open_parent_at(int root_fd, int dir_fd, const char *path, ChaseFlags chase_flags, char **ret_filename) { int pfd, r; assert(!(chase_flags & (CHASE_NONEXISTENT|CHASE_STEP))); - r = chaseat(dir_fd, path, CHASE_PARENT|CHASE_EXTRACT_FILENAME|chase_flags, ret_filename, &pfd); + r = chaseat(root_fd, dir_fd, path, CHASE_PARENT|CHASE_EXTRACT_FILENAME|chase_flags, ret_filename, &pfd); if (r < 0) return r; diff --git a/src/basic/chase.h b/src/basic/chase.h index d779658ba1..afb50fa61e 100644 --- a/src/basic/chase.h +++ b/src/basic/chase.h @@ -15,21 +15,19 @@ typedef enum ChaseFlags { * right-most component refers to symlink, return O_PATH fd of the symlink. */ CHASE_WARN = 1 << 8, /* Emit an appropriate warning when an error is encountered. * Note: this may do an NSS lookup, hence this flag cannot be used in PID 1. */ - CHASE_AT_RESOLVE_IN_ROOT = 1 << 9, /* Same as openat2()'s RESOLVE_IN_ROOT flag, symlinks are resolved - * relative to the given directory fd instead of root. */ - CHASE_PROHIBIT_SYMLINKS = 1 << 10, /* Refuse all symlinks */ - CHASE_PARENT = 1 << 11, /* Chase the parent directory of the given path. Note that the + CHASE_PROHIBIT_SYMLINKS = 1 << 9, /* Refuse all symlinks */ + CHASE_PARENT = 1 << 10, /* Chase the parent directory of the given path. Note that the * full path is still stored in ret_path and only the returned * file descriptor will point to the parent directory. Note that * the result path is the root or '.', then the file descriptor * also points to the result path even if this flag is set. * When this specified, chase() will succeed with 1 even if the * file points to the last path component does not exist. */ - CHASE_MKDIR_0755 = 1 << 12, /* Create any missing directories in the given path. */ - CHASE_EXTRACT_FILENAME = 1 << 13, /* Only return the last component of the resolved path */ - CHASE_MUST_BE_DIRECTORY = 1 << 14, /* Fail if returned inode fd is not a dir */ - CHASE_MUST_BE_REGULAR = 1 << 15, /* Fail if returned inode fd is not a regular file */ - CHASE_MUST_BE_SOCKET = 1 << 16, /* Fail if returned inode fd is not a socket */ + CHASE_MKDIR_0755 = 1 << 11, /* Create any missing directories in the given path. */ + CHASE_EXTRACT_FILENAME = 1 << 12, /* Only return the last component of the resolved path */ + CHASE_MUST_BE_DIRECTORY = 1 << 13, /* Fail if returned inode fd is not a dir */ + CHASE_MUST_BE_REGULAR = 1 << 14, /* Fail if returned inode fd is not a regular file */ + CHASE_MUST_BE_SOCKET = 1 << 15, /* Fail if returned inode fd is not a socket */ } ChaseFlags; int statx_unsafe_transition(const struct statx *a, const struct statx *b); @@ -51,12 +49,12 @@ int chase_and_fopen_unlocked(const char *path, const char *root, ChaseFlags chas int chase_and_unlink(const char *path, const char *root, ChaseFlags chase_flags, int unlink_flags, char **ret_path); int chase_and_open_parent(const char *path, const char *root, ChaseFlags chase_flags, char **ret_filename); -int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int *ret_fd); +int chaseat(int root_fd, int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int *ret_fd); -int chase_and_openat(int dir_fd, const char *path, ChaseFlags chase_flags, int open_flags, char **ret_path); -int chase_and_opendirat(int dir_fd, const char *path, ChaseFlags chase_flags, char **ret_path, DIR **ret_dir); -int chase_and_statat(int dir_fd, const char *path, ChaseFlags chase_flags, char **ret_path, struct stat *ret_stat); -int chase_and_accessat(int dir_fd, const char *path, ChaseFlags chase_flags, int access_mode, char **ret_path); -int chase_and_fopenat_unlocked(int dir_fd, const char *path, ChaseFlags chase_flags, const char *open_flags, char **ret_path, FILE **ret_file); -int chase_and_unlinkat(int dir_fd, const char *path, ChaseFlags chase_flags, int unlink_flags, char **ret_path); -int chase_and_open_parent_at(int dir_fd, const char *path, ChaseFlags chase_flags, char **ret_filename); +int chase_and_openat(int root_fd, int dir_fd, const char *path, ChaseFlags chase_flags, int open_flags, char **ret_path); +int chase_and_opendirat(int root_fd, int dir_fd, const char *path, ChaseFlags chase_flags, char **ret_path, DIR **ret_dir); +int chase_and_statat(int root_fd, int dir_fd, const char *path, ChaseFlags chase_flags, char **ret_path, struct stat *ret_stat); +int chase_and_accessat(int root_fd, int dir_fd, const char *path, ChaseFlags chase_flags, int access_mode, char **ret_path); +int chase_and_fopenat_unlocked(int root_fd, int dir_fd, const char *path, ChaseFlags chase_flags, const char *open_flags, char **ret_path, FILE **ret_file); +int chase_and_unlinkat(int root_fd, int dir_fd, const char *path, ChaseFlags chase_flags, int unlink_flags, char **ret_path); +int chase_and_open_parent_at(int root_fd, int dir_fd, const char *path, ChaseFlags chase_flags, char **ret_filename); diff --git a/src/basic/conf-files.c b/src/basic/conf-files.c index 4db486d8ad..d4ff194d4c 100644 --- a/src/basic/conf-files.c +++ b/src/basic/conf-files.c @@ -128,7 +128,7 @@ static bool conf_files_need_stat(ConfFilesFlags flags) { } static ChaseFlags conf_files_chase_flags(ConfFilesFlags flags) { - ChaseFlags chase_flags = CHASE_AT_RESOLVE_IN_ROOT; + ChaseFlags chase_flags = 0; if (!conf_files_need_stat(flags) || FLAGS_SET(flags, CONF_FILES_FILTER_MASKED_BY_SYMLINK)) /* Even if no verification is requested, let's unconditionally call chaseat(), @@ -164,7 +164,7 @@ static int conf_file_chase_and_verify( root = empty_to_root(root); - r = chaseat(rfd, path, conf_files_chase_flags(flags), &resolved_path, &fd); + r = chaseat(rfd, rfd, path, conf_files_chase_flags(flags), &resolved_path, &fd); if (r < 0) return log_full_errno(log_level, r, "Failed to chase '%s%s': %m", root, skip_leading_slash(original_path)); @@ -306,7 +306,7 @@ int conf_file_new_at( if (r < 0 && r != -EDESTADDRREQ) return log_full_errno(log_level, r, "Failed to extract directory from '%s': %m", path); if (r >= 0) { - r = chaseat(rfd, dirpath, + r = chaseat(rfd, rfd, dirpath, CHASE_MUST_BE_DIRECTORY | conf_files_chase_flags(flags), &resolved_dirpath, /* ret_fd= */ NULL); if (r < 0) @@ -637,7 +637,7 @@ static int conf_files_list_impl( _cleanup_closedir_ DIR *dir = NULL; _cleanup_free_ char *path = NULL; - r = chase_and_opendirat(rfd, *p, CHASE_AT_RESOLVE_IN_ROOT, &path, &dir); + r = chase_and_opendirat(rfd, rfd, *p, 0, &path, &dir); if (r < 0) { if (r != -ENOENT) log_full_errno(conf_files_log_level(flags), r, diff --git a/src/basic/fileio.c b/src/basic/fileio.c index 2dfa37f20b..661667a6b2 100644 --- a/src/basic/fileio.c +++ b/src/basic/fileio.c @@ -1684,7 +1684,8 @@ int write_data_file_atomic_at( _cleanup_close_ int mfd = -EBADF; if (dn) { /* If there's a directory component, readjust our position */ - r = chaseat(dir_fd, + r = chaseat(XAT_FDROOT, + dir_fd, dn, FLAGS_SET(flags, WRITE_DATA_FILE_MKDIR_0755) ? CHASE_MKDIR_0755 : 0, /* ret_path= */ NULL, diff --git a/src/basic/mkdir.c b/src/basic/mkdir.c index 837880baa2..7b353542e3 100644 --- a/src/basic/mkdir.c +++ b/src/basic/mkdir.c @@ -46,7 +46,7 @@ int mkdirat_safe_internal( if ((flags & MKDIR_FOLLOW_SYMLINK) && S_ISLNK(st.st_mode)) { _cleanup_free_ char *p = NULL; - r = chaseat(dir_fd, path, CHASE_NONEXISTENT, &p, NULL); + r = chaseat(XAT_FDROOT, dir_fd, path, CHASE_NONEXISTENT, &p, NULL); if (r < 0) return r; if (r == 0) diff --git a/src/basic/os-util.c b/src/basic/os-util.c index 66bab1bcee..06b476f134 100644 --- a/src/basic/os-util.c +++ b/src/basic/os-util.c @@ -172,10 +172,10 @@ int open_os_release_at(int rfd, char **ret_path, int *ret_fd) { e = secure_getenv("SYSTEMD_OS_RELEASE"); if (e) - return chaseat(rfd, e, CHASE_AT_RESOLVE_IN_ROOT, ret_path, ret_fd); + return chaseat(rfd, rfd, e, /* flags= */ 0, ret_path, ret_fd); FOREACH_STRING(path, "/etc/os-release", "/usr/lib/os-release") { - r = chaseat(rfd, path, CHASE_AT_RESOLVE_IN_ROOT, ret_path, ret_fd); + r = chaseat(rfd, rfd, path, /* flags= */ 0, ret_path, ret_fd); if (r != -ENOENT) return r; } @@ -238,7 +238,7 @@ int open_extension_release_at( return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "The extension name %s is invalid.", extension); p = strjoina(image_class_release_info[image_class].release_file_path_prefix, extension); - r = chaseat(rfd, p, CHASE_AT_RESOLVE_IN_ROOT, ret_path, ret_fd); + r = chaseat(rfd, rfd, p, /* flags= */ 0, ret_path, ret_fd); log_full_errno_zerook(LOG_DEBUG, MIN(r, 0), "Checking for %s: %m", p); if (r != -ENOENT) return r; @@ -249,7 +249,7 @@ int open_extension_release_at( * xattr is checked to ensure the author of the image considers it OK if names do not match. */ p = image_class_release_info[image_class].release_file_directory; - r = chase_and_opendirat(rfd, p, CHASE_AT_RESOLVE_IN_ROOT, &dir_path, &dir); + r = chase_and_opendirat(rfd, rfd, p, /* chase_flags= */ 0, &dir_path, &dir); if (r < 0) return log_debug_errno(r, "Cannot open %s, ignoring: %m", p); diff --git a/src/basic/user-util.c b/src/basic/user-util.c index 93a3852879..b735d27474 100644 --- a/src/basic/user-util.c +++ b/src/basic/user-util.c @@ -185,7 +185,7 @@ const char* default_root_shell_at(int rfd) { assert(rfd >= 0 || rfd == AT_FDCWD); - int r = chaseat(rfd, DEFAULT_USER_SHELL, CHASE_AT_RESOLVE_IN_ROOT, NULL, NULL); + int r = chaseat(rfd, rfd, DEFAULT_USER_SHELL, /* flags= */ 0, NULL, NULL); if (r < 0 && r != -ENOENT) log_debug_errno(r, "Failed to look up shell '%s': %m", DEFAULT_USER_SHELL); if (r > 0) diff --git a/src/bootctl/bootctl-install.c b/src/bootctl/bootctl-install.c index 96bc9213cf..a8ac742b9a 100644 --- a/src/bootctl/bootctl-install.c +++ b/src/bootctl/bootctl-install.c @@ -276,9 +276,10 @@ static int load_etc_machine_info(InstallContext *c) { _cleanup_close_ int fd = chase_and_openat( + c->root_fd, c->root_fd, "/etc/machine-info", - CHASE_AT_RESOLVE_IN_ROOT|CHASE_MUST_BE_REGULAR, + CHASE_MUST_BE_REGULAR, O_RDONLY|O_CLOEXEC, /* ret_path= */ NULL); if (fd == -ENOENT) @@ -392,8 +393,9 @@ static int settle_make_entry_directory(InstallContext *c) { _cleanup_close_ int fd = -EBADF; r = chaseat(c->root_fd, + c->root_fd, "/etc/machine-id", - CHASE_AT_RESOLVE_IN_ROOT|CHASE_MUST_BE_REGULAR, + CHASE_MUST_BE_REGULAR, /* ret_path= */ NULL, &fd); if (r < 0) @@ -546,8 +548,9 @@ static int mkdir_one(const char *root, int root_fd, const char *path) { return log_oom(); r = chaseat(root_fd, + root_fd, path, - CHASE_AT_RESOLVE_IN_ROOT|CHASE_PROHIBIT_SYMLINKS|CHASE_MKDIR_0755|CHASE_MUST_BE_DIRECTORY, + CHASE_PROHIBIT_SYMLINKS|CHASE_MKDIR_0755|CHASE_MUST_BE_DIRECTORY, /* ret_path= */ NULL, /* ret_fd= */ NULL); if (r < 0) @@ -611,9 +614,10 @@ static int update_efi_boot_binaries( _cleanup_closedir_ DIR *d = NULL; r = chase_and_opendirat( + c->esp_fd, c->esp_fd, "/EFI/BOOT", - CHASE_AT_RESOLVE_IN_ROOT|CHASE_PROHIBIT_SYMLINKS|CHASE_MUST_BE_DIRECTORY, + CHASE_PROHIBIT_SYMLINKS|CHASE_MUST_BE_DIRECTORY, /* ret_path= */ NULL, &d); if (r == -ENOENT) @@ -679,9 +683,10 @@ static int copy_one_file( _cleanup_close_ int source_fd = -EBADF; if (IN_SET(c->install_source, INSTALL_SOURCE_AUTO, INSTALL_SOURCE_IMAGE)) { source_fd = chase_and_openat( + c->root_fd, c->root_fd, sp, - CHASE_AT_RESOLVE_IN_ROOT|CHASE_MUST_BE_REGULAR, + CHASE_MUST_BE_REGULAR, O_RDONLY|O_CLOEXEC, &source_path); if (source_fd < 0 && (source_fd != -ENOENT || c->install_source != INSTALL_SOURCE_AUTO)) @@ -709,8 +714,9 @@ static int copy_one_file( _cleanup_close_ int dest_parent_fd = -EBADF; r = chaseat(c->esp_fd, + c->esp_fd, "/EFI/systemd", - CHASE_AT_RESOLVE_IN_ROOT|CHASE_PROHIBIT_SYMLINKS|CHASE_MKDIR_0755|CHASE_MUST_BE_DIRECTORY, + CHASE_PROHIBIT_SYMLINKS|CHASE_MKDIR_0755|CHASE_MUST_BE_DIRECTORY, /* ret_path= */ NULL, &dest_parent_fd); if (r < 0) @@ -740,8 +746,9 @@ static int copy_one_file( _cleanup_close_ int default_dest_parent_fd = -EBADF; r = chaseat(c->esp_fd, + c->esp_fd, "/EFI/BOOT", - CHASE_AT_RESOLVE_IN_ROOT|CHASE_PROHIBIT_SYMLINKS|CHASE_MKDIR_0755|CHASE_MUST_BE_DIRECTORY, + CHASE_PROHIBIT_SYMLINKS|CHASE_MKDIR_0755|CHASE_MUST_BE_DIRECTORY, /* ret_path= */ NULL, &default_dest_parent_fd); if (r < 0) @@ -778,9 +785,10 @@ static int install_binaries( _cleanup_closedir_ DIR *d = NULL; if (IN_SET(c->install_source, INSTALL_SOURCE_AUTO, INSTALL_SOURCE_IMAGE)) { r = chase_and_opendirat( + c->root_fd, c->root_fd, BOOTLIBDIR, - CHASE_AT_RESOLVE_IN_ROOT|CHASE_MUST_BE_DIRECTORY, + CHASE_MUST_BE_DIRECTORY, &source_path, &d); if (r < 0 && (r != -ENOENT || c->install_source != INSTALL_SOURCE_AUTO)) @@ -845,8 +853,9 @@ static int install_loader_config(InstallContext *c) { _cleanup_close_ int loader_dir_fd = -EBADF; r = chaseat(c->esp_fd, + c->esp_fd, "loader", - CHASE_AT_RESOLVE_IN_ROOT|CHASE_PROHIBIT_SYMLINKS|CHASE_MKDIR_0755|CHASE_MUST_BE_DIRECTORY, + CHASE_PROHIBIT_SYMLINKS|CHASE_MKDIR_0755|CHASE_MUST_BE_DIRECTORY, /* ret_path= */ NULL, &loader_dir_fd); if (r < 0) @@ -899,8 +908,9 @@ static int install_loader_specification(InstallContext *c) { _cleanup_close_ int loader_dir_fd = -EBADF; r = chaseat(dollar_boot_fd, + dollar_boot_fd, "loader", - CHASE_AT_RESOLVE_IN_ROOT|CHASE_PROHIBIT_SYMLINKS|CHASE_MKDIR_0755|CHASE_MUST_BE_DIRECTORY, + CHASE_PROHIBIT_SYMLINKS|CHASE_MKDIR_0755|CHASE_MUST_BE_DIRECTORY, /* ret_path= */ NULL, &loader_dir_fd); if (r < 0) @@ -973,8 +983,9 @@ static int install_entry_token(InstallContext *c) { _cleanup_close_ int dfd = -EBADF; r = chaseat(c->root_fd, + c->root_fd, confdir, - CHASE_AT_RESOLVE_IN_ROOT|CHASE_MKDIR_0755|CHASE_MUST_BE_DIRECTORY, + CHASE_MKDIR_0755|CHASE_MUST_BE_DIRECTORY, /* ret_path= */ NULL, &dfd); if (r < 0) @@ -1040,8 +1051,9 @@ static int install_secure_boot_auto_enroll(InstallContext *c) { _cleanup_close_ int keys_fd = -EBADF; r = chaseat(c->esp_fd, + c->esp_fd, "loader/keys/auto", - CHASE_AT_RESOLVE_IN_ROOT|CHASE_PROHIBIT_SYMLINKS|CHASE_MKDIR_0755|CHASE_MUST_BE_DIRECTORY, + CHASE_PROHIBIT_SYMLINKS|CHASE_MKDIR_0755|CHASE_MUST_BE_DIRECTORY, /* ret_path= */ NULL, &keys_fd); if (r < 0) @@ -1352,9 +1364,10 @@ static int install_variables( return log_oom(); r = chase_and_accessat( + c->esp_fd, c->esp_fd, path, - CHASE_AT_RESOLVE_IN_ROOT|CHASE_PROHIBIT_SYMLINKS|CHASE_MUST_BE_REGULAR, + CHASE_PROHIBIT_SYMLINKS|CHASE_MUST_BE_REGULAR, F_OK, /* ret_path= */ NULL); if (r == -ENOENT) @@ -1436,9 +1449,10 @@ static int are_we_installed(InstallContext *c) { return c->esp_fd; _cleanup_close_ int fd = chase_and_openat( + c->esp_fd, c->esp_fd, "/EFI/systemd", - CHASE_AT_RESOLVE_IN_ROOT|CHASE_PROHIBIT_SYMLINKS|CHASE_MUST_BE_DIRECTORY, + CHASE_PROHIBIT_SYMLINKS|CHASE_MUST_BE_DIRECTORY, O_RDONLY|O_CLOEXEC|O_DIRECTORY, /* ret_path= */ NULL); if (fd == -ENOENT) @@ -1655,9 +1669,10 @@ static int remove_boot_efi(InstallContext *c) { _cleanup_closedir_ DIR *d = NULL; _cleanup_free_ char *p = NULL; r = chase_and_opendirat( + c->esp_fd, c->esp_fd, "/EFI/BOOT", - CHASE_AT_RESOLVE_IN_ROOT|CHASE_PROHIBIT_SYMLINKS|CHASE_MUST_BE_DIRECTORY, + CHASE_PROHIBIT_SYMLINKS|CHASE_MUST_BE_DIRECTORY, &p, &d); if (r == -ENOENT) @@ -1725,9 +1740,10 @@ static int unlink_inode(const char *root, int root_fd, const char *path, mode_t return log_oom(); r = chase_and_unlinkat( + root_fd, root_fd, path, - CHASE_AT_RESOLVE_IN_ROOT|CHASE_PROHIBIT_SYMLINKS, + CHASE_PROHIBIT_SYMLINKS, S_ISDIR(type) ? AT_REMOVEDIR : 0, /* ret_path= */ NULL); if (r < 0) { @@ -1773,8 +1789,9 @@ static int remove_binaries(InstallContext *c) { _cleanup_close_ int efi_fd = -EBADF; r = chaseat(c->esp_fd, + c->esp_fd, "EFI", - CHASE_AT_RESOLVE_IN_ROOT|CHASE_PROHIBIT_SYMLINKS|CHASE_MUST_BE_DIRECTORY, + CHASE_PROHIBIT_SYMLINKS|CHASE_MUST_BE_DIRECTORY, /* ret_path= */ NULL, &efi_fd); if (r < 0) { diff --git a/src/core/exec-invoke.c b/src/core/exec-invoke.c index 2138367218..c663532e00 100644 --- a/src/core/exec-invoke.c +++ b/src/core/exec-invoke.c @@ -4152,7 +4152,7 @@ static int apply_working_directory( r = chase(wd, runtime->ephemeral_copy ?: context->root_directory, - CHASE_PREFIX_ROOT|CHASE_AT_RESOLVE_IN_ROOT|CHASE_TRIGGER_AUTOFS, + CHASE_PREFIX_ROOT|CHASE_TRIGGER_AUTOFS, /* ret_path= */ NULL, &dfd); if (r >= 0) diff --git a/src/core/service.c b/src/core/service.c index 63e6599421..aa00413dc3 100644 --- a/src/core/service.c +++ b/src/core/service.c @@ -5911,7 +5911,7 @@ int service_determine_exec_selinux_label(Service *s, char **ret) { _cleanup_free_ char *path = NULL; if (s->exec_context.root_directory_as_fd) - r = chaseat(s->root_directory_fd, c->path, CHASE_AT_RESOLVE_IN_ROOT|CHASE_TRIGGER_AUTOFS, &path, NULL); + r = chaseat(s->root_directory_fd, s->root_directory_fd, c->path, CHASE_TRIGGER_AUTOFS, &path, NULL); else r = chase(c->path, s->exec_context.root_directory, CHASE_PREFIX_ROOT|CHASE_TRIGGER_AUTOFS, &path, NULL); if (r < 0) { diff --git a/src/firstboot/firstboot.c b/src/firstboot/firstboot.c index ea4dd5f184..3006d93407 100644 --- a/src/firstboot/firstboot.c +++ b/src/firstboot/firstboot.c @@ -339,8 +339,8 @@ static int process_locale(int rfd, sd_varlink **mute_console_link) { assert(rfd >= 0); - pfd = chase_and_open_parent_at(rfd, etc_locale_conf(), - CHASE_AT_RESOLVE_IN_ROOT|CHASE_MKDIR_0755|CHASE_WARN|CHASE_NOFOLLOW, + pfd = chase_and_open_parent_at(rfd, rfd, etc_locale_conf(), + CHASE_MKDIR_0755|CHASE_WARN|CHASE_NOFOLLOW, &f); if (pfd < 0) return log_error_errno(pfd, "Failed to chase /etc/locale.conf: %m"); @@ -474,8 +474,8 @@ static int process_keymap(int rfd, sd_varlink **mute_console_link) { assert(rfd >= 0); - pfd = chase_and_open_parent_at(rfd, etc_vconsole_conf(), - CHASE_AT_RESOLVE_IN_ROOT|CHASE_MKDIR_0755|CHASE_WARN|CHASE_NOFOLLOW, + pfd = chase_and_open_parent_at(rfd, rfd, etc_vconsole_conf(), + CHASE_MKDIR_0755|CHASE_WARN|CHASE_NOFOLLOW, &f); if (pfd < 0) return log_error_errno(pfd, "Failed to chase /etc/vconsole.conf: %m"); @@ -590,8 +590,8 @@ static int process_timezone(int rfd, sd_varlink **mute_console_link) { assert(rfd >= 0); - pfd = chase_and_open_parent_at(rfd, etc_localtime(), - CHASE_AT_RESOLVE_IN_ROOT|CHASE_MKDIR_0755|CHASE_WARN|CHASE_NOFOLLOW, + pfd = chase_and_open_parent_at(rfd, rfd, etc_localtime(), + CHASE_MKDIR_0755|CHASE_WARN|CHASE_NOFOLLOW, &f); if (pfd < 0) return log_error_errno(pfd, "Failed to chase /etc/localtime: %m"); @@ -703,9 +703,7 @@ static int process_hostname(int rfd, sd_varlink **mute_console_link) { assert(rfd >= 0); - pfd = chase_and_open_parent_at(rfd, etc_hostname(), - CHASE_AT_RESOLVE_IN_ROOT|CHASE_MKDIR_0755|CHASE_WARN, - &f); + pfd = chase_and_open_parent_at(rfd, rfd, etc_hostname(), CHASE_MKDIR_0755|CHASE_WARN, &f); if (pfd < 0) return log_error_errno(pfd, "Failed to chase /etc/hostname: %m"); @@ -738,8 +736,8 @@ static int process_machine_id(int rfd) { assert(rfd >= 0); - pfd = chase_and_open_parent_at(rfd, "/etc/machine-id", - CHASE_AT_RESOLVE_IN_ROOT|CHASE_MKDIR_0755|CHASE_WARN|CHASE_NOFOLLOW, + pfd = chase_and_open_parent_at(rfd, rfd, "/etc/machine-id", + CHASE_MKDIR_0755|CHASE_WARN|CHASE_NOFOLLOW, &f); if (pfd < 0) return log_error_errno(pfd, "Failed to chase /etc/machine-id: %m"); @@ -848,7 +846,7 @@ static int find_shell(int rfd, const char *path) { if (!valid_shell(path)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "%s is not a valid shell", path); - r = chaseat(rfd, path, CHASE_AT_RESOLVE_IN_ROOT, NULL, NULL); + r = chaseat(rfd, rfd, path, /* flags= */ 0, /* ret_path= */ NULL, /* ret_fd= */ NULL); if (r < 0) return log_error_errno(r, "Failed to resolve shell %s: %m", path); @@ -1052,8 +1050,8 @@ static int process_root_account(int rfd, sd_varlink **mute_console_link) { assert(rfd >= 0); - pfd = chase_and_open_parent_at(rfd, "/etc/passwd", - CHASE_AT_RESOLVE_IN_ROOT|CHASE_MKDIR_0755|CHASE_WARN|CHASE_NOFOLLOW, + pfd = chase_and_open_parent_at(rfd, rfd, "/etc/passwd", + CHASE_MKDIR_0755|CHASE_WARN|CHASE_NOFOLLOW, NULL); if (pfd < 0) return log_error_errno(pfd, "Failed to chase /etc/passwd: %m"); @@ -1169,8 +1167,8 @@ static int process_kernel_cmdline(int rfd) { assert(rfd >= 0); - pfd = chase_and_open_parent_at(rfd, "/etc/kernel/cmdline", - CHASE_AT_RESOLVE_IN_ROOT|CHASE_MKDIR_0755|CHASE_WARN|CHASE_NOFOLLOW, + pfd = chase_and_open_parent_at(rfd, rfd, "/etc/kernel/cmdline", + CHASE_MKDIR_0755|CHASE_WARN|CHASE_NOFOLLOW, &f); if (pfd < 0) return log_error_errno(pfd, "Failed to chase /etc/kernel/cmdline: %m"); @@ -1202,7 +1200,7 @@ static int reset_one(int rfd, const char *path) { assert(rfd >= 0); assert(path); - pfd = chase_and_open_parent_at(rfd, path, CHASE_AT_RESOLVE_IN_ROOT|CHASE_WARN|CHASE_NOFOLLOW, &f); + pfd = chase_and_open_parent_at(rfd, rfd, path, CHASE_WARN|CHASE_NOFOLLOW, &f); if (pfd == -ENOENT) return 0; if (pfd < 0) diff --git a/src/kernel-install/kernel-install.c b/src/kernel-install/kernel-install.c index aeded46c22..331923e9e5 100644 --- a/src/kernel-install/kernel-install.c +++ b/src/kernel-install/kernel-install.c @@ -348,7 +348,7 @@ static int context_set_path(Context *c, const char *s, const char *source, const return 0; if (c->rfd >= 0) { - r = chaseat(c->rfd, s, CHASE_AT_RESOLVE_IN_ROOT, &p, /* ret_fd= */ NULL); + r = chaseat(c->rfd, c->rfd, s, /* flags= */ 0, &p, /* ret_fd= */ NULL); if (r < 0) return log_warning_errno(r, "Failed to chase path %s for %s specified via %s, ignoring: %m", s, name, source); @@ -396,7 +396,7 @@ static int context_set_path_strv(Context *c, char* const* strv, const char *sour char *p; if (c->rfd >= 0) { - r = chaseat(c->rfd, *s, CHASE_AT_RESOLVE_IN_ROOT, &p, /* ret_fd= */ NULL); + r = chaseat(c->rfd, c->rfd, *s, /* flags= */ 0, &p, /* ret_fd= */ NULL); if (r < 0) return log_warning_errno(r, "Failed to chase path %s for %s specified via %s: %m", *s, name, source); @@ -503,7 +503,7 @@ static int context_load_machine_info(Context *c) { return 0; } - r = chase_and_fopenat_unlocked(c->rfd, path, CHASE_AT_RESOLVE_IN_ROOT, "re", NULL, &f); + r = chase_and_fopenat_unlocked(c->rfd, c->rfd, path, /* chase_flags= */ 0, "re", NULL, &f); if (r == -ENOENT) return 0; if (r < 0) @@ -631,7 +631,7 @@ static int context_ensure_boot_root(Context *c) { /* If all else fails, use /boot. */ if (c->rfd >= 0) { - r = chaseat(c->rfd, "/boot", CHASE_AT_RESOLVE_IN_ROOT, &c->boot_root, /* ret_fd= */ NULL); + r = chaseat(c->rfd, c->rfd, "/boot", 0, &c->boot_root, /* ret_fd= */ NULL); if (r < 0) return log_error_errno(r, "Failed to chase '/boot/': %m"); } else { @@ -794,7 +794,7 @@ static int context_ensure_layout(Context *c) { return log_oom(); _cleanup_fclose_ FILE *f = NULL; - r = chase_and_fopenat_unlocked(c->rfd, srel_path, CHASE_AT_RESOLVE_IN_ROOT|CHASE_MUST_BE_REGULAR, "re", /* ret_path= */ NULL, &f); + r = chase_and_fopenat_unlocked(c->rfd, c->rfd, srel_path, CHASE_MUST_BE_REGULAR, "re", /* ret_path= */ NULL, &f); if (r < 0) { if (r != -ENOENT) return log_error_errno(r, "Failed to open '%s': %m", srel_path); @@ -824,7 +824,7 @@ static int context_ensure_layout(Context *c) { if (!entry_token_path) return log_oom(); - r = chaseat(c->rfd, entry_token_path, CHASE_AT_RESOLVE_IN_ROOT|CHASE_MUST_BE_DIRECTORY, /* ret_path= */ NULL, /* ret_fd= */ NULL); + r = chaseat(c->rfd, c->rfd, entry_token_path, CHASE_MUST_BE_DIRECTORY, /* ret_path= */ NULL, /* ret_fd= */ NULL); if (r < 0) { if (!IN_SET(r, -ENOENT, -ENOTDIR)) return log_error_errno(r, "Failed to check if '%s' exists and is a directory: %m", entry_token_path); @@ -916,7 +916,7 @@ static int context_make_entry_dir(Context *c) { return 0; log_debug("mkdir -p %s", c->entry_dir); - fd = chase_and_openat(c->rfd, c->entry_dir, CHASE_AT_RESOLVE_IN_ROOT | CHASE_MKDIR_0755, + fd = chase_and_openat(c->rfd, c->rfd, c->entry_dir, CHASE_MKDIR_0755, O_CLOEXEC | O_CREAT | O_DIRECTORY | O_PATH, NULL); if (fd < 0) return log_error_errno(fd, "Failed to make directory '%s': %m", c->entry_dir); @@ -940,7 +940,7 @@ static int context_remove_entry_dir(Context *c) { return 0; log_debug("rm -rf %s", c->entry_dir); - fd = chase_and_openat(c->rfd, c->entry_dir, CHASE_AT_RESOLVE_IN_ROOT, O_CLOEXEC | O_DIRECTORY, &p); + fd = chase_and_openat(c->rfd, c->rfd, c->entry_dir, /* chase_flags= */ 0, O_CLOEXEC | O_DIRECTORY, &p); if (fd < 0) { if (IN_SET(fd, -ENOTDIR, -ENOENT)) return 0; @@ -1237,7 +1237,7 @@ static int verb_add_all(int argc, char *argv[], uintptr_t _data, void *userdata) if (r < 0) return r; - fd = chase_and_openat(c.rfd, "/usr/lib/modules", CHASE_AT_RESOLVE_IN_ROOT, O_DIRECTORY|O_RDONLY|O_CLOEXEC, NULL); + fd = chase_and_openat(c.rfd, c.rfd, "/usr/lib/modules", /* chase_flags= */ 0, O_DIRECTORY|O_RDONLY|O_CLOEXEC, NULL); if (fd < 0) return log_error_errno(fd, "Failed to open %s/usr/lib/modules/: %m", strempty(arg_root)); @@ -1467,7 +1467,7 @@ static int verb_list(int argc, char *argv[], uintptr_t _data, void *userdata) { if (r < 0) return r; - fd = chase_and_openat(c.rfd, "/usr/lib/modules", CHASE_AT_RESOLVE_IN_ROOT, O_DIRECTORY|O_RDONLY|O_CLOEXEC, NULL); + fd = chase_and_openat(c.rfd, c.rfd, "/usr/lib/modules", /* chase_flags= */ 0, O_DIRECTORY|O_RDONLY|O_CLOEXEC, NULL); if (fd < 0) return log_error_errno(fd, "Failed to open %s/usr/lib/modules/: %m", strempty(arg_root)); diff --git a/src/libsystemd/sd-id128/sd-id128.c b/src/libsystemd/sd-id128/sd-id128.c index f2a7209a25..852b01ab1a 100644 --- a/src/libsystemd/sd-id128/sd-id128.c +++ b/src/libsystemd/sd-id128/sd-id128.c @@ -147,7 +147,7 @@ int id128_get_machine_at(int rfd, sd_id128_t *ret) { return sd_id128_get_machine(ret); _cleanup_close_ int fd = - chase_and_openat(rfd, "/etc/machine-id", CHASE_AT_RESOLVE_IN_ROOT|CHASE_MUST_BE_REGULAR, O_RDONLY|O_CLOEXEC|O_NOCTTY, /* ret_path= */ NULL); + chase_and_openat(rfd, rfd, "/etc/machine-id", CHASE_MUST_BE_REGULAR, O_RDONLY|O_CLOEXEC|O_NOCTTY, /* ret_path= */ NULL); if (fd < 0) return fd; diff --git a/src/mountfsd/mountwork.c b/src/mountfsd/mountwork.c index 42860db134..54a5203da2 100644 --- a/src/mountfsd/mountwork.c +++ b/src/mountfsd/mountwork.c @@ -242,7 +242,7 @@ static int verify_trusted_image_fd_by_path(int fd) { if (!filename_is_valid(e)) continue; - r = chaseat(dir_fd, e, CHASE_SAFE|CHASE_TRIGGER_AUTOFS, NULL, &inode_fd); + r = chaseat(XAT_FDROOT, dir_fd, e, CHASE_SAFE|CHASE_TRIGGER_AUTOFS, NULL, &inode_fd); if (r < 0) return log_error_errno(r, "Couldn't verify that specified image '%s' is in search path '%s': %m", p, s); diff --git a/src/portable/portable.c b/src/portable/portable.c index 5e60ad4694..8b2be8cead 100644 --- a/src/portable/portable.c +++ b/src/portable/portable.c @@ -363,7 +363,7 @@ static int extract_now( _cleanup_free_ char *relative = NULL, *resolved = NULL; _cleanup_closedir_ DIR *d = NULL; - r = chase_and_opendirat(rfd, *i, CHASE_AT_RESOLVE_IN_ROOT, &relative, &d); + r = chase_and_opendirat(rfd, rfd, *i, /* chase_flags= */ 0, &relative, &d); if (r < 0) { log_debug_errno(r, "Failed to open unit path '%s', ignoring: %m", *i); continue; diff --git a/src/shared/boot-entry.c b/src/shared/boot-entry.c index c9e966ba04..ce75807491 100644 --- a/src/shared/boot-entry.c +++ b/src/shared/boot-entry.c @@ -32,7 +32,7 @@ static int entry_token_load_one(int rfd, const char *dir, BootEntryTokenType *ty if (!p) return log_oom(); - r = chase_and_fopenat_unlocked(rfd, p, CHASE_AT_RESOLVE_IN_ROOT, "re", NULL, &f); + r = chase_and_fopenat_unlocked(rfd, rfd, p, /* chase_flags= */ 0, "re", NULL, &f); if (r == -ENOENT) return 0; if (r < 0) diff --git a/src/shared/bootspec.c b/src/shared/bootspec.c index c3774a5235..3338d75f66 100644 --- a/src/shared/bootspec.c +++ b/src/shared/bootspec.c @@ -1126,7 +1126,7 @@ static int boot_entries_find_unified_addons( assert(ret_addons); assert(config); - r = chase_and_opendirat(d_fd, addon_dir, CHASE_AT_RESOLVE_IN_ROOT, &full, &d); + r = chase_and_opendirat(d_fd, d_fd, addon_dir, /* chase_flags= */ 0, &full, &d); if (r == -ENOENT) return 0; if (r < 0) diff --git a/src/shared/btrfs-util.c b/src/shared/btrfs-util.c index 2095d803f5..bb3e28f6bb 100644 --- a/src/shared/btrfs-util.c +++ b/src/shared/btrfs-util.c @@ -1014,7 +1014,7 @@ int btrfs_subvol_remove_at(int dir_fd, const char *path, BtrfsRemoveFlags flags) assert(path); - fd = chase_and_openat(dir_fd, path, CHASE_PARENT|CHASE_EXTRACT_FILENAME, O_CLOEXEC, &subvolume); + fd = chase_and_openat(XAT_FDROOT, dir_fd, path, CHASE_PARENT|CHASE_EXTRACT_FILENAME, O_CLOEXEC, &subvolume); if (fd < 0) return fd; @@ -1427,7 +1427,7 @@ int btrfs_subvol_snapshot_at_full( if (old_fd < 0) return old_fd; - new_fd = chase_and_openat(dir_fdt, to, CHASE_PARENT|CHASE_EXTRACT_FILENAME, O_CLOEXEC, &subvolume); + new_fd = chase_and_openat(XAT_FDROOT, dir_fdt, to, CHASE_PARENT|CHASE_EXTRACT_FILENAME, O_CLOEXEC, &subvolume); if (new_fd < 0) return new_fd; diff --git a/src/shared/conf-parser.c b/src/shared/conf-parser.c index b448032939..ba0bc4ad8c 100644 --- a/src/shared/conf-parser.c +++ b/src/shared/conf-parser.c @@ -509,7 +509,7 @@ static int config_parse_many_files( /* Pin and stat() all dropins */ STRV_FOREACH(fn, files) { _cleanup_fclose_ FILE *f = NULL; - r = chase_and_fopenat_unlocked(root_fd, *fn, CHASE_AT_RESOLVE_IN_ROOT|CHASE_MUST_BE_REGULAR, "re", /* ret_path= */ NULL, &f); + r = chase_and_fopenat_unlocked(root_fd, root_fd, *fn, CHASE_MUST_BE_REGULAR, "re", /* ret_path= */ NULL, &f); if (r == -ENOENT) continue; if (r < 0) @@ -544,7 +544,7 @@ static int config_parse_many_files( /* First process the first found main config file. */ STRV_FOREACH(fn, conf_files) { _cleanup_fclose_ FILE *f = NULL; - r = chase_and_fopenat_unlocked(root_fd, *fn, CHASE_AT_RESOLVE_IN_ROOT|CHASE_MUST_BE_REGULAR, "re", /* ret_path= */ NULL, &f); + r = chase_and_fopenat_unlocked(root_fd, root_fd, *fn, CHASE_MUST_BE_REGULAR, "re", /* ret_path= */ NULL, &f); if (r == -ENOENT) continue; if (r < 0) diff --git a/src/shared/discover-image.c b/src/shared/discover-image.c index 1595bb2184..0410b523ba 100644 --- a/src/shared/discover-image.c +++ b/src/shared/discover-image.c @@ -891,7 +891,7 @@ int image_find(RuntimeScope scope, _cleanup_closedir_ DIR *d = NULL; _cleanup_free_ char *search_path = NULL; - r = chase_and_opendirat(rfd, *s, CHASE_AT_RESOLVE_IN_ROOT, &search_path, &d); + r = chase_and_opendirat(rfd, rfd, *s, /* chase_flags= */ 0, &search_path, &d); if (r == -ENOENT) continue; if (r < 0) @@ -907,7 +907,7 @@ int image_find(RuntimeScope scope, return -ENOMEM; /* Follow symlinks only inside given root */ - r = chaseat(rfd, fname_path, CHASE_AT_RESOLVE_IN_ROOT, &chased_path, &fd); + r = chaseat(rfd, rfd, fname_path, /* flags= */ 0, &chased_path, &fd); if (r == -ENOENT) continue; if (r < 0) @@ -1097,7 +1097,7 @@ int image_discover( _cleanup_closedir_ DIR *d = NULL; _cleanup_free_ char *search_path = NULL; - r = chase_and_opendirat(rfd, *s, CHASE_AT_RESOLVE_IN_ROOT, &search_path, &d); + r = chase_and_opendirat(rfd, rfd, *s, /* chase_flags= */ 0, &search_path, &d); if (r == -ENOENT) continue; if (r < 0) @@ -1121,7 +1121,7 @@ int image_discover( return -ENOMEM; /* Follow symlinks only inside given root */ - r = chaseat(rfd, fname_path, CHASE_AT_RESOLVE_IN_ROOT, &chased_path, &fd); + r = chaseat(rfd, rfd, fname_path, /* flags= */ 0, &chased_path, &fd); if (r == -ENOENT) continue; if (r < 0) diff --git a/src/shared/find-esp.c b/src/shared/find-esp.c index d29719785f..3497ca6da3 100644 --- a/src/shared/find-esp.c +++ b/src/shared/find-esp.c @@ -337,7 +337,7 @@ static int verify_esp( _cleanup_free_ char *p = NULL; _cleanup_close_ int fd = -EBADF; - r = chaseat(rfd, path, CHASE_AT_RESOLVE_IN_ROOT|CHASE_TRIGGER_AUTOFS, &p, &fd); + r = chaseat(rfd, rfd, path, CHASE_TRIGGER_AUTOFS, &p, &fd); if (r < 0) return log_full_errno((searching && r == -ENOENT) || (unprivileged_mode && ERRNO_IS_PRIVILEGE(r)) ? LOG_DEBUG : LOG_ERR, @@ -441,7 +441,7 @@ int find_esp_and_warn_at_full( "$SYSTEMD_ESP_PATH does not refer to an absolute path, refusing to use it: \"%s\"", path); - r = chaseat(rfd, path, CHASE_AT_RESOLVE_IN_ROOT|CHASE_TRIGGER_AUTOFS, &p, &fd); + r = chaseat(rfd, rfd, path, CHASE_TRIGGER_AUTOFS, &p, &fd); if (r < 0) return log_error_errno(r, "Failed to resolve path \"%s\": %m", path); @@ -735,7 +735,7 @@ static int verify_xbootldr( _cleanup_free_ char *p = NULL; _cleanup_close_ int fd = -EBADF; - r = chaseat(rfd, path, CHASE_AT_RESOLVE_IN_ROOT|CHASE_TRIGGER_AUTOFS, &p, &fd); + r = chaseat(rfd, rfd, path, CHASE_TRIGGER_AUTOFS, &p, &fd); if (r < 0) return log_full_errno((searching && r == -ENOENT) || (unprivileged_mode && ERRNO_IS_PRIVILEGE(r)) ? LOG_DEBUG : LOG_ERR, @@ -809,7 +809,7 @@ int find_xbootldr_and_warn_at_full( "$SYSTEMD_XBOOTLDR_PATH does not refer to an absolute path, refusing to use it: \"%s\"", path); - r = chaseat(rfd, path, CHASE_AT_RESOLVE_IN_ROOT|CHASE_TRIGGER_AUTOFS, &p, &fd); + r = chaseat(rfd, rfd, path, CHASE_TRIGGER_AUTOFS, &p, &fd); if (r < 0) return log_error_errno(r, "Failed to resolve path \"%s\": %m", p); diff --git a/src/shared/mstack.c b/src/shared/mstack.c index 32e95fd230..cc3cb9a75f 100644 --- a/src/shared/mstack.c +++ b/src/shared/mstack.c @@ -1032,7 +1032,7 @@ int mstack_bind_mounts( if (mstack->usr_mount_fd >= 0) { _cleanup_close_ int subdir_fd = -EBADF; - r = chaseat(root_fd, "usr", CHASE_AT_RESOLVE_IN_ROOT|CHASE_PROHIBIT_SYMLINKS|CHASE_MKDIR_0755|CHASE_MUST_BE_DIRECTORY, /* ret_path= */ NULL, &subdir_fd); + r = chaseat(root_fd, root_fd, "usr", CHASE_PROHIBIT_SYMLINKS|CHASE_MKDIR_0755|CHASE_MUST_BE_DIRECTORY, /* ret_path= */ NULL, &subdir_fd); if (r < 0) return log_debug_errno(r, "Failed to open mount point inode '%s': %m", where); @@ -1051,7 +1051,7 @@ int mstack_bind_mounts( assert(m->mount_fd >= 0); _cleanup_close_ int subdir_fd = -EBADF; - r = chaseat(root_fd, m->where, CHASE_AT_RESOLVE_IN_ROOT|CHASE_PROHIBIT_SYMLINKS|CHASE_MKDIR_0755|CHASE_MUST_BE_DIRECTORY, /* ret_path= */ NULL, &subdir_fd); + r = chaseat(root_fd, root_fd, m->where, CHASE_PROHIBIT_SYMLINKS|CHASE_MKDIR_0755|CHASE_MUST_BE_DIRECTORY, /* ret_path= */ NULL, &subdir_fd); if (r < 0) return log_debug_errno(r, "Failed to open mount point inode '%s': %m", m->where); diff --git a/src/shared/tar-util.c b/src/shared/tar-util.c index 3f7dc88a6a..dad6337b75 100644 --- a/src/shared/tar-util.c +++ b/src/shared/tar-util.c @@ -922,8 +922,8 @@ int tar_x(int input_fd, int tree_fd, TarFlags flags) { "Invalid hardlink path name '%s' in entry, refusing.", target); _cleanup_close_ int target_fd = -EBADF; - r = chaseat(tree_fd, target, - CHASE_PROHIBIT_SYMLINKS|CHASE_AT_RESOLVE_IN_ROOT|CHASE_NOFOLLOW, + r = chaseat(tree_fd, tree_fd, target, + CHASE_PROHIBIT_SYMLINKS|CHASE_NOFOLLOW, /* ret_path= */ NULL, &target_fd); if (r < 0) return log_error_errno( @@ -953,8 +953,8 @@ int tar_x(int input_fd, int tree_fd, TarFlags flags) { _cleanup_close_ int target_parent_fd = -EBADF; _cleanup_free_ char *target_filename = NULL; - r = chaseat(tree_fd, target, - CHASE_PROHIBIT_SYMLINKS|CHASE_AT_RESOLVE_IN_ROOT|CHASE_PARENT|CHASE_EXTRACT_FILENAME|CHASE_NOFOLLOW, + r = chaseat(tree_fd, tree_fd, target, + CHASE_PROHIBIT_SYMLINKS|CHASE_PARENT|CHASE_EXTRACT_FILENAME|CHASE_NOFOLLOW, &target_filename, &target_parent_fd); if (r < 0) return log_error_errno(r, "Failed to find inode '%s' which shall be hardlinked as '%s': %m", diff --git a/src/shared/tests.h b/src/shared/tests.h index adf1b0f689..9a2b7c0412 100644 --- a/src/shared/tests.h +++ b/src/shared/tests.h @@ -477,6 +477,18 @@ _noreturn_ void log_test_failed_internal(const char *file, int line, const char }) #endif +#ifdef __COVERITY__ +# define ASSERT_PATH_EQ(expr1, expr2) __coverity_check__(path_equal((expr1), (expr2))) +#else +# define ASSERT_PATH_EQ(expr1, expr2) \ + ({ \ + const char *_expr1 = (expr1), *_expr2 = (expr2); \ + if (!path_equal(_expr1, _expr2)) \ + log_test_failed("Expected \"%s == %s\", got \"%s != %s\"", \ + #expr1, #expr2, strnull(_expr1), strnull(_expr2)); \ + }) +#endif + #ifdef __COVERITY__ # define ASSERT_NOT_STREQ(expr1, expr2) __coverity_check__(!streq_ptr((expr1), (expr2))) #else diff --git a/src/shared/vpick.c b/src/shared/vpick.c index 38ceb225cd..b68991cda1 100644 --- a/src/shared/vpick.c +++ b/src/shared/vpick.c @@ -197,8 +197,9 @@ static int pin_choice( if (inode_fd < 0 || FLAGS_SET(flags, PICK_RESOLVE)) { r = chaseat(toplevel_fd, + toplevel_fd, inode_path, - CHASE_AT_RESOLVE_IN_ROOT, + /* flags= */ 0, FLAGS_SET(flags, PICK_RESOLVE) ? &resolved_path : NULL, inode_fd < 0 ? &inode_fd : NULL); if (r < 0) @@ -327,7 +328,7 @@ static int make_choice( assert(ret); if (inode_fd < 0) { - r = chaseat(toplevel_fd, inode_path, CHASE_AT_RESOLVE_IN_ROOT, NULL, &inode_fd); + r = chaseat(toplevel_fd, toplevel_fd, inode_path, /* flags= */ 0, NULL, &inode_fd); if (r < 0) return r; } @@ -344,7 +345,7 @@ static int make_choice( return log_oom_debug(); _cleanup_close_ int object_fd = -EBADF; - r = chaseat(toplevel_fd, p, CHASE_AT_RESOLVE_IN_ROOT, &object_path, &object_fd); + r = chaseat(toplevel_fd, toplevel_fd, p, /* flags= */ 0, &object_path, &object_fd); if (r == -ENOENT) { *ret = PICK_RESULT_NULL; return 0; diff --git a/src/sysext/sysext.c b/src/sysext/sysext.c index 0ac35d1b56..0475de3e6e 100644 --- a/src/sysext/sysext.c +++ b/src/sysext/sysext.c @@ -1056,7 +1056,7 @@ static int resolve_mutable_directory( /* This also creates, e.g., /var/lib/extensions.mutable/usr if needed and all parent * directories plus it also works when the last part is a symlink to the real /usr but we * can't use chase_and_open here because it does not behave the same. */ - r = chase(path, root, CHASE_AT_RESOLVE_IN_ROOT|CHASE_MKDIR_0755|CHASE_MUST_BE_DIRECTORY|CHASE_PREFIX_ROOT, /* ret_path */ NULL, &path_fd); + r = chase(path, root, CHASE_MKDIR_0755|CHASE_MUST_BE_DIRECTORY|CHASE_PREFIX_ROOT, /* ret_path */ NULL, &path_fd); if (r < 0) return log_error_errno(r, "Failed to chase/create base directory '%s/%s': %m", strempty(root), skip_leading_slash(path)); diff --git a/src/test/test-chase.c b/src/test/test-chase.c index 721f56a250..ed07ac5cef 100644 --- a/src/test/test-chase.c +++ b/src/test/test-chase.c @@ -27,10 +27,10 @@ static void test_chase_extract_filename_one(const char *path, const char *root, log_debug("/* %s(path=%s, root=%s) */", __func__, path, strnull(root)); - assert_se(chase(path, root, CHASE_EXTRACT_FILENAME, &ret1, NULL) > 0); + ASSERT_OK_POSITIVE(chase(path, root, CHASE_EXTRACT_FILENAME, &ret1, NULL)); ASSERT_STREQ(ret1, expected); - assert_se(chase(path, root, 0, &ret2, NULL) > 0); + ASSERT_OK_POSITIVE(chase(path, root, 0, &ret2, NULL)); ASSERT_OK(chase_extract_filename(ret2, root, &fname)); ASSERT_STREQ(fname, expected); } @@ -41,97 +41,84 @@ TEST(chase) { char *temp; const char *top, *p, *pslash, *q, *qslash; struct stat st; - int r; temp = strjoina(arg_test_dir ?: "/tmp", "/test-chase.XXXXXX"); - assert_se(mkdtemp(temp)); + ASSERT_NOT_NULL(mkdtemp(temp)); top = strjoina(temp, "/top"); ASSERT_OK(mkdir(top, 0700)); p = strjoina(top, "/dot"); if (symlink(".", p) < 0) { - assert_se(IN_SET(errno, EINVAL, ENOSYS, ENOTTY, EPERM)); + ASSERT_TRUE(IN_SET(errno, EINVAL, ENOSYS, ENOTTY, EPERM)); log_tests_skipped_errno(errno, "symlink() not possible"); goto cleanup; }; p = strjoina(top, "/dotdot"); - ASSERT_OK(symlink("..", p)); + ASSERT_OK_ERRNO(symlink("..", p)); p = strjoina(top, "/dotdota"); - ASSERT_OK(symlink("../a", p)); + ASSERT_OK_ERRNO(symlink("../a", p)); p = strjoina(temp, "/a"); - ASSERT_OK(symlink("b", p)); + ASSERT_OK_ERRNO(symlink("b", p)); p = strjoina(temp, "/b"); - ASSERT_OK(symlink("/usr", p)); + ASSERT_OK_ERRNO(symlink("/usr", p)); p = strjoina(temp, "/start"); - ASSERT_OK(symlink("top/dot/dotdota", p)); + ASSERT_OK_ERRNO(symlink("top/dot/dotdota", p)); /* Paths that use symlinks underneath the "root" */ - r = chase(p, NULL, 0, &result, NULL); - assert_se(r > 0); - assert_se(path_equal(result, "/usr")); + ASSERT_OK_POSITIVE(chase(p, NULL, 0, &result, NULL)); + ASSERT_PATH_EQ(result, "/usr"); result = mfree(result); - r = chase(p, "/.//../../../", 0, &result, NULL); - assert_se(r > 0); - assert_se(path_equal(result, "/usr")); + ASSERT_OK_POSITIVE(chase(p, "/.//../../../", 0, &result, NULL)); + ASSERT_PATH_EQ(result, "/usr"); result = mfree(result); pslash = strjoina(p, "/"); - r = chase(pslash, NULL, 0, &result, NULL); - assert_se(r > 0); - assert_se(path_equal(result, "/usr/")); + ASSERT_OK_POSITIVE(chase(pslash, NULL, 0, &result, NULL)); + ASSERT_PATH_EQ(result, "/usr/"); result = mfree(result); - r = chase(p, temp, 0, &result, NULL); - assert_se(r == -ENOENT); - - r = chase(pslash, temp, 0, &result, NULL); - assert_se(r == -ENOENT); + ASSERT_ERROR(chase(p, temp, 0, &result, NULL), ENOENT); + ASSERT_ERROR(chase(pslash, temp, 0, &result, NULL), ENOENT); q = strjoina(temp, "/usr"); - r = chase(p, temp, CHASE_NONEXISTENT, &result, NULL); - assert_se(r == 0); - assert_se(path_equal(result, q)); + ASSERT_OK_ZERO(chase(p, temp, CHASE_NONEXISTENT, &result, NULL)); + ASSERT_PATH_EQ(result, q); result = mfree(result); qslash = strjoina(q, "/"); - r = chase(pslash, temp, CHASE_NONEXISTENT, &result, NULL); - assert_se(r == 0); - assert_se(path_equal(result, qslash)); + ASSERT_OK_ZERO(chase(pslash, temp, CHASE_NONEXISTENT, &result, NULL)); + ASSERT_PATH_EQ(result, qslash); result = mfree(result); - ASSERT_OK(mkdir(q, 0700)); + ASSERT_OK_ERRNO(mkdir(q, 0700)); - r = chase(p, temp, 0, &result, NULL); - assert_se(r > 0); - assert_se(path_equal(result, q)); + ASSERT_OK_POSITIVE(chase(p, temp, 0, &result, NULL)); + ASSERT_PATH_EQ(result, q); result = mfree(result); - r = chase(pslash, temp, 0, &result, NULL); - assert_se(r > 0); - assert_se(path_equal(result, qslash)); + ASSERT_OK_POSITIVE(chase(pslash, temp, 0, &result, NULL)); + ASSERT_PATH_EQ(result, qslash); result = mfree(result); p = strjoina(temp, "/slash"); - assert_se(symlink("/", p) >= 0); + ASSERT_OK_ERRNO(symlink("/", p)); - r = chase(p, NULL, 0, &result, NULL); - assert_se(r > 0); - assert_se(path_equal(result, "/")); + ASSERT_OK_POSITIVE(chase(p, NULL, 0, &result, NULL)); + ASSERT_PATH_EQ(result, "/"); result = mfree(result); - r = chase(p, temp, 0, &result, NULL); - assert_se(r > 0); - assert_se(path_equal(result, temp)); + ASSERT_OK_POSITIVE(chase(p, temp, 0, &result, NULL)); + ASSERT_PATH_EQ(result, temp); result = mfree(result); /* Tests for CHASE_EXTRACT_FILENAME and chase_extract_filename() */ @@ -150,205 +137,182 @@ TEST(chase) { /* Paths that would "escape" outside of the "root" */ p = strjoina(temp, "/6dots"); - ASSERT_OK(symlink("../../..", p)); + ASSERT_OK_ERRNO(symlink("../../..", p)); - r = chase(p, temp, 0, &result, NULL); - assert_se(r > 0 && path_equal(result, temp)); + ASSERT_OK_POSITIVE(chase(p, temp, 0, &result, NULL)); + ASSERT_PATH_EQ(result, temp); result = mfree(result); p = strjoina(temp, "/6dotsusr"); - ASSERT_OK(symlink("../../../usr", p)); + ASSERT_OK_ERRNO(symlink("../../../usr", p)); - r = chase(p, temp, 0, &result, NULL); - assert_se(r > 0 && path_equal(result, q)); + ASSERT_OK_POSITIVE(chase(p, temp, 0, &result, NULL)); + ASSERT_PATH_EQ(result, q); result = mfree(result); p = strjoina(temp, "/top/8dotsusr"); - ASSERT_OK(symlink("../../../../usr", p)); + ASSERT_OK_ERRNO(symlink("../../../../usr", p)); - r = chase(p, temp, 0, &result, NULL); - assert_se(r > 0 && path_equal(result, q)); + ASSERT_OK_POSITIVE(chase(p, temp, 0, &result, NULL)); + ASSERT_PATH_EQ(result, q); result = mfree(result); /* Paths that contain repeated slashes */ p = strjoina(temp, "/slashslash"); - ASSERT_OK(symlink("///usr///", p)); + ASSERT_OK_ERRNO(symlink("///usr///", p)); - r = chase(p, NULL, 0, &result, NULL); - assert_se(r > 0); - assert_se(path_equal(result, "/usr")); + ASSERT_OK_POSITIVE(chase(p, NULL, 0, &result, NULL)); + ASSERT_PATH_EQ(result, "/usr"); ASSERT_STREQ(result, "/usr"); /* we guarantee that we drop redundant slashes */ result = mfree(result); - r = chase(p, temp, 0, &result, NULL); - assert_se(r > 0); - assert_se(path_equal(result, q)); + ASSERT_OK_POSITIVE(chase(p, temp, 0, &result, NULL)); + ASSERT_PATH_EQ(result, q); result = mfree(result); /* Paths underneath the "root" with different UIDs while using CHASE_SAFE */ if (geteuid() == 0 && !userns_has_single_user()) { p = strjoina(temp, "/user"); - ASSERT_OK(mkdir(p, 0755)); - ASSERT_OK(chown(p, UID_NOBODY, GID_NOBODY)); + ASSERT_OK_ERRNO(mkdir(p, 0755)); + ASSERT_OK_ERRNO(chown(p, UID_NOBODY, GID_NOBODY)); q = strjoina(temp, "/user/root"); - ASSERT_OK(mkdir(q, 0755)); + ASSERT_OK_ERRNO(mkdir(q, 0755)); p = strjoina(q, "/link"); - ASSERT_OK(symlink("/", p)); + ASSERT_OK_ERRNO(symlink("/", p)); /* Fail when user-owned directories contain root-owned subdirectories. */ - r = chase(p, temp, CHASE_SAFE, &result, NULL); - assert_se(r == -ENOLINK); + ASSERT_ERROR(chase(p, temp, CHASE_SAFE, &result, NULL), ENOLINK); result = mfree(result); /* Allow this when the user-owned directories are all in the "root". */ - r = chase(p, q, CHASE_SAFE, &result, NULL); - assert_se(r > 0); + ASSERT_OK_POSITIVE(chase(p, q, CHASE_SAFE, &result, NULL)); result = mfree(result); } /* Paths using . */ - r = chase("/etc/./.././", NULL, 0, &result, NULL); - assert_se(r > 0); - assert_se(path_equal(result, "/")); + ASSERT_OK_POSITIVE(chase("/etc/./.././", NULL, 0, &result, NULL)); + ASSERT_PATH_EQ(result, "/"); result = mfree(result); - r = chase("/etc/./.././", "/etc", 0, &result, NULL); - assert_se(r > 0 && path_equal(result, "/etc")); + ASSERT_OK_POSITIVE(chase("/etc/./.././", "/etc", 0, &result, NULL)); + ASSERT_PATH_EQ(result, "/etc"); result = mfree(result); - r = chase("/../.././//../../etc", NULL, 0, &result, NULL); - assert_se(r > 0); + ASSERT_OK_POSITIVE(chase("/../.././//../../etc", NULL, 0, &result, NULL)); ASSERT_STREQ(result, "/etc"); result = mfree(result); - r = chase("/../.././//../../test-chase.fsldajfl", NULL, CHASE_NONEXISTENT, &result, NULL); - assert_se(r == 0); + ASSERT_OK_ZERO(chase("/../.././//../../test-chase.fsldajfl", NULL, CHASE_NONEXISTENT, &result, NULL)); ASSERT_STREQ(result, "/test-chase.fsldajfl"); result = mfree(result); - r = chase("/../.././//../../etc", "/", CHASE_PREFIX_ROOT, &result, NULL); - assert_se(r > 0); + ASSERT_OK_POSITIVE(chase("/../.././//../../etc", "/", CHASE_PREFIX_ROOT, &result, NULL)); ASSERT_STREQ(result, "/etc"); result = mfree(result); - r = chase("/../.././//../../test-chase.fsldajfl", "/", CHASE_PREFIX_ROOT|CHASE_NONEXISTENT, &result, NULL); - assert_se(r == 0); + ASSERT_OK_ZERO(chase("/../.././//../../test-chase.fsldajfl", "/", CHASE_PREFIX_ROOT|CHASE_NONEXISTENT, &result, NULL)); ASSERT_STREQ(result, "/test-chase.fsldajfl"); result = mfree(result); - r = chase("/.path/with/dot", temp, CHASE_PREFIX_ROOT|CHASE_NONEXISTENT, &result, NULL); - ASSERT_OK(r); + ASSERT_OK(chase("/.path/with/dot", temp, CHASE_PREFIX_ROOT|CHASE_NONEXISTENT, &result, NULL)); q = strjoina(temp, "/.path/with/dot"); ASSERT_STREQ(result, q); result = mfree(result); - r = chase("/etc/machine-id/foo", NULL, 0, &result, NULL); - assert_se(IN_SET(r, -ENOTDIR, -ENOENT)); + ASSERT_TRUE(IN_SET(chase("/etc/machine-id/foo", NULL, 0, &result, NULL), -ENOTDIR, -ENOENT)); result = mfree(result); /* Path that loops back to self */ p = strjoina(temp, "/recursive-symlink"); - ASSERT_OK(symlink("recursive-symlink", p)); - r = chase(p, NULL, 0, &result, NULL); - assert_se(r == -ELOOP); + ASSERT_OK_ERRNO(symlink("recursive-symlink", p)); + ASSERT_ERROR(chase(p, NULL, 0, &result, NULL), ELOOP); /* Path which doesn't exist */ p = strjoina(temp, "/idontexist"); - r = chase(p, NULL, 0, &result, NULL); - assert_se(r == -ENOENT); - - r = chase(p, NULL, CHASE_NONEXISTENT, &result, NULL); - assert_se(r == 0); - assert_se(path_equal(result, p)); + ASSERT_ERROR(chase(p, NULL, 0, &result, NULL), ENOENT); + ASSERT_OK_ZERO(chase(p, NULL, CHASE_NONEXISTENT, &result, NULL)); + ASSERT_PATH_EQ(result, p); result = mfree(result); p = strjoina(temp, "/idontexist/meneither"); - r = chase(p, NULL, 0, &result, NULL); - assert_se(r == -ENOENT); + ASSERT_ERROR(chase(p, NULL, 0, &result, NULL), ENOENT); - r = chase(p, NULL, CHASE_NONEXISTENT, &result, NULL); - assert_se(r == 0); - assert_se(path_equal(result, p)); + ASSERT_OK_ZERO(chase(p, NULL, CHASE_NONEXISTENT, &result, NULL)); + ASSERT_PATH_EQ(result, p); result = mfree(result); /* Relative paths */ ASSERT_OK(safe_getcwd(&pwd)); - ASSERT_OK(chdir(temp)); + ASSERT_OK_ERRNO(chdir(temp)); p = "this/is/a/relative/path"; - r = chase(p, NULL, CHASE_NONEXISTENT, &result, NULL); - assert_se(r == 0); + ASSERT_OK_ZERO(chase(p, NULL, CHASE_NONEXISTENT, &result, NULL)); p = strjoina(temp, "/", p); - assert_se(path_equal(result, p)); + ASSERT_PATH_EQ(result, p); result = mfree(result); p = "this/is/a/relative/path"; - r = chase(p, temp, CHASE_NONEXISTENT, &result, NULL); - assert_se(r == 0); + ASSERT_OK_ZERO(chase(p, temp, CHASE_NONEXISTENT, &result, NULL)); p = strjoina(temp, "/", p); - assert_se(path_equal(result, p)); + ASSERT_PATH_EQ(result, p); result = mfree(result); - assert_se(chdir(pwd) >= 0); + ASSERT_OK_ERRNO(chdir(pwd)); /* Path which doesn't exist, but contains weird stuff */ p = strjoina(temp, "/idontexist/.."); - r = chase(p, NULL, 0, &result, NULL); - assert_se(r == -ENOENT); + ASSERT_ERROR(chase(p, NULL, 0, &result, NULL), ENOENT); - r = chase(p, NULL, CHASE_NONEXISTENT, &result, NULL); - assert_se(r == -ENOENT); + ASSERT_ERROR(chase(p, NULL, CHASE_NONEXISTENT, &result, NULL), ENOENT); p = strjoina(temp, "/target"); q = strjoina(temp, "/top"); - assert_se(symlink(q, p) >= 0); + ASSERT_OK_ERRNO(symlink(q, p)); p = strjoina(temp, "/target/idontexist"); - r = chase(p, NULL, 0, &result, NULL); - assert_se(r == -ENOENT); + ASSERT_ERROR(chase(p, NULL, 0, &result, NULL), ENOENT); if (geteuid() == 0 && !userns_has_single_user()) { p = strjoina(temp, "/priv1"); - ASSERT_OK(mkdir(p, 0755)); + ASSERT_OK_ERRNO(mkdir(p, 0755)); q = strjoina(p, "/priv2"); - ASSERT_OK(mkdir(q, 0755)); + ASSERT_OK_ERRNO(mkdir(q, 0755)); ASSERT_OK(chase(q, NULL, CHASE_SAFE, NULL, NULL)); - ASSERT_OK(chown(q, UID_NOBODY, GID_NOBODY)); + ASSERT_OK_ERRNO(chown(q, UID_NOBODY, GID_NOBODY)); ASSERT_OK(chase(q, NULL, CHASE_SAFE, NULL, NULL)); ASSERT_OK(chown(p, UID_NOBODY, GID_NOBODY)); ASSERT_OK(chase(q, NULL, CHASE_SAFE, NULL, NULL)); - assert_se(chown(q, 0, 0) >= 0); - assert_se(chase(q, NULL, CHASE_SAFE, NULL, NULL) == -ENOLINK); + ASSERT_OK_ERRNO(chown(q, 0, 0)); + ASSERT_ERROR(chase(q, NULL, CHASE_SAFE, NULL, NULL), ENOLINK); - ASSERT_OK(rmdir(q)); - ASSERT_OK(symlink("/etc/passwd", q)); - assert_se(chase(q, NULL, CHASE_SAFE, NULL, NULL) == -ENOLINK); + ASSERT_OK_ERRNO(rmdir(q)); + ASSERT_OK_ERRNO(symlink("/etc/passwd", q)); + ASSERT_ERROR(chase(q, NULL, CHASE_SAFE, NULL, NULL), ENOLINK); - assert_se(chown(p, 0, 0) >= 0); + ASSERT_OK_ERRNO(chown(p, 0, 0)); ASSERT_OK(chase(q, NULL, CHASE_SAFE, NULL, NULL)); } p = strjoina(temp, "/machine-id-test"); - ASSERT_OK(symlink("/usr/../etc/./machine-id", p)); + ASSERT_OK_ERRNO(symlink("/usr/../etc/./machine-id", p)); - r = chase(p, NULL, 0, NULL, &pfd); - if (r != -ENOENT && sd_id128_get_machine(NULL) >= 0) { + if (chase(p, NULL, 0, NULL, &pfd) != -ENOENT && sd_id128_get_machine(NULL) >= 0) { _cleanup_close_ int fd = -EBADF; sd_id128_t a, b; @@ -360,112 +324,187 @@ TEST(chase) { ASSERT_OK(id128_read_fd(fd, ID128_FORMAT_PLAIN, &a)); ASSERT_OK(sd_id128_get_machine(&b)); - assert_se(sd_id128_equal(a, b)); + ASSERT_TRUE(sd_id128_equal(a, b)); } - assert_se(lstat(p, &st) >= 0); - r = chase_and_unlink(p, NULL, 0, 0, &result); - assert_se(r == 0); - assert_se(path_equal(result, p)); + ASSERT_OK_ERRNO(lstat(p, &st)); + ASSERT_OK_ZERO(chase_and_unlink(p, NULL, 0, 0, &result)); + ASSERT_PATH_EQ(result, p); result = mfree(result); - assert_se(lstat(p, &st) == -1 && errno == ENOENT); + ASSERT_ERROR_ERRNO(lstat(p, &st), ENOENT); /* Test CHASE_NOFOLLOW */ p = strjoina(temp, "/target"); q = strjoina(temp, "/symlink"); - assert_se(symlink(p, q) >= 0); - r = chase(q, NULL, CHASE_NOFOLLOW, &result, &pfd); - ASSERT_OK(r); - ASSERT_OK(pfd); - assert_se(path_equal(result, q)); - ASSERT_OK(fstat(pfd, &st)); - assert_se(S_ISLNK(st.st_mode)); + ASSERT_OK_ERRNO(symlink(p, q)); + ASSERT_OK(chase(q, NULL, CHASE_NOFOLLOW, &result, &pfd)); + ASSERT_PATH_EQ(result, q); + ASSERT_OK_ERRNO(fstat(pfd, &st)); + ASSERT_TRUE(S_ISLNK(st.st_mode)); result = mfree(result); pfd = safe_close(pfd); /* s1 -> s2 -> nonexistent */ q = strjoina(temp, "/s1"); - ASSERT_OK(symlink("s2", q)); + ASSERT_OK_ERRNO(symlink("s2", q)); p = strjoina(temp, "/s2"); - ASSERT_OK(symlink("nonexistent", p)); - r = chase(q, NULL, CHASE_NOFOLLOW, &result, &pfd); - ASSERT_OK(r); - ASSERT_OK(pfd); - assert_se(path_equal(result, q)); - ASSERT_OK(fstat(pfd, &st)); - assert_se(S_ISLNK(st.st_mode)); + ASSERT_OK_ERRNO(symlink("nonexistent", p)); + ASSERT_OK(chase(q, NULL, CHASE_NOFOLLOW, &result, &pfd)); + ASSERT_PATH_EQ(result, q); + ASSERT_OK_ERRNO(fstat(pfd, &st)); + ASSERT_TRUE(S_ISLNK(st.st_mode)); result = mfree(result); pfd = safe_close(pfd); /* Test CHASE_STEP */ p = strjoina(temp, "/start"); - r = chase(p, NULL, CHASE_STEP, &result, NULL); - assert_se(r == 0); + ASSERT_OK_ZERO(chase(p, NULL, CHASE_STEP, &result, NULL)); p = strjoina(temp, "/top/dot/dotdota"); ASSERT_STREQ(p, result); result = mfree(result); - r = chase(p, NULL, CHASE_STEP, &result, NULL); - assert_se(r == 0); + ASSERT_OK_ZERO(chase(p, NULL, CHASE_STEP, &result, NULL)); p = strjoina(temp, "/top/dotdota"); ASSERT_STREQ(p, result); result = mfree(result); - r = chase(p, NULL, CHASE_STEP, &result, NULL); - assert_se(r == 0); + ASSERT_OK_ZERO(chase(p, NULL, CHASE_STEP, &result, NULL)); p = strjoina(temp, "/top/../a"); ASSERT_STREQ(p, result); result = mfree(result); - r = chase(p, NULL, CHASE_STEP, &result, NULL); - assert_se(r == 0); + ASSERT_OK_ZERO(chase(p, NULL, CHASE_STEP, &result, NULL)); p = strjoina(temp, "/a"); ASSERT_STREQ(p, result); result = mfree(result); - r = chase(p, NULL, CHASE_STEP, &result, NULL); - assert_se(r == 0); + ASSERT_OK_ZERO(chase(p, NULL, CHASE_STEP, &result, NULL)); p = strjoina(temp, "/b"); ASSERT_STREQ(p, result); result = mfree(result); - r = chase(p, NULL, CHASE_STEP, &result, NULL); - assert_se(r == 0); + ASSERT_OK_ZERO(chase(p, NULL, CHASE_STEP, &result, NULL)); ASSERT_STREQ(result, "/usr"); result = mfree(result); - r = chase("/usr", NULL, CHASE_STEP, &result, NULL); - assert_se(r > 0); + ASSERT_OK_POSITIVE(chase("/usr", NULL, CHASE_STEP, &result, NULL)); ASSERT_STREQ(result, "/usr"); result = mfree(result); /* Make sure that symlinks in the "root" path are not resolved, but those below are */ p = strjoina("/etc/..", temp, "/self"); - assert_se(symlink(".", p) >= 0); + ASSERT_OK_ERRNO(symlink(".", p)); q = strjoina(p, "/top/dot/dotdota"); - r = chase(q, p, 0, &result, NULL); - assert_se(r > 0); - assert_se(path_equal(path_startswith(result, p), "usr")); + ASSERT_OK_POSITIVE(chase(q, p, 0, &result, NULL)); + ASSERT_PATH_EQ(path_startswith(result, p), "usr"); result = mfree(result); /* Test CHASE_PROHIBIT_SYMLINKS */ - assert_se(chase("top/dot", temp, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, NULL, NULL) == -EREMCHG); - assert_se(chase("top/dot", temp, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS|CHASE_WARN, NULL, NULL) == -EREMCHG); - assert_se(chase("top/dotdot", temp, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, NULL, NULL) == -EREMCHG); - assert_se(chase("top/dotdot", temp, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS|CHASE_WARN, NULL, NULL) == -EREMCHG); - assert_se(chase("top/dot/dot", temp, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, NULL, NULL) == -EREMCHG); - assert_se(chase("top/dot/dot", temp, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS|CHASE_WARN, NULL, NULL) == -EREMCHG); + ASSERT_ERROR(chase("top/dot", temp, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, NULL, NULL), EREMCHG); + ASSERT_ERROR(chase("top/dot", temp, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS|CHASE_WARN, NULL, NULL), EREMCHG); + ASSERT_ERROR(chase("top/dotdot", temp, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, NULL, NULL), EREMCHG); + ASSERT_ERROR(chase("top/dotdot", temp, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS|CHASE_WARN, NULL, NULL), EREMCHG); + ASSERT_ERROR(chase("top/dot/dot", temp, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, NULL, NULL), EREMCHG); + ASSERT_ERROR(chase("top/dot/dot", temp, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS|CHASE_WARN, NULL, NULL), EREMCHG); cleanup: ASSERT_OK(rm_rf(temp, REMOVE_ROOT|REMOVE_PHYSICAL)); } +TEST(chase_and_open) { + _cleanup_free_ char *result = NULL; + _cleanup_close_ int fd = -EBADF; + + /* Test chase_and_open() with various CHASE_PARENT / CHASE_EXTRACT_FILENAME combinations. */ + + /* No CHASE_PARENT, no CHASE_EXTRACT_FILENAME, with ret_path — opens the target, returns full path. */ + fd = ASSERT_OK(chase_and_open("/usr/lib", NULL, 0, O_PATH|O_DIRECTORY|O_CLOEXEC, &result)); + ASSERT_OK(fd_verify_directory(fd)); + ASSERT_STREQ(result, "/usr/lib"); + fd = safe_close(fd); + result = mfree(result); + + /* CHASE_PARENT with ret_path — opens parent dir, returns full path including final component. */ + fd = ASSERT_OK(chase_and_open("/usr/lib", NULL, CHASE_PARENT, O_PATH|O_DIRECTORY|O_CLOEXEC, &result)); + ASSERT_OK(fd_verify_directory(fd)); + ASSERT_OK_ERRNO(faccessat(fd, "lib", F_OK, 0)); + ASSERT_STREQ(result, "/usr/lib"); + fd = safe_close(fd); + result = mfree(result); + + /* CHASE_PARENT|CHASE_EXTRACT_FILENAME — opens parent dir, returns just the filename. */ + fd = ASSERT_OK(chase_and_open("/usr/lib", NULL, CHASE_PARENT|CHASE_EXTRACT_FILENAME, O_PATH|O_DIRECTORY|O_CLOEXEC, &result)); + ASSERT_OK(fd_verify_directory(fd)); + ASSERT_OK_ERRNO(faccessat(fd, "lib", F_OK, 0)); + ASSERT_STREQ(result, "lib"); + fd = safe_close(fd); + result = mfree(result); + + /* CHASE_EXTRACT_FILENAME only — opens the target itself, returns just the filename. */ + fd = ASSERT_OK(chase_and_open("/usr/lib", NULL, CHASE_EXTRACT_FILENAME, O_PATH|O_DIRECTORY|O_CLOEXEC, &result)); + ASSERT_OK(fd_verify_directory(fd)); + ASSERT_STREQ(result, "lib"); + fd = safe_close(fd); + result = mfree(result); + + /* CHASE_EXTRACT_FILENAME on a regular file (regression test for a bug where chase_and_open() + * reopened the parent directory instead of the target file). */ + fd = ASSERT_OK(chase_and_open("/etc/os-release", NULL, CHASE_EXTRACT_FILENAME, O_PATH|O_CLOEXEC, &result)); + ASSERT_STREQ(result, "os-release"); + ASSERT_OK(fd_verify_regular(fd)); + fd = safe_close(fd); + result = mfree(result); + + /* CHASE_PARENT through a symlink — symlink is followed, parent of the target is opened. */ + fd = ASSERT_OK(chase_and_open("/etc/os-release", NULL, CHASE_PARENT, O_PATH|O_DIRECTORY|O_CLOEXEC, &result)); + ASSERT_OK(fd_verify_directory(fd)); + ASSERT_NOT_NULL(result); + fd = safe_close(fd); + result = mfree(result); + + /* CHASE_PARENT|CHASE_NOFOLLOW through a symlink — symlink is NOT followed, parent of the + * symlink is opened. */ + fd = ASSERT_OK(chase_and_open("/etc/os-release", NULL, CHASE_PARENT|CHASE_NOFOLLOW, O_PATH|O_DIRECTORY|O_CLOEXEC, &result)); + ASSERT_OK(fd_verify_directory(fd)); + ASSERT_OK_ERRNO(faccessat(fd, "os-release", F_OK, AT_SYMLINK_NOFOLLOW)); + ASSERT_STREQ(result, "/etc/os-release"); + fd = safe_close(fd); + result = mfree(result); + + /* CHASE_PARENT|CHASE_NOFOLLOW|CHASE_EXTRACT_FILENAME through a symlink — parent of the symlink + * is opened, returns just the symlink name. */ + fd = ASSERT_OK(chase_and_open("/etc/os-release", NULL, CHASE_PARENT|CHASE_NOFOLLOW|CHASE_EXTRACT_FILENAME, O_PATH|O_DIRECTORY|O_CLOEXEC, &result)); + ASSERT_OK(fd_verify_directory(fd)); + ASSERT_OK_ERRNO(faccessat(fd, "os-release", F_OK, AT_SYMLINK_NOFOLLOW)); + ASSERT_STREQ(result, "os-release"); + fd = safe_close(fd); + result = mfree(result); + + /* When the resolved path equals the root directory itself, the filename should be "." — not + * the basename of the root directory. This is the edge case that chase_extract_filename() + * handles by stripping the root prefix before extracting, which plain path_extract_filename() + * would get wrong. */ + _cleanup_(rm_rf_physical_and_freep) char *tmpdir = NULL; + _cleanup_close_ int tfd = -EBADF; + + tfd = ASSERT_OK(mkdtemp_open(NULL, 0, &tmpdir)); + /* Create a symlink to "/" — when chased under tmpdir as root, it resolves to tmpdir itself. */ + ASSERT_OK_ERRNO(symlinkat("/", tfd, "to_root")); + + _cleanup_free_ char *link_path = ASSERT_NOT_NULL(path_join(tmpdir, "to_root")); + fd = ASSERT_OK(chase_and_open(link_path, tmpdir, 0, O_PATH|O_DIRECTORY|O_CLOEXEC, &result)); + ASSERT_OK(fd_verify_directory(fd)); + ASSERT_PATH_EQ(result, tmpdir); + fd = safe_close(fd); + result = mfree(result); +} + TEST(chaseat) { _cleanup_(rm_rf_physical_and_freep) char *t = NULL; - _cleanup_close_ int tfd = -EBADF, fd = -EBADF; + _cleanup_close_ int tfd = -EBADF, fd = -EBADF, fd2 = -EBADF; _cleanup_free_ char *result = NULL; _cleanup_closedir_ DIR *dir = NULL; _cleanup_fclose_ FILE *f = NULL; @@ -474,13 +513,13 @@ TEST(chaseat) { ASSERT_OK(tfd = mkdtemp_open(NULL, 0, &t)); - /* Test that AT_FDCWD with CHASE_AT_RESOLVE_IN_ROOT resolves against / and not the current working + /* Test that AT_FDCWD resolves against / and not the current working * directory. */ - ASSERT_OK(symlinkat("/usr", tfd, "abc")); + ASSERT_OK_ERRNO(symlinkat("/usr", tfd, "abc")); p = strjoina(t, "/abc"); - ASSERT_OK(chaseat(AT_FDCWD, p, CHASE_AT_RESOLVE_IN_ROOT, &result, NULL)); + ASSERT_OK(chaseat(XAT_FDROOT, AT_FDCWD, p, 0, &result, NULL)); ASSERT_STREQ(result, "/usr"); result = mfree(result); @@ -489,11 +528,24 @@ TEST(chaseat) { fd = open("/", O_CLOEXEC | O_DIRECTORY | O_PATH); ASSERT_OK(fd); - ASSERT_OK(chaseat(fd, p, 0, &result, NULL)); + ASSERT_OK(chaseat(XAT_FDROOT, fd, p, 0, &result, NULL)); ASSERT_STREQ(result, "/usr"); result = mfree(result); - ASSERT_OK(chaseat(fd, p, CHASE_AT_RESOLVE_IN_ROOT, &result, NULL)); + /* (XAT_FDROOT, fd-of-/, relative): fd points to "/" which is the host root, so root_fd is + * normalized to XAT_FDROOT internally. A relative path resolves from "/". Result is absolute. */ + ASSERT_OK(chaseat(XAT_FDROOT, fd, "usr", 0, &result, &fd2)); + ASSERT_STREQ(result, "/usr"); + ASSERT_TRUE(inode_same_at(fd2, NULL, AT_FDCWD, "/usr", AT_EMPTY_PATH)); + result = mfree(result); + fd2 = safe_close(fd2); + + /* Same without ret_path to exercise the shortcut. */ + ASSERT_OK(chaseat(XAT_FDROOT, fd, "usr", 0, NULL, &fd2)); + ASSERT_TRUE(inode_same_at(fd2, NULL, AT_FDCWD, "/usr", AT_EMPTY_PATH)); + fd2 = safe_close(fd2); + + ASSERT_OK(chaseat(fd, fd, p, 0, &result, NULL)); ASSERT_STREQ(result, "/usr"); result = mfree(result); @@ -501,12 +553,12 @@ TEST(chaseat) { /* Same but with XAT_FDROOT */ _cleanup_close_ int found_fd1 = -EBADF; - ASSERT_OK(chaseat(XAT_FDROOT, p, 0, &result, &found_fd1)); + ASSERT_OK(chaseat(XAT_FDROOT, XAT_FDROOT, p, 0, &result, &found_fd1)); ASSERT_STREQ(result, "/usr"); result = mfree(result); _cleanup_close_ int found_fd2 = -EBADF; - ASSERT_OK(chaseat(XAT_FDROOT, p, CHASE_AT_RESOLVE_IN_ROOT, &result, &found_fd2)); + ASSERT_OK(chaseat(XAT_FDROOT, XAT_FDROOT, p, 0, &result, &found_fd2)); ASSERT_STREQ(result, "/usr"); result = mfree(result); assert(fd_inode_same(found_fd1, found_fd2) > 0); @@ -514,12 +566,12 @@ TEST(chaseat) { /* Do the same XAT_FDROOT tests again, this time without querying the path, so that the open_tree() * shortcut can work */ _cleanup_close_ int found_fd3 = -EBADF; - ASSERT_OK(chaseat(XAT_FDROOT, p, 0, NULL, &found_fd3)); + ASSERT_OK(chaseat(XAT_FDROOT, XAT_FDROOT, p, 0, NULL, &found_fd3)); assert(fd_inode_same(found_fd1, found_fd3) > 0); assert(fd_inode_same(found_fd2, found_fd3) > 0); _cleanup_close_ int found_fd4 = -EBADF; - ASSERT_OK(chaseat(XAT_FDROOT, p, CHASE_AT_RESOLVE_IN_ROOT, NULL, &found_fd4)); + ASSERT_OK(chaseat(XAT_FDROOT, XAT_FDROOT, p, 0, NULL, &found_fd4)); assert(fd_inode_same(found_fd1, found_fd4) > 0); assert(fd_inode_same(found_fd2, found_fd4) > 0); assert(fd_inode_same(found_fd3, found_fd4) > 0); @@ -529,102 +581,138 @@ TEST(chaseat) { found_fd3 = safe_close(found_fd3); found_fd4 = safe_close(found_fd4); + /* (XAT_FDROOT, XAT_FDROOT, relative): relative path from host root. XAT_FDROOT as dir_fd + * redirects to root_fd which is also XAT_FDROOT (/), so "usr" resolves to /usr. Result is + * absolute because root_fd == XAT_FDROOT. */ + ASSERT_OK(chaseat(XAT_FDROOT, XAT_FDROOT, "usr", 0, &result, NULL)); + ASSERT_STREQ(result, "/usr"); + result = mfree(result); + + /* Same without ret_path so the shortcut can fire. */ + ASSERT_OK(chaseat(XAT_FDROOT, XAT_FDROOT, "usr", 0, NULL, &fd)); + ASSERT_TRUE(inode_same_at(fd, NULL, AT_FDCWD, "/usr", AT_EMPTY_PATH)); + fd = safe_close(fd); + + /* (XAT_FDROOT, AT_FDCWD, relative): relative path from current working directory. */ + _cleanup_free_ char *cwd_saved = NULL; + ASSERT_OK(safe_getcwd(&cwd_saved)); + + ASSERT_OK_ERRNO(chdir(t)); + + ASSERT_OK(chaseat(XAT_FDROOT, AT_FDCWD, "abc", 0, &result, &fd)); + ASSERT_TRUE(inode_same_at(fd, NULL, AT_FDCWD, "/usr", AT_EMPTY_PATH)); + ASSERT_STREQ(result, "/usr"); + fd = safe_close(fd); + result = mfree(result); + + /* Same without ret_path to exercise the shortcut. */ + ASSERT_OK(chaseat(XAT_FDROOT, AT_FDCWD, "abc", 0, NULL, &fd)); + ASSERT_TRUE(inode_same_at(fd, NULL, AT_FDCWD, "/usr", AT_EMPTY_PATH)); + fd = safe_close(fd); + + /* A plain file (no symlink indirection) should also work. */ + fd = ASSERT_OK_ERRNO(openat(tfd, "cwd_test", O_CREAT|O_CLOEXEC, 0600)); + fd = safe_close(fd); + + ASSERT_OK(chaseat(XAT_FDROOT, AT_FDCWD, "cwd_test", 0, &result, &fd)); + ASSERT_TRUE(inode_same_at(fd, NULL, tfd, "cwd_test", AT_EMPTY_PATH)); + fd = safe_close(fd); + result = mfree(result); + + ASSERT_OK_ERRNO(chdir(cwd_saved)); + /* 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_OK(chaseat(tfd, "abc", 0, &result, NULL)); + ASSERT_OK(chaseat(XAT_FDROOT, tfd, "abc", 0, &result, NULL)); ASSERT_STREQ(result, "/usr"); result = mfree(result); - ASSERT_OK(chaseat(tfd, "/abc", 0, &result, NULL)); - ASSERT_STREQ(result, "/usr"); - result = mfree(result); + ASSERT_ERROR(chaseat(tfd, tfd, "abc", 0, NULL, NULL), ENOENT); + ASSERT_ERROR(chaseat(tfd, tfd, "/abc", 0, 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, NULL, NULL) == -ENOENT); - - ASSERT_OK(chaseat(tfd, "abc", CHASE_AT_RESOLVE_IN_ROOT | CHASE_NONEXISTENT, &result, NULL)); + ASSERT_OK(chaseat(tfd, tfd, "abc", CHASE_NONEXISTENT, &result, NULL)); ASSERT_STREQ(result, "usr"); result = mfree(result); - ASSERT_OK(chaseat(tfd, "/abc", CHASE_AT_RESOLVE_IN_ROOT | CHASE_NONEXISTENT, &result, NULL)); + ASSERT_OK(chaseat(tfd, tfd, "/abc", CHASE_NONEXISTENT, &result, NULL)); ASSERT_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. */ - ASSERT_OK(fd = openat(tfd, "def", O_CREAT|O_CLOEXEC, 0700)); + fd = ASSERT_OK_ERRNO(openat(tfd, "def", O_CREAT|O_CLOEXEC, 0700)); fd = safe_close(fd); - ASSERT_OK(symlinkat("/def", tfd, "qed")); - ASSERT_OK(chaseat(tfd, "qed", CHASE_AT_RESOLVE_IN_ROOT, &result, NULL)); + ASSERT_OK_ERRNO(symlinkat("/def", tfd, "qed")); + ASSERT_OK(chaseat(tfd, tfd, "qed", 0, &result, NULL)); ASSERT_STREQ(result, "def"); result = mfree(result); - ASSERT_OK(chaseat(tfd, "/qed", CHASE_AT_RESOLVE_IN_ROOT, &result, NULL)); + ASSERT_OK(chaseat(tfd, tfd, "/qed", 0, &result, NULL)); ASSERT_STREQ(result, "def"); result = mfree(result); - /* Valid directory file descriptor without CHASE_AT_RESOLVE_IN_ROOT should resolve symlinks against + /* Valid directory file descriptor should resolve symlinks against * host's root. */ - assert_se(chaseat(tfd, "/qed", 0, NULL, NULL) == -ENOENT); + ASSERT_ERROR(chaseat(XAT_FDROOT, tfd, "/qed", 0, NULL, NULL), ENOENT); /* Test CHASE_PARENT */ - ASSERT_OK(fd = open_mkdir_at(tfd, "chase", O_CLOEXEC, 0755)); - ASSERT_OK(symlinkat("/def", fd, "parent")); + fd = ASSERT_OK(open_mkdir_at(tfd, "chase", O_CLOEXEC, 0755)); + ASSERT_OK_ERRNO(symlinkat("/def", fd, "parent")); fd = safe_close(fd); /* Make sure that when we chase a symlink parent directory, that we chase the parent directory of the * symlink target and not the symlink itself. But if we add CHASE_NOFOLLOW, we get the parent * directory of the symlink itself. */ - ASSERT_OK(chaseat(tfd, "chase/parent", CHASE_PARENT|CHASE_AT_RESOLVE_IN_ROOT, &result, &fd)); - ASSERT_OK(faccessat(fd, "def", F_OK, 0)); + ASSERT_OK(chaseat(tfd, tfd, "chase/parent", CHASE_PARENT, &result, &fd)); + ASSERT_OK_ERRNO(faccessat(fd, "def", F_OK, 0)); ASSERT_STREQ(result, "def"); fd = safe_close(fd); result = mfree(result); - ASSERT_OK(chaseat(tfd, "chase/parent", CHASE_AT_RESOLVE_IN_ROOT|CHASE_PARENT|CHASE_NOFOLLOW, &result, &fd)); - ASSERT_OK(faccessat(fd, "parent", F_OK, AT_SYMLINK_NOFOLLOW)); + ASSERT_OK(chaseat(tfd, tfd, "chase/parent", CHASE_PARENT|CHASE_NOFOLLOW, &result, &fd)); + ASSERT_OK_ERRNO(faccessat(fd, "parent", F_OK, AT_SYMLINK_NOFOLLOW)); ASSERT_STREQ(result, "chase/parent"); fd = safe_close(fd); result = mfree(result); - ASSERT_OK(chaseat(tfd, "chase", CHASE_PARENT|CHASE_AT_RESOLVE_IN_ROOT, &result, &fd)); - ASSERT_OK(faccessat(fd, "chase", F_OK, 0)); + ASSERT_OK(chaseat(tfd, tfd, "chase", CHASE_PARENT, &result, &fd)); + ASSERT_OK_ERRNO(faccessat(fd, "chase", F_OK, 0)); ASSERT_STREQ(result, "chase"); fd = safe_close(fd); result = mfree(result); - ASSERT_OK(chaseat(tfd, "/", CHASE_PARENT|CHASE_AT_RESOLVE_IN_ROOT, &result, NULL)); + ASSERT_OK(chaseat(tfd, tfd, "/", CHASE_PARENT, &result, NULL)); ASSERT_STREQ(result, "."); result = mfree(result); - assert_se(chaseat(tfd, ".", CHASE_PARENT|CHASE_AT_RESOLVE_IN_ROOT, &result, NULL)); + ASSERT_OK(chaseat(tfd, tfd, ".", CHASE_PARENT, &result, NULL)); ASSERT_STREQ(result, "."); result = mfree(result); /* Test CHASE_MKDIR_0755 */ - ASSERT_OK(chaseat(tfd, "m/k/d/i/r", CHASE_MKDIR_0755|CHASE_NONEXISTENT, &result, NULL)); - ASSERT_OK(faccessat(tfd, "m/k/d/i", F_OK, 0)); - assert_se(RET_NERRNO(faccessat(tfd, "m/k/d/i/r", F_OK, 0)) == -ENOENT); + ASSERT_OK(chaseat(XAT_FDROOT, tfd, "m/k/d/i/r", CHASE_MKDIR_0755|CHASE_NONEXISTENT, &result, NULL)); + ASSERT_OK_ERRNO(faccessat(tfd, "m/k/d/i", F_OK, 0)); + ASSERT_ERROR(RET_NERRNO(faccessat(tfd, "m/k/d/i/r", F_OK, 0)), ENOENT); ASSERT_STREQ(result, "m/k/d/i/r"); result = mfree(result); - ASSERT_OK(chaseat(tfd, "m/../q", CHASE_MKDIR_0755|CHASE_NONEXISTENT, &result, NULL)); - ASSERT_OK(faccessat(tfd, "m", F_OK, 0)); - assert_se(RET_NERRNO(faccessat(tfd, "q", F_OK, 0)) == -ENOENT); + ASSERT_OK(chaseat(XAT_FDROOT, tfd, "m/../q", CHASE_MKDIR_0755|CHASE_NONEXISTENT, &result, NULL)); + ASSERT_OK_ERRNO(faccessat(tfd, "m", F_OK, 0)); + ASSERT_ERROR(RET_NERRNO(faccessat(tfd, "q", F_OK, 0)), ENOENT); ASSERT_STREQ(result, "q"); result = mfree(result); - assert_se(chaseat(tfd, "i/../p", CHASE_MKDIR_0755|CHASE_NONEXISTENT, NULL, NULL) == -ENOENT); + ASSERT_ERROR(chaseat(XAT_FDROOT, tfd, "i/../p", CHASE_MKDIR_0755|CHASE_NONEXISTENT, NULL, NULL), ENOENT); /* Test CHASE_MKDIR_0755|CHASE_PARENT — creates intermediate dirs but not the final component */ - ASSERT_OK(chaseat(tfd, "mkp/a/r/e/n/t/file", CHASE_MKDIR_0755|CHASE_PARENT, &result, &fd)); - ASSERT_OK(faccessat(tfd, "mkp/a/r/e/n/t", F_OK, 0)); - assert_se(RET_NERRNO(faccessat(tfd, "mkp/a/r/e/n/t/file", F_OK, 0)) == -ENOENT); + ASSERT_OK(chaseat(XAT_FDROOT, tfd, "mkp/a/r/e/n/t/file", CHASE_MKDIR_0755|CHASE_PARENT, &result, &fd)); + ASSERT_OK_ERRNO(faccessat(tfd, "mkp/a/r/e/n/t", F_OK, 0)); + ASSERT_ERROR(RET_NERRNO(faccessat(tfd, "mkp/a/r/e/n/t/file", F_OK, 0)), ENOENT); ASSERT_OK(fd_verify_directory(fd)); fd = safe_close(fd); ASSERT_STREQ(result, "mkp/a/r/e/n/t/file"); @@ -632,68 +720,133 @@ TEST(chaseat) { /* Test CHASE_EXTRACT_FILENAME */ - ASSERT_OK(chaseat(tfd, "chase/parent", CHASE_AT_RESOLVE_IN_ROOT|CHASE_PARENT|CHASE_NOFOLLOW|CHASE_EXTRACT_FILENAME, &result, &fd)); - ASSERT_OK(faccessat(fd, result, F_OK, AT_SYMLINK_NOFOLLOW)); + ASSERT_OK(chaseat(tfd, tfd, "chase/parent", CHASE_PARENT|CHASE_NOFOLLOW|CHASE_EXTRACT_FILENAME, &result, &fd)); + ASSERT_OK_ERRNO(faccessat(fd, result, F_OK, AT_SYMLINK_NOFOLLOW)); ASSERT_STREQ(result, "parent"); fd = safe_close(fd); result = mfree(result); - ASSERT_OK(chaseat(tfd, "chase", CHASE_PARENT|CHASE_AT_RESOLVE_IN_ROOT|CHASE_EXTRACT_FILENAME, &result, &fd)); - ASSERT_OK(faccessat(fd, result, F_OK, 0)); + ASSERT_OK(chaseat(tfd, tfd, "chase", CHASE_PARENT|CHASE_EXTRACT_FILENAME, &result, &fd)); + ASSERT_OK_ERRNO(faccessat(fd, result, F_OK, 0)); ASSERT_STREQ(result, "chase"); fd = safe_close(fd); result = mfree(result); - ASSERT_OK(chaseat(tfd, "/", CHASE_PARENT|CHASE_AT_RESOLVE_IN_ROOT|CHASE_EXTRACT_FILENAME, &result, NULL)); + ASSERT_OK(chaseat(tfd, tfd, "/", CHASE_PARENT|CHASE_EXTRACT_FILENAME, &result, NULL)); ASSERT_STREQ(result, "."); result = mfree(result); - ASSERT_OK(chaseat(tfd, ".", CHASE_PARENT|CHASE_AT_RESOLVE_IN_ROOT|CHASE_EXTRACT_FILENAME, &result, NULL)); + ASSERT_OK(chaseat(tfd, tfd, ".", CHASE_PARENT|CHASE_EXTRACT_FILENAME, &result, NULL)); ASSERT_STREQ(result, "."); result = mfree(result); - ASSERT_OK(chaseat(tfd, NULL, CHASE_PARENT|CHASE_AT_RESOLVE_IN_ROOT|CHASE_EXTRACT_FILENAME, &result, NULL)); + ASSERT_OK(chaseat(tfd, tfd, NULL, CHASE_PARENT|CHASE_EXTRACT_FILENAME, &result, NULL)); ASSERT_STREQ(result, "."); result = mfree(result); /* Test chase_and_openat() */ - fd = chase_and_openat(tfd, "o/p/e/n/f/i/l/e", CHASE_MKDIR_0755, O_CREAT|O_EXCL|O_CLOEXEC, NULL); - ASSERT_OK(fd); + fd = ASSERT_OK(chase_and_openat(XAT_FDROOT, tfd, "o/p/e/n/f/i/l/e", CHASE_MKDIR_0755, O_CREAT|O_EXCL|O_CLOEXEC, NULL)); ASSERT_OK(fd_verify_regular(fd)); fd = safe_close(fd); - fd = chase_and_openat(tfd, "o/p/e/n/d/i/r", CHASE_MKDIR_0755, O_DIRECTORY|O_CREAT|O_EXCL|O_CLOEXEC, NULL); - ASSERT_OK(fd); + fd = ASSERT_OK(chase_and_openat(XAT_FDROOT, tfd, "o/p/e/n/d/i/r", CHASE_MKDIR_0755, O_DIRECTORY|O_CREAT|O_EXCL|O_CLOEXEC, NULL)); ASSERT_OK(fd_verify_directory(fd)); fd = safe_close(fd); - fd = chase_and_openat(tfd, NULL, CHASE_PARENT|CHASE_EXTRACT_FILENAME, O_PATH|O_DIRECTORY|O_CLOEXEC, &result); - ASSERT_OK(fd); + fd = ASSERT_OK(chase_and_openat(XAT_FDROOT, tfd, NULL, CHASE_PARENT|CHASE_EXTRACT_FILENAME, O_PATH|O_DIRECTORY|O_CLOEXEC, &result)); ASSERT_STREQ(result, "."); fd = safe_close(fd); result = mfree(result); /* Test chase_and_openat() with CHASE_MKDIR_0755|CHASE_PARENT — opens parent dir */ - fd = chase_and_openat(tfd, "mkopen/p/a/r/file.txt", CHASE_MKDIR_0755|CHASE_PARENT, O_RDONLY|O_CLOEXEC, NULL); - ASSERT_OK(fd); + fd = ASSERT_OK(chase_and_openat(XAT_FDROOT, tfd, "mkopen/p/a/r/file.txt", CHASE_MKDIR_0755|CHASE_PARENT, O_RDONLY|O_CLOEXEC, NULL)); ASSERT_OK(fd_verify_directory(fd)); ASSERT_OK(faccessat(tfd, "mkopen/p/a/r", F_OK, 0)); - assert_se(RET_NERRNO(faccessat(tfd, "mkopen/p/a/r/file.txt", F_OK, 0)) == -ENOENT); + ASSERT_ERROR(RET_NERRNO(faccessat(tfd, "mkopen/p/a/r/file.txt", F_OK, 0)), ENOENT); fd = safe_close(fd); /* Test chase_and_openat() with CHASE_MKDIR_0755|CHASE_MUST_BE_DIRECTORY + O_CREAT — creates and opens target dir */ - fd = chase_and_openat(tfd, "mkopen/d/i/r/target", CHASE_MKDIR_0755|CHASE_MUST_BE_DIRECTORY, O_CREAT|O_RDONLY|O_CLOEXEC, NULL); - ASSERT_OK(fd); + fd = ASSERT_OK(chase_and_openat(XAT_FDROOT, tfd, "mkopen/d/i/r/target", CHASE_MKDIR_0755|CHASE_MUST_BE_DIRECTORY, O_CREAT|O_RDONLY|O_CLOEXEC, NULL)); ASSERT_OK(fd_verify_directory(fd)); ASSERT_OK(faccessat(tfd, "mkopen/d/i/r/target", F_OK, 0)); fd = safe_close(fd); + /* Test chase_and_openat() with various CHASE_PARENT / CHASE_EXTRACT_FILENAME combinations */ + + /* No CHASE_PARENT, no CHASE_EXTRACT_FILENAME, with ret_path — opens the target, returns full path. */ + fd = ASSERT_OK(chase_and_openat(XAT_FDROOT, tfd, "o/p/e/n/d/i/r", 0, O_PATH|O_DIRECTORY|O_CLOEXEC, &result)); + ASSERT_OK(fd_verify_directory(fd)); + ASSERT_STREQ(result, "o/p/e/n/d/i/r"); + fd = safe_close(fd); + result = mfree(result); + + /* CHASE_PARENT with ret_path — opens parent dir, returns full path including final component. */ + fd = ASSERT_OK(chase_and_openat(XAT_FDROOT, tfd, "o/p/e/n/d/i/r", CHASE_PARENT, O_PATH|O_DIRECTORY|O_CLOEXEC, &result)); + ASSERT_OK(fd_verify_directory(fd)); + ASSERT_OK_ERRNO(faccessat(fd, "r", F_OK, 0)); + ASSERT_STREQ(result, "o/p/e/n/d/i/r"); + fd = safe_close(fd); + result = mfree(result); + + /* CHASE_PARENT|CHASE_EXTRACT_FILENAME with a real multi-component path — opens parent dir, + * returns just the filename. */ + fd = ASSERT_OK(chase_and_openat(XAT_FDROOT, tfd, "o/p/e/n/d/i/r", CHASE_PARENT|CHASE_EXTRACT_FILENAME, O_PATH|O_DIRECTORY|O_CLOEXEC, &result)); + ASSERT_OK(fd_verify_directory(fd)); + ASSERT_OK_ERRNO(faccessat(fd, "r", F_OK, 0)); + ASSERT_STREQ(result, "r"); + fd = safe_close(fd); + result = mfree(result); + + /* CHASE_EXTRACT_FILENAME only (without CHASE_PARENT) — opens the target itself, returns just + * the filename. */ + fd = ASSERT_OK(chase_and_openat(XAT_FDROOT, tfd, "o/p/e/n/d/i/r", CHASE_EXTRACT_FILENAME, O_PATH|O_DIRECTORY|O_CLOEXEC, &result)); + ASSERT_OK(fd_verify_directory(fd)); + ASSERT_STREQ(result, "r"); + fd = safe_close(fd); + result = mfree(result); + + /* CHASE_PARENT through a symlink — the symlink is followed, parent of the target is opened. + * "chase/parent" where parent→/def: resolves to /def, parent is the root dir. */ + fd = ASSERT_OK(chase_and_openat(tfd, tfd, "chase/parent", CHASE_PARENT, O_PATH|O_DIRECTORY|O_CLOEXEC, &result)); + ASSERT_OK(fd_verify_directory(fd)); + ASSERT_OK_ERRNO(faccessat(fd, "def", F_OK, 0)); + ASSERT_STREQ(result, "def"); + fd = safe_close(fd); + result = mfree(result); + + /* CHASE_PARENT|CHASE_EXTRACT_FILENAME through a symlink — parent of the target is opened, + * returns just the target filename. */ + fd = ASSERT_OK(chase_and_openat(tfd, tfd, "chase/parent", CHASE_PARENT|CHASE_EXTRACT_FILENAME, O_PATH|O_DIRECTORY|O_CLOEXEC, &result)); + ASSERT_OK(fd_verify_directory(fd)); + ASSERT_OK_ERRNO(faccessat(fd, "def", F_OK, 0)); + ASSERT_STREQ(result, "def"); + fd = safe_close(fd); + result = mfree(result); + + /* CHASE_PARENT|CHASE_NOFOLLOW through a symlink — the symlink is NOT followed, parent of the + * symlink is opened. */ + fd = ASSERT_OK(chase_and_openat(tfd, tfd, "chase/parent", CHASE_PARENT|CHASE_NOFOLLOW, O_PATH|O_DIRECTORY|O_CLOEXEC, &result)); + ASSERT_OK(fd_verify_directory(fd)); + ASSERT_OK_ERRNO(faccessat(fd, "parent", F_OK, AT_SYMLINK_NOFOLLOW)); + ASSERT_STREQ(result, "chase/parent"); + fd = safe_close(fd); + result = mfree(result); + + /* CHASE_PARENT|CHASE_NOFOLLOW|CHASE_EXTRACT_FILENAME through a symlink — parent of the symlink + * is opened, returns just the symlink name. */ + fd = ASSERT_OK(chase_and_openat(tfd, tfd, "chase/parent", CHASE_PARENT|CHASE_NOFOLLOW|CHASE_EXTRACT_FILENAME, O_PATH|O_DIRECTORY|O_CLOEXEC, &result)); + ASSERT_OK(fd_verify_directory(fd)); + ASSERT_OK_ERRNO(faccessat(fd, "parent", F_OK, AT_SYMLINK_NOFOLLOW)); + ASSERT_STREQ(result, "parent"); + fd = safe_close(fd); + result = mfree(result); + /* Test chase_and_openatdir() */ - ASSERT_OK(chase_and_opendirat(tfd, "o/p/e/n/d/i", 0, &result, &dir)); + ASSERT_OK(chase_and_opendirat(XAT_FDROOT, tfd, "o/p/e/n/d/i", 0, &result, &dir)); FOREACH_DIRENT(de, dir, assert_not_reached()) ASSERT_STREQ(de->d_name, "r"); ASSERT_STREQ(result, "o/p/e/n/d/i"); @@ -701,57 +854,165 @@ TEST(chaseat) { /* Test chase_and_statat() */ - ASSERT_OK(chase_and_statat(tfd, "o/p", 0, &result, &st)); + ASSERT_OK(chase_and_statat(XAT_FDROOT, tfd, "o/p", 0, &result, &st)); ASSERT_OK(stat_verify_directory(&st)); ASSERT_STREQ(result, "o/p"); result = mfree(result); /* Test chase_and_accessat() */ - ASSERT_OK(chase_and_accessat(tfd, "o/p/e", 0, F_OK, &result)); + ASSERT_OK(chase_and_accessat(XAT_FDROOT, tfd, "o/p/e", 0, F_OK, &result)); ASSERT_STREQ(result, "o/p/e"); result = mfree(result); /* Test chase_and_fopenat_unlocked() */ - ASSERT_OK(chase_and_fopenat_unlocked(tfd, "o/p/e/n/f/i/l/e", 0, "re", &result, &f)); - assert_se(fread(&(char[1]) {}, 1, 1, f) == 0); - assert_se(feof(f)); + ASSERT_OK(chase_and_fopenat_unlocked(XAT_FDROOT, tfd, "o/p/e/n/f/i/l/e", 0, "re", &result, &f)); + ASSERT_EQ(fread(&(char[1]) {}, 1, 1, f), 0u); + ASSERT_TRUE(feof(f)); f = safe_fclose(f); ASSERT_STREQ(result, "o/p/e/n/f/i/l/e"); result = mfree(result); /* Test chase_and_unlinkat() */ - ASSERT_OK(chase_and_unlinkat(tfd, "o/p/e/n/f/i/l/e", 0, 0, &result)); + ASSERT_OK(chase_and_unlinkat(XAT_FDROOT, tfd, "o/p/e/n/f/i/l/e", 0, 0, &result)); ASSERT_STREQ(result, "o/p/e/n/f/i/l/e"); result = mfree(result); /* Test chase_and_open_parent_at() */ - ASSERT_OK(fd = chase_and_open_parent_at(tfd, "chase/parent", CHASE_AT_RESOLVE_IN_ROOT|CHASE_NOFOLLOW, &result)); - ASSERT_OK(faccessat(fd, result, F_OK, AT_SYMLINK_NOFOLLOW)); + fd = ASSERT_OK(chase_and_open_parent_at(XAT_FDROOT, tfd, "chase/parent", CHASE_NOFOLLOW, &result)); + ASSERT_OK_ERRNO(faccessat(fd, result, F_OK, AT_SYMLINK_NOFOLLOW)); ASSERT_STREQ(result, "parent"); fd = safe_close(fd); result = mfree(result); - ASSERT_OK(fd = chase_and_open_parent_at(tfd, "chase", CHASE_AT_RESOLVE_IN_ROOT, &result)); - ASSERT_OK(faccessat(fd, result, F_OK, 0)); + fd = ASSERT_OK(chase_and_open_parent_at(XAT_FDROOT, tfd, "chase", 0, &result)); + ASSERT_OK_ERRNO(faccessat(fd, result, F_OK, 0)); ASSERT_STREQ(result, "chase"); fd = safe_close(fd); result = mfree(result); - ASSERT_OK(fd = chase_and_open_parent_at(tfd, "/", CHASE_AT_RESOLVE_IN_ROOT, &result)); + fd = ASSERT_OK(chase_and_open_parent_at(XAT_FDROOT, tfd, "/", 0, &result)); ASSERT_STREQ(result, "."); fd = safe_close(fd); result = mfree(result); - ASSERT_OK(fd = chase_and_open_parent_at(tfd, ".", CHASE_AT_RESOLVE_IN_ROOT, &result)); + fd = ASSERT_OK(chase_and_open_parent_at(XAT_FDROOT, tfd, ".", 0, &result)); ASSERT_STREQ(result, "."); fd = safe_close(fd); result = mfree(result); } +TEST(chaseat_separate_root_and_dir) { + _cleanup_(rm_rf_physical_and_freep) char *t = NULL; + _cleanup_close_ int root_fd = -EBADF, sub_fd = -EBADF, fd = -EBADF; + _cleanup_free_ char *result = NULL; + + /* Exercise chaseat() with root_fd != dir_fd. The root marks the chroot boundary (symlinks may + * not escape it, absolute symlinks resolve to it), while dir_fd is the starting directory for + * relative paths. */ + + root_fd = ASSERT_OK(mkdtemp_open(NULL, 0, &t)); + + /* Create a file at the root and a subdirectory containing another file. */ + ASSERT_OK_ERRNO(mkdirat(root_fd, "sub", 0755)); + sub_fd = ASSERT_OK_ERRNO(openat(root_fd, "sub", O_CLOEXEC|O_DIRECTORY|O_PATH)); + + fd = ASSERT_OK_ERRNO(openat(root_fd, "outside", O_CREAT|O_CLOEXEC, 0600)); + fd = safe_close(fd); + + fd = ASSERT_OK_ERRNO(openat(sub_fd, "inside", O_CREAT|O_CLOEXEC, 0600)); + fd = safe_close(fd); + + /* Relative lookup from sub_fd under root_fd finds sub's own files. */ + ASSERT_OK(chaseat(root_fd, sub_fd, "inside", 0, &result, NULL)); + ASSERT_STREQ(result, "inside"); + result = mfree(result); + + /* Absolute path with dir_fd=sub_fd and root_fd set: path is relative to root_fd so "/inside" finds + * nothing. */ + ASSERT_ERROR(chaseat(root_fd, sub_fd, "/inside", 0, &result, NULL), ENOENT); + ASSERT_OK_ZERO(chaseat(root_fd, sub_fd, "/inside", CHASE_NONEXISTENT, &result, NULL)); + result = mfree(result); + + /* "../outside" from sub_fd goes up one level (within root), finds root's file. */ + ASSERT_OK(chaseat(root_fd, sub_fd, "../outside", 0, &result, NULL)); + ASSERT_STREQ(result, "../outside"); + result = mfree(result); + + /* "../../../outside" cannot escape above root_fd — clamped. Still resolves to root's file. */ + ASSERT_OK(chaseat(root_fd, sub_fd, "../../../outside", 0, &result, NULL)); + ASSERT_STREQ(result, "../outside"); + result = mfree(result); + + /* Absolute symlink inside sub pointing at "/outside" — with root_fd set, /outside resolves to + * root_fd/outside, not the host's /outside. */ + ASSERT_OK_ERRNO(symlinkat("/outside", sub_fd, "escape_abs")); + ASSERT_OK(chaseat(root_fd, sub_fd, "escape_abs", 0, &result, &fd)); + ASSERT_TRUE(inode_same_at(fd, NULL, root_fd, "outside", AT_EMPTY_PATH)); + ASSERT_STREQ(result, "/outside"); + result = mfree(result); + fd = safe_close(fd); + + /* Relative symlink trying to escape via many ".." — also clamped to root. */ + ASSERT_OK_ERRNO(symlinkat("../../../../../outside", sub_fd, "escape_rel")); + ASSERT_OK(chaseat(root_fd, sub_fd, "escape_rel", 0, &result, NULL)); + ASSERT_STREQ(result, "../outside"); + result = mfree(result); + + /* Symlink pointing to an absolute host path that does NOT exist under our root must fail, not + * leak to the host. /etc almost always exists on the host; under our tmp root it doesn't. */ + ASSERT_OK_ERRNO(symlinkat("/etc", sub_fd, "escape_host")); + ASSERT_ERROR(chaseat(root_fd, sub_fd, "escape_host/hosts", 0, NULL, NULL), ENOENT); + + /* Chasing just ".." from root_fd itself stays at root. */ + ASSERT_OK(chaseat(root_fd, root_fd, "..", 0, &result, NULL)); + ASSERT_STREQ(result, "."); + result = mfree(result); + + /* (real-fd, XAT_FDROOT, relative): XAT_FDROOT as dir_fd redirects to root_fd, so relative + * paths start at root_fd. Result is relative because root_fd is a non-host-root fd and + * dir_fd (after redirection) equals root_fd. */ + ASSERT_OK(chaseat(root_fd, XAT_FDROOT, "sub/inside", 0, &result, &fd)); + ASSERT_TRUE(inode_same_at(fd, NULL, sub_fd, "inside", AT_EMPTY_PATH)); + ASSERT_STREQ(result, "sub/inside"); + fd = safe_close(fd); + result = mfree(result); + + /* (real-fd, XAT_FDROOT, absolute): same as relative — absolute paths also resolve from + * root_fd. Leading slash is stripped. */ + ASSERT_OK(chaseat(root_fd, XAT_FDROOT, "/sub/inside", 0, &result, &fd)); + ASSERT_TRUE(inode_same_at(fd, NULL, sub_fd, "inside", AT_EMPTY_PATH)); + ASSERT_STREQ(result, "sub/inside"); + fd = safe_close(fd); + result = mfree(result); + + /* (real-fd, XAT_FDROOT, absolute) resolving to root: "/outside" lives directly under + * root_fd. */ + ASSERT_OK(chaseat(root_fd, XAT_FDROOT, "/outside", 0, &result, &fd)); + ASSERT_TRUE(inode_same_at(fd, NULL, root_fd, "outside", AT_EMPTY_PATH)); + ASSERT_STREQ(result, "outside"); + fd = safe_close(fd); + result = mfree(result); + + /* (real-fd, XAT_FDROOT) with an absolute symlink: the symlink target "/outside" resolves + * relative to root_fd, not the host root. Since dir_fd == root_fd (XAT_FDROOT was redirected), + * the result stays relative. */ + ASSERT_OK(chaseat(root_fd, XAT_FDROOT, "sub/escape_abs", 0, &result, &fd)); + ASSERT_TRUE(inode_same_at(fd, NULL, root_fd, "outside", AT_EMPTY_PATH)); + ASSERT_STREQ(result, "outside"); + fd = safe_close(fd); + result = mfree(result); + + /* (real-fd, XAT_FDROOT) with non-existent path. */ + ASSERT_OK_ZERO(chaseat(root_fd, XAT_FDROOT, "/nonexistent", CHASE_NONEXISTENT, &result, NULL)); + ASSERT_STREQ(result, "nonexistent"); + result = mfree(result); + ASSERT_ERROR(chaseat(root_fd, XAT_FDROOT, "/nonexistent", 0, NULL, NULL), ENOENT); +} + TEST(chaseat_prefix_root) { _cleanup_free_ char *cwd = NULL, *ret = NULL, *expected = NULL; @@ -773,7 +1034,7 @@ TEST(chaseat_prefix_root) { ret = mfree(ret); ASSERT_OK(chaseat_prefix_root("hoge", "a/b//./c///", &ret)); - assert_se(expected = path_join(cwd, "a/b/c/hoge")); + expected = ASSERT_NOT_NULL(path_join(cwd, "a/b/c/hoge")); ASSERT_STREQ(ret, expected); ret = mfree(ret); @@ -784,8 +1045,8 @@ TEST(chaseat_prefix_root) { 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_OK(chaseat_prefix_root("./hoge/aaa/../././b", "a/b//./c///", &ret)); + expected = ASSERT_NOT_NULL(path_join(cwd, "a/b/c/hoge/aaa/../././b")); ASSERT_STREQ(ret, expected); } @@ -794,9 +1055,9 @@ TEST(trailing_dot_dot) { _cleanup_close_ int fd = -EBADF; ASSERT_OK(chase("/usr/..", NULL, CHASE_PARENT, &path, &fd)); - assert_se(path_equal(path, "/")); + ASSERT_PATH_EQ(path, "/"); ASSERT_OK(fd_get_path(fd, &fdpath)); - assert_se(path_equal(fdpath, "/")); + ASSERT_PATH_EQ(fdpath, "/"); path = mfree(path); fdpath = mfree(fdpath); @@ -811,9 +1072,9 @@ TEST(trailing_dot_dot) { _cleanup_free_ char *expected1 = ASSERT_PTR(path_join(t, "a/b/c")); _cleanup_free_ char *expected2 = ASSERT_PTR(path_join(t, "a/b")); - assert_se(path_equal(path, expected1)); + ASSERT_PATH_EQ(path, expected1); ASSERT_OK(fd_get_path(fd, &fdpath)); - assert_se(path_equal(fdpath, expected2)); + ASSERT_PATH_EQ(fdpath, expected2); } TEST(use_chase_as_mkdir_p) { diff --git a/src/tpm2-setup/tpm2-swtpm.c b/src/tpm2-setup/tpm2-swtpm.c index 71ad6b5e9d..db5ec09312 100644 --- a/src/tpm2-setup/tpm2-swtpm.c +++ b/src/tpm2-setup/tpm2-swtpm.c @@ -174,6 +174,7 @@ static int run(int argc, char *argv[]) { _cleanup_free_ char *unprefixed_state_dir = NULL; r = chaseat(esp_fd, + esp_fd, "/loader/swtpm", CHASE_TRIGGER_AUTOFS|CHASE_PROHIBIT_SYMLINKS|CHASE_MKDIR_0755|CHASE_MUST_BE_DIRECTORY, &unprefixed_state_dir,