coredump: do not leak TID in the host to container (#43349)

PID, UID, and GID are converted by sending struct ucred, however, TID
was not converted. Let's convert TID if corresponding pidfd exists, and
discards TID otherwise.

Fixes 4c7af7d9d1.
This commit is contained in:
Yu Watanabe
2026-08-15 01:38:04 +09:00
committed by GitHub
10 changed files with 175 additions and 118 deletions
+7 -6
View File
@@ -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) {
+8 -2
View File
@@ -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 */
+7 -6
View File
@@ -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;
+2 -1
View File
@@ -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, \
}
+10
View File
@@ -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);
+2 -2
View File
@@ -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));
}
+10
View File
@@ -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 <fcntl.h>
/* 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. */
+2 -101
View File
@@ -1,9 +1,9 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include <linux/types.h>
/* Here, we do not use glibc's pidfd.h, as its definition of struct pidfd_info may be slightly older. */
#include <linux/pidfd.h>
#include <signal.h>
#include <sys/ioctl.h>
/* 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 */
+8
View File
@@ -0,0 +1,8 @@
/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
#ifndef _LINUX_IOCTL_H
#define _LINUX_IOCTL_H
#include <asm/ioctl.h>
#endif /* _LINUX_IOCTL_H */
+119
View File
@@ -0,0 +1,119 @@
/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
#ifndef _LINUX_PIDFD_H
#define _LINUX_PIDFD_H
#include <linux/types.h>
#include <linux/fcntl.h>
#include <linux/ioctl.h>
/* 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 */