* [PATCH v2 01/10] pid: add helpers to operate on a struct pid array
2026-09-09 10:42 [PATCH v2 00/10] net: support thread-specific pidfds for send and connect Christian Brauner
@ 2026-09-09 10:43 ` Christian Brauner
2026-09-10 4:14 ` Kuniyuki Iwashima
2026-09-09 10:43 ` [PATCH v2 02/10] af_unix: record the pid of the sending thread Christian Brauner
` (11 subsequent siblings)
12 siblings, 1 reply; 19+ messages in thread
From: Christian Brauner @ 2026-09-09 10:43 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 ++++++++++++
tools/lib/python/kdoc/xforms_lists.py | 1 +
6 files changed, 101 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];
diff --git a/tools/lib/python/kdoc/xforms_lists.py b/tools/lib/python/kdoc/xforms_lists.py
index e3dda2fe8a53..e792785a15ec 100644
--- a/tools/lib/python/kdoc/xforms_lists.py
+++ b/tools/lib/python/kdoc/xforms_lists.py
@@ -45,6 +45,7 @@ class CTransforms:
(CMatch("DECLARE_HASHTABLE"), r"unsigned long \1[1 << ((\2) - 1)]"),
(CMatch("DECLARE_KFIFO"), r"\2 *\1"),
(CMatch("DECLARE_KFIFO_PTR"), r"\2 *\1"),
+ (CMatch("DECLARE_PIDS"), r"struct pid *\1[(\2) + 1]"),
(CMatch("(?:__)?DECLARE_FLEX_ARRAY"), r"\1 \2[]"),
(CMatch("DEFINE_DMA_UNMAP_ADDR"), r"dma_addr_t \1"),
(CMatch("DEFINE_DMA_UNMAP_LEN"), r"__u32 \1"),
--
2.53.0
^ permalink raw reply [flat|nested] 19+ messages in thread* Re: [PATCH v2 01/10] pid: add helpers to operate on a struct pid array
2026-09-09 10:43 ` [PATCH v2 01/10] pid: add helpers to operate on a struct pid array Christian Brauner
@ 2026-09-10 4:14 ` Kuniyuki Iwashima
0 siblings, 0 replies; 19+ messages in thread
From: Kuniyuki Iwashima @ 2026-09-10 4:14 UTC (permalink / raw)
To: Christian Brauner
Cc: Jakub Kicinski, 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 Wed, Sep 9, 2026 at 3:43 AM Christian Brauner <brauner@kernel.org> wrote:
>
> 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>
Reviewed-by: Kuniyuki Iwashima <kuniyu@google.com>
^ permalink raw reply [flat|nested] 19+ messages in thread
* [PATCH v2 02/10] af_unix: record the pid of the sending thread
2026-09-09 10:42 [PATCH v2 00/10] net: support thread-specific pidfds for send and connect Christian Brauner
2026-09-09 10:43 ` [PATCH v2 01/10] pid: add helpers to operate on a struct pid array Christian Brauner
@ 2026-09-09 10:43 ` Christian Brauner
2026-09-09 10:43 ` [PATCH v2 03/10] net: add SO_PASSPIDFD_THREAD to get a thread-specific SCM_PIDFD Christian Brauner
` (10 subsequent siblings)
12 siblings, 0 replies; 19+ messages in thread
From: Christian Brauner @ 2026-09-09 10:43 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 [flat|nested] 19+ messages in thread* [PATCH v2 03/10] net: add SO_PASSPIDFD_THREAD to get a thread-specific SCM_PIDFD
2026-09-09 10:42 [PATCH v2 00/10] net: support thread-specific pidfds for send and connect Christian Brauner
2026-09-09 10:43 ` [PATCH v2 01/10] pid: add helpers to operate on a struct pid array Christian Brauner
2026-09-09 10:43 ` [PATCH v2 02/10] af_unix: record the pid of the sending thread Christian Brauner
@ 2026-09-09 10:43 ` Christian Brauner
2026-09-10 5:24 ` Kuniyuki Iwashima
2026-09-09 10:43 ` [PATCH v2 04/10] selftests/net: SO_PASSPIDFD_THREAD Christian Brauner
` (9 subsequent siblings)
12 siblings, 1 reply; 19+ messages in thread
From: Christian Brauner @ 2026-09-09 10:43 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 [flat|nested] 19+ messages in thread* Re: [PATCH v2 03/10] net: add SO_PASSPIDFD_THREAD to get a thread-specific SCM_PIDFD
2026-09-09 10:43 ` [PATCH v2 03/10] net: add SO_PASSPIDFD_THREAD to get a thread-specific SCM_PIDFD Christian Brauner
@ 2026-09-10 5:24 ` Kuniyuki Iwashima
0 siblings, 0 replies; 19+ messages in thread
From: Kuniyuki Iwashima @ 2026-09-10 5:24 UTC (permalink / raw)
To: Christian Brauner
Cc: Jakub Kicinski, 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 Wed, Sep 9, 2026 at 3:43 AM Christian Brauner <brauner@kernel.org> wrote:
>
> 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;
nit: Please keep the reverse xmas tree order.
> 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;
I'm wondering how this flag can be useful.
I thought this should be
if (pid_has_task(pid, PIDTYPE_TGID))
flags |= PIDFD_THREAD;
because when the sender sends TGID, this flag is set even though
it is a thread leader.
It seems PIDFD_THREAD is just feedback of whether the
receiver has set SO_PASSPIDFD or _THREAD, which the
application should already know.
> +
> + 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 SO_PASSPIDFD had boolean check, it would have been possible
to reuse SO_PASSPIDFD==2 as SO_PASSPIDFD_THREAD.
Given two options are mutually exclusive here, I think it's cleaner
to have u32 SO_PASSPIDFD_OPTIONS or something, which can
store 32 flags for future extension.
#define SO_PASSPIDFD_OPTION 86
#define SO_PASSPIDFD_THREAD 1
Then, sk_scm_pidfd_thread will be used in unix_skb_scm_eq()
and scm_pidfd_recv() only.
> + 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] 19+ messages in thread
* [PATCH v2 04/10] selftests/net: SO_PASSPIDFD_THREAD
2026-09-09 10:42 [PATCH v2 00/10] net: support thread-specific pidfds for send and connect Christian Brauner
` (2 preceding siblings ...)
2026-09-09 10:43 ` [PATCH v2 03/10] net: add SO_PASSPIDFD_THREAD to get a thread-specific SCM_PIDFD Christian Brauner
@ 2026-09-09 10:43 ` Christian Brauner
2026-09-09 10:43 ` [PATCH v2 05/10] net: turn sk_peer_pid into an array indexed by pid type Christian Brauner
` (8 subsequent siblings)
12 siblings, 0 replies; 19+ messages in thread
From: Christian Brauner @ 2026-09-09 10:43 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 [flat|nested] 19+ messages in thread* [PATCH v2 05/10] net: turn sk_peer_pid into an array indexed by pid type
2026-09-09 10:42 [PATCH v2 00/10] net: support thread-specific pidfds for send and connect Christian Brauner
` (3 preceding siblings ...)
2026-09-09 10:43 ` [PATCH v2 04/10] selftests/net: SO_PASSPIDFD_THREAD Christian Brauner
@ 2026-09-09 10:43 ` Christian Brauner
2026-09-09 10:43 ` [PATCH v2 06/10] af_unix: record the pid of the connecting thread Christian Brauner
` (7 subsequent siblings)
12 siblings, 0 replies; 19+ messages in thread
From: Christian Brauner @ 2026-09-09 10:43 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 ++--
include/trace/events/landlock.h | 2 +-
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 +++++++-------
8 files changed, 24 insertions(+), 23 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/include/trace/events/landlock.h b/include/trace/events/landlock.h
index f82588f6f90e..762e721b1d9c 100644
--- a/include/trace/events/landlock.h
+++ b/include/trace/events/landlock.h
@@ -943,7 +943,7 @@ TRACE_EVENT(landlock_deny_scope_abstract_unix_socket,
* these hooks, so this READ_ONCE() is safe; sun_path is the
* reliable identifier.
*/
- peer_pid = READ_ONCE(peer->sk_peer_pid);
+ peer_pid = READ_ONCE(peer->sk_peer_pid[PIDTYPE_TGID]);
__entry->peer_pid = peer_pid ? pid_nr(peer_pid) : 0;
__assign_str(sun_path);
),
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 [flat|nested] 19+ messages in thread* [PATCH v2 06/10] af_unix: record the pid of the connecting thread
2026-09-09 10:42 [PATCH v2 00/10] net: support thread-specific pidfds for send and connect Christian Brauner
` (4 preceding siblings ...)
2026-09-09 10:43 ` [PATCH v2 05/10] net: turn sk_peer_pid into an array indexed by pid type Christian Brauner
@ 2026-09-09 10:43 ` Christian Brauner
2026-09-09 10:43 ` [PATCH v2 07/10] net: add SO_PEERPIDFD_THREAD to get a thread-specific pidfd Christian Brauner
` (6 subsequent siblings)
12 siblings, 0 replies; 19+ messages in thread
From: Christian Brauner @ 2026-09-09 10:43 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 [flat|nested] 19+ messages in thread* [PATCH v2 07/10] net: add SO_PEERPIDFD_THREAD to get a thread-specific pidfd
2026-09-09 10:42 [PATCH v2 00/10] net: support thread-specific pidfds for send and connect Christian Brauner
` (5 preceding siblings ...)
2026-09-09 10:43 ` [PATCH v2 06/10] af_unix: record the pid of the connecting thread Christian Brauner
@ 2026-09-09 10:43 ` Christian Brauner
2026-09-10 6:11 ` Kuniyuki Iwashima
2026-09-09 10:43 ` [PATCH v2 08/10] selftests/net: SO_PEERPIDFD_THREAD Christian Brauner
` (5 subsequent siblings)
12 siblings, 1 reply; 19+ messages in thread
From: Christian Brauner @ 2026-09-09 10:43 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 [flat|nested] 19+ messages in thread* Re: [PATCH v2 07/10] net: add SO_PEERPIDFD_THREAD to get a thread-specific pidfd
2026-09-09 10:43 ` [PATCH v2 07/10] net: add SO_PEERPIDFD_THREAD to get a thread-specific pidfd Christian Brauner
@ 2026-09-10 6:11 ` Kuniyuki Iwashima
0 siblings, 0 replies; 19+ messages in thread
From: Kuniyuki Iwashima @ 2026-09-10 6:11 UTC (permalink / raw)
To: Christian Brauner
Cc: Jakub Kicinski, 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 Wed, Sep 9, 2026 at 3:43 AM Christian Brauner <brauner@kernel.org> wrote:
>
> 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.
nit: while at it, s/were/is/
> + */
> + if (sk->sk_family == AF_UNIX)
nit: please use sk_is_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] 19+ messages in thread
* [PATCH v2 08/10] selftests/net: SO_PEERPIDFD_THREAD
2026-09-09 10:42 [PATCH v2 00/10] net: support thread-specific pidfds for send and connect Christian Brauner
` (6 preceding siblings ...)
2026-09-09 10:43 ` [PATCH v2 07/10] net: add SO_PEERPIDFD_THREAD to get a thread-specific pidfd Christian Brauner
@ 2026-09-09 10:43 ` Christian Brauner
2026-09-09 10:43 ` [PATCH v2 09/10] pidfs: record the coredump on the dumping thread's pid too Christian Brauner
` (4 subsequent siblings)
12 siblings, 0 replies; 19+ messages in thread
From: Christian Brauner @ 2026-09-09 10:43 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 [flat|nested] 19+ messages in thread* [PATCH v2 09/10] pidfs: record the coredump on the dumping thread's pid too
2026-09-09 10:42 [PATCH v2 00/10] net: support thread-specific pidfds for send and connect Christian Brauner
` (7 preceding siblings ...)
2026-09-09 10:43 ` [PATCH v2 08/10] selftests/net: SO_PEERPIDFD_THREAD Christian Brauner
@ 2026-09-09 10:43 ` Christian Brauner
2026-09-09 10:43 ` [PATCH v2 10/10] selftests/coredump: check the dumping thread's pidfd Christian Brauner
` (3 subsequent siblings)
12 siblings, 0 replies; 19+ messages in thread
From: Christian Brauner @ 2026-09-09 10:43 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 [flat|nested] 19+ messages in thread* [PATCH v2 10/10] selftests/coredump: check the dumping thread's pidfd
2026-09-09 10:42 [PATCH v2 00/10] net: support thread-specific pidfds for send and connect Christian Brauner
` (8 preceding siblings ...)
2026-09-09 10:43 ` [PATCH v2 09/10] pidfs: record the coredump on the dumping thread's pid too Christian Brauner
@ 2026-09-09 10:43 ` Christian Brauner
2026-09-09 11:10 ` [PATCH v2 00/10] net: support thread-specific pidfds for send and connect Alexander Mikhalitsyn
` (2 subsequent siblings)
12 siblings, 0 replies; 19+ messages in thread
From: Christian Brauner @ 2026-09-09 10:43 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 [flat|nested] 19+ messages in thread* Re: [PATCH v2 00/10] net: support thread-specific pidfds for send and connect
2026-09-09 10:42 [PATCH v2 00/10] net: support thread-specific pidfds for send and connect Christian Brauner
` (9 preceding siblings ...)
2026-09-09 10:43 ` [PATCH v2 10/10] selftests/coredump: check the dumping thread's pidfd Christian Brauner
@ 2026-09-09 11:10 ` Alexander Mikhalitsyn
2026-09-09 12:40 ` Christian Brauner
2026-09-10 6:33 ` Kuniyuki Iwashima
12 siblings, 0 replies; 19+ messages in thread
From: Alexander Mikhalitsyn @ 2026-09-09 11:10 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:43 Uhr schrieb Christian Brauner
<brauner@kernel.org>:
>
> 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>
Reviewed-by: Alexander Mikhalitsyn <aleksandr.mikhalitsyn@futurfusion.io>
> ---
> Changes in v2:
> - Fix a few documentation issues.
> - Link to v1: https://patch.msgid.link/20260831-work-unix-passpidfd-v1-0-70cbfda0c7ba@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/trace/events/landlock.h | 2 +-
> 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 +-
> tools/lib/python/kdoc/xforms_lists.py | 1 +
> .../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 +++++++++++++++++++++
> 28 files changed, 907 insertions(+), 110 deletions(-)
> ---
> base-commit: cee9395acd8043be0644b25c34bfa86623f2b935
> change-id: 20260825-work-unix-passpidfd-086a1ec09e8a
>
^ permalink raw reply [flat|nested] 19+ messages in thread* Re: [PATCH v2 00/10] net: support thread-specific pidfds for send and connect
2026-09-09 10:42 [PATCH v2 00/10] net: support thread-specific pidfds for send and connect Christian Brauner
` (10 preceding siblings ...)
2026-09-09 11:10 ` [PATCH v2 00/10] net: support thread-specific pidfds for send and connect Alexander Mikhalitsyn
@ 2026-09-09 12:40 ` Christian Brauner
2026-09-09 18:39 ` Jakub Kicinski
2026-09-10 6:33 ` Kuniyuki Iwashima
12 siblings, 1 reply; 19+ messages in thread
From: Christian Brauner @ 2026-09-09 12:40 UTC (permalink / raw)
To: Jakub Kicinski, Kuniyuki Iwashima, Oleg Nesterov, Christian Brauner
Cc: David S. Miller, Eric Dumazet, Paolo Abeni, Simon Horman,
Willem de Bruijn, netdev, linux-kernel, Alexander Viro, Jan Kara,
linux-fsdevel, Alexander Mikhalitsyn
On Wed, 09 Sep 2026 12:42:59 +0200, Christian Brauner wrote:
> 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.
>
> [...]
@Jakub, shared branch in vfs-7.4.shared.net-next.pidfd. I'm open to rebasing if
you have need for that on your end during the cycle.
---
Applied to the vfs-7.4.shared.net-next.pidfd branch of the vfs/vfs.git tree.
Patches in the vfs-7.4.shared.net-next.pidfd branch should appear in linux-next soon.
Please report any outstanding bugs that were missed during review in a
new review to the original patch series allowing us to drop it.
It's encouraged to provide Acked-bys and Reviewed-bys even though the
patch has now been applied. If possible patch trailers will be updated.
Note that commit hashes shown below are subject to change due to rebase,
trailer updates or similar. If in doubt, please check the listed branch.
tree: https://git.kernel.org/pub/scm/linux/kernel/git/vfs/vfs.git
branch: vfs-7.4.shared.net-next.pidfd
[01/10] pid: add helpers to operate on a struct pid array
https://git.kernel.org/vfs/vfs/c/b674dcb18f1d
[02/10] af_unix: record the pid of the sending thread
https://git.kernel.org/vfs/vfs/c/a8575fe8b94e
[03/10] net: add SO_PASSPIDFD_THREAD to get a thread-specific SCM_PIDFD
https://git.kernel.org/vfs/vfs/c/78dc8469581d
[04/10] selftests/net: SO_PASSPIDFD_THREAD
https://git.kernel.org/vfs/vfs/c/7810049c5675
[05/10] net: turn sk_peer_pid into an array indexed by pid type
https://git.kernel.org/vfs/vfs/c/0149a6ceaf8f
[06/10] af_unix: record the pid of the connecting thread
https://git.kernel.org/vfs/vfs/c/4dd362b00be5
[07/10] net: add SO_PEERPIDFD_THREAD to get a thread-specific pidfd
https://git.kernel.org/vfs/vfs/c/41972f2e5ab0
[08/10] selftests/net: SO_PEERPIDFD_THREAD
https://git.kernel.org/vfs/vfs/c/6e8bef9686e4
[09/10] pidfs: record the coredump on the dumping thread's pid too
https://git.kernel.org/vfs/vfs/c/52671ec3712c
[10/10] selftests/coredump: check the dumping thread's pidfd
https://git.kernel.org/vfs/vfs/c/b956027e73be
^ permalink raw reply [flat|nested] 19+ messages in thread* Re: [PATCH v2 00/10] net: support thread-specific pidfds for send and connect
2026-09-09 12:40 ` Christian Brauner
@ 2026-09-09 18:39 ` Jakub Kicinski
0 siblings, 0 replies; 19+ messages in thread
From: Jakub Kicinski @ 2026-09-09 18:39 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 Wed, 9 Sep 2026 14:40:25 +0200 Christian Brauner wrote:
> @Jakub, shared branch in vfs-7.4.shared.net-next.pidfd. I'm open to rebasing if
> you have need for that on your end during the cycle.
SG, thanks! Once we pull the hashes need to stay stable, we have a lot
of subordinate trees and too much code for feature branches.
Let's give it a couple of days, let AI and Kuniyuki review, and take
it from there.
^ permalink raw reply [flat|nested] 19+ messages in thread
* Re: [PATCH v2 00/10] net: support thread-specific pidfds for send and connect
2026-09-09 10:42 [PATCH v2 00/10] net: support thread-specific pidfds for send and connect Christian Brauner
` (11 preceding siblings ...)
2026-09-09 12:40 ` Christian Brauner
@ 2026-09-10 6:33 ` Kuniyuki Iwashima
2026-09-10 7:54 ` Christian Brauner
12 siblings, 1 reply; 19+ messages in thread
From: Kuniyuki Iwashima @ 2026-09-10 6:33 UTC (permalink / raw)
To: Christian Brauner
Cc: Jakub Kicinski, 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 Wed, Sep 9, 2026 at 3:43 AM Christian Brauner <brauner@kernel.org> wrote:
>
> 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.
systemd-coredump currently uses SO_PEERPIDFD, so
SO_PEERPIDFD_THREAD makes sense to me, but is there
real need/use-case to fetch each thread's pidfd per sendmsg()
via cmsg ?
>
> Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
> ---
> Changes in v2:
> - Fix a few documentation issues.
> - Link to v1: https://patch.msgid.link/20260831-work-unix-passpidfd-v1-0-70cbfda0c7ba@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/trace/events/landlock.h | 2 +-
> 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 +-
> tools/lib/python/kdoc/xforms_lists.py | 1 +
> .../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 +++++++++++++++++++++
> 28 files changed, 907 insertions(+), 110 deletions(-)
> ---
> base-commit: cee9395acd8043be0644b25c34bfa86623f2b935
> change-id: 20260825-work-unix-passpidfd-086a1ec09e8a
>
^ permalink raw reply [flat|nested] 19+ messages in thread* Re: [PATCH v2 00/10] net: support thread-specific pidfds for send and connect
2026-09-10 6:33 ` Kuniyuki Iwashima
@ 2026-09-10 7:54 ` Christian Brauner
0 siblings, 0 replies; 19+ messages in thread
From: Christian Brauner @ 2026-09-10 7:54 UTC (permalink / raw)
To: Kuniyuki Iwashima
Cc: Christian Brauner, Jakub Kicinski, 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-09 23:33 -0700, Kuniyuki Iwashima wrote:
> On Wed, Sep 9, 2026 at 3:43 AM Christian Brauner <brauner@kernel.org> wrote:
> >
> > 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.
>
> systemd-coredump currently uses SO_PEERPIDFD, so
And dbus-broker and polkit and etc pp.
> SO_PEERPIDFD_THREAD makes sense to me, but is there
> real need/use-case to fetch each thread's pidfd per sendmsg()
> via cmsg ?
I want to be able to authenticate individual threads in a thread-group
on a per message basis in an authentication framework.
Thanks for the review!
^ permalink raw reply [flat|nested] 19+ messages in thread