* [PATCH 00/10] net: support thread-specific pidfds for send and connect
@ 2026-08-31 11:21 Christian Brauner
2026-08-31 11:21 ` [PATCH 01/10] pid: add helpers to operate on a struct pid array Christian Brauner
` (9 more replies)
0 siblings, 10 replies; 25+ messages in thread
From: Christian Brauner @ 2026-08-31 11:21 UTC (permalink / raw)
To: Jakub Kicinski, Kuniyuki Iwashima, Oleg Nesterov
Cc: David S. Miller, Eric Dumazet, Paolo Abeni, Simon Horman,
Willem de Bruijn, netdev, linux-kernel, Alexander Viro, Jan Kara,
linux-fsdevel, Alexander Mikhalitsyn,
Christian Brauner (Amutable)
SO_PASSPIDFD/SCM_PIDFD and SO_PEERPIDFD allow to retrieve a pidfd for
the thread-group leader. The coredump server using the coredump socket
cannot get a handle on the task that took the signal and is writing the
coredump easily. Workloads interested in per-thread authentification
have similar problems.
Add SO_PASSPIDFD_THREAD and SO_PEERPIDFD_THREAD. We record the sending
and the connecting thread in addition to the thread-group leader.
SO_PASSPIDFD_THREAD functions like SO_PASSPIDFD but instead of an
SCM_PIDFD message for the thread-group leader, SCM_PIDFD sends a pidfd
for the specific thread. SO_PASSPIDFD_THREAD is mutually exclusive with
SO_PASSPIDFD. The last one set takes precedence and disables the other
one. Both SO_PASSCRED and SO_PASSPIDFD receivers see one writer per
process as before.
SO_PEERPIDFD_THREAD allows to retrieve a pidfd for the specific thread
that called connect(), listen(), or socketpair().
pidfs_coredump() now also stamps the dumping thread's struct pid so a
pidfd for that thread reports the coredump like the pidfd of the
thread-group leader does.
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
Christian Brauner (10):
pid: add helpers to operate on a struct pid array
af_unix: record the pid of the sending thread
net: add SO_PASSPIDFD_THREAD to get a thread-specific SCM_PIDFD
selftests/net: SO_PASSPIDFD_THREAD
net: turn sk_peer_pid into an array indexed by pid type
af_unix: record the pid of the connecting thread
net: add SO_PEERPIDFD_THREAD to get a thread-specific pidfd
selftests/net: SO_PEERPIDFD_THREAD
pidfs: record the coredump on the dumping thread's pid too
selftests/coredump: check the dumping thread's pidfd
arch/alpha/include/uapi/asm/socket.h | 4 +
arch/mips/include/uapi/asm/socket.h | 4 +
arch/parisc/include/uapi/asm/socket.h | 4 +
arch/sparc/include/uapi/asm/socket.h | 4 +
fs/coredump.c | 22 +-
fs/pidfs.c | 27 +-
include/linux/coredump.h | 4 +-
include/linux/pid.h | 53 +++
include/linux/pid_types.h | 8 +
include/linux/pidfs.h | 5 +
include/linux/sched/signal.h | 18 +
include/net/scm.h | 7 +-
include/net/sock.h | 14 +-
include/uapi/asm-generic/socket.h | 4 +
net/bluetooth/af_bluetooth.c | 6 +-
net/bluetooth/hci_sock.c | 8 +-
net/bluetooth/l2cap_sock.c | 2 +-
net/core/scm.c | 36 +-
net/core/sock.c | 120 ++++---
net/unix/af_unix.c | 67 ++--
net/unix/af_unix.h | 3 +-
.../selftests/coredump/coredump_socket_test.c | 175 ++++++++++
tools/testing/selftests/coredump/coredump_test.h | 2 +
.../selftests/coredump/coredump_test_helpers.c | 49 +++
tools/testing/selftests/net/af_unix/Makefile | 2 +
tools/testing/selftests/net/af_unix/scm_pidfd.c | 366 +++++++++++++++++++++
26 files changed, 905 insertions(+), 109 deletions(-)
---
base-commit: cee9395acd8043be0644b25c34bfa86623f2b935
change-id: 20260825-work-unix-passpidfd-086a1ec09e8a
^ permalink raw reply [flat|nested] 25+ messages in thread
* [PATCH 01/10] pid: add helpers to operate on a struct pid array
2026-08-31 11:21 [PATCH 00/10] net: support thread-specific pidfds for send and connect Christian Brauner
@ 2026-08-31 11:21 ` Christian Brauner
2026-09-07 10:07 ` Alexander Mikhalitsyn
2026-08-31 11:21 ` [PATCH 02/10] af_unix: record the pid of the sending thread Christian Brauner
` (8 subsequent siblings)
9 siblings, 1 reply; 25+ messages in thread
From: Christian Brauner @ 2026-08-31 11:21 UTC (permalink / raw)
To: Jakub Kicinski, Kuniyuki Iwashima, Oleg Nesterov
Cc: David S. Miller, Eric Dumazet, Paolo Abeni, Simon Horman,
Willem de Bruijn, netdev, linux-kernel, Alexander Viro, Jan Kara,
linux-fsdevel, Alexander Mikhalitsyn,
Christian Brauner (Amutable)
We're about to extend af_unix sockets and coredump code with the ability
to hand out thread-specific pidfds. Add a few simple helpers that allow
to operate on multiple struct pids up to PIDTYPE_MAX with automatic
bounds checking.
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
fs/pidfs.c | 16 +++++++++++++
include/linux/pid.h | 53 ++++++++++++++++++++++++++++++++++++++++++++
include/linux/pid_types.h | 8 +++++++
include/linux/pidfs.h | 5 +++++
include/linux/sched/signal.h | 18 +++++++++++++++
5 files changed, 100 insertions(+)
diff --git a/fs/pidfs.c b/fs/pidfs.c
index a6a643f15d08..586af2e5811c 100644
--- a/fs/pidfs.c
+++ b/fs/pidfs.c
@@ -1070,6 +1070,22 @@ int pidfs_register_pid_gfp(struct pid *pid, gfp_t gfp)
return 0;
}
+/* Register the pids up to pid type @last of @pids in pidfs. */
+int __pidfs_register_pids(struct pid *const *pids, enum pid_type last)
+{
+ if (WARN_ON_ONCE(last >= PIDTYPE_MAX))
+ return -EINVAL;
+
+ for (enum pid_type type = PIDTYPE_PID; type <= last; type++) {
+ int ret = pidfs_register_pid(pids[type]);
+
+ if (unlikely(ret))
+ return ret;
+ }
+
+ return 0;
+}
+
static struct dentry *pidfs_stash_dentry(struct dentry **stashed,
struct dentry *dentry)
{
diff --git a/include/linux/pid.h b/include/linux/pid.h
index ddaef0bbc8ba..87635d0cc1f7 100644
--- a/include/linux/pid.h
+++ b/include/linux/pid.h
@@ -2,6 +2,9 @@
#ifndef _LINUX_PID_H
#define _LINUX_PID_H
+#include <linux/array_size.h>
+#include <linux/build_bug.h>
+#include <linux/minmax.h>
#include <linux/pid_types.h>
#include <linux/rculist.h>
#include <linux/rcupdate.h>
@@ -92,6 +95,56 @@ static inline struct pid *get_pid(struct pid *pid)
}
extern void put_pid(struct pid *pid);
+
+/*
+ * Helpers for arrays of struct pid indexed by pid type declared with
+ * DECLARE_PIDS(). The array covers PIDTYPE_PID up to the pid type it
+ * was declared with and the helpers take that bound from the array.
+ */
+static inline void __get_pids(struct pid **dst, struct pid *const *src,
+ enum pid_type last)
+{
+ for (enum pid_type type = PIDTYPE_PID; type <= last; type++)
+ dst[type] = get_pid(src[type]);
+}
+
+static inline void __put_pids(struct pid **pids, enum pid_type last)
+{
+ for (enum pid_type type = PIDTYPE_PID; type <= last; type++) {
+ put_pid(pids[type]);
+ pids[type] = NULL;
+ }
+}
+
+static inline void __swap_pids(struct pid **a, struct pid **b,
+ enum pid_type last)
+{
+ for (enum pid_type type = PIDTYPE_PID; type <= last; type++)
+ swap(a[type], b[type]);
+}
+
+static inline bool __pids_equal(struct pid *const *a, struct pid *const *b,
+ enum pid_type last)
+{
+ for (enum pid_type type = PIDTYPE_PID; type <= last; type++)
+ if (a[type] != b[type])
+ return false;
+ return true;
+}
+
+/* The last pid type an array declared with DECLARE_PIDS() covers. */
+#define pids_last(pids) \
+ ((enum pid_type)(ARRAY_SIZE(pids) - 1 + \
+ BUILD_BUG_ON_ZERO(ARRAY_SIZE(pids) > PIDTYPE_MAX)))
+
+#define __pids_last2(a, b) \
+ (pids_last(a) + BUILD_BUG_ON_ZERO(ARRAY_SIZE(a) != ARRAY_SIZE(b)))
+
+#define get_pids(dst, src) __get_pids(dst, src, __pids_last2(dst, src))
+#define put_pids(pids) __put_pids(pids, pids_last(pids))
+#define swap_pids(a, b) __swap_pids(a, b, __pids_last2(a, b))
+#define pids_equal(a, b) __pids_equal(a, b, __pids_last2(a, b))
+
extern struct task_struct *pid_task(struct pid *pid, enum pid_type);
static inline bool pid_has_task(struct pid *pid, enum pid_type type)
{
diff --git a/include/linux/pid_types.h b/include/linux/pid_types.h
index c2aee1d91dcf..3302690a2a28 100644
--- a/include/linux/pid_types.h
+++ b/include/linux/pid_types.h
@@ -2,6 +2,8 @@
#ifndef _LINUX_PID_TYPES_H
#define _LINUX_PID_TYPES_H
+#include <linux/build_bug.h>
+
enum pid_type {
PIDTYPE_PID,
PIDTYPE_TGID,
@@ -10,6 +12,12 @@ enum pid_type {
PIDTYPE_MAX,
};
+struct pid;
+
+/* An array of struct pid indexed by pid type, PIDTYPE_PID up to @last. */
+#define DECLARE_PIDS(name, last) \
+ struct pid *name[(last) + 1 + BUILD_BUG_ON_ZERO((last) >= PIDTYPE_MAX)]
+
struct pid_namespace;
extern struct pid_namespace init_pid_ns;
diff --git a/include/linux/pidfs.h b/include/linux/pidfs.h
index 0abf7da9ab23..3c1e82f1a369 100644
--- a/include/linux/pidfs.h
+++ b/include/linux/pidfs.h
@@ -3,6 +3,7 @@
#define _LINUX_PID_FS_H
#include <linux/gfp_types.h>
+#include <linux/pid.h>
struct coredump_params;
@@ -32,5 +33,9 @@ static inline int pidfs_register_pid(struct pid *pid)
}
void pidfs_free_pid(struct pid *pid);
+int __pidfs_register_pids(struct pid *const *pids, enum pid_type last);
+
+/* Register the pids of an array declared with DECLARE_PIDS(). */
+#define pidfs_register_pids(pids) __pidfs_register_pids(pids, pids_last(pids))
#endif /* _LINUX_PID_FS_H */
diff --git a/include/linux/sched/signal.h b/include/linux/sched/signal.h
index 584ae88b435e..9444b47789a0 100644
--- a/include/linux/sched/signal.h
+++ b/include/linux/sched/signal.h
@@ -677,6 +677,24 @@ struct pid *task_pid_type(struct task_struct *task, enum pid_type type)
return pid;
}
+/* Fill @pids with the pid types of @task up to @last, without references. */
+static inline void __task_pids(struct pid **pids, enum pid_type last,
+ struct task_struct *task)
+{
+ for (enum pid_type type = PIDTYPE_PID; type <= last; type++)
+ pids[type] = task_pid_type(task, type);
+}
+
+static inline void __get_task_pids(struct pid **pids, enum pid_type last,
+ struct task_struct *task)
+{
+ for (enum pid_type type = PIDTYPE_PID; type <= last; type++)
+ pids[type] = get_pid(task_pid_type(task, type));
+}
+
+#define task_pids(pids, task) __task_pids(pids, pids_last(pids), task)
+#define get_task_pids(pids, task) __get_task_pids(pids, pids_last(pids), task)
+
static inline struct pid *task_tgid(struct task_struct *task)
{
return task->signal->pids[PIDTYPE_TGID];
--
2.53.0
^ permalink raw reply related [flat|nested] 25+ messages in thread
* [PATCH 02/10] af_unix: record the pid of the sending thread
2026-08-31 11:21 [PATCH 00/10] net: support thread-specific pidfds for send and connect Christian Brauner
2026-08-31 11:21 ` [PATCH 01/10] pid: add helpers to operate on a struct pid array Christian Brauner
@ 2026-08-31 11:21 ` Christian Brauner
2026-09-07 9:55 ` Alexander Mikhalitsyn
2026-08-31 11:21 ` [PATCH 03/10] net: add SO_PASSPIDFD_THREAD to get a thread-specific SCM_PIDFD Christian Brauner
` (7 subsequent siblings)
9 siblings, 1 reply; 25+ messages in thread
From: Christian Brauner @ 2026-08-31 11:21 UTC (permalink / raw)
To: Jakub Kicinski, Kuniyuki Iwashima, Oleg Nesterov
Cc: David S. Miller, Eric Dumazet, Paolo Abeni, Simon Horman,
Willem de Bruijn, netdev, linux-kernel, Alexander Viro, Jan Kara,
linux-fsdevel, Alexander Mikhalitsyn,
Christian Brauner (Amutable)
Currently only the struct pid of the thread-group leader is recorded.
The identity of the actual thread that sent the message or is connected
to a given socket cannot be retrieved.
Add the plumbing to make it possible to retrieve a pidfd for the sender.
Nothing uses the thread-specific struct pid yet. No functional changes.
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
include/net/scm.h | 7 +++----
net/core/scm.c | 21 +++++++++++++++++----
net/unix/af_unix.c | 23 +++++++++++++----------
net/unix/af_unix.h | 3 ++-
4 files changed, 35 insertions(+), 19 deletions(-)
diff --git a/include/net/scm.h b/include/net/scm.h
index 86ae6bc109ec..aa7d15c5fc27 100644
--- a/include/net/scm.h
+++ b/include/net/scm.h
@@ -42,7 +42,7 @@ struct scm_fp_list {
};
struct scm_cookie {
- struct pid *pid; /* Skb credentials */
+ DECLARE_PIDS(pid, PIDTYPE_TGID); /* Skb credentials by pid type */
struct scm_fp_list *fp; /* Passed files */
struct scm_creds creds; /* Skb credentials */
#ifdef CONFIG_SECURITY_NETWORK
@@ -69,7 +69,7 @@ static __inline__ void unix_get_peersec_dgram(struct socket *sock, struct scm_co
static __inline__ void scm_set_cred(struct scm_cookie *scm,
struct pid *pid, kuid_t uid, kgid_t gid)
{
- scm->pid = get_pid(pid);
+ scm->pid[PIDTYPE_TGID] = get_pid(pid);
scm->creds.pid = pid_vnr(pid);
scm->creds.uid = uid;
scm->creds.gid = gid;
@@ -77,8 +77,7 @@ static __inline__ void scm_set_cred(struct scm_cookie *scm,
static __inline__ void scm_destroy_cred(struct scm_cookie *scm)
{
- put_pid(scm->pid);
- scm->pid = NULL;
+ put_pids(scm->pid);
}
static __inline__ void scm_destroy(struct scm_cookie *scm)
diff --git a/net/core/scm.c b/net/core/scm.c
index f0d44ecdb11f..9b9e119c353a 100644
--- a/net/core/scm.c
+++ b/net/core/scm.c
@@ -149,6 +149,7 @@ EXPORT_SYMBOL(__scm_destroy);
static inline int scm_replace_pid(struct scm_cookie *scm, struct pid *pid)
{
+ struct pid *thread_pid;
int err;
/* drop all previous references */
@@ -158,7 +159,18 @@ static inline int scm_replace_pid(struct scm_cookie *scm, struct pid *pid)
if (unlikely(err))
return err;
- scm->pid = pid;
+ /* A sender naming its own thread-group sends from the current thread. */
+ if (pid == task_tgid(current))
+ thread_pid = task_pid(current);
+ else
+ thread_pid = pid;
+
+ err = pidfs_register_pid(thread_pid);
+ if (unlikely(err))
+ return err;
+
+ scm->pid[PIDTYPE_TGID] = pid;
+ scm->pid[PIDTYPE_PID] = get_pid(thread_pid);
scm->creds.pid = pid_vnr(pid);
return 0;
}
@@ -207,7 +219,8 @@ int __scm_send(struct socket *sock, struct msghdr *msg, struct scm_cookie *p)
if (err)
goto error;
- if (!p->pid || pid_vnr(p->pid) != creds.pid) {
+ if (!p->pid[PIDTYPE_TGID] ||
+ pid_vnr(p->pid[PIDTYPE_TGID]) != creds.pid) {
struct pid *pid;
err = -ESRCH;
pid = find_get_pid(creds.pid);
@@ -504,10 +517,10 @@ static void scm_pidfd_recv(struct msghdr *msg, struct scm_cookie *scm)
return;
}
- if (!scm->pid)
+ if (!scm->pid[PIDTYPE_TGID])
return;
- pidfd = pidfd_prepare(scm->pid, PIDFD_STALE, &pidfd_file);
+ pidfd = pidfd_prepare(scm->pid[PIDTYPE_TGID], PIDFD_STALE, &pidfd_file);
if (put_cmsg(msg, SOL_SOCKET, SCM_PIDFD, sizeof(int), &pidfd)) {
if (pidfd_file) {
diff --git a/net/unix/af_unix.c b/net/unix/af_unix.c
index 13f9926bf205..011af84e3626 100644
--- a/net/unix/af_unix.c
+++ b/net/unix/af_unix.c
@@ -1973,7 +1973,7 @@ static void unix_destruct_scm(struct sk_buff *skb)
{
struct scm_cookie scm = {};
- swap(scm.pid, UNIXCB(skb).pid);
+ swap_pids(scm.pid, UNIXCB(skb).pid);
if (UNIXCB(skb).fp)
unix_detach_fds(&scm, skb);
@@ -1991,7 +1991,7 @@ static int unix_scm_to_skb(struct scm_cookie *scm, struct sk_buff *skb, bool sen
{
int err = 0;
- UNIXCB(skb).pid = get_pid(scm->pid);
+ get_pids(UNIXCB(skb).pid, scm->pid);
UNIXCB(skb).uid = scm->creds.uid;
UNIXCB(skb).gid = scm->creds.gid;
UNIXCB(skb).fp = NULL;
@@ -2005,7 +2005,10 @@ static int unix_scm_to_skb(struct scm_cookie *scm, struct sk_buff *skb, bool sen
static void unix_skb_to_scm(struct sk_buff *skb, struct scm_cookie *scm)
{
- scm_set_cred(scm, UNIXCB(skb).pid, UNIXCB(skb).uid, UNIXCB(skb).gid);
+ get_pids(scm->pid, UNIXCB(skb).pid);
+ scm->creds.pid = pid_vnr(scm->pid[PIDTYPE_TGID]);
+ scm->creds.uid = UNIXCB(skb).uid;
+ scm->creds.gid = UNIXCB(skb).gid;
unix_set_secdata(scm, skb);
}
@@ -2025,20 +2028,20 @@ static void unix_skb_to_scm(struct sk_buff *skb, struct scm_cookie *scm)
static int unix_maybe_add_creds(struct sk_buff *skb, const struct sock *sk,
const struct sock *other)
{
- if (UNIXCB(skb).pid)
+ if (UNIXCB(skb).pid[PIDTYPE_TGID])
return 0;
if (unix_may_passcred(sk) || unix_may_passcred(other) ||
!other->sk_socket) {
- struct pid *pid;
int err;
- pid = task_tgid(current);
- err = pidfs_register_pid(pid);
- if (unlikely(err))
+ get_task_pids(UNIXCB(skb).pid, current);
+ err = pidfs_register_pids(UNIXCB(skb).pid);
+ if (unlikely(err)) {
+ put_pids(UNIXCB(skb).pid);
return err;
+ }
- UNIXCB(skb).pid = get_pid(pid);
current_uid_gid(&UNIXCB(skb).uid, &UNIXCB(skb).gid);
}
@@ -2048,7 +2051,7 @@ static int unix_maybe_add_creds(struct sk_buff *skb, const struct sock *sk,
static bool unix_skb_scm_eq(struct sk_buff *skb,
struct scm_cookie *scm)
{
- return UNIXCB(skb).pid == scm->pid &&
+ return UNIXCB(skb).pid[PIDTYPE_TGID] == scm->pid[PIDTYPE_TGID] &&
uid_eq(UNIXCB(skb).uid, scm->creds.uid) &&
gid_eq(UNIXCB(skb).gid, scm->creds.gid) &&
unix_secdata_eq(scm, skb);
diff --git a/net/unix/af_unix.h b/net/unix/af_unix.h
index 8119dbeef3a3..402742895acc 100644
--- a/net/unix/af_unix.h
+++ b/net/unix/af_unix.h
@@ -2,6 +2,7 @@
#ifndef __AF_UNIX_H
#define __AF_UNIX_H
+#include <linux/pid_types.h>
#include <linux/uidgid.h>
#define UNIX_HASH_MOD (256 - 1)
@@ -11,7 +12,7 @@
struct sock *unix_peer_get(struct sock *sk);
struct unix_skb_parms {
- struct pid *pid; /* skb credentials */
+ DECLARE_PIDS(pid, PIDTYPE_TGID); /* skb credentials by pid type */
kuid_t uid;
kgid_t gid;
struct scm_fp_list *fp; /* Passed files */
--
2.53.0
^ permalink raw reply related [flat|nested] 25+ messages in thread
* [PATCH 03/10] net: add SO_PASSPIDFD_THREAD to get a thread-specific SCM_PIDFD
2026-08-31 11:21 [PATCH 00/10] net: support thread-specific pidfds for send and connect Christian Brauner
2026-08-31 11:21 ` [PATCH 01/10] pid: add helpers to operate on a struct pid array Christian Brauner
2026-08-31 11:21 ` [PATCH 02/10] af_unix: record the pid of the sending thread Christian Brauner
@ 2026-08-31 11:21 ` Christian Brauner
2026-09-07 10:02 ` Alexander Mikhalitsyn
2026-08-31 11:21 ` [PATCH 04/10] selftests/net: SO_PASSPIDFD_THREAD Christian Brauner
` (6 subsequent siblings)
9 siblings, 1 reply; 25+ messages in thread
From: Christian Brauner @ 2026-08-31 11:21 UTC (permalink / raw)
To: Jakub Kicinski, Kuniyuki Iwashima, Oleg Nesterov
Cc: David S. Miller, Eric Dumazet, Paolo Abeni, Simon Horman,
Willem de Bruijn, netdev, linux-kernel, Alexander Viro, Jan Kara,
linux-fsdevel, Alexander Mikhalitsyn,
Christian Brauner (Amutable)
Currently, SCM_PIDFD carries a pidfd for the thread-group leader. A
broker or the coredump server cannot learn the identity of the specific
thread that sent a given message. Now that both struct pids are recorded
a receiver can ask for the specific identity it needs.
So add SO_PASSPIDFD_THREAD as a sibling of SO_PASSPIDFD. Either option
makes recvmsg() deliver an SCM_PIDFD. SO_PASSPIDFD sends a pidfd for the
thread-group leader and SO_PASSPIDFD_THREAD sends a pidfd for the
specific thread.
The two options are mutually exclusive. Enabling one switches the other
off, so getsockopt() always reports which of the two is active.
On SOCK_STREAM sockets recvmsg() only stops merging data at a thread
boundary when the receiver asked for a thread pidfd. For SO_PASSCRED and
SO_PASSPIDFD receivers all threads of one process remain a single
writer.
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
arch/alpha/include/uapi/asm/socket.h | 2 ++
arch/mips/include/uapi/asm/socket.h | 2 ++
arch/parisc/include/uapi/asm/socket.h | 2 ++
arch/sparc/include/uapi/asm/socket.h | 2 ++
include/net/sock.h | 10 +++++++++-
include/uapi/asm-generic/socket.h | 2 ++
net/core/scm.c | 19 +++++++++++++------
net/core/sock.c | 26 ++++++++++++++++++++++++--
net/unix/af_unix.c | 11 ++++++++---
9 files changed, 64 insertions(+), 12 deletions(-)
diff --git a/arch/alpha/include/uapi/asm/socket.h b/arch/alpha/include/uapi/asm/socket.h
index 946a5fad2691..bb3d534826bb 100644
--- a/arch/alpha/include/uapi/asm/socket.h
+++ b/arch/alpha/include/uapi/asm/socket.h
@@ -157,6 +157,8 @@
#define SO_RIGHTS_NOTRUNC 85
+#define SO_PASSPIDFD_THREAD 86
+
#if !defined(__KERNEL__)
#if __BITS_PER_LONG == 64
diff --git a/arch/mips/include/uapi/asm/socket.h b/arch/mips/include/uapi/asm/socket.h
index f1641dde135f..269badcaa086 100644
--- a/arch/mips/include/uapi/asm/socket.h
+++ b/arch/mips/include/uapi/asm/socket.h
@@ -168,6 +168,8 @@
#define SO_RIGHTS_NOTRUNC 85
+#define SO_PASSPIDFD_THREAD 86
+
#if !defined(__KERNEL__)
#if __BITS_PER_LONG == 64
diff --git a/arch/parisc/include/uapi/asm/socket.h b/arch/parisc/include/uapi/asm/socket.h
index f3a3815c7dc2..313aee10a52c 100644
--- a/arch/parisc/include/uapi/asm/socket.h
+++ b/arch/parisc/include/uapi/asm/socket.h
@@ -149,6 +149,8 @@
#define SO_RIGHTS_NOTRUNC 0x4053
+#define SO_PASSPIDFD_THREAD 0x4054
+
#if !defined(__KERNEL__)
#if __BITS_PER_LONG == 64
diff --git a/arch/sparc/include/uapi/asm/socket.h b/arch/sparc/include/uapi/asm/socket.h
index 7907f3b1f0ee..bd3e69bcce7a 100644
--- a/arch/sparc/include/uapi/asm/socket.h
+++ b/arch/sparc/include/uapi/asm/socket.h
@@ -150,6 +150,8 @@
#define SO_RIGHTS_NOTRUNC 0x005e
+#define SO_PASSPIDFD_THREAD 0x005f
+
#if !defined(__KERNEL__)
diff --git a/include/net/sock.h b/include/net/sock.h
index 51185222aac2..fc09c92e8a83 100644
--- a/include/net/sock.h
+++ b/include/net/sock.h
@@ -356,6 +356,7 @@ struct sk_filter;
* @sk_scm_security: flagged by SO_PASSSEC to recv SCM_SECURITY
* @sk_scm_pidfd: flagged by SO_PASSPIDFD to recv SCM_PIDFD
* @sk_scm_rights: flagged by SO_PASSRIGHTS to recv SCM_RIGHTS
+ * @sk_scm_pidfd_thread: flagged by SO_PASSPIDFD_THREAD to recv a thread SCM_PIDFD
* @sk_scm_unused: unused flags for scm_recv()
* @ns_tracker: tracker for netns reference
* @sk_user_frags: xarray of pages the user is holding a reference on.
@@ -562,7 +563,8 @@ struct sock {
sk_scm_security : 1,
sk_scm_pidfd : 1,
sk_scm_rights : 1,
- sk_scm_unused : 4;
+ sk_scm_pidfd_thread : 1,
+ sk_scm_unused : 3;
};
};
u8 sk_clockid;
@@ -2986,6 +2988,12 @@ static inline bool sk_is_stream_unix(const struct sock *sk)
return sk_is_unix(sk) && sk->sk_type == SOCK_STREAM;
}
+/* SO_PASSPIDFD or SO_PASSPIDFD_THREAD asked for an SCM_PIDFD. */
+static inline bool sk_scm_pidfd_wanted(const struct sock *sk)
+{
+ return sk->sk_scm_pidfd || sk->sk_scm_pidfd_thread;
+}
+
static inline bool sk_is_vsock(const struct sock *sk)
{
return sk->sk_family == AF_VSOCK;
diff --git a/include/uapi/asm-generic/socket.h b/include/uapi/asm-generic/socket.h
index 84ea7b92936e..d1e5c6de146d 100644
--- a/include/uapi/asm-generic/socket.h
+++ b/include/uapi/asm-generic/socket.h
@@ -152,6 +152,8 @@
#define SO_RIGHTS_NOTRUNC 85
+#define SO_PASSPIDFD_THREAD 86
+
#if !defined(__KERNEL__)
#if __BITS_PER_LONG == 64 || (defined(__x86_64__) && defined(__ILP32__))
diff --git a/net/core/scm.c b/net/core/scm.c
index 9b9e119c353a..d69768414af4 100644
--- a/net/core/scm.c
+++ b/net/core/scm.c
@@ -499,9 +499,13 @@ static bool scm_has_secdata(struct sock *sk)
}
#endif
-static void scm_pidfd_recv(struct msghdr *msg, struct scm_cookie *scm)
+static void scm_pidfd_recv(struct sock *sk, struct msghdr *msg,
+ struct scm_cookie *scm)
{
+ enum pid_type type = sk->sk_scm_pidfd_thread ? PIDTYPE_PID : PIDTYPE_TGID;
+ struct pid *pid = scm->pid[type];
struct file *pidfd_file = NULL;
+ unsigned int flags = PIDFD_STALE;
int len, pidfd;
/* put_cmsg() doesn't return an error if CMSG is truncated,
@@ -517,10 +521,13 @@ static void scm_pidfd_recv(struct msghdr *msg, struct scm_cookie *scm)
return;
}
- if (!scm->pid[PIDTYPE_TGID])
+ if (!pid)
return;
- pidfd = pidfd_prepare(scm->pid[PIDTYPE_TGID], PIDFD_STALE, &pidfd_file);
+ if (type == PIDTYPE_PID)
+ flags |= PIDFD_THREAD;
+
+ pidfd = pidfd_prepare(pid, flags, &pidfd_file);
if (put_cmsg(msg, SOL_SOCKET, SCM_PIDFD, sizeof(int), &pidfd)) {
if (pidfd_file) {
@@ -539,7 +546,7 @@ static bool __scm_recv_common(struct sock *sk, struct msghdr *msg,
struct scm_cookie *scm, int flags)
{
if (!msg->msg_control) {
- if (sk->sk_scm_credentials || sk->sk_scm_pidfd ||
+ if (sk->sk_scm_credentials || sk_scm_pidfd_wanted(sk) ||
scm->fp || scm_has_secdata(sk))
msg->msg_flags |= MSG_CTRUNC;
@@ -586,8 +593,8 @@ void scm_recv_unix(struct socket *sock, struct msghdr *msg,
scm_detach_fds(msg, scm, READ_ONCE(u->scm_rights_notrunc));
}
- if (sock->sk->sk_scm_pidfd)
- scm_pidfd_recv(msg, scm);
+ if (sk_scm_pidfd_wanted(sock->sk))
+ scm_pidfd_recv(sock->sk, msg, scm);
scm_destroy_cred(scm);
}
diff --git a/net/core/sock.c b/net/core/sock.c
index 1ad41904db25..f9615b0de10e 100644
--- a/net/core/sock.c
+++ b/net/core/sock.c
@@ -1571,10 +1571,25 @@ int sk_setsockopt(struct sock *sk, int level, int optname,
break;
case SO_PASSPIDFD:
- if (sk_is_unix(sk))
+ if (sk_is_unix(sk)) {
+ /* Mutually exclusive with SO_PASSPIDFD_THREAD. */
sk->sk_scm_pidfd = valbool;
- else
+ if (valbool)
+ sk->sk_scm_pidfd_thread = 0;
+ } else {
+ ret = -EOPNOTSUPP;
+ }
+ break;
+
+ case SO_PASSPIDFD_THREAD:
+ if (sk_is_unix(sk)) {
+ /* Mutually exclusive with SO_PASSPIDFD. */
+ sk->sk_scm_pidfd_thread = valbool;
+ if (valbool)
+ sk->sk_scm_pidfd = 0;
+ } else {
ret = -EOPNOTSUPP;
+ }
break;
case SO_PASSRIGHTS:
@@ -1892,6 +1907,13 @@ int sk_getsockopt(struct sock *sk, int level, int optname,
v.val = sk->sk_scm_pidfd;
break;
+ case SO_PASSPIDFD_THREAD:
+ if (!sk_is_unix(sk))
+ return -EOPNOTSUPP;
+
+ v.val = sk->sk_scm_pidfd_thread;
+ break;
+
case SO_PASSRIGHTS:
if (!sk_is_unix(sk))
return -EOPNOTSUPP;
diff --git a/net/unix/af_unix.c b/net/unix/af_unix.c
index 011af84e3626..468a9c479b87 100644
--- a/net/unix/af_unix.c
+++ b/net/unix/af_unix.c
@@ -803,7 +803,7 @@ static void copy_peercred(struct sock *sk, struct sock *peersk)
static bool unix_may_passcred(const struct sock *sk)
{
- return sk->sk_scm_credentials || sk->sk_scm_pidfd;
+ return sk->sk_scm_credentials || sk_scm_pidfd_wanted(sk);
}
static int unix_listen(struct socket *sock, int backlog)
@@ -2048,9 +2048,14 @@ static int unix_maybe_add_creds(struct sk_buff *skb, const struct sock *sk,
return 0;
}
-static bool unix_skb_scm_eq(struct sk_buff *skb,
+static bool unix_skb_scm_eq(const struct sock *sk, struct sk_buff *skb,
struct scm_cookie *scm)
{
+ /* Only a thread pidfd receiver can tell threads of one process apart. */
+ if (sk->sk_scm_pidfd_thread &&
+ UNIXCB(skb).pid[PIDTYPE_PID] != scm->pid[PIDTYPE_PID])
+ return false;
+
return UNIXCB(skb).pid[PIDTYPE_TGID] == scm->pid[PIDTYPE_TGID] &&
uid_eq(UNIXCB(skb).uid, scm->creds.uid) &&
gid_eq(UNIXCB(skb).gid, scm->creds.gid) &&
@@ -3029,7 +3034,7 @@ static int unix_stream_read_generic(struct unix_stream_read_state *state,
if (check_creds) {
/* Never glue messages from different writers */
- if (!unix_skb_scm_eq(skb, &scm))
+ if (!unix_skb_scm_eq(sk, skb, &scm))
break;
} else if (unix_may_passcred(sk)) {
/* Copy credentials */
--
2.53.0
^ permalink raw reply related [flat|nested] 25+ messages in thread
* [PATCH 04/10] selftests/net: SO_PASSPIDFD_THREAD
2026-08-31 11:21 [PATCH 00/10] net: support thread-specific pidfds for send and connect Christian Brauner
` (2 preceding siblings ...)
2026-08-31 11:21 ` [PATCH 03/10] net: add SO_PASSPIDFD_THREAD to get a thread-specific SCM_PIDFD Christian Brauner
@ 2026-08-31 11:21 ` Christian Brauner
2026-09-07 10:04 ` Alexander Mikhalitsyn
2026-08-31 11:21 ` [PATCH 05/10] net: turn sk_peer_pid into an array indexed by pid type Christian Brauner
` (5 subsequent siblings)
9 siblings, 1 reply; 25+ messages in thread
From: Christian Brauner @ 2026-08-31 11:21 UTC (permalink / raw)
To: Jakub Kicinski, Kuniyuki Iwashima, Oleg Nesterov
Cc: David S. Miller, Eric Dumazet, Paolo Abeni, Simon Horman,
Willem de Bruijn, netdev, linux-kernel, Alexander Viro, Jan Kara,
linux-fsdevel, Alexander Mikhalitsyn,
Christian Brauner (Amutable)
Extend the af_unix scm_pidfd selftest with SO_PASSPIDFD_THREAD tests.
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
tools/testing/selftests/net/af_unix/Makefile | 2 +
tools/testing/selftests/net/af_unix/scm_pidfd.c | 269 ++++++++++++++++++++++++
2 files changed, 271 insertions(+)
diff --git a/tools/testing/selftests/net/af_unix/Makefile b/tools/testing/selftests/net/af_unix/Makefile
index a66f10fb0c23..45b841758f1b 100644
--- a/tools/testing/selftests/net/af_unix/Makefile
+++ b/tools/testing/selftests/net/af_unix/Makefile
@@ -23,6 +23,8 @@ TEST_GEN_FILES := scm_rights_denial_lsm.bpf.o
include ../../lib.mk
include ../bpf.mk
+$(OUTPUT)/scm_pidfd: CFLAGS += -pthread
+
$(OUTPUT)/scm_rights_denial_lsm: $(BPFOBJ)
$(OUTPUT)/scm_rights_denial_lsm: CFLAGS += -I$(SCRATCH_DIR)/include
$(OUTPUT)/scm_rights_denial_lsm: LDLIBS += -lelf -lz
diff --git a/tools/testing/selftests/net/af_unix/scm_pidfd.c b/tools/testing/selftests/net/af_unix/scm_pidfd.c
index 2c18b92a2603..019c48e1cdcd 100644
--- a/tools/testing/selftests/net/af_unix/scm_pidfd.c
+++ b/tools/testing/selftests/net/af_unix/scm_pidfd.c
@@ -10,6 +10,7 @@
#include <unistd.h>
#include <string.h>
#include <errno.h>
+#include <pthread.h>
#include <sys/un.h>
#include <sys/signal.h>
#include <sys/types.h>
@@ -27,6 +28,10 @@
#define SCM_PIDFD 0x04
#endif
+#ifndef SO_PASSPIDFD_THREAD
+#define SO_PASSPIDFD_THREAD 86
+#endif
+
#define CHILD_EXIT_CODE_OK 123
static void child_die()
@@ -553,4 +558,268 @@ TEST_F(scm_pidfd, test)
close(pfd);
}
+struct thread_ids {
+ pid_t pid;
+ pid_t tid;
+};
+
+static void *send_ids_thread(void *arg)
+{
+ int fd = *(int *)arg;
+ struct thread_ids ids = {
+ .pid = getpid(),
+ .tid = gettid(),
+ };
+ char sync;
+
+ if (send(fd, &ids, sizeof(ids), 0) != sizeof(ids))
+ return (void *)1;
+
+ /* stay alive until the receiver has looked at our pidfd */
+ if (read(fd, &sync, 1) != 1)
+ return (void *)1;
+
+ return NULL;
+}
+
+static void *send_ids_creds_thread(void *arg)
+{
+ int fd = *(int *)arg;
+ struct thread_ids ids = {
+ .pid = getpid(),
+ .tid = gettid(),
+ };
+ struct ucred ucred = {
+ .pid = getpid(),
+ .uid = getuid(),
+ .gid = getgid(),
+ };
+ char control[CMSG_SPACE(sizeof(ucred))] = { 0 };
+ struct iovec iov;
+ struct msghdr msg = { 0 };
+ struct cmsghdr *cmsg;
+ char sync;
+
+ iov.iov_base = &ids;
+ iov.iov_len = sizeof(ids);
+
+ msg.msg_iov = &iov;
+ msg.msg_iovlen = 1;
+ msg.msg_control = control;
+ msg.msg_controllen = sizeof(control);
+
+ cmsg = CMSG_FIRSTHDR(&msg);
+ cmsg->cmsg_level = SOL_SOCKET;
+ cmsg->cmsg_type = SCM_CREDENTIALS;
+ cmsg->cmsg_len = CMSG_LEN(sizeof(ucred));
+ memcpy(CMSG_DATA(cmsg), &ucred, sizeof(ucred));
+
+ if (sendmsg(fd, &msg, 0) != sizeof(ids))
+ return (void *)1;
+
+ if (read(fd, &sync, 1) != 1)
+ return (void *)1;
+
+ return NULL;
+}
+
+static void thread_client(int fd, int syncfd, void *(*sender)(void *))
+{
+ pthread_t thread;
+ void *ret;
+ char sync;
+
+ /* wait until the receiver enabled SO_PASSPIDFD */
+ if (read(syncfd, &sync, 1) != 1)
+ child_die();
+
+ if (pthread_create(&thread, NULL, sender, &fd))
+ child_die();
+
+ if (pthread_join(thread, &ret) || ret)
+ child_die();
+
+ exit(0);
+}
+
+static int recv_pidfd(int fd, struct thread_ids *ids)
+{
+ char control[CMSG_SPACE(sizeof(int))] = { 0 };
+ struct iovec iov;
+ struct msghdr msg = { 0 };
+ struct cmsghdr *cmsg;
+ int pidfd = -1;
+
+ iov.iov_base = ids;
+ iov.iov_len = sizeof(*ids);
+
+ msg.msg_iov = &iov;
+ msg.msg_iovlen = 1;
+ msg.msg_control = control;
+ msg.msg_controllen = sizeof(control);
+
+ if (recvmsg(fd, &msg, 0) != sizeof(*ids)) {
+ log_err("recvmsg");
+ return -1;
+ }
+
+ if (msg.msg_flags & (MSG_TRUNC | MSG_CTRUNC)) {
+ log_err("recvmsg: truncated");
+ return -1;
+ }
+
+ for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL;
+ cmsg = CMSG_NXTHDR(&msg, cmsg)) {
+ if (cmsg->cmsg_level == SOL_SOCKET &&
+ cmsg->cmsg_type == SCM_PIDFD)
+ memcpy(&pidfd, CMSG_DATA(cmsg), sizeof(pidfd));
+ }
+
+ return pidfd;
+}
+
+static int thread_pidfd_flow(void *(*sender)(void *), int optname,
+ struct thread_ids *ids, struct pidfd_info *info)
+{
+ int sk[2];
+ int syncpipe[2];
+ int pidfd;
+ int child_status = 0;
+ pid_t child;
+
+ if (socketpair(AF_UNIX, SOCK_STREAM, 0, sk))
+ return -1;
+
+ if (pipe(syncpipe))
+ return -1;
+
+ child = fork();
+ if (child < 0)
+ return -1;
+
+ if (child == 0) {
+ close(sk[0]);
+ close(syncpipe[1]);
+ thread_client(sk[1], syncpipe[0], sender);
+ }
+ close(sk[1]);
+ close(syncpipe[0]);
+
+ int on = 1;
+
+ if (setsockopt(sk[0], SOL_SOCKET, optname, &on, sizeof(on))) {
+ log_err("Failed to set pidfd passing option");
+ return -1;
+ }
+
+ if (write(syncpipe[1], "1", 1) != 1)
+ return -1;
+ close(syncpipe[1]);
+
+ pidfd = recv_pidfd(sk[0], ids);
+ if (pidfd < 0)
+ return -1;
+
+ info->mask = PIDFD_INFO_PID;
+ if (ioctl(pidfd, PIDFD_GET_INFO, info)) {
+ log_err("ioctl(PIDFD_GET_INFO)");
+ close(pidfd);
+ return -1;
+ }
+ close(pidfd);
+
+ /* release the sending thread */
+ if (write(sk[0], "x", 1) != 1)
+ return -1;
+ close(sk[0]);
+
+ waitpid(child, &child_status, 0);
+ if (!WIFEXITED(child_status) || WEXITSTATUS(child_status))
+ return -1;
+
+ return 0;
+}
+
+static int sockopt_set(int fd, int optname, int val)
+{
+ return setsockopt(fd, SOL_SOCKET, optname, &val, sizeof(val));
+}
+
+static int sockopt_get(int fd, int optname)
+{
+ socklen_t len = sizeof(int);
+ int val = -1;
+
+ if (getsockopt(fd, SOL_SOCKET, optname, &val, &len))
+ return -1;
+
+ return val;
+}
+
+TEST(scm_pidfd_setsockopt_values)
+{
+ int sk[2];
+
+ ASSERT_EQ(0, socketpair(AF_UNIX, SOCK_STREAM, 0, sk));
+
+ ASSERT_EQ(0, sockopt_set(sk[0], SO_PASSPIDFD_THREAD, 1));
+ ASSERT_EQ(1, sockopt_get(sk[0], SO_PASSPIDFD_THREAD));
+ ASSERT_EQ(0, sockopt_get(sk[0], SO_PASSPIDFD));
+
+ /* The option set last wins. */
+ ASSERT_EQ(0, sockopt_set(sk[0], SO_PASSPIDFD, 1));
+ ASSERT_EQ(1, sockopt_get(sk[0], SO_PASSPIDFD));
+ ASSERT_EQ(0, sockopt_get(sk[0], SO_PASSPIDFD_THREAD));
+
+ ASSERT_EQ(0, sockopt_set(sk[0], SO_PASSPIDFD_THREAD, 1));
+ ASSERT_EQ(1, sockopt_get(sk[0], SO_PASSPIDFD_THREAD));
+ ASSERT_EQ(0, sockopt_get(sk[0], SO_PASSPIDFD));
+
+ /* Disabling one option leaves the other alone. */
+ ASSERT_EQ(0, sockopt_set(sk[0], SO_PASSPIDFD, 0));
+ ASSERT_EQ(1, sockopt_get(sk[0], SO_PASSPIDFD_THREAD));
+ ASSERT_EQ(0, sockopt_set(sk[0], SO_PASSPIDFD_THREAD, 0));
+ ASSERT_EQ(0, sockopt_get(sk[0], SO_PASSPIDFD));
+ ASSERT_EQ(0, sockopt_get(sk[0], SO_PASSPIDFD_THREAD));
+
+ close(sk[0]);
+ close(sk[1]);
+}
+
+TEST(scm_pidfd_thread)
+{
+ struct thread_ids ids;
+ struct pidfd_info info;
+
+ ASSERT_EQ(0, thread_pidfd_flow(send_ids_thread, SO_PASSPIDFD_THREAD,
+ &ids, &info));
+ ASSERT_NE(ids.pid, ids.tid);
+ EXPECT_EQ(ids.tid, info.pid);
+ EXPECT_EQ(ids.pid, info.tgid);
+}
+
+TEST(scm_pidfd_thread_group)
+{
+ struct thread_ids ids;
+ struct pidfd_info info;
+
+ ASSERT_EQ(0, thread_pidfd_flow(send_ids_thread, SO_PASSPIDFD,
+ &ids, &info));
+ ASSERT_NE(ids.pid, ids.tid);
+ EXPECT_EQ(ids.pid, info.pid);
+ EXPECT_EQ(ids.pid, info.tgid);
+}
+
+TEST(scm_pidfd_thread_creds)
+{
+ struct thread_ids ids;
+ struct pidfd_info info;
+
+ ASSERT_EQ(0, thread_pidfd_flow(send_ids_creds_thread, SO_PASSPIDFD_THREAD,
+ &ids, &info));
+ ASSERT_NE(ids.pid, ids.tid);
+ EXPECT_EQ(ids.tid, info.pid);
+ EXPECT_EQ(ids.pid, info.tgid);
+}
+
TEST_HARNESS_MAIN
--
2.53.0
^ permalink raw reply related [flat|nested] 25+ messages in thread
* [PATCH 05/10] net: turn sk_peer_pid into an array indexed by pid type
2026-08-31 11:21 [PATCH 00/10] net: support thread-specific pidfds for send and connect Christian Brauner
` (3 preceding siblings ...)
2026-08-31 11:21 ` [PATCH 04/10] selftests/net: SO_PASSPIDFD_THREAD Christian Brauner
@ 2026-08-31 11:21 ` Christian Brauner
2026-09-02 0:20 ` Jakub Kicinski
2026-08-31 11:21 ` [PATCH 06/10] af_unix: record the pid of the connecting thread Christian Brauner
` (4 subsequent siblings)
9 siblings, 1 reply; 25+ messages in thread
From: Christian Brauner @ 2026-08-31 11:21 UTC (permalink / raw)
To: Jakub Kicinski, Kuniyuki Iwashima, Oleg Nesterov
Cc: David S. Miller, Eric Dumazet, Paolo Abeni, Simon Horman,
Willem de Bruijn, netdev, linux-kernel, Alexander Viro, Jan Kara,
linux-fsdevel, Alexander Mikhalitsyn,
Christian Brauner (Amutable)
Currently only the struct pid of the thread-group leader is recorded
for a socket's peer. To make room for the struct pid of the thread that
called connect(), listen() or socketpair() turn sk_peer_pid into an
array indexed by pid type. All users, including bluetooth and the
coredump socket, keep using the PIDTYPE_TGID slot.
Nothing fills the PIDTYPE_PID slot yet. No functional changes.
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
fs/coredump.c | 2 +-
include/net/sock.h | 4 ++--
net/bluetooth/af_bluetooth.c | 6 +++---
net/bluetooth/hci_sock.c | 8 ++++----
net/bluetooth/l2cap_sock.c | 2 +-
net/core/sock.c | 9 +++++----
net/unix/af_unix.c | 14 +++++++-------
7 files changed, 23 insertions(+), 22 deletions(-)
diff --git a/fs/coredump.c b/fs/coredump.c
index ac3cd74808c6..71a0093ada1b 100644
--- a/fs/coredump.c
+++ b/fs/coredump.c
@@ -722,7 +722,7 @@ static bool coredump_sock_connect(struct core_name *cn, struct coredump_params *
}
/* ... and validate that @sk_peer_pid matches @cprm.pid. */
- if (WARN_ON_ONCE(unix_peer(socket->sk)->sk_peer_pid != cprm->pid))
+ if (WARN_ON_ONCE(unix_peer(socket->sk)->sk_peer_pid[PIDTYPE_TGID] != cprm->pid))
return false;
cprm->limit = RLIM_INFINITY;
diff --git a/include/net/sock.h b/include/net/sock.h
index fc09c92e8a83..67b743bab220 100644
--- a/include/net/sock.h
+++ b/include/net/sock.h
@@ -301,7 +301,7 @@ struct sk_filter;
* @sk_type: socket type (%SOCK_STREAM, etc)
* @sk_protocol: which protocol this socket belongs in this network family
* @sk_peer_lock: lock protecting @sk_peer_pid and @sk_peer_cred
- * @sk_peer_pid: &struct pid for this socket's peer
+ * @sk_peer_pid: &struct pid for this socket's peer, by pid type
* @sk_peer_cred: %SO_PEERCRED setting
* @sk_rcvlowat: %SO_RCVLOWAT setting
* @sk_rcvtimeo: %SO_RCVTIMEO setting
@@ -546,7 +546,7 @@ struct sock {
u64 sk_ino;
spinlock_t sk_peer_lock;
int sk_bind_phc;
- struct pid *sk_peer_pid;
+ DECLARE_PIDS(sk_peer_pid, PIDTYPE_TGID);
const struct cred *sk_peer_cred;
ktime_t sk_stamp;
diff --git a/net/bluetooth/af_bluetooth.c b/net/bluetooth/af_bluetooth.c
index 411d66f24393..7758e9ea3848 100644
--- a/net/bluetooth/af_bluetooth.c
+++ b/net/bluetooth/af_bluetooth.c
@@ -161,7 +161,7 @@ struct sock *bt_sock_alloc(struct net *net, struct socket *sock,
/* Init peer information so it can be properly monitored */
if (!kern) {
spin_lock(&sk->sk_peer_lock);
- sk->sk_peer_pid = get_pid(task_tgid(current));
+ sk->sk_peer_pid[PIDTYPE_TGID] = get_pid(task_tgid(current));
sk->sk_peer_cred = get_current_cred();
spin_unlock(&sk->sk_peer_lock);
}
@@ -235,9 +235,9 @@ void bt_accept_enqueue(struct sock *parent, struct sock *sk, bool bh)
* socket is allocated by the kernel.
*/
spin_lock(&sk->sk_peer_lock);
- old_pid = sk->sk_peer_pid;
+ old_pid = sk->sk_peer_pid[PIDTYPE_TGID];
old_cred = sk->sk_peer_cred;
- sk->sk_peer_pid = get_pid(parent->sk_peer_pid);
+ sk->sk_peer_pid[PIDTYPE_TGID] = get_pid(parent->sk_peer_pid[PIDTYPE_TGID]);
sk->sk_peer_cred = get_cred(parent->sk_peer_cred);
spin_unlock(&sk->sk_peer_lock);
diff --git a/net/bluetooth/hci_sock.c b/net/bluetooth/hci_sock.c
index 070ca388f9ac..91e4738eabdb 100644
--- a/net/bluetooth/hci_sock.c
+++ b/net/bluetooth/hci_sock.c
@@ -273,21 +273,21 @@ static void hci_sock_copy_creds(struct sock *sk, struct sk_buff *skb)
creds = &bt_cb(skb)->creds;
/* Check if peer credentials is set */
- if (!sk->sk_peer_pid) {
+ if (!sk->sk_peer_pid[PIDTYPE_TGID]) {
/* Check if parent peer credentials is set */
- if (bt_sk(sk)->parent && bt_sk(sk)->parent->sk_peer_pid)
+ if (bt_sk(sk)->parent && bt_sk(sk)->parent->sk_peer_pid[PIDTYPE_TGID])
sk = bt_sk(sk)->parent;
else
return;
}
/* Check if scm_creds already set */
- if (creds->pid == pid_vnr(sk->sk_peer_pid))
+ if (creds->pid == pid_vnr(sk->sk_peer_pid[PIDTYPE_TGID]))
return;
memset(creds, 0, sizeof(*creds));
- creds->pid = pid_vnr(sk->sk_peer_pid);
+ creds->pid = pid_vnr(sk->sk_peer_pid[PIDTYPE_TGID]);
if (sk->sk_peer_cred) {
creds->uid = sk->sk_peer_cred->uid;
creds->gid = sk->sk_peer_cred->gid;
diff --git a/net/bluetooth/l2cap_sock.c b/net/bluetooth/l2cap_sock.c
index 1194c37e466f..872d8fb31b6f 100644
--- a/net/bluetooth/l2cap_sock.c
+++ b/net/bluetooth/l2cap_sock.c
@@ -1890,7 +1890,7 @@ static struct pid *l2cap_sock_get_peer_pid_cb(struct l2cap_chan *chan)
{
struct sock *sk = chan->data;
- return sk->sk_peer_pid;
+ return sk->sk_peer_pid[PIDTYPE_TGID];
}
static void l2cap_sock_suspend_cb(struct l2cap_chan *chan)
diff --git a/net/core/sock.c b/net/core/sock.c
index f9615b0de10e..6ada7e7eb7d7 100644
--- a/net/core/sock.c
+++ b/net/core/sock.c
@@ -1928,7 +1928,8 @@ int sk_getsockopt(struct sock *sk, int level, int optname,
len = sizeof(peercred);
spin_lock(&sk->sk_peer_lock);
- cred_to_ucred(sk->sk_peer_pid, sk->sk_peer_cred, &peercred);
+ cred_to_ucred(sk->sk_peer_pid[PIDTYPE_TGID], sk->sk_peer_cred,
+ &peercred);
spin_unlock(&sk->sk_peer_lock);
if (copy_to_sockptr(optval, &peercred, len))
@@ -1947,7 +1948,7 @@ int sk_getsockopt(struct sock *sk, int level, int optname,
len = sizeof(pidfd);
spin_lock(&sk->sk_peer_lock);
- peer_pid = get_pid(sk->sk_peer_pid);
+ peer_pid = get_pid(sk->sk_peer_pid[PIDTYPE_TGID]);
spin_unlock(&sk->sk_peer_lock);
if (!peer_pid)
@@ -2401,7 +2402,7 @@ static void __sk_destruct(struct rcu_head *head)
/* We do not need to acquire sk->sk_peer_lock, we are the last user. */
put_cred(sk->sk_peer_cred);
- put_pid(sk->sk_peer_pid);
+ put_pids(sk->sk_peer_pid);
if (likely(sk->sk_net_refcnt)) {
put_net_track(net, &sk->ns_tracker);
@@ -3797,7 +3798,7 @@ void sock_init_data_uid(struct socket *sock, struct sock *sk, kuid_t uid)
sk->sk_frag.offset = 0;
sk->sk_peek_off = -1;
- sk->sk_peer_pid = NULL;
+ memset(sk->sk_peer_pid, 0, sizeof(sk->sk_peer_pid));
sk->sk_peer_cred = NULL;
spin_lock_init(&sk->sk_peer_lock);
diff --git a/net/unix/af_unix.c b/net/unix/af_unix.c
index 468a9c479b87..335abd23c9bf 100644
--- a/net/unix/af_unix.c
+++ b/net/unix/af_unix.c
@@ -737,7 +737,7 @@ static void unix_release_sock(struct sock *sk, int embrion)
}
struct unix_peercred {
- struct pid *peer_pid;
+ DECLARE_PIDS(peer_pid, PIDTYPE_TGID);
const struct cred *peer_cred;
};
@@ -749,7 +749,7 @@ static inline int prepare_peercred(struct unix_peercred *peercred)
pid = task_tgid(current);
err = pidfs_register_pid(pid);
if (likely(!err)) {
- peercred->peer_pid = get_pid(pid);
+ peercred->peer_pid[PIDTYPE_TGID] = get_pid(pid);
peercred->peer_cred = get_current_cred();
}
return err;
@@ -762,7 +762,7 @@ static void drop_peercred(struct unix_peercred *peercred)
might_sleep();
- swap(peercred->peer_pid, pid);
+ swap(peercred->peer_pid[PIDTYPE_TGID], pid);
swap(peercred->peer_cred, cred);
put_pid(pid);
@@ -772,7 +772,7 @@ static void drop_peercred(struct unix_peercred *peercred)
static inline void init_peercred(struct sock *sk,
const struct unix_peercred *peercred)
{
- sk->sk_peer_pid = peercred->peer_pid;
+ sk->sk_peer_pid[PIDTYPE_TGID] = peercred->peer_pid[PIDTYPE_TGID];
sk->sk_peer_cred = peercred->peer_cred;
}
@@ -782,12 +782,12 @@ static void update_peercred(struct sock *sk, struct unix_peercred *peercred)
struct pid *old_pid;
spin_lock(&sk->sk_peer_lock);
- old_pid = sk->sk_peer_pid;
+ old_pid = sk->sk_peer_pid[PIDTYPE_TGID];
old_cred = sk->sk_peer_cred;
init_peercred(sk, peercred);
spin_unlock(&sk->sk_peer_lock);
- peercred->peer_pid = old_pid;
+ peercred->peer_pid[PIDTYPE_TGID] = old_pid;
peercred->peer_cred = old_cred;
}
@@ -796,7 +796,7 @@ static void copy_peercred(struct sock *sk, struct sock *peersk)
lockdep_assert_held(&unix_sk(peersk)->lock);
spin_lock(&sk->sk_peer_lock);
- sk->sk_peer_pid = get_pid(peersk->sk_peer_pid);
+ sk->sk_peer_pid[PIDTYPE_TGID] = get_pid(peersk->sk_peer_pid[PIDTYPE_TGID]);
sk->sk_peer_cred = get_cred(peersk->sk_peer_cred);
spin_unlock(&sk->sk_peer_lock);
}
--
2.53.0
^ permalink raw reply related [flat|nested] 25+ messages in thread
* [PATCH 06/10] af_unix: record the pid of the connecting thread
2026-08-31 11:21 [PATCH 00/10] net: support thread-specific pidfds for send and connect Christian Brauner
` (4 preceding siblings ...)
2026-08-31 11:21 ` [PATCH 05/10] net: turn sk_peer_pid into an array indexed by pid type Christian Brauner
@ 2026-08-31 11:21 ` Christian Brauner
2026-09-07 10:29 ` Alexander Mikhalitsyn
2026-08-31 11:21 ` [PATCH 07/10] net: add SO_PEERPIDFD_THREAD to get a thread-specific pidfd Christian Brauner
` (3 subsequent siblings)
9 siblings, 1 reply; 25+ messages in thread
From: Christian Brauner @ 2026-08-31 11:21 UTC (permalink / raw)
To: Jakub Kicinski, Kuniyuki Iwashima, Oleg Nesterov
Cc: David S. Miller, Eric Dumazet, Paolo Abeni, Simon Horman,
Willem de Bruijn, netdev, linux-kernel, Alexander Viro, Jan Kara,
linux-fsdevel, Alexander Mikhalitsyn,
Christian Brauner (Amutable)
Currently only the struct pid of the thread-group leader is recorded.
The identity of the actual thread that connected to a given socket
cannot be retrieved.
Add the plumbing to make it possible to retrieve a pidfd for the
connecting thread. Nothing uses the thread-specific struct pid yet. No
functional changes.
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
net/unix/af_unix.c | 30 +++++++++++++-----------------
1 file changed, 13 insertions(+), 17 deletions(-)
diff --git a/net/unix/af_unix.c b/net/unix/af_unix.c
index 335abd23c9bf..d01ee76c8026 100644
--- a/net/unix/af_unix.c
+++ b/net/unix/af_unix.c
@@ -743,51 +743,47 @@ struct unix_peercred {
static inline int prepare_peercred(struct unix_peercred *peercred)
{
- struct pid *pid;
int err;
- pid = task_tgid(current);
- err = pidfs_register_pid(pid);
- if (likely(!err)) {
- peercred->peer_pid[PIDTYPE_TGID] = get_pid(pid);
- peercred->peer_cred = get_current_cred();
+ get_task_pids(peercred->peer_pid, current);
+ err = pidfs_register_pids(peercred->peer_pid);
+ if (unlikely(err)) {
+ put_pids(peercred->peer_pid);
+ return err;
}
- return err;
+
+ peercred->peer_cred = get_current_cred();
+ return 0;
}
static void drop_peercred(struct unix_peercred *peercred)
{
const struct cred *cred = NULL;
- struct pid *pid = NULL;
might_sleep();
- swap(peercred->peer_pid[PIDTYPE_TGID], pid);
+ put_pids(peercred->peer_pid);
swap(peercred->peer_cred, cred);
-
- put_pid(pid);
put_cred(cred);
}
static inline void init_peercred(struct sock *sk,
const struct unix_peercred *peercred)
{
- sk->sk_peer_pid[PIDTYPE_TGID] = peercred->peer_pid[PIDTYPE_TGID];
+ memcpy(sk->sk_peer_pid, peercred->peer_pid, sizeof(sk->sk_peer_pid));
sk->sk_peer_cred = peercred->peer_cred;
}
static void update_peercred(struct sock *sk, struct unix_peercred *peercred)
{
const struct cred *old_cred;
- struct pid *old_pid;
spin_lock(&sk->sk_peer_lock);
- old_pid = sk->sk_peer_pid[PIDTYPE_TGID];
+ swap_pids(sk->sk_peer_pid, peercred->peer_pid);
old_cred = sk->sk_peer_cred;
- init_peercred(sk, peercred);
+ sk->sk_peer_cred = peercred->peer_cred;
spin_unlock(&sk->sk_peer_lock);
- peercred->peer_pid[PIDTYPE_TGID] = old_pid;
peercred->peer_cred = old_cred;
}
@@ -796,7 +792,7 @@ static void copy_peercred(struct sock *sk, struct sock *peersk)
lockdep_assert_held(&unix_sk(peersk)->lock);
spin_lock(&sk->sk_peer_lock);
- sk->sk_peer_pid[PIDTYPE_TGID] = get_pid(peersk->sk_peer_pid[PIDTYPE_TGID]);
+ get_pids(sk->sk_peer_pid, peersk->sk_peer_pid);
sk->sk_peer_cred = get_cred(peersk->sk_peer_cred);
spin_unlock(&sk->sk_peer_lock);
}
--
2.53.0
^ permalink raw reply related [flat|nested] 25+ messages in thread
* [PATCH 07/10] net: add SO_PEERPIDFD_THREAD to get a thread-specific pidfd
2026-08-31 11:21 [PATCH 00/10] net: support thread-specific pidfds for send and connect Christian Brauner
` (5 preceding siblings ...)
2026-08-31 11:21 ` [PATCH 06/10] af_unix: record the pid of the connecting thread Christian Brauner
@ 2026-08-31 11:21 ` Christian Brauner
2026-09-07 10:49 ` Alexander Mikhalitsyn
2026-08-31 11:21 ` [PATCH 08/10] selftests/net: SO_PEERPIDFD_THREAD Christian Brauner
` (2 subsequent siblings)
9 siblings, 1 reply; 25+ messages in thread
From: Christian Brauner @ 2026-08-31 11:21 UTC (permalink / raw)
To: Jakub Kicinski, Kuniyuki Iwashima, Oleg Nesterov
Cc: David S. Miller, Eric Dumazet, Paolo Abeni, Simon Horman,
Willem de Bruijn, netdev, linux-kernel, Alexander Viro, Jan Kara,
linux-fsdevel, Alexander Mikhalitsyn,
Christian Brauner (Amutable)
SO_PEERPIDFD hands out a pidfd for the thread-group that called
connect() or socketpair(). Enable workloads such as the coredump server
or a broker to get a pidfd of the specific thread that connected to the
socket.
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
arch/alpha/include/uapi/asm/socket.h | 2 +
arch/mips/include/uapi/asm/socket.h | 2 +
arch/parisc/include/uapi/asm/socket.h | 2 +
arch/sparc/include/uapi/asm/socket.h | 2 +
include/uapi/asm-generic/socket.h | 2 +
net/core/sock.c | 87 ++++++++++++++++++++---------------
net/unix/af_unix.c | 1 +
7 files changed, 61 insertions(+), 37 deletions(-)
diff --git a/arch/alpha/include/uapi/asm/socket.h b/arch/alpha/include/uapi/asm/socket.h
index bb3d534826bb..5d3524c26b2b 100644
--- a/arch/alpha/include/uapi/asm/socket.h
+++ b/arch/alpha/include/uapi/asm/socket.h
@@ -159,6 +159,8 @@
#define SO_PASSPIDFD_THREAD 86
+#define SO_PEERPIDFD_THREAD 87
+
#if !defined(__KERNEL__)
#if __BITS_PER_LONG == 64
diff --git a/arch/mips/include/uapi/asm/socket.h b/arch/mips/include/uapi/asm/socket.h
index 269badcaa086..245a43f52fb1 100644
--- a/arch/mips/include/uapi/asm/socket.h
+++ b/arch/mips/include/uapi/asm/socket.h
@@ -170,6 +170,8 @@
#define SO_PASSPIDFD_THREAD 86
+#define SO_PEERPIDFD_THREAD 87
+
#if !defined(__KERNEL__)
#if __BITS_PER_LONG == 64
diff --git a/arch/parisc/include/uapi/asm/socket.h b/arch/parisc/include/uapi/asm/socket.h
index 313aee10a52c..f23710e1c671 100644
--- a/arch/parisc/include/uapi/asm/socket.h
+++ b/arch/parisc/include/uapi/asm/socket.h
@@ -151,6 +151,8 @@
#define SO_PASSPIDFD_THREAD 0x4054
+#define SO_PEERPIDFD_THREAD 0x4055
+
#if !defined(__KERNEL__)
#if __BITS_PER_LONG == 64
diff --git a/arch/sparc/include/uapi/asm/socket.h b/arch/sparc/include/uapi/asm/socket.h
index bd3e69bcce7a..b35b25bdefc2 100644
--- a/arch/sparc/include/uapi/asm/socket.h
+++ b/arch/sparc/include/uapi/asm/socket.h
@@ -152,6 +152,8 @@
#define SO_PASSPIDFD_THREAD 0x005f
+#define SO_PEERPIDFD_THREAD 0x0060
+
#if !defined(__KERNEL__)
diff --git a/include/uapi/asm-generic/socket.h b/include/uapi/asm-generic/socket.h
index d1e5c6de146d..56fed7ab27ab 100644
--- a/include/uapi/asm-generic/socket.h
+++ b/include/uapi/asm-generic/socket.h
@@ -154,6 +154,8 @@
#define SO_PASSPIDFD_THREAD 86
+#define SO_PEERPIDFD_THREAD 87
+
#if !defined(__KERNEL__)
#if __BITS_PER_LONG == 64 || (defined(__x86_64__) && defined(__ILP32__))
diff --git a/net/core/sock.c b/net/core/sock.c
index 6ada7e7eb7d7..cb2ffd329bc6 100644
--- a/net/core/sock.c
+++ b/net/core/sock.c
@@ -1743,6 +1743,50 @@ static int groups_to_user(sockptr_t dst, const struct group_info *src)
return 0;
}
+/* Hand out a pidfd for @type of the socket's peer via SO_PEERPIDFD*. */
+static int sk_getsockopt_peerpidfd(struct sock *sk, enum pid_type type,
+ sockptr_t optval, sockptr_t optlen, int len)
+{
+ struct file *pidfd_file = NULL;
+ unsigned int flags = 0;
+ struct pid *peer_pid;
+ int pidfd;
+
+ if (len > sizeof(pidfd))
+ len = sizeof(pidfd);
+
+ spin_lock(&sk->sk_peer_lock);
+ peer_pid = get_pid(sk->sk_peer_pid[type]);
+ spin_unlock(&sk->sk_peer_lock);
+
+ if (!peer_pid)
+ return -ENODATA;
+
+ /* The use of PIDFD_STALE requires stashing of struct pid on pidfs
+ * with pidfs_register_pid() and only AF_UNIX were prepared for this.
+ */
+ if (sk->sk_family == AF_UNIX)
+ flags |= PIDFD_STALE;
+ if (type == PIDTYPE_PID)
+ flags |= PIDFD_THREAD;
+
+ pidfd = pidfd_prepare(peer_pid, flags, &pidfd_file);
+ put_pid(peer_pid);
+ if (pidfd < 0)
+ return pidfd;
+
+ if (copy_to_sockptr(optval, &pidfd, len) ||
+ copy_to_sockptr(optlen, &len, sizeof(int))) {
+ put_unused_fd(pidfd);
+ fput(pidfd_file);
+
+ return -EFAULT;
+ }
+
+ fd_install(pidfd, pidfd_file);
+ return 0;
+}
+
int sk_getsockopt(struct sock *sk, int level, int optname,
sockptr_t optval, sockptr_t optlen)
{
@@ -1938,45 +1982,14 @@ int sk_getsockopt(struct sock *sk, int level, int optname,
}
case SO_PEERPIDFD:
- {
- struct pid *peer_pid;
- struct file *pidfd_file = NULL;
- unsigned int flags = 0;
- int pidfd;
-
- if (len > sizeof(pidfd))
- len = sizeof(pidfd);
-
- spin_lock(&sk->sk_peer_lock);
- peer_pid = get_pid(sk->sk_peer_pid[PIDTYPE_TGID]);
- spin_unlock(&sk->sk_peer_lock);
-
- if (!peer_pid)
- return -ENODATA;
-
- /* The use of PIDFD_STALE requires stashing of struct pid
- * on pidfs with pidfs_register_pid() and only AF_UNIX
- * were prepared for this.
- */
- if (sk->sk_family == AF_UNIX)
- flags = PIDFD_STALE;
+ return sk_getsockopt_peerpidfd(sk, PIDTYPE_TGID, optval, optlen, len);
- pidfd = pidfd_prepare(peer_pid, flags, &pidfd_file);
- put_pid(peer_pid);
- if (pidfd < 0)
- return pidfd;
-
- if (copy_to_sockptr(optval, &pidfd, len) ||
- copy_to_sockptr(optlen, &len, sizeof(int))) {
- put_unused_fd(pidfd);
- fput(pidfd_file);
-
- return -EFAULT;
- }
+ case SO_PEERPIDFD_THREAD:
+ /* Only AF_UNIX records the peer's connecting thread. */
+ if (!sk_is_unix(sk))
+ return -EOPNOTSUPP;
- fd_install(pidfd, pidfd_file);
- return 0;
- }
+ return sk_getsockopt_peerpidfd(sk, PIDTYPE_PID, optval, optlen, len);
case SO_PEERGROUPS:
{
diff --git a/net/unix/af_unix.c b/net/unix/af_unix.c
index d01ee76c8026..563d9827c5cc 100644
--- a/net/unix/af_unix.c
+++ b/net/unix/af_unix.c
@@ -1056,6 +1056,7 @@ static bool unix_bpf_bypass_getsockopt(int level, int optname)
if (level == SOL_SOCKET) {
switch (optname) {
case SO_PEERPIDFD:
+ case SO_PEERPIDFD_THREAD:
return true;
default:
return false;
--
2.53.0
^ permalink raw reply related [flat|nested] 25+ messages in thread
* [PATCH 08/10] selftests/net: SO_PEERPIDFD_THREAD
2026-08-31 11:21 [PATCH 00/10] net: support thread-specific pidfds for send and connect Christian Brauner
` (6 preceding siblings ...)
2026-08-31 11:21 ` [PATCH 07/10] net: add SO_PEERPIDFD_THREAD to get a thread-specific pidfd Christian Brauner
@ 2026-08-31 11:21 ` Christian Brauner
2026-09-07 10:51 ` Alexander Mikhalitsyn
2026-08-31 11:21 ` [PATCH 09/10] pidfs: record the coredump on the dumping thread's pid too Christian Brauner
2026-08-31 11:21 ` [PATCH 10/10] selftests/coredump: check the dumping thread's pidfd Christian Brauner
9 siblings, 1 reply; 25+ messages in thread
From: Christian Brauner @ 2026-08-31 11:21 UTC (permalink / raw)
To: Jakub Kicinski, Kuniyuki Iwashima, Oleg Nesterov
Cc: David S. Miller, Eric Dumazet, Paolo Abeni, Simon Horman,
Willem de Bruijn, netdev, linux-kernel, Alexander Viro, Jan Kara,
linux-fsdevel, Alexander Mikhalitsyn,
Christian Brauner (Amutable)
Add tests for SO_PEERPIDFD_THREAD.
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
tools/testing/selftests/net/af_unix/scm_pidfd.c | 97 +++++++++++++++++++++++++
1 file changed, 97 insertions(+)
diff --git a/tools/testing/selftests/net/af_unix/scm_pidfd.c b/tools/testing/selftests/net/af_unix/scm_pidfd.c
index 019c48e1cdcd..bbbd5e7b4fa7 100644
--- a/tools/testing/selftests/net/af_unix/scm_pidfd.c
+++ b/tools/testing/selftests/net/af_unix/scm_pidfd.c
@@ -32,6 +32,10 @@
#define SO_PASSPIDFD_THREAD 86
#endif
+#ifndef SO_PEERPIDFD_THREAD
+#define SO_PEERPIDFD_THREAD 87
+#endif
+
#define CHILD_EXIT_CODE_OK 123
static void child_die()
@@ -822,4 +826,97 @@ TEST(scm_pidfd_thread_creds)
EXPECT_EQ(ids.pid, info.tgid);
}
+static void *peer_connect_thread(void *arg)
+{
+ struct sock_addr *sa = arg;
+ struct thread_ids ids = {
+ .pid = getpid(),
+ .tid = gettid(),
+ };
+ int fd;
+ char sync;
+
+ fd = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (fd < 0)
+ return (void *)1;
+
+ if (connect(fd, (struct sockaddr *)&sa->listen_addr, sa->addrlen))
+ return (void *)1;
+
+ if (send(fd, &ids, sizeof(ids), 0) != sizeof(ids))
+ return (void *)1;
+
+ /* stay alive until the server has looked at our pidfd */
+ if (read(fd, &sync, 1) != 1)
+ return (void *)1;
+
+ close(fd);
+ return NULL;
+}
+
+static int peer_pidfd_info(int fd, int optname, struct pidfd_info *info)
+{
+ int pidfd;
+ socklen_t len = sizeof(pidfd);
+
+ if (getsockopt(fd, SOL_SOCKET, optname, &pidfd, &len)) {
+ log_err("getsockopt(SO_PEERPIDFD*)");
+ return -1;
+ }
+
+ info->mask = PIDFD_INFO_PID;
+ if (ioctl(pidfd, PIDFD_GET_INFO, info)) {
+ log_err("ioctl(PIDFD_GET_INFO)");
+ close(pidfd);
+ return -1;
+ }
+
+ close(pidfd);
+ return 0;
+}
+
+/* SO_PEERPIDFD_THREAD returns a pidfd for the peer's connecting thread. */
+TEST(so_peerpidfd_thread)
+{
+ struct sock_addr sa;
+ struct thread_ids ids;
+ struct pidfd_info info;
+ pthread_t thread;
+ void *tret;
+ int server, cfd;
+
+ server = socket(AF_UNIX, SOCK_STREAM, 0);
+ ASSERT_LE(0, server);
+
+ fill_sockaddr(&sa, true);
+ ASSERT_EQ(0, bind(server, (struct sockaddr *)&sa.listen_addr, sa.addrlen));
+ ASSERT_EQ(0, listen(server, 1));
+
+ ASSERT_EQ(0, pthread_create(&thread, NULL, peer_connect_thread, &sa));
+
+ cfd = accept(server, NULL, NULL);
+ ASSERT_LE(0, cfd);
+
+ ASSERT_EQ(sizeof(ids), recv(cfd, &ids, sizeof(ids), MSG_WAITALL));
+ ASSERT_NE(ids.pid, ids.tid);
+
+ /* SO_PEERPIDFD refers to the peer's thread-group. */
+ ASSERT_EQ(0, peer_pidfd_info(cfd, SO_PEERPIDFD, &info));
+ EXPECT_EQ(ids.pid, info.pid);
+ EXPECT_EQ(ids.pid, info.tgid);
+
+ /* SO_PEERPIDFD_THREAD refers to the connecting thread. */
+ ASSERT_EQ(0, peer_pidfd_info(cfd, SO_PEERPIDFD_THREAD, &info));
+ EXPECT_EQ(ids.tid, info.pid);
+ EXPECT_EQ(ids.pid, info.tgid);
+
+ /* release the connecting thread */
+ ASSERT_EQ(1, write(cfd, "x", 1));
+ ASSERT_EQ(0, pthread_join(thread, &tret));
+ ASSERT_EQ(NULL, tret);
+
+ close(cfd);
+ close(server);
+}
+
TEST_HARNESS_MAIN
--
2.53.0
^ permalink raw reply related [flat|nested] 25+ messages in thread
* [PATCH 09/10] pidfs: record the coredump on the dumping thread's pid too
2026-08-31 11:21 [PATCH 00/10] net: support thread-specific pidfds for send and connect Christian Brauner
` (7 preceding siblings ...)
2026-08-31 11:21 ` [PATCH 08/10] selftests/net: SO_PEERPIDFD_THREAD Christian Brauner
@ 2026-08-31 11:21 ` Christian Brauner
2026-09-07 10:59 ` Alexander Mikhalitsyn
2026-08-31 11:21 ` [PATCH 10/10] selftests/coredump: check the dumping thread's pidfd Christian Brauner
9 siblings, 1 reply; 25+ messages in thread
From: Christian Brauner @ 2026-08-31 11:21 UTC (permalink / raw)
To: Jakub Kicinski, Kuniyuki Iwashima, Oleg Nesterov
Cc: David S. Miller, Eric Dumazet, Paolo Abeni, Simon Horman,
Willem de Bruijn, netdev, linux-kernel, Alexander Viro, Jan Kara,
linux-fsdevel, Alexander Mikhalitsyn,
Christian Brauner (Amutable)
If a thread-group coredumps only the thread-group leader pidfd will
return coredump information. A pidfd for the thread that took the fatal
signal cannot be used to retrieve it.
Record both the thread-group leader and the specific thread that took
the signal and register both in pidfs. Mark both the thread-group leader
and the specific thread with the coredump information so retrieval works
for both pidfds.
Now that both SO_PEERPIDFD and SO_PEERPIDFD_THREAD are available it's
easy to get the coredump information for the specific thread.
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
fs/coredump.c | 22 +++++++++++++---------
fs/pidfs.c | 11 +++++++++--
include/linux/coredump.h | 4 +++-
3 files changed, 25 insertions(+), 12 deletions(-)
diff --git a/fs/coredump.c b/fs/coredump.c
index 71a0093ada1b..b5ff4b3e1831 100644
--- a/fs/coredump.c
+++ b/fs/coredump.c
@@ -454,7 +454,7 @@ static bool coredump_parse(struct core_name *cn, struct coredump_params *cprm,
* leader we know that the thread-group leader
* cannot be reaped until @current has exited.
*/
- cprm->pid = task_tgid(current);
+ task_pids(cprm->pid, current);
err = cn_printf(cn, "%d", COREDUMP_PIDFD_NUMBER);
break;
}
@@ -626,13 +626,17 @@ static int umh_coredump_setup(struct subprocess_info *info, struct cred *new)
struct coredump_params *cp = (struct coredump_params *)info->data;
int err;
- if (cp->pid) {
+ if (cp->pid[PIDTYPE_TGID]) {
struct file *pidfs_file __free(fput) = NULL;
- pidfs_file = pidfs_alloc_file(cp->pid, 0);
+ pidfs_file = pidfs_alloc_file(cp->pid[PIDTYPE_TGID], 0);
if (IS_ERR(pidfs_file))
return PTR_ERR(pidfs_file);
+ err = pidfs_register_pids(cp->pid);
+ if (err)
+ return err;
+
pidfs_coredump(cp);
/*
@@ -695,12 +699,12 @@ static bool coredump_sock_connect(struct core_name *cn, struct coredump_params *
return false;
/*
- * Set the thread-group leader pid which is used for the peer
- * credentials during connect() below. Then immediately register
- * it in pidfs...
+ * Set the pids of the dumping thread and its thread-group leader
+ * which are used for the peer credentials during connect() below.
+ * Then immediately register them in pidfs...
*/
- cprm->pid = task_tgid(current);
- retval = pidfs_register_pid(cprm->pid);
+ task_pids(cprm->pid, current);
+ retval = pidfs_register_pids(cprm->pid);
if (retval)
return false;
@@ -722,7 +726,7 @@ static bool coredump_sock_connect(struct core_name *cn, struct coredump_params *
}
/* ... and validate that @sk_peer_pid matches @cprm.pid. */
- if (WARN_ON_ONCE(unix_peer(socket->sk)->sk_peer_pid[PIDTYPE_TGID] != cprm->pid))
+ if (WARN_ON_ONCE(!pids_equal(unix_peer(socket->sk)->sk_peer_pid, cprm->pid)))
return false;
cprm->limit = RLIM_INFINITY;
diff --git a/fs/pidfs.c b/fs/pidfs.c
index 586af2e5811c..29299b2c7ca7 100644
--- a/fs/pidfs.c
+++ b/fs/pidfs.c
@@ -793,9 +793,9 @@ void pidfs_exit(struct task_struct *tsk)
}
#ifdef CONFIG_COREDUMP
-void pidfs_coredump(const struct coredump_params *cprm)
+static void pidfs_coredump_pid(struct pid *pid,
+ const struct coredump_params *cprm)
{
- struct pid *pid = cprm->pid;
struct pidfs_attr *attr;
attr = READ_ONCE(pid->attr);
@@ -814,6 +814,13 @@ void pidfs_coredump(const struct coredump_params *cprm)
smp_wmb();
set_bit(PIDFS_ATTR_BIT_COREDUMP, &attr->attr_mask);
}
+
+void pidfs_coredump(const struct coredump_params *cprm)
+{
+ /* The dumping thread's pidfd reports the coredump as well. */
+ for (enum pid_type type = PIDTYPE_PID; type <= pids_last(cprm->pid); type++)
+ pidfs_coredump_pid(cprm->pid[type], cprm);
+}
#endif
static struct vfsmount *pidfs_mnt __ro_after_init;
diff --git a/include/linux/coredump.h b/include/linux/coredump.h
index 7b38ee2e7913..0bbb7de6a402 100644
--- a/include/linux/coredump.h
+++ b/include/linux/coredump.h
@@ -5,6 +5,7 @@
#include <linux/types.h>
#include <linux/mm.h>
#include <linux/fs.h>
+#include <linux/pid_types.h>
#include <linux/sched/coredump.h>
#include <asm/siginfo.h>
@@ -32,7 +33,8 @@ struct coredump_params {
int vma_count;
size_t vma_data_size;
struct core_vma_metadata *vma_meta;
- struct pid *pid;
+ /* Dumping thread and its thread-group leader by pid type. */
+ DECLARE_PIDS(pid, PIDTYPE_TGID);
};
extern unsigned int core_file_note_size_limit;
--
2.53.0
^ permalink raw reply related [flat|nested] 25+ messages in thread
* [PATCH 10/10] selftests/coredump: check the dumping thread's pidfd
2026-08-31 11:21 [PATCH 00/10] net: support thread-specific pidfds for send and connect Christian Brauner
` (8 preceding siblings ...)
2026-08-31 11:21 ` [PATCH 09/10] pidfs: record the coredump on the dumping thread's pid too Christian Brauner
@ 2026-08-31 11:21 ` Christian Brauner
2026-09-07 11:00 ` Alexander Mikhalitsyn
9 siblings, 1 reply; 25+ messages in thread
From: Christian Brauner @ 2026-08-31 11:21 UTC (permalink / raw)
To: Jakub Kicinski, Kuniyuki Iwashima, Oleg Nesterov
Cc: David S. Miller, Eric Dumazet, Paolo Abeni, Simon Horman,
Willem de Bruijn, netdev, linux-kernel, Alexander Viro, Jan Kara,
linux-fsdevel, Alexander Mikhalitsyn,
Christian Brauner (Amutable)
Crash from a non-leader thread and verify that the pidfd from
SO_PEERPIDFD_THREAD on the coredump socket refers to that thread and
reports the coredump like the thread-group leader's pidfd does.
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
.../selftests/coredump/coredump_socket_test.c | 175 +++++++++++++++++++++
tools/testing/selftests/coredump/coredump_test.h | 2 +
.../selftests/coredump/coredump_test_helpers.c | 49 ++++++
3 files changed, 226 insertions(+)
diff --git a/tools/testing/selftests/coredump/coredump_socket_test.c b/tools/testing/selftests/coredump/coredump_socket_test.c
index 422728f632ca..ec73bb690bbc 100644
--- a/tools/testing/selftests/coredump/coredump_socket_test.c
+++ b/tools/testing/selftests/coredump/coredump_socket_test.c
@@ -592,6 +592,181 @@ TEST_F(coredump, socket_coredump_signal_sigsegv)
wait_and_check_coredump_server(pid_coredump_server, _metadata, self);
}
+static bool check_coredump_info(const struct pidfd_info *info, const char *what)
+{
+ if (!(info->mask & PIDFD_INFO_COREDUMP)) {
+ fprintf(stderr, "%s: PIDFD_INFO_COREDUMP not set in mask\n", what);
+ return false;
+ }
+
+ if (!(info->coredump_mask & PIDFD_COREDUMPED)) {
+ fprintf(stderr, "%s: PIDFD_COREDUMPED not set in coredump_mask\n", what);
+ return false;
+ }
+
+ if (!(info->mask & PIDFD_INFO_COREDUMP_SIGNAL) || info->coredump_signal != SIGSEGV) {
+ fprintf(stderr, "%s: coredump_signal=%d, expected SIGSEGV=%d\n",
+ what, info->coredump_signal, SIGSEGV);
+ return false;
+ }
+
+ if (!(info->mask & PIDFD_INFO_COREDUMP_CODE) || info->coredump_code != SEGV_MAPERR) {
+ fprintf(stderr, "%s: coredump_code=%d, expected SEGV_MAPERR=%d\n",
+ what, info->coredump_code, SEGV_MAPERR);
+ return false;
+ }
+
+ return true;
+}
+
+/*
+ * Test: PIDFD_INFO_COREDUMP on the dumping thread's pidfd
+ *
+ * Crash from a non-leader thread and verify that the pidfd from
+ * SO_PEERPIDFD_THREAD refers to that thread and reports the coredump
+ * like the thread-group leader's pidfd from SO_PEERPIDFD does.
+ */
+TEST_F(coredump, socket_coredump_thread)
+{
+ int pidfd, ret, status;
+ pid_t pid, pid_coredump_server;
+ struct pidfd_info info = {};
+ int ipc_sockets[2];
+ char c;
+
+ ASSERT_TRUE(set_core_pattern("@/tmp/coredump.socket"));
+
+ ret = socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, ipc_sockets);
+ ASSERT_EQ(ret, 0);
+
+ pid_coredump_server = fork();
+ ASSERT_GE(pid_coredump_server, 0);
+ if (pid_coredump_server == 0) {
+ int fd_server = -1, fd_coredump = -1, fd_peer_pidfd = -1;
+ int fd_thread_pidfd = -1, fd_core_file = -1;
+ struct pidfd_info thread_info = {};
+ int exit_code = EXIT_FAILURE;
+
+ close(ipc_sockets[0]);
+
+ fd_server = create_and_listen_unix_socket("/tmp/coredump.socket");
+ if (fd_server < 0) {
+ fprintf(stderr, "socket_coredump_thread: listen socket failed: %m\n");
+ goto out;
+ }
+
+ if (write_nointr(ipc_sockets[1], "1", 1) < 0) {
+ fprintf(stderr, "socket_coredump_thread: ipc write failed: %m\n");
+ goto out;
+ }
+
+ close(ipc_sockets[1]);
+
+ fd_coredump = accept4(fd_server, NULL, NULL, SOCK_CLOEXEC);
+ if (fd_coredump < 0) {
+ fprintf(stderr, "socket_coredump_thread: accept4 failed: %m\n");
+ goto out;
+ }
+
+ fd_peer_pidfd = get_peer_pidfd(fd_coredump);
+ if (fd_peer_pidfd < 0) {
+ fprintf(stderr, "socket_coredump_thread: get_peer_pidfd failed\n");
+ goto out;
+ }
+
+ fd_thread_pidfd = get_peer_pidfd_thread(fd_coredump);
+ if (fd_thread_pidfd < 0) {
+ fprintf(stderr, "socket_coredump_thread: get_peer_pidfd_thread failed\n");
+ goto out;
+ }
+
+ if (!get_pidfd_info(fd_peer_pidfd, &info) ||
+ !get_pidfd_info(fd_thread_pidfd, &thread_info)) {
+ fprintf(stderr, "socket_coredump_thread: get_pidfd_info failed\n");
+ goto out;
+ }
+
+ /* The peer is the thread-group leader, the dumping thread is not. */
+ if (info.pid != info.tgid || thread_info.tgid != info.tgid ||
+ thread_info.pid == thread_info.tgid) {
+ fprintf(stderr, "socket_coredump_thread: unexpected ids %d/%d and %d/%d\n",
+ info.pid, info.tgid, thread_info.pid, thread_info.tgid);
+ goto out;
+ }
+
+ if (!check_coredump_info(&info, "SO_PEERPIDFD") ||
+ !check_coredump_info(&thread_info, "SO_PEERPIDFD_THREAD"))
+ goto out;
+
+ fd_core_file = open_coredump_tmpfile(self->fd_tmpfs_detached);
+ if (fd_core_file < 0) {
+ fprintf(stderr, "socket_coredump_thread: core tmpfile failed: %m\n");
+ goto out;
+ }
+
+ for (;;) {
+ char buffer[4096];
+ ssize_t bytes_read, bytes_write;
+
+ bytes_read = read(fd_coredump, buffer, sizeof(buffer));
+ if (bytes_read < 0) {
+ fprintf(stderr, "socket_coredump_thread: core read failed: %m\n");
+ goto out;
+ }
+
+ if (bytes_read == 0)
+ break;
+
+ bytes_write = write(fd_core_file, buffer, bytes_read);
+ if (bytes_read != bytes_write) {
+ fprintf(stderr, "socket_coredump_thread: core write %zd/%zd: %m\n",
+ bytes_read, bytes_write);
+ goto out;
+ }
+ }
+
+ exit_code = EXIT_SUCCESS;
+ fprintf(stderr, "socket_coredump_thread: completed successfully\n");
+out:
+ if (fd_core_file >= 0)
+ close(fd_core_file);
+ if (fd_thread_pidfd >= 0)
+ close(fd_thread_pidfd);
+ if (fd_peer_pidfd >= 0)
+ close(fd_peer_pidfd);
+ if (fd_coredump >= 0)
+ close(fd_coredump);
+ if (fd_server >= 0)
+ close(fd_server);
+ _exit(exit_code);
+ }
+ self->pid_coredump_server = pid_coredump_server;
+
+ EXPECT_EQ(close(ipc_sockets[1]), 0);
+ ASSERT_EQ(read_nointr(ipc_sockets[0], &c, 1), 1);
+ EXPECT_EQ(close(ipc_sockets[0]), 0);
+
+ pid = fork();
+ ASSERT_GE(pid, 0);
+ if (pid == 0)
+ crashing_child_thread();
+
+ pidfd = sys_pidfd_open(pid, 0);
+ ASSERT_GE(pidfd, 0);
+
+ waitpid(pid, &status, 0);
+ ASSERT_TRUE(WIFSIGNALED(status));
+ ASSERT_EQ(WTERMSIG(status), SIGSEGV);
+ ASSERT_TRUE(WCOREDUMP(status));
+
+ ASSERT_TRUE(get_pidfd_info(pidfd, &info));
+ ASSERT_TRUE(!!(info.mask & PIDFD_INFO_COREDUMP));
+ ASSERT_TRUE(!!(info.coredump_mask & PIDFD_COREDUMPED));
+ ASSERT_EQ(info.coredump_signal, SIGSEGV);
+
+ wait_and_check_coredump_server(pid_coredump_server, _metadata, self);
+}
+
/*
* Test: PIDFD_INFO_COREDUMP_SIGNAL via simple socket coredump with SIGABRT
*
diff --git a/tools/testing/selftests/coredump/coredump_test.h b/tools/testing/selftests/coredump/coredump_test.h
index ed47f01fa53c..4212656e31f0 100644
--- a/tools/testing/selftests/coredump/coredump_test.h
+++ b/tools/testing/selftests/coredump/coredump_test.h
@@ -27,10 +27,12 @@ FIXTURE(coredump)
/* Shared helper function declarations */
void *do_nothing(void *arg);
void crashing_child(void);
+void crashing_child_thread(void);
int create_detached_tmpfs(void);
int create_and_listen_unix_socket(const char *path);
bool set_core_pattern(const char *pattern);
int get_peer_pidfd(int fd);
+int get_peer_pidfd_thread(int fd);
bool get_pidfd_info(int fd_peer_pidfd, struct pidfd_info *info);
/* Inline helper that uses harness types */
diff --git a/tools/testing/selftests/coredump/coredump_test_helpers.c b/tools/testing/selftests/coredump/coredump_test_helpers.c
index 2a20faf9cb0a..36306069f62e 100644
--- a/tools/testing/selftests/coredump/coredump_test_helpers.c
+++ b/tools/testing/selftests/coredump/coredump_test_helpers.c
@@ -13,6 +13,7 @@
#include <string.h>
#include <sys/epoll.h>
#include <sys/ioctl.h>
+#include <sys/mman.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/un.h>
@@ -38,6 +39,10 @@ struct _fixture_coredump_data {
#define NUM_THREAD_SPAWN 128
+#ifndef SO_PEERPIDFD_THREAD
+#define SO_PEERPIDFD_THREAD 87
+#endif
+
void *do_nothing(void *arg)
{
(void)arg;
@@ -59,6 +64,36 @@ void crashing_child(void)
i = *(volatile int *)NULL;
}
+static void *crashing_thread(void *arg)
+{
+ int *p;
+
+ (void)arg;
+
+ /* crash on purpose with SEGV_MAPERR */
+ p = mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+ if (p == MAP_FAILED)
+ return NULL;
+ munmap(p, PAGE_SIZE);
+ *p = 0;
+
+ return NULL;
+}
+
+void crashing_child_thread(void)
+{
+ pthread_t thread;
+ int i;
+
+ for (i = 0; i < NUM_THREAD_SPAWN; ++i)
+ pthread_create(&thread, NULL, do_nothing, NULL);
+
+ /* crash from a non-leader thread */
+ pthread_create(&thread, NULL, crashing_thread, NULL);
+ pause();
+}
+
int create_detached_tmpfs(void)
{
int fd_context, fd_tmpfs;
@@ -138,6 +173,20 @@ int get_peer_pidfd(int fd)
return fd_peer_pidfd;
}
+int get_peer_pidfd_thread(int fd)
+{
+ int fd_peer_pidfd;
+ socklen_t fd_peer_pidfd_len = sizeof(fd_peer_pidfd);
+ int ret = getsockopt(fd, SOL_SOCKET, SO_PEERPIDFD_THREAD, &fd_peer_pidfd,
+ &fd_peer_pidfd_len);
+ if (ret < 0) {
+ fprintf(stderr, "%s: getsockopt(SO_PEERPIDFD_THREAD) failed: %m\n", __func__);
+ return -1;
+ }
+ fprintf(stderr, "%s: successfully retrieved pidfd %d\n", __func__, fd_peer_pidfd);
+ return fd_peer_pidfd;
+}
+
bool get_pidfd_info(int fd_peer_pidfd, struct pidfd_info *info)
{
int ret;
--
2.53.0
^ permalink raw reply related [flat|nested] 25+ messages in thread
* Re: [PATCH 05/10] net: turn sk_peer_pid into an array indexed by pid type
2026-08-31 11:21 ` [PATCH 05/10] net: turn sk_peer_pid into an array indexed by pid type Christian Brauner
@ 2026-09-02 0:20 ` Jakub Kicinski
2026-09-04 9:03 ` Christian Brauner
0 siblings, 1 reply; 25+ messages in thread
From: Jakub Kicinski @ 2026-09-02 0:20 UTC (permalink / raw)
To: Christian Brauner
Cc: Kuniyuki Iwashima, Oleg Nesterov, David S. Miller, Eric Dumazet,
Paolo Abeni, Simon Horman, Willem de Bruijn, netdev, linux-kernel,
Alexander Viro, Jan Kara, linux-fsdevel, Alexander Mikhalitsyn
On Mon, 31 Aug 2026 13:21:17 +0200 Christian Brauner wrote:
> Currently only the struct pid of the thread-group leader is recorded
> for a socket's peer. To make room for the struct pid of the thread that
> called connect(), listen() or socketpair() turn sk_peer_pid into an
> array indexed by pid type. All users, including bluetooth and the
> coredump socket, keep using the PIDTYPE_TGID slot.
>
> Nothing fills the PIDTYPE_PID slot yet. No functional changes.
This patch suffers from missing kdoc and general inability to build:
Warning: include/net/sock.h:606 struct member 'PIDTYPE_TGID' not described in 'sock'
Warning: include/net/sock.h:606 Excess struct member 'sk_peer_pid' description in 'sock' (did you mean one of: '#define sk_portpair __sk_common.skc_portpair', 'sk_peer_cred', 'sk_peer_lock', 'sk_peek_off')
include/trace/events/landlock.h:946:13: error: incompatible pointer types assigning to 'struct pid *' from 'struct pid *const volatile *' [-Wincompatible-pointer-types]
What's the merging path for these? Judging by the To: perhaps you
expect us to take these directly?
--
pw-bot: cr
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH 05/10] net: turn sk_peer_pid into an array indexed by pid type
2026-09-02 0:20 ` Jakub Kicinski
@ 2026-09-04 9:03 ` Christian Brauner
2026-09-04 15:38 ` Jakub Kicinski
0 siblings, 1 reply; 25+ messages in thread
From: Christian Brauner @ 2026-09-04 9:03 UTC (permalink / raw)
To: Jakub Kicinski
Cc: Christian Brauner, Kuniyuki Iwashima, Oleg Nesterov,
David S. Miller, Eric Dumazet, Paolo Abeni, Simon Horman,
Willem de Bruijn, netdev, linux-kernel, Alexander Viro, Jan Kara,
linux-fsdevel, Alexander Mikhalitsyn
On 2026-09-01 17:20 -0700, Jakub Kicinski wrote:
> On Mon, 31 Aug 2026 13:21:17 +0200 Christian Brauner wrote:
> > Currently only the struct pid of the thread-group leader is recorded
> > for a socket's peer. To make room for the struct pid of the thread that
> > called connect(), listen() or socketpair() turn sk_peer_pid into an
> > array indexed by pid type. All users, including bluetooth and the
> > coredump socket, keep using the PIDTYPE_TGID slot.
> >
> > Nothing fills the PIDTYPE_PID slot yet. No functional changes.
>
> This patch suffers from missing kdoc and general inability to build:
Oh, damn, the automated pipeline should have done a per-patch build.
Fixing, thanks!
> Warning: include/net/sock.h:606 struct member 'PIDTYPE_TGID' not described in 'sock'
> Warning: include/net/sock.h:606 Excess struct member 'sk_peer_pid' description in 'sock' (did you mean one of: '#define sk_portpair __sk_common.skc_portpair', 'sk_peer_cred', 'sk_peer_lock', 'sk_peek_off')
>
>
> include/trace/events/landlock.h:946:13: error: incompatible pointer types assigning to 'struct pid *' from 'struct pid *const volatile *' [-Wincompatible-pointer-types]
>
>
> What's the merging path for these? Judging by the To: perhaps you
> expect us to take these directly?
If you have no quarrels with me taking it through the vfs tree it would
help because I have other work on top of this. Otherwise a shared branch
also works.
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH 05/10] net: turn sk_peer_pid into an array indexed by pid type
2026-09-04 9:03 ` Christian Brauner
@ 2026-09-04 15:38 ` Jakub Kicinski
0 siblings, 0 replies; 25+ messages in thread
From: Jakub Kicinski @ 2026-09-04 15:38 UTC (permalink / raw)
To: Christian Brauner
Cc: Kuniyuki Iwashima, Oleg Nesterov, David S. Miller, Eric Dumazet,
Paolo Abeni, Simon Horman, Willem de Bruijn, netdev, linux-kernel,
Alexander Viro, Jan Kara, linux-fsdevel, Alexander Mikhalitsyn
On Fri, 04 Sep 2026 11:03:24 +0200 Christian Brauner wrote:
> > Warning: include/net/sock.h:606 struct member 'PIDTYPE_TGID' not described in 'sock'
> > Warning: include/net/sock.h:606 Excess struct member 'sk_peer_pid' description in 'sock' (did you mean one of: '#define sk_portpair __sk_common.skc_portpair', 'sk_peer_cred', 'sk_peer_lock', 'sk_peek_off')
> >
> >
> > include/trace/events/landlock.h:946:13: error: incompatible pointer types assigning to 'struct pid *' from 'struct pid *const volatile *' [-Wincompatible-pointer-types]
> >
> >
> > What's the merging path for these? Judging by the To: perhaps you
> > expect us to take these directly?
>
> If you have no quarrels with me taking it through the vfs tree it would
> help because I have other work on top of this. Otherwise a shared branch
> also works.
Shared branch, please. It's too much code to be sitting in another tree
for the full development cycle
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH 02/10] af_unix: record the pid of the sending thread
2026-08-31 11:21 ` [PATCH 02/10] af_unix: record the pid of the sending thread Christian Brauner
@ 2026-09-07 9:55 ` Alexander Mikhalitsyn
2026-09-09 10:36 ` Christian Brauner
0 siblings, 1 reply; 25+ messages in thread
From: Alexander Mikhalitsyn @ 2026-09-07 9:55 UTC (permalink / raw)
To: Christian Brauner
Cc: Jakub Kicinski, Kuniyuki Iwashima, Oleg Nesterov, David S. Miller,
Eric Dumazet, Paolo Abeni, Simon Horman, Willem de Bruijn, netdev,
linux-kernel, Alexander Viro, Jan Kara, linux-fsdevel
Am Mo., 31. Aug. 2026 um 13:21 Uhr schrieb Christian Brauner
<brauner@kernel.org>:
>
> Currently only the struct pid of the thread-group leader is recorded.
> The identity of the actual thread that sent the message or is connected
> to a given socket cannot be retrieved.
>
> Add the plumbing to make it possible to retrieve a pidfd for the sender.
> Nothing uses the thread-specific struct pid yet. No functional changes.
>
> Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
> ---
> include/net/scm.h | 7 +++----
> net/core/scm.c | 21 +++++++++++++++++----
> net/unix/af_unix.c | 23 +++++++++++++----------
> net/unix/af_unix.h | 3 ++-
> 4 files changed, 35 insertions(+), 19 deletions(-)
>
> diff --git a/include/net/scm.h b/include/net/scm.h
> index 86ae6bc109ec..aa7d15c5fc27 100644
> --- a/include/net/scm.h
> +++ b/include/net/scm.h
> @@ -42,7 +42,7 @@ struct scm_fp_list {
> };
>
> struct scm_cookie {
> - struct pid *pid; /* Skb credentials */
> + DECLARE_PIDS(pid, PIDTYPE_TGID); /* Skb credentials by pid type */
> struct scm_fp_list *fp; /* Passed files */
> struct scm_creds creds; /* Skb credentials */
> #ifdef CONFIG_SECURITY_NETWORK
> @@ -69,7 +69,7 @@ static __inline__ void unix_get_peersec_dgram(struct socket *sock, struct scm_co
> static __inline__ void scm_set_cred(struct scm_cookie *scm,
> struct pid *pid, kuid_t uid, kgid_t gid)
> {
> - scm->pid = get_pid(pid);
> + scm->pid[PIDTYPE_TGID] = get_pid(pid);
> scm->creds.pid = pid_vnr(pid);
> scm->creds.uid = uid;
> scm->creds.gid = gid;
> @@ -77,8 +77,7 @@ static __inline__ void scm_set_cred(struct scm_cookie *scm,
>
> static __inline__ void scm_destroy_cred(struct scm_cookie *scm)
> {
> - put_pid(scm->pid);
> - scm->pid = NULL;
> + put_pids(scm->pid);
> }
>
> static __inline__ void scm_destroy(struct scm_cookie *scm)
> diff --git a/net/core/scm.c b/net/core/scm.c
> index f0d44ecdb11f..9b9e119c353a 100644
> --- a/net/core/scm.c
> +++ b/net/core/scm.c
> @@ -149,6 +149,7 @@ EXPORT_SYMBOL(__scm_destroy);
>
> static inline int scm_replace_pid(struct scm_cookie *scm, struct pid *pid)
> {
> + struct pid *thread_pid;
> int err;
>
> /* drop all previous references */
> @@ -158,7 +159,18 @@ static inline int scm_replace_pid(struct scm_cookie *scm, struct pid *pid)
> if (unlikely(err))
> return err;
>
> - scm->pid = pid;
> + /* A sender naming its own thread-group sends from the current thread. */
> + if (pid == task_tgid(current))
> + thread_pid = task_pid(current);
> + else
> + thread_pid = pid;
Hi Christian,
The patch looks good to me, but this spot raised some doubts for me.
If scm_replace_pid() is called with pid == task_tgid(current) then all
good, because we
get scm->pid[PIDTYPE_TGID] == pid and scm->pid[PIDTYPE_PID] ==
task_pid(current). All good.
If scm_replace_pid() is supplied with pid that has a type PIDTYPE_TGID, then:
- scm->pid[PIDTYPE_TGID] == pid. Good.
- scm->pid[PIDTYPE_PID] = pid. Doesn't looks correct, shoudn't we do
something like:
{
struct task_struct *task;
rcu_read_lock();
task = pid_task(pid, PIDTYPE_TGID);
thread_pid = task_pid(task);
rcu_read_unlock();
}
to get a correct thread_pid for both cases here?
Kind regards,
Alex
> +
> + err = pidfs_register_pid(thread_pid);
> + if (unlikely(err))
> + return err;
> +
> + scm->pid[PIDTYPE_TGID] = pid;
> + scm->pid[PIDTYPE_PID] = get_pid(thread_pid);
> scm->creds.pid = pid_vnr(pid);
> return 0;
> }
> @@ -207,7 +219,8 @@ int __scm_send(struct socket *sock, struct msghdr *msg, struct scm_cookie *p)
> if (err)
> goto error;
>
> - if (!p->pid || pid_vnr(p->pid) != creds.pid) {
> + if (!p->pid[PIDTYPE_TGID] ||
> + pid_vnr(p->pid[PIDTYPE_TGID]) != creds.pid) {
> struct pid *pid;
> err = -ESRCH;
> pid = find_get_pid(creds.pid);
> @@ -504,10 +517,10 @@ static void scm_pidfd_recv(struct msghdr *msg, struct scm_cookie *scm)
> return;
> }
>
> - if (!scm->pid)
> + if (!scm->pid[PIDTYPE_TGID])
> return;
>
> - pidfd = pidfd_prepare(scm->pid, PIDFD_STALE, &pidfd_file);
> + pidfd = pidfd_prepare(scm->pid[PIDTYPE_TGID], PIDFD_STALE, &pidfd_file);
>
> if (put_cmsg(msg, SOL_SOCKET, SCM_PIDFD, sizeof(int), &pidfd)) {
> if (pidfd_file) {
> diff --git a/net/unix/af_unix.c b/net/unix/af_unix.c
> index 13f9926bf205..011af84e3626 100644
> --- a/net/unix/af_unix.c
> +++ b/net/unix/af_unix.c
> @@ -1973,7 +1973,7 @@ static void unix_destruct_scm(struct sk_buff *skb)
> {
> struct scm_cookie scm = {};
>
> - swap(scm.pid, UNIXCB(skb).pid);
> + swap_pids(scm.pid, UNIXCB(skb).pid);
>
> if (UNIXCB(skb).fp)
> unix_detach_fds(&scm, skb);
> @@ -1991,7 +1991,7 @@ static int unix_scm_to_skb(struct scm_cookie *scm, struct sk_buff *skb, bool sen
> {
> int err = 0;
>
> - UNIXCB(skb).pid = get_pid(scm->pid);
> + get_pids(UNIXCB(skb).pid, scm->pid);
> UNIXCB(skb).uid = scm->creds.uid;
> UNIXCB(skb).gid = scm->creds.gid;
> UNIXCB(skb).fp = NULL;
> @@ -2005,7 +2005,10 @@ static int unix_scm_to_skb(struct scm_cookie *scm, struct sk_buff *skb, bool sen
>
> static void unix_skb_to_scm(struct sk_buff *skb, struct scm_cookie *scm)
> {
> - scm_set_cred(scm, UNIXCB(skb).pid, UNIXCB(skb).uid, UNIXCB(skb).gid);
> + get_pids(scm->pid, UNIXCB(skb).pid);
> + scm->creds.pid = pid_vnr(scm->pid[PIDTYPE_TGID]);
> + scm->creds.uid = UNIXCB(skb).uid;
> + scm->creds.gid = UNIXCB(skb).gid;
> unix_set_secdata(scm, skb);
> }
>
> @@ -2025,20 +2028,20 @@ static void unix_skb_to_scm(struct sk_buff *skb, struct scm_cookie *scm)
> static int unix_maybe_add_creds(struct sk_buff *skb, const struct sock *sk,
> const struct sock *other)
> {
> - if (UNIXCB(skb).pid)
> + if (UNIXCB(skb).pid[PIDTYPE_TGID])
> return 0;
>
> if (unix_may_passcred(sk) || unix_may_passcred(other) ||
> !other->sk_socket) {
> - struct pid *pid;
> int err;
>
> - pid = task_tgid(current);
> - err = pidfs_register_pid(pid);
> - if (unlikely(err))
> + get_task_pids(UNIXCB(skb).pid, current);
> + err = pidfs_register_pids(UNIXCB(skb).pid);
> + if (unlikely(err)) {
> + put_pids(UNIXCB(skb).pid);
> return err;
> + }
>
> - UNIXCB(skb).pid = get_pid(pid);
> current_uid_gid(&UNIXCB(skb).uid, &UNIXCB(skb).gid);
> }
>
> @@ -2048,7 +2051,7 @@ static int unix_maybe_add_creds(struct sk_buff *skb, const struct sock *sk,
> static bool unix_skb_scm_eq(struct sk_buff *skb,
> struct scm_cookie *scm)
> {
> - return UNIXCB(skb).pid == scm->pid &&
> + return UNIXCB(skb).pid[PIDTYPE_TGID] == scm->pid[PIDTYPE_TGID] &&
> uid_eq(UNIXCB(skb).uid, scm->creds.uid) &&
> gid_eq(UNIXCB(skb).gid, scm->creds.gid) &&
> unix_secdata_eq(scm, skb);
> diff --git a/net/unix/af_unix.h b/net/unix/af_unix.h
> index 8119dbeef3a3..402742895acc 100644
> --- a/net/unix/af_unix.h
> +++ b/net/unix/af_unix.h
> @@ -2,6 +2,7 @@
> #ifndef __AF_UNIX_H
> #define __AF_UNIX_H
>
> +#include <linux/pid_types.h>
> #include <linux/uidgid.h>
>
> #define UNIX_HASH_MOD (256 - 1)
> @@ -11,7 +12,7 @@
> struct sock *unix_peer_get(struct sock *sk);
>
> struct unix_skb_parms {
> - struct pid *pid; /* skb credentials */
> + DECLARE_PIDS(pid, PIDTYPE_TGID); /* skb credentials by pid type */
> kuid_t uid;
> kgid_t gid;
> struct scm_fp_list *fp; /* Passed files */
>
> --
> 2.53.0
>
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH 03/10] net: add SO_PASSPIDFD_THREAD to get a thread-specific SCM_PIDFD
2026-08-31 11:21 ` [PATCH 03/10] net: add SO_PASSPIDFD_THREAD to get a thread-specific SCM_PIDFD Christian Brauner
@ 2026-09-07 10:02 ` Alexander Mikhalitsyn
0 siblings, 0 replies; 25+ messages in thread
From: Alexander Mikhalitsyn @ 2026-09-07 10:02 UTC (permalink / raw)
To: Christian Brauner
Cc: Jakub Kicinski, Kuniyuki Iwashima, Oleg Nesterov, David S. Miller,
Eric Dumazet, Paolo Abeni, Simon Horman, Willem de Bruijn, netdev,
linux-kernel, Alexander Viro, Jan Kara, linux-fsdevel
Am Mo., 31. Aug. 2026 um 13:21 Uhr schrieb Christian Brauner
<brauner@kernel.org>:
>
> Currently, SCM_PIDFD carries a pidfd for the thread-group leader. A
> broker or the coredump server cannot learn the identity of the specific
> thread that sent a given message. Now that both struct pids are recorded
> a receiver can ask for the specific identity it needs.
>
> So add SO_PASSPIDFD_THREAD as a sibling of SO_PASSPIDFD. Either option
> makes recvmsg() deliver an SCM_PIDFD. SO_PASSPIDFD sends a pidfd for the
> thread-group leader and SO_PASSPIDFD_THREAD sends a pidfd for the
> specific thread.
>
> The two options are mutually exclusive. Enabling one switches the other
> off, so getsockopt() always reports which of the two is active.
>
> On SOCK_STREAM sockets recvmsg() only stops merging data at a thread
> boundary when the receiver asked for a thread pidfd. For SO_PASSCRED and
> SO_PASSPIDFD receivers all threads of one process remain a single
> writer.
>
> Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
LGTM
Reviewed-by: Alexander Mikhalitsyn <aleksandr.mikhalitsyn@futurfusion.io>
> ---
> arch/alpha/include/uapi/asm/socket.h | 2 ++
> arch/mips/include/uapi/asm/socket.h | 2 ++
> arch/parisc/include/uapi/asm/socket.h | 2 ++
> arch/sparc/include/uapi/asm/socket.h | 2 ++
> include/net/sock.h | 10 +++++++++-
> include/uapi/asm-generic/socket.h | 2 ++
> net/core/scm.c | 19 +++++++++++++------
> net/core/sock.c | 26 ++++++++++++++++++++++++--
> net/unix/af_unix.c | 11 ++++++++---
> 9 files changed, 64 insertions(+), 12 deletions(-)
>
> diff --git a/arch/alpha/include/uapi/asm/socket.h b/arch/alpha/include/uapi/asm/socket.h
> index 946a5fad2691..bb3d534826bb 100644
> --- a/arch/alpha/include/uapi/asm/socket.h
> +++ b/arch/alpha/include/uapi/asm/socket.h
> @@ -157,6 +157,8 @@
>
> #define SO_RIGHTS_NOTRUNC 85
>
> +#define SO_PASSPIDFD_THREAD 86
> +
> #if !defined(__KERNEL__)
>
> #if __BITS_PER_LONG == 64
> diff --git a/arch/mips/include/uapi/asm/socket.h b/arch/mips/include/uapi/asm/socket.h
> index f1641dde135f..269badcaa086 100644
> --- a/arch/mips/include/uapi/asm/socket.h
> +++ b/arch/mips/include/uapi/asm/socket.h
> @@ -168,6 +168,8 @@
>
> #define SO_RIGHTS_NOTRUNC 85
>
> +#define SO_PASSPIDFD_THREAD 86
> +
> #if !defined(__KERNEL__)
>
> #if __BITS_PER_LONG == 64
> diff --git a/arch/parisc/include/uapi/asm/socket.h b/arch/parisc/include/uapi/asm/socket.h
> index f3a3815c7dc2..313aee10a52c 100644
> --- a/arch/parisc/include/uapi/asm/socket.h
> +++ b/arch/parisc/include/uapi/asm/socket.h
> @@ -149,6 +149,8 @@
>
> #define SO_RIGHTS_NOTRUNC 0x4053
>
> +#define SO_PASSPIDFD_THREAD 0x4054
> +
> #if !defined(__KERNEL__)
>
> #if __BITS_PER_LONG == 64
> diff --git a/arch/sparc/include/uapi/asm/socket.h b/arch/sparc/include/uapi/asm/socket.h
> index 7907f3b1f0ee..bd3e69bcce7a 100644
> --- a/arch/sparc/include/uapi/asm/socket.h
> +++ b/arch/sparc/include/uapi/asm/socket.h
> @@ -150,6 +150,8 @@
>
> #define SO_RIGHTS_NOTRUNC 0x005e
>
> +#define SO_PASSPIDFD_THREAD 0x005f
> +
> #if !defined(__KERNEL__)
>
>
> diff --git a/include/net/sock.h b/include/net/sock.h
> index 51185222aac2..fc09c92e8a83 100644
> --- a/include/net/sock.h
> +++ b/include/net/sock.h
> @@ -356,6 +356,7 @@ struct sk_filter;
> * @sk_scm_security: flagged by SO_PASSSEC to recv SCM_SECURITY
> * @sk_scm_pidfd: flagged by SO_PASSPIDFD to recv SCM_PIDFD
> * @sk_scm_rights: flagged by SO_PASSRIGHTS to recv SCM_RIGHTS
> + * @sk_scm_pidfd_thread: flagged by SO_PASSPIDFD_THREAD to recv a thread SCM_PIDFD
> * @sk_scm_unused: unused flags for scm_recv()
> * @ns_tracker: tracker for netns reference
> * @sk_user_frags: xarray of pages the user is holding a reference on.
> @@ -562,7 +563,8 @@ struct sock {
> sk_scm_security : 1,
> sk_scm_pidfd : 1,
> sk_scm_rights : 1,
> - sk_scm_unused : 4;
> + sk_scm_pidfd_thread : 1,
> + sk_scm_unused : 3;
> };
> };
> u8 sk_clockid;
> @@ -2986,6 +2988,12 @@ static inline bool sk_is_stream_unix(const struct sock *sk)
> return sk_is_unix(sk) && sk->sk_type == SOCK_STREAM;
> }
>
> +/* SO_PASSPIDFD or SO_PASSPIDFD_THREAD asked for an SCM_PIDFD. */
> +static inline bool sk_scm_pidfd_wanted(const struct sock *sk)
> +{
> + return sk->sk_scm_pidfd || sk->sk_scm_pidfd_thread;
> +}
> +
> static inline bool sk_is_vsock(const struct sock *sk)
> {
> return sk->sk_family == AF_VSOCK;
> diff --git a/include/uapi/asm-generic/socket.h b/include/uapi/asm-generic/socket.h
> index 84ea7b92936e..d1e5c6de146d 100644
> --- a/include/uapi/asm-generic/socket.h
> +++ b/include/uapi/asm-generic/socket.h
> @@ -152,6 +152,8 @@
>
> #define SO_RIGHTS_NOTRUNC 85
>
> +#define SO_PASSPIDFD_THREAD 86
> +
> #if !defined(__KERNEL__)
>
> #if __BITS_PER_LONG == 64 || (defined(__x86_64__) && defined(__ILP32__))
> diff --git a/net/core/scm.c b/net/core/scm.c
> index 9b9e119c353a..d69768414af4 100644
> --- a/net/core/scm.c
> +++ b/net/core/scm.c
> @@ -499,9 +499,13 @@ static bool scm_has_secdata(struct sock *sk)
> }
> #endif
>
> -static void scm_pidfd_recv(struct msghdr *msg, struct scm_cookie *scm)
> +static void scm_pidfd_recv(struct sock *sk, struct msghdr *msg,
> + struct scm_cookie *scm)
> {
> + enum pid_type type = sk->sk_scm_pidfd_thread ? PIDTYPE_PID : PIDTYPE_TGID;
> + struct pid *pid = scm->pid[type];
> struct file *pidfd_file = NULL;
> + unsigned int flags = PIDFD_STALE;
> int len, pidfd;
>
> /* put_cmsg() doesn't return an error if CMSG is truncated,
> @@ -517,10 +521,13 @@ static void scm_pidfd_recv(struct msghdr *msg, struct scm_cookie *scm)
> return;
> }
>
> - if (!scm->pid[PIDTYPE_TGID])
> + if (!pid)
> return;
>
> - pidfd = pidfd_prepare(scm->pid[PIDTYPE_TGID], PIDFD_STALE, &pidfd_file);
> + if (type == PIDTYPE_PID)
> + flags |= PIDFD_THREAD;
> +
> + pidfd = pidfd_prepare(pid, flags, &pidfd_file);
>
> if (put_cmsg(msg, SOL_SOCKET, SCM_PIDFD, sizeof(int), &pidfd)) {
> if (pidfd_file) {
> @@ -539,7 +546,7 @@ static bool __scm_recv_common(struct sock *sk, struct msghdr *msg,
> struct scm_cookie *scm, int flags)
> {
> if (!msg->msg_control) {
> - if (sk->sk_scm_credentials || sk->sk_scm_pidfd ||
> + if (sk->sk_scm_credentials || sk_scm_pidfd_wanted(sk) ||
> scm->fp || scm_has_secdata(sk))
> msg->msg_flags |= MSG_CTRUNC;
>
> @@ -586,8 +593,8 @@ void scm_recv_unix(struct socket *sock, struct msghdr *msg,
> scm_detach_fds(msg, scm, READ_ONCE(u->scm_rights_notrunc));
> }
>
> - if (sock->sk->sk_scm_pidfd)
> - scm_pidfd_recv(msg, scm);
> + if (sk_scm_pidfd_wanted(sock->sk))
> + scm_pidfd_recv(sock->sk, msg, scm);
>
> scm_destroy_cred(scm);
> }
> diff --git a/net/core/sock.c b/net/core/sock.c
> index 1ad41904db25..f9615b0de10e 100644
> --- a/net/core/sock.c
> +++ b/net/core/sock.c
> @@ -1571,10 +1571,25 @@ int sk_setsockopt(struct sock *sk, int level, int optname,
> break;
>
> case SO_PASSPIDFD:
> - if (sk_is_unix(sk))
> + if (sk_is_unix(sk)) {
> + /* Mutually exclusive with SO_PASSPIDFD_THREAD. */
> sk->sk_scm_pidfd = valbool;
> - else
> + if (valbool)
> + sk->sk_scm_pidfd_thread = 0;
> + } else {
> + ret = -EOPNOTSUPP;
> + }
> + break;
> +
> + case SO_PASSPIDFD_THREAD:
> + if (sk_is_unix(sk)) {
> + /* Mutually exclusive with SO_PASSPIDFD. */
> + sk->sk_scm_pidfd_thread = valbool;
> + if (valbool)
> + sk->sk_scm_pidfd = 0;
> + } else {
> ret = -EOPNOTSUPP;
> + }
> break;
>
> case SO_PASSRIGHTS:
> @@ -1892,6 +1907,13 @@ int sk_getsockopt(struct sock *sk, int level, int optname,
> v.val = sk->sk_scm_pidfd;
> break;
>
> + case SO_PASSPIDFD_THREAD:
> + if (!sk_is_unix(sk))
> + return -EOPNOTSUPP;
> +
> + v.val = sk->sk_scm_pidfd_thread;
> + break;
> +
> case SO_PASSRIGHTS:
> if (!sk_is_unix(sk))
> return -EOPNOTSUPP;
> diff --git a/net/unix/af_unix.c b/net/unix/af_unix.c
> index 011af84e3626..468a9c479b87 100644
> --- a/net/unix/af_unix.c
> +++ b/net/unix/af_unix.c
> @@ -803,7 +803,7 @@ static void copy_peercred(struct sock *sk, struct sock *peersk)
>
> static bool unix_may_passcred(const struct sock *sk)
> {
> - return sk->sk_scm_credentials || sk->sk_scm_pidfd;
> + return sk->sk_scm_credentials || sk_scm_pidfd_wanted(sk);
> }
>
> static int unix_listen(struct socket *sock, int backlog)
> @@ -2048,9 +2048,14 @@ static int unix_maybe_add_creds(struct sk_buff *skb, const struct sock *sk,
> return 0;
> }
>
> -static bool unix_skb_scm_eq(struct sk_buff *skb,
> +static bool unix_skb_scm_eq(const struct sock *sk, struct sk_buff *skb,
> struct scm_cookie *scm)
> {
> + /* Only a thread pidfd receiver can tell threads of one process apart. */
> + if (sk->sk_scm_pidfd_thread &&
> + UNIXCB(skb).pid[PIDTYPE_PID] != scm->pid[PIDTYPE_PID])
> + return false;
> +
> return UNIXCB(skb).pid[PIDTYPE_TGID] == scm->pid[PIDTYPE_TGID] &&
> uid_eq(UNIXCB(skb).uid, scm->creds.uid) &&
> gid_eq(UNIXCB(skb).gid, scm->creds.gid) &&
> @@ -3029,7 +3034,7 @@ static int unix_stream_read_generic(struct unix_stream_read_state *state,
>
> if (check_creds) {
> /* Never glue messages from different writers */
> - if (!unix_skb_scm_eq(skb, &scm))
> + if (!unix_skb_scm_eq(sk, skb, &scm))
> break;
> } else if (unix_may_passcred(sk)) {
> /* Copy credentials */
>
> --
> 2.53.0
>
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH 04/10] selftests/net: SO_PASSPIDFD_THREAD
2026-08-31 11:21 ` [PATCH 04/10] selftests/net: SO_PASSPIDFD_THREAD Christian Brauner
@ 2026-09-07 10:04 ` Alexander Mikhalitsyn
0 siblings, 0 replies; 25+ messages in thread
From: Alexander Mikhalitsyn @ 2026-09-07 10:04 UTC (permalink / raw)
To: Christian Brauner
Cc: Jakub Kicinski, Kuniyuki Iwashima, Oleg Nesterov, David S. Miller,
Eric Dumazet, Paolo Abeni, Simon Horman, Willem de Bruijn, netdev,
linux-kernel, Alexander Viro, Jan Kara, linux-fsdevel
Am Mo., 31. Aug. 2026 um 13:21 Uhr schrieb Christian Brauner
<brauner@kernel.org>:
>
> Extend the af_unix scm_pidfd selftest with SO_PASSPIDFD_THREAD tests.
>
> Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
LGTM
Reviewed-by: Alexander Mikhalitsyn <aleksandr.mikhalitsyn@futurfusion.io>
> ---
> tools/testing/selftests/net/af_unix/Makefile | 2 +
> tools/testing/selftests/net/af_unix/scm_pidfd.c | 269 ++++++++++++++++++++++++
> 2 files changed, 271 insertions(+)
>
> diff --git a/tools/testing/selftests/net/af_unix/Makefile b/tools/testing/selftests/net/af_unix/Makefile
> index a66f10fb0c23..45b841758f1b 100644
> --- a/tools/testing/selftests/net/af_unix/Makefile
> +++ b/tools/testing/selftests/net/af_unix/Makefile
> @@ -23,6 +23,8 @@ TEST_GEN_FILES := scm_rights_denial_lsm.bpf.o
> include ../../lib.mk
> include ../bpf.mk
>
> +$(OUTPUT)/scm_pidfd: CFLAGS += -pthread
> +
> $(OUTPUT)/scm_rights_denial_lsm: $(BPFOBJ)
> $(OUTPUT)/scm_rights_denial_lsm: CFLAGS += -I$(SCRATCH_DIR)/include
> $(OUTPUT)/scm_rights_denial_lsm: LDLIBS += -lelf -lz
> diff --git a/tools/testing/selftests/net/af_unix/scm_pidfd.c b/tools/testing/selftests/net/af_unix/scm_pidfd.c
> index 2c18b92a2603..019c48e1cdcd 100644
> --- a/tools/testing/selftests/net/af_unix/scm_pidfd.c
> +++ b/tools/testing/selftests/net/af_unix/scm_pidfd.c
> @@ -10,6 +10,7 @@
> #include <unistd.h>
> #include <string.h>
> #include <errno.h>
> +#include <pthread.h>
> #include <sys/un.h>
> #include <sys/signal.h>
> #include <sys/types.h>
> @@ -27,6 +28,10 @@
> #define SCM_PIDFD 0x04
> #endif
>
> +#ifndef SO_PASSPIDFD_THREAD
> +#define SO_PASSPIDFD_THREAD 86
> +#endif
> +
> #define CHILD_EXIT_CODE_OK 123
>
> static void child_die()
> @@ -553,4 +558,268 @@ TEST_F(scm_pidfd, test)
> close(pfd);
> }
>
> +struct thread_ids {
> + pid_t pid;
> + pid_t tid;
> +};
> +
> +static void *send_ids_thread(void *arg)
> +{
> + int fd = *(int *)arg;
> + struct thread_ids ids = {
> + .pid = getpid(),
> + .tid = gettid(),
> + };
> + char sync;
> +
> + if (send(fd, &ids, sizeof(ids), 0) != sizeof(ids))
> + return (void *)1;
> +
> + /* stay alive until the receiver has looked at our pidfd */
> + if (read(fd, &sync, 1) != 1)
> + return (void *)1;
> +
> + return NULL;
> +}
> +
> +static void *send_ids_creds_thread(void *arg)
> +{
> + int fd = *(int *)arg;
> + struct thread_ids ids = {
> + .pid = getpid(),
> + .tid = gettid(),
> + };
> + struct ucred ucred = {
> + .pid = getpid(),
> + .uid = getuid(),
> + .gid = getgid(),
> + };
> + char control[CMSG_SPACE(sizeof(ucred))] = { 0 };
> + struct iovec iov;
> + struct msghdr msg = { 0 };
> + struct cmsghdr *cmsg;
> + char sync;
> +
> + iov.iov_base = &ids;
> + iov.iov_len = sizeof(ids);
> +
> + msg.msg_iov = &iov;
> + msg.msg_iovlen = 1;
> + msg.msg_control = control;
> + msg.msg_controllen = sizeof(control);
> +
> + cmsg = CMSG_FIRSTHDR(&msg);
> + cmsg->cmsg_level = SOL_SOCKET;
> + cmsg->cmsg_type = SCM_CREDENTIALS;
> + cmsg->cmsg_len = CMSG_LEN(sizeof(ucred));
> + memcpy(CMSG_DATA(cmsg), &ucred, sizeof(ucred));
> +
> + if (sendmsg(fd, &msg, 0) != sizeof(ids))
> + return (void *)1;
> +
> + if (read(fd, &sync, 1) != 1)
> + return (void *)1;
> +
> + return NULL;
> +}
> +
> +static void thread_client(int fd, int syncfd, void *(*sender)(void *))
> +{
> + pthread_t thread;
> + void *ret;
> + char sync;
> +
> + /* wait until the receiver enabled SO_PASSPIDFD */
> + if (read(syncfd, &sync, 1) != 1)
> + child_die();
> +
> + if (pthread_create(&thread, NULL, sender, &fd))
> + child_die();
> +
> + if (pthread_join(thread, &ret) || ret)
> + child_die();
> +
> + exit(0);
> +}
> +
> +static int recv_pidfd(int fd, struct thread_ids *ids)
> +{
> + char control[CMSG_SPACE(sizeof(int))] = { 0 };
> + struct iovec iov;
> + struct msghdr msg = { 0 };
> + struct cmsghdr *cmsg;
> + int pidfd = -1;
> +
> + iov.iov_base = ids;
> + iov.iov_len = sizeof(*ids);
> +
> + msg.msg_iov = &iov;
> + msg.msg_iovlen = 1;
> + msg.msg_control = control;
> + msg.msg_controllen = sizeof(control);
> +
> + if (recvmsg(fd, &msg, 0) != sizeof(*ids)) {
> + log_err("recvmsg");
> + return -1;
> + }
> +
> + if (msg.msg_flags & (MSG_TRUNC | MSG_CTRUNC)) {
> + log_err("recvmsg: truncated");
> + return -1;
> + }
> +
> + for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL;
> + cmsg = CMSG_NXTHDR(&msg, cmsg)) {
> + if (cmsg->cmsg_level == SOL_SOCKET &&
> + cmsg->cmsg_type == SCM_PIDFD)
> + memcpy(&pidfd, CMSG_DATA(cmsg), sizeof(pidfd));
> + }
> +
> + return pidfd;
> +}
> +
> +static int thread_pidfd_flow(void *(*sender)(void *), int optname,
> + struct thread_ids *ids, struct pidfd_info *info)
> +{
> + int sk[2];
> + int syncpipe[2];
> + int pidfd;
> + int child_status = 0;
> + pid_t child;
> +
> + if (socketpair(AF_UNIX, SOCK_STREAM, 0, sk))
> + return -1;
> +
> + if (pipe(syncpipe))
> + return -1;
> +
> + child = fork();
> + if (child < 0)
> + return -1;
> +
> + if (child == 0) {
> + close(sk[0]);
> + close(syncpipe[1]);
> + thread_client(sk[1], syncpipe[0], sender);
> + }
> + close(sk[1]);
> + close(syncpipe[0]);
> +
> + int on = 1;
> +
> + if (setsockopt(sk[0], SOL_SOCKET, optname, &on, sizeof(on))) {
> + log_err("Failed to set pidfd passing option");
> + return -1;
> + }
> +
> + if (write(syncpipe[1], "1", 1) != 1)
> + return -1;
> + close(syncpipe[1]);
> +
> + pidfd = recv_pidfd(sk[0], ids);
> + if (pidfd < 0)
> + return -1;
> +
> + info->mask = PIDFD_INFO_PID;
> + if (ioctl(pidfd, PIDFD_GET_INFO, info)) {
> + log_err("ioctl(PIDFD_GET_INFO)");
> + close(pidfd);
> + return -1;
> + }
> + close(pidfd);
> +
> + /* release the sending thread */
> + if (write(sk[0], "x", 1) != 1)
> + return -1;
> + close(sk[0]);
> +
> + waitpid(child, &child_status, 0);
> + if (!WIFEXITED(child_status) || WEXITSTATUS(child_status))
> + return -1;
> +
> + return 0;
> +}
> +
> +static int sockopt_set(int fd, int optname, int val)
> +{
> + return setsockopt(fd, SOL_SOCKET, optname, &val, sizeof(val));
> +}
> +
> +static int sockopt_get(int fd, int optname)
> +{
> + socklen_t len = sizeof(int);
> + int val = -1;
> +
> + if (getsockopt(fd, SOL_SOCKET, optname, &val, &len))
> + return -1;
> +
> + return val;
> +}
> +
> +TEST(scm_pidfd_setsockopt_values)
> +{
> + int sk[2];
> +
> + ASSERT_EQ(0, socketpair(AF_UNIX, SOCK_STREAM, 0, sk));
> +
> + ASSERT_EQ(0, sockopt_set(sk[0], SO_PASSPIDFD_THREAD, 1));
> + ASSERT_EQ(1, sockopt_get(sk[0], SO_PASSPIDFD_THREAD));
> + ASSERT_EQ(0, sockopt_get(sk[0], SO_PASSPIDFD));
> +
> + /* The option set last wins. */
> + ASSERT_EQ(0, sockopt_set(sk[0], SO_PASSPIDFD, 1));
> + ASSERT_EQ(1, sockopt_get(sk[0], SO_PASSPIDFD));
> + ASSERT_EQ(0, sockopt_get(sk[0], SO_PASSPIDFD_THREAD));
> +
> + ASSERT_EQ(0, sockopt_set(sk[0], SO_PASSPIDFD_THREAD, 1));
> + ASSERT_EQ(1, sockopt_get(sk[0], SO_PASSPIDFD_THREAD));
> + ASSERT_EQ(0, sockopt_get(sk[0], SO_PASSPIDFD));
> +
> + /* Disabling one option leaves the other alone. */
> + ASSERT_EQ(0, sockopt_set(sk[0], SO_PASSPIDFD, 0));
> + ASSERT_EQ(1, sockopt_get(sk[0], SO_PASSPIDFD_THREAD));
> + ASSERT_EQ(0, sockopt_set(sk[0], SO_PASSPIDFD_THREAD, 0));
> + ASSERT_EQ(0, sockopt_get(sk[0], SO_PASSPIDFD));
> + ASSERT_EQ(0, sockopt_get(sk[0], SO_PASSPIDFD_THREAD));
> +
> + close(sk[0]);
> + close(sk[1]);
> +}
> +
> +TEST(scm_pidfd_thread)
> +{
> + struct thread_ids ids;
> + struct pidfd_info info;
> +
> + ASSERT_EQ(0, thread_pidfd_flow(send_ids_thread, SO_PASSPIDFD_THREAD,
> + &ids, &info));
> + ASSERT_NE(ids.pid, ids.tid);
> + EXPECT_EQ(ids.tid, info.pid);
> + EXPECT_EQ(ids.pid, info.tgid);
> +}
> +
> +TEST(scm_pidfd_thread_group)
> +{
> + struct thread_ids ids;
> + struct pidfd_info info;
> +
> + ASSERT_EQ(0, thread_pidfd_flow(send_ids_thread, SO_PASSPIDFD,
> + &ids, &info));
> + ASSERT_NE(ids.pid, ids.tid);
> + EXPECT_EQ(ids.pid, info.pid);
> + EXPECT_EQ(ids.pid, info.tgid);
> +}
> +
> +TEST(scm_pidfd_thread_creds)
> +{
> + struct thread_ids ids;
> + struct pidfd_info info;
> +
> + ASSERT_EQ(0, thread_pidfd_flow(send_ids_creds_thread, SO_PASSPIDFD_THREAD,
> + &ids, &info));
> + ASSERT_NE(ids.pid, ids.tid);
> + EXPECT_EQ(ids.tid, info.pid);
> + EXPECT_EQ(ids.pid, info.tgid);
> +}
> +
> TEST_HARNESS_MAIN
>
> --
> 2.53.0
>
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH 01/10] pid: add helpers to operate on a struct pid array
2026-08-31 11:21 ` [PATCH 01/10] pid: add helpers to operate on a struct pid array Christian Brauner
@ 2026-09-07 10:07 ` Alexander Mikhalitsyn
0 siblings, 0 replies; 25+ messages in thread
From: Alexander Mikhalitsyn @ 2026-09-07 10:07 UTC (permalink / raw)
To: Christian Brauner
Cc: Jakub Kicinski, Kuniyuki Iwashima, Oleg Nesterov, David S. Miller,
Eric Dumazet, Paolo Abeni, Simon Horman, Willem de Bruijn, netdev,
linux-kernel, Alexander Viro, Jan Kara, linux-fsdevel
Am Mo., 31. Aug. 2026 um 13:21 Uhr schrieb Christian Brauner
<brauner@kernel.org>:
>
> We're about to extend af_unix sockets and coredump code with the ability
> to hand out thread-specific pidfds. Add a few simple helpers that allow
> to operate on multiple struct pids up to PIDTYPE_MAX with automatic
> bounds checking.
>
> Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
LGTM
Reviewed-by: Alexander Mikhalitsyn <aleksandr.mikhalitsyn@futurfusion.io>
> ---
> fs/pidfs.c | 16 +++++++++++++
> include/linux/pid.h | 53 ++++++++++++++++++++++++++++++++++++++++++++
> include/linux/pid_types.h | 8 +++++++
> include/linux/pidfs.h | 5 +++++
> include/linux/sched/signal.h | 18 +++++++++++++++
> 5 files changed, 100 insertions(+)
>
> diff --git a/fs/pidfs.c b/fs/pidfs.c
> index a6a643f15d08..586af2e5811c 100644
> --- a/fs/pidfs.c
> +++ b/fs/pidfs.c
> @@ -1070,6 +1070,22 @@ int pidfs_register_pid_gfp(struct pid *pid, gfp_t gfp)
> return 0;
> }
>
> +/* Register the pids up to pid type @last of @pids in pidfs. */
> +int __pidfs_register_pids(struct pid *const *pids, enum pid_type last)
> +{
> + if (WARN_ON_ONCE(last >= PIDTYPE_MAX))
> + return -EINVAL;
> +
> + for (enum pid_type type = PIDTYPE_PID; type <= last; type++) {
> + int ret = pidfs_register_pid(pids[type]);
> +
> + if (unlikely(ret))
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> static struct dentry *pidfs_stash_dentry(struct dentry **stashed,
> struct dentry *dentry)
> {
> diff --git a/include/linux/pid.h b/include/linux/pid.h
> index ddaef0bbc8ba..87635d0cc1f7 100644
> --- a/include/linux/pid.h
> +++ b/include/linux/pid.h
> @@ -2,6 +2,9 @@
> #ifndef _LINUX_PID_H
> #define _LINUX_PID_H
>
> +#include <linux/array_size.h>
> +#include <linux/build_bug.h>
> +#include <linux/minmax.h>
> #include <linux/pid_types.h>
> #include <linux/rculist.h>
> #include <linux/rcupdate.h>
> @@ -92,6 +95,56 @@ static inline struct pid *get_pid(struct pid *pid)
> }
>
> extern void put_pid(struct pid *pid);
> +
> +/*
> + * Helpers for arrays of struct pid indexed by pid type declared with
> + * DECLARE_PIDS(). The array covers PIDTYPE_PID up to the pid type it
> + * was declared with and the helpers take that bound from the array.
> + */
> +static inline void __get_pids(struct pid **dst, struct pid *const *src,
> + enum pid_type last)
> +{
> + for (enum pid_type type = PIDTYPE_PID; type <= last; type++)
> + dst[type] = get_pid(src[type]);
> +}
> +
> +static inline void __put_pids(struct pid **pids, enum pid_type last)
> +{
> + for (enum pid_type type = PIDTYPE_PID; type <= last; type++) {
> + put_pid(pids[type]);
> + pids[type] = NULL;
> + }
> +}
> +
> +static inline void __swap_pids(struct pid **a, struct pid **b,
> + enum pid_type last)
> +{
> + for (enum pid_type type = PIDTYPE_PID; type <= last; type++)
> + swap(a[type], b[type]);
> +}
> +
> +static inline bool __pids_equal(struct pid *const *a, struct pid *const *b,
> + enum pid_type last)
> +{
> + for (enum pid_type type = PIDTYPE_PID; type <= last; type++)
> + if (a[type] != b[type])
> + return false;
> + return true;
> +}
> +
> +/* The last pid type an array declared with DECLARE_PIDS() covers. */
> +#define pids_last(pids) \
> + ((enum pid_type)(ARRAY_SIZE(pids) - 1 + \
> + BUILD_BUG_ON_ZERO(ARRAY_SIZE(pids) > PIDTYPE_MAX)))
> +
> +#define __pids_last2(a, b) \
> + (pids_last(a) + BUILD_BUG_ON_ZERO(ARRAY_SIZE(a) != ARRAY_SIZE(b)))
> +
> +#define get_pids(dst, src) __get_pids(dst, src, __pids_last2(dst, src))
> +#define put_pids(pids) __put_pids(pids, pids_last(pids))
> +#define swap_pids(a, b) __swap_pids(a, b, __pids_last2(a, b))
> +#define pids_equal(a, b) __pids_equal(a, b, __pids_last2(a, b))
> +
> extern struct task_struct *pid_task(struct pid *pid, enum pid_type);
> static inline bool pid_has_task(struct pid *pid, enum pid_type type)
> {
> diff --git a/include/linux/pid_types.h b/include/linux/pid_types.h
> index c2aee1d91dcf..3302690a2a28 100644
> --- a/include/linux/pid_types.h
> +++ b/include/linux/pid_types.h
> @@ -2,6 +2,8 @@
> #ifndef _LINUX_PID_TYPES_H
> #define _LINUX_PID_TYPES_H
>
> +#include <linux/build_bug.h>
> +
> enum pid_type {
> PIDTYPE_PID,
> PIDTYPE_TGID,
> @@ -10,6 +12,12 @@ enum pid_type {
> PIDTYPE_MAX,
> };
>
> +struct pid;
> +
> +/* An array of struct pid indexed by pid type, PIDTYPE_PID up to @last. */
> +#define DECLARE_PIDS(name, last) \
> + struct pid *name[(last) + 1 + BUILD_BUG_ON_ZERO((last) >= PIDTYPE_MAX)]
> +
> struct pid_namespace;
> extern struct pid_namespace init_pid_ns;
>
> diff --git a/include/linux/pidfs.h b/include/linux/pidfs.h
> index 0abf7da9ab23..3c1e82f1a369 100644
> --- a/include/linux/pidfs.h
> +++ b/include/linux/pidfs.h
> @@ -3,6 +3,7 @@
> #define _LINUX_PID_FS_H
>
> #include <linux/gfp_types.h>
> +#include <linux/pid.h>
>
> struct coredump_params;
>
> @@ -32,5 +33,9 @@ static inline int pidfs_register_pid(struct pid *pid)
> }
>
> void pidfs_free_pid(struct pid *pid);
> +int __pidfs_register_pids(struct pid *const *pids, enum pid_type last);
> +
> +/* Register the pids of an array declared with DECLARE_PIDS(). */
> +#define pidfs_register_pids(pids) __pidfs_register_pids(pids, pids_last(pids))
>
> #endif /* _LINUX_PID_FS_H */
> diff --git a/include/linux/sched/signal.h b/include/linux/sched/signal.h
> index 584ae88b435e..9444b47789a0 100644
> --- a/include/linux/sched/signal.h
> +++ b/include/linux/sched/signal.h
> @@ -677,6 +677,24 @@ struct pid *task_pid_type(struct task_struct *task, enum pid_type type)
> return pid;
> }
>
> +/* Fill @pids with the pid types of @task up to @last, without references. */
> +static inline void __task_pids(struct pid **pids, enum pid_type last,
> + struct task_struct *task)
> +{
> + for (enum pid_type type = PIDTYPE_PID; type <= last; type++)
> + pids[type] = task_pid_type(task, type);
> +}
> +
> +static inline void __get_task_pids(struct pid **pids, enum pid_type last,
> + struct task_struct *task)
> +{
> + for (enum pid_type type = PIDTYPE_PID; type <= last; type++)
> + pids[type] = get_pid(task_pid_type(task, type));
> +}
> +
> +#define task_pids(pids, task) __task_pids(pids, pids_last(pids), task)
> +#define get_task_pids(pids, task) __get_task_pids(pids, pids_last(pids), task)
> +
> static inline struct pid *task_tgid(struct task_struct *task)
> {
> return task->signal->pids[PIDTYPE_TGID];
>
> --
> 2.53.0
>
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH 06/10] af_unix: record the pid of the connecting thread
2026-08-31 11:21 ` [PATCH 06/10] af_unix: record the pid of the connecting thread Christian Brauner
@ 2026-09-07 10:29 ` Alexander Mikhalitsyn
0 siblings, 0 replies; 25+ messages in thread
From: Alexander Mikhalitsyn @ 2026-09-07 10:29 UTC (permalink / raw)
To: Christian Brauner
Cc: Jakub Kicinski, Kuniyuki Iwashima, Oleg Nesterov, David S. Miller,
Eric Dumazet, Paolo Abeni, Simon Horman, Willem de Bruijn, netdev,
linux-kernel, Alexander Viro, Jan Kara, linux-fsdevel
Am Mo., 31. Aug. 2026 um 13:21 Uhr schrieb Christian Brauner
<brauner@kernel.org>:
>
> Currently only the struct pid of the thread-group leader is recorded.
> The identity of the actual thread that connected to a given socket
> cannot be retrieved.
>
> Add the plumbing to make it possible to retrieve a pidfd for the
> connecting thread. Nothing uses the thread-specific struct pid yet. No
> functional changes.
>
> Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
LGTM
Reviewed-by: Alexander Mikhalitsyn <aleksandr.mikhalitsyn@futurfusion.io>
> ---
> net/unix/af_unix.c | 30 +++++++++++++-----------------
> 1 file changed, 13 insertions(+), 17 deletions(-)
>
> diff --git a/net/unix/af_unix.c b/net/unix/af_unix.c
> index 335abd23c9bf..d01ee76c8026 100644
> --- a/net/unix/af_unix.c
> +++ b/net/unix/af_unix.c
> @@ -743,51 +743,47 @@ struct unix_peercred {
>
> static inline int prepare_peercred(struct unix_peercred *peercred)
> {
> - struct pid *pid;
> int err;
>
> - pid = task_tgid(current);
> - err = pidfs_register_pid(pid);
> - if (likely(!err)) {
> - peercred->peer_pid[PIDTYPE_TGID] = get_pid(pid);
> - peercred->peer_cred = get_current_cred();
> + get_task_pids(peercred->peer_pid, current);
> + err = pidfs_register_pids(peercred->peer_pid);
> + if (unlikely(err)) {
> + put_pids(peercred->peer_pid);
> + return err;
> }
> - return err;
> +
> + peercred->peer_cred = get_current_cred();
> + return 0;
> }
>
> static void drop_peercred(struct unix_peercred *peercred)
> {
> const struct cred *cred = NULL;
> - struct pid *pid = NULL;
>
> might_sleep();
>
> - swap(peercred->peer_pid[PIDTYPE_TGID], pid);
> + put_pids(peercred->peer_pid);
> swap(peercred->peer_cred, cred);
> -
> - put_pid(pid);
> put_cred(cred);
> }
>
> static inline void init_peercred(struct sock *sk,
> const struct unix_peercred *peercred)
> {
> - sk->sk_peer_pid[PIDTYPE_TGID] = peercred->peer_pid[PIDTYPE_TGID];
> + memcpy(sk->sk_peer_pid, peercred->peer_pid, sizeof(sk->sk_peer_pid));
> sk->sk_peer_cred = peercred->peer_cred;
> }
>
> static void update_peercred(struct sock *sk, struct unix_peercred *peercred)
> {
> const struct cred *old_cred;
> - struct pid *old_pid;
>
> spin_lock(&sk->sk_peer_lock);
> - old_pid = sk->sk_peer_pid[PIDTYPE_TGID];
> + swap_pids(sk->sk_peer_pid, peercred->peer_pid);
> old_cred = sk->sk_peer_cred;
> - init_peercred(sk, peercred);
> + sk->sk_peer_cred = peercred->peer_cred;
> spin_unlock(&sk->sk_peer_lock);
>
> - peercred->peer_pid[PIDTYPE_TGID] = old_pid;
> peercred->peer_cred = old_cred;
> }
>
> @@ -796,7 +792,7 @@ static void copy_peercred(struct sock *sk, struct sock *peersk)
> lockdep_assert_held(&unix_sk(peersk)->lock);
>
> spin_lock(&sk->sk_peer_lock);
> - sk->sk_peer_pid[PIDTYPE_TGID] = get_pid(peersk->sk_peer_pid[PIDTYPE_TGID]);
> + get_pids(sk->sk_peer_pid, peersk->sk_peer_pid);
> sk->sk_peer_cred = get_cred(peersk->sk_peer_cred);
> spin_unlock(&sk->sk_peer_lock);
> }
>
> --
> 2.53.0
>
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH 07/10] net: add SO_PEERPIDFD_THREAD to get a thread-specific pidfd
2026-08-31 11:21 ` [PATCH 07/10] net: add SO_PEERPIDFD_THREAD to get a thread-specific pidfd Christian Brauner
@ 2026-09-07 10:49 ` Alexander Mikhalitsyn
0 siblings, 0 replies; 25+ messages in thread
From: Alexander Mikhalitsyn @ 2026-09-07 10:49 UTC (permalink / raw)
To: Christian Brauner
Cc: Jakub Kicinski, Kuniyuki Iwashima, Oleg Nesterov, David S. Miller,
Eric Dumazet, Paolo Abeni, Simon Horman, Willem de Bruijn, netdev,
linux-kernel, Alexander Viro, Jan Kara, linux-fsdevel
Am Mo., 31. Aug. 2026 um 13:21 Uhr schrieb Christian Brauner
<brauner@kernel.org>:
>
> SO_PEERPIDFD hands out a pidfd for the thread-group that called
> connect() or socketpair(). Enable workloads such as the coredump server
> or a broker to get a pidfd of the specific thread that connected to the
> socket.
>
> Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
LGTM
Reviewed-by: Alexander Mikhalitsyn <aleksandr.mikhalitsyn@futurfusion.io>
> ---
> arch/alpha/include/uapi/asm/socket.h | 2 +
> arch/mips/include/uapi/asm/socket.h | 2 +
> arch/parisc/include/uapi/asm/socket.h | 2 +
> arch/sparc/include/uapi/asm/socket.h | 2 +
> include/uapi/asm-generic/socket.h | 2 +
> net/core/sock.c | 87 ++++++++++++++++++++---------------
> net/unix/af_unix.c | 1 +
> 7 files changed, 61 insertions(+), 37 deletions(-)
>
> diff --git a/arch/alpha/include/uapi/asm/socket.h b/arch/alpha/include/uapi/asm/socket.h
> index bb3d534826bb..5d3524c26b2b 100644
> --- a/arch/alpha/include/uapi/asm/socket.h
> +++ b/arch/alpha/include/uapi/asm/socket.h
> @@ -159,6 +159,8 @@
>
> #define SO_PASSPIDFD_THREAD 86
>
> +#define SO_PEERPIDFD_THREAD 87
> +
> #if !defined(__KERNEL__)
>
> #if __BITS_PER_LONG == 64
> diff --git a/arch/mips/include/uapi/asm/socket.h b/arch/mips/include/uapi/asm/socket.h
> index 269badcaa086..245a43f52fb1 100644
> --- a/arch/mips/include/uapi/asm/socket.h
> +++ b/arch/mips/include/uapi/asm/socket.h
> @@ -170,6 +170,8 @@
>
> #define SO_PASSPIDFD_THREAD 86
>
> +#define SO_PEERPIDFD_THREAD 87
> +
> #if !defined(__KERNEL__)
>
> #if __BITS_PER_LONG == 64
> diff --git a/arch/parisc/include/uapi/asm/socket.h b/arch/parisc/include/uapi/asm/socket.h
> index 313aee10a52c..f23710e1c671 100644
> --- a/arch/parisc/include/uapi/asm/socket.h
> +++ b/arch/parisc/include/uapi/asm/socket.h
> @@ -151,6 +151,8 @@
>
> #define SO_PASSPIDFD_THREAD 0x4054
>
> +#define SO_PEERPIDFD_THREAD 0x4055
> +
> #if !defined(__KERNEL__)
>
> #if __BITS_PER_LONG == 64
> diff --git a/arch/sparc/include/uapi/asm/socket.h b/arch/sparc/include/uapi/asm/socket.h
> index bd3e69bcce7a..b35b25bdefc2 100644
> --- a/arch/sparc/include/uapi/asm/socket.h
> +++ b/arch/sparc/include/uapi/asm/socket.h
> @@ -152,6 +152,8 @@
>
> #define SO_PASSPIDFD_THREAD 0x005f
>
> +#define SO_PEERPIDFD_THREAD 0x0060
> +
> #if !defined(__KERNEL__)
>
>
> diff --git a/include/uapi/asm-generic/socket.h b/include/uapi/asm-generic/socket.h
> index d1e5c6de146d..56fed7ab27ab 100644
> --- a/include/uapi/asm-generic/socket.h
> +++ b/include/uapi/asm-generic/socket.h
> @@ -154,6 +154,8 @@
>
> #define SO_PASSPIDFD_THREAD 86
>
> +#define SO_PEERPIDFD_THREAD 87
> +
> #if !defined(__KERNEL__)
>
> #if __BITS_PER_LONG == 64 || (defined(__x86_64__) && defined(__ILP32__))
> diff --git a/net/core/sock.c b/net/core/sock.c
> index 6ada7e7eb7d7..cb2ffd329bc6 100644
> --- a/net/core/sock.c
> +++ b/net/core/sock.c
> @@ -1743,6 +1743,50 @@ static int groups_to_user(sockptr_t dst, const struct group_info *src)
> return 0;
> }
>
> +/* Hand out a pidfd for @type of the socket's peer via SO_PEERPIDFD*. */
> +static int sk_getsockopt_peerpidfd(struct sock *sk, enum pid_type type,
> + sockptr_t optval, sockptr_t optlen, int len)
> +{
> + struct file *pidfd_file = NULL;
> + unsigned int flags = 0;
> + struct pid *peer_pid;
> + int pidfd;
> +
> + if (len > sizeof(pidfd))
> + len = sizeof(pidfd);
> +
> + spin_lock(&sk->sk_peer_lock);
> + peer_pid = get_pid(sk->sk_peer_pid[type]);
> + spin_unlock(&sk->sk_peer_lock);
> +
> + if (!peer_pid)
> + return -ENODATA;
> +
> + /* The use of PIDFD_STALE requires stashing of struct pid on pidfs
> + * with pidfs_register_pid() and only AF_UNIX were prepared for this.
> + */
> + if (sk->sk_family == AF_UNIX)
> + flags |= PIDFD_STALE;
> + if (type == PIDTYPE_PID)
> + flags |= PIDFD_THREAD;
> +
> + pidfd = pidfd_prepare(peer_pid, flags, &pidfd_file);
> + put_pid(peer_pid);
> + if (pidfd < 0)
> + return pidfd;
> +
> + if (copy_to_sockptr(optval, &pidfd, len) ||
> + copy_to_sockptr(optlen, &len, sizeof(int))) {
> + put_unused_fd(pidfd);
> + fput(pidfd_file);
> +
> + return -EFAULT;
> + }
> +
> + fd_install(pidfd, pidfd_file);
> + return 0;
> +}
> +
> int sk_getsockopt(struct sock *sk, int level, int optname,
> sockptr_t optval, sockptr_t optlen)
> {
> @@ -1938,45 +1982,14 @@ int sk_getsockopt(struct sock *sk, int level, int optname,
> }
>
> case SO_PEERPIDFD:
> - {
> - struct pid *peer_pid;
> - struct file *pidfd_file = NULL;
> - unsigned int flags = 0;
> - int pidfd;
> -
> - if (len > sizeof(pidfd))
> - len = sizeof(pidfd);
> -
> - spin_lock(&sk->sk_peer_lock);
> - peer_pid = get_pid(sk->sk_peer_pid[PIDTYPE_TGID]);
> - spin_unlock(&sk->sk_peer_lock);
> -
> - if (!peer_pid)
> - return -ENODATA;
> -
> - /* The use of PIDFD_STALE requires stashing of struct pid
> - * on pidfs with pidfs_register_pid() and only AF_UNIX
> - * were prepared for this.
> - */
> - if (sk->sk_family == AF_UNIX)
> - flags = PIDFD_STALE;
> + return sk_getsockopt_peerpidfd(sk, PIDTYPE_TGID, optval, optlen, len);
>
> - pidfd = pidfd_prepare(peer_pid, flags, &pidfd_file);
> - put_pid(peer_pid);
> - if (pidfd < 0)
> - return pidfd;
> -
> - if (copy_to_sockptr(optval, &pidfd, len) ||
> - copy_to_sockptr(optlen, &len, sizeof(int))) {
> - put_unused_fd(pidfd);
> - fput(pidfd_file);
> -
> - return -EFAULT;
> - }
> + case SO_PEERPIDFD_THREAD:
> + /* Only AF_UNIX records the peer's connecting thread. */
> + if (!sk_is_unix(sk))
> + return -EOPNOTSUPP;
>
> - fd_install(pidfd, pidfd_file);
> - return 0;
> - }
> + return sk_getsockopt_peerpidfd(sk, PIDTYPE_PID, optval, optlen, len);
>
> case SO_PEERGROUPS:
> {
> diff --git a/net/unix/af_unix.c b/net/unix/af_unix.c
> index d01ee76c8026..563d9827c5cc 100644
> --- a/net/unix/af_unix.c
> +++ b/net/unix/af_unix.c
> @@ -1056,6 +1056,7 @@ static bool unix_bpf_bypass_getsockopt(int level, int optname)
> if (level == SOL_SOCKET) {
> switch (optname) {
> case SO_PEERPIDFD:
> + case SO_PEERPIDFD_THREAD:
> return true;
> default:
> return false;
>
> --
> 2.53.0
>
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH 08/10] selftests/net: SO_PEERPIDFD_THREAD
2026-08-31 11:21 ` [PATCH 08/10] selftests/net: SO_PEERPIDFD_THREAD Christian Brauner
@ 2026-09-07 10:51 ` Alexander Mikhalitsyn
0 siblings, 0 replies; 25+ messages in thread
From: Alexander Mikhalitsyn @ 2026-09-07 10:51 UTC (permalink / raw)
To: Christian Brauner
Cc: Jakub Kicinski, Kuniyuki Iwashima, Oleg Nesterov, David S. Miller,
Eric Dumazet, Paolo Abeni, Simon Horman, Willem de Bruijn, netdev,
linux-kernel, Alexander Viro, Jan Kara, linux-fsdevel
Am Mo., 31. Aug. 2026 um 13:21 Uhr schrieb Christian Brauner
<brauner@kernel.org>:
>
> Add tests for SO_PEERPIDFD_THREAD.
>
> Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
LGTM
Reviewed-by: Alexander Mikhalitsyn <aleksandr.mikhalitsyn@futurfusion.io>
> ---
> tools/testing/selftests/net/af_unix/scm_pidfd.c | 97 +++++++++++++++++++++++++
> 1 file changed, 97 insertions(+)
>
> diff --git a/tools/testing/selftests/net/af_unix/scm_pidfd.c b/tools/testing/selftests/net/af_unix/scm_pidfd.c
> index 019c48e1cdcd..bbbd5e7b4fa7 100644
> --- a/tools/testing/selftests/net/af_unix/scm_pidfd.c
> +++ b/tools/testing/selftests/net/af_unix/scm_pidfd.c
> @@ -32,6 +32,10 @@
> #define SO_PASSPIDFD_THREAD 86
> #endif
>
> +#ifndef SO_PEERPIDFD_THREAD
> +#define SO_PEERPIDFD_THREAD 87
> +#endif
> +
> #define CHILD_EXIT_CODE_OK 123
>
> static void child_die()
> @@ -822,4 +826,97 @@ TEST(scm_pidfd_thread_creds)
> EXPECT_EQ(ids.pid, info.tgid);
> }
>
> +static void *peer_connect_thread(void *arg)
> +{
> + struct sock_addr *sa = arg;
> + struct thread_ids ids = {
> + .pid = getpid(),
> + .tid = gettid(),
> + };
> + int fd;
> + char sync;
> +
> + fd = socket(AF_UNIX, SOCK_STREAM, 0);
> + if (fd < 0)
> + return (void *)1;
> +
> + if (connect(fd, (struct sockaddr *)&sa->listen_addr, sa->addrlen))
> + return (void *)1;
> +
> + if (send(fd, &ids, sizeof(ids), 0) != sizeof(ids))
> + return (void *)1;
> +
> + /* stay alive until the server has looked at our pidfd */
> + if (read(fd, &sync, 1) != 1)
> + return (void *)1;
> +
> + close(fd);
> + return NULL;
> +}
> +
> +static int peer_pidfd_info(int fd, int optname, struct pidfd_info *info)
> +{
> + int pidfd;
> + socklen_t len = sizeof(pidfd);
> +
> + if (getsockopt(fd, SOL_SOCKET, optname, &pidfd, &len)) {
> + log_err("getsockopt(SO_PEERPIDFD*)");
> + return -1;
> + }
> +
> + info->mask = PIDFD_INFO_PID;
> + if (ioctl(pidfd, PIDFD_GET_INFO, info)) {
> + log_err("ioctl(PIDFD_GET_INFO)");
> + close(pidfd);
> + return -1;
> + }
> +
> + close(pidfd);
> + return 0;
> +}
> +
> +/* SO_PEERPIDFD_THREAD returns a pidfd for the peer's connecting thread. */
> +TEST(so_peerpidfd_thread)
> +{
> + struct sock_addr sa;
> + struct thread_ids ids;
> + struct pidfd_info info;
> + pthread_t thread;
> + void *tret;
> + int server, cfd;
> +
> + server = socket(AF_UNIX, SOCK_STREAM, 0);
> + ASSERT_LE(0, server);
> +
> + fill_sockaddr(&sa, true);
> + ASSERT_EQ(0, bind(server, (struct sockaddr *)&sa.listen_addr, sa.addrlen));
> + ASSERT_EQ(0, listen(server, 1));
> +
> + ASSERT_EQ(0, pthread_create(&thread, NULL, peer_connect_thread, &sa));
> +
> + cfd = accept(server, NULL, NULL);
> + ASSERT_LE(0, cfd);
> +
> + ASSERT_EQ(sizeof(ids), recv(cfd, &ids, sizeof(ids), MSG_WAITALL));
> + ASSERT_NE(ids.pid, ids.tid);
> +
> + /* SO_PEERPIDFD refers to the peer's thread-group. */
> + ASSERT_EQ(0, peer_pidfd_info(cfd, SO_PEERPIDFD, &info));
> + EXPECT_EQ(ids.pid, info.pid);
> + EXPECT_EQ(ids.pid, info.tgid);
> +
> + /* SO_PEERPIDFD_THREAD refers to the connecting thread. */
> + ASSERT_EQ(0, peer_pidfd_info(cfd, SO_PEERPIDFD_THREAD, &info));
> + EXPECT_EQ(ids.tid, info.pid);
> + EXPECT_EQ(ids.pid, info.tgid);
> +
> + /* release the connecting thread */
> + ASSERT_EQ(1, write(cfd, "x", 1));
> + ASSERT_EQ(0, pthread_join(thread, &tret));
> + ASSERT_EQ(NULL, tret);
> +
> + close(cfd);
> + close(server);
> +}
> +
> TEST_HARNESS_MAIN
>
> --
> 2.53.0
>
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH 09/10] pidfs: record the coredump on the dumping thread's pid too
2026-08-31 11:21 ` [PATCH 09/10] pidfs: record the coredump on the dumping thread's pid too Christian Brauner
@ 2026-09-07 10:59 ` Alexander Mikhalitsyn
0 siblings, 0 replies; 25+ messages in thread
From: Alexander Mikhalitsyn @ 2026-09-07 10:59 UTC (permalink / raw)
To: Christian Brauner
Cc: Jakub Kicinski, Kuniyuki Iwashima, Oleg Nesterov, David S. Miller,
Eric Dumazet, Paolo Abeni, Simon Horman, Willem de Bruijn, netdev,
linux-kernel, Alexander Viro, Jan Kara, linux-fsdevel
Am Mo., 31. Aug. 2026 um 13:22 Uhr schrieb Christian Brauner
<brauner@kernel.org>:
>
> If a thread-group coredumps only the thread-group leader pidfd will
> return coredump information. A pidfd for the thread that took the fatal
> signal cannot be used to retrieve it.
>
> Record both the thread-group leader and the specific thread that took
> the signal and register both in pidfs. Mark both the thread-group leader
> and the specific thread with the coredump information so retrieval works
> for both pidfds.
>
> Now that both SO_PEERPIDFD and SO_PEERPIDFD_THREAD are available it's
> easy to get the coredump information for the specific thread.
>
> Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
LGTM
Reviewed-by: Alexander Mikhalitsyn <aleksandr.mikhalitsyn@futurfusion.io>
> ---
> fs/coredump.c | 22 +++++++++++++---------
> fs/pidfs.c | 11 +++++++++--
> include/linux/coredump.h | 4 +++-
> 3 files changed, 25 insertions(+), 12 deletions(-)
>
> diff --git a/fs/coredump.c b/fs/coredump.c
> index 71a0093ada1b..b5ff4b3e1831 100644
> --- a/fs/coredump.c
> +++ b/fs/coredump.c
> @@ -454,7 +454,7 @@ static bool coredump_parse(struct core_name *cn, struct coredump_params *cprm,
> * leader we know that the thread-group leader
> * cannot be reaped until @current has exited.
> */
> - cprm->pid = task_tgid(current);
> + task_pids(cprm->pid, current);
> err = cn_printf(cn, "%d", COREDUMP_PIDFD_NUMBER);
> break;
> }
> @@ -626,13 +626,17 @@ static int umh_coredump_setup(struct subprocess_info *info, struct cred *new)
> struct coredump_params *cp = (struct coredump_params *)info->data;
> int err;
>
> - if (cp->pid) {
> + if (cp->pid[PIDTYPE_TGID]) {
> struct file *pidfs_file __free(fput) = NULL;
>
> - pidfs_file = pidfs_alloc_file(cp->pid, 0);
> + pidfs_file = pidfs_alloc_file(cp->pid[PIDTYPE_TGID], 0);
> if (IS_ERR(pidfs_file))
> return PTR_ERR(pidfs_file);
>
> + err = pidfs_register_pids(cp->pid);
> + if (err)
> + return err;
> +
> pidfs_coredump(cp);
>
> /*
> @@ -695,12 +699,12 @@ static bool coredump_sock_connect(struct core_name *cn, struct coredump_params *
> return false;
>
> /*
> - * Set the thread-group leader pid which is used for the peer
> - * credentials during connect() below. Then immediately register
> - * it in pidfs...
> + * Set the pids of the dumping thread and its thread-group leader
> + * which are used for the peer credentials during connect() below.
> + * Then immediately register them in pidfs...
> */
> - cprm->pid = task_tgid(current);
> - retval = pidfs_register_pid(cprm->pid);
> + task_pids(cprm->pid, current);
> + retval = pidfs_register_pids(cprm->pid);
> if (retval)
> return false;
>
> @@ -722,7 +726,7 @@ static bool coredump_sock_connect(struct core_name *cn, struct coredump_params *
> }
>
> /* ... and validate that @sk_peer_pid matches @cprm.pid. */
> - if (WARN_ON_ONCE(unix_peer(socket->sk)->sk_peer_pid[PIDTYPE_TGID] != cprm->pid))
> + if (WARN_ON_ONCE(!pids_equal(unix_peer(socket->sk)->sk_peer_pid, cprm->pid)))
> return false;
>
> cprm->limit = RLIM_INFINITY;
> diff --git a/fs/pidfs.c b/fs/pidfs.c
> index 586af2e5811c..29299b2c7ca7 100644
> --- a/fs/pidfs.c
> +++ b/fs/pidfs.c
> @@ -793,9 +793,9 @@ void pidfs_exit(struct task_struct *tsk)
> }
>
> #ifdef CONFIG_COREDUMP
> -void pidfs_coredump(const struct coredump_params *cprm)
> +static void pidfs_coredump_pid(struct pid *pid,
> + const struct coredump_params *cprm)
> {
> - struct pid *pid = cprm->pid;
> struct pidfs_attr *attr;
>
> attr = READ_ONCE(pid->attr);
> @@ -814,6 +814,13 @@ void pidfs_coredump(const struct coredump_params *cprm)
> smp_wmb();
> set_bit(PIDFS_ATTR_BIT_COREDUMP, &attr->attr_mask);
> }
> +
> +void pidfs_coredump(const struct coredump_params *cprm)
> +{
> + /* The dumping thread's pidfd reports the coredump as well. */
> + for (enum pid_type type = PIDTYPE_PID; type <= pids_last(cprm->pid); type++)
> + pidfs_coredump_pid(cprm->pid[type], cprm);
> +}
> #endif
>
> static struct vfsmount *pidfs_mnt __ro_after_init;
> diff --git a/include/linux/coredump.h b/include/linux/coredump.h
> index 7b38ee2e7913..0bbb7de6a402 100644
> --- a/include/linux/coredump.h
> +++ b/include/linux/coredump.h
> @@ -5,6 +5,7 @@
> #include <linux/types.h>
> #include <linux/mm.h>
> #include <linux/fs.h>
> +#include <linux/pid_types.h>
> #include <linux/sched/coredump.h>
> #include <asm/siginfo.h>
>
> @@ -32,7 +33,8 @@ struct coredump_params {
> int vma_count;
> size_t vma_data_size;
> struct core_vma_metadata *vma_meta;
> - struct pid *pid;
> + /* Dumping thread and its thread-group leader by pid type. */
> + DECLARE_PIDS(pid, PIDTYPE_TGID);
> };
>
> extern unsigned int core_file_note_size_limit;
>
> --
> 2.53.0
>
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH 10/10] selftests/coredump: check the dumping thread's pidfd
2026-08-31 11:21 ` [PATCH 10/10] selftests/coredump: check the dumping thread's pidfd Christian Brauner
@ 2026-09-07 11:00 ` Alexander Mikhalitsyn
0 siblings, 0 replies; 25+ messages in thread
From: Alexander Mikhalitsyn @ 2026-09-07 11:00 UTC (permalink / raw)
To: Christian Brauner
Cc: Jakub Kicinski, Kuniyuki Iwashima, Oleg Nesterov, David S. Miller,
Eric Dumazet, Paolo Abeni, Simon Horman, Willem de Bruijn, netdev,
linux-kernel, Alexander Viro, Jan Kara, linux-fsdevel
Am Mo., 31. Aug. 2026 um 13:22 Uhr schrieb Christian Brauner
<brauner@kernel.org>:
>
> Crash from a non-leader thread and verify that the pidfd from
> SO_PEERPIDFD_THREAD on the coredump socket refers to that thread and
> reports the coredump like the thread-group leader's pidfd does.
>
> Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
LGTM
Reviewed-by: Alexander Mikhalitsyn <aleksandr.mikhalitsyn@futurfusion.io>
> ---
> .../selftests/coredump/coredump_socket_test.c | 175 +++++++++++++++++++++
> tools/testing/selftests/coredump/coredump_test.h | 2 +
> .../selftests/coredump/coredump_test_helpers.c | 49 ++++++
> 3 files changed, 226 insertions(+)
>
> diff --git a/tools/testing/selftests/coredump/coredump_socket_test.c b/tools/testing/selftests/coredump/coredump_socket_test.c
> index 422728f632ca..ec73bb690bbc 100644
> --- a/tools/testing/selftests/coredump/coredump_socket_test.c
> +++ b/tools/testing/selftests/coredump/coredump_socket_test.c
> @@ -592,6 +592,181 @@ TEST_F(coredump, socket_coredump_signal_sigsegv)
> wait_and_check_coredump_server(pid_coredump_server, _metadata, self);
> }
>
> +static bool check_coredump_info(const struct pidfd_info *info, const char *what)
> +{
> + if (!(info->mask & PIDFD_INFO_COREDUMP)) {
> + fprintf(stderr, "%s: PIDFD_INFO_COREDUMP not set in mask\n", what);
> + return false;
> + }
> +
> + if (!(info->coredump_mask & PIDFD_COREDUMPED)) {
> + fprintf(stderr, "%s: PIDFD_COREDUMPED not set in coredump_mask\n", what);
> + return false;
> + }
> +
> + if (!(info->mask & PIDFD_INFO_COREDUMP_SIGNAL) || info->coredump_signal != SIGSEGV) {
> + fprintf(stderr, "%s: coredump_signal=%d, expected SIGSEGV=%d\n",
> + what, info->coredump_signal, SIGSEGV);
> + return false;
> + }
> +
> + if (!(info->mask & PIDFD_INFO_COREDUMP_CODE) || info->coredump_code != SEGV_MAPERR) {
> + fprintf(stderr, "%s: coredump_code=%d, expected SEGV_MAPERR=%d\n",
> + what, info->coredump_code, SEGV_MAPERR);
> + return false;
> + }
> +
> + return true;
> +}
> +
> +/*
> + * Test: PIDFD_INFO_COREDUMP on the dumping thread's pidfd
> + *
> + * Crash from a non-leader thread and verify that the pidfd from
> + * SO_PEERPIDFD_THREAD refers to that thread and reports the coredump
> + * like the thread-group leader's pidfd from SO_PEERPIDFD does.
> + */
> +TEST_F(coredump, socket_coredump_thread)
> +{
> + int pidfd, ret, status;
> + pid_t pid, pid_coredump_server;
> + struct pidfd_info info = {};
> + int ipc_sockets[2];
> + char c;
> +
> + ASSERT_TRUE(set_core_pattern("@/tmp/coredump.socket"));
> +
> + ret = socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, ipc_sockets);
> + ASSERT_EQ(ret, 0);
> +
> + pid_coredump_server = fork();
> + ASSERT_GE(pid_coredump_server, 0);
> + if (pid_coredump_server == 0) {
> + int fd_server = -1, fd_coredump = -1, fd_peer_pidfd = -1;
> + int fd_thread_pidfd = -1, fd_core_file = -1;
> + struct pidfd_info thread_info = {};
> + int exit_code = EXIT_FAILURE;
> +
> + close(ipc_sockets[0]);
> +
> + fd_server = create_and_listen_unix_socket("/tmp/coredump.socket");
> + if (fd_server < 0) {
> + fprintf(stderr, "socket_coredump_thread: listen socket failed: %m\n");
> + goto out;
> + }
> +
> + if (write_nointr(ipc_sockets[1], "1", 1) < 0) {
> + fprintf(stderr, "socket_coredump_thread: ipc write failed: %m\n");
> + goto out;
> + }
> +
> + close(ipc_sockets[1]);
> +
> + fd_coredump = accept4(fd_server, NULL, NULL, SOCK_CLOEXEC);
> + if (fd_coredump < 0) {
> + fprintf(stderr, "socket_coredump_thread: accept4 failed: %m\n");
> + goto out;
> + }
> +
> + fd_peer_pidfd = get_peer_pidfd(fd_coredump);
> + if (fd_peer_pidfd < 0) {
> + fprintf(stderr, "socket_coredump_thread: get_peer_pidfd failed\n");
> + goto out;
> + }
> +
> + fd_thread_pidfd = get_peer_pidfd_thread(fd_coredump);
> + if (fd_thread_pidfd < 0) {
> + fprintf(stderr, "socket_coredump_thread: get_peer_pidfd_thread failed\n");
> + goto out;
> + }
> +
> + if (!get_pidfd_info(fd_peer_pidfd, &info) ||
> + !get_pidfd_info(fd_thread_pidfd, &thread_info)) {
> + fprintf(stderr, "socket_coredump_thread: get_pidfd_info failed\n");
> + goto out;
> + }
> +
> + /* The peer is the thread-group leader, the dumping thread is not. */
> + if (info.pid != info.tgid || thread_info.tgid != info.tgid ||
> + thread_info.pid == thread_info.tgid) {
> + fprintf(stderr, "socket_coredump_thread: unexpected ids %d/%d and %d/%d\n",
> + info.pid, info.tgid, thread_info.pid, thread_info.tgid);
> + goto out;
> + }
> +
> + if (!check_coredump_info(&info, "SO_PEERPIDFD") ||
> + !check_coredump_info(&thread_info, "SO_PEERPIDFD_THREAD"))
> + goto out;
> +
> + fd_core_file = open_coredump_tmpfile(self->fd_tmpfs_detached);
> + if (fd_core_file < 0) {
> + fprintf(stderr, "socket_coredump_thread: core tmpfile failed: %m\n");
> + goto out;
> + }
> +
> + for (;;) {
> + char buffer[4096];
> + ssize_t bytes_read, bytes_write;
> +
> + bytes_read = read(fd_coredump, buffer, sizeof(buffer));
> + if (bytes_read < 0) {
> + fprintf(stderr, "socket_coredump_thread: core read failed: %m\n");
> + goto out;
> + }
> +
> + if (bytes_read == 0)
> + break;
> +
> + bytes_write = write(fd_core_file, buffer, bytes_read);
> + if (bytes_read != bytes_write) {
> + fprintf(stderr, "socket_coredump_thread: core write %zd/%zd: %m\n",
> + bytes_read, bytes_write);
> + goto out;
> + }
> + }
> +
> + exit_code = EXIT_SUCCESS;
> + fprintf(stderr, "socket_coredump_thread: completed successfully\n");
> +out:
> + if (fd_core_file >= 0)
> + close(fd_core_file);
> + if (fd_thread_pidfd >= 0)
> + close(fd_thread_pidfd);
> + if (fd_peer_pidfd >= 0)
> + close(fd_peer_pidfd);
> + if (fd_coredump >= 0)
> + close(fd_coredump);
> + if (fd_server >= 0)
> + close(fd_server);
> + _exit(exit_code);
> + }
> + self->pid_coredump_server = pid_coredump_server;
> +
> + EXPECT_EQ(close(ipc_sockets[1]), 0);
> + ASSERT_EQ(read_nointr(ipc_sockets[0], &c, 1), 1);
> + EXPECT_EQ(close(ipc_sockets[0]), 0);
> +
> + pid = fork();
> + ASSERT_GE(pid, 0);
> + if (pid == 0)
> + crashing_child_thread();
> +
> + pidfd = sys_pidfd_open(pid, 0);
> + ASSERT_GE(pidfd, 0);
> +
> + waitpid(pid, &status, 0);
> + ASSERT_TRUE(WIFSIGNALED(status));
> + ASSERT_EQ(WTERMSIG(status), SIGSEGV);
> + ASSERT_TRUE(WCOREDUMP(status));
> +
> + ASSERT_TRUE(get_pidfd_info(pidfd, &info));
> + ASSERT_TRUE(!!(info.mask & PIDFD_INFO_COREDUMP));
> + ASSERT_TRUE(!!(info.coredump_mask & PIDFD_COREDUMPED));
> + ASSERT_EQ(info.coredump_signal, SIGSEGV);
> +
> + wait_and_check_coredump_server(pid_coredump_server, _metadata, self);
> +}
> +
> /*
> * Test: PIDFD_INFO_COREDUMP_SIGNAL via simple socket coredump with SIGABRT
> *
> diff --git a/tools/testing/selftests/coredump/coredump_test.h b/tools/testing/selftests/coredump/coredump_test.h
> index ed47f01fa53c..4212656e31f0 100644
> --- a/tools/testing/selftests/coredump/coredump_test.h
> +++ b/tools/testing/selftests/coredump/coredump_test.h
> @@ -27,10 +27,12 @@ FIXTURE(coredump)
> /* Shared helper function declarations */
> void *do_nothing(void *arg);
> void crashing_child(void);
> +void crashing_child_thread(void);
> int create_detached_tmpfs(void);
> int create_and_listen_unix_socket(const char *path);
> bool set_core_pattern(const char *pattern);
> int get_peer_pidfd(int fd);
> +int get_peer_pidfd_thread(int fd);
> bool get_pidfd_info(int fd_peer_pidfd, struct pidfd_info *info);
>
> /* Inline helper that uses harness types */
> diff --git a/tools/testing/selftests/coredump/coredump_test_helpers.c b/tools/testing/selftests/coredump/coredump_test_helpers.c
> index 2a20faf9cb0a..36306069f62e 100644
> --- a/tools/testing/selftests/coredump/coredump_test_helpers.c
> +++ b/tools/testing/selftests/coredump/coredump_test_helpers.c
> @@ -13,6 +13,7 @@
> #include <string.h>
> #include <sys/epoll.h>
> #include <sys/ioctl.h>
> +#include <sys/mman.h>
> #include <sys/socket.h>
> #include <sys/types.h>
> #include <sys/un.h>
> @@ -38,6 +39,10 @@ struct _fixture_coredump_data {
>
> #define NUM_THREAD_SPAWN 128
>
> +#ifndef SO_PEERPIDFD_THREAD
> +#define SO_PEERPIDFD_THREAD 87
> +#endif
> +
> void *do_nothing(void *arg)
> {
> (void)arg;
> @@ -59,6 +64,36 @@ void crashing_child(void)
> i = *(volatile int *)NULL;
> }
>
> +static void *crashing_thread(void *arg)
> +{
> + int *p;
> +
> + (void)arg;
> +
> + /* crash on purpose with SEGV_MAPERR */
> + p = mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE,
> + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
> + if (p == MAP_FAILED)
> + return NULL;
> + munmap(p, PAGE_SIZE);
> + *p = 0;
> +
> + return NULL;
> +}
> +
> +void crashing_child_thread(void)
> +{
> + pthread_t thread;
> + int i;
> +
> + for (i = 0; i < NUM_THREAD_SPAWN; ++i)
> + pthread_create(&thread, NULL, do_nothing, NULL);
> +
> + /* crash from a non-leader thread */
> + pthread_create(&thread, NULL, crashing_thread, NULL);
> + pause();
> +}
> +
> int create_detached_tmpfs(void)
> {
> int fd_context, fd_tmpfs;
> @@ -138,6 +173,20 @@ int get_peer_pidfd(int fd)
> return fd_peer_pidfd;
> }
>
> +int get_peer_pidfd_thread(int fd)
> +{
> + int fd_peer_pidfd;
> + socklen_t fd_peer_pidfd_len = sizeof(fd_peer_pidfd);
> + int ret = getsockopt(fd, SOL_SOCKET, SO_PEERPIDFD_THREAD, &fd_peer_pidfd,
> + &fd_peer_pidfd_len);
> + if (ret < 0) {
> + fprintf(stderr, "%s: getsockopt(SO_PEERPIDFD_THREAD) failed: %m\n", __func__);
> + return -1;
> + }
> + fprintf(stderr, "%s: successfully retrieved pidfd %d\n", __func__, fd_peer_pidfd);
> + return fd_peer_pidfd;
> +}
> +
> bool get_pidfd_info(int fd_peer_pidfd, struct pidfd_info *info)
> {
> int ret;
>
> --
> 2.53.0
>
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH 02/10] af_unix: record the pid of the sending thread
2026-09-07 9:55 ` Alexander Mikhalitsyn
@ 2026-09-09 10:36 ` Christian Brauner
2026-09-09 10:47 ` Alexander Mikhalitsyn
0 siblings, 1 reply; 25+ messages in thread
From: Christian Brauner @ 2026-09-09 10:36 UTC (permalink / raw)
To: Alexander Mikhalitsyn
Cc: Jakub Kicinski, Kuniyuki Iwashima, Oleg Nesterov, David S. Miller,
Eric Dumazet, Paolo Abeni, Simon Horman, Willem de Bruijn, netdev,
linux-kernel, Alexander Viro, Jan Kara, linux-fsdevel
On Mon, Sep 07, 2026 at 11:55:23AM +0200, Alexander Mikhalitsyn wrote:
> Am Mo., 31. Aug. 2026 um 13:21 Uhr schrieb Christian Brauner
> <brauner@kernel.org>:
> >
> > Currently only the struct pid of the thread-group leader is recorded.
> > The identity of the actual thread that sent the message or is connected
> > to a given socket cannot be retrieved.
> >
> > Add the plumbing to make it possible to retrieve a pidfd for the sender.
> > Nothing uses the thread-specific struct pid yet. No functional changes.
> >
> > Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
> > ---
> > include/net/scm.h | 7 +++----
> > net/core/scm.c | 21 +++++++++++++++++----
> > net/unix/af_unix.c | 23 +++++++++++++----------
> > net/unix/af_unix.h | 3 ++-
> > 4 files changed, 35 insertions(+), 19 deletions(-)
> >
> > diff --git a/include/net/scm.h b/include/net/scm.h
> > index 86ae6bc109ec..aa7d15c5fc27 100644
> > --- a/include/net/scm.h
> > +++ b/include/net/scm.h
> > @@ -42,7 +42,7 @@ struct scm_fp_list {
> > };
> >
> > struct scm_cookie {
> > - struct pid *pid; /* Skb credentials */
> > + DECLARE_PIDS(pid, PIDTYPE_TGID); /* Skb credentials by pid type */
> > struct scm_fp_list *fp; /* Passed files */
> > struct scm_creds creds; /* Skb credentials */
> > #ifdef CONFIG_SECURITY_NETWORK
> > @@ -69,7 +69,7 @@ static __inline__ void unix_get_peersec_dgram(struct socket *sock, struct scm_co
> > static __inline__ void scm_set_cred(struct scm_cookie *scm,
> > struct pid *pid, kuid_t uid, kgid_t gid)
> > {
> > - scm->pid = get_pid(pid);
> > + scm->pid[PIDTYPE_TGID] = get_pid(pid);
> > scm->creds.pid = pid_vnr(pid);
> > scm->creds.uid = uid;
> > scm->creds.gid = gid;
> > @@ -77,8 +77,7 @@ static __inline__ void scm_set_cred(struct scm_cookie *scm,
> >
> > static __inline__ void scm_destroy_cred(struct scm_cookie *scm)
> > {
> > - put_pid(scm->pid);
> > - scm->pid = NULL;
> > + put_pids(scm->pid);
> > }
> >
> > static __inline__ void scm_destroy(struct scm_cookie *scm)
> > diff --git a/net/core/scm.c b/net/core/scm.c
> > index f0d44ecdb11f..9b9e119c353a 100644
> > --- a/net/core/scm.c
> > +++ b/net/core/scm.c
> > @@ -149,6 +149,7 @@ EXPORT_SYMBOL(__scm_destroy);
> >
> > static inline int scm_replace_pid(struct scm_cookie *scm, struct pid *pid)
> > {
> > + struct pid *thread_pid;
> > int err;
> >
> > /* drop all previous references */
> > @@ -158,7 +159,18 @@ static inline int scm_replace_pid(struct scm_cookie *scm, struct pid *pid)
> > if (unlikely(err))
> > return err;
> >
> > - scm->pid = pid;
> > + /* A sender naming its own thread-group sends from the current thread. */
> > + if (pid == task_tgid(current))
> > + thread_pid = task_pid(current);
> > + else
> > + thread_pid = pid;
>
> Hi Christian,
>
> The patch looks good to me, but this spot raised some doubts for me.
>
> If scm_replace_pid() is called with pid == task_tgid(current) then all
> good, because we
> get scm->pid[PIDTYPE_TGID] == pid and scm->pid[PIDTYPE_PID] ==
> task_pid(current). All good.
>
> If scm_replace_pid() is supplied with pid that has a type PIDTYPE_TGID, then:
> - scm->pid[PIDTYPE_TGID] == pid. Good.
> - scm->pid[PIDTYPE_PID] = pid. Doesn't looks correct, shoudn't we do
After chatting with you, I think I get the concern. You think that
scm->pid[PIDTYPE_PID] always points to a thread-group leader if pid ==
task_tgid(current). Yes, that's true but that's intended and fine. The
thread-group leader is just a thread in the thread-group just with
magical behavior (that we would really like to get rid of but can't
easily...).
Thanks for looking closely at this though!
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH 02/10] af_unix: record the pid of the sending thread
2026-09-09 10:36 ` Christian Brauner
@ 2026-09-09 10:47 ` Alexander Mikhalitsyn
0 siblings, 0 replies; 25+ messages in thread
From: Alexander Mikhalitsyn @ 2026-09-09 10:47 UTC (permalink / raw)
To: Christian Brauner
Cc: Jakub Kicinski, Kuniyuki Iwashima, Oleg Nesterov, David S. Miller,
Eric Dumazet, Paolo Abeni, Simon Horman, Willem de Bruijn, netdev,
linux-kernel, Alexander Viro, Jan Kara, linux-fsdevel
Am Mi., 9. Sept. 2026 um 12:36 Uhr schrieb Christian Brauner
<brauner@kernel.org>:
>
> On Mon, Sep 07, 2026 at 11:55:23AM +0200, Alexander Mikhalitsyn wrote:
> > Am Mo., 31. Aug. 2026 um 13:21 Uhr schrieb Christian Brauner
> > <brauner@kernel.org>:
> > >
> > > Currently only the struct pid of the thread-group leader is recorded.
> > > The identity of the actual thread that sent the message or is connected
> > > to a given socket cannot be retrieved.
> > >
> > > Add the plumbing to make it possible to retrieve a pidfd for the sender.
> > > Nothing uses the thread-specific struct pid yet. No functional changes.
> > >
> > > Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
> > > ---
> > > include/net/scm.h | 7 +++----
> > > net/core/scm.c | 21 +++++++++++++++++----
> > > net/unix/af_unix.c | 23 +++++++++++++----------
> > > net/unix/af_unix.h | 3 ++-
> > > 4 files changed, 35 insertions(+), 19 deletions(-)
> > >
> > > diff --git a/include/net/scm.h b/include/net/scm.h
> > > index 86ae6bc109ec..aa7d15c5fc27 100644
> > > --- a/include/net/scm.h
> > > +++ b/include/net/scm.h
> > > @@ -42,7 +42,7 @@ struct scm_fp_list {
> > > };
> > >
> > > struct scm_cookie {
> > > - struct pid *pid; /* Skb credentials */
> > > + DECLARE_PIDS(pid, PIDTYPE_TGID); /* Skb credentials by pid type */
> > > struct scm_fp_list *fp; /* Passed files */
> > > struct scm_creds creds; /* Skb credentials */
> > > #ifdef CONFIG_SECURITY_NETWORK
> > > @@ -69,7 +69,7 @@ static __inline__ void unix_get_peersec_dgram(struct socket *sock, struct scm_co
> > > static __inline__ void scm_set_cred(struct scm_cookie *scm,
> > > struct pid *pid, kuid_t uid, kgid_t gid)
> > > {
> > > - scm->pid = get_pid(pid);
> > > + scm->pid[PIDTYPE_TGID] = get_pid(pid);
> > > scm->creds.pid = pid_vnr(pid);
> > > scm->creds.uid = uid;
> > > scm->creds.gid = gid;
> > > @@ -77,8 +77,7 @@ static __inline__ void scm_set_cred(struct scm_cookie *scm,
> > >
> > > static __inline__ void scm_destroy_cred(struct scm_cookie *scm)
> > > {
> > > - put_pid(scm->pid);
> > > - scm->pid = NULL;
> > > + put_pids(scm->pid);
> > > }
> > >
> > > static __inline__ void scm_destroy(struct scm_cookie *scm)
> > > diff --git a/net/core/scm.c b/net/core/scm.c
> > > index f0d44ecdb11f..9b9e119c353a 100644
> > > --- a/net/core/scm.c
> > > +++ b/net/core/scm.c
> > > @@ -149,6 +149,7 @@ EXPORT_SYMBOL(__scm_destroy);
> > >
> > > static inline int scm_replace_pid(struct scm_cookie *scm, struct pid *pid)
> > > {
> > > + struct pid *thread_pid;
> > > int err;
> > >
> > > /* drop all previous references */
> > > @@ -158,7 +159,18 @@ static inline int scm_replace_pid(struct scm_cookie *scm, struct pid *pid)
> > > if (unlikely(err))
> > > return err;
> > >
> > > - scm->pid = pid;
> > > + /* A sender naming its own thread-group sends from the current thread. */
> > > + if (pid == task_tgid(current))
> > > + thread_pid = task_pid(current);
> > > + else
> > > + thread_pid = pid;
> >
> > Hi Christian,
> >
> > The patch looks good to me, but this spot raised some doubts for me.
> >
> > If scm_replace_pid() is called with pid == task_tgid(current) then all
> > good, because we
> > get scm->pid[PIDTYPE_TGID] == pid and scm->pid[PIDTYPE_PID] ==
> > task_pid(current). All good.
> >
> > If scm_replace_pid() is supplied with pid that has a type PIDTYPE_TGID, then:
> > - scm->pid[PIDTYPE_TGID] == pid. Good.
> > - scm->pid[PIDTYPE_PID] = pid. Doesn't looks correct, shoudn't we do
>
> After chatting with you, I think I get the concern. You think that
> scm->pid[PIDTYPE_PID] always points to a thread-group leader if pid ==
> task_tgid(current). Yes, that's true but that's intended and fine. The
> thread-group leader is just a thread in the thread-group just with
> magical behavior (that we would really like to get rid of but can't
> easily...).
Yes, my concern was about another case when `pid != task_tgid(current)`, but the
chunk of code I've suggested makes no sense. When I looked at it with
a fresh head I realized this.
In scm_replace_pid, once we've got `pid` pointing to a leader's thread
we can't really do anything
other than taking leader thread's TID as a scm->pid[PIDTYPE_PID]...
>
> Thanks for looking closely at this though!
LGTM
Reviewed-by: Alexander Mikhalitsyn <aleksandr.mikhalitsyn@futurfusion.io>
^ permalink raw reply [flat|nested] 25+ messages in thread
end of thread, other threads:[~2026-09-09 10:47 UTC | newest]
Thread overview: 25+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-31 11:21 [PATCH 00/10] net: support thread-specific pidfds for send and connect Christian Brauner
2026-08-31 11:21 ` [PATCH 01/10] pid: add helpers to operate on a struct pid array Christian Brauner
2026-09-07 10:07 ` Alexander Mikhalitsyn
2026-08-31 11:21 ` [PATCH 02/10] af_unix: record the pid of the sending thread Christian Brauner
2026-09-07 9:55 ` Alexander Mikhalitsyn
2026-09-09 10:36 ` Christian Brauner
2026-09-09 10:47 ` Alexander Mikhalitsyn
2026-08-31 11:21 ` [PATCH 03/10] net: add SO_PASSPIDFD_THREAD to get a thread-specific SCM_PIDFD Christian Brauner
2026-09-07 10:02 ` Alexander Mikhalitsyn
2026-08-31 11:21 ` [PATCH 04/10] selftests/net: SO_PASSPIDFD_THREAD Christian Brauner
2026-09-07 10:04 ` Alexander Mikhalitsyn
2026-08-31 11:21 ` [PATCH 05/10] net: turn sk_peer_pid into an array indexed by pid type Christian Brauner
2026-09-02 0:20 ` Jakub Kicinski
2026-09-04 9:03 ` Christian Brauner
2026-09-04 15:38 ` Jakub Kicinski
2026-08-31 11:21 ` [PATCH 06/10] af_unix: record the pid of the connecting thread Christian Brauner
2026-09-07 10:29 ` Alexander Mikhalitsyn
2026-08-31 11:21 ` [PATCH 07/10] net: add SO_PEERPIDFD_THREAD to get a thread-specific pidfd Christian Brauner
2026-09-07 10:49 ` Alexander Mikhalitsyn
2026-08-31 11:21 ` [PATCH 08/10] selftests/net: SO_PEERPIDFD_THREAD Christian Brauner
2026-09-07 10:51 ` Alexander Mikhalitsyn
2026-08-31 11:21 ` [PATCH 09/10] pidfs: record the coredump on the dumping thread's pid too Christian Brauner
2026-09-07 10:59 ` Alexander Mikhalitsyn
2026-08-31 11:21 ` [PATCH 10/10] selftests/coredump: check the dumping thread's pidfd Christian Brauner
2026-09-07 11:00 ` Alexander Mikhalitsyn
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.