Split the single directory fd that chaseat() used to take into two separate fds: a root_fd that sets the chroot boundary (symlinks may not escape it, absolute symlinks resolve relative to it), and a dir_fd that path resolution starts from. This makes the chroot semantics of chaseat() explicit at every call site instead of encoding them in the CHASE_AT_RESOLVE_IN_ROOT flag, which is removed. It also decouples the starting directory from the root boundary, so callers can descend from any inode inside the tree without having to reopen the root separately. XAT_FDROOT passed as root_fd means "no containment" (host root); as dir_fd it means "start at root_fd". For a smoother transition, AT_FDCWD is also accepted as root_fd and treated as XAT_FDROOT. When root_fd points to a directory that is actually the host root, it is normalized to XAT_FDROOT up front so the existing shortcut path can kick in. Absolute paths returned by chaseat() are now relative to root_fd, and relative paths are relative to dir_fd. The result is absolute only when there is no chroot boundary (root_fd is XAT_FDROOT), or when an absolute symlink made resolution jump out of the dir_fd subtree; otherwise callers get a relative path they can feed straight back into an openat()-style call against dir_fd. Specifically, when dir_fd == root_fd and we're not operating on the host's root directory, we return a relative path even if we received an absolute path or resolved an absolute symlink to allow passing the path directly to openat() style functions. We do this to not have to go modify every caller of chaseat() to make sure they deal properly with any absolute paths they might receive. Only when root_fd != dir_fd do we have to return an absolute path to indicate that the path is relative to root_fd and not dir_fd. The shortcut that skips the per-component walk is reworked around a new chase_xopenat() helper that funnels CHASE_NOFOLLOW, CHASE_MUST_BE_* and CHASE_TRIGGER_AUTOFS through xopenat_full()'s O_NOFOLLOW, O_DIRECTORY, XO_REGULAR, XO_SOCKET and XO_TRIGGER_AUTOMOUNT flags. As a result these flags no longer force us off the shortcut and can be dropped from CHASE_NO_SHORTCUT_MASK, and the old openat_opath_with_automount() helper goes away. A CHASE_MUST_BE_ANY alias is introduced for shortcut callers (stat/access paths) that don't go through xopenat_full() and still need to bail on those flags locally. All *_and_* helpers built on top of chaseat() (chase_and_openat, chase_and_opendirat, chase_and_statat, chase_and_accessat, chase_and_fopenat_unlocked, chase_and_unlinkat, chase_and_open_parent_at) gain the same root_fd parameter, and every call site in the tree is ported to the new signature. chase_and_open() is also fixed to correctly handle CHASE_EXTRACT_FILENAME without CHASE_PARENT.
61 lines
4.9 KiB
C
61 lines
4.9 KiB
C
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
|
#pragma once
|
|
|
|
#include "basic-forward.h"
|
|
|
|
typedef enum ChaseFlags {
|
|
CHASE_PREFIX_ROOT = 1 << 0, /* The specified path will be prefixed by the specified root before beginning the iteration */
|
|
CHASE_NONEXISTENT = 1 << 1, /* It's OK if the path doesn't actually exist. */
|
|
CHASE_NO_AUTOFS = 1 << 2, /* Return -EREMOTE if autofs mount point found */
|
|
CHASE_TRIGGER_AUTOFS = 1 << 3, /* Use open_tree() rather than openat() to trigger autofs mount */
|
|
CHASE_SAFE = 1 << 4, /* Return -EPERM if we ever traverse from unprivileged to privileged files or directories */
|
|
CHASE_TRAIL_SLASH = 1 << 5, /* Any trailing slash will be preserved */
|
|
CHASE_STEP = 1 << 6, /* Just execute a single step of the normalization */
|
|
CHASE_NOFOLLOW = 1 << 7, /* Do not follow the path's right-most component. With ret_fd, when the path's
|
|
* 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_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 << 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);
|
|
bool stat_unsafe_transition(const struct stat *a, const struct stat *b);
|
|
|
|
/* How many iterations to execute before returning -ELOOP */
|
|
#define CHASE_MAX 128U
|
|
|
|
int chase(const char *path_with_prefix, const char *root, ChaseFlags flags, char **ret_path, int *ret_fd);
|
|
|
|
int chaseat_prefix_root(const char *path, const char *root, char **ret);
|
|
int chase_extract_filename(const char *path, const char *root, char **ret);
|
|
|
|
int chase_and_open(const char *path, const char *root, ChaseFlags chase_flags, int open_flags, char **ret_path);
|
|
int chase_and_opendir(const char *path, const char *root, ChaseFlags chase_flags, char **ret_path, DIR **ret_dir);
|
|
int chase_and_stat(const char *path, const char *root, ChaseFlags chase_flags, char **ret_path, struct stat *ret_stat);
|
|
int chase_and_access(const char *path, const char *root, ChaseFlags chase_flags, int access_mode, char **ret_path);
|
|
int chase_and_fopen_unlocked(const char *path, const char *root, ChaseFlags chase_flags, const char *open_flags, char **ret_path, FILE **ret_file);
|
|
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 root_fd, int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int *ret_fd);
|
|
|
|
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);
|