diff --git a/src/basic/pidref.c b/src/basic/pidref.c index 649a207c1e..ff52ccc311 100644 --- a/src/basic/pidref.c +++ b/src/basic/pidref.c @@ -91,7 +91,7 @@ bool pidref_equal(PidRef *a, PidRef *b) { return a->fd_id == b->fd_id; } -int pidref_set_pid(PidRef *pidref, pid_t pid) { +int pidref_set_pid_full(PidRef *pidref, pid_t pid, unsigned flags) { uint64_t pidfdid = 0; int fd; @@ -104,10 +104,11 @@ int pidref_set_pid(PidRef *pidref, pid_t pid) { (void) pidfd_get_inode_id_self_cached(&pidfdid); } - fd = pidfd_open(pid, 0); + fd = pidfd_open(pid, flags); if (fd < 0) { - /* Graceful fallback in case the kernel is out of fds */ - if (!ERRNO_IS_RESOURCE(errno)) + /* Graceful fallback in case the kernel is out of fds. + * The PIDFD_THREAD flag is supported since kernel v6.9 (64bef697d33b75fc06c5789b3f8108680271529f). */ + if (!(ERRNO_IS_RESOURCE(errno) || (errno == EINVAL && FLAGS_SET(flags, PIDFD_THREAD)))) return log_debug_errno(errno, "Failed to open pidfd for pid " PID_FMT ": %m", pid); fd = -EBADF; @@ -149,7 +150,7 @@ int pidref_set_pid_and_pidfd_id( return 0; } -int pidref_set_pidstr(PidRef *pidref, const char *pid) { +int pidref_set_pidstr_full(PidRef *pidref, const char *pid, unsigned flags) { pid_t nr; int r; @@ -159,7 +160,7 @@ int pidref_set_pidstr(PidRef *pidref, const char *pid) { if (r < 0) return r; - return pidref_set_pid(pidref, nr); + return pidref_set_pid_full(pidref, nr, flags); } int pidref_set_pidfd(PidRef *pidref, int fd) { diff --git a/src/basic/pidref.h b/src/basic/pidref.h index a4c138c4bf..ceec126141 100644 --- a/src/basic/pidref.h +++ b/src/basic/pidref.h @@ -70,8 +70,14 @@ bool pidref_equal(PidRef *a, PidRef *b); /* This turns a pid_t into a PidRef structure, and acquires a pidfd for it, if possible. (As opposed to * PIDREF_MAKE_FROM_PID() above, which does not acquire a pidfd.) */ -int pidref_set_pid(PidRef *pidref, pid_t pid); -int pidref_set_pidstr(PidRef *pidref, const char *pid); +int pidref_set_pid_full(PidRef *pidref, pid_t pid, unsigned flags); +static inline int pidref_set_pid(PidRef *pidref, pid_t pid) { + return pidref_set_pid_full(pidref, pid, /* flags= */ 0); +} +int pidref_set_pidstr_full(PidRef *pidref, const char *pid, unsigned flags); +static inline int pidref_set_pidstr(PidRef *pidref, const char *pid) { + return pidref_set_pidstr_full(pidref, pid, /* flags= */ 0); +} int pidref_set_pid_and_pidfd_id(PidRef *pidref, pid_t pid, uint64_t pidfd_id); int pidref_set_pidfd(PidRef *pidref, int fd); int pidref_set_pidfd_take(PidRef *pidref, int fd); /* takes ownership of the passed pidfd on success */ diff --git a/src/coredump/coredump-context.c b/src/coredump/coredump-context.c index 24d273499a..f7e978bbfc 100644 --- a/src/coredump/coredump-context.c +++ b/src/coredump/coredump-context.c @@ -48,6 +48,7 @@ void coredump_context_done(CoredumpContext *context) { assert(context); pidref_done(&context->pidref); + pidref_done(&context->tidref); free(context->hostname); free(context->comm); free(context->exe); @@ -266,8 +267,8 @@ int coredump_context_build_iovw(CoredumpContext *context) { if (r < 0) return log_error_errno(r, "Failed to add COREDUMP_COMM= field: %m"); - if (context->tid > 0) - (void) iovw_put_string_fieldf(&context->iovw, "COREDUMP_TID=", PID_FMT, context->tid); + if (pidref_is_set(&context->tidref)) + (void) iovw_put_string_fieldf(&context->iovw, "COREDUMP_TID=", PID_FMT, context->tidref.pid); if (context->thread_name) (void) iovw_put_string_field(&context->iovw, "COREDUMP_THREAD_NAME=", context->thread_name); @@ -383,10 +384,10 @@ static int coredump_context_parse_from_procfs(CoredumpContext *context) { if (r < 0) return log_error_errno(r, "Failed to get COMM: %m"); - if (context->tid > 0) { - r = pid_get_comm(context->tid, &context->thread_name); + if (pidref_is_set(&context->tidref)) { + r = pidref_get_comm(&context->tidref, &context->thread_name); if (r < 0) - log_warning_errno(r, "Failed to get comm for thread "PID_FMT", ignoring: %m", context->tid); + log_warning_errno(r, "Failed to get comm for thread "PID_FMT", ignoring: %m", context->tidref.pid); } r = get_process_exe(pid, &context->exe); @@ -518,7 +519,7 @@ static int context_parse_one(CoredumpContext *context, MetadataField meta, bool return 0; } case META_ARGV_TID: - r = parse_pid(s, &context->tid); + r = pidref_set_pidstr_full(&context->tidref, s, PIDFD_THREAD); if (r < 0) log_warning_errno(r, "Failed to parse TID \"%s\", ignoring: %m", s); return 0; diff --git a/src/coredump/coredump-context.h b/src/coredump/coredump-context.h index a2ce16b853..ee4334dce2 100644 --- a/src/coredump/coredump-context.h +++ b/src/coredump/coredump-context.h @@ -57,7 +57,7 @@ struct CoredumpContext { uint64_t rlimit; /* META_ARGV_RLIMIT */ char *hostname; /* META_ARGV_HOSTNAME */ unsigned dumpable; /* META_ARGV_DUMPABLE */ - pid_t tid; /* META_ARGV_TID */ + PidRef tidref; /* META_ARGV_TID */ char *comm; /* META_COMM */ char *exe; /* META_EXE */ char *unit; /* META_UNIT */ @@ -78,6 +78,7 @@ struct CoredumpContext { .pidref = PIDREF_NULL, \ .uid = UID_INVALID, \ .gid = GID_INVALID, \ + .tidref = PIDREF_NULL, \ .mount_tree_fd = -EBADF, \ .input_fd = -EBADF, \ } diff --git a/src/coredump/coredump-send.c b/src/coredump/coredump-send.c index 4817d82611..5b77301206 100644 --- a/src/coredump/coredump-send.c +++ b/src/coredump/coredump-send.c @@ -301,6 +301,16 @@ int coredump_send_to_container(CoredumpContext *context) { context->uid = ucred.uid; context->gid = ucred.gid; + /* Do not leak the TID in the initial PID namespace. */ + int tidfd = TAKE_FD(context->tidref.fd); + pid_t tid = context->tidref.pid; + context->tidref = PIDREF_NULL; + if (tidfd >= 0) { + r = pidref_set_pidfd_consume(&context->tidref, tidfd); + if (r < 0) + log_warning_errno(r, "Failed to acquire PidRef of TID ["PID_FMT"], ignoring: %m", tid); + } + r = coredump_context_build_iovw(context); if (r < 0) _exit(EXIT_FAILURE); diff --git a/src/coredump/coredump-submit.c b/src/coredump/coredump-submit.c index fe1042ce4a..55f8b6e9e1 100644 --- a/src/coredump/coredump-submit.c +++ b/src/coredump/coredump-submit.c @@ -201,8 +201,8 @@ static int fix_xattr(int fd, const CoredumpContext *context) { RET_GATHER(r, fix_xattr_one(fd, "user.coredump.hostname", context->hostname)); RET_GATHER(r, fix_xattr_one(fd, "user.coredump.comm", context->comm)); RET_GATHER(r, fix_xattr_one(fd, "user.coredump.exe", context->exe)); - if (context->tid > 0) { - RET_GATHER(r, fix_xattr_format(fd, "user.coredump.tid", PID_FMT, context->tid)); + if (pidref_is_set(&context->tidref)) { + RET_GATHER(r, fix_xattr_format(fd, "user.coredump.tid", PID_FMT, context->tidref.pid)); RET_GATHER(r, fix_xattr_one(fd, "user.coredump.thread_name", context->thread_name)); } diff --git a/src/include/override/linux/fcntl.h b/src/include/override/linux/fcntl.h new file mode 100644 index 0000000000..c2afd9c99b --- /dev/null +++ b/src/include/override/linux/fcntl.h @@ -0,0 +1,10 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +/* We do not use linux/fcntl.h as it conflicts with glibc's fcntl.h. */ +#include + +/* The below are defined in linux/fcntl.h, but not defined in glibc's fcntl.h. + * They are necessary for linux/pidfd.h, hence define them here. */ +#define PIDFD_SELF_THREAD -10000 /* Current thread. */ +#define PIDFD_SELF_THREAD_GROUP -10001 /* Current thread group leader. */ diff --git a/src/include/override/sys/pidfd.h b/src/include/override/sys/pidfd.h index 28fb75698e..0f4d243d18 100644 --- a/src/include/override/sys/pidfd.h +++ b/src/include/override/sys/pidfd.h @@ -1,9 +1,9 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -#include +/* Here, we do not use glibc's pidfd.h, as its definition of struct pidfd_info may be slightly older. */ +#include #include -#include /* Defined since glibc-2.36. * Supported since kernel v5.3 (7615d9e1780e26e0178c93c55b73309a5dc093d7). */ @@ -14,102 +14,3 @@ int pidfd_open_shim(pid_t pid, unsigned flags); * Supported since kernel v5.1 (3eb39f47934f9d5a3027fe00d906a45fe3a15fad). */ int pidfd_send_signal_shim(int fd, int sig, siginfo_t *info, unsigned flags); #define pidfd_send_signal pidfd_send_signal_shim - -/* since glibc-2.41 */ -#ifndef PIDFS_IOCTL_MAGIC -# define PIDFS_IOCTL_MAGIC 0xFF - -# 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 - -/* defined in linux/pidfd.h */ -#ifndef PIDFD_GET_INFO - -/* Flags for pidfd_info. */ -#define PIDFD_INFO_PID (1UL << 0) /* Always returned, even if not requested */ -#define PIDFD_INFO_CREDS (1UL << 1) /* Always returned, even if not requested */ -#define PIDFD_INFO_CGROUPID (1UL << 2) /* Always returned if available, even if not requested */ -#define PIDFD_INFO_EXIT (1UL << 3) /* Only returned if requested. */ -#define PIDFD_INFO_COREDUMP (1UL << 4) /* Only returned if requested. */ -#define PIDFD_INFO_SUPPORTED_MASK (1UL << 5) /* Want/got supported mask flags */ -#define PIDFD_INFO_COREDUMP_SIGNAL (1UL << 6) /* Always returned if PIDFD_INFO_COREDUMP is requested. */ -#define PIDFD_INFO_COREDUMP_CODE (1UL << 7) /* Always returned if PIDFD_INFO_COREDUMP is requested. */ - -#define PIDFD_INFO_SIZE_VER0 64 /* sizeof first published struct */ -#define PIDFD_INFO_SIZE_VER1 72 /* sizeof second published struct */ -#define PIDFD_INFO_SIZE_VER2 80 /* sizeof third published struct */ -#define PIDFD_INFO_SIZE_VER3 88 /* sizeof fourth published struct */ - -/* - * Values for @coredump_mask in pidfd_info. - * Only valid if PIDFD_INFO_COREDUMP is set in @mask. - * - * Note, the @PIDFD_COREDUMP_ROOT flag indicates that the generated - * coredump should be treated as sensitive and access should only be - * granted to privileged users. - */ -#define PIDFD_COREDUMPED (1U << 0) /* Did crash and... */ -#define PIDFD_COREDUMP_SKIP (1U << 1) /* coredumping generation was skipped. */ -#define PIDFD_COREDUMP_USER (1U << 2) /* coredump was done as the user. */ -#define PIDFD_COREDUMP_ROOT (1U << 3) /* coredump was done as root. */ - -struct pidfd_info { - /* - * This mask is similar to the request_mask in statx(2). - * - * Userspace indicates what extensions or expensive-to-calculate fields - * they want by setting the corresponding bits in mask. The kernel - * will ignore bits that it does not know about. - * - * When filling the structure, the kernel will only set bits - * corresponding to the fields that were actually filled by the kernel. - * This also includes any future extensions that might be automatically - * filled. If the structure size is too small to contain a field - * (requested or not), to avoid confusion the mask will not - * contain a bit for that field. - * - * As such, userspace MUST verify that mask contains the - * corresponding flags after the ioctl(2) returns to ensure that it is - * using valid data. - */ - __u64 mask; - /* - * The information contained in the following fields might be stale at the - * time it is received, as the target process might have exited as soon as - * the IOCTL was processed, and there is no way to avoid that. However, it - * is guaranteed that if the call was successful, then the information was - * correct and referred to the intended process at the time the work was - * performed. */ - __u64 cgroupid; - __u32 pid; - __u32 tgid; - __u32 ppid; - __u32 ruid; - __u32 rgid; - __u32 euid; - __u32 egid; - __u32 suid; - __u32 sgid; - __u32 fsuid; - __u32 fsgid; - __s32 exit_code; /* since kernel v6.15 (7477d7dce48a996ae4e4f0b5f7bd82de7ec9131b) */ - struct { /* coredump info */ - __u32 coredump_mask; /* since kernel v6.16 (1d8db6fd698de1f73b1a7d72aea578fdd18d9a87) */ - __u32 coredump_signal; /* since kernel v6.19 (036375522be8425874e9e0f907c7127e315c7a52) */ - __u32 coredump_code; /* since kernel v7.1 (701f7f4fbabbf4989ba6fbf033b160dd943221d5) */ - __u32 coredump_pad; /* since kernel v7.1 (701f7f4fbabbf4989ba6fbf033b160dd943221d5) */ - }; - __u64 supported_mask; /* Mask flags that this kernel supports */ -}; - -#define PIDFD_GET_INFO _IOWR(PIDFS_IOCTL_MAGIC, 11, struct pidfd_info) -#endif /* PIDFD_GET_INFO */ diff --git a/src/include/uapi/linux/ioctl.h b/src/include/uapi/linux/ioctl.h new file mode 100644 index 0000000000..b292e8093b --- /dev/null +++ b/src/include/uapi/linux/ioctl.h @@ -0,0 +1,8 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +#ifndef _LINUX_IOCTL_H +#define _LINUX_IOCTL_H + +#include + +#endif /* _LINUX_IOCTL_H */ + diff --git a/src/include/uapi/linux/pidfd.h b/src/include/uapi/linux/pidfd.h new file mode 100644 index 0000000000..69873fffdf --- /dev/null +++ b/src/include/uapi/linux/pidfd.h @@ -0,0 +1,119 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ + +#ifndef _LINUX_PIDFD_H +#define _LINUX_PIDFD_H + +#include +#include +#include + +/* Flags for pidfd_open(). */ +#define PIDFD_NONBLOCK O_NONBLOCK +#define PIDFD_THREAD O_EXCL + +/* Flags for pidfd_send_signal(). */ +#define PIDFD_SIGNAL_THREAD (1UL << 0) +#define PIDFD_SIGNAL_THREAD_GROUP (1UL << 1) +#define PIDFD_SIGNAL_PROCESS_GROUP (1UL << 2) + +/* Flags for pidfd_info. */ +#define PIDFD_INFO_PID (1UL << 0) /* Always returned, even if not requested */ +#define PIDFD_INFO_CREDS (1UL << 1) /* Always returned, even if not requested */ +#define PIDFD_INFO_CGROUPID (1UL << 2) /* Always returned if available, even if not requested */ +#define PIDFD_INFO_EXIT (1UL << 3) /* Only returned if requested. */ +#define PIDFD_INFO_COREDUMP (1UL << 4) /* Only returned if requested. */ +#define PIDFD_INFO_SUPPORTED_MASK (1UL << 5) /* Want/got supported mask flags */ +#define PIDFD_INFO_COREDUMP_SIGNAL (1UL << 6) /* Always returned if PIDFD_INFO_COREDUMP is requested. */ +#define PIDFD_INFO_COREDUMP_CODE (1UL << 7) /* Always returned if PIDFD_INFO_COREDUMP is requested. */ + +#define PIDFD_INFO_SIZE_VER0 64 /* sizeof first published struct */ +#define PIDFD_INFO_SIZE_VER1 72 /* sizeof second published struct */ +#define PIDFD_INFO_SIZE_VER2 80 /* sizeof third published struct */ +#define PIDFD_INFO_SIZE_VER3 88 /* sizeof fourth published struct */ + +/* + * Values for @coredump_mask in pidfd_info. + * Only valid if PIDFD_INFO_COREDUMP is set in @mask. + * + * Note, the @PIDFD_COREDUMP_ROOT flag indicates that the generated + * coredump should be treated as sensitive and access should only be + * granted to privileged users. + */ +#define PIDFD_COREDUMPED (1U << 0) /* Did crash and... */ +#define PIDFD_COREDUMP_SKIP (1U << 1) /* coredumping generation was skipped. */ +#define PIDFD_COREDUMP_USER (1U << 2) /* coredump was done as the user. */ +#define PIDFD_COREDUMP_ROOT (1U << 3) /* coredump was done as root. */ + +/* + * ...and for userland we make life simpler - PIDFD_SELF refers to the current + * thread, PIDFD_SELF_PROCESS refers to the process thread group leader. + * + * For nearly all practical uses, a user will want to use PIDFD_SELF. + */ +#define PIDFD_SELF PIDFD_SELF_THREAD +#define PIDFD_SELF_PROCESS PIDFD_SELF_THREAD_GROUP + +struct pidfd_info { + /* + * This mask is similar to the request_mask in statx(2). + * + * Userspace indicates what extensions or expensive-to-calculate fields + * they want by setting the corresponding bits in mask. The kernel + * will ignore bits that it does not know about. + * + * When filling the structure, the kernel will only set bits + * corresponding to the fields that were actually filled by the kernel. + * This also includes any future extensions that might be automatically + * filled. If the structure size is too small to contain a field + * (requested or not), to avoid confusion the mask will not + * contain a bit for that field. + * + * As such, userspace MUST verify that mask contains the + * corresponding flags after the ioctl(2) returns to ensure that it is + * using valid data. + */ + __u64 mask; + /* + * The information contained in the following fields might be stale at the + * time it is received, as the target process might have exited as soon as + * the IOCTL was processed, and there is no way to avoid that. However, it + * is guaranteed that if the call was successful, then the information was + * correct and referred to the intended process at the time the work was + * performed. */ + __u64 cgroupid; + __u32 pid; + __u32 tgid; + __u32 ppid; + __u32 ruid; + __u32 rgid; + __u32 euid; + __u32 egid; + __u32 suid; + __u32 sgid; + __u32 fsuid; + __u32 fsgid; + __s32 exit_code; + struct /* coredump info */ { + __u32 coredump_mask; + __u32 coredump_signal; + __u32 coredump_code; + __u32 coredump_pad; /* align supported_mask to 8 bytes */ + }; + __u64 supported_mask; /* Mask flags that this kernel supports */ +}; + +#define PIDFS_IOCTL_MAGIC 0xFF + +#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) +#define PIDFD_GET_INFO _IOWR(PIDFS_IOCTL_MAGIC, 11, struct pidfd_info) + +#endif /* _LINUX_PIDFD_H */