mirror of
https://github.com/systemd/systemd.git
synced 2026-08-08 17:10:55 +00:00
chase: port to statx()
In one of the next commits we want to acquire .stx_mnt_id from statx() for each inode we traverse (plain fstat() doesn't provide that field). Hence let's port chase() over to statx() as preparation for that. No change in behaviour.
This commit is contained in:
@@ -31,15 +31,33 @@
|
||||
CHASE_MUST_BE_REGULAR | \
|
||||
CHASE_MUST_BE_SOCKET)
|
||||
|
||||
bool unsafe_transition(const struct stat *a, const struct stat *b) {
|
||||
/* Returns true if the transition from a to b is safe, i.e. that we never transition from unprivileged to
|
||||
* privileged files or directories. Why bother? So that unprivileged code can't symlink to privileged files
|
||||
* making us believe we read something safe even though it isn't safe in the specific context we open it in. */
|
||||
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
|
||||
* unprivileged to privileged files or directories. Why bother? So that unprivileged code can't
|
||||
* symlink to privileged files making us believe we read something safe even though it isn't safe in
|
||||
* the specific context we open it in. */
|
||||
|
||||
if (a->st_uid == 0) /* Transitioning from privileged to unprivileged is always fine */
|
||||
if (a == 0) /* Transitioning from privileged to unprivileged is always fine */
|
||||
return false;
|
||||
|
||||
return a->st_uid != b->st_uid; /* Otherwise we need to stay within the same UID */
|
||||
return a != b; /* Otherwise we need to stay within the same UID */
|
||||
}
|
||||
|
||||
int statx_unsafe_transition(const struct statx *a, const struct statx *b) {
|
||||
assert(a);
|
||||
assert(b);
|
||||
|
||||
if (!FLAGS_SET(a->stx_mask, STATX_UID) || !FLAGS_SET(b->stx_mask, STATX_UID))
|
||||
return -ENODATA;
|
||||
|
||||
return uid_unsafe_transition(a->stx_uid, b->stx_uid);
|
||||
}
|
||||
|
||||
bool stat_unsafe_transition(const struct stat *a, const struct stat *b) {
|
||||
assert(a);
|
||||
assert(b);
|
||||
|
||||
return uid_unsafe_transition(a->st_uid, b->st_uid);
|
||||
}
|
||||
|
||||
static int log_unsafe_transition(int a, int b, const char *path, ChaseFlags flags) {
|
||||
@@ -129,9 +147,10 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int
|
||||
_cleanup_free_ char *buffer = NULL, *done = NULL;
|
||||
_cleanup_close_ int fd = -EBADF, root_fd = -EBADF;
|
||||
bool exists = true, append_trail_slash = false;
|
||||
struct stat st; /* stat obtained from fd */
|
||||
struct statx stx; /* statx obtained from fd */
|
||||
bool need_absolute = false; /* allocate early to avoid compiler warnings around goto */
|
||||
const char *todo;
|
||||
unsigned mask = STATX_TYPE|STATX_UID|STATX_INO;
|
||||
int r;
|
||||
|
||||
assert(!FLAGS_SET(flags, CHASE_PREFIX_ROOT));
|
||||
@@ -245,8 +264,9 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int
|
||||
if (fd < 0)
|
||||
return -errno;
|
||||
|
||||
if (fstat(fd, &st) < 0)
|
||||
return -errno;
|
||||
r = xstatx(fd, /* path= */ NULL, /* flags= */ 0, mask, &stx);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
exists = true;
|
||||
goto success;
|
||||
@@ -306,8 +326,9 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int
|
||||
if (fd < 0)
|
||||
return -errno;
|
||||
|
||||
if (fstat(fd, &st) < 0)
|
||||
return -errno;
|
||||
r = xstatx(fd, /* path= */ NULL, /* flags= */ 0, mask, &stx);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
/* 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
|
||||
@@ -337,7 +358,7 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int
|
||||
for (unsigned n_steps = 0;; n_steps++) {
|
||||
_cleanup_free_ char *first = NULL;
|
||||
_cleanup_close_ int child = -EBADF;
|
||||
struct stat st_child;
|
||||
struct statx stx_child;
|
||||
const char *e;
|
||||
|
||||
/* If people change our tree behind our back, they might send us in circles. Put a limit on
|
||||
@@ -359,7 +380,7 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int
|
||||
if (streq(first, "..")) {
|
||||
_cleanup_free_ char *parent = NULL;
|
||||
_cleanup_close_ int fd_parent = -EBADF;
|
||||
struct stat st_parent;
|
||||
struct statx stx_parent;
|
||||
|
||||
/* If we already are at the top, then going up will not change anything. This is
|
||||
* in-line with how the kernel handles this. */
|
||||
@@ -373,13 +394,14 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int
|
||||
if (fd_parent < 0)
|
||||
return -errno;
|
||||
|
||||
if (fstat(fd_parent, &st_parent) < 0)
|
||||
return -errno;
|
||||
r = xstatx(fd_parent, /* path= */ NULL, /* flags= */ 0, mask, &stx_parent);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
/* If we opened the same directory, that _may_ indicate that we're at the host root
|
||||
* directory. Let's confirm that in more detail with dir_fd_is_root(). And if so,
|
||||
* going up won't change anything. */
|
||||
if (stat_inode_same(&st_parent, &st)) {
|
||||
if (statx_inode_same(&stx_parent, &stx)) {
|
||||
r = dir_fd_is_root(fd);
|
||||
if (r < 0)
|
||||
return r;
|
||||
@@ -419,35 +441,44 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int
|
||||
if (FLAGS_SET(flags, CHASE_STEP))
|
||||
goto chased_one;
|
||||
|
||||
if (FLAGS_SET(flags, CHASE_SAFE) &&
|
||||
unsafe_transition(&st, &st_parent))
|
||||
return log_unsafe_transition(fd, fd_parent, path, flags);
|
||||
if (FLAGS_SET(flags, CHASE_SAFE)) {
|
||||
r = statx_unsafe_transition(&stx, &stx_parent);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (r > 0)
|
||||
return log_unsafe_transition(fd, fd_parent, path, flags);
|
||||
}
|
||||
|
||||
/* If the path ends on a "..", and CHASE_PARENT is specified then our current 'fd' is
|
||||
* the child of the returned normalized path, not the parent as requested. To correct
|
||||
* this we have to go *two* levels up. */
|
||||
if (FLAGS_SET(flags, CHASE_PARENT) && isempty(todo)) {
|
||||
_cleanup_close_ int fd_grandparent = -EBADF;
|
||||
struct stat st_grandparent;
|
||||
struct statx stx_grandparent;
|
||||
|
||||
fd_grandparent = openat(fd_parent, "..", O_CLOEXEC|O_NOFOLLOW|O_PATH|O_DIRECTORY);
|
||||
if (fd_grandparent < 0)
|
||||
return -errno;
|
||||
|
||||
if (fstat(fd_grandparent, &st_grandparent) < 0)
|
||||
return -errno;
|
||||
r = xstatx(fd_grandparent, /* path= */ NULL, /* flags= */ 0, mask, &stx_grandparent);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (FLAGS_SET(flags, CHASE_SAFE) &&
|
||||
unsafe_transition(&st_parent, &st_grandparent))
|
||||
return log_unsafe_transition(fd_parent, fd_grandparent, path, flags);
|
||||
if (FLAGS_SET(flags, CHASE_SAFE)) {
|
||||
r = statx_unsafe_transition(&stx_parent, &stx_grandparent);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (r > 0)
|
||||
return log_unsafe_transition(fd_parent, fd_grandparent, path, flags);
|
||||
}
|
||||
|
||||
st = st_grandparent;
|
||||
stx = stx_grandparent;
|
||||
close_and_replace(fd, fd_grandparent);
|
||||
break;
|
||||
}
|
||||
|
||||
/* update fd and stat */
|
||||
st = st_parent;
|
||||
stx = stx_parent;
|
||||
close_and_replace(fd, fd_parent);
|
||||
continue;
|
||||
}
|
||||
@@ -483,18 +514,23 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int
|
||||
}
|
||||
|
||||
/* ... and then check what it actually is. */
|
||||
if (fstat(child, &st_child) < 0)
|
||||
return -errno;
|
||||
r = xstatx(child, /* path= */ NULL, /* flags= */ 0, mask, &stx_child);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (FLAGS_SET(flags, CHASE_SAFE) &&
|
||||
unsafe_transition(&st, &st_child))
|
||||
return log_unsafe_transition(fd, child, path, flags);
|
||||
if (FLAGS_SET(flags, CHASE_SAFE)) {
|
||||
r = statx_unsafe_transition(&stx, &stx_child);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (r > 0)
|
||||
return log_unsafe_transition(fd, child, path, flags);
|
||||
}
|
||||
|
||||
if (FLAGS_SET(flags, CHASE_NO_AUTOFS) &&
|
||||
fd_is_fs_type(child, AUTOFS_SUPER_MAGIC) > 0)
|
||||
return log_autofs_mount_point(child, path, flags);
|
||||
|
||||
if (S_ISLNK(st_child.st_mode) && !(FLAGS_SET(flags, CHASE_NOFOLLOW) && isempty(todo))) {
|
||||
if (S_ISLNK(stx_child.stx_mode) && !(FLAGS_SET(flags, CHASE_NOFOLLOW) && isempty(todo))) {
|
||||
_cleanup_free_ char *destination = NULL;
|
||||
|
||||
if (FLAGS_SET(flags, CHASE_PROHIBIT_SYMLINKS))
|
||||
@@ -516,12 +552,17 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int
|
||||
if (fd < 0)
|
||||
return fd;
|
||||
|
||||
if (fstat(fd, &st) < 0)
|
||||
return -errno;
|
||||
r = xstatx(fd, /* path= */ NULL, /* flags= */ 0, mask, &stx);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (FLAGS_SET(flags, CHASE_SAFE) &&
|
||||
unsafe_transition(&st_child, &st))
|
||||
return log_unsafe_transition(child, fd, path, flags);
|
||||
if (FLAGS_SET(flags, CHASE_SAFE)) {
|
||||
r = statx_unsafe_transition(&stx_child, &stx);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (r > 0)
|
||||
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. */
|
||||
@@ -555,26 +596,26 @@ int chaseat(int dir_fd, const char *path, ChaseFlags flags, char **ret_path, int
|
||||
break;
|
||||
|
||||
/* And iterate again, but go one directory further down. */
|
||||
st = st_child;
|
||||
stx = stx_child;
|
||||
close_and_replace(fd, child);
|
||||
}
|
||||
|
||||
success:
|
||||
if (exists) {
|
||||
if (FLAGS_SET(flags, CHASE_MUST_BE_DIRECTORY)) {
|
||||
r = stat_verify_directory(&st);
|
||||
r = statx_verify_directory(&stx);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
if (FLAGS_SET(flags, CHASE_MUST_BE_REGULAR)) {
|
||||
r = stat_verify_regular(&st);
|
||||
r = statx_verify_regular(&stx);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
if (FLAGS_SET(flags, CHASE_MUST_BE_SOCKET)) {
|
||||
r = stat_verify_socket(&st);
|
||||
r = statx_verify_socket(&stx);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
@@ -32,7 +32,8 @@ typedef enum ChaseFlags {
|
||||
CHASE_MUST_BE_SOCKET = 1 << 16, /* Fail if returned inode fd is not a socket */
|
||||
} ChaseFlags;
|
||||
|
||||
bool unsafe_transition(const struct stat *a, const struct stat *b);
|
||||
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
|
||||
|
||||
@@ -2633,7 +2633,7 @@ static int rm_if_wrong_type_safe(
|
||||
}
|
||||
|
||||
/* Fail before removing anything if this is an unsafe transition. */
|
||||
if (follow_links && unsafe_transition(parent_st, &st)) {
|
||||
if (follow_links && stat_unsafe_transition(parent_st, &st)) {
|
||||
(void) fd_get_path(parent_fd, &parent_name);
|
||||
return log_error_errno(SYNTHETIC_ERRNO(ENOLINK),
|
||||
"Unsafe transition from \"%s\" to \"%s\".", parent_name ?: "...", name);
|
||||
|
||||
Reference in New Issue
Block a user