mirror of
https://github.com/systemd/systemd.git
synced 2026-08-10 01:48:09 +00:00
chase: Use openat2() if available
Let's make use of openat2() if we can in chaseat().
This commit is contained in:
committed by
Daan De Meyer
parent
452214c09b
commit
8b81e3b751
@@ -593,6 +593,7 @@ foreach ident : [
|
||||
['mount_setattr', '''#include <sys/mount.h>'''], # since glibc-2.36
|
||||
['move_mount', '''#include <sys/mount.h>'''], # since glibc-2.36
|
||||
['open_tree', '''#include <sys/mount.h>'''], # since glibc-2.36
|
||||
['openat2', '''#include <fcntl.h>'''], # since glibc-2.42
|
||||
['pidfd_open', '''#include <sys/pidfd.h>'''], # since glibc-2.36
|
||||
['pidfd_send_signal', '''#include <sys/pidfd.h>'''], # since glibc-2.36
|
||||
['pidfd_spawn', '''#include <spawn.h>'''], # since glibc-2.39
|
||||
|
||||
@@ -50,6 +50,93 @@ static int chase_statx(int fd, struct statx *ret) {
|
||||
ret);
|
||||
}
|
||||
|
||||
static int chase_openat2(int root_fd, int dir_fd, const char *path, ChaseFlags chase_flags) {
|
||||
/* Open the target of a chase operation via openat2(), translating the relevant ChaseFlags into
|
||||
* RESOLVE_* and O_* flags and verifying MUST_BE_REGULAR/SOCKET via fstat after the open. Returns
|
||||
* -EOPNOTSUPP when openat2() is unavailable (older kernels) or blocked by a seccomp filter
|
||||
* (notably systemd's own filter, which returns ENOSYS to force programs onto the openat()
|
||||
* fallback path) — the verdict is cached so subsequent calls in the same process skip the syscall
|
||||
* entirely. */
|
||||
|
||||
static bool can_openat2 = true;
|
||||
int r;
|
||||
|
||||
assert(path);
|
||||
assert(dir_fd >= 0 || IN_SET(dir_fd, AT_FDCWD, XAT_FDROOT));
|
||||
|
||||
if (!can_openat2)
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
/* openat2() can handle everything the regular shortcut handles, plus a real root boundary (via
|
||||
* RESOLVE_IN_ROOT) and CHASE_PROHIBIT_SYMLINKS (via RESOLVE_NO_SYMLINKS). It cannot model the other
|
||||
* CHASE_NO_SHORTCUT flags, cannot trigger automounts on O_PATH fds, and RESOLVE_IN_ROOT requires
|
||||
* the dirfd to be the root. Bail out so the caller falls back to the regular chase loop. */
|
||||
if ((chase_flags & (CHASE_NO_SHORTCUT_MASK & ~CHASE_PROHIBIT_SYMLINKS)) != 0)
|
||||
return -EOPNOTSUPP;
|
||||
if (FLAGS_SET(chase_flags, CHASE_TRIGGER_AUTOFS))
|
||||
return -EOPNOTSUPP;
|
||||
if (root_fd != XAT_FDROOT && root_fd != dir_fd)
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
_cleanup_close_ int dir_fd_local = -EBADF;
|
||||
if (dir_fd == XAT_FDROOT) {
|
||||
if (path_is_absolute(path))
|
||||
dir_fd = AT_FDCWD;
|
||||
else {
|
||||
dir_fd_local = open("/", O_CLOEXEC|O_DIRECTORY|O_PATH);
|
||||
if (dir_fd_local < 0)
|
||||
return -errno;
|
||||
dir_fd = dir_fd_local;
|
||||
}
|
||||
}
|
||||
|
||||
struct open_how how = {
|
||||
.flags = O_PATH|O_CLOEXEC,
|
||||
};
|
||||
if (FLAGS_SET(chase_flags, CHASE_NOFOLLOW))
|
||||
how.flags |= O_NOFOLLOW;
|
||||
if (FLAGS_SET(chase_flags, CHASE_MUST_BE_DIRECTORY))
|
||||
how.flags |= O_DIRECTORY;
|
||||
if (root_fd != XAT_FDROOT)
|
||||
how.resolve |= RESOLVE_IN_ROOT;
|
||||
if (FLAGS_SET(chase_flags, CHASE_PROHIBIT_SYMLINKS))
|
||||
how.resolve |= RESOLVE_NO_SYMLINKS;
|
||||
|
||||
_cleanup_close_ int fd = openat2(dir_fd, path, &how, sizeof(how));
|
||||
if (fd < 0) {
|
||||
/* ENOSYS: kernel too old or seccomp filter (systemd's filter returns ENOSYS).
|
||||
* EPERM: Some seccomp profiles of container runtimes use EPERM rather than ENOSYS.
|
||||
* But EPERM might also be returned because we can't access some component of the path. So
|
||||
* we can't cache the result and skip using openat2() if it is blocked with EPERM. Instead
|
||||
* we fall back to userspace chase() if we get EPERM.
|
||||
* EAGAIN: with RESOLVE_IN_ROOT the kernel returns this when a ".." component
|
||||
* (typically from following a symlink like /etc/os-release → ../usr/lib/os-release)
|
||||
* is processed and the global mount_lock or rename_lock seqcount changed during
|
||||
* the walk. Any mount activity anywhere in the system bumps mount_lock, so this
|
||||
* fires reliably while we're still setting up a mount tree. Fall back to the
|
||||
* regular chase loop, which handles root boundaries without openat2(). Don't
|
||||
* cache this — the condition is per-call, not a kernel/sandbox capability. */
|
||||
if (errno == ENOSYS)
|
||||
can_openat2 = false;
|
||||
if (IN_SET(errno, ENOSYS, EPERM, EAGAIN))
|
||||
return -EOPNOTSUPP;
|
||||
return -errno;
|
||||
}
|
||||
|
||||
if (FLAGS_SET(chase_flags, CHASE_MUST_BE_REGULAR)) {
|
||||
r = fd_verify_regular(fd);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
if (FLAGS_SET(chase_flags, CHASE_MUST_BE_SOCKET)) {
|
||||
r = fd_verify_socket(fd);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
return TAKE_FD(fd);
|
||||
}
|
||||
|
||||
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
|
||||
@@ -262,6 +349,20 @@ int chaseat(int root_fd, int dir_fd, const char *path, ChaseFlags flags, char **
|
||||
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;
|
||||
|
||||
if (!ret_path) {
|
||||
r = chase_openat2(root_fd, dir_fd, path, flags);
|
||||
if (r >= 0) {
|
||||
if (ret_fd)
|
||||
*ret_fd = r;
|
||||
else
|
||||
safe_close(r);
|
||||
|
||||
return 1;
|
||||
}
|
||||
if (r != -EOPNOTSUPP)
|
||||
return r;
|
||||
}
|
||||
|
||||
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_*
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
#pragma once
|
||||
|
||||
#include_next <fcntl.h> /* IWYU pragma: export */
|
||||
#include <linux/openat2.h> /* IWYU pragma: export */
|
||||
#include <stddef.h>
|
||||
|
||||
/* This is defined since glibc-2.41. */
|
||||
#ifndef F_DUPFD_QUERY
|
||||
@@ -22,3 +24,10 @@
|
||||
#ifndef AT_HANDLE_MNT_ID_UNIQUE
|
||||
#define AT_HANDLE_MNT_ID_UNIQUE 0x001 /* Return the u64 unique mount ID. */
|
||||
#endif
|
||||
|
||||
/* Defined since glibc-2.42.
|
||||
* Supported since kernel v5.6 (fddb5d430ad9fa91b49b1d34d0202ffe2fa0e179). */
|
||||
#if !HAVE_OPENAT2
|
||||
int missing_openat2(int dfd, const char *filename, const struct open_how *how, size_t usize);
|
||||
# define openat2 missing_openat2
|
||||
#endif
|
||||
|
||||
11
src/libc/fcntl.c
Normal file
11
src/libc/fcntl.c
Normal file
@@ -0,0 +1,11 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <sys/syscall.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#if !HAVE_OPENAT2
|
||||
int missing_openat2(int dfd, const char *filename, const struct open_how *how, size_t usize) {
|
||||
return syscall(__NR_openat2, dfd, filename, how, usize);
|
||||
}
|
||||
#endif
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
libc_wrapper_sources = files(
|
||||
'bpf.c',
|
||||
'fcntl.c',
|
||||
'ioprio.c',
|
||||
'kcmp.c',
|
||||
'kexec.c',
|
||||
|
||||
Reference in New Issue
Block a user