[PATCH v4 09/11] pidfs, coredump: allow to verify coredump connection

Christian Brauner
2025-05-07T16:14:27Z

When a coredump connection is initiated use the socket cookie as the
coredump cookie and store it in the pidfd. The receiver can now easily
authenticate that the connection is coming from the kernel.

Unless the coredump server expects to handle connection from
non-crashing task it can validate that the connection has been made from
a crashing task:

fd_coredump = accept4(fd_socket, NULL, NULL, SOCK_CLOEXEC);
getsockopt(fd_coredump, SOL_SOCKET, SO_PEERPIDFD, &fd_peer_pidfd, &fd_peer_pidfd_len);

struct pidfd_info info = {
info.mask = PIDFD_INFO_EXIT | PIDFD_INFO_COREDUMP,
};

ioctl(pidfd, PIDFD_GET_INFO, &info);
/* Refuse connections that aren't from a crashing task. */
if (!(info.mask & PIDFD_INFO_COREDUMP) || !(info.coredump_mask & PIDFD_COREDUMPED) )
close(fd_coredump);

/*
* Make sure that the coredump cookie matches the connection cookie.
* If they don't it's not the coredump connection from the kernel.
* We'll get another connection request in a bit.
*/
getsocketop(fd_coredump, SOL_SOCKET, SO_COOKIE, &peer_cookie, &peer_cookie_len);
if (!info.coredump_cookie || (info.coredump_cookie != peer_cookie))
close(fd_coredump);

The kernel guarantees that by the time the connection is made the
coredump info is available.

Signed-off-by: Christian Brauner <brauner@kernel.org>
---
fs/coredump.c | 3 ++-
fs/pidfs.c | 20 +++++++++++++++++++-
include/linux/net.h | 1 +
include/linux/pidfs.h | 1 +
include/uapi/linux/pidfd.h | 1 +
net/unix/af_unix.c | 7 +++++++
6 files changed, 31 insertions(+), 2 deletions(-)

diff --git a/fs/coredump.c b/fs/coredump.c
index ddff1854988f..cfb7a3459785 100644
--- a/fs/coredump.c
+++ b/fs/coredump.c
@@ -907,7 +907,8 @@ void do_coredump(const kernel_siginfo_t *siginfo)
*/
retval = kernel_connect(socket,
(struct sockaddr *)(&coredump_unix_socket),
- COREDUMP_UNIX_SOCKET_ADDR_SIZE, O_NONBLOCK);
+ COREDUMP_UNIX_SOCKET_ADDR_SIZE, O_NONBLOCK |
+ SOCK_COREDUMP);

/*
* ... So we can safely put our pidfs reference now...
diff --git a/fs/pidfs.c b/fs/pidfs.c
index 8c4d83fb115b..7ff1e7923f19 100644
--- a/fs/pidfs.c
+++ b/fs/pidfs.c
@@ -35,6 +35,7 @@ struct pidfs_exit_info {
__u64 cgroupid;
__s32 exit_code;
__u32 coredump_mask;
+ __u64 coredump_cookie;
};

struct pidfs_inode {
@@ -300,6 +301,7 @@ static long pidfd_info(struct file *file, unsigned int cmd, unsigned long arg)

if (mask & PIDFD_INFO_COREDUMP) {
kinfo.mask |= PIDFD_INFO_COREDUMP;
+ kinfo.coredump_cookie = READ_ONCE(pidfs_i(inode)->__pei.coredump_cookie);
kinfo.coredump_mask = READ_ONCE(pidfs_i(inode)->__pei.coredump_mask);
}

@@ -321,8 +323,10 @@ static long pidfd_info(struct file *file, unsigned int cmd, unsigned long arg)

if (!(kinfo.mask & PIDFD_INFO_COREDUMP)) {
task_lock(task);
- if (task->mm)
+ if (task->mm) {
+ kinfo.coredump_cookie = READ_ONCE(pidfs_i(inode)->__pei.coredump_cookie);
kinfo.coredump_mask = pidfs_coredump_mask(task->mm->flags);
+ }
task_unlock(task);
}

@@ -589,6 +593,20 @@ void pidfs_exit(struct task_struct *tsk)
}
}

+void pidfs_coredump_cookie(struct pid *pid, u64 coredump_cookie)
+{
+ struct pidfs_exit_info *exit_info;
+ struct dentry *dentry = pid->stashed;
+ struct inode *inode;
+
+ if (WARN_ON_ONCE(!dentry))
+ return;
+
+ inode = d_inode(dentry);
+ exit_info = &pidfs_i(inode)->__pei;
+ smp_store_release(&exit_info->coredump_cookie, coredump_cookie);
+}
+
void pidfs_coredump(const struct coredump_params *cprm)
{
struct pid *pid = cprm->pid;
diff --git a/include/linux/net.h b/include/linux/net.h
index 0ff950eecc6b..005f1e52e7f1 100644
--- a/include/linux/net.h
+++ b/include/linux/net.h
@@ -81,6 +81,7 @@ enum sock_type {
#ifndef SOCK_NONBLOCK
#define SOCK_NONBLOCK O_NONBLOCK
#endif
+#define SOCK_COREDUMP O_TRUNC

#endif /* ARCH_HAS_SOCKET_TYPES */

diff --git a/include/linux/pidfs.h b/include/linux/pidfs.h
index f7729b9371bc..5875037be272 100644
--- a/include/linux/pidfs.h
+++ b/include/linux/pidfs.h
@@ -10,6 +10,7 @@ void pidfs_add_pid(struct pid *pid);
void pidfs_remove_pid(struct pid *pid);
void pidfs_exit(struct task_struct *tsk);
void pidfs_coredump(const struct coredump_params *cprm);
+void pidfs_coredump_cookie(struct pid *pid, u64 coredump_cookie);
extern const struct dentry_operations pidfs_dentry_operations;
int pidfs_register_pid(struct pid *pid);
void pidfs_get_pid(struct pid *pid);
diff --git a/include/uapi/linux/pidfd.h b/include/uapi/linux/pidfd.h
index 84ac709f560c..f46819a02d23 100644
--- a/include/uapi/linux/pidfd.h
+++ b/include/uapi/linux/pidfd.h
@@ -108,6 +108,7 @@ struct pidfd_info {
__s32 exit_code;
__u32 coredump_mask;
__u32 __spare1;
+ __u64 coredump_cookie;
};

#define PIDFS_IOCTL_MAGIC 0xFF
diff --git a/net/unix/af_unix.c b/net/unix/af_unix.c
index 148d008862e7..45e7a6363939 100644
--- a/net/unix/af_unix.c
+++ b/net/unix/af_unix.c
@@ -101,6 +101,7 @@
#include <linux/string.h>
#include <linux/uaccess.h>
#include <linux/pidfs.h>
+#include <linux/sock_diag.h>
#include <net/af_unix.h>
#include <net/net_namespace.h>
#include <net/scm.h>
@@ -771,6 +772,7 @@ static void unix_release_sock(struct sock *sk, int embrion)

struct unix_peercred {
struct pid *peer_pid;
+ u64 cookie;
const struct cred *peer_cred;
};

@@ -806,6 +808,8 @@ static void drop_peercred(struct unix_peercred *peercred)
static inline void init_peercred(struct sock *sk,
const struct unix_peercred *peercred)
{
+ if (peercred->cookie)
+ pidfs_coredump_cookie(peercred->peer_pid, peercred->cookie);
sk->sk_peer_pid = peercred->peer_pid;
sk->sk_peer_cred = peercred->peer_cred;
}
@@ -1717,6 +1721,9 @@ static int unix_stream_connect(struct socket *sock, struct sockaddr *uaddr,
unix_peer(newsk) = sk;
newsk->sk_state = TCP_ESTABLISHED;
newsk->sk_type = sk->sk_type;
+ /* Prepare a new socket cookie for the receiver. */
+ if (flags & SOCK_COREDUMP)
+ peercred.cookie = sock_gen_cookie(newsk);
init_peercred(newsk, &peercred);
newu = unix_sk(newsk);
newu->listener = other;

--
2.47.2