diff --git a/README b/README index 4dabdaee06..7501b6b943 100644 --- a/README +++ b/README @@ -35,6 +35,7 @@ REQUIREMENTS: ≥ 4.9 for RENAME_NOREPLACE support in vfat ≥ 4.10 for cgroup-bpf egress and ingress hooks ≥ 4.11 for nsfs + # FIXME: drop compat glue and remove entries above before v258 ≥ 4.15 for cgroup-bpf device hook and cpu controller in cgroup v2 ≥ 4.17 for cgroup-bpf socket address hooks and /sys/power/resume_offset ≥ 4.20 for PSI (used by systemd-oomd) @@ -43,16 +44,17 @@ REQUIREMENTS: ≥ 5.4 for pidfd, new mount API, and signed Verity images ≥ 5.6 for getrandom() GRND_INSECURE ≥ 5.7 for CLONE_INTO_CGROUP, BPF links and the BPF LSM hook - ≥ 5.9 for close_range() ≥ 5.8 for LOOP_CONFIGURE and STATX_ATTR_MOUNT_ROOT + ≥ 5.9 for close_range() ≥ 6.3 for MFD_EXEC/MFD_NOEXEC_SEAL and tmpfs noswap option ≥ 6.5 for name_to_handle_at() AT_HANDLE_FID, SO_PEERPIDFD/SO_PASSPIDFD, and MOVE_MOUNT_BENEATH ≥ 6.9 for pidfs - ⛔ Kernel versions below 4.3 ("minimum baseline") are not supported at + ⛔ Kernel versions below 4.11 ("minimum baseline") are not supported at all, and are missing required functionality (e.g. CLOCK_BOOTTIME support - for timerfd_create(), getrandom(), ambient capabilities, or memfd_create()). + for timerfd_create(), getrandom(), ambient capabilities, memfd_create(), + or nsfs (NS_GET_NSTYPE)). ⚠️ Kernel versions below 5.4 ("recommended baseline") have significant gaps in functionality and are not recommended for use with this version diff --git a/TODO b/TODO index 5cefbc48aa..e693afcfc8 100644 --- a/TODO +++ b/TODO @@ -144,9 +144,6 @@ Features: that terminal emulators can maybe connect a password manager or so, and highlight things specially. -* Port pidref_namespace_open() to use PIDFD_GET_MNT_NAMESPACE and related - ioctls to get nsfds directly from pidfds. - * start using STATX_SUBVOL in btrfs_is_subvol(). Also, make use of it generically, so that image discovery recognizes bcachefs subvols too. diff --git a/src/basic/cgroup-util.c b/src/basic/cgroup-util.c index 25dcab91fc..a5b9829f94 100644 --- a/src/basic/cgroup-util.c +++ b/src/basic/cgroup-util.c @@ -68,6 +68,27 @@ int cg_cgroupid_open(int cgroupfs_fd, uint64_t id) { return RET_NERRNO(open_by_handle_at(cgroupfs_fd, &fh.file_handle, O_DIRECTORY|O_CLOEXEC)); } +int cg_path_from_cgroupid(int cgroupfs_fd, uint64_t id, char **ret) { + _cleanup_close_ int cgfd = -EBADF; + int r; + + cgfd = cg_cgroupid_open(cgroupfs_fd, id); + if (cgfd < 0) + return cgfd; + + _cleanup_free_ char *path = NULL; + r = fd_get_path(cgfd, &path); + if (r < 0) + return r; + + if (!path_startswith(path, "/sys/fs/cgroup/")) + return -EXDEV; /* recognizable error */ + + if (ret) + *ret = TAKE_PTR(path); + return 0; +} + int cg_get_cgroupid_at(int dfd, const char *path, uint64_t *ret) { cg_file_handle fh = CG_FILE_HANDLE_INIT; int mnt_id; @@ -842,6 +863,10 @@ int cg_pidref_get_path(const char *controller, const PidRef *pidref, char **ret_ if (pidref_is_remote(pidref)) return -EREMOTE; + // XXX: Ideally we'd use pidfd_get_cgroupid() + cg_path_from_cgroupid() here, to extract this + // bit of information from pidfd directly. However, the latter requires privilege and it's + // not entirely clear how to handle cgroups from outer namespace. + r = cg_pid_get_path(controller, pidref->pid, &path); if (r < 0) return r; diff --git a/src/basic/cgroup-util.h b/src/basic/cgroup-util.h index 23aa84f100..77294779fe 100644 --- a/src/basic/cgroup-util.h +++ b/src/basic/cgroup-util.h @@ -183,6 +183,7 @@ typedef enum CGroupUnified { int cg_path_open(const char *controller, const char *path); int cg_cgroupid_open(int fsfd, uint64_t id); +int cg_path_from_cgroupid(int cgroupfs_fd, uint64_t id, char **ret); int cg_get_cgroupid_at(int dfd, const char *path, uint64_t *ret); static inline int cg_path_get_cgroupid(const char *path, uint64_t *ret) { return cg_get_cgroupid_at(AT_FDCWD, path, ret); diff --git a/src/basic/chattr-util.c b/src/basic/chattr-util.c index 4aabcc6818..39fdf970a7 100644 --- a/src/basic/chattr-util.c +++ b/src/basic/chattr-util.c @@ -103,7 +103,7 @@ int chattr_full( continue; if (ioctl(fd, FS_IOC_SETFLAGS, &new_one) < 0) { - if (errno != EINVAL && !ERRNO_IS_NOT_SUPPORTED(errno)) + if (!ERRNO_IS_IOCTL_NOT_SUPPORTED(errno)) return -errno; log_full_errno(FLAGS_SET(flags, CHATTR_WARN_UNSUPPORTED_FLAGS) ? LOG_WARNING : LOG_DEBUG, diff --git a/src/basic/errno-util.h b/src/basic/errno-util.h index 48b76e4bf7..02572e3bdc 100644 --- a/src/basic/errno-util.h +++ b/src/basic/errno-util.h @@ -158,7 +158,7 @@ static inline bool ERRNO_IS_NEG_RESOURCE(intmax_t r) { } _DEFINE_ABS_WRAPPER(RESOURCE); -/* Seven different errors for "operation/system call/ioctl/socket feature not supported" */ +/* Seven different errors for "operation/system call/socket feature not supported" */ static inline bool ERRNO_IS_NEG_NOT_SUPPORTED(intmax_t r) { return IN_SET(r, -EOPNOTSUPP, @@ -172,6 +172,12 @@ static inline bool ERRNO_IS_NEG_NOT_SUPPORTED(intmax_t r) { } _DEFINE_ABS_WRAPPER(NOT_SUPPORTED); +/* ioctl() with unsupported command/arg might additionally return EINVAL */ +static inline bool ERRNO_IS_NEG_IOCTL_NOT_SUPPORTED(intmax_t r) { + return ERRNO_IS_NEG_NOT_SUPPORTED(r) || r == -EINVAL; +} +_DEFINE_ABS_WRAPPER(IOCTL_NOT_SUPPORTED); + /* Two different errors for access problems */ static inline bool ERRNO_IS_NEG_PRIVILEGE(intmax_t r) { return IN_SET(r, diff --git a/src/basic/meson.build b/src/basic/meson.build index e02f787c75..147040a193 100644 --- a/src/basic/meson.build +++ b/src/basic/meson.build @@ -72,6 +72,7 @@ basic_sources = files( 'parse-util.c', 'path-util.c', 'percent-util.c', + 'pidfd-util.c', 'pidref.c', 'prioq.c', 'proc-cmdline.c', diff --git a/src/basic/missing_pidfd.h b/src/basic/missing_pidfd.h new file mode 100644 index 0000000000..4c81514284 --- /dev/null +++ b/src/basic/missing_pidfd.h @@ -0,0 +1,48 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include +#if HAVE_PIDFD_OPEN +#include +#endif + +#ifndef PIDFS_IOCTL_MAGIC +# define PIDFS_IOCTL_MAGIC 0xFF +#endif + +#ifndef PIDFD_GET_CGROUP_NAMESPACE +# define PIDFD_GET_CGROUP_NAMESPACE _IO(PIDFS_IOCTL_MAGIC, 1) +# define PIDFD_GET_IPC_NAMESPACE _IO(PIDFS_IOCTL_MAGIC, 2) +# define PIDFD_GET_MNT_NAMESPACE _IO(PIDFS_IOCTL_MAGIC, 3) +# define PIDFD_GET_NET_NAMESPACE _IO(PIDFS_IOCTL_MAGIC, 4) +# define PIDFD_GET_PID_NAMESPACE _IO(PIDFS_IOCTL_MAGIC, 5) +# define PIDFD_GET_PID_FOR_CHILDREN_NAMESPACE _IO(PIDFS_IOCTL_MAGIC, 6) +# define PIDFD_GET_TIME_NAMESPACE _IO(PIDFS_IOCTL_MAGIC, 7) +# define PIDFD_GET_TIME_FOR_CHILDREN_NAMESPACE _IO(PIDFS_IOCTL_MAGIC, 8) +# define PIDFD_GET_USER_NAMESPACE _IO(PIDFS_IOCTL_MAGIC, 9) +# define PIDFD_GET_UTS_NAMESPACE _IO(PIDFS_IOCTL_MAGIC, 10) +#endif + +#ifndef PIDFD_GET_INFO +struct pidfd_info { + __u64 mask; + __u64 cgroupid; + __u32 pid; + __u32 tgid; + __u32 ppid; + __u32 ruid; + __u32 rgid; + __u32 euid; + __u32 egid; + __u32 suid; + __u32 sgid; + __u32 fsuid; + __u32 fsgid; + __u32 spare0[1]; +}; + +#define PIDFD_GET_INFO _IOWR(PIDFS_IOCTL_MAGIC, 11, struct pidfd_info) +#define PIDFD_INFO_PID (1UL << 0) +#define PIDFD_INFO_CREDS (1UL << 1) +#define PIDFD_INFO_CGROUPID (1UL << 2) +#endif diff --git a/src/basic/namespace-util.c b/src/basic/namespace-util.c index 332e8cdfd5..9293999a01 100644 --- a/src/basic/namespace-util.c +++ b/src/basic/namespace-util.c @@ -18,29 +18,30 @@ #include "mountpoint-util.h" #include "namespace-util.h" #include "parse-util.h" +#include "pidfd-util.h" #include "process-util.h" #include "stat-util.h" #include "stdio-util.h" #include "user-util.h" const struct namespace_info namespace_info[_NAMESPACE_TYPE_MAX + 1] = { - [NAMESPACE_CGROUP] = { "cgroup", "ns/cgroup", CLONE_NEWCGROUP, PROC_CGROUP_INIT_INO }, - [NAMESPACE_IPC] = { "ipc", "ns/ipc", CLONE_NEWIPC, PROC_IPC_INIT_INO }, - [NAMESPACE_NET] = { "net", "ns/net", CLONE_NEWNET, 0 }, + [NAMESPACE_CGROUP] = { "cgroup", "ns/cgroup", CLONE_NEWCGROUP, PIDFD_GET_CGROUP_NAMESPACE, PROC_CGROUP_INIT_INO }, + [NAMESPACE_IPC] = { "ipc", "ns/ipc", CLONE_NEWIPC, PIDFD_GET_IPC_NAMESPACE, PROC_IPC_INIT_INO }, + [NAMESPACE_NET] = { "net", "ns/net", CLONE_NEWNET, PIDFD_GET_NET_NAMESPACE, 0 }, /* So, the mount namespace flag is called CLONE_NEWNS for historical * reasons. Let's expose it here under a more explanatory name: "mnt". * This is in-line with how the kernel exposes namespaces in /proc/$PID/ns. */ - [NAMESPACE_MOUNT] = { "mnt", "ns/mnt", CLONE_NEWNS, 0 }, - [NAMESPACE_PID] = { "pid", "ns/pid", CLONE_NEWPID, PROC_PID_INIT_INO }, - [NAMESPACE_USER] = { "user", "ns/user", CLONE_NEWUSER, PROC_USER_INIT_INO }, - [NAMESPACE_UTS] = { "uts", "ns/uts", CLONE_NEWUTS, PROC_UTS_INIT_INO }, - [NAMESPACE_TIME] = { "time", "ns/time", CLONE_NEWTIME, PROC_TIME_INIT_INO }, - { /* Allow callers to iterate over the array without using _NAMESPACE_TYPE_MAX. */ }, + [NAMESPACE_MOUNT] = { "mnt", "ns/mnt", CLONE_NEWNS, PIDFD_GET_MNT_NAMESPACE, 0 }, + [NAMESPACE_PID] = { "pid", "ns/pid", CLONE_NEWPID, PIDFD_GET_PID_NAMESPACE, PROC_PID_INIT_INO }, + [NAMESPACE_USER] = { "user", "ns/user", CLONE_NEWUSER, PIDFD_GET_USER_NAMESPACE, PROC_USER_INIT_INO }, + [NAMESPACE_UTS] = { "uts", "ns/uts", CLONE_NEWUTS, PIDFD_GET_UTS_NAMESPACE, PROC_UTS_INIT_INO }, + [NAMESPACE_TIME] = { "time", "ns/time", CLONE_NEWTIME, PIDFD_GET_TIME_NAMESPACE, PROC_TIME_INIT_INO }, + {}, /* Allow callers to iterate over the array without using _NAMESPACE_TYPE_MAX. */ }; #define pid_namespace_path(pid, type) procfs_file_alloca(pid, namespace_info[type].proc_path) -static NamespaceType clone_flag_to_namespace_type(unsigned long clone_flag) { +NamespaceType clone_flag_to_namespace_type(unsigned long clone_flag) { for (NamespaceType t = 0; t < _NAMESPACE_TYPE_MAX; t++) if (((namespace_info[t].clone_flag ^ clone_flag) & (CLONE_NEWCGROUP|CLONE_NEWIPC|CLONE_NEWNET|CLONE_NEWNS|CLONE_NEWPID|CLONE_NEWUSER|CLONE_NEWUTS|CLONE_NEWTIME)) == 0) return t; @@ -48,6 +49,62 @@ static NamespaceType clone_flag_to_namespace_type(unsigned long clone_flag) { return _NAMESPACE_TYPE_INVALID; } +static int pidref_namespace_open_by_type_internal(const PidRef *pidref, NamespaceType type, bool *need_verify) { + int r; + + assert(pidref_is_set(pidref)); + assert(type >= 0 && type < _NAMESPACE_TYPE_MAX); + + if (pidref_is_remote(pidref)) + return -EREMOTE; + + if (pidref->fd >= 0) { + r = pidfd_get_namespace(pidref->fd, namespace_info[type].pidfd_get_ns_ioctl_cmd); + if (!ERRNO_IS_NEG_NOT_SUPPORTED(r)) + return r; + } + + if (need_verify) /* The caller shall call pidref_verify() later */ + *need_verify = true; + + _cleanup_close_ int nsfd = -EBADF; + const char *p; + + p = pid_namespace_path(pidref->pid, type); + nsfd = open(p, O_RDONLY|O_NOCTTY|O_CLOEXEC); + if (nsfd < 0) { + if (errno == ENOENT && proc_mounted() == 0) + return -ENOSYS; + + return -errno; + } + + if (!need_verify) { /* Otherwise we verify on our own */ + r = pidref_verify(pidref); + if (r < 0) + return r; + } + + return TAKE_FD(nsfd); +} + +int pidref_namespace_open_by_type(const PidRef *pidref, NamespaceType type) { + return pidref_namespace_open_by_type_internal(pidref, type, NULL); +} + +int namespace_open_by_type(NamespaceType type) { + _cleanup_(pidref_done) PidRef self = PIDREF_NULL; + int r; + + assert(type >= 0 && type < _NAMESPACE_TYPE_MAX); + + r = pidref_set_self(&self); + if (r < 0) + return r; + + return pidref_namespace_open_by_type(&self, type); +} + int pidref_namespace_open( const PidRef *pidref, int *ret_pidns_fd, @@ -58,58 +115,56 @@ int pidref_namespace_open( _cleanup_close_ int pidns_fd = -EBADF, mntns_fd = -EBADF, netns_fd = -EBADF, userns_fd = -EBADF, root_fd = -EBADF; + bool need_verify = false; int r; assert(pidref_is_set(pidref)); - if (ret_pidns_fd) { - const char *pidns; + if (pidref_is_remote(pidref)) + return -EREMOTE; - pidns = pid_namespace_path(pidref->pid, NAMESPACE_PID); - pidns_fd = open(pidns, O_RDONLY|O_NOCTTY|O_CLOEXEC); + if (ret_pidns_fd) { + pidns_fd = pidref_namespace_open_by_type_internal(pidref, NAMESPACE_PID, &need_verify); if (pidns_fd < 0) - return -errno; + return pidns_fd; } if (ret_mntns_fd) { - const char *mntns; - - mntns = pid_namespace_path(pidref->pid, NAMESPACE_MOUNT); - mntns_fd = open(mntns, O_RDONLY|O_NOCTTY|O_CLOEXEC); + mntns_fd = pidref_namespace_open_by_type_internal(pidref, NAMESPACE_MOUNT, &need_verify); if (mntns_fd < 0) - return -errno; + return mntns_fd; } if (ret_netns_fd) { - const char *netns; - - netns = pid_namespace_path(pidref->pid, NAMESPACE_NET); - netns_fd = open(netns, O_RDONLY|O_NOCTTY|O_CLOEXEC); + netns_fd = pidref_namespace_open_by_type_internal(pidref, NAMESPACE_NET, &need_verify); if (netns_fd < 0) - return -errno; + return netns_fd; } if (ret_userns_fd) { - const char *userns; - - userns = pid_namespace_path(pidref->pid, NAMESPACE_USER); - userns_fd = open(userns, O_RDONLY|O_NOCTTY|O_CLOEXEC); - if (userns_fd < 0 && errno != ENOENT) - return -errno; + userns_fd = pidref_namespace_open_by_type_internal(pidref, NAMESPACE_USER, &need_verify); + if (userns_fd < 0 && !IN_SET(userns_fd, -ENOENT, -ENOPKG)) + return userns_fd; } if (ret_root_fd) { const char *root; root = procfs_file_alloca(pidref->pid, "root"); - root_fd = open(root, O_RDONLY|O_NOCTTY|O_CLOEXEC|O_DIRECTORY); + root_fd = RET_NERRNO(open(root, O_CLOEXEC|O_DIRECTORY)); + if (root_fd == -ENOENT && proc_mounted() == 0) + return -ENOSYS; if (root_fd < 0) - return -errno; + return root_fd; + + need_verify = true; } - r = pidref_verify(pidref); - if (r < 0) - return r; + if (need_verify) { + r = pidref_verify(pidref); + if (r < 0) + return r; + } if (ret_pidns_fd) *ret_pidns_fd = TAKE_FD(pidns_fd); @@ -154,10 +209,10 @@ int namespace_enter(int pidns_fd, int mntns_fd, int netns_fd, int userns_fd, int /* Can't setns to your own userns, since then you could escalate from non-root to root in * your own namespace, so check if namespaces are equal before attempting to enter. */ - r = inode_same_at(userns_fd, "", AT_FDCWD, "/proc/self/ns/user", AT_EMPTY_PATH); + r = is_our_namespace(userns_fd, NAMESPACE_USER); if (r < 0) return r; - if (r) + if (r > 0) userns_fd = -EBADF; } @@ -188,50 +243,128 @@ int namespace_enter(int pidns_fd, int mntns_fd, int netns_fd, int userns_fd, int return reset_uid_gid(); } -int fd_is_ns(int fd, unsigned long nsflag) { - struct statfs s; +int fd_is_namespace(int fd, NamespaceType type) { int r; - /* Checks whether the specified file descriptor refers to a namespace created by specifying nsflag in clone(). - * On old kernels there's no nice way to detect that, hence on those we'll return a recognizable error (EUCLEAN), - * so that callers can handle this somewhat nicely. - * - * This function returns > 0 if the fd definitely refers to a network namespace, 0 if it definitely does not - * refer to a network namespace, -EUCLEAN if we can't determine, and other negative error codes on error. */ + /* Checks whether the specified file descriptor refers to a namespace (of type if type != _NAMESPACE_INVALID). */ - if (fstatfs(fd, &s) < 0) + assert(fd >= 0); + assert(type < _NAMESPACE_TYPE_MAX); + + r = fd_is_fs_type(fd, NSFS_MAGIC); + if (r <= 0) + return r; + + if (type < 0) + return true; + + int clone_flag = ioctl(fd, NS_GET_NSTYPE); + if (clone_flag < 0) return -errno; - if (!is_fs_type(&s, NSFS_MAGIC)) { - /* On really old kernels, there was no "nsfs", and network namespace sockets belonged to procfs - * instead. Handle that in a somewhat smart way. */ + NamespaceType found_type = clone_flag_to_namespace_type(clone_flag); + if (found_type < 0) + return -EBADF; /* Uh? Unknown namespace type? */ - if (is_fs_type(&s, PROC_SUPER_MAGIC)) { - struct statfs t; + return found_type == type; +} - /* OK, so it is procfs. Let's see if our own network namespace is procfs, too. If so, then the - * passed fd might refer to a network namespace, but we can't know for sure. In that case, - * return a recognizable error. */ +int is_our_namespace(int fd, NamespaceType type) { + int r; - if (statfs("/proc/self/ns/net", &t) < 0) - return -errno; + assert(fd >= 0); + assert(type < _NAMESPACE_TYPE_MAX); - if (s.f_type == t.f_type) - return -EUCLEAN; /* It's possible, we simply don't know */ + r = fd_is_namespace(fd, type); + if (r < 0) + return r; + if (r == 0) /* Not a namespace or not of the right type? */ + return -EUCLEAN; + + _cleanup_close_ int our_ns = namespace_open_by_type(type); + if (our_ns < 0) + return our_ns; + + return fd_inode_same(fd, our_ns); +} + +int namespace_is_init(NamespaceType type) { + int r; + + assert(type >= 0); + assert(type < _NAMESPACE_TYPE_MAX); + + if (namespace_info[type].root_inode == 0) + return -EBADR; /* Cannot answer this question */ + + const char *p = pid_namespace_path(0, type); + + struct stat st; + r = RET_NERRNO(stat(p, &st)); + if (r == -ENOENT) + /* If the /proc/ns/ API is not around in /proc/ then ns is off in the kernel and we are in the init ns */ + return proc_mounted() == 0 ? -ENOSYS : true; + if (r < 0) + return r; + + return st.st_ino == namespace_info[type].root_inode; +} + +int pidref_in_same_namespace(PidRef *pid1, PidRef *pid2, NamespaceType type) { + _cleanup_close_ int ns1 = -EBADF, ns2 = -EBADF; + + /* Accepts NULL to indicate our own process */ + + assert(!pid1 || pidref_is_set(pid1)); + assert(!pid2 || pidref_is_set(pid2)); + assert(type >= 0 && type < _NAMESPACE_TYPE_MAX); + + if (pidref_equal(pid1, pid2)) + return true; + + if (!pid1) + ns1 = namespace_open_by_type(type); + else + ns1 = pidref_namespace_open_by_type(pid1, type); + if (ns1 < 0) + return ns1; + + if (!pid2) + ns2 = namespace_open_by_type(type); + else + ns2 = pidref_namespace_open_by_type(pid2, type); + if (ns2 < 0) + return ns2; + + return fd_inode_same(ns1, ns2); +} + +int namespace_get_leader(pid_t pid, NamespaceType type, pid_t *ret) { + int r; + + assert(pid >= 0); + assert(type >= 0 && type < _NAMESPACE_TYPE_MAX); + assert(ret); + + for (;;) { + pid_t ppid; + + r = pid_get_ppid(pid, &ppid); + if (r < 0) + return r; + + r = in_same_namespace(pid, ppid, type); + if (r < 0) + return r; + if (r == 0) { + /* If the parent and the child are not in the same namespace, then the child is + * the leader we are looking for. */ + *ret = pid; + return 0; } - return 0; /* No! */ + pid = ppid; } - - r = ioctl(fd, NS_GET_NSTYPE); - if (r < 0) { - if (errno == ENOTTY) /* Old kernels didn't know this ioctl, let's also return a recognizable error in that case */ - return -EUCLEAN; - - return -errno; - } - - return (unsigned long) r == nsflag; } int detach_mount_namespace(void) { @@ -402,30 +535,6 @@ int netns_acquire(void) { return TAKE_FD(netns_fd); } -int in_same_namespace(pid_t pid1, pid_t pid2, NamespaceType type) { - const char *ns_path; - struct stat ns_st1, ns_st2; - - if (pid1 == 0) - pid1 = getpid_cached(); - - if (pid2 == 0) - pid2 = getpid_cached(); - - if (pid1 == pid2) - return 1; - - ns_path = pid_namespace_path(pid1, type); - if (stat(ns_path, &ns_st1) < 0) - return -errno; - - ns_path = pid_namespace_path(pid2, type); - if (stat(ns_path, &ns_st2) < 0) - return -errno; - - return stat_inode_same(&ns_st1, &ns_st2); -} - int parse_userns_uid_range(const char *s, uid_t *ret_uid_shift, uid_t *ret_uid_range) { _cleanup_free_ char *buffer = NULL; const char *range, *shift; @@ -464,75 +573,6 @@ int parse_userns_uid_range(const char *s, uid_t *ret_uid_shift, uid_t *ret_uid_r return 0; } -int namespace_open_by_type(NamespaceType type) { - const char *p; - int fd; - - assert(type >= 0); - assert(type < _NAMESPACE_TYPE_MAX); - - p = pid_namespace_path(0, type); - - fd = RET_NERRNO(open(p, O_RDONLY|O_NOCTTY|O_CLOEXEC)); - if (fd == -ENOENT && proc_mounted() == 0) - return -ENOSYS; - - return fd; -} - -int namespace_is_init(NamespaceType type) { - int r; - - assert(type >= 0); - assert(type <= _NAMESPACE_TYPE_MAX); - - if (namespace_info[type].root_inode == 0) - return -EBADR; /* Cannot answer this question */ - - const char *p = pid_namespace_path(0, type); - - struct stat st; - r = RET_NERRNO(stat(p, &st)); - if (r == -ENOENT) - /* If the /proc/ns/ API is not around in /proc/ then ns is off in the kernel and we are in the init ns */ - return proc_mounted() == 0 ? -ENOSYS : true; - if (r < 0) - return r; - - return st.st_ino == namespace_info[type].root_inode; -} - -int is_our_namespace(int fd, NamespaceType request_type) { - int clone_flag; - - assert(fd >= 0); - - clone_flag = ioctl(fd, NS_GET_NSTYPE); - if (clone_flag < 0) - return -errno; - - NamespaceType found_type = clone_flag_to_namespace_type(clone_flag); - if (found_type < 0) - return -EBADF; /* Uh? Unknown namespace type? */ - - if (request_type >= 0 && request_type != found_type) /* It's a namespace, but not of the right type? */ - return -EUCLEAN; - - struct stat st_fd, st_ours; - if (fstat(fd, &st_fd) < 0) - return -errno; - - const char *p = pid_namespace_path(0, found_type); - if (stat(p, &st_ours) < 0) { - if (errno == ENOENT) - return proc_mounted() == 0 ? -ENOSYS : -ENOENT; - - return -errno; - } - - return stat_inode_same(&st_ours, &st_fd); -} - int is_idmapping_supported(const char *path) { _cleanup_close_ int mount_fd = -EBADF, userns_fd = -EBADF, dir_fd = -EBADF; _cleanup_free_ char *uid_map = NULL, *gid_map = NULL; diff --git a/src/basic/namespace-util.h b/src/basic/namespace-util.h index e92d407864..319efec4ac 100644 --- a/src/basic/namespace-util.h +++ b/src/basic/namespace-util.h @@ -3,11 +3,9 @@ #include -typedef enum NamespaceType NamespaceType; - #include "pidref.h" -enum NamespaceType { +typedef enum NamespaceType { NAMESPACE_CGROUP, NAMESPACE_IPC, NAMESPACE_NET, @@ -18,15 +16,21 @@ enum NamespaceType { NAMESPACE_TIME, _NAMESPACE_TYPE_MAX, _NAMESPACE_TYPE_INVALID = -EINVAL, -}; +} NamespaceType; extern const struct namespace_info { const char *proc_name; const char *proc_path; - unsigned int clone_flag; + unsigned long clone_flag; + unsigned long pidfd_get_ns_ioctl_cmd; ino_t root_inode; } namespace_info[_NAMESPACE_TYPE_MAX + 1]; +NamespaceType clone_flag_to_namespace_type(unsigned long clone_flag); + +int pidref_namespace_open_by_type(const PidRef *pidref, NamespaceType type); +int namespace_open_by_type(NamespaceType type); + int pidref_namespace_open( const PidRef *pidref, int *ret_pidns_fd, @@ -41,9 +45,24 @@ int namespace_open( int *ret_netns_fd, int *ret_userns_fd, int *ret_root_fd); + int namespace_enter(int pidns_fd, int mntns_fd, int netns_fd, int userns_fd, int root_fd); -int fd_is_ns(int fd, unsigned long nsflag); +int fd_is_namespace(int fd, NamespaceType type); +int is_our_namespace(int fd, NamespaceType type); + +int namespace_is_init(NamespaceType type); + +int pidref_in_same_namespace(PidRef *pid1, PidRef *pid2, NamespaceType type); +static inline int in_same_namespace(pid_t pid1, pid_t pid2, NamespaceType type) { + assert(pid1 >= 0); + assert(pid2 >= 0); + return pidref_in_same_namespace(pid1 == 0 ? NULL : &PIDREF_MAKE_FROM_PID(pid1), + pid2 == 0 ? NULL : &PIDREF_MAKE_FROM_PID(pid2), + type); +} + +int namespace_get_leader(pid_t pid, NamespaceType type, pid_t *ret); int detach_mount_namespace(void); int detach_mount_namespace_harder(uid_t target_uid, gid_t target_gid); @@ -69,14 +88,6 @@ int userns_acquire(const char *uid_map, const char *gid_map); int netns_acquire(void); -int in_same_namespace(pid_t pid1, pid_t pid2, NamespaceType type); - int parse_userns_uid_range(const char *s, uid_t *ret_uid_shift, uid_t *ret_uid_range); -int namespace_open_by_type(NamespaceType type); - -int namespace_is_init(NamespaceType type); - -int is_our_namespace(int fd, NamespaceType type); - int is_idmapping_supported(const char *path); diff --git a/src/basic/pidfd-util.c b/src/basic/pidfd-util.c new file mode 100644 index 0000000000..204439e444 --- /dev/null +++ b/src/basic/pidfd-util.c @@ -0,0 +1,251 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include +#include + +#include "errno-util.h" +#include "fd-util.h" +#include "fileio.h" +#include "macro.h" +#include "memory-util.h" +#include "missing_magic.h" +#include "parse-util.h" +#include "path-util.h" +#include "pidfd-util.h" +#include "process-util.h" +#include "stat-util.h" +#include "string-util.h" + +static int have_pidfs = -1; + +static int pidfd_check_pidfs(void) { + + if (have_pidfs >= 0) + return have_pidfs; + + _cleanup_close_ int fd = pidfd_open(getpid_cached(), 0); + if (fd < 0) { + if (ERRNO_IS_NOT_SUPPORTED(errno)) + return (have_pidfs = false); + + return -errno; + } + + return (have_pidfs = fd_is_fs_type(fd, PID_FS_MAGIC)); +} + +int pidfd_get_namespace(int fd, unsigned long ns_type_cmd) { + static bool cached_supported = true; + + /* Obtain the namespace fd from pidfd directly through ioctl(PIDFD_GET_*_NAMESPACE). + * + * Returns -EOPNOTSUPP if ioctl on pidfds are not supported, -ENOPKG if the requested namespace + * is disabled in kernel. (The errno used are different from what kernel returns via ioctl(), + * see below) */ + + assert(fd >= 0); + + /* If we know ahead of time that pidfs is unavailable, shortcut things. But otherwise we don't + * call pidfd_check_pidfs() here, which is kinda extraneous and our own cache is required + * anyways (pidfs is introduced in kernel 6.9 while ioctl support there is added in 6.11). */ + if (have_pidfs == 0 || !cached_supported) + return -EOPNOTSUPP; + + int nsfd = ioctl(fd, ns_type_cmd); + if (nsfd < 0) { + /* Kernel returns EOPNOTSUPP if the ns type in question is disabled. Hence we need to look + * at precise errno instead of generic ERRNO_IS_(IOCTL_)NOT_SUPPORTED. */ + if (IN_SET(errno, ENOTTY, EINVAL)) { + cached_supported = false; + return -EOPNOTSUPP; + } + if (errno == EOPNOTSUPP) /* Translate to something more recognizable */ + return -ENOPKG; + + return -errno; + } + + return nsfd; +} + +static int pidfd_get_info(int fd, struct pidfd_info *info) { + static bool cached_supported = true; + + assert(fd >= 0); + assert(info); + + if (have_pidfs == 0 || !cached_supported) + return -EOPNOTSUPP; + + if (ioctl(fd, PIDFD_GET_INFO, info) < 0) { + if (ERRNO_IS_IOCTL_NOT_SUPPORTED(errno)) { + cached_supported = false; + return -EOPNOTSUPP; + } + + return -errno; + } + + return 0; +} + +static int pidfd_get_pid_fdinfo(int fd, pid_t *ret) { + char path[STRLEN("/proc/self/fdinfo/") + DECIMAL_STR_MAX(int)]; + _cleanup_free_ char *fdinfo = NULL; + int r; + + assert(fd >= 0); + + xsprintf(path, "/proc/self/fdinfo/%i", fd); + + r = read_full_virtual_file(path, &fdinfo, NULL); + if (r == -ENOENT) + return proc_fd_enoent_errno(); + if (r < 0) + return r; + + char *p = find_line_startswith(fdinfo, "Pid:"); + if (!p) + return -ENOTTY; /* not a pidfd? */ + + p = skip_leading_chars(p, /* bad = */ NULL); + p[strcspn(p, WHITESPACE)] = 0; + + if (streq(p, "0")) + return -EREMOTE; /* PID is in foreign PID namespace? */ + if (streq(p, "-1")) + return -ESRCH; /* refers to reaped process? */ + + return parse_pid(p, ret); +} + +static int pidfd_get_pid_ioctl(int fd, pid_t *ret) { + struct pidfd_info info = { .mask = PIDFD_INFO_PID }; + int r; + + assert(fd >= 0); + + r = pidfd_get_info(fd, &info); + if (r < 0) + return r; + + assert(FLAGS_SET(info.mask, PIDFD_INFO_PID)); + + if (ret) + *ret = info.pid; + return 0; +} + +int pidfd_get_pid(int fd, pid_t *ret) { + int r; + + /* Converts a pidfd into a pid. We try ioctl(PIDFD_GET_INFO) (kernel 6.13+) first, + * /proc/self/fdinfo/ as fallback. Well known errors: + * + * -EBADF → fd invalid + * -ESRCH → fd valid, but process is already reaped + * + * pidfd_get_pid_fdinfo() might additionally fail for other reasons: + * + * -ENOSYS → /proc/ not mounted + * -ENOTTY → fd valid, but not a pidfd + * -EREMOTE → fd valid, but pid is in another namespace we cannot translate to the local one + * (when using PIDFD_GET_INFO this is indistinguishable from -ESRCH) + */ + + assert(fd >= 0); + + r = pidfd_get_pid_ioctl(fd, ret); + if (r != -EOPNOTSUPP) + return r; + + return pidfd_get_pid_fdinfo(fd, ret); +} + +int pidfd_verify_pid(int pidfd, pid_t pid) { + pid_t current_pid; + int r; + + assert(pidfd >= 0); + assert(pid > 0); + + r = pidfd_get_pid(pidfd, ¤t_pid); + if (r < 0) + return r; + + return current_pid != pid ? -ESRCH : 0; +} + +int pidfd_get_ppid(int fd, pid_t *ret) { + struct pidfd_info info = { .mask = PIDFD_INFO_PID }; + int r; + + assert(fd >= 0); + + r = pidfd_get_info(fd, &info); + if (r < 0) + return r; + + assert(FLAGS_SET(info.mask, PIDFD_INFO_PID)); + + if (info.ppid == 0) /* See comments in pid_get_ppid() */ + return -EADDRNOTAVAIL; + + if (ret) + *ret = info.ppid; + return 0; +} + +int pidfd_get_uid(int fd, uid_t *ret) { + struct pidfd_info info = { .mask = PIDFD_INFO_CREDS }; + int r; + + assert(fd >= 0); + + r = pidfd_get_info(fd, &info); + if (r < 0) + return r; + + assert(FLAGS_SET(info.mask, PIDFD_INFO_CREDS)); + + if (ret) + *ret = info.ruid; + return 0; +} + +int pidfd_get_cgroupid(int fd, uint64_t *ret) { + struct pidfd_info info = { .mask = PIDFD_INFO_CGROUPID }; + int r; + + assert(fd >= 0); + + r = pidfd_get_info(fd, &info); + if (r < 0) + return r; + + assert(FLAGS_SET(info.mask, PIDFD_INFO_CGROUPID)); + + if (ret) + *ret = info.cgroupid; + return 0; +} + +int pidfd_get_inode_id(int fd, uint64_t *ret) { + int r; + + assert(fd >= 0); + + r = pidfd_check_pidfs(); + if (r < 0) + return r; + if (r == 0) + return -EOPNOTSUPP; + + struct stat st; + if (fstat(fd, &st) < 0) + return -errno; + + if (ret) + *ret = (uint64_t) st.st_ino; + return 0; +} diff --git a/src/basic/pidfd-util.h b/src/basic/pidfd-util.h new file mode 100644 index 0000000000..374e96261b --- /dev/null +++ b/src/basic/pidfd-util.h @@ -0,0 +1,19 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include +#include + +#include "missing_pidfd.h" +#include "missing_syscall.h" + +int pidfd_get_namespace(int fd, unsigned long ns_type_cmd); + +int pidfd_get_pid(int fd, pid_t *ret); +int pidfd_verify_pid(int pidfd, pid_t pid); + +int pidfd_get_ppid(int fd, pid_t *ret); +int pidfd_get_uid(int fd, uid_t *ret); +int pidfd_get_cgroupid(int fd, uint64_t *ret); + +int pidfd_get_inode_id(int fd, uint64_t *ret); diff --git a/src/basic/pidref.c b/src/basic/pidref.c index 5e4b3438c1..f665438375 100644 --- a/src/basic/pidref.c +++ b/src/basic/pidref.c @@ -1,36 +1,14 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#if HAVE_PIDFD_OPEN -#include -#endif - #include "errno-util.h" #include "fd-util.h" -#include "missing_magic.h" #include "missing_syscall.h" #include "missing_wait.h" #include "parse-util.h" +#include "pidfd-util.h" #include "pidref.h" #include "process-util.h" #include "signal-util.h" -#include "stat-util.h" - -static int pidfd_inode_ids_supported(void) { - static int cached = -1; - - if (cached >= 0) - return cached; - - _cleanup_close_ int fd = pidfd_open(getpid_cached(), 0); - if (fd < 0) { - if (ERRNO_IS_NOT_SUPPORTED(errno)) - return (cached = false); - - return -errno; - } - - return (cached = fd_is_fs_type(fd, PID_FS_MAGIC)); -} int pidref_acquire_pidfd_id(PidRef *pidref) { int r; @@ -49,19 +27,14 @@ int pidref_acquire_pidfd_id(PidRef *pidref) { if (pidref->fd_id > 0) return 0; - r = pidfd_inode_ids_supported(); - if (r < 0) + r = pidfd_get_inode_id(pidref->fd, &pidref->fd_id); + if (r < 0) { + if (!ERRNO_IS_NEG_NOT_SUPPORTED(r)) + log_debug_errno(r, "Failed to get inode number of pidfd for pid " PID_FMT ": %m", + pidref->pid); return r; - if (r == 0) - return -EOPNOTSUPP; + } - struct stat st; - - if (fstat(pidref->fd, &st) < 0) - return log_debug_errno(errno, "Failed to get inode number of pidfd for pid " PID_FMT ": %m", - pidref->pid); - - pidref->fd_id = (uint64_t) st.st_ino; return 0; } diff --git a/src/basic/process-util.c b/src/basic/process-util.c index d02a122a35..53ac2717e3 100644 --- a/src/basic/process-util.c +++ b/src/basic/process-util.c @@ -48,6 +48,7 @@ #include "nulstr-util.h" #include "parse-util.h" #include "path-util.h" +#include "pidfd-util.h" #include "process-util.h" #include "raw-clone.h" #include "rlimit-util.h" @@ -395,33 +396,6 @@ int container_get_leader(const char *machine, pid_t *pid) { return 0; } -int namespace_get_leader(pid_t pid, NamespaceType type, pid_t *ret) { - int r; - - assert(ret); - - for (;;) { - pid_t ppid; - - r = get_process_ppid(pid, &ppid); - if (r < 0) - return r; - - r = in_same_namespace(pid, ppid, type); - if (r < 0) - return r; - if (r == 0) { - /* If the parent and the child are not in the same - * namespace, then the child is the leader we are - * looking for. */ - *ret = pid; - return 0; - } - - pid = ppid; - } -} - int pid_is_kernel_thread(pid_t pid) { _cleanup_free_ char *line = NULL; unsigned long long flags; @@ -584,7 +558,6 @@ int pid_get_uid(pid_t pid, uid_t *ret) { } int pidref_get_uid(const PidRef *pid, uid_t *ret) { - uid_t uid; int r; if (!pidref_is_set(pid)) @@ -593,6 +566,13 @@ int pidref_get_uid(const PidRef *pid, uid_t *ret) { if (pidref_is_remote(pid)) return -EREMOTE; + if (pid->fd >= 0) { + r = pidfd_get_uid(pid->fd, ret); + if (!ERRNO_IS_NEG_NOT_SUPPORTED(r)) + return r; + } + + uid_t uid; r = pid_get_uid(pid->pid, &uid); if (r < 0) return r; @@ -678,7 +658,7 @@ int get_process_environ(pid_t pid, char **ret) { return 0; } -int get_process_ppid(pid_t pid, pid_t *ret) { +int pid_get_ppid(pid_t pid, pid_t *ret) { _cleanup_free_ char *line = NULL; unsigned long ppid; const char *p; @@ -733,6 +713,35 @@ int get_process_ppid(pid_t pid, pid_t *ret) { return 0; } +int pidref_get_ppid(const PidRef *pidref, pid_t *ret) { + int r; + + if (!pidref_is_set(pidref)) + return -ESRCH; + + if (pidref_is_remote(pidref)) + return -EREMOTE; + + if (pidref->fd >= 0) { + r = pidfd_get_ppid(pidref->fd, ret); + if (!ERRNO_IS_NEG_NOT_SUPPORTED(r)) + return r; + } + + pid_t ppid; + r = pid_get_ppid(pidref->pid, ret ? &ppid : NULL); + if (r < 0) + return r; + + r = pidref_verify(pidref); + if (r < 0) + return r; + + if (ret) + *ret = ppid; + return 0; +} + int pid_get_start_time(pid_t pid, usec_t *ret) { _cleanup_free_ char *line = NULL; const char *p; @@ -1073,25 +1082,8 @@ int getenv_for_pid(pid_t pid, const char *field, char **ret) { return 0; } -int pid_is_my_child(pid_t pid) { - pid_t ppid; - int r; - - if (pid < 0) - return -ESRCH; - - if (pid <= 1) - return false; - - r = get_process_ppid(pid, &ppid); - if (r < 0) - return r; - - return ppid == getpid_cached(); -} - int pidref_is_my_child(const PidRef *pid) { - int r, result; + int r; if (!pidref_is_set(pid)) return -ESRCH; @@ -1099,38 +1091,30 @@ int pidref_is_my_child(const PidRef *pid) { if (pidref_is_remote(pid)) return -EREMOTE; - result = pid_is_my_child(pid->pid); - if (result < 0) - return result; + if (pid->pid == 1 || pidref_is_self(pid)) + return false; - r = pidref_verify(pid); + pid_t ppid; + r = pidref_get_ppid(pid, &ppid); if (r < 0) return r; - return result; + return ppid == getpid_cached(); } -int pid_is_unwaited(pid_t pid) { - /* Checks whether a PID is still valid at all, including a zombie */ +int pid_is_my_child(pid_t pid) { - if (pid < 0) - return -ESRCH; + if (pid == 0) + return false; - if (pid <= 1) /* If we or PID 1 would be dead and have been waited for, this code would not be running */ - return true; - - if (pid == getpid_cached()) - return true; - - if (kill(pid, 0) >= 0) - return true; - - return errno != ESRCH; + return pidref_is_my_child(&PIDREF_MAKE_FROM_PID(pid)); } int pidref_is_unwaited(const PidRef *pid) { int r; + /* Checks whether a PID is still valid at all, including a zombie */ + if (!pidref_is_set(pid)) return -ESRCH; @@ -1149,6 +1133,14 @@ int pidref_is_unwaited(const PidRef *pid) { return true; } +int pid_is_unwaited(pid_t pid) { + + if (pid == 0) + return true; + + return pidref_is_unwaited(&PIDREF_MAKE_FROM_PID(pid)); +} + int pid_is_alive(pid_t pid) { int r; @@ -1859,59 +1851,6 @@ int get_oom_score_adjust(int *ret) { return 0; } -int pidfd_get_pid(int fd, pid_t *ret) { - char path[STRLEN("/proc/self/fdinfo/") + DECIMAL_STR_MAX(int)]; - _cleanup_free_ char *fdinfo = NULL; - int r; - - /* Converts a pidfd into a pid. Well known errors: - * - * -EBADF → fd invalid - * -ENOSYS → /proc/ not mounted - * -ENOTTY → fd valid, but not a pidfd - * -EREMOTE → fd valid, but pid is in another namespace we cannot translate to the local one - * -ESRCH → fd valid, but process is already reaped - */ - - assert(fd >= 0); - - xsprintf(path, "/proc/self/fdinfo/%i", fd); - - r = read_full_virtual_file(path, &fdinfo, NULL); - if (r == -ENOENT) - return proc_fd_enoent_errno(); - if (r < 0) - return r; - - char *p = find_line_startswith(fdinfo, "Pid:"); - if (!p) - return -ENOTTY; /* not a pidfd? */ - - p = skip_leading_chars(p, /* bad = */ NULL); - p[strcspn(p, WHITESPACE)] = 0; - - if (streq(p, "0")) - return -EREMOTE; /* PID is in foreign PID namespace? */ - if (streq(p, "-1")) - return -ESRCH; /* refers to reaped process? */ - - return parse_pid(p, ret); -} - -int pidfd_verify_pid(int pidfd, pid_t pid) { - pid_t current_pid; - int r; - - assert(pidfd >= 0); - assert(pid > 0); - - r = pidfd_get_pid(pidfd, ¤t_pid); - if (r < 0) - return r; - - return current_pid != pid ? -ESRCH : 0; -} - static int rlimit_to_nice(rlim_t limit) { if (limit <= 1) return PRIO_MAX-1; /* i.e. 19 */ diff --git a/src/basic/process-util.h b/src/basic/process-util.h index f1088afc1d..b436a7b1b3 100644 --- a/src/basic/process-util.h +++ b/src/basic/process-util.h @@ -14,7 +14,6 @@ #include "alloc-util.h" #include "format-util.h" #include "macro.h" -#include "namespace-util.h" #include "pidref.h" #include "time-util.h" @@ -53,15 +52,14 @@ int get_process_gid(pid_t pid, gid_t *ret); int get_process_cwd(pid_t pid, char **ret); int get_process_root(pid_t pid, char **ret); int get_process_environ(pid_t pid, char **ret); -int get_process_ppid(pid_t pid, pid_t *ret); +int pid_get_ppid(pid_t pid, pid_t *ret); +int pidref_get_ppid(const PidRef *pidref, pid_t *ret); int pid_get_start_time(pid_t pid, usec_t *ret); -int pidref_get_start_time(const PidRef* pid, usec_t *ret); +int pidref_get_start_time(const PidRef *pid, usec_t *ret); int get_process_umask(pid_t pid, mode_t *ret); int container_get_leader(const char *machine, pid_t *pid); -int namespace_get_leader(pid_t pid, NamespaceType type, pid_t *ret); - int wait_for_terminate(pid_t pid, siginfo_t *status); typedef enum WaitFlags { @@ -250,9 +248,6 @@ assert_cc(TASKS_MAX <= (unsigned long) PID_T_MAX); /* Like TAKE_PTR() but for pid_t, resetting them to 0 */ #define TAKE_PID(pid) TAKE_GENERIC(pid, pid_t, 0) -int pidfd_get_pid(int fd, pid_t *ret); -int pidfd_verify_pid(int pidfd, pid_t pid); - int setpriority_closest(int priority); _noreturn_ void freeze(void); diff --git a/src/basic/terminal-util.c b/src/basic/terminal-util.c index 725ccc2b87..ab926a4214 100644 --- a/src/basic/terminal-util.c +++ b/src/basic/terminal-util.c @@ -2334,7 +2334,7 @@ int pty_open_peer_racefree(int fd, int mode) { if (peer_fd >= 0) return peer_fd; - if (ERRNO_IS_NOT_SUPPORTED(errno) || errno == EINVAL) /* new ioctl() is not supported, return a clear error */ + if (ERRNO_IS_IOCTL_NOT_SUPPORTED(errno)) /* new ioctl() is not supported, return a clear error */ return -EOPNOTSUPP; if (errno != EIO) diff --git a/src/basic/virt.c b/src/basic/virt.c index 88a406ef22..1ba8d36dba 100644 --- a/src/basic/virt.c +++ b/src/basic/virt.c @@ -16,6 +16,7 @@ #include "fileio.h" #include "macro.h" #include "missing_threads.h" +#include "namespace-util.h" #include "process-util.h" #include "stat-util.h" #include "string-table.h" diff --git a/src/core/namespace.c b/src/core/namespace.c index 72e9ca48b4..1a0c51fe72 100644 --- a/src/core/namespace.c +++ b/src/core/namespace.c @@ -3253,6 +3253,7 @@ int setup_shareable_ns(int ns_storage_socket[static 2], unsigned long nsflag) { int open_shareable_ns_path(int ns_storage_socket[static 2], const char *path, unsigned long nsflag) { _cleanup_close_ int ns = -EBADF; + NamespaceType type; int r; assert(ns_storage_socket); @@ -3264,6 +3265,9 @@ int open_shareable_ns_path(int ns_storage_socket[static 2], const char *path, un * it. This is supposed to be called ahead of time, i.e. before setup_shareable_ns() which will * allocate a new anonymous ns if needed. */ + type = clone_flag_to_namespace_type(nsflag); + assert(type >= 0); + r = posix_lock(ns_storage_socket[0], LOCK_EX); if (r < 0) return r; @@ -3282,11 +3286,11 @@ int open_shareable_ns_path(int ns_storage_socket[static 2], const char *path, un if (ns < 0) return -errno; - r = fd_is_ns(ns, nsflag); + r = fd_is_namespace(ns, type); + if (r < 0) + return r; if (r == 0) return -EINVAL; - if (r < 0 && r != -EUCLEAN) /* EUCLEAN: we don't know */ - return r; r = send_one_fd(ns_storage_socket[1], ns, MSG_DONTWAIT); if (r < 0) diff --git a/src/core/socket.c b/src/core/socket.c index 7e369e34af..ecdd6cf51b 100644 --- a/src/core/socket.c +++ b/src/core/socket.c @@ -31,6 +31,7 @@ #include "mkdir-label.h" #include "parse-util.h" #include "path-util.h" +#include "pidfd-util.h" #include "process-util.h" #include "recurse-dir.h" #include "selinux-util.h" @@ -828,13 +829,13 @@ static void socket_dump(Unit *u, FILE *f, const char *prefix) { cgroup_context_dump(UNIT(s), f, prefix); } -static int instance_from_socket(int fd, unsigned nr, char **instance) { - socklen_t l; - char *r; +static int instance_from_socket(int fd, unsigned nr, char **ret) { union sockaddr_union local, remote; + socklen_t l; + int r; assert(fd >= 0); - assert(instance); + assert(ret); l = sizeof(local); if (getsockname(fd, &local.sa, &l) < 0) @@ -844,6 +845,8 @@ static int instance_from_socket(int fd, unsigned nr, char **instance) { if (getpeername(fd, &remote.sa, &l) < 0) return -errno; + char *s; + switch (local.sa.sa_family) { case AF_INET: { @@ -851,7 +854,7 @@ static int instance_from_socket(int fd, unsigned nr, char **instance) { a = be32toh(local.in.sin_addr.s_addr), b = be32toh(remote.in.sin_addr.s_addr); - if (asprintf(&r, + if (asprintf(&s, "%u-%u.%u.%u.%u:%u-%u.%u.%u.%u:%u", nr, a >> 24, (a >> 16) & 0xFF, (a >> 8) & 0xFF, a & 0xFF, @@ -874,7 +877,7 @@ static int instance_from_socket(int fd, unsigned nr, char **instance) { *a = local.in6.sin6_addr.s6_addr+12, *b = remote.in6.sin6_addr.s6_addr+12; - if (asprintf(&r, + if (asprintf(&s, "%u-%u.%u.%u.%u:%u-%u.%u.%u.%u:%u", nr, a[0], a[1], a[2], a[3], @@ -883,7 +886,7 @@ static int instance_from_socket(int fd, unsigned nr, char **instance) { be16toh(remote.in6.sin6_port)) < 0) return -ENOMEM; } else { - if (asprintf(&r, + if (asprintf(&s, "%u-%s:%u-%s:%u", nr, IN6_ADDR_TO_STRING(&local.in6.sin6_addr), @@ -898,30 +901,35 @@ static int instance_from_socket(int fd, unsigned nr, char **instance) { case AF_UNIX: { struct ucred ucred; - int k; - k = getpeercred(fd, &ucred); - if (k >= 0) { - if (asprintf(&r, - "%u-"PID_FMT"-"UID_FMT, - nr, ucred.pid, ucred.uid) < 0) + r = getpeercred(fd, &ucred); + if (r >= 0) { + _cleanup_close_ int pidfd = getpeerpidfd(fd); + uint64_t pidfd_id; + + if (pidfd >= 0 && pidfd_get_inode_id(pidfd, &pidfd_id) >= 0) + r = asprintf(&s, "%u-" PID_FMT "_%" PRIu64 "-" UID_FMT, + nr, ucred.pid, pidfd_id, ucred.uid); + else + r = asprintf(&s, "%u-" PID_FMT "-" UID_FMT, + nr, ucred.pid, ucred.uid); + if (r < 0) return -ENOMEM; - } else if (k == -ENODATA) { - /* This handles the case where somebody is - * connecting from another pid/uid namespace + } else if (r == -ENODATA) { + /* This handles the case where somebody is connecting from another pid/uid namespace * (e.g. from outside of our container). */ - if (asprintf(&r, + if (asprintf(&s, "%u-unknown", nr) < 0) return -ENOMEM; } else - return k; + return r; break; } case AF_VSOCK: - if (asprintf(&r, + if (asprintf(&s, "%u-%u:%u-%u:%u", nr, local.vm.svm_cid, local.vm.svm_port, @@ -934,7 +942,7 @@ static int instance_from_socket(int fd, unsigned nr, char **instance) { assert_not_reached(); } - *instance = r; + *ret = s; return 0; } @@ -1395,15 +1403,14 @@ int socket_load_service_unit(Socket *s, int cfd, Unit **ret) { if (cfd >= 0) { r = instance_from_socket(cfd, s->n_accepted, &instance); - if (r < 0) { - if (ERRNO_IS_DISCONNECT(r)) - /* ENOTCONN is legitimate if TCP RST was received. Other socket families might return - * different errors. This connection is over, but the socket unit lives on. */ - return log_unit_debug_errno(UNIT(s), r, - "Got %s on incoming socket, assuming aborted connection attempt, ignoring.", - errno_to_name(r)); + if (ERRNO_IS_NEG_DISCONNECT(r)) + /* ENOTCONN is legitimate if TCP RST was received. Other socket families might return + * different errors. This connection is over, but the socket unit lives on. */ + return log_unit_debug_errno(UNIT(s), r, + "Got %s on incoming socket, assuming aborted connection attempt, ignoring.", + errno_to_name(r)); + if (r < 0) return r; - } } /* For accepting sockets, we don't know how the instance will be called until we get a connection and diff --git a/src/coredump/coredump.c b/src/coredump/coredump.c index 6970a6a898..c9f6a276e5 100644 --- a/src/coredump/coredump.c +++ b/src/coredump/coredump.c @@ -1761,7 +1761,7 @@ static int acquire_pid_mount_tree_fd(const Context *context, int *ret_fd) { return 0; } -static int process_kernel(int argc, char* argv[]) { +static int process_kernel(int argc, char *argv[]) { _cleanup_(iovw_free_freep) struct iovec_wrapper *iovw = NULL; _cleanup_(context_done) Context context = CONTEXT_NULL; int r; @@ -1799,7 +1799,7 @@ static int process_kernel(int argc, char* argv[]) { context.meta[META_ARGV_UID], context.meta[META_ARGV_SIGNAL], signal_to_string(context.signo)); - r = in_same_namespace(getpid_cached(), context.pidref.pid, NAMESPACE_PID); + r = pidref_in_same_namespace(/* pid1 = */ NULL, &context.pidref, NAMESPACE_PID); if (r < 0) log_debug_errno(r, "Failed to check pidns of crashing process, ignoring: %m"); if (r == 0) { diff --git a/src/libsystemd/sd-event/sd-event.c b/src/libsystemd/sd-event/sd-event.c index 7aea7d2581..c1f1747fd0 100644 --- a/src/libsystemd/sd-event/sd-event.c +++ b/src/libsystemd/sd-event/sd-event.c @@ -1,9 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include -#if HAVE_PIDFD_OPEN -#include -#endif #include #include @@ -31,6 +28,7 @@ #include "origin-id.h" #include "path-util.h" #include "prioq.h" +#include "pidfd-util.h" #include "process-util.h" #include "psi-util.h" #include "set.h" diff --git a/src/libsystemd/sd-login/sd-login.c b/src/libsystemd/sd-login/sd-login.c index b2c67c9d09..0fbc23afcb 100644 --- a/src/libsystemd/sd-login/sd-login.c +++ b/src/libsystemd/sd-login/sd-login.c @@ -22,6 +22,7 @@ #include "macro.h" #include "parse-util.h" #include "path-util.h" +#include "pidfd-util.h" #include "process-util.h" #include "socket-util.h" #include "stdio-util.h" diff --git a/src/libsystemd/sd-varlink/varlink-util.c b/src/libsystemd/sd-varlink/varlink-util.c index 765108515c..22f1a94f0b 100644 --- a/src/libsystemd/sd-varlink/varlink-util.c +++ b/src/libsystemd/sd-varlink/varlink-util.c @@ -19,7 +19,7 @@ int varlink_get_peer_pidref(sd_varlink *v, PidRef *ret) { int pidfd = sd_varlink_get_peer_pidfd(v); if (pidfd < 0) { - if (!ERRNO_IS_NEG_NOT_SUPPORTED(pidfd) && pidfd != -EINVAL) + if (!ERRNO_IS_NEG_IOCTL_NOT_SUPPORTED(pidfd)) return pidfd; pid_t pid; diff --git a/src/machine/machined-core.c b/src/machine/machined-core.c index 52cb915c30..90fd91f134 100644 --- a/src/machine/machined-core.c +++ b/src/machine/machined-core.c @@ -7,6 +7,7 @@ #include "fileio.h" #include "iovec-util.h" #include "machined.h" +#include "namespace-util.h" #include "process-util.h" #include "socket-util.h" #include "strv.h" @@ -182,7 +183,7 @@ void manager_enqueue_gc(Manager *m) { (void) sd_event_source_set_description(m->deferred_gc_event_source, "deferred-gc"); } -int machine_get_addresses(Machine* machine, struct local_address **ret_addresses) { +int machine_get_addresses(Machine *machine, struct local_address **ret_addresses) { assert(machine); assert(ret_addresses); @@ -206,7 +207,7 @@ int machine_get_addresses(Machine* machine, struct local_address **ret_addresses pid_t child; int r; - r = in_same_namespace(/* pid1 = */ 0, machine->leader.pid, NAMESPACE_NET); + r = pidref_in_same_namespace(/* pid1 = */ NULL, &machine->leader, NAMESPACE_NET); if (r < 0) return log_debug_errno(r, "Failed to check if container has private network: %m"); if (r > 0) diff --git a/src/mountfsd/mountwork.c b/src/mountfsd/mountwork.c index af46287fcf..cd2f034832 100644 --- a/src/mountfsd/mountwork.c +++ b/src/mountfsd/mountwork.c @@ -227,7 +227,7 @@ static int validate_userns(sd_varlink *link, int *userns_fd) { if (r < 0) return log_debug_errno(r, "User namespace file descriptor has unsafe flags set: %m"); - r = fd_is_ns(*userns_fd, CLONE_NEWUSER); + r = fd_is_namespace(*userns_fd, NAMESPACE_USER); if (r < 0) return r; if (r == 0) diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c index cc037c078f..4977ebd193 100644 --- a/src/nspawn/nspawn.c +++ b/src/nspawn/nspawn.c @@ -5244,12 +5244,10 @@ static int run_container( if (child_netns_fd < 0) return log_error_errno(errno, "Cannot open file %s: %m", arg_network_namespace_path); - r = fd_is_ns(child_netns_fd, CLONE_NEWNET); - if (r == -EUCLEAN) - log_debug_errno(r, "Cannot determine if passed network namespace path '%s' really refers to a network namespace, assuming it does.", arg_network_namespace_path); - else if (r < 0) + r = fd_is_namespace(child_netns_fd, NAMESPACE_NET); + if (r < 0) return log_error_errno(r, "Failed to check %s fs type: %m", arg_network_namespace_path); - else if (r == 0) + if (r == 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Path %s doesn't refer to a network namespace, refusing.", arg_network_namespace_path); } diff --git a/src/nsresourced/nsresourcework.c b/src/nsresourced/nsresourcework.c index 21fc2d9088..1cce13d442 100644 --- a/src/nsresourced/nsresourcework.c +++ b/src/nsresourced/nsresourcework.c @@ -666,7 +666,7 @@ static int validate_userns(sd_varlink *link, int userns_fd) { return log_debug_errno(r, "User namespace file descriptor has unsafe flags set: %m"); /* Validate this is actually a valid user namespace fd */ - r = fd_is_ns(userns_fd, CLONE_NEWUSER); + r = fd_is_namespace(userns_fd, NAMESPACE_USER); if (r < 0) return log_debug_errno(r, "Failed to check if user namespace fd is actually a user namespace: %m"); if (r == 0) @@ -1455,7 +1455,7 @@ static int validate_netns(sd_varlink *link, int userns_fd, int netns_fd) { return log_debug_errno(r, "Network namespace file descriptor has unsafe flags set: %m"); /* Validate this is actually a valid network namespace fd */ - r = fd_is_ns(netns_fd, CLONE_NEWNET); + r = fd_is_namespace(netns_fd, NAMESPACE_NET); if (r < 0) return r; if (r == 0) diff --git a/src/nsresourced/userns-restrict.c b/src/nsresourced/userns-restrict.c index be33f49f55..aed2e135cb 100644 --- a/src/nsresourced/userns-restrict.c +++ b/src/nsresourced/userns-restrict.c @@ -280,7 +280,7 @@ int userns_restrict_put_by_fd( assert(userns_fd >= 0); assert(n_mount_fds == 0 || mount_fds); - r = fd_is_ns(userns_fd, CLONE_NEWUSER); + r = fd_is_namespace(userns_fd, NAMESPACE_USER); if (r < 0) return log_debug_errno(r, "Failed to determine if file descriptor is user namespace: %m"); if (r == 0) diff --git a/src/shared/install-file.c b/src/shared/install-file.c index ea2189fc9e..c0a2204276 100644 --- a/src/shared/install-file.c +++ b/src/shared/install-file.c @@ -44,8 +44,7 @@ static int fs_make_very_read_only(int fd) { r = btrfs_subvol_set_read_only_fd(fd, true); if (r >= 0) return 0; - - if (!ERRNO_IS_NOT_SUPPORTED(r) && r != -EINVAL) + if (!ERRNO_IS_NEG_IOCTL_NOT_SUPPORTED(r)) return r; } diff --git a/src/shared/loop-util.c b/src/shared/loop-util.c index d707d466ab..9c105a6d13 100644 --- a/src/shared/loop-util.c +++ b/src/shared/loop-util.c @@ -312,11 +312,8 @@ static int loop_configure( if (!loop_configure_broken) { if (ioctl(fd, LOOP_CONFIGURE, c) < 0) { - /* Do fallback only if LOOP_CONFIGURE is not supported, propagate all other - * errors. Note that the kernel is weird: non-existing ioctls currently return EINVAL - * rather than ENOTTY on loopback block devices. They should fix that in the kernel, - * but in the meantime we accept both here. */ - if (!ERRNO_IS_NOT_SUPPORTED(errno) && errno != EINVAL) + /* Do fallback only if LOOP_CONFIGURE is not supported, propagate all other errors. */ + if (!ERRNO_IS_IOCTL_NOT_SUPPORTED(errno)) return log_device_debug_errno(dev, errno, "ioctl(LOOP_CONFIGURE) failed: %m"); loop_configure_broken = true; diff --git a/src/shared/mount-util.c b/src/shared/mount-util.c index 756fb74c35..0d8a1f68eb 100644 --- a/src/shared/mount-util.c +++ b/src/shared/mount-util.c @@ -1097,7 +1097,7 @@ static int mount_in_namespace( if (r < 0) return log_debug_errno(r, "Failed to retrieve FDs of the target process' namespace: %m"); - r = inode_same_at(mntns_fd, "", AT_FDCWD, "/proc/self/ns/mnt", AT_EMPTY_PATH); + r = is_our_namespace(mntns_fd, NAMESPACE_MOUNT); if (r < 0) return log_debug_errno(r, "Failed to determine if mount namespaces are equal: %m"); /* We can't add new mounts at runtime if the process wasn't started in a namespace */ diff --git a/src/test/test-namespace.c b/src/test/test-namespace.c index 66a8bdf427..d646306acf 100644 --- a/src/test/test-namespace.c +++ b/src/test/test-namespace.c @@ -164,6 +164,31 @@ TEST(ipcns) { test_shareable_ns(CLONE_NEWIPC); } +TEST(fd_is_namespace) { + _cleanup_close_ int fd = -EBADF; + + ASSERT_OK_ZERO(fd_is_namespace(STDIN_FILENO, NAMESPACE_NET)); + ASSERT_OK_ZERO(fd_is_namespace(STDOUT_FILENO, NAMESPACE_NET)); + ASSERT_OK_ZERO(fd_is_namespace(STDERR_FILENO, NAMESPACE_NET)); + + fd = namespace_open_by_type(NAMESPACE_MOUNT); + if (IN_SET(fd, -ENOSYS, -ENOENT)) { + log_notice("Path %s not found, skipping test", "/proc/self/ns/mnt"); + return; + } + ASSERT_OK(fd); + ASSERT_OK_POSITIVE(fd_is_namespace(fd, NAMESPACE_MOUNT)); + ASSERT_OK_ZERO(fd_is_namespace(fd, NAMESPACE_NET)); + fd = safe_close(fd); + + ASSERT_OK(fd = namespace_open_by_type(NAMESPACE_IPC)); + ASSERT_OK_POSITIVE(fd_is_namespace(fd, NAMESPACE_IPC)); + fd = safe_close(fd); + + ASSERT_OK(fd = namespace_open_by_type(NAMESPACE_NET)); + ASSERT_OK_POSITIVE(fd_is_namespace(fd, NAMESPACE_NET)); +} + TEST(protect_kernel_logs) { static const NamespaceParameters p = { .runtime_scope = RUNTIME_SCOPE_SYSTEM, diff --git a/src/test/test-process-util.c b/src/test/test-process-util.c index e6ba6fea70..5a6bcdffcc 100644 --- a/src/test/test-process-util.c +++ b/src/test/test-process-util.c @@ -69,7 +69,7 @@ static void test_pid_get_comm_one(pid_t pid) { ASSERT_OK(pid_get_cmdline(pid, 1, 0, &d)); log_info("PID"PID_FMT" cmdline truncated to 1: '%s'", pid, d); - r = get_process_ppid(pid, &e); + r = pid_get_ppid(pid, &e); if (pid == 1) ASSERT_ERROR(r, EADDRNOTAVAIL); else @@ -813,18 +813,18 @@ TEST(setpriority_closest) { } } -TEST(get_process_ppid) { +TEST(pid_get_ppid) { uint64_t limit; int r; - ASSERT_ERROR(get_process_ppid(1, NULL), EADDRNOTAVAIL); + ASSERT_ERROR(pid_get_ppid(1, NULL), EADDRNOTAVAIL); /* the process with the PID above the global limit definitely doesn't exist. Verify that */ ASSERT_OK(procfs_get_pid_max(&limit)); log_debug("kernel.pid_max = %"PRIu64, limit); if (limit < INT_MAX) { - r = get_process_ppid(limit + 1, NULL); + r = pid_get_ppid(limit + 1, NULL); log_debug_errno(r, "get_process_limit(%"PRIu64") → %d/%m", limit + 1, r); assert(r == -ESRCH); } @@ -833,7 +833,7 @@ TEST(get_process_ppid) { _cleanup_free_ char *c1 = NULL, *c2 = NULL; pid_t ppid; - r = get_process_ppid(pid, &ppid); + r = pid_get_ppid(pid, &ppid); if (r == -EADDRNOTAVAIL) { log_info("No further parent PID"); break; diff --git a/src/test/test-stat-util.c b/src/test/test-stat-util.c index 4687b561fc..3ab177d1ce 100644 --- a/src/test/test-stat-util.c +++ b/src/test/test-stat-util.c @@ -165,31 +165,6 @@ TEST(path_is_read_only_fs) { assert_se(path_is_read_only_fs("/i-dont-exist") == -ENOENT); } -TEST(fd_is_ns) { - _cleanup_close_ int fd = -EBADF; - - assert_se(fd_is_ns(STDIN_FILENO, CLONE_NEWNET) == 0); - assert_se(fd_is_ns(STDERR_FILENO, CLONE_NEWNET) == 0); - assert_se(fd_is_ns(STDOUT_FILENO, CLONE_NEWNET) == 0); - - fd = open("/proc/self/ns/mnt", O_CLOEXEC|O_RDONLY); - if (fd < 0) { - assert_se(errno == ENOENT); - log_notice("Path %s not found, skipping test", "/proc/self/ns/mnt"); - return; - } - assert_se(fd >= 0); - assert_se(IN_SET(fd_is_ns(fd, CLONE_NEWNET), 0, -EUCLEAN)); - fd = safe_close(fd); - - assert_se((fd = open("/proc/self/ns/ipc", O_CLOEXEC|O_RDONLY)) >= 0); - assert_se(IN_SET(fd_is_ns(fd, CLONE_NEWIPC), 1, -EUCLEAN)); - fd = safe_close(fd); - - assert_se((fd = open("/proc/self/ns/net", O_CLOEXEC|O_RDONLY)) >= 0); - assert_se(IN_SET(fd_is_ns(fd, CLONE_NEWNET), 1, -EUCLEAN)); -} - TEST(dir_is_empty) { _cleanup_(rm_rf_physical_and_freep) char *empty_dir = NULL; _cleanup_free_ char *j = NULL, *jj = NULL, *jjj = NULL; diff --git a/src/vmspawn/vmspawn.c b/src/vmspawn/vmspawn.c index 00e040d963..845c06dea2 100644 --- a/src/vmspawn/vmspawn.c +++ b/src/vmspawn/vmspawn.c @@ -46,6 +46,7 @@ #include "macro.h" #include "main-func.h" #include "mkdir.h" +#include "namespace-util.h" #include "netif-util.h" #include "pager.h" #include "parse-argument.h"