AI Trajectory


Trajectory: (Expand/Collapse All)
Seq Timestamp Type Name Duration
0/0 2026/10/05 15:13 flow patch-triage
11m Args:
null

Results:
{
  "EnableConfigs": [
    "UNIX",
    "COREDUMP",
    "BT"
  ],
  "FocusSymbols": [
    "sk_getsockopt_peerpidfd",
    "scm_pidfd_recv",
    "scm_recv_unix",
    "unix_skb_scm_eq",
    "unix_maybe_add_creds",
    "__pidfs_register_pids",
    "pidfs_coredump",
    "coredump_sock_connect"
  ],
  "KMSANReasoning": "The patch series extends PIDFD passing across AF_UNIX sockets and coredump logic to support thread-level pidfds (SO_PASSPIDFD_THREAD, SO_PEERPIDFD_THREAD, SCM_PIDFD_THREAD) using a new DECLARE_PIDS macro and associated helper functions.\n\nAnalysis of KMSAN vs KASAN applicability:\n1. Kernel data copied to user space:\n   - In `sk_getsockopt_peerpidfd`, the pidfd integer and length are scalars fully initialized prior to `copy_to_sockptr`.\n   - In `sk_getsockopt`, the union `v` is zero-initialized via `memset(\u0026v, 0, sizeof(v))` before writing `v.val = sk-\u003esk_scm_pidfd_thread`.\n   - In `scm_pidfd_recv`, `put_cmsg` writes a single scalar `int pidfd` prepared by `pidfd_prepare`.\n   - There are no uninitialized struct paddings or data buffers exposed to userspace.\n\n2. Structure allocation and initialization:\n   - `struct unix_peercred` instances are zero-initialized via `= {}`.\n   - `struct scm_cookie` instances are zero-initialized via `memset` or `= {}` in all code paths (`scm_send`, `unix_stream_read_generic`, `unix_dgram_recvmsg`, `unix_destruct_scm`).\n   - `struct unix_skb_parms` resides in `skb-\u003ecb`, which is zeroed on skb allocation.\n   - `sk-\u003esk_peer_pid` is explicitly zeroed via `memset` in `sock_init_data_uid` and zero-allocated in `sk_alloc`.\n   - `struct coredump_params` is zero-initialized at declaration in `vfs_coredump`.\n\n3. Branching and control flow:\n   - All helper macros (`get_pids`, `put_pids`, `swap_pids`, `pids_equal`, `task_pids`) operate on bounds bounded strictly by compile-time checks (`pids_last()`), and no branches depend on uninitialized values.\n\nAny risks introduced by this patch primarily concern struct pid refcounting, error unwinding, or socket state, which are detected by KASAN, refcount_t sanity checks, and kmemleak. A dedicated KMSAN fuzzing session is not warranted.",
  "NeedsKMSAN": false,
  "Reasoning": "The patch introduces SO_PASSPIDFD_THREAD and SO_PEERPIDFD_THREAD socket options along with SCM_PIDFD_THREAD control messages, enabling AF_UNIX sockets to pass and query thread-level pidfds. It restructures struct sock and SCM credentials to track arrays of struct pid up to PIDTYPE_TGID, updates stream message coalescing logic to split messages across different threads when thread pidfds are requested, adds new pidfs registration helpers with WARN_ON_ONCE checks, and modifies coredump socket connection verification. These changes touch reachable core socket, AF_UNIX, pidfs, and coredump code paths that warrant fuzzing.",
  "WorthFuzzing": true
}

1/1 2026/10/05 15:13 action read-patch-diff
0m Args:
null

Results:
{
  "PatchDiff": "commit 1c4055d34a8b4ed36cbd7c628843b23623959855\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate:   Mon Oct 5 15:13:48 2026 +0000\n\n    syz-cluster: applied patch under review\n\ndiff --git a/arch/alpha/include/uapi/asm/socket.h b/arch/alpha/include/uapi/asm/socket.h\nindex 946a5fad26912..5d3524c26b2bc 100644\n--- a/arch/alpha/include/uapi/asm/socket.h\n+++ b/arch/alpha/include/uapi/asm/socket.h\n@@ -157,6 +157,10 @@\n \n #define SO_RIGHTS_NOTRUNC      85\n \n+#define SO_PASSPIDFD_THREAD    86\n+\n+#define SO_PEERPIDFD_THREAD    87\n+\n #if !defined(__KERNEL__)\n \n #if __BITS_PER_LONG == 64\ndiff --git a/arch/mips/include/uapi/asm/socket.h b/arch/mips/include/uapi/asm/socket.h\nindex f1641dde135fc..245a43f52fb1a 100644\n--- a/arch/mips/include/uapi/asm/socket.h\n+++ b/arch/mips/include/uapi/asm/socket.h\n@@ -168,6 +168,10 @@\n \n #define SO_RIGHTS_NOTRUNC      85\n \n+#define SO_PASSPIDFD_THREAD    86\n+\n+#define SO_PEERPIDFD_THREAD    87\n+\n #if !defined(__KERNEL__)\n \n #if __BITS_PER_LONG == 64\ndiff --git a/arch/parisc/include/uapi/asm/socket.h b/arch/parisc/include/uapi/asm/socket.h\nindex f3a3815c7dc29..f23710e1c6714 100644\n--- a/arch/parisc/include/uapi/asm/socket.h\n+++ b/arch/parisc/include/uapi/asm/socket.h\n@@ -149,6 +149,10 @@\n \n #define SO_RIGHTS_NOTRUNC\t0x4053\n \n+#define SO_PASSPIDFD_THREAD\t0x4054\n+\n+#define SO_PEERPIDFD_THREAD\t0x4055\n+\n #if !defined(__KERNEL__)\n \n #if __BITS_PER_LONG == 64\ndiff --git a/arch/sparc/include/uapi/asm/socket.h b/arch/sparc/include/uapi/asm/socket.h\nindex 7907f3b1f0ee0..b35b25bdefc2b 100644\n--- a/arch/sparc/include/uapi/asm/socket.h\n+++ b/arch/sparc/include/uapi/asm/socket.h\n@@ -150,6 +150,10 @@\n \n #define SO_RIGHTS_NOTRUNC        0x005e\n \n+#define SO_PASSPIDFD_THREAD      0x005f\n+\n+#define SO_PEERPIDFD_THREAD      0x0060\n+\n #if !defined(__KERNEL__)\n \n \ndiff --git a/fs/coredump.c b/fs/coredump.c\nindex 6114839f5178b..870ad720b2152 100644\n--- a/fs/coredump.c\n+++ b/fs/coredump.c\n@@ -454,7 +454,7 @@ static bool coredump_parse(struct core_name *cn, struct coredump_params *cprm,\n \t\t\t\t * leader we know that the thread-group leader\n \t\t\t\t * cannot be reaped until @current has exited.\n \t\t\t\t */\n-\t\t\t\tcprm-\u003epid = task_tgid(current);\n+\t\t\t\ttask_pids(cprm-\u003epid, current);\n \t\t\t\terr = cn_printf(cn, \"%d\", COREDUMP_PIDFD_NUMBER);\n \t\t\t\tbreak;\n \t\t\t}\n@@ -626,13 +626,17 @@ static int umh_coredump_setup(struct subprocess_info *info, struct cred *new)\n \tstruct coredump_params *cp = (struct coredump_params *)info-\u003edata;\n \tint err;\n \n-\tif (cp-\u003epid) {\n+\tif (cp-\u003epid[PIDTYPE_TGID]) {\n \t\tstruct file *pidfs_file __free(fput) = NULL;\n \n-\t\tpidfs_file = pidfs_alloc_file(cp-\u003epid, 0);\n+\t\tpidfs_file = pidfs_alloc_file(cp-\u003epid[PIDTYPE_TGID], 0);\n \t\tif (IS_ERR(pidfs_file))\n \t\t\treturn PTR_ERR(pidfs_file);\n \n+\t\terr = pidfs_register_pids(cp-\u003epid);\n+\t\tif (err)\n+\t\t\treturn err;\n+\n \t\tpidfs_coredump(cp);\n \n \t\t/*\n@@ -695,12 +699,12 @@ static bool coredump_sock_connect(struct core_name *cn, struct coredump_params *\n \t\treturn false;\n \n \t/*\n-\t * Set the thread-group leader pid which is used for the peer\n-\t * credentials during connect() below. Then immediately register\n-\t * it in pidfs...\n+\t * Set the pids of the dumping thread and its thread-group leader\n+\t * which are used for the peer credentials during connect() below.\n+\t * Then immediately register them in pidfs...\n \t */\n-\tcprm-\u003epid = task_tgid(current);\n-\tretval = pidfs_register_pid(cprm-\u003epid);\n+\ttask_pids(cprm-\u003epid, current);\n+\tretval = pidfs_register_pids(cprm-\u003epid);\n \tif (retval)\n \t\treturn false;\n \n@@ -722,7 +726,7 @@ static bool coredump_sock_connect(struct core_name *cn, struct coredump_params *\n \t}\n \n \t/* ... and validate that @sk_peer_pid matches @cprm.pid. */\n-\tif (WARN_ON_ONCE(unix_peer(socket-\u003esk)-\u003esk_peer_pid != cprm-\u003epid))\n+\tif (WARN_ON_ONCE(!pids_equal(unix_peer(socket-\u003esk)-\u003esk_peer_pid, cprm-\u003epid)))\n \t\treturn false;\n \n \tcprm-\u003elimit = RLIM_INFINITY;\ndiff --git a/fs/pidfs.c b/fs/pidfs.c\nindex a6a643f15d08a..29299b2c7ca71 100644\n--- a/fs/pidfs.c\n+++ b/fs/pidfs.c\n@@ -793,9 +793,9 @@ void pidfs_exit(struct task_struct *tsk)\n }\n \n #ifdef CONFIG_COREDUMP\n-void pidfs_coredump(const struct coredump_params *cprm)\n+static void pidfs_coredump_pid(struct pid *pid,\n+\t\t\t       const struct coredump_params *cprm)\n {\n-\tstruct pid *pid = cprm-\u003epid;\n \tstruct pidfs_attr *attr;\n \n \tattr = READ_ONCE(pid-\u003eattr);\n@@ -814,6 +814,13 @@ void pidfs_coredump(const struct coredump_params *cprm)\n \tsmp_wmb();\n \tset_bit(PIDFS_ATTR_BIT_COREDUMP, \u0026attr-\u003eattr_mask);\n }\n+\n+void pidfs_coredump(const struct coredump_params *cprm)\n+{\n+\t/* The dumping thread's pidfd reports the coredump as well. */\n+\tfor (enum pid_type type = PIDTYPE_PID; type \u003c= pids_last(cprm-\u003epid); type++)\n+\t\tpidfs_coredump_pid(cprm-\u003epid[type], cprm);\n+}\n #endif\n \n static struct vfsmount *pidfs_mnt __ro_after_init;\n@@ -1070,6 +1077,22 @@ int pidfs_register_pid_gfp(struct pid *pid, gfp_t gfp)\n \treturn 0;\n }\n \n+/* Register the pids up to pid type @last of @pids in pidfs. */\n+int __pidfs_register_pids(struct pid *const *pids, enum pid_type last)\n+{\n+\tif (WARN_ON_ONCE(last \u003e= PIDTYPE_MAX))\n+\t\treturn -EINVAL;\n+\n+\tfor (enum pid_type type = PIDTYPE_PID; type \u003c= last; type++) {\n+\t\tint ret = pidfs_register_pid(pids[type]);\n+\n+\t\tif (unlikely(ret))\n+\t\t\treturn ret;\n+\t}\n+\n+\treturn 0;\n+}\n+\n static struct dentry *pidfs_stash_dentry(struct dentry **stashed,\n \t\t\t\t\t struct dentry *dentry)\n {\ndiff --git a/include/linux/coredump.h b/include/linux/coredump.h\nindex 7b38ee2e7913b..0bbb7de6a4021 100644\n--- a/include/linux/coredump.h\n+++ b/include/linux/coredump.h\n@@ -5,6 +5,7 @@\n #include \u003clinux/types.h\u003e\n #include \u003clinux/mm.h\u003e\n #include \u003clinux/fs.h\u003e\n+#include \u003clinux/pid_types.h\u003e\n #include \u003clinux/sched/coredump.h\u003e\n #include \u003casm/siginfo.h\u003e\n \n@@ -32,7 +33,8 @@ struct coredump_params {\n \tint vma_count;\n \tsize_t vma_data_size;\n \tstruct core_vma_metadata *vma_meta;\n-\tstruct pid *pid;\n+\t/* Dumping thread and its thread-group leader by pid type. */\n+\tDECLARE_PIDS(pid, PIDTYPE_TGID);\n };\n \n extern unsigned int core_file_note_size_limit;\ndiff --git a/include/linux/pid.h b/include/linux/pid.h\nindex ddaef0bbc8ba3..87635d0cc1f71 100644\n--- a/include/linux/pid.h\n+++ b/include/linux/pid.h\n@@ -2,6 +2,9 @@\n #ifndef _LINUX_PID_H\n #define _LINUX_PID_H\n \n+#include \u003clinux/array_size.h\u003e\n+#include \u003clinux/build_bug.h\u003e\n+#include \u003clinux/minmax.h\u003e\n #include \u003clinux/pid_types.h\u003e\n #include \u003clinux/rculist.h\u003e\n #include \u003clinux/rcupdate.h\u003e\n@@ -92,6 +95,56 @@ static inline struct pid *get_pid(struct pid *pid)\n }\n \n extern void put_pid(struct pid *pid);\n+\n+/*\n+ * Helpers for arrays of struct pid indexed by pid type declared with\n+ * DECLARE_PIDS(). The array covers PIDTYPE_PID up to the pid type it\n+ * was declared with and the helpers take that bound from the array.\n+ */\n+static inline void __get_pids(struct pid **dst, struct pid *const *src,\n+\t\t\t      enum pid_type last)\n+{\n+\tfor (enum pid_type type = PIDTYPE_PID; type \u003c= last; type++)\n+\t\tdst[type] = get_pid(src[type]);\n+}\n+\n+static inline void __put_pids(struct pid **pids, enum pid_type last)\n+{\n+\tfor (enum pid_type type = PIDTYPE_PID; type \u003c= last; type++) {\n+\t\tput_pid(pids[type]);\n+\t\tpids[type] = NULL;\n+\t}\n+}\n+\n+static inline void __swap_pids(struct pid **a, struct pid **b,\n+\t\t\t       enum pid_type last)\n+{\n+\tfor (enum pid_type type = PIDTYPE_PID; type \u003c= last; type++)\n+\t\tswap(a[type], b[type]);\n+}\n+\n+static inline bool __pids_equal(struct pid *const *a, struct pid *const *b,\n+\t\t\t\tenum pid_type last)\n+{\n+\tfor (enum pid_type type = PIDTYPE_PID; type \u003c= last; type++)\n+\t\tif (a[type] != b[type])\n+\t\t\treturn false;\n+\treturn true;\n+}\n+\n+/* The last pid type an array declared with DECLARE_PIDS() covers. */\n+#define pids_last(pids)\t\t\t\t\t\t\t\\\n+\t((enum pid_type)(ARRAY_SIZE(pids) - 1 +\t\t\t\t\\\n+\t\t\t BUILD_BUG_ON_ZERO(ARRAY_SIZE(pids) \u003e PIDTYPE_MAX)))\n+\n+#define __pids_last2(a, b)\t\t\t\t\t\t\\\n+\t(pids_last(a) + BUILD_BUG_ON_ZERO(ARRAY_SIZE(a) != ARRAY_SIZE(b)))\n+\n+#define get_pids(dst, src)\t__get_pids(dst, src, __pids_last2(dst, src))\n+#define put_pids(pids)\t\t__put_pids(pids, pids_last(pids))\n+#define swap_pids(a, b)\t\t__swap_pids(a, b, __pids_last2(a, b))\n+#define pids_equal(a, b)\t__pids_equal(a, b, __pids_last2(a, b))\n+\n extern struct task_struct *pid_task(struct pid *pid, enum pid_type);\n static inline bool pid_has_task(struct pid *pid, enum pid_type type)\n {\ndiff --git a/include/linux/pid_types.h b/include/linux/pid_types.h\nindex c2aee1d91dcfd..3302690a2a281 100644\n--- a/include/linux/pid_types.h\n+++ b/include/linux/pid_types.h\n@@ -2,6 +2,8 @@\n #ifndef _LINUX_PID_TYPES_H\n #define _LINUX_PID_TYPES_H\n \n+#include \u003clinux/build_bug.h\u003e\n+\n enum pid_type {\n \tPIDTYPE_PID,\n \tPIDTYPE_TGID,\n@@ -10,6 +12,12 @@ enum pid_type {\n \tPIDTYPE_MAX,\n };\n \n+struct pid;\n+\n+/* An array of struct pid indexed by pid type, PIDTYPE_PID up to @last. */\n+#define DECLARE_PIDS(name, last)\t\t\t\t\t\\\n+\tstruct pid *name[(last) + 1 + BUILD_BUG_ON_ZERO((last) \u003e= PIDTYPE_MAX)]\n+\n struct pid_namespace;\n extern struct pid_namespace init_pid_ns;\n \ndiff --git a/include/linux/pidfs.h b/include/linux/pidfs.h\nindex 0abf7da9ab236..3c1e82f1a3691 100644\n--- a/include/linux/pidfs.h\n+++ b/include/linux/pidfs.h\n@@ -3,6 +3,7 @@\n #define _LINUX_PID_FS_H\n \n #include \u003clinux/gfp_types.h\u003e\n+#include \u003clinux/pid.h\u003e\n \n struct coredump_params;\n \n@@ -32,5 +33,9 @@ static inline int pidfs_register_pid(struct pid *pid)\n }\n \n void pidfs_free_pid(struct pid *pid);\n+int __pidfs_register_pids(struct pid *const *pids, enum pid_type last);\n+\n+/* Register the pids of an array declared with DECLARE_PIDS(). */\n+#define pidfs_register_pids(pids)\t__pidfs_register_pids(pids, pids_last(pids))\n \n #endif /* _LINUX_PID_FS_H */\ndiff --git a/include/linux/sched/signal.h b/include/linux/sched/signal.h\nindex d45a5476b97de..3149cdb976c60 100644\n--- a/include/linux/sched/signal.h\n+++ b/include/linux/sched/signal.h\n@@ -674,6 +674,24 @@ struct pid *task_pid_type(struct task_struct *task, enum pid_type type)\n \treturn pid;\n }\n \n+/* Fill @pids with the pid types of @task up to @last, without references. */\n+static inline void __task_pids(struct pid **pids, enum pid_type last,\n+\t\t\t       struct task_struct *task)\n+{\n+\tfor (enum pid_type type = PIDTYPE_PID; type \u003c= last; type++)\n+\t\tpids[type] = task_pid_type(task, type);\n+}\n+\n+static inline void __get_task_pids(struct pid **pids, enum pid_type last,\n+\t\t\t\t   struct task_struct *task)\n+{\n+\tfor (enum pid_type type = PIDTYPE_PID; type \u003c= last; type++)\n+\t\tpids[type] = get_pid(task_pid_type(task, type));\n+}\n+\n+#define task_pids(pids, task)\t\t__task_pids(pids, pids_last(pids), task)\n+#define get_task_pids(pids, task)\t__get_task_pids(pids, pids_last(pids), task)\n+\n static inline struct pid *task_tgid(struct task_struct *task)\n {\n \treturn task-\u003esignal-\u003epids[PIDTYPE_TGID];\ndiff --git a/include/linux/socket.h b/include/linux/socket.h\nindex 5a5eb12501032..b17fdb7d38a77 100644\n--- a/include/linux/socket.h\n+++ b/include/linux/socket.h\n@@ -189,10 +189,11 @@ static inline size_t msg_data_left(const struct msghdr *msg)\n \n /* \"Socket\"-level control message types: */\n \n-#define\tSCM_RIGHTS\t0x01\t\t/* rw: access rights (array of int) */\n-#define SCM_CREDENTIALS 0x02\t\t/* rw: struct ucred\t\t*/\n-#define SCM_SECURITY\t0x03\t\t/* rw: security label\t\t*/\n-#define SCM_PIDFD\t0x04\t\t/* ro: pidfd (int)\t\t*/\n+#define\tSCM_RIGHTS\t\t0x01\t/* rw: access rights (array of int) */\n+#define SCM_CREDENTIALS\t\t0x02\t/* rw: struct ucred\t\t*/\n+#define SCM_SECURITY\t\t0x03\t/* rw: security label\t\t*/\n+#define SCM_PIDFD\t\t0x04\t/* ro: pidfd (int)\t\t*/\n+#define SCM_PIDFD_THREAD\t0x05\t/* ro: thread pidfd (int)\t*/\n \n struct ucred {\n \t__u32\tpid;\ndiff --git a/include/net/scm.h b/include/net/scm.h\nindex 86ae6bc109ec6..b68a143f42de5 100644\n--- a/include/net/scm.h\n+++ b/include/net/scm.h\n@@ -42,7 +42,7 @@ struct scm_fp_list {\n };\n \n struct scm_cookie {\n-\tstruct pid\t\t*pid;\t\t/* Skb credentials */\n+\tDECLARE_PIDS(pid, PIDTYPE_TGID);\t/* Skb credentials by pid type */\n \tstruct scm_fp_list\t*fp;\t\t/* Passed files\t\t*/\n \tstruct scm_creds\tcreds;\t\t/* Skb credentials\t*/\n #ifdef CONFIG_SECURITY_NETWORK\n@@ -69,7 +69,6 @@ static __inline__ void unix_get_peersec_dgram(struct socket *sock, struct scm_co\n static __inline__ void scm_set_cred(struct scm_cookie *scm,\n \t\t\t\t    struct pid *pid, kuid_t uid, kgid_t gid)\n {\n-\tscm-\u003epid = get_pid(pid);\n \tscm-\u003ecreds.pid = pid_vnr(pid);\n \tscm-\u003ecreds.uid = uid;\n \tscm-\u003ecreds.gid = gid;\n@@ -77,8 +76,7 @@ static __inline__ void scm_set_cred(struct scm_cookie *scm,\n \n static __inline__ void scm_destroy_cred(struct scm_cookie *scm)\n {\n-\tput_pid(scm-\u003epid);\n-\tscm-\u003epid = NULL;\n+\tput_pids(scm-\u003epid);\n }\n \n static __inline__ void scm_destroy(struct scm_cookie *scm)\n@@ -94,8 +92,10 @@ static __inline__ int scm_send(struct socket *sock, struct msghdr *msg,\n \tmemset(scm, 0, sizeof(*scm));\n \tscm-\u003ecreds.uid = INVALID_UID;\n \tscm-\u003ecreds.gid = INVALID_GID;\n-\tif (forcecreds)\n+\tif (forcecreds) {\n+\t\tscm-\u003epid[PIDTYPE_TGID] = get_pid(task_tgid(current));\n \t\tscm_set_cred(scm, task_tgid(current), current_uid(), current_gid());\n+\t}\n \tunix_get_peersec_dgram(sock, scm);\n \tif (msg-\u003emsg_controllen \u003c= 0)\n \t\treturn 0;\ndiff --git a/include/net/sock.h b/include/net/sock.h\nindex 60ea55dc18854..77dfb170d3c89 100644\n--- a/include/net/sock.h\n+++ b/include/net/sock.h\n@@ -301,7 +301,7 @@ struct sk_filter;\n   *\t@sk_type: socket type (%SOCK_STREAM, etc)\n   *\t@sk_protocol: which protocol this socket belongs in this network family\n   *\t@sk_peer_lock: lock protecting @sk_peer_pid and @sk_peer_cred\n-  *\t@sk_peer_pid: \u0026struct pid for this socket's peer\n+  *\t@sk_peer_pid: \u0026struct pid for this socket's peer, by pid type\n   *\t@sk_peer_cred: %SO_PEERCRED setting\n   *\t@sk_rcvlowat: %SO_RCVLOWAT setting\n   *\t@sk_rcvtimeo: %SO_RCVTIMEO setting\n@@ -356,6 +356,7 @@ struct sk_filter;\n   *\t@sk_scm_security: flagged by SO_PASSSEC to recv SCM_SECURITY\n   *\t@sk_scm_pidfd: flagged by SO_PASSPIDFD to recv SCM_PIDFD\n   *\t@sk_scm_rights: flagged by SO_PASSRIGHTS to recv SCM_RIGHTS\n+  *\t@sk_scm_pidfd_thread: flagged by SO_PASSPIDFD_THREAD to recv a thread SCM_PIDFD\n   *\t@sk_scm_unused: unused flags for scm_recv()\n   *\t@ns_tracker: tracker for netns reference\n   *\t@sk_user_frags: xarray of pages the user is holding a reference on.\n@@ -545,7 +546,7 @@ struct sock {\n \tu64\t\t\tsk_ino;\n \tspinlock_t\t\tsk_peer_lock;\n \tint\t\t\tsk_bind_phc;\n-\tstruct pid\t\t*sk_peer_pid;\n+\tDECLARE_PIDS(sk_peer_pid, PIDTYPE_TGID);\n \tconst struct cred\t*sk_peer_cred;\n \n \tktime_t\t\t\tsk_stamp;\n@@ -562,7 +563,8 @@ struct sock {\n \t\t\t\tsk_scm_security : 1,\n \t\t\t\tsk_scm_pidfd : 1,\n \t\t\t\tsk_scm_rights : 1,\n-\t\t\t\tsk_scm_unused : 4;\n+\t\t\t\tsk_scm_pidfd_thread : 1,\n+\t\t\t\tsk_scm_unused : 3;\n \t\t};\n \t};\n \tu8\t\t\tsk_clockid;\ndiff --git a/include/trace/events/landlock.h b/include/trace/events/landlock.h\nindex 3a43638c9bc29..9e172ea22d95c 100644\n--- a/include/trace/events/landlock.h\n+++ b/include/trace/events/landlock.h\n@@ -1037,7 +1037,7 @@ TRACE_EVENT(landlock_deny_scope_abstract_unix_socket,\n \t\t * updates.  The peer socket keeps a reference to sk_peer_pid\n \t\t * through pid_nr(); sun_path is the reliable identifier.\n \t\t */\n-\t\tpeer_pid\t\t= READ_ONCE(peer-\u003esk_peer_pid);\n+\t\tpeer_pid\t\t= READ_ONCE(peer-\u003esk_peer_pid[PIDTYPE_TGID]);\n \t\t__entry-\u003epeer_pid\t= peer_pid ? pid_nr(peer_pid) : 0;\n \t\t__assign_str(sun_path);\n \t),\ndiff --git a/include/uapi/asm-generic/socket.h b/include/uapi/asm-generic/socket.h\nindex 84ea7b92936e2..56fed7ab27ab8 100644\n--- a/include/uapi/asm-generic/socket.h\n+++ b/include/uapi/asm-generic/socket.h\n@@ -152,6 +152,10 @@\n \n #define SO_RIGHTS_NOTRUNC\t85\n \n+#define SO_PASSPIDFD_THREAD\t86\n+\n+#define SO_PEERPIDFD_THREAD\t87\n+\n #if !defined(__KERNEL__)\n \n #if __BITS_PER_LONG == 64 || (defined(__x86_64__) \u0026\u0026 defined(__ILP32__))\ndiff --git a/net/bluetooth/af_bluetooth.c b/net/bluetooth/af_bluetooth.c\nindex 411d66f24393d..7758e9ea3848c 100644\n--- a/net/bluetooth/af_bluetooth.c\n+++ b/net/bluetooth/af_bluetooth.c\n@@ -161,7 +161,7 @@ struct sock *bt_sock_alloc(struct net *net, struct socket *sock,\n \t/* Init peer information so it can be properly monitored */\n \tif (!kern) {\n \t\tspin_lock(\u0026sk-\u003esk_peer_lock);\n-\t\tsk-\u003esk_peer_pid  = get_pid(task_tgid(current));\n+\t\tsk-\u003esk_peer_pid[PIDTYPE_TGID]  = get_pid(task_tgid(current));\n \t\tsk-\u003esk_peer_cred = get_current_cred();\n \t\tspin_unlock(\u0026sk-\u003esk_peer_lock);\n \t}\n@@ -235,9 +235,9 @@ void bt_accept_enqueue(struct sock *parent, struct sock *sk, bool bh)\n \t * socket is allocated by the kernel.\n \t */\n \tspin_lock(\u0026sk-\u003esk_peer_lock);\n-\told_pid = sk-\u003esk_peer_pid;\n+\told_pid = sk-\u003esk_peer_pid[PIDTYPE_TGID];\n \told_cred = sk-\u003esk_peer_cred;\n-\tsk-\u003esk_peer_pid = get_pid(parent-\u003esk_peer_pid);\n+\tsk-\u003esk_peer_pid[PIDTYPE_TGID] = get_pid(parent-\u003esk_peer_pid[PIDTYPE_TGID]);\n \tsk-\u003esk_peer_cred = get_cred(parent-\u003esk_peer_cred);\n \tspin_unlock(\u0026sk-\u003esk_peer_lock);\n \ndiff --git a/net/bluetooth/hci_sock.c b/net/bluetooth/hci_sock.c\nindex 6d56c77741e19..4c40068ba5fb2 100644\n--- a/net/bluetooth/hci_sock.c\n+++ b/net/bluetooth/hci_sock.c\n@@ -284,21 +284,21 @@ static void hci_sock_copy_creds(struct sock *sk, struct sk_buff *skb)\n \tcreds = \u0026bt_cb(skb)-\u003ecreds;\n \n \t/* Check if peer credentials is set */\n-\tif (!sk-\u003esk_peer_pid) {\n+\tif (!sk-\u003esk_peer_pid[PIDTYPE_TGID]) {\n \t\t/* Check if parent peer credentials is set */\n-\t\tif (bt_sk(sk)-\u003eparent \u0026\u0026 bt_sk(sk)-\u003eparent-\u003esk_peer_pid)\n+\t\tif (bt_sk(sk)-\u003eparent \u0026\u0026 bt_sk(sk)-\u003eparent-\u003esk_peer_pid[PIDTYPE_TGID])\n \t\t\tsk = bt_sk(sk)-\u003eparent;\n \t\telse\n \t\t\treturn;\n \t}\n \n \t/* Check if scm_creds already set */\n-\tif (creds-\u003epid == pid_vnr(sk-\u003esk_peer_pid))\n+\tif (creds-\u003epid == pid_vnr(sk-\u003esk_peer_pid[PIDTYPE_TGID]))\n \t\treturn;\n \n \tmemset(creds, 0, sizeof(*creds));\n \n-\tcreds-\u003epid = pid_vnr(sk-\u003esk_peer_pid);\n+\tcreds-\u003epid = pid_vnr(sk-\u003esk_peer_pid[PIDTYPE_TGID]);\n \tif (sk-\u003esk_peer_cred) {\n \t\tcreds-\u003euid = sk-\u003esk_peer_cred-\u003euid;\n \t\tcreds-\u003egid = sk-\u003esk_peer_cred-\u003egid;\ndiff --git a/net/bluetooth/l2cap_sock.c b/net/bluetooth/l2cap_sock.c\nindex 1194c37e466f1..872d8fb31b6fb 100644\n--- a/net/bluetooth/l2cap_sock.c\n+++ b/net/bluetooth/l2cap_sock.c\n@@ -1890,7 +1890,7 @@ static struct pid *l2cap_sock_get_peer_pid_cb(struct l2cap_chan *chan)\n {\n \tstruct sock *sk = chan-\u003edata;\n \n-\treturn sk-\u003esk_peer_pid;\n+\treturn sk-\u003esk_peer_pid[PIDTYPE_TGID];\n }\n \n static void l2cap_sock_suspend_cb(struct l2cap_chan *chan)\ndiff --git a/net/core/scm.c b/net/core/scm.c\nindex f0d44ecdb11fc..00025579d53e0 100644\n--- a/net/core/scm.c\n+++ b/net/core/scm.c\n@@ -149,6 +149,7 @@ EXPORT_SYMBOL(__scm_destroy);\n \n static inline int scm_replace_pid(struct scm_cookie *scm, struct pid *pid)\n {\n+\tstruct pid *thread_pid;\n \tint err;\n \n \t/* drop all previous references */\n@@ -158,7 +159,18 @@ static inline int scm_replace_pid(struct scm_cookie *scm, struct pid *pid)\n \tif (unlikely(err))\n \t\treturn err;\n \n-\tscm-\u003epid = pid;\n+\t/* A sender naming its own thread-group sends from the current thread. */\n+\tif (pid == task_tgid(current))\n+\t\tthread_pid = task_pid(current);\n+\telse\n+\t\tthread_pid = pid;\n+\n+\terr = pidfs_register_pid(thread_pid);\n+\tif (unlikely(err))\n+\t\treturn err;\n+\n+\tscm-\u003epid[PIDTYPE_TGID] = pid;\n+\tscm-\u003epid[PIDTYPE_PID] = get_pid(thread_pid);\n \tscm-\u003ecreds.pid = pid_vnr(pid);\n \treturn 0;\n }\n@@ -207,7 +219,8 @@ int __scm_send(struct socket *sock, struct msghdr *msg, struct scm_cookie *p)\n \t\t\tif (err)\n \t\t\t\tgoto error;\n \n-\t\t\tif (!p-\u003epid || pid_vnr(p-\u003epid) != creds.pid) {\n+\t\t\tif (!p-\u003epid[PIDTYPE_TGID] ||\n+\t\t\t    pid_vnr(p-\u003epid[PIDTYPE_TGID]) != creds.pid) {\n \t\t\t\tstruct pid *pid;\n \t\t\t\terr = -ESRCH;\n \t\t\t\tpid = find_get_pid(creds.pid);\n@@ -486,11 +499,16 @@ static bool scm_has_secdata(struct sock *sk)\n }\n #endif\n \n-static void scm_pidfd_recv(struct msghdr *msg, struct scm_cookie *scm)\n+static void scm_pidfd_recv(struct msghdr *msg, struct scm_cookie *scm,\n+\t\t\t   enum pid_type type, int cmsg_type)\n {\n+\tunsigned int flags = PIDFD_STALE;\n \tstruct file *pidfd_file = NULL;\n+\tstruct pid *pid;\n \tint len, pidfd;\n \n+\tpid = scm-\u003epid[type];\n+\n \t/* put_cmsg() doesn't return an error if CMSG is truncated,\n \t * that's why we need to opencode these checks here.\n \t */\n@@ -504,12 +522,15 @@ static void scm_pidfd_recv(struct msghdr *msg, struct scm_cookie *scm)\n \t\treturn;\n \t}\n \n-\tif (!scm-\u003epid)\n+\tif (!pid)\n \t\treturn;\n \n-\tpidfd = pidfd_prepare(scm-\u003epid, PIDFD_STALE, \u0026pidfd_file);\n+\tif (type == PIDTYPE_PID)\n+\t\tflags |= PIDFD_THREAD;\n \n-\tif (put_cmsg(msg, SOL_SOCKET, SCM_PIDFD, sizeof(int), \u0026pidfd)) {\n+\tpidfd = pidfd_prepare(pid, flags, \u0026pidfd_file);\n+\n+\tif (put_cmsg(msg, SOL_SOCKET, cmsg_type, sizeof(int), \u0026pidfd)) {\n \t\tif (pidfd_file) {\n \t\t\tput_unused_fd(pidfd);\n \t\t\tfput(pidfd_file);\n@@ -527,7 +548,7 @@ static bool __scm_recv_common(struct sock *sk, struct msghdr *msg,\n {\n \tif (!msg-\u003emsg_control) {\n \t\tif (sk-\u003esk_scm_credentials || sk-\u003esk_scm_pidfd ||\n-\t\t    scm-\u003efp || scm_has_secdata(sk))\n+\t\t    sk-\u003esk_scm_pidfd_thread || scm-\u003efp || scm_has_secdata(sk))\n \t\t\tmsg-\u003emsg_flags |= MSG_CTRUNC;\n \n \t\tscm_destroy(scm);\n@@ -574,7 +595,10 @@ void scm_recv_unix(struct socket *sock, struct msghdr *msg,\n \t}\n \n \tif (sock-\u003esk-\u003esk_scm_pidfd)\n-\t\tscm_pidfd_recv(msg, scm);\n+\t\tscm_pidfd_recv(msg, scm, PIDTYPE_TGID, SCM_PIDFD);\n+\n+\tif (sock-\u003esk-\u003esk_scm_pidfd_thread)\n+\t\tscm_pidfd_recv(msg, scm, PIDTYPE_PID, SCM_PIDFD_THREAD);\n \n \tscm_destroy_cred(scm);\n }\ndiff --git a/net/core/sock.c b/net/core/sock.c\nindex e8551df8330ff..3c25d66ba31af 100644\n--- a/net/core/sock.c\n+++ b/net/core/sock.c\n@@ -1578,6 +1578,13 @@ int sk_setsockopt(struct sock *sk, int level, int optname,\n \t\t\tret = -EOPNOTSUPP;\n \t\tbreak;\n \n+\tcase SO_PASSPIDFD_THREAD:\n+\t\tif (sk_is_unix(sk))\n+\t\t\tsk-\u003esk_scm_pidfd_thread = valbool;\n+\t\telse\n+\t\t\tret = -EOPNOTSUPP;\n+\t\tbreak;\n+\n \tcase SO_PASSRIGHTS:\n \t\tif (sk_is_unix(sk))\n \t\t\tsk-\u003esk_scm_rights = valbool;\n@@ -1729,6 +1736,50 @@ static int groups_to_user(sockptr_t dst, const struct group_info *src)\n \treturn 0;\n }\n \n+/* Hand out a pidfd for @type of the socket's peer via SO_PEERPIDFD*. */\n+static int sk_getsockopt_peerpidfd(struct sock *sk, enum pid_type type,\n+\t\t\t\t   sockptr_t optval, sockptr_t optlen, int len)\n+{\n+\tstruct file *pidfd_file = NULL;\n+\tunsigned int flags = 0;\n+\tstruct pid *peer_pid;\n+\tint pidfd;\n+\n+\tif (len \u003e sizeof(pidfd))\n+\t\tlen = sizeof(pidfd);\n+\n+\tspin_lock(\u0026sk-\u003esk_peer_lock);\n+\tpeer_pid = get_pid(sk-\u003esk_peer_pid[type]);\n+\tspin_unlock(\u0026sk-\u003esk_peer_lock);\n+\n+\tif (!peer_pid)\n+\t\treturn -ENODATA;\n+\n+\t/* The use of PIDFD_STALE requires stashing of struct pid on pidfs\n+\t * with pidfs_register_pid() and only AF_UNIX is prepared for this.\n+\t */\n+\tif (sk_is_unix(sk))\n+\t\tflags |= PIDFD_STALE;\n+\tif (type == PIDTYPE_PID)\n+\t\tflags |= PIDFD_THREAD;\n+\n+\tpidfd = pidfd_prepare(peer_pid, flags, \u0026pidfd_file);\n+\tput_pid(peer_pid);\n+\tif (pidfd \u003c 0)\n+\t\treturn pidfd;\n+\n+\tif (copy_to_sockptr(optval, \u0026pidfd, len) ||\n+\t    copy_to_sockptr(optlen, \u0026len, sizeof(int))) {\n+\t\tput_unused_fd(pidfd);\n+\t\tfput(pidfd_file);\n+\n+\t\treturn -EFAULT;\n+\t}\n+\n+\tfd_install(pidfd, pidfd_file);\n+\treturn 0;\n+}\n+\n int sk_getsockopt(struct sock *sk, int level, int optname,\n \t\t  sockptr_t optval, sockptr_t optlen)\n {\n@@ -1893,6 +1944,13 @@ int sk_getsockopt(struct sock *sk, int level, int optname,\n \t\tv.val = sk-\u003esk_scm_pidfd;\n \t\tbreak;\n \n+\tcase SO_PASSPIDFD_THREAD:\n+\t\tif (!sk_is_unix(sk))\n+\t\t\treturn -EOPNOTSUPP;\n+\n+\t\tv.val = sk-\u003esk_scm_pidfd_thread;\n+\t\tbreak;\n+\n \tcase SO_PASSRIGHTS:\n \t\tif (!sk_is_unix(sk))\n \t\t\treturn -EOPNOTSUPP;\n@@ -1907,7 +1965,8 @@ int sk_getsockopt(struct sock *sk, int level, int optname,\n \t\t\tlen = sizeof(peercred);\n \n \t\tspin_lock(\u0026sk-\u003esk_peer_lock);\n-\t\tcred_to_ucred(sk-\u003esk_peer_pid, sk-\u003esk_peer_cred, \u0026peercred);\n+\t\tcred_to_ucred(sk-\u003esk_peer_pid[PIDTYPE_TGID], sk-\u003esk_peer_cred,\n+\t\t\t      \u0026peercred);\n \t\tspin_unlock(\u0026sk-\u003esk_peer_lock);\n \n \t\tif (copy_to_sockptr(optval, \u0026peercred, len))\n@@ -1916,45 +1975,14 @@ int sk_getsockopt(struct sock *sk, int level, int optname,\n \t}\n \n \tcase SO_PEERPIDFD:\n-\t{\n-\t\tstruct pid *peer_pid;\n-\t\tstruct file *pidfd_file = NULL;\n-\t\tunsigned int flags = 0;\n-\t\tint pidfd;\n-\n-\t\tif (len \u003e sizeof(pidfd))\n-\t\t\tlen = sizeof(pidfd);\n-\n-\t\tspin_lock(\u0026sk-\u003esk_peer_lock);\n-\t\tpeer_pid = get_pid(sk-\u003esk_peer_pid);\n-\t\tspin_unlock(\u0026sk-\u003esk_peer_lock);\n-\n-\t\tif (!peer_pid)\n-\t\t\treturn -ENODATA;\n-\n-\t\t/* The use of PIDFD_STALE requires stashing of struct pid\n-\t\t * on pidfs with pidfs_register_pid() and only AF_UNIX\n-\t\t * were prepared for this.\n-\t\t */\n-\t\tif (sk-\u003esk_family == AF_UNIX)\n-\t\t\tflags = PIDFD_STALE;\n+\t\treturn sk_getsockopt_peerpidfd(sk, PIDTYPE_TGID, optval, optlen, len);\n \n-\t\tpidfd = pidfd_prepare(peer_pid, flags, \u0026pidfd_file);\n-\t\tput_pid(peer_pid);\n-\t\tif (pidfd \u003c 0)\n-\t\t\treturn pidfd;\n-\n-\t\tif (copy_to_sockptr(optval, \u0026pidfd, len) ||\n-\t\t    copy_to_sockptr(optlen, \u0026len, sizeof(int))) {\n-\t\t\tput_unused_fd(pidfd);\n-\t\t\tfput(pidfd_file);\n-\n-\t\t\treturn -EFAULT;\n-\t\t}\n+\tcase SO_PEERPIDFD_THREAD:\n+\t\t/* Only AF_UNIX records the peer's connecting thread. */\n+\t\tif (!sk_is_unix(sk))\n+\t\t\treturn -EOPNOTSUPP;\n \n-\t\tfd_install(pidfd, pidfd_file);\n-\t\treturn 0;\n-\t}\n+\t\treturn sk_getsockopt_peerpidfd(sk, PIDTYPE_PID, optval, optlen, len);\n \n \tcase SO_PEERGROUPS:\n \t{\n@@ -2380,7 +2408,7 @@ static void __sk_destruct(struct rcu_head *head)\n \n \t/* We do not need to acquire sk-\u003esk_peer_lock, we are the last user. */\n \tput_cred(sk-\u003esk_peer_cred);\n-\tput_pid(sk-\u003esk_peer_pid);\n+\tput_pids(sk-\u003esk_peer_pid);\n \n \tif (likely(sk-\u003esk_net_refcnt)) {\n \t\tput_net_track(net, \u0026sk-\u003ens_tracker);\n@@ -3785,7 +3813,7 @@ void sock_init_data_uid(struct socket *sock, struct sock *sk, kuid_t uid)\n \tsk-\u003esk_frag.offset\t=\t0;\n \tsk-\u003esk_peek_off\t\t=\t-1;\n \n-\tsk-\u003esk_peer_pid \t=\tNULL;\n+\tmemset(sk-\u003esk_peer_pid, 0, sizeof(sk-\u003esk_peer_pid));\n \tsk-\u003esk_peer_cred\t=\tNULL;\n \tspin_lock_init(\u0026sk-\u003esk_peer_lock);\n \ndiff --git a/net/unix/af_unix.c b/net/unix/af_unix.c\nindex 42cffeafc8c14..a78e8ef3e0b0e 100644\n--- a/net/unix/af_unix.c\n+++ b/net/unix/af_unix.c\n@@ -737,57 +737,53 @@ static void unix_release_sock(struct sock *sk, int embrion)\n }\n \n struct unix_peercred {\n-\tstruct pid *peer_pid;\n+\tDECLARE_PIDS(peer_pid, PIDTYPE_TGID);\n \tconst struct cred *peer_cred;\n };\n \n static inline int prepare_peercred(struct unix_peercred *peercred)\n {\n-\tstruct pid *pid;\n \tint err;\n \n-\tpid = task_tgid(current);\n-\terr = pidfs_register_pid(pid);\n-\tif (likely(!err)) {\n-\t\tpeercred-\u003epeer_pid = get_pid(pid);\n-\t\tpeercred-\u003epeer_cred = get_current_cred();\n+\tget_task_pids(peercred-\u003epeer_pid, current);\n+\terr = pidfs_register_pids(peercred-\u003epeer_pid);\n+\tif (unlikely(err)) {\n+\t\tput_pids(peercred-\u003epeer_pid);\n+\t\treturn err;\n \t}\n-\treturn err;\n+\n+\tpeercred-\u003epeer_cred = get_current_cred();\n+\treturn 0;\n }\n \n static void drop_peercred(struct unix_peercred *peercred)\n {\n \tconst struct cred *cred = NULL;\n-\tstruct pid *pid = NULL;\n \n \tmight_sleep();\n \n-\tswap(peercred-\u003epeer_pid, pid);\n+\tput_pids(peercred-\u003epeer_pid);\n \tswap(peercred-\u003epeer_cred, cred);\n-\n-\tput_pid(pid);\n \tput_cred(cred);\n }\n \n static inline void init_peercred(struct sock *sk,\n \t\t\t\t const struct unix_peercred *peercred)\n {\n-\tsk-\u003esk_peer_pid = peercred-\u003epeer_pid;\n+\tmemcpy(sk-\u003esk_peer_pid, peercred-\u003epeer_pid, sizeof(sk-\u003esk_peer_pid));\n \tsk-\u003esk_peer_cred = peercred-\u003epeer_cred;\n }\n \n static void update_peercred(struct sock *sk, struct unix_peercred *peercred)\n {\n \tconst struct cred *old_cred;\n-\tstruct pid *old_pid;\n \n \tspin_lock(\u0026sk-\u003esk_peer_lock);\n-\told_pid = sk-\u003esk_peer_pid;\n+\tswap_pids(sk-\u003esk_peer_pid, peercred-\u003epeer_pid);\n \told_cred = sk-\u003esk_peer_cred;\n-\tinit_peercred(sk, peercred);\n+\tsk-\u003esk_peer_cred = peercred-\u003epeer_cred;\n \tspin_unlock(\u0026sk-\u003esk_peer_lock);\n \n-\tpeercred-\u003epeer_pid = old_pid;\n \tpeercred-\u003epeer_cred = old_cred;\n }\n \n@@ -796,14 +792,15 @@ static void copy_peercred(struct sock *sk, struct sock *peersk)\n \tlockdep_assert_held(\u0026unix_sk(peersk)-\u003elock);\n \n \tspin_lock(\u0026sk-\u003esk_peer_lock);\n-\tsk-\u003esk_peer_pid = get_pid(peersk-\u003esk_peer_pid);\n+\tget_pids(sk-\u003esk_peer_pid, peersk-\u003esk_peer_pid);\n \tsk-\u003esk_peer_cred = get_cred(peersk-\u003esk_peer_cred);\n \tspin_unlock(\u0026sk-\u003esk_peer_lock);\n }\n \n static bool unix_may_passcred(const struct sock *sk)\n {\n-\treturn sk-\u003esk_scm_credentials || sk-\u003esk_scm_pidfd;\n+\treturn sk-\u003esk_scm_credentials || sk-\u003esk_scm_pidfd ||\n+\t\tsk-\u003esk_scm_pidfd_thread;\n }\n \n static int unix_listen(struct socket *sock, int backlog)\n@@ -1060,6 +1057,7 @@ static bool unix_bpf_bypass_getsockopt(int level, int optname)\n \tif (level == SOL_SOCKET) {\n \t\tswitch (optname) {\n \t\tcase SO_PEERPIDFD:\n+\t\tcase SO_PEERPIDFD_THREAD:\n \t\t\treturn true;\n \t\tdefault:\n \t\t\treturn false;\n@@ -1973,7 +1971,7 @@ static void unix_destruct_scm(struct sk_buff *skb)\n {\n \tstruct scm_cookie scm = {};\n \n-\tswap(scm.pid, UNIXCB(skb).pid);\n+\tswap_pids(scm.pid, UNIXCB(skb).pid);\n \n \tif (UNIXCB(skb).fp)\n \t\tunix_detach_fds(\u0026scm, skb);\n@@ -1991,7 +1989,7 @@ static int unix_scm_to_skb(struct scm_cookie *scm, struct sk_buff *skb, bool sen\n {\n \tint err = 0;\n \n-\tUNIXCB(skb).pid = get_pid(scm-\u003epid);\n+\tget_pids(UNIXCB(skb).pid, scm-\u003epid);\n \tUNIXCB(skb).uid = scm-\u003ecreds.uid;\n \tUNIXCB(skb).gid = scm-\u003ecreds.gid;\n \tUNIXCB(skb).fp = NULL;\n@@ -2005,7 +2003,8 @@ static int unix_scm_to_skb(struct scm_cookie *scm, struct sk_buff *skb, bool sen\n \n static void unix_skb_to_scm(struct sk_buff *skb, struct scm_cookie *scm)\n {\n-\tscm_set_cred(scm, UNIXCB(skb).pid, UNIXCB(skb).uid, UNIXCB(skb).gid);\n+\tget_pids(scm-\u003epid, UNIXCB(skb).pid);\n+\tscm_set_cred(scm, UNIXCB(skb).pid[PIDTYPE_TGID], UNIXCB(skb).uid, UNIXCB(skb).gid);\n \tunix_set_secdata(scm, skb);\n }\n \n@@ -2025,30 +2024,35 @@ static void unix_skb_to_scm(struct sk_buff *skb, struct scm_cookie *scm)\n static int unix_maybe_add_creds(struct sk_buff *skb, const struct sock *sk,\n \t\t\t\tconst struct sock *other)\n {\n-\tif (UNIXCB(skb).pid)\n+\tif (UNIXCB(skb).pid[PIDTYPE_TGID])\n \t\treturn 0;\n \n \tif (unix_may_passcred(sk) || unix_may_passcred(other) ||\n \t    !other-\u003esk_socket) {\n-\t\tstruct pid *pid;\n \t\tint err;\n \n-\t\tpid = task_tgid(current);\n-\t\terr = pidfs_register_pid(pid);\n-\t\tif (unlikely(err))\n+\t\tget_task_pids(UNIXCB(skb).pid, current);\n+\t\terr = pidfs_register_pids(UNIXCB(skb).pid);\n+\t\tif (unlikely(err)) {\n+\t\t\tput_pids(UNIXCB(skb).pid);\n \t\t\treturn err;\n+\t\t}\n \n-\t\tUNIXCB(skb).pid = get_pid(pid);\n \t\tcurrent_uid_gid(\u0026UNIXCB(skb).uid, \u0026UNIXCB(skb).gid);\n \t}\n \n \treturn 0;\n }\n \n-static bool unix_skb_scm_eq(struct sk_buff *skb,\n+static bool unix_skb_scm_eq(const struct sock *sk, struct sk_buff *skb,\n \t\t\t    struct scm_cookie *scm)\n {\n-\treturn UNIXCB(skb).pid == scm-\u003epid \u0026\u0026\n+\t/* Only a thread pidfd receiver can tell threads of one process apart. */\n+\tif (sk-\u003esk_scm_pidfd_thread \u0026\u0026\n+\t    UNIXCB(skb).pid[PIDTYPE_PID] != scm-\u003epid[PIDTYPE_PID])\n+\t\treturn false;\n+\n+\treturn UNIXCB(skb).pid[PIDTYPE_TGID] == scm-\u003epid[PIDTYPE_TGID] \u0026\u0026\n \t       uid_eq(UNIXCB(skb).uid, scm-\u003ecreds.uid) \u0026\u0026\n \t       gid_eq(UNIXCB(skb).gid, scm-\u003ecreds.gid) \u0026\u0026\n \t       unix_secdata_eq(scm, skb);\n@@ -3030,7 +3034,7 @@ static int unix_stream_read_generic(struct unix_stream_read_state *state,\n \n \t\tif (check_creds) {\n \t\t\t/* Never glue messages from different writers */\n-\t\t\tif (!unix_skb_scm_eq(skb, \u0026scm))\n+\t\t\tif (!unix_skb_scm_eq(sk, skb, \u0026scm))\n \t\t\t\tbreak;\n \t\t} else if (unix_may_passcred(sk)) {\n \t\t\t/* Copy credentials */\ndiff --git a/net/unix/af_unix.h b/net/unix/af_unix.h\nindex 8119dbeef3a3c..402742895acca 100644\n--- a/net/unix/af_unix.h\n+++ b/net/unix/af_unix.h\n@@ -2,6 +2,7 @@\n #ifndef __AF_UNIX_H\n #define __AF_UNIX_H\n \n+#include \u003clinux/pid_types.h\u003e\n #include \u003clinux/uidgid.h\u003e\n \n #define UNIX_HASH_MOD\t(256 - 1)\n@@ -11,7 +12,7 @@\n struct sock *unix_peer_get(struct sock *sk);\n \n struct unix_skb_parms {\n-\tstruct pid\t\t*pid;\t\t/* skb credentials\t*/\n+\tDECLARE_PIDS(pid, PIDTYPE_TGID);\t/* skb credentials by pid type */\n \tkuid_t\t\t\tuid;\n \tkgid_t\t\t\tgid;\n \tstruct scm_fp_list\t*fp;\t\t/* Passed files\t\t*/\ndiff --git a/tools/lib/python/kdoc/xforms_lists.py b/tools/lib/python/kdoc/xforms_lists.py\nindex e3dda2fe8a537..e792785a15ec6 100644\n--- a/tools/lib/python/kdoc/xforms_lists.py\n+++ b/tools/lib/python/kdoc/xforms_lists.py\n@@ -45,6 +45,7 @@ class CTransforms:\n         (CMatch(\"DECLARE_HASHTABLE\"), r\"unsigned long \\1[1 \u003c\u003c ((\\2) - 1)]\"),\n         (CMatch(\"DECLARE_KFIFO\"), r\"\\2 *\\1\"),\n         (CMatch(\"DECLARE_KFIFO_PTR\"), r\"\\2 *\\1\"),\n+        (CMatch(\"DECLARE_PIDS\"), r\"struct pid *\\1[(\\2) + 1]\"),\n         (CMatch(\"(?:__)?DECLARE_FLEX_ARRAY\"), r\"\\1 \\2[]\"),\n         (CMatch(\"DEFINE_DMA_UNMAP_ADDR\"), r\"dma_addr_t \\1\"),\n         (CMatch(\"DEFINE_DMA_UNMAP_LEN\"), r\"__u32 \\1\"),\ndiff --git a/tools/testing/selftests/coredump/coredump_socket_test.c b/tools/testing/selftests/coredump/coredump_socket_test.c\nindex 422728f632ca6..ec73bb690bbcb 100644\n--- a/tools/testing/selftests/coredump/coredump_socket_test.c\n+++ b/tools/testing/selftests/coredump/coredump_socket_test.c\n@@ -592,6 +592,181 @@ TEST_F(coredump, socket_coredump_signal_sigsegv)\n \twait_and_check_coredump_server(pid_coredump_server, _metadata, self);\n }\n \n+static bool check_coredump_info(const struct pidfd_info *info, const char *what)\n+{\n+\tif (!(info-\u003emask \u0026 PIDFD_INFO_COREDUMP)) {\n+\t\tfprintf(stderr, \"%s: PIDFD_INFO_COREDUMP not set in mask\\n\", what);\n+\t\treturn false;\n+\t}\n+\n+\tif (!(info-\u003ecoredump_mask \u0026 PIDFD_COREDUMPED)) {\n+\t\tfprintf(stderr, \"%s: PIDFD_COREDUMPED not set in coredump_mask\\n\", what);\n+\t\treturn false;\n+\t}\n+\n+\tif (!(info-\u003emask \u0026 PIDFD_INFO_COREDUMP_SIGNAL) || info-\u003ecoredump_signal != SIGSEGV) {\n+\t\tfprintf(stderr, \"%s: coredump_signal=%d, expected SIGSEGV=%d\\n\",\n+\t\t\twhat, info-\u003ecoredump_signal, SIGSEGV);\n+\t\treturn false;\n+\t}\n+\n+\tif (!(info-\u003emask \u0026 PIDFD_INFO_COREDUMP_CODE) || info-\u003ecoredump_code != SEGV_MAPERR) {\n+\t\tfprintf(stderr, \"%s: coredump_code=%d, expected SEGV_MAPERR=%d\\n\",\n+\t\t\twhat, info-\u003ecoredump_code, SEGV_MAPERR);\n+\t\treturn false;\n+\t}\n+\n+\treturn true;\n+}\n+\n+/*\n+ * Test: PIDFD_INFO_COREDUMP on the dumping thread's pidfd\n+ *\n+ * Crash from a non-leader thread and verify that the pidfd from\n+ * SO_PEERPIDFD_THREAD refers to that thread and reports the coredump\n+ * like the thread-group leader's pidfd from SO_PEERPIDFD does.\n+ */\n+TEST_F(coredump, socket_coredump_thread)\n+{\n+\tint pidfd, ret, status;\n+\tpid_t pid, pid_coredump_server;\n+\tstruct pidfd_info info = {};\n+\tint ipc_sockets[2];\n+\tchar c;\n+\n+\tASSERT_TRUE(set_core_pattern(\"@/tmp/coredump.socket\"));\n+\n+\tret = socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, ipc_sockets);\n+\tASSERT_EQ(ret, 0);\n+\n+\tpid_coredump_server = fork();\n+\tASSERT_GE(pid_coredump_server, 0);\n+\tif (pid_coredump_server == 0) {\n+\t\tint fd_server = -1, fd_coredump = -1, fd_peer_pidfd = -1;\n+\t\tint fd_thread_pidfd = -1, fd_core_file = -1;\n+\t\tstruct pidfd_info thread_info = {};\n+\t\tint exit_code = EXIT_FAILURE;\n+\n+\t\tclose(ipc_sockets[0]);\n+\n+\t\tfd_server = create_and_listen_unix_socket(\"/tmp/coredump.socket\");\n+\t\tif (fd_server \u003c 0) {\n+\t\t\tfprintf(stderr, \"socket_coredump_thread: listen socket failed: %m\\n\");\n+\t\t\tgoto out;\n+\t\t}\n+\n+\t\tif (write_nointr(ipc_sockets[1], \"1\", 1) \u003c 0) {\n+\t\t\tfprintf(stderr, \"socket_coredump_thread: ipc write failed: %m\\n\");\n+\t\t\tgoto out;\n+\t\t}\n+\n+\t\tclose(ipc_sockets[1]);\n+\n+\t\tfd_coredump = accept4(fd_server, NULL, NULL, SOCK_CLOEXEC);\n+\t\tif (fd_coredump \u003c 0) {\n+\t\t\tfprintf(stderr, \"socket_coredump_thread: accept4 failed: %m\\n\");\n+\t\t\tgoto out;\n+\t\t}\n+\n+\t\tfd_peer_pidfd = get_peer_pidfd(fd_coredump);\n+\t\tif (fd_peer_pidfd \u003c 0) {\n+\t\t\tfprintf(stderr, \"socket_coredump_thread: get_peer_pidfd failed\\n\");\n+\t\t\tgoto out;\n+\t\t}\n+\n+\t\tfd_thread_pidfd = get_peer_pidfd_thread(fd_coredump);\n+\t\tif (fd_thread_pidfd \u003c 0) {\n+\t\t\tfprintf(stderr, \"socket_coredump_thread: get_peer_pidfd_thread failed\\n\");\n+\t\t\tgoto out;\n+\t\t}\n+\n+\t\tif (!get_pidfd_info(fd_peer_pidfd, \u0026info) ||\n+\t\t    !get_pidfd_info(fd_thread_pidfd, \u0026thread_info)) {\n+\t\t\tfprintf(stderr, \"socket_coredump_thread: get_pidfd_info failed\\n\");\n+\t\t\tgoto out;\n+\t\t}\n+\n+\t\t/* The peer is the thread-group leader, the dumping thread is not. */\n+\t\tif (info.pid != info.tgid || thread_info.tgid != info.tgid ||\n+\t\t    thread_info.pid == thread_info.tgid) {\n+\t\t\tfprintf(stderr, \"socket_coredump_thread: unexpected ids %d/%d and %d/%d\\n\",\n+\t\t\t\tinfo.pid, info.tgid, thread_info.pid, thread_info.tgid);\n+\t\t\tgoto out;\n+\t\t}\n+\n+\t\tif (!check_coredump_info(\u0026info, \"SO_PEERPIDFD\") ||\n+\t\t    !check_coredump_info(\u0026thread_info, \"SO_PEERPIDFD_THREAD\"))\n+\t\t\tgoto out;\n+\n+\t\tfd_core_file = open_coredump_tmpfile(self-\u003efd_tmpfs_detached);\n+\t\tif (fd_core_file \u003c 0) {\n+\t\t\tfprintf(stderr, \"socket_coredump_thread: core tmpfile failed: %m\\n\");\n+\t\t\tgoto out;\n+\t\t}\n+\n+\t\tfor (;;) {\n+\t\t\tchar buffer[4096];\n+\t\t\tssize_t bytes_read, bytes_write;\n+\n+\t\t\tbytes_read = read(fd_coredump, buffer, sizeof(buffer));\n+\t\t\tif (bytes_read \u003c 0) {\n+\t\t\t\tfprintf(stderr, \"socket_coredump_thread: core read failed: %m\\n\");\n+\t\t\t\tgoto out;\n+\t\t\t}\n+\n+\t\t\tif (bytes_read == 0)\n+\t\t\t\tbreak;\n+\n+\t\t\tbytes_write = write(fd_core_file, buffer, bytes_read);\n+\t\t\tif (bytes_read != bytes_write) {\n+\t\t\t\tfprintf(stderr, \"socket_coredump_thread: core write %zd/%zd: %m\\n\",\n+\t\t\t\t\tbytes_read, bytes_write);\n+\t\t\t\tgoto out;\n+\t\t\t}\n+\t\t}\n+\n+\t\texit_code = EXIT_SUCCESS;\n+\t\tfprintf(stderr, \"socket_coredump_thread: completed successfully\\n\");\n+out:\n+\t\tif (fd_core_file \u003e= 0)\n+\t\t\tclose(fd_core_file);\n+\t\tif (fd_thread_pidfd \u003e= 0)\n+\t\t\tclose(fd_thread_pidfd);\n+\t\tif (fd_peer_pidfd \u003e= 0)\n+\t\t\tclose(fd_peer_pidfd);\n+\t\tif (fd_coredump \u003e= 0)\n+\t\t\tclose(fd_coredump);\n+\t\tif (fd_server \u003e= 0)\n+\t\t\tclose(fd_server);\n+\t\t_exit(exit_code);\n+\t}\n+\tself-\u003epid_coredump_server = pid_coredump_server;\n+\n+\tEXPECT_EQ(close(ipc_sockets[1]), 0);\n+\tASSERT_EQ(read_nointr(ipc_sockets[0], \u0026c, 1), 1);\n+\tEXPECT_EQ(close(ipc_sockets[0]), 0);\n+\n+\tpid = fork();\n+\tASSERT_GE(pid, 0);\n+\tif (pid == 0)\n+\t\tcrashing_child_thread();\n+\n+\tpidfd = sys_pidfd_open(pid, 0);\n+\tASSERT_GE(pidfd, 0);\n+\n+\twaitpid(pid, \u0026status, 0);\n+\tASSERT_TRUE(WIFSIGNALED(status));\n+\tASSERT_EQ(WTERMSIG(status), SIGSEGV);\n+\tASSERT_TRUE(WCOREDUMP(status));\n+\n+\tASSERT_TRUE(get_pidfd_info(pidfd, \u0026info));\n+\tASSERT_TRUE(!!(info.mask \u0026 PIDFD_INFO_COREDUMP));\n+\tASSERT_TRUE(!!(info.coredump_mask \u0026 PIDFD_COREDUMPED));\n+\tASSERT_EQ(info.coredump_signal, SIGSEGV);\n+\n+\twait_and_check_coredump_server(pid_coredump_server, _metadata, self);\n+}\n+\n /*\n  * Test: PIDFD_INFO_COREDUMP_SIGNAL via simple socket coredump with SIGABRT\n  *\ndiff --git a/tools/testing/selftests/coredump/coredump_test.h b/tools/testing/selftests/coredump/coredump_test.h\nindex ed47f01fa53c5..4212656e31f01 100644\n--- a/tools/testing/selftests/coredump/coredump_test.h\n+++ b/tools/testing/selftests/coredump/coredump_test.h\n@@ -27,10 +27,12 @@ FIXTURE(coredump)\n /* Shared helper function declarations */\n void *do_nothing(void *arg);\n void crashing_child(void);\n+void crashing_child_thread(void);\n int create_detached_tmpfs(void);\n int create_and_listen_unix_socket(const char *path);\n bool set_core_pattern(const char *pattern);\n int get_peer_pidfd(int fd);\n+int get_peer_pidfd_thread(int fd);\n bool get_pidfd_info(int fd_peer_pidfd, struct pidfd_info *info);\n \n /* Inline helper that uses harness types */\ndiff --git a/tools/testing/selftests/coredump/coredump_test_helpers.c b/tools/testing/selftests/coredump/coredump_test_helpers.c\nindex 2a20faf9cb0ad..36306069f62e5 100644\n--- a/tools/testing/selftests/coredump/coredump_test_helpers.c\n+++ b/tools/testing/selftests/coredump/coredump_test_helpers.c\n@@ -13,6 +13,7 @@\n #include \u003cstring.h\u003e\n #include \u003csys/epoll.h\u003e\n #include \u003csys/ioctl.h\u003e\n+#include \u003csys/mman.h\u003e\n #include \u003csys/socket.h\u003e\n #include \u003csys/types.h\u003e\n #include \u003csys/un.h\u003e\n@@ -38,6 +39,10 @@ struct _fixture_coredump_data {\n \n #define NUM_THREAD_SPAWN 128\n \n+#ifndef SO_PEERPIDFD_THREAD\n+#define SO_PEERPIDFD_THREAD 87\n+#endif\n+\n void *do_nothing(void *arg)\n {\n \t(void)arg;\n@@ -59,6 +64,36 @@ void crashing_child(void)\n \ti = *(volatile int *)NULL;\n }\n \n+static void *crashing_thread(void *arg)\n+{\n+\tint *p;\n+\n+\t(void)arg;\n+\n+\t/* crash on purpose with SEGV_MAPERR */\n+\tp = mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE,\n+\t\t MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);\n+\tif (p == MAP_FAILED)\n+\t\treturn NULL;\n+\tmunmap(p, PAGE_SIZE);\n+\t*p = 0;\n+\n+\treturn NULL;\n+}\n+\n+void crashing_child_thread(void)\n+{\n+\tpthread_t thread;\n+\tint i;\n+\n+\tfor (i = 0; i \u003c NUM_THREAD_SPAWN; ++i)\n+\t\tpthread_create(\u0026thread, NULL, do_nothing, NULL);\n+\n+\t/* crash from a non-leader thread */\n+\tpthread_create(\u0026thread, NULL, crashing_thread, NULL);\n+\tpause();\n+}\n+\n int create_detached_tmpfs(void)\n {\n \tint fd_context, fd_tmpfs;\n@@ -138,6 +173,20 @@ int get_peer_pidfd(int fd)\n \treturn fd_peer_pidfd;\n }\n \n+int get_peer_pidfd_thread(int fd)\n+{\n+\tint fd_peer_pidfd;\n+\tsocklen_t fd_peer_pidfd_len = sizeof(fd_peer_pidfd);\n+\tint ret = getsockopt(fd, SOL_SOCKET, SO_PEERPIDFD_THREAD, \u0026fd_peer_pidfd,\n+\t\t\t     \u0026fd_peer_pidfd_len);\n+\tif (ret \u003c 0) {\n+\t\tfprintf(stderr, \"%s: getsockopt(SO_PEERPIDFD_THREAD) failed: %m\\n\", __func__);\n+\t\treturn -1;\n+\t}\n+\tfprintf(stderr, \"%s: successfully retrieved pidfd %d\\n\", __func__, fd_peer_pidfd);\n+\treturn fd_peer_pidfd;\n+}\n+\n bool get_pidfd_info(int fd_peer_pidfd, struct pidfd_info *info)\n {\n \tint ret;\ndiff --git a/tools/testing/selftests/net/af_unix/Makefile b/tools/testing/selftests/net/af_unix/Makefile\nindex a66f10fb0c231..45b841758f1bf 100644\n--- a/tools/testing/selftests/net/af_unix/Makefile\n+++ b/tools/testing/selftests/net/af_unix/Makefile\n@@ -23,6 +23,8 @@ TEST_GEN_FILES := scm_rights_denial_lsm.bpf.o\n include ../../lib.mk\n include ../bpf.mk\n \n+$(OUTPUT)/scm_pidfd: CFLAGS += -pthread\n+\n $(OUTPUT)/scm_rights_denial_lsm: $(BPFOBJ)\n $(OUTPUT)/scm_rights_denial_lsm: CFLAGS += -I$(SCRATCH_DIR)/include\n $(OUTPUT)/scm_rights_denial_lsm: LDLIBS += -lelf -lz\ndiff --git a/tools/testing/selftests/net/af_unix/scm_pidfd.c b/tools/testing/selftests/net/af_unix/scm_pidfd.c\nindex 2c18b92a26035..cbe53fab44fc5 100644\n--- a/tools/testing/selftests/net/af_unix/scm_pidfd.c\n+++ b/tools/testing/selftests/net/af_unix/scm_pidfd.c\n@@ -10,6 +10,7 @@\n #include \u003cunistd.h\u003e\n #include \u003cstring.h\u003e\n #include \u003cerrno.h\u003e\n+#include \u003cpthread.h\u003e\n #include \u003csys/un.h\u003e\n #include \u003csys/signal.h\u003e\n #include \u003csys/types.h\u003e\n@@ -27,6 +28,18 @@\n #define SCM_PIDFD 0x04\n #endif\n \n+#ifndef SCM_PIDFD_THREAD\n+#define SCM_PIDFD_THREAD 0x05\n+#endif\n+\n+#ifndef SO_PASSPIDFD_THREAD\n+#define SO_PASSPIDFD_THREAD 86\n+#endif\n+\n+#ifndef SO_PEERPIDFD_THREAD\n+#define SO_PEERPIDFD_THREAD 87\n+#endif\n+\n #define CHILD_EXIT_CODE_OK 123\n \n static void child_die()\n@@ -553,4 +566,593 @@ TEST_F(scm_pidfd, test)\n \tclose(pfd);\n }\n \n+struct thread_ids {\n+\tpid_t pid;\n+\tpid_t tid;\n+};\n+\n+#define MAX_WRITERS 2\n+\n+/* Used by writers to signal they've written, so we can sequence multiple writers */\n+static int seq_pipe[2];\n+\n+static void *send_ids_thread(void *arg)\n+{\n+\tint fd = *(int *)arg;\n+\tstruct thread_ids ids = {\n+\t\t.pid = getpid(),\n+\t\t.tid = gettid(),\n+\t};\n+\tchar sync;\n+\n+\tif (send(fd, \u0026ids, sizeof(ids), 0) != sizeof(ids))\n+\t\treturn (void *)1;\n+\n+\t/* let the client start the next writer, so the queue order is known */\n+\tif (write(seq_pipe[1], \"1\", 1) != 1)\n+\t\treturn (void *)1;\n+\n+\t/* stay alive until the receiver has looked at our pidfd */\n+\tif (read(fd, \u0026sync, 1) != 1)\n+\t\treturn (void *)1;\n+\n+\treturn NULL;\n+}\n+\n+static void *send_ids_creds_thread(void *arg)\n+{\n+\tint fd = *(int *)arg;\n+\tstruct thread_ids ids = {\n+\t\t.pid = getpid(),\n+\t\t.tid = gettid(),\n+\t};\n+\tstruct ucred ucred = {\n+\t\t.pid = getpid(),\n+\t\t.uid = getuid(),\n+\t\t.gid = getgid(),\n+\t};\n+\tchar control[CMSG_SPACE(sizeof(ucred))] = { 0 };\n+\tstruct iovec iov;\n+\tstruct msghdr msg = { 0 };\n+\tstruct cmsghdr *cmsg;\n+\tchar sync;\n+\n+\tiov.iov_base = \u0026ids;\n+\tiov.iov_len = sizeof(ids);\n+\n+\tmsg.msg_iov = \u0026iov;\n+\tmsg.msg_iovlen = 1;\n+\tmsg.msg_control = control;\n+\tmsg.msg_controllen = sizeof(control);\n+\n+\tcmsg = CMSG_FIRSTHDR(\u0026msg);\n+\tcmsg-\u003ecmsg_level = SOL_SOCKET;\n+\tcmsg-\u003ecmsg_type = SCM_CREDENTIALS;\n+\tcmsg-\u003ecmsg_len = CMSG_LEN(sizeof(ucred));\n+\tmemcpy(CMSG_DATA(cmsg), \u0026ucred, sizeof(ucred));\n+\n+\tif (sendmsg(fd, \u0026msg, 0) != sizeof(ids))\n+\t\treturn (void *)1;\n+\n+\t/* signal we've written our info */\n+\tif (write(seq_pipe[1], \"1\", 1) != 1)\n+\t\treturn (void *)1;\n+\n+\t/* stay alive until the receiver has looked at our pidfd */\n+\tif (read(fd, \u0026sync, 1) != 1)\n+\t\treturn (void *)1;\n+\n+\treturn NULL;\n+}\n+\n+/*\n+ * Runs @nwriters copies of @sender, each in its own non-leader thread, one\n+ * at a time so the queue order is known, then tells the parent on @ackfd\n+ * that everything is queued.\n+ */\n+static void thread_client(int fd, int syncfd, int ackfd,\n+\t\t\t  void *(*sender)(void *), int nwriters)\n+{\n+\tpthread_t writers[MAX_WRITERS];\n+\tvoid *ret;\n+\tchar sync;\n+\tint i;\n+\n+\t/* wait until the receiver enabled the options it wants */\n+\tif (read(syncfd, \u0026sync, 1) != 1)\n+\t\tchild_die();\n+\n+\tfor (i = 0; i \u003c nwriters; i++) {\n+\t\tif (pthread_create(\u0026writers[i], NULL, sender, \u0026fd))\n+\t\t\tchild_die();\n+\n+\t\tif (read(seq_pipe[0], \u0026sync, 1) != 1)\n+\t\t\tchild_die();\n+\t}\n+\n+\tif (write(ackfd, \"1\", 1) != 1)\n+\t\tchild_die();\n+\n+\tfor (i = 0; i \u003c nwriters; i++)\n+\t\tif (pthread_join(writers[i], \u0026ret) || ret)\n+\t\t\tchild_die();\n+\n+\texit(0);\n+}\n+\n+struct pidfd_msg {\n+\tssize_t len;\n+\tstruct thread_ids ids;\n+\tstruct pidfd_info tgid_info;\n+\tstruct pidfd_info thread_info;\n+\tint tgid_flags;\n+\tint thread_flags;\n+\tbool have_tgid;\n+\tbool have_thread;\n+};\n+\n+static int get_pidfd_info(int pidfd, struct pidfd_info *info)\n+{\n+\tinfo-\u003emask = PIDFD_INFO_PID;\n+\tif (ioctl(pidfd, PIDFD_GET_INFO, info)) {\n+\t\tlog_err(\"ioctl(PIDFD_GET_INFO)\");\n+\t\treturn -1;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+/* One recvmsg(), reporting how many bytes came back and what the pidfd\n+ * cmsgs that came with them say.\n+ */\n+static int read_pidfd_msg(int fd, struct pidfd_msg *out)\n+{\n+\tchar control[CMSG_SPACE(sizeof(int)) * 2] = { 0 };\n+\tint tgid_pidfd = -1, thread_pidfd = -1;\n+\tstruct thread_ids buf[2] = { 0 };\n+\tstruct msghdr msg = { 0 };\n+\tstruct cmsghdr *cmsg;\n+\tstruct iovec iov;\n+\n+\tiov.iov_base = buf;\n+\tiov.iov_len = sizeof(buf);\n+\tmsg.msg_iov = \u0026iov;\n+\tmsg.msg_iovlen = 1;\n+\tmsg.msg_control = control;\n+\tmsg.msg_controllen = sizeof(control);\n+\n+\tout-\u003elen = recvmsg(fd, \u0026msg, 0);\n+\tif (out-\u003elen \u003c 0) {\n+\t\tlog_err(\"recvmsg\");\n+\t\treturn -1;\n+\t}\n+\n+\tif (msg.msg_flags \u0026 MSG_CTRUNC) {\n+\t\tlog_err(\"recvmsg: control truncated\");\n+\t\treturn -1;\n+\t}\n+\n+\tout-\u003eids = buf[0];\n+\n+\tfor (cmsg = CMSG_FIRSTHDR(\u0026msg); cmsg != NULL;\n+\t     cmsg = CMSG_NXTHDR(\u0026msg, cmsg)) {\n+\t\tif (cmsg-\u003ecmsg_level != SOL_SOCKET)\n+\t\t\tcontinue;\n+\n+\t\tif (cmsg-\u003ecmsg_type == SCM_PIDFD)\n+\t\t\tmemcpy(\u0026tgid_pidfd, CMSG_DATA(cmsg), sizeof(tgid_pidfd));\n+\t\telse if (cmsg-\u003ecmsg_type == SCM_PIDFD_THREAD)\n+\t\t\tmemcpy(\u0026thread_pidfd, CMSG_DATA(cmsg), sizeof(thread_pidfd));\n+\t}\n+\n+\tout-\u003ehave_tgid = tgid_pidfd \u003e= 0;\n+\tout-\u003ehave_thread = thread_pidfd \u003e= 0;\n+\n+\tif (out-\u003ehave_tgid) {\n+\t\tif (get_pidfd_info(tgid_pidfd, \u0026out-\u003etgid_info))\n+\t\t\treturn -1;\n+\t\tout-\u003etgid_flags = fcntl(tgid_pidfd, F_GETFL);\n+\t\tclose(tgid_pidfd);\n+\t}\n+\n+\tif (out-\u003ehave_thread) {\n+\t\tif (get_pidfd_info(thread_pidfd, \u0026out-\u003ethread_info))\n+\t\t\treturn -1;\n+\t\tout-\u003ethread_flags = fcntl(thread_pidfd, F_GETFL);\n+\t\tclose(thread_pidfd);\n+\t}\n+\n+\treturn 0;\n+}\n+\n+/*\n+ * Runs @nwriters threads of one child process against a SOCK_STREAM pair,\n+ * all of them queued before the parent reads. SO_PASSPIDFD is set when\n+ * @want_tgid is given, SO_PASSPIDFD_THREAD when @want_thread is. @nread\n+ * reads are returned in @out.\n+ */\n+static int pidfd_flow(void *(*sender)(void *), int nwriters, bool want_tgid,\n+\t\t      bool want_thread, struct pidfd_msg *out, int nread)\n+{\n+\tint child_status = 0;\n+\tint syncpipe[2];\n+\tint ackpipe[2];\n+\tint sk[2];\n+\tint on = 1;\n+\tchar sync;\n+\tpid_t child;\n+\tint i;\n+\n+\tif (nwriters \u003e MAX_WRITERS)\n+\t\treturn -1;\n+\n+\tif (socketpair(AF_UNIX, SOCK_STREAM, 0, sk))\n+\t\treturn -1;\n+\tif (pipe(syncpipe) || pipe(ackpipe) || pipe(seq_pipe))\n+\t\treturn -1;\n+\n+\tchild = fork();\n+\tif (child \u003c 0)\n+\t\treturn -1;\n+\n+\tif (child == 0) {\n+\t\tclose(sk[0]);\n+\t\tclose(syncpipe[1]);\n+\t\tclose(ackpipe[0]);\n+\t\tthread_client(sk[1], syncpipe[0], ackpipe[1], sender, nwriters);\n+\t}\n+\tclose(sk[1]);\n+\tclose(syncpipe[0]);\n+\tclose(ackpipe[1]);\n+\tclose(seq_pipe[0]);\n+\tclose(seq_pipe[1]);\n+\n+\tif (want_tgid \u0026\u0026\n+\t    setsockopt(sk[0], SOL_SOCKET, SO_PASSPIDFD, \u0026on, sizeof(on))) {\n+\t\tlog_err(\"Failed to set SO_PASSPIDFD\");\n+\t\treturn -1;\n+\t}\n+\n+\tif (want_thread \u0026\u0026\n+\t    setsockopt(sk[0], SOL_SOCKET, SO_PASSPIDFD_THREAD, \u0026on, sizeof(on))) {\n+\t\tlog_err(\"Failed to set SO_PASSPIDFD_THREAD\");\n+\t\treturn -1;\n+\t}\n+\n+\t/* let the child know the options are set, it can write now */\n+\tif (write(syncpipe[1], \"1\", 1) != 1)\n+\t\treturn -1;\n+\tclose(syncpipe[1]);\n+\n+\t/* wait until every writer has queued its message */\n+\tif (read(ackpipe[0], \u0026sync, 1) != 1)\n+\t\treturn -1;\n+\tclose(ackpipe[0]);\n+\n+\tfor (i = 0; i \u003c nread; i++)\n+\t\tif (read_pidfd_msg(sk[0], \u0026out[i]))\n+\t\t\treturn -1;\n+\n+\t/* release the writers */\n+\tfor (i = 0; i \u003c nwriters; i++)\n+\t\tif (write(sk[0], \"x\", 1) != 1)\n+\t\t\treturn -1;\n+\tclose(sk[0]);\n+\n+\twaitpid(child, \u0026child_status, 0);\n+\tif (!WIFEXITED(child_status) || WEXITSTATUS(child_status))\n+\t\treturn -1;\n+\n+\treturn 0;\n+}\n+\n+static int sockopt_set(int fd, int optname, int val)\n+{\n+\treturn setsockopt(fd, SOL_SOCKET, optname, \u0026val, sizeof(val));\n+}\n+\n+static int sockopt_get(int fd, int optname)\n+{\n+\tsocklen_t len = sizeof(int);\n+\tint val = -1;\n+\n+\tif (getsockopt(fd, SOL_SOCKET, optname, \u0026val, \u0026len))\n+\t\treturn -1;\n+\n+\treturn val;\n+}\n+\n+TEST(scm_pidfd_setsockopt_values)\n+{\n+\tint sk[2];\n+\n+\tASSERT_EQ(0, socketpair(AF_UNIX, SOCK_STREAM, 0, sk));\n+\n+\t/* Verify that the options are truly set independently */\n+\tASSERT_EQ(0, sockopt_set(sk[0], SO_PASSPIDFD_THREAD, 1));\n+\tASSERT_EQ(1, sockopt_get(sk[0], SO_PASSPIDFD_THREAD));\n+\tASSERT_EQ(0, sockopt_get(sk[0], SO_PASSPIDFD));\n+\n+\tASSERT_EQ(0, sockopt_set(sk[0], SO_PASSPIDFD, 1));\n+\tASSERT_EQ(1, sockopt_get(sk[0], SO_PASSPIDFD));\n+\tASSERT_EQ(1, sockopt_get(sk[0], SO_PASSPIDFD_THREAD));\n+\n+\t/* Verify that the options are truly cleared independently */\n+\tASSERT_EQ(0, sockopt_set(sk[0], SO_PASSPIDFD, 0));\n+\tASSERT_EQ(0, sockopt_get(sk[0], SO_PASSPIDFD));\n+\tASSERT_EQ(1, sockopt_get(sk[0], SO_PASSPIDFD_THREAD));\n+\n+\tASSERT_EQ(0, sockopt_set(sk[0], SO_PASSPIDFD_THREAD, 0));\n+\tASSERT_EQ(0, sockopt_get(sk[0], SO_PASSPIDFD_THREAD));\n+\tASSERT_EQ(0, sockopt_get(sk[0], SO_PASSPIDFD));\n+\n+\tclose(sk[0]);\n+\tclose(sk[1]);\n+}\n+\n+/* A receiver that only asked about the process cannot tell the two\n+ * threads apart, so their writes are glued into one read.\n+ */\n+TEST(scm_pidfd_stream_glues_threads)\n+{\n+\tstruct pidfd_msg msg[1] = { 0 };\n+\n+\tASSERT_EQ(0, pidfd_flow(send_ids_thread, 2, true, false, msg, 1));\n+\tEXPECT_EQ(sizeof(struct thread_ids) * 2, msg[0].len);\n+\n+\tEXPECT_TRUE(msg[0].have_tgid);\n+\tEXPECT_FALSE(msg[0].have_thread);\n+\tEXPECT_EQ(msg[0].ids.pid, msg[0].tgid_info.pid);\n+\tEXPECT_EQ(msg[0].ids.pid, msg[0].tgid_info.tgid);\n+}\n+\n+/* A receiver asking for both thread and process pidfd should get a unique\n+ * msg for each thread writing\n+ */\n+TEST(scm_pidfd_thread_stream_splits_on_threads)\n+{\n+\tstruct pidfd_msg msg[2] = { 0 };\n+\tint i;\n+\n+\tASSERT_EQ(0, pidfd_flow(send_ids_thread, 2, true, true, msg, 2));\n+\n+\tfor (i = 0; i \u003c 2; i++) {\n+\t\tEXPECT_EQ(sizeof(struct thread_ids), msg[i].len);\n+\t\tEXPECT_TRUE(msg[i].have_tgid);\n+\t\tEXPECT_TRUE(msg[i].have_thread);\n+\n+\t\t/* Make sure the info makes sense for the pidfd type */\n+\t\tEXPECT_EQ(msg[i].ids.pid, msg[i].tgid_info.pid);\n+\t\tEXPECT_EQ(msg[i].ids.pid, msg[i].tgid_info.tgid);\n+\t\tEXPECT_EQ(msg[i].ids.tid, msg[i].thread_info.pid);\n+\t\tEXPECT_EQ(msg[i].ids.pid, msg[i].thread_info.tgid);\n+\t\tEXPECT_NE(msg[i].ids.pid, msg[i].ids.tid);\n+\t}\n+\n+\t/* Make sure we really got unique threads per message */\n+\tEXPECT_NE(msg[0].ids.tid, msg[1].ids.tid);\n+\tEXPECT_EQ(msg[0].ids.pid, msg[1].ids.pid);\n+\tEXPECT_EQ(msg[0].tgid_info.pid, msg[1].tgid_info.pid);\n+\tEXPECT_NE(msg[0].thread_info.pid, msg[1].thread_info.pid);\n+}\n+\n+/* Sends from the thread-group leader */\n+static int leader_flow(bool want_thread, struct pidfd_msg *out)\n+{\n+\tstruct thread_ids ids = {\n+\t\t.pid = getpid(),\n+\t\t.tid = gettid(),\n+\t};\n+\tint sk[2];\n+\tint on = 1;\n+\tint ret;\n+\n+\tif (socketpair(AF_UNIX, SOCK_STREAM, 0, sk))\n+\t\treturn -1;\n+\n+\tif (setsockopt(sk[0], SOL_SOCKET, SO_PASSPIDFD, \u0026on, sizeof(on))) {\n+\t\tlog_err(\"Failed to set SO_PASSPIDFD\");\n+\t\treturn -1;\n+\t}\n+\n+\tif (want_thread \u0026\u0026\n+\t    setsockopt(sk[0], SOL_SOCKET, SO_PASSPIDFD_THREAD, \u0026on, sizeof(on))) {\n+\t\tlog_err(\"Failed to set SO_PASSPIDFD_THREAD\");\n+\t\treturn -1;\n+\t}\n+\n+\tif (send(sk[1], \u0026ids, sizeof(ids), 0) != sizeof(ids)) {\n+\t\tlog_err(\"send\");\n+\t\treturn -1;\n+\t}\n+\n+\tret = read_pidfd_msg(sk[0], out);\n+\n+\tclose(sk[0]);\n+\tclose(sk[1]);\n+\n+\treturn ret;\n+}\n+\n+TEST(scm_pidfd_leader_sender)\n+{\n+\tstruct pidfd_msg msg = { 0 };\n+\n+\tASSERT_EQ(getpid(), gettid());\n+\n+\tASSERT_EQ(0, leader_flow(false, \u0026msg));\n+\tASSERT_TRUE(msg.have_tgid);\n+\tEXPECT_FALSE(msg.have_thread);\n+\n+\tEXPECT_EQ(getpid(), msg.tgid_info.pid);\n+\tEXPECT_EQ(getpid(), msg.tgid_info.tgid);\n+\tEXPECT_FALSE(msg.tgid_flags \u0026 O_EXCL);\n+}\n+\n+TEST(scm_pidfd_thread_leader_sender)\n+{\n+\tstruct pidfd_msg msg = { 0 };\n+\n+\tASSERT_EQ(getpid(), gettid());\n+\n+\tASSERT_EQ(0, leader_flow(true, \u0026msg));\n+\tASSERT_TRUE(msg.have_tgid);\n+\tASSERT_TRUE(msg.have_thread);\n+\n+\tEXPECT_EQ(getpid(), msg.tgid_info.pid);\n+\tEXPECT_EQ(getpid(), msg.tgid_info.tgid);\n+\tEXPECT_EQ(getpid(), msg.thread_info.pid);\n+\tEXPECT_EQ(getpid(), msg.thread_info.tgid);\n+\n+\tEXPECT_FALSE(msg.tgid_flags \u0026 O_EXCL);\n+\tEXPECT_TRUE(msg.thread_flags \u0026 O_EXCL);\n+}\n+\n+TEST(scm_pidfd_thread_and_group)\n+{\n+\tstruct pidfd_msg msg = { 0 };\n+\n+\tASSERT_EQ(0, pidfd_flow(send_ids_thread, 1, true, true, \u0026msg, 1));\n+\tASSERT_NE(msg.ids.pid, msg.ids.tid);\n+\tASSERT_TRUE(msg.have_tgid);\n+\tASSERT_TRUE(msg.have_thread);\n+\n+\tEXPECT_EQ(msg.ids.pid, msg.tgid_info.pid);\n+\tEXPECT_EQ(msg.ids.pid, msg.tgid_info.tgid);\n+\n+\tEXPECT_EQ(msg.ids.tid, msg.thread_info.pid);\n+\tEXPECT_EQ(msg.ids.pid, msg.thread_info.tgid);\n+}\n+\n+TEST(scm_pidfd_thread)\n+{\n+\tstruct pidfd_msg msg = { 0 };\n+\n+\tASSERT_EQ(0, pidfd_flow(send_ids_thread, 1, false, true, \u0026msg, 1));\n+\tASSERT_NE(msg.ids.pid, msg.ids.tid);\n+\tASSERT_TRUE(msg.have_thread);\n+\tEXPECT_FALSE(msg.have_tgid);\n+\tEXPECT_EQ(msg.ids.tid, msg.thread_info.pid);\n+\tEXPECT_EQ(msg.ids.pid, msg.thread_info.tgid);\n+}\n+\n+TEST(scm_pidfd_thread_group)\n+{\n+\tstruct pidfd_msg msg = { 0 };\n+\n+\tASSERT_EQ(0, pidfd_flow(send_ids_thread, 1, true, false, \u0026msg, 1));\n+\tASSERT_NE(msg.ids.pid, msg.ids.tid);\n+\tASSERT_TRUE(msg.have_tgid);\n+\tEXPECT_FALSE(msg.have_thread);\n+\tEXPECT_EQ(msg.ids.pid, msg.tgid_info.pid);\n+\tEXPECT_EQ(msg.ids.pid, msg.tgid_info.tgid);\n+}\n+\n+TEST(scm_pidfd_thread_creds)\n+{\n+\tstruct pidfd_msg msg = { 0 };\n+\n+\tASSERT_EQ(0, pidfd_flow(send_ids_creds_thread, 1, true, true, \u0026msg, 1));\n+\tASSERT_NE(msg.ids.pid, msg.ids.tid);\n+\tASSERT_TRUE(msg.have_tgid);\n+\tASSERT_TRUE(msg.have_thread);\n+\tEXPECT_EQ(msg.ids.pid, msg.tgid_info.pid);\n+\tEXPECT_EQ(msg.ids.pid, msg.tgid_info.tgid);\n+\tEXPECT_EQ(msg.ids.tid, msg.thread_info.pid);\n+\tEXPECT_EQ(msg.ids.pid, msg.thread_info.tgid);\n+}\n+\n+static void *peer_connect_thread(void *arg)\n+{\n+\tstruct sock_addr *sa = arg;\n+\tstruct thread_ids ids = {\n+\t\t.pid = getpid(),\n+\t\t.tid = gettid(),\n+\t};\n+\tint fd;\n+\tchar sync;\n+\n+\tfd = socket(AF_UNIX, SOCK_STREAM, 0);\n+\tif (fd \u003c 0)\n+\t\treturn (void *)1;\n+\n+\tif (connect(fd, (struct sockaddr *)\u0026sa-\u003elisten_addr, sa-\u003eaddrlen))\n+\t\treturn (void *)1;\n+\n+\tif (send(fd, \u0026ids, sizeof(ids), 0) != sizeof(ids))\n+\t\treturn (void *)1;\n+\n+\t/* stay alive until the server has looked at our pidfd */\n+\tif (read(fd, \u0026sync, 1) != 1)\n+\t\treturn (void *)1;\n+\n+\tclose(fd);\n+\treturn NULL;\n+}\n+\n+static int peer_pidfd_info(int fd, int optname, struct pidfd_info *info)\n+{\n+\tint pidfd;\n+\tsocklen_t len = sizeof(pidfd);\n+\n+\tif (getsockopt(fd, SOL_SOCKET, optname, \u0026pidfd, \u0026len)) {\n+\t\tlog_err(\"getsockopt(SO_PEERPIDFD*)\");\n+\t\treturn -1;\n+\t}\n+\n+\tinfo-\u003emask = PIDFD_INFO_PID;\n+\tif (ioctl(pidfd, PIDFD_GET_INFO, info)) {\n+\t\tlog_err(\"ioctl(PIDFD_GET_INFO)\");\n+\t\tclose(pidfd);\n+\t\treturn -1;\n+\t}\n+\n+\tclose(pidfd);\n+\treturn 0;\n+}\n+\n+/* SO_PEERPIDFD_THREAD returns a pidfd for the peer's connecting thread. */\n+TEST(so_peerpidfd_thread)\n+{\n+\tstruct sock_addr sa;\n+\tstruct thread_ids ids;\n+\tstruct pidfd_info info;\n+\tpthread_t thread;\n+\tvoid *tret;\n+\tint server, cfd;\n+\n+\tserver = socket(AF_UNIX, SOCK_STREAM, 0);\n+\tASSERT_LE(0, server);\n+\n+\tfill_sockaddr(\u0026sa, true);\n+\tASSERT_EQ(0, bind(server, (struct sockaddr *)\u0026sa.listen_addr, sa.addrlen));\n+\tASSERT_EQ(0, listen(server, 1));\n+\n+\tASSERT_EQ(0, pthread_create(\u0026thread, NULL, peer_connect_thread, \u0026sa));\n+\n+\tcfd = accept(server, NULL, NULL);\n+\tASSERT_LE(0, cfd);\n+\n+\tASSERT_EQ(sizeof(ids), recv(cfd, \u0026ids, sizeof(ids), MSG_WAITALL));\n+\tASSERT_NE(ids.pid, ids.tid);\n+\n+\t/* SO_PEERPIDFD refers to the peer's thread-group. */\n+\tASSERT_EQ(0, peer_pidfd_info(cfd, SO_PEERPIDFD, \u0026info));\n+\tEXPECT_EQ(ids.pid, info.pid);\n+\tEXPECT_EQ(ids.pid, info.tgid);\n+\n+\t/* SO_PEERPIDFD_THREAD refers to the connecting thread. */\n+\tASSERT_EQ(0, peer_pidfd_info(cfd, SO_PEERPIDFD_THREAD, \u0026info));\n+\tEXPECT_EQ(ids.tid, info.pid);\n+\tEXPECT_EQ(ids.pid, info.tgid);\n+\n+\t/* release the connecting thread */\n+\tASSERT_EQ(1, write(cfd, \"x\", 1));\n+\tASSERT_EQ(0, pthread_join(thread, \u0026tret));\n+\tASSERT_EQ(NULL, tret);\n+\n+\tclose(cfd);\n+\tclose(server);\n+}\n+\n TEST_HARNESS_MAIN\n"
}

2/1 2026/10/05 15:13 agent patch-evaluator
2m Model:
core

Args:
null

Results:
{
  "EnableConfigs": [
    "UNIX",
    "COREDUMP",
    "BT"
  ],
  "FocusSymbols": [
    "sk_getsockopt_peerpidfd",
    "scm_pidfd_recv",
    "scm_recv_unix",
    "unix_skb_scm_eq",
    "unix_maybe_add_creds",
    "__pidfs_register_pids",
    "pidfs_coredump",
    "coredump_sock_connect"
  ],
  "Reasoning": "The patch introduces SO_PASSPIDFD_THREAD and SO_PEERPIDFD_THREAD socket options along with SCM_PIDFD_THREAD control messages, enabling AF_UNIX sockets to pass and query thread-level pidfds. It restructures struct sock and SCM credentials to track arrays of struct pid up to PIDTYPE_TGID, updates stream message coalescing logic to split messages across different threads when thread pidfds are requested, adds new pidfs registration helpers with WARN_ON_ONCE checks, and modifies coredump socket connection verification. These changes touch reachable core socket, AF_UNIX, pidfs, and coredump code paths that warrant fuzzing.",
  "WorthFuzzing": true
}

Instruction:
You are an expert Linux kernel maintainer and security engineer.
Your job is to review a provided patch series and evaluate whether it warrants fuzzing with syzkaller.

IMPORTANT: The changes have ALREADY been applied and committed as the HEAD commit in
your workspace. Do NOT rely on internal assumptions. You must actively use your code access
tools to inspect the actual source code, callers, and surrounding context.

================================================================================
1. CORE TRIAGE PHILOSOPHY
================================================================================
The goal of patch fuzzing is to discover crashes, regressions, exposed latent bugs,
and newly triggered assertions introduced by the patch series.

- REACHABILITY IS THE PRIMARY GATE:
  Fuzzing can only discover bugs in code that can actually execute in standard virtualized
  environments (GCE or QEMU, utilizing software-emulated devices like USB gadgets, netdev, tun/tap).
  If the modified code is structurally unreachable (see Section 2), it MUST NOT be fuzzed,
  regardless of whether it adds assertions or complex logic.

- DO NOT BLINDLY TRUST "NO FUNCTIONAL CHANGE" (NFCI) OR "REFACTORING" CLAIMS:
  Patch authors routinely label changes as "cleanups", "refactorings", or state
  "No functional change intended". Do NOT take these claims at face value.
  Code refactorings that rearrange logic, introduce helper functions, or alter state management
  in core subsystems frequently introduce subtle semantic shifts or uncover latent kernel bugs.
  If reachable executable code is modified or refactored, it MUST be fuzzed.

- NEW OR MODIFIED ASSERTIONS IN REACHABLE CODE MUST BE FUZZED:
  When a patch introduces or modifies runtime checks or assertions (e.g., WARN_ON*, VM_WARN_ON*,
  BUG_ON*, lockdep_assert*) in reachable code paths, it enforces new or stricter invariants.
  Even if the author believes the invariant always holds, fuzzing is essential to verify whether
  an unusual sequence of operations can violate it.

================================================================================
2. WHEN TO RETURN WorthFuzzing=false (NEGATIVE CRITERIA)
================================================================================
Return WorthFuzzing=false ONLY IF all modified code falls strictly into one or more of these categories:

- Non-kernel and non-executable changes:
  * Modifications to Documentation/, comments, or spelling fixes.
  * User-space directories, self-tests, samples, or scripts (e.g., tools/, samples/, scripts/, usr/)
    that do not affect the compiled kernel image (vmlinux) or kernel modules.
  * Purely decorative logging (e.g., message strings in pr_err, printk, dev_info) or tracepoints
    that do not alter control flow or data structures.
  * Build system or Kconfig changes that do not alter compiled C logic.
- Structurally unreachable hardware:
  * Vendor-specific PCIe switches, SmartNICs, or GPU drivers (e.g., mlxsw, pds_core, qed,
    ionic, amdgpu) requiring physical ASIC/PCIe cards not emulated in standard QEMU.
- Unreachable execution paths:
  * Driver teardown callbacks (.remove, .shutdown, pci_unregister_driver) executed only during
    physical PCI hot-unplug or manual sysfs driver unbinding.
  * Code paths exclusive to architectures other than the target architecture.

================================================================================
3. WHEN TO RETURN WorthFuzzing=true (POSITIVE CRITERIA)
================================================================================
Return WorthFuzzing=true whenever the patch touches reachable executable code, including:
- Core Subsystems:
  * Any logic modifications in memory management (mm/), synchronization/locking (kernel/locking/),
    BPF, scheduler, core networking, VFS, or syscall handling.
- Refactorings and Code Cleanups:
  * Any restructuring of reachable data structures, helper abstractions, or algorithm flows.
- Runtime Assertions and Defensive Checks:
  * Any introduction or alteration of assertions (WARN_ON*, VM_WARN_ON*, BUG_ON*, etc.) in reachable paths.
- Reachable Drivers and Protocols:
  * Drivers accessible via virtual buses (virtio, USB gadget, loopback, netlink, binder, sockets, etc.).

================================================================================
4. EXTRACTING FocusSymbols (PREVENTING DILUTION)
================================================================================
When WorthFuzzing=true, you must extract specific kernel functions into FocusSymbols to guide the fuzzer:

- AVOID UBIQUITOUS LIFECYCLE HOT-PATHS:
  Do NOT list generic, ubiquitous functions called by almost every program in the corpus
  (including, but not limited to: general memory allocators and deallocators, page fault
  and trap handlers, or core synchronization primitives; this is not an exhaustive list).
  Listing ubiquitous functions causes the fuzzer to classify thousands of unrelated tests as "focused",
  which severely dilutes fuzzing effort away from the actual changes.

- TARGET SPECIFIC FEATURE LOGIC AND ENTRYPOINTS:
  List functions that specifically implement the logic being added or altered, or direct API entrypoints
  for the subsystem feature under review.

- HANDLING STATIC INLINE FUNCTIONS IN HEADERS (.h):
  Compiler-inlined static functions (such as static inlines in mm/*.h or include/linux/*.h) lack
  distinct symbol addresses in vmlinux and cannot be targeted directly by symbol coverage filters.
  If the changes are primarily in static inline helpers, identify non-static, feature-specific caller
  functions in .c files that exercise them (avoiding ubiquitous lifecycle wrappers).

================================================================================
5. IDENTIFYING EnableConfigs
================================================================================
Identify any specific CONFIG_ options required to properly compile and reach the modified code:
- Inspect Kconfig files and #ifdef guards; do not make assumptions.
- Check "depends on" lines in Kconfig to include any non-standard parent subsystem configs needed.
- Strip any 'CONFIG_' prefix (e.g., return "NET_IPV4" instead of "CONFIG_NET_IPV4").
Prefer calling several tools at the same time to save round-trips.


Use set-results tool to provide results of the analysis.
It must be called exactly once before the final reply.
Ignore results of this tool.

Prompt:
Target architecture: amd64

For your convenience, here is the diff of the changes:
commit 1c4055d34a8b4ed36cbd7c628843b23623959855
Author: syz-cluster <triage@syzkaller.com>
Date:   Mon Oct 5 15:13:48 2026 +0000

    syz-cluster: applied patch under review

diff --git a/arch/alpha/include/uapi/asm/socket.h b/arch/alpha/include/uapi/asm/socket.h
index 946a5fad26912..5d3524c26b2bc 100644
--- a/arch/alpha/include/uapi/asm/socket.h
+++ b/arch/alpha/include/uapi/asm/socket.h
@@ -157,6 +157,10 @@
 
 #define SO_RIGHTS_NOTRUNC      85
 
+#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 f1641dde135fc..245a43f52fb1a 100644
--- a/arch/mips/include/uapi/asm/socket.h
+++ b/arch/mips/include/uapi/asm/socket.h
@@ -168,6 +168,10 @@
 
 #define SO_RIGHTS_NOTRUNC      85
 
+#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 f3a3815c7dc29..f23710e1c6714 100644
--- a/arch/parisc/include/uapi/asm/socket.h
+++ b/arch/parisc/include/uapi/asm/socket.h
@@ -149,6 +149,10 @@
 
 #define SO_RIGHTS_NOTRUNC	0x4053
 
+#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 7907f3b1f0ee0..b35b25bdefc2b 100644
--- a/arch/sparc/include/uapi/asm/socket.h
+++ b/arch/sparc/include/uapi/asm/socket.h
@@ -150,6 +150,10 @@
 
 #define SO_RIGHTS_NOTRUNC        0x005e
 
+#define SO_PASSPIDFD_THREAD      0x005f
+
+#define SO_PEERPIDFD_THREAD      0x0060
+
 #if !defined(__KERNEL__)
 
 
diff --git a/fs/coredump.c b/fs/coredump.c
index 6114839f5178b..870ad720b2152 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 != 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 a6a643f15d08a..29299b2c7ca71 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;
@@ -1070,6 +1077,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/coredump.h b/include/linux/coredump.h
index 7b38ee2e7913b..0bbb7de6a4021 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;
diff --git a/include/linux/pid.h b/include/linux/pid.h
index ddaef0bbc8ba3..87635d0cc1f71 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 c2aee1d91dcfd..3302690a2a281 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 0abf7da9ab236..3c1e82f1a3691 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 d45a5476b97de..3149cdb976c60 100644
--- a/include/linux/sched/signal.h
+++ b/include/linux/sched/signal.h
@@ -674,6 +674,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/include/linux/socket.h b/include/linux/socket.h
index 5a5eb12501032..b17fdb7d38a77 100644
--- a/include/linux/socket.h
+++ b/include/linux/socket.h
@@ -189,10 +189,11 @@ static inline size_t msg_data_left(const struct msghdr *msg)
 
 /* "Socket"-level control message types: */
 
-#define	SCM_RIGHTS	0x01		/* rw: access rights (array of int) */
-#define SCM_CREDENTIALS 0x02		/* rw: struct ucred		*/
-#define SCM_SECURITY	0x03		/* rw: security label		*/
-#define SCM_PIDFD	0x04		/* ro: pidfd (int)		*/
+#define	SCM_RIGHTS		0x01	/* rw: access rights (array of int) */
+#define SCM_CREDENTIALS		0x02	/* rw: struct ucred		*/
+#define SCM_SECURITY		0x03	/* rw: security label		*/
+#define SCM_PIDFD		0x04	/* ro: pidfd (int)		*/
+#define SCM_PIDFD_THREAD	0x05	/* ro: thread pidfd (int)	*/
 
 struct ucred {
 	__u32	pid;
diff --git a/include/net/scm.h b/include/net/scm.h
index 86ae6bc109ec6..b68a143f42de5 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,6 @@ 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->creds.pid = pid_vnr(pid);
 	scm->creds.uid = uid;
 	scm->creds.gid = gid;
@@ -77,8 +76,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)
@@ -94,8 +92,10 @@ static __inline__ int scm_send(struct socket *sock, struct msghdr *msg,
 	memset(scm, 0, sizeof(*scm));
 	scm->creds.uid = INVALID_UID;
 	scm->creds.gid = INVALID_GID;
-	if (forcecreds)
+	if (forcecreds) {
+		scm->pid[PIDTYPE_TGID] = get_pid(task_tgid(current));
 		scm_set_cred(scm, task_tgid(current), current_uid(), current_gid());
+	}
 	unix_get_peersec_dgram(sock, scm);
 	if (msg->msg_controllen <= 0)
 		return 0;
diff --git a/include/net/sock.h b/include/net/sock.h
index 60ea55dc18854..77dfb170d3c89 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
@@ -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.
@@ -545,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;
@@ -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;
diff --git a/include/trace/events/landlock.h b/include/trace/events/landlock.h
index 3a43638c9bc29..9e172ea22d95c 100644
--- a/include/trace/events/landlock.h
+++ b/include/trace/events/landlock.h
@@ -1037,7 +1037,7 @@ TRACE_EVENT(landlock_deny_scope_abstract_unix_socket,
 		 * updates.  The peer socket keeps a reference to sk_peer_pid
 		 * through pid_nr(); 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/include/uapi/asm-generic/socket.h b/include/uapi/asm-generic/socket.h
index 84ea7b92936e2..56fed7ab27ab8 100644
--- a/include/uapi/asm-generic/socket.h
+++ b/include/uapi/asm-generic/socket.h
@@ -152,6 +152,10 @@
 
 #define SO_RIGHTS_NOTRUNC	85
 
+#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/bluetooth/af_bluetooth.c b/net/bluetooth/af_bluetooth.c
index 411d66f24393d..7758e9ea3848c 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 6d56c77741e19..4c40068ba5fb2 100644
--- a/net/bluetooth/hci_sock.c
+++ b/net/bluetooth/hci_sock.c
@@ -284,21 +284,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 1194c37e466f1..872d8fb31b6fb 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/scm.c b/net/core/scm.c
index f0d44ecdb11fc..00025579d53e0 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);
@@ -486,11 +499,16 @@ 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 msghdr *msg, struct scm_cookie *scm,
+			   enum pid_type type, int cmsg_type)
 {
+	unsigned int flags = PIDFD_STALE;
 	struct file *pidfd_file = NULL;
+	struct pid *pid;
 	int len, pidfd;
 
+	pid = scm->pid[type];
+
 	/* put_cmsg() doesn't return an error if CMSG is truncated,
 	 * that's why we need to opencode these checks here.
 	 */
@@ -504,12 +522,15 @@ static void scm_pidfd_recv(struct msghdr *msg, struct scm_cookie *scm)
 		return;
 	}
 
-	if (!scm->pid)
+	if (!pid)
 		return;
 
-	pidfd = pidfd_prepare(scm->pid, PIDFD_STALE, &pidfd_file);
+	if (type == PIDTYPE_PID)
+		flags |= PIDFD_THREAD;
 
-	if (put_cmsg(msg, SOL_SOCKET, SCM_PIDFD, sizeof(int), &pidfd)) {
+	pidfd = pidfd_prepare(pid, flags, &pidfd_file);
+
+	if (put_cmsg(msg, SOL_SOCKET, cmsg_type, sizeof(int), &pidfd)) {
 		if (pidfd_file) {
 			put_unused_fd(pidfd);
 			fput(pidfd_file);
@@ -527,7 +548,7 @@ static bool __scm_recv_common(struct sock *sk, struct msghdr *msg,
 {
 	if (!msg->msg_control) {
 		if (sk->sk_scm_credentials || sk->sk_scm_pidfd ||
-		    scm->fp || scm_has_secdata(sk))
+		    sk->sk_scm_pidfd_thread || scm->fp || scm_has_secdata(sk))
 			msg->msg_flags |= MSG_CTRUNC;
 
 		scm_destroy(scm);
@@ -574,7 +595,10 @@ void scm_recv_unix(struct socket *sock, struct msghdr *msg,
 	}
 
 	if (sock->sk->sk_scm_pidfd)
-		scm_pidfd_recv(msg, scm);
+		scm_pidfd_recv(msg, scm, PIDTYPE_TGID, SCM_PIDFD);
+
+	if (sock->sk->sk_scm_pidfd_thread)
+		scm_pidfd_recv(msg, scm, PIDTYPE_PID, SCM_PIDFD_THREAD);
 
 	scm_destroy_cred(scm);
 }
diff --git a/net/core/sock.c b/net/core/sock.c
index e8551df8330ff..3c25d66ba31af 100644
--- a/net/core/sock.c
+++ b/net/core/sock.c
@@ -1578,6 +1578,13 @@ int sk_setsockopt(struct sock *sk, int level, int optname,
 			ret = -EOPNOTSUPP;
 		break;
 
+	case SO_PASSPIDFD_THREAD:
+		if (sk_is_unix(sk))
+			sk->sk_scm_pidfd_thread = valbool;
+		else
+			ret = -EOPNOTSUPP;
+		break;
+
 	case SO_PASSRIGHTS:
 		if (sk_is_unix(sk))
 			sk->sk_scm_rights = valbool;
@@ -1729,6 +1736,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 is prepared for this.
+	 */
+	if (sk_is_unix(sk))
+		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)
 {
@@ -1893,6 +1944,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;
@@ -1907,7 +1965,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))
@@ -1916,45 +1975,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);
-		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:
 	{
@@ -2380,7 +2408,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);
@@ -3785,7 +3813,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 42cffeafc8c14..a78e8ef3e0b0e 100644
--- a/net/unix/af_unix.c
+++ b/net/unix/af_unix.c
@@ -737,57 +737,53 @@ 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;
 };
 
 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 = 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, 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 = peercred->peer_pid;
+	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;
+	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 = old_pid;
 	peercred->peer_cred = old_cred;
 }
 
@@ -796,14 +792,15 @@ 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);
+	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);
 }
 
 static bool unix_may_passcred(const struct sock *sk)
 {
-	return sk->sk_scm_credentials || sk->sk_scm_pidfd;
+	return sk->sk_scm_credentials || sk->sk_scm_pidfd ||
+		sk->sk_scm_pidfd_thread;
 }
 
 static int unix_listen(struct socket *sock, int backlog)
@@ -1060,6 +1057,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;
@@ -1973,7 +1971,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 +1989,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 +2003,8 @@ 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_set_cred(scm, UNIXCB(skb).pid[PIDTYPE_TGID], UNIXCB(skb).uid, UNIXCB(skb).gid);
 	unix_set_secdata(scm, skb);
 }
 
@@ -2025,30 +2024,35 @@ 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);
 	}
 
 	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)
 {
-	return UNIXCB(skb).pid == scm->pid &&
+	/* 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) &&
 	       unix_secdata_eq(scm, skb);
@@ -3030,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 */
diff --git a/net/unix/af_unix.h b/net/unix/af_unix.h
index 8119dbeef3a3c..402742895acca 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		*/
diff --git a/tools/lib/python/kdoc/xforms_lists.py b/tools/lib/python/kdoc/xforms_lists.py
index e3dda2fe8a537..e792785a15ec6 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"),
diff --git a/tools/testing/selftests/coredump/coredump_socket_test.c b/tools/testing/selftests/coredump/coredump_socket_test.c
index 422728f632ca6..ec73bb690bbcb 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 ed47f01fa53c5..4212656e31f01 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 2a20faf9cb0ad..36306069f62e5 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;
diff --git a/tools/testing/selftests/net/af_unix/Makefile b/tools/testing/selftests/net/af_unix/Makefile
index a66f10fb0c231..45b841758f1bf 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 2c18b92a26035..cbe53fab44fc5 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,18 @@
 #define SCM_PIDFD 0x04
 #endif
 
+#ifndef SCM_PIDFD_THREAD
+#define SCM_PIDFD_THREAD 0x05
+#endif
+
+#ifndef SO_PASSPIDFD_THREAD
+#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()
@@ -553,4 +566,593 @@ TEST_F(scm_pidfd, test)
 	close(pfd);
 }
 
+struct thread_ids {
+	pid_t pid;
+	pid_t tid;
+};
+
+#define MAX_WRITERS 2
+
+/* Used by writers to signal they've written, so we can sequence multiple writers */
+static int seq_pipe[2];
+
+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;
+
+	/* let the client start the next writer, so the queue order is known */
+	if (write(seq_pipe[1], "1", 1) != 1)
+		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;
+
+	/* signal we've written our info */
+	if (write(seq_pipe[1], "1", 1) != 1)
+		return (void *)1;
+
+	/* stay alive until the receiver has looked at our pidfd */
+	if (read(fd, &sync, 1) != 1)
+		return (void *)1;
+
+	return NULL;
+}
+
+/*
+ * Runs @nwriters copies of @sender, each in its own non-leader thread, one
+ * at a time so the queue order is known, then tells the parent on @ackfd
+ * that everything is queued.
+ */
+static void thread_client(int fd, int syncfd, int ackfd,
+			  void *(*sender)(void *), int nwriters)
+{
+	pthread_t writers[MAX_WRITERS];
+	void *ret;
+	char sync;
+	int i;
+
+	/* wait until the receiver enabled the options it wants */
+	if (read(syncfd, &sync, 1) != 1)
+		child_die();
+
+	for (i = 0; i < nwriters; i++) {
+		if (pthread_create(&writers[i], NULL, sender, &fd))
+			child_die();
+
+		if (read(seq_pipe[0], &sync, 1) != 1)
+			child_die();
+	}
+
+	if (write(ackfd, "1", 1) != 1)
+		child_die();
+
+	for (i = 0; i < nwriters; i++)
+		if (pthread_join(writers[i], &ret) || ret)
+			child_die();
+
+	exit(0);
+}
+
+struct pidfd_msg {
+	ssize_t len;
+	struct thread_ids ids;
+	struct pidfd_info tgid_info;
+	struct pidfd_info thread_info;
+	int tgid_flags;
+	int thread_flags;
+	bool have_tgid;
+	bool have_thread;
+};
+
+static int get_pidfd_info(int pidfd, struct pidfd_info *info)
+{
+	info->mask = PIDFD_INFO_PID;
+	if (ioctl(pidfd, PIDFD_GET_INFO, info)) {
+		log_err("ioctl(PIDFD_GET_INFO)");
+		return -1;
+	}
+
+	return 0;
+}
+
+/* One recvmsg(), reporting how many bytes came back and what the pidfd
+ * cmsgs that came with them say.
+ */
+static int read_pidfd_msg(int fd, struct pidfd_msg *out)
+{
+	char control[CMSG_SPACE(sizeof(int)) * 2] = { 0 };
+	int tgid_pidfd = -1, thread_pidfd = -1;
+	struct thread_ids buf[2] = { 0 };
+	struct msghdr msg = { 0 };
+	struct cmsghdr *cmsg;
+	struct iovec iov;
+
+	iov.iov_base = buf;
+	iov.iov_len = sizeof(buf);
+	msg.msg_iov = &iov;
+	msg.msg_iovlen = 1;
+	msg.msg_control = control;
+	msg.msg_controllen = sizeof(control);
+
+	out->len = recvmsg(fd, &msg, 0);
+	if (out->len < 0) {
+		log_err("recvmsg");
+		return -1;
+	}
+
+	if (msg.msg_flags & MSG_CTRUNC) {
+		log_err("recvmsg: control truncated");
+		return -1;
+	}
+
+	out->ids = buf[0];
+
+	for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL;
+	     cmsg = CMSG_NXTHDR(&msg, cmsg)) {
+		if (cmsg->cmsg_level != SOL_SOCKET)
+			continue;
+
+		if (cmsg->cmsg_type == SCM_PIDFD)
+			memcpy(&tgid_pidfd, CMSG_DATA(cmsg), sizeof(tgid_pidfd));
+		else if (cmsg->cmsg_type == SCM_PIDFD_THREAD)
+			memcpy(&thread_pidfd, CMSG_DATA(cmsg), sizeof(thread_pidfd));
+	}
+
+	out->have_tgid = tgid_pidfd >= 0;
+	out->have_thread = thread_pidfd >= 0;
+
+	if (out->have_tgid) {
+		if (get_pidfd_info(tgid_pidfd, &out->tgid_info))
+			return -1;
+		out->tgid_flags = fcntl(tgid_pidfd, F_GETFL);
+		close(tgid_pidfd);
+	}
+
+	if (out->have_thread) {
+		if (get_pidfd_info(thread_pidfd, &out->thread_info))
+			return -1;
+		out->thread_flags = fcntl(thread_pidfd, F_GETFL);
+		close(thread_pidfd);
+	}
+
+	return 0;
+}
+
+/*
+ * Runs @nwriters threads of one child process against a SOCK_STREAM pair,
+ * all of them queued before the parent reads. SO_PASSPIDFD is set when
+ * @want_tgid is given, SO_PASSPIDFD_THREAD when @want_thread is. @nread
+ * reads are returned in @out.
+ */
+static int pidfd_flow(void *(*sender)(void *), int nwriters, bool want_tgid,
+		      bool want_thread, struct pidfd_msg *out, int nread)
+{
+	int child_status = 0;
+	int syncpipe[2];
+	int ackpipe[2];
+	int sk[2];
+	int on = 1;
+	char sync;
+	pid_t child;
+	int i;
+
+	if (nwriters > MAX_WRITERS)
+		return -1;
+
+	if (socketpair(AF_UNIX, SOCK_STREAM, 0, sk))
+		return -1;
+	if (pipe(syncpipe) || pipe(ackpipe) || pipe(seq_pipe))
+		return -1;
+
+	child = fork();
+	if (child < 0)
+		return -1;
+
+	if (child == 0) {
+		close(sk[0]);
+		close(syncpipe[1]);
+		close(ackpipe[0]);
+		thread_client(sk[1], syncpipe[0], ackpipe[1], sender, nwriters);
+	}
+	close(sk[1]);
+	close(syncpipe[0]);
+	close(ackpipe[1]);
+	close(seq_pipe[0]);
+	close(seq_pipe[1]);
+
+	if (want_tgid &&
+	    setsockopt(sk[0], SOL_SOCKET, SO_PASSPIDFD, &on, sizeof(on))) {
+		log_err("Failed to set SO_PASSPIDFD");
+		return -1;
+	}
+
+	if (want_thread &&
+	    setsockopt(sk[0], SOL_SOCKET, SO_PASSPIDFD_THREAD, &on, sizeof(on))) {
+		log_err("Failed to set SO_PASSPIDFD_THREAD");
+		return -1;
+	}
+
+	/* let the child know the options are set, it can write now */
+	if (write(syncpipe[1], "1", 1) != 1)
+		return -1;
+	close(syncpipe[1]);
+
+	/* wait until every writer has queued its message */
+	if (read(ackpipe[0], &sync, 1) != 1)
+		return -1;
+	close(ackpipe[0]);
+
+	for (i = 0; i < nread; i++)
+		if (read_pidfd_msg(sk[0], &out[i]))
+			return -1;
+
+	/* release the writers */
+	for (i = 0; i < nwriters; i++)
+		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));
+
+	/* Verify that the options are truly set independently */
+	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));
+
+	ASSERT_EQ(0, sockopt_set(sk[0], SO_PASSPIDFD, 1));
+	ASSERT_EQ(1, sockopt_get(sk[0], SO_PASSPIDFD));
+	ASSERT_EQ(1, sockopt_get(sk[0], SO_PASSPIDFD_THREAD));
+
+	/* Verify that the options are truly cleared independently */
+	ASSERT_EQ(0, sockopt_set(sk[0], SO_PASSPIDFD, 0));
+	ASSERT_EQ(0, sockopt_get(sk[0], SO_PASSPIDFD));
+	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_THREAD));
+	ASSERT_EQ(0, sockopt_get(sk[0], SO_PASSPIDFD));
+
+	close(sk[0]);
+	close(sk[1]);
+}
+
+/* A receiver that only asked about the process cannot tell the two
+ * threads apart, so their writes are glued into one read.
+ */
+TEST(scm_pidfd_stream_glues_threads)
+{
+	struct pidfd_msg msg[1] = { 0 };
+
+	ASSERT_EQ(0, pidfd_flow(send_ids_thread, 2, true, false, msg, 1));
+	EXPECT_EQ(sizeof(struct thread_ids) * 2, msg[0].len);
+
+	EXPECT_TRUE(msg[0].have_tgid);
+	EXPECT_FALSE(msg[0].have_thread);
+	EXPECT_EQ(msg[0].ids.pid, msg[0].tgid_info.pid);
+	EXPECT_EQ(msg[0].ids.pid, msg[0].tgid_info.tgid);
+}
+
+/* A receiver asking for both thread and process pidfd should get a unique
+ * msg for each thread writing
+ */
+TEST(scm_pidfd_thread_stream_splits_on_threads)
+{
+	struct pidfd_msg msg[2] = { 0 };
+	int i;
+
+	ASSERT_EQ(0, pidfd_flow(send_ids_thread, 2, true, true, msg, 2));
+
+	for (i = 0; i < 2; i++) {
+		EXPECT_EQ(sizeof(struct thread_ids), msg[i].len);
+		EXPECT_TRUE(msg[i].have_tgid);
+		EXPECT_TRUE(msg[i].have_thread);
+
+		/* Make sure the info makes sense for the pidfd type */
+		EXPECT_EQ(msg[i].ids.pid, msg[i].tgid_info.pid);
+		EXPECT_EQ(msg[i].ids.pid, msg[i].tgid_info.tgid);
+		EXPECT_EQ(msg[i].ids.tid, msg[i].thread_info.pid);
+		EXPECT_EQ(msg[i].ids.pid, msg[i].thread_info.tgid);
+		EXPECT_NE(msg[i].ids.pid, msg[i].ids.tid);
+	}
+
+	/* Make sure we really got unique threads per message */
+	EXPECT_NE(msg[0].ids.tid, msg[1].ids.tid);
+	EXPECT_EQ(msg[0].ids.pid, msg[1].ids.pid);
+	EXPECT_EQ(msg[0].tgid_info.pid, msg[1].tgid_info.pid);
+	EXPECT_NE(msg[0].thread_info.pid, msg[1].thread_info.pid);
+}
+
+/* Sends from the thread-group leader */
+static int leader_flow(bool want_thread, struct pidfd_msg *out)
+{
+	struct thread_ids ids = {
+		.pid = getpid(),
+		.tid = gettid(),
+	};
+	int sk[2];
+	int on = 1;
+	int ret;
+
+	if (socketpair(AF_UNIX, SOCK_STREAM, 0, sk))
+		return -1;
+
+	if (setsockopt(sk[0], SOL_SOCKET, SO_PASSPIDFD, &on, sizeof(on))) {
+		log_err("Failed to set SO_PASSPIDFD");
+		return -1;
+	}
+
+	if (want_thread &&
+	    setsockopt(sk[0], SOL_SOCKET, SO_PASSPIDFD_THREAD, &on, sizeof(on))) {
+		log_err("Failed to set SO_PASSPIDFD_THREAD");
+		return -1;
+	}
+
+	if (send(sk[1], &ids, sizeof(ids), 0) != sizeof(ids)) {
+		log_err("send");
+		return -1;
+	}
+
+	ret = read_pidfd_msg(sk[0], out);
+
+	close(sk[0]);
+	close(sk[1]);
+
+	return ret;
+}
+
+TEST(scm_pidfd_leader_sender)
+{
+	struct pidfd_msg msg = { 0 };
+
+	ASSERT_EQ(getpid(), gettid());
+
+	ASSERT_EQ(0, leader_flow(false, &msg));
+	ASSERT_TRUE(msg.have_tgid);
+	EXPECT_FALSE(msg.have_thread);
+
+	EXPECT_EQ(getpid(), msg.tgid_info.pid);
+	EXPECT_EQ(getpid(), msg.tgid_info.tgid);
+	EXPECT_FALSE(msg.tgid_flags & O_EXCL);
+}
+
+TEST(scm_pidfd_thread_leader_sender)
+{
+	struct pidfd_msg msg = { 0 };
+
+	ASSERT_EQ(getpid(), gettid());
+
+	ASSERT_EQ(0, leader_flow(true, &msg));
+	ASSERT_TRUE(msg.have_tgid);
+	ASSERT_TRUE(msg.have_thread);
+
+	EXPECT_EQ(getpid(), msg.tgid_info.pid);
+	EXPECT_EQ(getpid(), msg.tgid_info.tgid);
+	EXPECT_EQ(getpid(), msg.thread_info.pid);
+	EXPECT_EQ(getpid(), msg.thread_info.tgid);
+
+	EXPECT_FALSE(msg.tgid_flags & O_EXCL);
+	EXPECT_TRUE(msg.thread_flags & O_EXCL);
+}
+
+TEST(scm_pidfd_thread_and_group)
+{
+	struct pidfd_msg msg = { 0 };
+
+	ASSERT_EQ(0, pidfd_flow(send_ids_thread, 1, true, true, &msg, 1));
+	ASSERT_NE(msg.ids.pid, msg.ids.tid);
+	ASSERT_TRUE(msg.have_tgid);
+	ASSERT_TRUE(msg.have_thread);
+
+	EXPECT_EQ(msg.ids.pid, msg.tgid_info.pid);
+	EXPECT_EQ(msg.ids.pid, msg.tgid_info.tgid);
+
+	EXPECT_EQ(msg.ids.tid, msg.thread_info.pid);
+	EXPECT_EQ(msg.ids.pid, msg.thread_info.tgid);
+}
+
+TEST(scm_pidfd_thread)
+{
+	struct pidfd_msg msg = { 0 };
+
+	ASSERT_EQ(0, pidfd_flow(send_ids_thread, 1, false, true, &msg, 1));
+	ASSERT_NE(msg.ids.pid, msg.ids.tid);
+	ASSERT_TRUE(msg.have_thread);
+	EXPECT_FALSE(msg.have_tgid);
+	EXPECT_EQ(msg.ids.tid, msg.thread_info.pid);
+	EXPECT_EQ(msg.ids.pid, msg.thread_info.tgid);
+}
+
+TEST(scm_pidfd_thread_group)
+{
+	struct pidfd_msg msg = { 0 };
+
+	ASSERT_EQ(0, pidfd_flow(send_ids_thread, 1, true, false, &msg, 1));
+	ASSERT_NE(msg.ids.pid, msg.ids.tid);
+	ASSERT_TRUE(msg.have_tgid);
+	EXPECT_FALSE(msg.have_thread);
+	EXPECT_EQ(msg.ids.pid, msg.tgid_info.pid);
+	EXPECT_EQ(msg.ids.pid, msg.tgid_info.tgid);
+}
+
+TEST(scm_pidfd_thread_creds)
+{
+	struct pidfd_msg msg = { 0 };
+
+	ASSERT_EQ(0, pidfd_flow(send_ids_creds_thread, 1, true, true, &msg, 1));
+	ASSERT_NE(msg.ids.pid, msg.ids.tid);
+	ASSERT_TRUE(msg.have_tgid);
+	ASSERT_TRUE(msg.have_thread);
+	EXPECT_EQ(msg.ids.pid, msg.tgid_info.pid);
+	EXPECT_EQ(msg.ids.pid, msg.tgid_info.tgid);
+	EXPECT_EQ(msg.ids.tid, msg.thread_info.pid);
+	EXPECT_EQ(msg.ids.pid, msg.thread_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

3/2 2026/10/05 15:13 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 27370
							output: 32
							thoughts: 2332
						

Thoughts:
**Patch Evaluation: PIDFD and SCM Socket Options**

Alright, let's break this down. My initial assessment screams "worth fuzzing" – this patch series is definitely touching sensitive areas. It introduces new socket options ( `SO_PASSPIDFD_THREAD`, `SO_PEERPIDFD_THREAD`) to manage and expose thread PIDs through file descriptors and SCM credentials, and modifies core kernel networking components, pid management, and coredump functionality. All of this can be reached directly via userspace syscalls on amd64, which makes it an obvious target for Syzkaller.

Here's the gist of what's happening: The patch is upgrading `pid` and `scm` structs, particularly the pid and peer structs to use `DECLARE_PIDS` which allows for managing the Thread Group ID (TGID) and Thread ID (PID) and registering them with `pidfs`. New socket options are available to pass a PIDFD representing the calling thread to a socket endpoint. Core functions in `net/core` and `net/unix` are being modified to support this new behavior, with modifications to how SCM credentials are handled and passed, and it appears there's new socket behavior when the `sk_scm_pidfd_thread` flag is set. The changes touch critical areas like coredump functionality, which gets new support to get pids. Bluetooth networking is also affected. Notably, there are new checks and potential splitting of reads in `unix_stream_read_generic` if different threads wrote the data when `sk_scm_pidfd_thread` is set.

Now, let's refine this for fuzzing purposes. We need to identify specific functions that are implementing *new* or *changed* behavior, and filter out the ubiquitous ones to prevent dilution.

The key focus symbols from the `.c` files seem to be:
*   `fs/coredump.c`: `coredump_parse`, `umh_coredump_setup`, `coredump_sock_connect`
*   `fs/pidfs.c`: `pidfs_coredump`, `__pidfs_register_pids`
*   `net/core/scm.c`: `scm_replace_pid`, `scm_pidfd_recv`, `scm_recv_unix`, `__scm_send`
*   `net/core/sock.c`: `sk_setsockopt`, `sk_getsockopt_peerpidfd`, `sk_getsockopt`
*   `net/unix/af_unix.c`:
    *   `unix_maybe_add_creds`
    *   `unix_skb_scm_eq`
    *   `unix_stream_read_generic`
    *   `unix_bpf_bypass_getsockopt`
    *   `unix_destruct_scm`
    *   `unix_scm_to_skb`
    *   `unix_skb_to_scm`
    *   `unix_stream_connect` (and its callees `prepare_peercred`, `update_peercred`, `copy_peercred`)

I'll need to double-check `scm_replace_pid` to ensure it's actually not static inline, and the static functions in `net/unix/af_unix.c` to see how directly reachable they are, but this gives us a good starting point for constructing a focused fuzzing campaign. This is all exposed through userspace entrypoints. This patch has a large potential for new or changed behavior through syscalls. This has a high likelihood of leading to exploitable vulnerabilities.



4/2 2026/10/05 15:14 tool grepper
0m Args:
{
  "Expression": "scm_replace_pid",
  "PathPrefix": "net/core/scm.c"
}

Results:
{
  "Output": "net/core/scm.c=148=EXPORT_SYMBOL(__scm_destroy);\nnet/core/scm.c-149-\nnet/core/scm.c:150:static inline int scm_replace_pid(struct scm_cookie *scm, struct pid *pid)\nnet/core/scm.c-151-{\n--\nnet/core/scm.c=178=int __scm_send(struct socket *sock, struct msghdr *msg, struct scm_cookie *p)\n--\nnet/core/scm.c-230-\t\t\t\t/* pass a struct pid reference from\nnet/core/scm.c:231:\t\t\t\t * find_get_pid() to scm_replace_pid().\nnet/core/scm.c-232-\t\t\t\t */\nnet/core/scm.c:233:\t\t\t\terr = scm_replace_pid(p, pid);\nnet/core/scm.c-234-\t\t\t\tif (err) {\n"
}

5/2 2026/10/05 15:14 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 30764
							output: 33
							thoughts: 118
						

Thoughts:
Okay, let's break this down.

**Investigating Potential PID Leakage Points**

Right, so I'm tracing some potential areas where we might see PID-related information being passed around. The first thing that jumps out is the connection between `__scm_send` and `scm_replace_pid`. That suggests some mechanism for replacing or manipulating PIDs during sending. Then, `scm_recv_unix` calling `scm_pidfd_recv` is another area to watch; we know receive operations are often where secrets are exposed, so that warrants a closer look.

Now, let's shift gears and consider `sk_getsockopt_peerpidfd`. That's intriguing because it's static within `net/core/sock.c` and is called through `sk_getsockopt`. Being static means it's localized, and `sk_getsockopt` is a well-known function, so it's probably worth investigating what kind of data it exposes.

Finally, I need to check out `__pidfs_register_pids`. I know that's located within `fs/pidfs.c` so I will go ahead and verify what the register function does. This is a common location for PID information to be stored.



6/2 2026/10/05 15:14 tool grepper
0m Args:
{
  "Expression": "pidfs_register_pids",
  "PathPrefix": "fs/pidfs.c"
}

Results:
{
  "Output": "fs/pidfs.c=1045=int pidfs_register_pid_gfp(struct pid *pid, gfp_t gfp)\n--\nfs/pidfs.c-1080-/* Register the pids up to pid type @last of @pids in pidfs. */\nfs/pidfs.c:1081:int __pidfs_register_pids(struct pid *const *pids, enum pid_type last)\nfs/pidfs.c-1082-{\n"
}

7/2 2026/10/05 15:14 llm patch-evaluator
1m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 31316
							output: 54
							thoughts: 46
						

Thoughts:
Okay, let's dive into this. Here's how I'd approach this task, as if these were my internal thoughts:

**Peering into the Unix Socket Credential Handling**

Alright, time to get my hands dirty with `net/unix/af_unix.c`. I need to understand the flow of credential management within the Unix domain socket code. The initial goal is to figure out the calling relationships, specifically for the credential-related functions. So, I need to trace and identify *who* calls `prepare_peercred`, `update_peercred`, `copy_peercred`, and `unix_maybe_add_creds`. Understanding the calling contexts will give me a better handle on how credentials are handled, copied, and potentially modified throughout the socket lifecycle. This kind of analysis is crucial for security audits, performance optimization (especially in scenarios with frequent credential transfers), and debugging any potential issues related to socket security. The goal is to fully map the logic behind this interaction. I’ll need to understand where and why those functions are called - from which other functions they are called from, and under what conditions. Let's see what the code reveals!



8/2 2026/10/05 15:15 tool grepper
0m Args:
{
  "Expression": "(prepare_peercred|update_peercred|copy_peercred|unix_maybe_add_creds)",
  "PathPrefix": "net/unix/af_unix.c"
}

Results:
{
  "Output": "net/unix/af_unix.c=739=struct unix_peercred {\n--\nnet/unix/af_unix.c-743-\nnet/unix/af_unix.c:744:static inline int prepare_peercred(struct unix_peercred *peercred)\nnet/unix/af_unix.c-745-{\n--\nnet/unix/af_unix.c=770=static inline void init_peercred(struct sock *sk,\n--\nnet/unix/af_unix.c-776-\nnet/unix/af_unix.c:777:static void update_peercred(struct sock *sk, struct unix_peercred *peercred)\nnet/unix/af_unix.c-778-{\n--\nnet/unix/af_unix.c-789-\nnet/unix/af_unix.c:790:static void copy_peercred(struct sock *sk, struct sock *peersk)\nnet/unix/af_unix.c-791-{\n--\nnet/unix/af_unix.c=806=static int unix_listen(struct socket *sock, int backlog)\n--\nnet/unix/af_unix.c-818-\t\tgoto out;\t/* No listens on an unbound socket */\nnet/unix/af_unix.c:819:\terr = prepare_peercred(\u0026peercred);\nnet/unix/af_unix.c-820-\tif (err)\n--\nnet/unix/af_unix.c-831-\t/* set credentials so connect can copy them */\nnet/unix/af_unix.c:832:\tupdate_peercred(sk, \u0026peercred);\nnet/unix/af_unix.c-833-\terr = 0;\n--\nnet/unix/af_unix.c=1632=static int unix_stream_connect(struct socket *sock, struct sockaddr_unsized *uaddr,\n--\nnet/unix/af_unix.c-1660-\nnet/unix/af_unix.c:1661:\terr = prepare_peercred(\u0026peercred);\nnet/unix/af_unix.c-1662-\tif (err)\n--\nnet/unix/af_unix.c-1780-\t/* Set credentials */\nnet/unix/af_unix.c:1781:\tcopy_peercred(sk, other);\nnet/unix/af_unix.c-1782-\n--\nnet/unix/af_unix.c=1812=static int unix_socketpair(struct socket *socka, struct socket *sockb)\n--\nnet/unix/af_unix.c-1817-\nnet/unix/af_unix.c:1818:\terr = prepare_peercred(\u0026ska_peercred);\nnet/unix/af_unix.c-1819-\tif (err)\n--\nnet/unix/af_unix.c-1821-\nnet/unix/af_unix.c:1822:\terr = prepare_peercred(\u0026skb_peercred);\nnet/unix/af_unix.c-1823-\tif (err) {\n--\nnet/unix/af_unix.c=2004=static void unix_skb_to_scm(struct sk_buff *skb, struct scm_cookie *scm)\n--\nnet/unix/af_unix.c-2011-/**\nnet/unix/af_unix.c:2012: * unix_maybe_add_creds() - Adds current task uid/gid and struct pid to skb if needed.\nnet/unix/af_unix.c-2013- * @skb: skb to attach creds to.\n--\nnet/unix/af_unix.c-2023- */\nnet/unix/af_unix.c:2024:static int unix_maybe_add_creds(struct sk_buff *skb, const struct sock *sk,\nnet/unix/af_unix.c-2025-\t\t\t\tconst struct sock *other)\n--\nnet/unix/af_unix.c=2094=static int unix_dgram_sendmsg(struct socket *sock, struct msghdr *msg,\n--\nnet/unix/af_unix.c-2188-\nnet/unix/af_unix.c:2189:\terr = unix_maybe_add_creds(skb, sk, other);\nnet/unix/af_unix.c-2190-\tif (err)\n--\nnet/unix/af_unix.c=2328=static int queue_oob(struct sock *sk, struct msghdr *msg, struct sock *other,\n--\nnet/unix/af_unix.c-2343-\nnet/unix/af_unix.c:2344:\terr = unix_maybe_add_creds(skb, sk, other);\nnet/unix/af_unix.c-2345-\tif (err)\n--\nnet/unix/af_unix.c=2388=static int unix_stream_sendmsg(struct socket *sock, struct msghdr *msg,\n--\nnet/unix/af_unix.c-2461-\nnet/unix/af_unix.c:2462:\t\terr = unix_maybe_add_creds(skb, sk, other);\nnet/unix/af_unix.c-2463-\t\tif (err)\n"
}

9/2 2026/10/05 15:15 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 33009
							output: 19
							thoughts: 1022
						

Thoughts:
**Analyzing Kernel Symbols for Fuzzing Target Selection**

Okay, let's break this down. My goal is to identify functions that are *specifically* relevant to the changes being introduced, suitable for focused fuzzing. We want to avoid listing generic, ubiquitous functions.

First, I need to list functions that implement the logic being added or altered, or direct API entrypoints for the subsystem feature under review. I started by examining `net/unix/af_unix.c`. I looked for both static and non-static symbols. `prepare_peercred` is inline in `net/unix/af_unix.c`. Functions like `copy_peercred`, `update_peercred`, and `drop_peercred` are static. Then I checked the other symbols: `unix_stream_connect`, `unix_listen`, `unix_socketpair`, `unix_dgram_sendmsg`, `unix_stream_sendmsg`, and `unix_stream_read_generic`.

Knowing that even static functions have symbols in vmlinux/kallsyms, unless they are inlined, I checked what's static/static inline in `net/unix/af_unix.c`: `unix_skb_scm_eq`, `unix_maybe_add_creds`, `unix_stream_read_generic`, `copy_peercred`, `update_peercred`, `drop_peercred`, `unix_destruct_scm`, `unix_scm_to_skb`, and `unix_skb_to_scm`.

Moving on to `net/core/scm.c`, I found `scm_pidfd_recv` (static), `scm_recv_unix` (non-static), `__scm_send` (non-static), and `scm_replace_pid` (static inline).

In `net/core/sock.c`, I looked at `sk_getsockopt_peerpidfd` (static), `sk_setsockopt` (non-static), `sk_getsockopt` (non-static), and `__sk_destruct` (static).

Finally, I checked `fs/pidfs.c` and `fs/coredump.c`, considering their relationship to the feature. In `fs/pidfs.c`, I saw `pidfs_coredump` (non-static), `pidfs_coredump_pid` (static), and `__pidfs_register_pids` (non-static). In `fs/coredump.c`, there were `coredump_sock_connect` (static) and `umh_coredump_setup` (static).

Now the crucial part: filtering. Based on the guidance, I need to identify *specific* entrypoints. I concluded that `sk_getsockopt` and `sk_setsockopt` are too generic. However, `sk_getsockopt_peerpidfd` is a new function implementing the logic for `SO_PEERPIDFD` and `SO_PEERPIDFD_THREAD`, a focused target. Similarly, `__pidfs_register_pids` specifically implements pidfs registration for pid arrays. `scm_pidfd_recv` is specifically altered for thread pidfd receiving. `scm_recv_unix` is the entrypoint calling `scm_pidfd_recv`. `unix_skb_scm_eq` is altered for stream cred equality checking with thread pidfd. `unix_maybe_add_creds` is altered to register task pids. `prepare_peercred`, `update_peercred`, and `copy_peercred` are in af_unix. And finally, `coredump_sock_connect` is altered for coredumping to sockets with peer pid check.

Now, before finalizing the list, I will make sure there aren't any specific config options I need to know about, I'll need to check the relevant Kconfig files. This should give me a solid set of targets for fuzzing.



10/2 2026/10/05 15:15 tool grepper
0m Error:
no matches

Args:
{
  "Expression": "CONFIG_PID_FS"
}

Results:
{
  "error": "no matches"
}

11/2 2026/10/05 15:15 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 34918
							output: 18
							thoughts: 15
						

Thoughts:
Okay, here's my summarized thought process, presented as if I were thinking it:

**Digging for the `pidfs.c` Source**

Alright, I need to track down the source file for `pidfs.c`. Given my understanding of typical kernel organization, my first instinct is to check the build system's configuration and build files. Where would I expect to find the information about building this specific file? Definitely the `Makefile` or, if this is a driver or module, the `Kconfig` files would be my primary targets. These are the usual suspects where I'd define the compilation rules and dependencies. Let's start there. I'll search those files for references to `pidfs.c` or, perhaps, a related module name.



12/2 2026/10/05 15:15 tool grepper
0m Args:
{
  "Expression": "pidfs\\.o"
}

Results:
{
  "Output": "fs/Makefile=10=obj-y :=\topen.o read_write.o file_table.o super.o \\\n--\nfs/Makefile-17-\t\tfs_dirent.o fs_context.o fs_parser.o fsopen.o init.o \\\nfs/Makefile:18:\t\tkernel_read_file.o mnt_idmapping.o remap_range.o pidfs.o \\\nfs/Makefile-19-\t\tfile_attr.o fserror.o nullfs.o failfs.o\n"
}

13/2 2026/10/05 15:15 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 35250
							output: 25
							thoughts: 62
						

Thoughts:
Here's my analysis of the provided information:

**Understanding the Kernel Filesystem Build Process**

Okay, so I'm looking at how this `pidfs.o` module gets built into the kernel. The crucial piece here is that it's unconditionally included. The `obj-y` directive confirms that `pidfs.o` is always built, no matter what. That's good to know for context.

Now, I'm checking `fs/pidfs.c` itself. The presence of `#ifdef CONFIG_COREDUMP` around `pidfs_coredump` is an interesting point. This immediately triggers my memory to consider the implications for core dumps. It seems that whether or not core dumps are supported, impacts how `pidfs` functions.

Finally, while I'm digging into filesystem stuff, I'll quickly check `CONFIG_UNIX` and `af_unix.c`. There is no relationship between the `pidfs` and `unix` filesystems from the prompt, but it's always good to refresh my awareness on related parts of the kernel.



14/2 2026/10/05 15:15 tool grepper
0m Args:
{
  "Expression": "af_unix",
  "PathPrefix": "net/"
}

Results:
{
  "Output": "net/core/scm.c-40-#include \u003cnet/cls_cgroup.h\u003e\nnet/core/scm.c:41:#include \u003cnet/af_unix.h\u003e\nnet/core/scm.c-42-\n--\nnet/unix/Makefile=6=obj-$(CONFIG_UNIX)\t+= unix.o\nnet/unix/Makefile-7-\nnet/unix/Makefile:8:unix-y\t\t\t:= af_unix.o garbage.o\nnet/unix/Makefile-9-unix-$(CONFIG_SYSCTL)\t+= sysctl_net_unix.o\n--\nnet/unix/af_unix.c-60- *\tsocketpair(...SOCK_RAW..) doesn't panic the kernel.\nnet/unix/af_unix.c:61: *\tBSD af_unix apparently has connect forgetting to block properly.\nnet/unix/af_unix.c-62- *\t\t(need to check this with the POSIX spec in detail)\n--\nnet/unix/af_unix.c-105-#include \u003clinux/uaccess.h\u003e\nnet/unix/af_unix.c:106:#include \u003cnet/af_unix.h\u003e\nnet/unix/af_unix.c-107-#include \u003cnet/net_namespace.h\u003e\n--\nnet/unix/af_unix.c-112-\nnet/unix/af_unix.c:113:#include \"af_unix.h\"\nnet/unix/af_unix.c-114-\n--\nnet/unix/af_unix.c=3941=static void __init bpf_iter_register(void)\n--\nnet/unix/af_unix.c-3948-\nnet/unix/af_unix.c:3949:static int __init af_unix_init(void)\nnet/unix/af_unix.c-3950-{\n--\nnet/unix/af_unix.c-3985-/* Later than subsys_initcall() because we depend on stuff initialised there */\nnet/unix/af_unix.c:3986:fs_initcall(af_unix_init);\n--\nnet/unix/diag.c-8-#include \u003clinux/user_namespace.h\u003e\nnet/unix/diag.c:9:#include \u003cnet/af_unix.h\u003e\nnet/unix/diag.c-10-#include \u003cnet/netlink.h\u003e\n--\nnet/unix/diag.c-13-\nnet/unix/diag.c:14:#include \"af_unix.h\"\nnet/unix/diag.c-15-\n--\nnet/unix/garbage.c-70-#include \u003clinux/workqueue.h\u003e\nnet/unix/garbage.c:71:#include \u003cnet/af_unix.h\u003e\nnet/unix/garbage.c-72-#include \u003cnet/scm.h\u003e\n--\nnet/unix/garbage.c-74-\nnet/unix/garbage.c:75:#include \"af_unix.h\"\nnet/unix/garbage.c-76-\n--\nnet/unix/sysctl_net_unix.c-2-/*\nnet/unix/sysctl_net_unix.c:3: * NET4:\tSysctl interface to net af_unix subsystem.\nnet/unix/sysctl_net_unix.c-4- *\n--\nnet/unix/sysctl_net_unix.c-10-#include \u003clinux/sysctl.h\u003e\nnet/unix/sysctl_net_unix.c:11:#include \u003cnet/af_unix.h\u003e\nnet/unix/sysctl_net_unix.c-12-#include \u003cnet/net_namespace.h\u003e\nnet/unix/sysctl_net_unix.c-13-\nnet/unix/sysctl_net_unix.c:14:#include \"af_unix.h\"\nnet/unix/sysctl_net_unix.c-15-\n--\nnet/unix/unix_bpf.c-5-#include \u003clinux/skmsg.h\u003e\nnet/unix/unix_bpf.c:6:#include \u003cnet/af_unix.h\u003e\nnet/unix/unix_bpf.c-7-\nnet/unix/unix_bpf.c:8:#include \"af_unix.h\"\nnet/unix/unix_bpf.c-9-\n"
}

15/2 2026/10/05 15:15 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 36623
							output: 26
							thoughts: 48
						

Thoughts:
Okay, here's my summary, considering my expertise:

**Checking Kernel Configuration Dependencies**

Alright, I'm working through these configuration options. First, it's clear that `CONFIG_UNIX` is a prerequisite for `af_unix.c`. That makes perfect sense. Now, let's think about `CONFIG_COREDUMP`. My initial thought is to cross-reference that with the relevant source file, probably `fs/coredump.c`, to see exactly how and where that configuration option is being utilized. Understanding the connections between these configurations and the kernel components is key to a robust build.



16/2 2026/10/05 15:15 tool grepper
0m Args:
{
  "Expression": "coredump\\.o",
  "PathPrefix": "fs/"
}

Results:
{
  "Output": "fs/Makefile=46=obj-$(CONFIG_NFS_COMMON)\t+= nfs_common/\nfs/Makefile:47:obj-$(CONFIG_COREDUMP)\t\t+= coredump.o\nfs/Makefile-48-obj-$(CONFIG_SYSCTL)\t\t+= drop_caches.o sysctls.o\n"
}

17/2 2026/10/05 15:15 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 36915
							output: 24
							thoughts: 279
						

Thoughts:
**My Analysis of the Core Dump and Bluetooth Changes**

Okay, so I'm looking at this patch and immediately, `CONFIG_COREDUMP` jumps out at me, as it controls `fs/coredump.c` and is used in `#ifdef CONFIG_COREDUMP` within `fs/pidfs.c`. But then I see modifications in `net/bluetooth/`. My initial thought is, "Wait, what's Bluetooth got to do with core dumps?" I need to figure out which config option enables that functionality. Aha! It's likely `CONFIG_BT`.

However, the real core of this seems to be the introduction of `SO_PASSPIDFD_THREAD` and `SO_PEERPIDFD_THREAD`. These are mainly associated with UNIX sockets (`UNIX`), alongside the core dump functionality.

Now, let me investigate the Bluetooth side of things a bit more. I'm checking the files in `net/bluetooth/` like `af_bluetooth.c`, `hci_sock.c`, and `l2cap_sock.c`. It looks like the change there is updating `sk->sk_peer_pid` to `sk->sk_peer_pid[PIDTYPE_TGID]`, which is likely about thread-level PID support. This seems like a consistent theme.

My understanding is that the new thread pidfd support is specifically tied to `AF_UNIX` sockets, which are using those new socket options I mentioned before, and also is tied to coredump functionality through `coredump_sock_connect` and `pidfs_coredump`.

Therefore, I need to focus on confirming that `UNIX` is enabled, and then I need to confirm `COREDUMP` is enabled. My next step will be to verify the dependencies for `UNIX`. I need to ensure that the core UNIX functionality, as well as the coredump feature, are all properly enabled.



18/2 2026/10/05 15:15 tool grepper
0m Args:
{
  "Expression": "config UNIX",
  "PathPrefix": "net/"
}

Results:
{
  "Output": "net/unix/Kconfig-5-\nnet/unix/Kconfig:6:config UNIX\nnet/unix/Kconfig-7-\tbool \"Unix domain sockets\"\n--\nnet/unix/Kconfig=19=config\tAF_UNIX_OOB\n--\nnet/unix/Kconfig-25-\nnet/unix/Kconfig:26:config UNIX_DIAG\nnet/unix/Kconfig-27-\ttristate \"UNIX: socket monitoring interface\"\n"
}

19/2 2026/10/05 15:15 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 37752
							output: 26
							thoughts: 54
						

Thoughts:
Okay, here's my take on those thoughts, framed as my own:

**Analyzing Kernel Configuration Options**

Right, let me see... I'm thinking about kernel configuration options here. `CONFIG_UNIX`, that's a straightforward boolean, the go-to for enabling Unix domain sockets. Pretty fundamental stuff. And then there's `CONFIG_COREDUMP`, another standard option, again a boolean. That dictates whether we're dumping core files. Now, I'm trying to place these in context... where are they used together? Let me quickly scan `fs/Kconfig.binfmt`. That sounds like the right place to check for the interaction between these two options. Let's see if there's any conditional logic there, or perhaps any dependencies. It's often where the core dump functionality is integrated with various binary formats.



20/2 2026/10/05 15:16 tool grepper
0m Args:
{
  "Expression": "config COREDUMP",
  "PathPrefix": "fs/"
}

Results:
{
  "Output": "fs/Kconfig.binfmt=171=config BINFMT_MISC_BPF\n--\nfs/Kconfig.binfmt-184-\nfs/Kconfig.binfmt:185:config COREDUMP\nfs/Kconfig.binfmt-186-\tbool \"Enable core dump support\" if EXPERT\n"
}

21/2 2026/10/05 15:16 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 38102
							output: 28
							thoughts: 66
						

Thoughts:
Okay, let's see. First, I need to verify `CONFIG_BT`. Got it, confirmed. Now, to make sure everything's covered, I should quickly check if the `net/bluetooth` subsystem actually requires `BT`. Ah, yes, it does. So, that means I need to add `BT`, `UNIX`, and `COREDUMP` to the configuration, as those are the areas that have been modified. I'll also double-check the `net/bluetooth/Kconfig` file, just to be absolutely certain I haven't missed any direct or indirect dependencies for `BT`. That'll confirm everything and give me a good picture of the overall requirements.



22/2 2026/10/05 15:16 tool grepper
0m Args:
{
  "Expression": "config BT\\b",
  "PathPrefix": "net/bluetooth/"
}

Results:
{
  "Output": "net/bluetooth/Kconfig-5-\nnet/bluetooth/Kconfig:6:menuconfig BT\nnet/bluetooth/Kconfig-7-\ttristate \"Bluetooth subsystem support\"\n"
}

23/2 2026/10/05 15:16 llm patch-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 38396
							output: 266
							thoughts: 1751
						

Thoughts:
**Analysis of Potential Fuzzing Targets in the Kernel Networking Stack**

Okay, let's break this down. The initial focus was on Bluetooth (`BT`), but the investigation quickly branched out to explore other aspects of socket operations, specifically those involving peer process identification and credential passing, particularly with AF_UNIX sockets. My thought process is driven by the desire to identify potentially exploitable code paths that are accessible through standard userspace syscalls.

I started by checking the core socket options functions, specifically `sk_getsockopt_peerpidfd` within `net/core/sock.c`, verifying its availability through `getsockopt` calls. This led me to examine how `SO_PEERPIDFD` and `SO_PEERPIDFD_THREAD` are handled, particularly in relation to AF_UNIX sockets. The related `sk_setsockopt` and `sk_getsockopt` calls seemed interesting. I then followed the control flow into `scm_pidfd_recv` and `scm_recv_unix`, exploring how process IDs and file descriptors are passed via socket control messages, focusing on the handling of `sk_scm_pidfd_thread` and its associated data structures.

The analysis expanded to functions like `__pidfs_register_pids`, `pidfs_coredump`, and `coredump_sock_connect` within `fs/pidfs.c` and `fs/coredump.c` respectively. I paid attention to any `WARN_ON_ONCE` statements, particularly the comparison of peer PIDs in `coredump_sock_connect`. These warnings are often a sign of potentially tricky edge cases.

I then honed in on the AF_UNIX stream read path, specifically `unix_stream_read_generic` within `net/unix/af_unix.c`. This appeared to be the core of the AF_UNIX stream receive loop, and, given its ubiquity, seemed like a crucial point to investigate. The control flow around it is modified with `sk_scm_pidfd_thread` and `unix_skb_scm_eq` (which are also in `af_unix.c`). I also investigated `unix_maybe_add_creds` which seems closely related to the function's functionality.

I confirmed that key functions like `unix_skb_scm_eq`, `scm_pidfd_recv`, `scm_recv_unix`, `__pidfs_register_pids`, `pidfs_coredump`, and `sk_getsockopt_peerpidfd` are *not* marked as `inline` or `static`, which implies these functions are exposed and accessible to the kernel. This is important for determining fuzzing accessibility.

Based on the analysis, I've identified the following functions as promising fuzzing targets:

*   `sk_getsockopt_peerpidfd`
*   `scm_pidfd_recv`
*   `scm_recv_unix`
*   `unix_skb_scm_eq`
*   `unix_maybe_add_creds`
*   `__pidfs_register_pids`
*   `pidfs_coredump`
*   `coredump_sock_connect`

I've also verified that these code paths are reachable through standard socket syscalls, that the identified configs ("UNIX", "COREDUMP", "BT") are without the `CONFIG_` prefix, and that there are no other significant changes related to trace events, selftests, or headers which will impact the analysis. The analysis is comprehensive, the code is accessible, the setup is well-defined, and the configurations are correctly specified. Thus, a fuzzing campaign is fully justified.



24/2 2026/10/05 15:16 tool set-results
0m Args:
{
  "EnableConfigs": [
    "UNIX",
    "COREDUMP",
    "BT"
  ],
  "FocusSymbols": [
    "sk_getsockopt_peerpidfd",
    "scm_pidfd_recv",
    "scm_recv_unix",
    "unix_skb_scm_eq",
    "unix_maybe_add_creds",
    "__pidfs_register_pids",
    "pidfs_coredump",
    "coredump_sock_connect"
  ],
  "Reasoning": "The patch introduces SO_PASSPIDFD_THREAD and SO_PEERPIDFD_THREAD socket options along with SCM_PIDFD_THREAD control messages, enabling AF_UNIX sockets to pass and query thread-level pidfds. It restructures struct sock and SCM credentials to track arrays of struct pid up to PIDTYPE_TGID, updates stream message coalescing logic to split messages across different threads when thread pidfds are requested, adds new pidfs registration helpers with WARN_ON_ONCE checks, and modifies coredump socket connection verification. These changes touch reachable core socket, AF_UNIX, pidfs, and coredump code paths that warrant fuzzing.",
  "WorthFuzzing": true
}

Results:
{
  "EnableConfigs": [
    "UNIX",
    "COREDUMP",
    "BT"
  ],
  "FocusSymbols": [
    "sk_getsockopt_peerpidfd",
    "scm_pidfd_recv",
    "scm_recv_unix",
    "unix_skb_scm_eq",
    "unix_maybe_add_creds",
    "__pidfs_register_pids",
    "pidfs_coredump",
    "coredump_sock_connect"
  ],
  "Reasoning": "The patch introduces SO_PASSPIDFD_THREAD and SO_PEERPIDFD_THREAD socket options along with SCM_PIDFD_THREAD control messages, enabling AF_UNIX sockets to pass and query thread-level pidfds. It restructures struct sock and SCM credentials to track arrays of struct pid up to PIDTYPE_TGID, updates stream message coalescing logic to split messages across different threads when thread pidfds are requested, adds new pidfs registration helpers with WARN_ON_ONCE checks, and modifies coredump socket connection verification. These changes touch reachable core socket, AF_UNIX, pidfs, and coredump code paths that warrant fuzzing.",
  "WorthFuzzing": true
}

25/1 2026/10/05 15:16 action If
8m Args:
{
  "WorthFuzzing": true
}

Results:
null

26/2 2026/10/05 15:16 agent kmsan-evaluator
8m Model:
core

Args:
null

Results:
{
  "KMSANReasoning": "The patch series extends PIDFD passing across AF_UNIX sockets and coredump logic to support thread-level pidfds (SO_PASSPIDFD_THREAD, SO_PEERPIDFD_THREAD, SCM_PIDFD_THREAD) using a new DECLARE_PIDS macro and associated helper functions.\n\nAnalysis of KMSAN vs KASAN applicability:\n1. Kernel data copied to user space:\n   - In `sk_getsockopt_peerpidfd`, the pidfd integer and length are scalars fully initialized prior to `copy_to_sockptr`.\n   - In `sk_getsockopt`, the union `v` is zero-initialized via `memset(\u0026v, 0, sizeof(v))` before writing `v.val = sk-\u003esk_scm_pidfd_thread`.\n   - In `scm_pidfd_recv`, `put_cmsg` writes a single scalar `int pidfd` prepared by `pidfd_prepare`.\n   - There are no uninitialized struct paddings or data buffers exposed to userspace.\n\n2. Structure allocation and initialization:\n   - `struct unix_peercred` instances are zero-initialized via `= {}`.\n   - `struct scm_cookie` instances are zero-initialized via `memset` or `= {}` in all code paths (`scm_send`, `unix_stream_read_generic`, `unix_dgram_recvmsg`, `unix_destruct_scm`).\n   - `struct unix_skb_parms` resides in `skb-\u003ecb`, which is zeroed on skb allocation.\n   - `sk-\u003esk_peer_pid` is explicitly zeroed via `memset` in `sock_init_data_uid` and zero-allocated in `sk_alloc`.\n   - `struct coredump_params` is zero-initialized at declaration in `vfs_coredump`.\n\n3. Branching and control flow:\n   - All helper macros (`get_pids`, `put_pids`, `swap_pids`, `pids_equal`, `task_pids`) operate on bounds bounded strictly by compile-time checks (`pids_last()`), and no branches depend on uninitialized values.\n\nAny risks introduced by this patch primarily concern struct pid refcounting, error unwinding, or socket state, which are detected by KASAN, refcount_t sanity checks, and kmemleak. A dedicated KMSAN fuzzing session is not warranted.",
  "NeedsKMSAN": false
}

Instruction:
You are an expert Linux kernel security engineer specializing in kernel memory
error detectors (KASAN and KMSAN). Your job is to review the provided patch series and
determine if the code changes justify spawning a dedicated KMSAN (KernelMemorySanitizer)
fuzzing session in addition to standard KASAN fuzzing.

CRITICAL DISTINCTION BETWEEN KASAN AND KMSAN:
- Standard KASAN kernel builds (upstream-apparmor-kasan.config) already enable
  a comprehensive suite of debugging tools and sanitizers, including KASAN
  (out-of-bounds accesses, use-after-free, double free, invalid free), LOCKDEP
  (locking bugs and deadlocks), UB-sanitizers, and memory corruption checks.
- KMSAN (KernelMemorySanitizer) detects reads of UNINITIALIZED memory (stack, heap,
  or page allocations) and kernel-to-user memory info-leaks.

Rule: THERE IS NO SENSE IN RUNNING A KMSAN SESSION IF A BUG CAN BE CAUGHT BY KASAN,
LOCKDEP, OR OTHER STANDARD BUG DETECTORS.
A dedicated KMSAN fuzzing session incurs significant resource costs. You must ONLY
set NeedsKMSAN=true if the code changes introduce or expose UNINITIALIZED MEMORY risks
that are detected ONLY by KMSAN.

Look holistically at the patch series and surrounding code. Even if no direct
uninitialized field accesses or new buffer allocations are added in the diff itself,
a patch may alter control flow, bounds checking, or data length calculations in ways
that change how the rest of the code operates on existing buffers (e.g. allowing
uninitialized stack/heap memory to be read, copied to user space, or used in control
flow). Do not hesitate to use your code access tools to inspect the surrounding code,
called functions, and callers.

Set NeedsKMSAN=true ONLY IF the patch introduces or modifies:
1. Kernel structures sent to user space (via copy_to_user, put_user, netlink skb
   attributes, ioctl output arguments, socket options, or BPF buffers) where fields
   or structure padding might not be fully initialized/zeroed.
2. Conditional logic or branching that depends on potentially uninitialized variables
   or struct fields.
3. Allocation or initialization of complex data structures where uninitialized fields
   could be read later in reachable code paths.
4. Bounds checks, lengths, or logic in a way that allows surrounding code to access
   uninitialized bytes of existing buffers.

Set NeedsKMSAN=false IF:
- The code changes primarily risk out-of-bounds access, array overflows, NULL pointer
  dereferences, locking deadlocks, or use-after-free bugs (these are already caught
  by KASAN, LOCKDEP, or standard bug detectors).
- All stack/heap structures touched or introduced by the patch are fully zeroed
  or initialized (e.g. using = {0}, memset, kzalloc) before being read or copied.
- The patch does not introduce any risk of uninitialized memory usage or info-leaks.

Use your code access tools to inspect the surrounding code if necessary, then provide
detailed KMSANReasoning contrasting KASAN vs KMSAN applicability for this patch.
Prefer calling several tools at the same time to save round-trips.


Use set-results tool to provide results of the analysis.
It must be called exactly once before the final reply.
Ignore results of this tool.

Prompt:
Target architecture: amd64

For your convenience, here is the diff of the changes:
commit 1c4055d34a8b4ed36cbd7c628843b23623959855
Author: syz-cluster <triage@syzkaller.com>
Date:   Mon Oct 5 15:13:48 2026 +0000

    syz-cluster: applied patch under review

diff --git a/arch/alpha/include/uapi/asm/socket.h b/arch/alpha/include/uapi/asm/socket.h
index 946a5fad26912..5d3524c26b2bc 100644
--- a/arch/alpha/include/uapi/asm/socket.h
+++ b/arch/alpha/include/uapi/asm/socket.h
@@ -157,6 +157,10 @@
 
 #define SO_RIGHTS_NOTRUNC      85
 
+#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 f1641dde135fc..245a43f52fb1a 100644
--- a/arch/mips/include/uapi/asm/socket.h
+++ b/arch/mips/include/uapi/asm/socket.h
@@ -168,6 +168,10 @@
 
 #define SO_RIGHTS_NOTRUNC      85
 
+#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 f3a3815c7dc29..f23710e1c6714 100644
--- a/arch/parisc/include/uapi/asm/socket.h
+++ b/arch/parisc/include/uapi/asm/socket.h
@@ -149,6 +149,10 @@
 
 #define SO_RIGHTS_NOTRUNC	0x4053
 
+#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 7907f3b1f0ee0..b35b25bdefc2b 100644
--- a/arch/sparc/include/uapi/asm/socket.h
+++ b/arch/sparc/include/uapi/asm/socket.h
@@ -150,6 +150,10 @@
 
 #define SO_RIGHTS_NOTRUNC        0x005e
 
+#define SO_PASSPIDFD_THREAD      0x005f
+
+#define SO_PEERPIDFD_THREAD      0x0060
+
 #if !defined(__KERNEL__)
 
 
diff --git a/fs/coredump.c b/fs/coredump.c
index 6114839f5178b..870ad720b2152 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 != 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 a6a643f15d08a..29299b2c7ca71 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;
@@ -1070,6 +1077,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/coredump.h b/include/linux/coredump.h
index 7b38ee2e7913b..0bbb7de6a4021 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;
diff --git a/include/linux/pid.h b/include/linux/pid.h
index ddaef0bbc8ba3..87635d0cc1f71 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 c2aee1d91dcfd..3302690a2a281 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 0abf7da9ab236..3c1e82f1a3691 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 d45a5476b97de..3149cdb976c60 100644
--- a/include/linux/sched/signal.h
+++ b/include/linux/sched/signal.h
@@ -674,6 +674,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/include/linux/socket.h b/include/linux/socket.h
index 5a5eb12501032..b17fdb7d38a77 100644
--- a/include/linux/socket.h
+++ b/include/linux/socket.h
@@ -189,10 +189,11 @@ static inline size_t msg_data_left(const struct msghdr *msg)
 
 /* "Socket"-level control message types: */
 
-#define	SCM_RIGHTS	0x01		/* rw: access rights (array of int) */
-#define SCM_CREDENTIALS 0x02		/* rw: struct ucred		*/
-#define SCM_SECURITY	0x03		/* rw: security label		*/
-#define SCM_PIDFD	0x04		/* ro: pidfd (int)		*/
+#define	SCM_RIGHTS		0x01	/* rw: access rights (array of int) */
+#define SCM_CREDENTIALS		0x02	/* rw: struct ucred		*/
+#define SCM_SECURITY		0x03	/* rw: security label		*/
+#define SCM_PIDFD		0x04	/* ro: pidfd (int)		*/
+#define SCM_PIDFD_THREAD	0x05	/* ro: thread pidfd (int)	*/
 
 struct ucred {
 	__u32	pid;
diff --git a/include/net/scm.h b/include/net/scm.h
index 86ae6bc109ec6..b68a143f42de5 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,6 @@ 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->creds.pid = pid_vnr(pid);
 	scm->creds.uid = uid;
 	scm->creds.gid = gid;
@@ -77,8 +76,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)
@@ -94,8 +92,10 @@ static __inline__ int scm_send(struct socket *sock, struct msghdr *msg,
 	memset(scm, 0, sizeof(*scm));
 	scm->creds.uid = INVALID_UID;
 	scm->creds.gid = INVALID_GID;
-	if (forcecreds)
+	if (forcecreds) {
+		scm->pid[PIDTYPE_TGID] = get_pid(task_tgid(current));
 		scm_set_cred(scm, task_tgid(current), current_uid(), current_gid());
+	}
 	unix_get_peersec_dgram(sock, scm);
 	if (msg->msg_controllen <= 0)
 		return 0;
diff --git a/include/net/sock.h b/include/net/sock.h
index 60ea55dc18854..77dfb170d3c89 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
@@ -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.
@@ -545,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;
@@ -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;
diff --git a/include/trace/events/landlock.h b/include/trace/events/landlock.h
index 3a43638c9bc29..9e172ea22d95c 100644
--- a/include/trace/events/landlock.h
+++ b/include/trace/events/landlock.h
@@ -1037,7 +1037,7 @@ TRACE_EVENT(landlock_deny_scope_abstract_unix_socket,
 		 * updates.  The peer socket keeps a reference to sk_peer_pid
 		 * through pid_nr(); 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/include/uapi/asm-generic/socket.h b/include/uapi/asm-generic/socket.h
index 84ea7b92936e2..56fed7ab27ab8 100644
--- a/include/uapi/asm-generic/socket.h
+++ b/include/uapi/asm-generic/socket.h
@@ -152,6 +152,10 @@
 
 #define SO_RIGHTS_NOTRUNC	85
 
+#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/bluetooth/af_bluetooth.c b/net/bluetooth/af_bluetooth.c
index 411d66f24393d..7758e9ea3848c 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 6d56c77741e19..4c40068ba5fb2 100644
--- a/net/bluetooth/hci_sock.c
+++ b/net/bluetooth/hci_sock.c
@@ -284,21 +284,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 1194c37e466f1..872d8fb31b6fb 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/scm.c b/net/core/scm.c
index f0d44ecdb11fc..00025579d53e0 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);
@@ -486,11 +499,16 @@ 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 msghdr *msg, struct scm_cookie *scm,
+			   enum pid_type type, int cmsg_type)
 {
+	unsigned int flags = PIDFD_STALE;
 	struct file *pidfd_file = NULL;
+	struct pid *pid;
 	int len, pidfd;
 
+	pid = scm->pid[type];
+
 	/* put_cmsg() doesn't return an error if CMSG is truncated,
 	 * that's why we need to opencode these checks here.
 	 */
@@ -504,12 +522,15 @@ static void scm_pidfd_recv(struct msghdr *msg, struct scm_cookie *scm)
 		return;
 	}
 
-	if (!scm->pid)
+	if (!pid)
 		return;
 
-	pidfd = pidfd_prepare(scm->pid, PIDFD_STALE, &pidfd_file);
+	if (type == PIDTYPE_PID)
+		flags |= PIDFD_THREAD;
 
-	if (put_cmsg(msg, SOL_SOCKET, SCM_PIDFD, sizeof(int), &pidfd)) {
+	pidfd = pidfd_prepare(pid, flags, &pidfd_file);
+
+	if (put_cmsg(msg, SOL_SOCKET, cmsg_type, sizeof(int), &pidfd)) {
 		if (pidfd_file) {
 			put_unused_fd(pidfd);
 			fput(pidfd_file);
@@ -527,7 +548,7 @@ static bool __scm_recv_common(struct sock *sk, struct msghdr *msg,
 {
 	if (!msg->msg_control) {
 		if (sk->sk_scm_credentials || sk->sk_scm_pidfd ||
-		    scm->fp || scm_has_secdata(sk))
+		    sk->sk_scm_pidfd_thread || scm->fp || scm_has_secdata(sk))
 			msg->msg_flags |= MSG_CTRUNC;
 
 		scm_destroy(scm);
@@ -574,7 +595,10 @@ void scm_recv_unix(struct socket *sock, struct msghdr *msg,
 	}
 
 	if (sock->sk->sk_scm_pidfd)
-		scm_pidfd_recv(msg, scm);
+		scm_pidfd_recv(msg, scm, PIDTYPE_TGID, SCM_PIDFD);
+
+	if (sock->sk->sk_scm_pidfd_thread)
+		scm_pidfd_recv(msg, scm, PIDTYPE_PID, SCM_PIDFD_THREAD);
 
 	scm_destroy_cred(scm);
 }
diff --git a/net/core/sock.c b/net/core/sock.c
index e8551df8330ff..3c25d66ba31af 100644
--- a/net/core/sock.c
+++ b/net/core/sock.c
@@ -1578,6 +1578,13 @@ int sk_setsockopt(struct sock *sk, int level, int optname,
 			ret = -EOPNOTSUPP;
 		break;
 
+	case SO_PASSPIDFD_THREAD:
+		if (sk_is_unix(sk))
+			sk->sk_scm_pidfd_thread = valbool;
+		else
+			ret = -EOPNOTSUPP;
+		break;
+
 	case SO_PASSRIGHTS:
 		if (sk_is_unix(sk))
 			sk->sk_scm_rights = valbool;
@@ -1729,6 +1736,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 is prepared for this.
+	 */
+	if (sk_is_unix(sk))
+		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)
 {
@@ -1893,6 +1944,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;
@@ -1907,7 +1965,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))
@@ -1916,45 +1975,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);
-		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:
 	{
@@ -2380,7 +2408,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);
@@ -3785,7 +3813,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 42cffeafc8c14..a78e8ef3e0b0e 100644
--- a/net/unix/af_unix.c
+++ b/net/unix/af_unix.c
@@ -737,57 +737,53 @@ 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;
 };
 
 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 = 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, 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 = peercred->peer_pid;
+	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;
+	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 = old_pid;
 	peercred->peer_cred = old_cred;
 }
 
@@ -796,14 +792,15 @@ 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);
+	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);
 }
 
 static bool unix_may_passcred(const struct sock *sk)
 {
-	return sk->sk_scm_credentials || sk->sk_scm_pidfd;
+	return sk->sk_scm_credentials || sk->sk_scm_pidfd ||
+		sk->sk_scm_pidfd_thread;
 }
 
 static int unix_listen(struct socket *sock, int backlog)
@@ -1060,6 +1057,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;
@@ -1973,7 +1971,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 +1989,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 +2003,8 @@ 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_set_cred(scm, UNIXCB(skb).pid[PIDTYPE_TGID], UNIXCB(skb).uid, UNIXCB(skb).gid);
 	unix_set_secdata(scm, skb);
 }
 
@@ -2025,30 +2024,35 @@ 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);
 	}
 
 	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)
 {
-	return UNIXCB(skb).pid == scm->pid &&
+	/* 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) &&
 	       unix_secdata_eq(scm, skb);
@@ -3030,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 */
diff --git a/net/unix/af_unix.h b/net/unix/af_unix.h
index 8119dbeef3a3c..402742895acca 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		*/
diff --git a/tools/lib/python/kdoc/xforms_lists.py b/tools/lib/python/kdoc/xforms_lists.py
index e3dda2fe8a537..e792785a15ec6 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"),
diff --git a/tools/testing/selftests/coredump/coredump_socket_test.c b/tools/testing/selftests/coredump/coredump_socket_test.c
index 422728f632ca6..ec73bb690bbcb 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 ed47f01fa53c5..4212656e31f01 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 2a20faf9cb0ad..36306069f62e5 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;
diff --git a/tools/testing/selftests/net/af_unix/Makefile b/tools/testing/selftests/net/af_unix/Makefile
index a66f10fb0c231..45b841758f1bf 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 2c18b92a26035..cbe53fab44fc5 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,18 @@
 #define SCM_PIDFD 0x04
 #endif
 
+#ifndef SCM_PIDFD_THREAD
+#define SCM_PIDFD_THREAD 0x05
+#endif
+
+#ifndef SO_PASSPIDFD_THREAD
+#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()
@@ -553,4 +566,593 @@ TEST_F(scm_pidfd, test)
 	close(pfd);
 }
 
+struct thread_ids {
+	pid_t pid;
+	pid_t tid;
+};
+
+#define MAX_WRITERS 2
+
+/* Used by writers to signal they've written, so we can sequence multiple writers */
+static int seq_pipe[2];
+
+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;
+
+	/* let the client start the next writer, so the queue order is known */
+	if (write(seq_pipe[1], "1", 1) != 1)
+		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;
+
+	/* signal we've written our info */
+	if (write(seq_pipe[1], "1", 1) != 1)
+		return (void *)1;
+
+	/* stay alive until the receiver has looked at our pidfd */
+	if (read(fd, &sync, 1) != 1)
+		return (void *)1;
+
+	return NULL;
+}
+
+/*
+ * Runs @nwriters copies of @sender, each in its own non-leader thread, one
+ * at a time so the queue order is known, then tells the parent on @ackfd
+ * that everything is queued.
+ */
+static void thread_client(int fd, int syncfd, int ackfd,
+			  void *(*sender)(void *), int nwriters)
+{
+	pthread_t writers[MAX_WRITERS];
+	void *ret;
+	char sync;
+	int i;
+
+	/* wait until the receiver enabled the options it wants */
+	if (read(syncfd, &sync, 1) != 1)
+		child_die();
+
+	for (i = 0; i < nwriters; i++) {
+		if (pthread_create(&writers[i], NULL, sender, &fd))
+			child_die();
+
+		if (read(seq_pipe[0], &sync, 1) != 1)
+			child_die();
+	}
+
+	if (write(ackfd, "1", 1) != 1)
+		child_die();
+
+	for (i = 0; i < nwriters; i++)
+		if (pthread_join(writers[i], &ret) || ret)
+			child_die();
+
+	exit(0);
+}
+
+struct pidfd_msg {
+	ssize_t len;
+	struct thread_ids ids;
+	struct pidfd_info tgid_info;
+	struct pidfd_info thread_info;
+	int tgid_flags;
+	int thread_flags;
+	bool have_tgid;
+	bool have_thread;
+};
+
+static int get_pidfd_info(int pidfd, struct pidfd_info *info)
+{
+	info->mask = PIDFD_INFO_PID;
+	if (ioctl(pidfd, PIDFD_GET_INFO, info)) {
+		log_err("ioctl(PIDFD_GET_INFO)");
+		return -1;
+	}
+
+	return 0;
+}
+
+/* One recvmsg(), reporting how many bytes came back and what the pidfd
+ * cmsgs that came with them say.
+ */
+static int read_pidfd_msg(int fd, struct pidfd_msg *out)
+{
+	char control[CMSG_SPACE(sizeof(int)) * 2] = { 0 };
+	int tgid_pidfd = -1, thread_pidfd = -1;
+	struct thread_ids buf[2] = { 0 };
+	struct msghdr msg = { 0 };
+	struct cmsghdr *cmsg;
+	struct iovec iov;
+
+	iov.iov_base = buf;
+	iov.iov_len = sizeof(buf);
+	msg.msg_iov = &iov;
+	msg.msg_iovlen = 1;
+	msg.msg_control = control;
+	msg.msg_controllen = sizeof(control);
+
+	out->len = recvmsg(fd, &msg, 0);
+	if (out->len < 0) {
+		log_err("recvmsg");
+		return -1;
+	}
+
+	if (msg.msg_flags & MSG_CTRUNC) {
+		log_err("recvmsg: control truncated");
+		return -1;
+	}
+
+	out->ids = buf[0];
+
+	for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL;
+	     cmsg = CMSG_NXTHDR(&msg, cmsg)) {
+		if (cmsg->cmsg_level != SOL_SOCKET)
+			continue;
+
+		if (cmsg->cmsg_type == SCM_PIDFD)
+			memcpy(&tgid_pidfd, CMSG_DATA(cmsg), sizeof(tgid_pidfd));
+		else if (cmsg->cmsg_type == SCM_PIDFD_THREAD)
+			memcpy(&thread_pidfd, CMSG_DATA(cmsg), sizeof(thread_pidfd));
+	}
+
+	out->have_tgid = tgid_pidfd >= 0;
+	out->have_thread = thread_pidfd >= 0;
+
+	if (out->have_tgid) {
+		if (get_pidfd_info(tgid_pidfd, &out->tgid_info))
+			return -1;
+		out->tgid_flags = fcntl(tgid_pidfd, F_GETFL);
+		close(tgid_pidfd);
+	}
+
+	if (out->have_thread) {
+		if (get_pidfd_info(thread_pidfd, &out->thread_info))
+			return -1;
+		out->thread_flags = fcntl(thread_pidfd, F_GETFL);
+		close(thread_pidfd);
+	}
+
+	return 0;
+}
+
+/*
+ * Runs @nwriters threads of one child process against a SOCK_STREAM pair,
+ * all of them queued before the parent reads. SO_PASSPIDFD is set when
+ * @want_tgid is given, SO_PASSPIDFD_THREAD when @want_thread is. @nread
+ * reads are returned in @out.
+ */
+static int pidfd_flow(void *(*sender)(void *), int nwriters, bool want_tgid,
+		      bool want_thread, struct pidfd_msg *out, int nread)
+{
+	int child_status = 0;
+	int syncpipe[2];
+	int ackpipe[2];
+	int sk[2];
+	int on = 1;
+	char sync;
+	pid_t child;
+	int i;
+
+	if (nwriters > MAX_WRITERS)
+		return -1;
+
+	if (socketpair(AF_UNIX, SOCK_STREAM, 0, sk))
+		return -1;
+	if (pipe(syncpipe) || pipe(ackpipe) || pipe(seq_pipe))
+		return -1;
+
+	child = fork();
+	if (child < 0)
+		return -1;
+
+	if (child == 0) {
+		close(sk[0]);
+		close(syncpipe[1]);
+		close(ackpipe[0]);
+		thread_client(sk[1], syncpipe[0], ackpipe[1], sender, nwriters);
+	}
+	close(sk[1]);
+	close(syncpipe[0]);
+	close(ackpipe[1]);
+	close(seq_pipe[0]);
+	close(seq_pipe[1]);
+
+	if (want_tgid &&
+	    setsockopt(sk[0], SOL_SOCKET, SO_PASSPIDFD, &on, sizeof(on))) {
+		log_err("Failed to set SO_PASSPIDFD");
+		return -1;
+	}
+
+	if (want_thread &&
+	    setsockopt(sk[0], SOL_SOCKET, SO_PASSPIDFD_THREAD, &on, sizeof(on))) {
+		log_err("Failed to set SO_PASSPIDFD_THREAD");
+		return -1;
+	}
+
+	/* let the child know the options are set, it can write now */
+	if (write(syncpipe[1], "1", 1) != 1)
+		return -1;
+	close(syncpipe[1]);
+
+	/* wait until every writer has queued its message */
+	if (read(ackpipe[0], &sync, 1) != 1)
+		return -1;
+	close(ackpipe[0]);
+
+	for (i = 0; i < nread; i++)
+		if (read_pidfd_msg(sk[0], &out[i]))
+			return -1;
+
+	/* release the writers */
+	for (i = 0; i < nwriters; i++)
+		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));
+
+	/* Verify that the options are truly set independently */
+	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));
+
+	ASSERT_EQ(0, sockopt_set(sk[0], SO_PASSPIDFD, 1));
+	ASSERT_EQ(1, sockopt_get(sk[0], SO_PASSPIDFD));
+	ASSERT_EQ(1, sockopt_get(sk[0], SO_PASSPIDFD_THREAD));
+
+	/* Verify that the options are truly cleared independently */
+	ASSERT_EQ(0, sockopt_set(sk[0], SO_PASSPIDFD, 0));
+	ASSERT_EQ(0, sockopt_get(sk[0], SO_PASSPIDFD));
+	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_THREAD));
+	ASSERT_EQ(0, sockopt_get(sk[0], SO_PASSPIDFD));
+
+	close(sk[0]);
+	close(sk[1]);
+}
+
+/* A receiver that only asked about the process cannot tell the two
+ * threads apart, so their writes are glued into one read.
+ */
+TEST(scm_pidfd_stream_glues_threads)
+{
+	struct pidfd_msg msg[1] = { 0 };
+
+	ASSERT_EQ(0, pidfd_flow(send_ids_thread, 2, true, false, msg, 1));
+	EXPECT_EQ(sizeof(struct thread_ids) * 2, msg[0].len);
+
+	EXPECT_TRUE(msg[0].have_tgid);
+	EXPECT_FALSE(msg[0].have_thread);
+	EXPECT_EQ(msg[0].ids.pid, msg[0].tgid_info.pid);
+	EXPECT_EQ(msg[0].ids.pid, msg[0].tgid_info.tgid);
+}
+
+/* A receiver asking for both thread and process pidfd should get a unique
+ * msg for each thread writing
+ */
+TEST(scm_pidfd_thread_stream_splits_on_threads)
+{
+	struct pidfd_msg msg[2] = { 0 };
+	int i;
+
+	ASSERT_EQ(0, pidfd_flow(send_ids_thread, 2, true, true, msg, 2));
+
+	for (i = 0; i < 2; i++) {
+		EXPECT_EQ(sizeof(struct thread_ids), msg[i].len);
+		EXPECT_TRUE(msg[i].have_tgid);
+		EXPECT_TRUE(msg[i].have_thread);
+
+		/* Make sure the info makes sense for the pidfd type */
+		EXPECT_EQ(msg[i].ids.pid, msg[i].tgid_info.pid);
+		EXPECT_EQ(msg[i].ids.pid, msg[i].tgid_info.tgid);
+		EXPECT_EQ(msg[i].ids.tid, msg[i].thread_info.pid);
+		EXPECT_EQ(msg[i].ids.pid, msg[i].thread_info.tgid);
+		EXPECT_NE(msg[i].ids.pid, msg[i].ids.tid);
+	}
+
+	/* Make sure we really got unique threads per message */
+	EXPECT_NE(msg[0].ids.tid, msg[1].ids.tid);
+	EXPECT_EQ(msg[0].ids.pid, msg[1].ids.pid);
+	EXPECT_EQ(msg[0].tgid_info.pid, msg[1].tgid_info.pid);
+	EXPECT_NE(msg[0].thread_info.pid, msg[1].thread_info.pid);
+}
+
+/* Sends from the thread-group leader */
+static int leader_flow(bool want_thread, struct pidfd_msg *out)
+{
+	struct thread_ids ids = {
+		.pid = getpid(),
+		.tid = gettid(),
+	};
+	int sk[2];
+	int on = 1;
+	int ret;
+
+	if (socketpair(AF_UNIX, SOCK_STREAM, 0, sk))
+		return -1;
+
+	if (setsockopt(sk[0], SOL_SOCKET, SO_PASSPIDFD, &on, sizeof(on))) {
+		log_err("Failed to set SO_PASSPIDFD");
+		return -1;
+	}
+
+	if (want_thread &&
+	    setsockopt(sk[0], SOL_SOCKET, SO_PASSPIDFD_THREAD, &on, sizeof(on))) {
+		log_err("Failed to set SO_PASSPIDFD_THREAD");
+		return -1;
+	}
+
+	if (send(sk[1], &ids, sizeof(ids), 0) != sizeof(ids)) {
+		log_err("send");
+		return -1;
+	}
+
+	ret = read_pidfd_msg(sk[0], out);
+
+	close(sk[0]);
+	close(sk[1]);
+
+	return ret;
+}
+
+TEST(scm_pidfd_leader_sender)
+{
+	struct pidfd_msg msg = { 0 };
+
+	ASSERT_EQ(getpid(), gettid());
+
+	ASSERT_EQ(0, leader_flow(false, &msg));
+	ASSERT_TRUE(msg.have_tgid);
+	EXPECT_FALSE(msg.have_thread);
+
+	EXPECT_EQ(getpid(), msg.tgid_info.pid);
+	EXPECT_EQ(getpid(), msg.tgid_info.tgid);
+	EXPECT_FALSE(msg.tgid_flags & O_EXCL);
+}
+
+TEST(scm_pidfd_thread_leader_sender)
+{
+	struct pidfd_msg msg = { 0 };
+
+	ASSERT_EQ(getpid(), gettid());
+
+	ASSERT_EQ(0, leader_flow(true, &msg));
+	ASSERT_TRUE(msg.have_tgid);
+	ASSERT_TRUE(msg.have_thread);
+
+	EXPECT_EQ(getpid(), msg.tgid_info.pid);
+	EXPECT_EQ(getpid(), msg.tgid_info.tgid);
+	EXPECT_EQ(getpid(), msg.thread_info.pid);
+	EXPECT_EQ(getpid(), msg.thread_info.tgid);
+
+	EXPECT_FALSE(msg.tgid_flags & O_EXCL);
+	EXPECT_TRUE(msg.thread_flags & O_EXCL);
+}
+
+TEST(scm_pidfd_thread_and_group)
+{
+	struct pidfd_msg msg = { 0 };
+
+	ASSERT_EQ(0, pidfd_flow(send_ids_thread, 1, true, true, &msg, 1));
+	ASSERT_NE(msg.ids.pid, msg.ids.tid);
+	ASSERT_TRUE(msg.have_tgid);
+	ASSERT_TRUE(msg.have_thread);
+
+	EXPECT_EQ(msg.ids.pid, msg.tgid_info.pid);
+	EXPECT_EQ(msg.ids.pid, msg.tgid_info.tgid);
+
+	EXPECT_EQ(msg.ids.tid, msg.thread_info.pid);
+	EXPECT_EQ(msg.ids.pid, msg.thread_info.tgid);
+}
+
+TEST(scm_pidfd_thread)
+{
+	struct pidfd_msg msg = { 0 };
+
+	ASSERT_EQ(0, pidfd_flow(send_ids_thread, 1, false, true, &msg, 1));
+	ASSERT_NE(msg.ids.pid, msg.ids.tid);
+	ASSERT_TRUE(msg.have_thread);
+	EXPECT_FALSE(msg.have_tgid);
+	EXPECT_EQ(msg.ids.tid, msg.thread_info.pid);
+	EXPECT_EQ(msg.ids.pid, msg.thread_info.tgid);
+}
+
+TEST(scm_pidfd_thread_group)
+{
+	struct pidfd_msg msg = { 0 };
+
+	ASSERT_EQ(0, pidfd_flow(send_ids_thread, 1, true, false, &msg, 1));
+	ASSERT_NE(msg.ids.pid, msg.ids.tid);
+	ASSERT_TRUE(msg.have_tgid);
+	EXPECT_FALSE(msg.have_thread);
+	EXPECT_EQ(msg.ids.pid, msg.tgid_info.pid);
+	EXPECT_EQ(msg.ids.pid, msg.tgid_info.tgid);
+}
+
+TEST(scm_pidfd_thread_creds)
+{
+	struct pidfd_msg msg = { 0 };
+
+	ASSERT_EQ(0, pidfd_flow(send_ids_creds_thread, 1, true, true, &msg, 1));
+	ASSERT_NE(msg.ids.pid, msg.ids.tid);
+	ASSERT_TRUE(msg.have_tgid);
+	ASSERT_TRUE(msg.have_thread);
+	EXPECT_EQ(msg.ids.pid, msg.tgid_info.pid);
+	EXPECT_EQ(msg.ids.pid, msg.tgid_info.tgid);
+	EXPECT_EQ(msg.ids.tid, msg.thread_info.pid);
+	EXPECT_EQ(msg.ids.pid, msg.thread_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

27/3 2026/10/05 15:16 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 26582
							output: 32
							thoughts: 800
						

Thoughts:
**KMSAN Evaluation: Patch Series Analysis**

Alright, let's get down to brass tacks. I need to figure out if this patch series warrants its own KMSAN fuzzing session, in addition to the usual KASAN checks. The key is understanding the *critical* distinction: KASAN covers the standard stuff - out-of-bounds access, UAFs, double-frees, memory corruption, and the like. KMSAN, on the other hand, is all about detecting reads of *uninitialized* memory, whether it's on the stack, heap, or page allocations, and kernel-to-user memory info-leaks.

The guiding principle is clear: if KASAN, LOCKDEP, or any of the usual suspects would catch the bug, there's no point in burning cycles on a KMSAN-specific session. So, I need to meticulously assess this patch against the established rules. *Only* if the patch touches these areas should I even consider NeedsKMSAN=true:

1.  Does it introduce or modify kernel structures that are sent to user space (via `copy_to_user`, `put_user`, netlink, ioctl, socket options, or BPF buffers), where fields or structure padding might not be properly initialized?
2.  Does the patch introduce conditional logic or branching that relies on potentially uninitialized variables or struct fields?
3.  Does it involve the allocation or initialization of complex data structures where uninitialized fields could be read later on?
4.  Does it mess with bounds checks, lengths, or logic in a way that could lead to uninitialized bytes of existing buffers being accessed?

Conversely, NeedsKMSAN=false if:

*   The code primarily risks out-of-bounds access, array overflows, NULL pointer dereferences, locking deadlocks, or use-after-free bugs.
*   All stack/heap structures touched or introduced by the patch are fully zeroed or initialized (e.g., using `= {0}`, `memset`, or `kzalloc`) *before* being read or copied.
*   The patch doesn't introduce any risk of uninitialized memory usage or info-leaks.

Now, let's dissect the patch. It seems to be introducing new socket options: `SO_PASSPIDFD_THREAD`, `SO_PEERPIDFD_THREAD`, and `SCM_PIDFD_THREAD`. That's the entry point. I need to analyze all the modified files and functions.

First up, the architecture-specific socket headers and the generic `socket.h`. That's straightforward enough - just definitions. The interesting stuff seems to be in `fs/coredump.c`.

Here's the rundown of `fs/coredump.c`: The `cprm->pid` has changed from a single `struct pid *pid;` to `DECLARE_PIDS(pid, PIDTYPE_TGID);`, which expands to `struct pid *pid[2]`. I need to understand why this change was necessary.

Looking at the usages: `task_pids(cprm->pid, current);`, `if (cp->pid[PIDTYPE_TGID]) {`, allocation/registration of `pidfs`, and a comparison in `coredump_sock_connect`. I need to figure out how `struct coredump_params` is allocated and initialized to understand if there is a potential for uninitialized memory usage. I need to grep for `struct coredump_params` in `fs/coredump.c`. Once I determine the allocation and initialization sites, I'll be able to make a call on whether KMSAN is necessary for this patch series.



28/3 2026/10/05 15:16 tool grepper
0m Args:
{
  "Expression": "coredump_params cprm",
  "PathPrefix": "fs/coredump.c"
}

Results:
{
  "Output": "fs/coredump.c=1160=void vfs_coredump(const kernel_siginfo_t *siginfo)\n--\nfs/coredump.c-1167-\tint argc = 0;\nfs/coredump.c:1168:\tstruct coredump_params cprm = {\nfs/coredump.c-1169-\t\t.siginfo = siginfo,\n"
}

29/3 2026/10/05 15:16 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 28304
							output: 34
							thoughts: 1236
						

Thoughts:
**Analyzing the Coredump and Socket Changes**

Okay, so I'm diving into this code related to coredumps and socket options, and a few things are catching my eye. First, the `vfs_coredump` function initializes a `coredump_params` structure, and it zeros out fields like `pid` that aren't explicitly set. This sets the stage for how those `pid` values are handled downstream.

Now, let's look at `fs/pidfs.c` to see how these PID values are used. Specifically, the `pidfs_coredump` function loops through PID types to call `pidfs_coredump_pid`. I'm curious about the relationship between these PID types. Looking at `pidfs_register_pids` and `pidfs_register_pid`, I'm asking myself what happens if a NULL PID is passed into the `pid` array. Is this handled?

Okay, now let's go back and look at the `umh_coredump_setup` function, which checks `cp->pid[PIDTYPE_TGID]`. Then, in `coredump_parse`, the code calls `task_pids` to fill the PID array. The `task_pids` function uses `task_pid_type`, which should return a valid PID for the current task's PID and TGID.

Next, I'm shifting focus to the socket and AF_UNIX changes, particularly around `getsockopt` and SCMs. Specifically, I'm examining `net/core/sock.c` and `sk_getsockopt_peerpidfd`. It looks like the code attempts to create a PIDFD for the socket's peer using `pidfd_prepare` but I'm thinking about the length check: `if (len > sizeof(pidfd))` and what would happen if that length `len` is smaller than the PIDFD. For example, if len is 1, 2, or 0, or even negative.

I also wonder if there are any changes to how the `len` variable is initialized in the function. Before I look at `sk_getsockopt`, it had `int len` in its definition. Let me check the full `sk_getsockopt` code in `net/core/sock.c` to see how it initializes `len`.



30/3 2026/10/05 15:16 tool grepper
0m Args:
{
  "Expression": "int sk_getsockopt\\(",
  "PathPrefix": "net/core/sock.c"
}

Results:
{
  "Output": "net/core/sock.c=1740=static int sk_getsockopt_peerpidfd(struct sock *sk, enum pid_type type,\n--\nnet/core/sock.c-1782-\nnet/core/sock.c:1783:int sk_getsockopt(struct sock *sk, int level, int optname,\nnet/core/sock.c-1784-\t\t  sockptr_t optval, sockptr_t optlen)\n"
}

31/3 2026/10/05 15:16 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 30208
							output: 36
							thoughts: 25
						

Thoughts:
Okay, here's my interpretation of what's going through my mind, rendered in first person, focusing on the core meaning:

**Analyzing Socket Closure in net/core/sock.c**

Alright, time to dive into `net/core/sock.c`. I need to understand the socket closure process, specifically from lines 1783 to 1815. This usually indicates a critical section dealing with the underlying mechanisms of releasing a socket. I'm hoping to get a handle on how resources are freed, any potential race conditions that are handled, and how different states are transitioned during a socket's lifecycle as it is closed. Given the line range, it is likely that the code concerns the socket structure itself, and its interaction with other associated kernel data structures. My primary goal here is to determine the sequence of actions that occur when a socket transitions from an active state to a closed one, looking for areas where efficiency or error handling might be improved, or where subtle issues could emerge under load. I'm going to look for mutexes, atomic operations, and any other synchronization primitives in use. I expect to be scanning for potential deadlocks or missed edge cases that might lead to resource leaks or unexpected socket behavior.



32/3 2026/10/05 15:16 tool read-file
0m Args:
{
  "File": "net/core/sock.c",
  "FirstLine": 1783,
  "LineCount": 40
}

Results:
{
  "Contents": "1783:\tint sk_getsockopt(struct sock *sk, int level, int optname,\n1784:\t\t\t  sockptr_t optval, sockptr_t optlen)\n1785:\t{\n1786:\t\tstruct socket *sock = sk-\u003esk_socket;\n1787:\t\n1788:\t\tunion {\n1789:\t\t\tint val;\n1790:\t\t\tu64 val64;\n1791:\t\t\tunsigned long ulval;\n1792:\t\t\tstruct linger ling;\n1793:\t\t\tstruct old_timeval32 tm32;\n1794:\t\t\tstruct __kernel_old_timeval tm;\n1795:\t\t\tstruct  __kernel_sock_timeval stm;\n1796:\t\t\tstruct sock_txtime txtime;\n1797:\t\t\tstruct so_timestamping timestamping;\n1798:\t\t} v;\n1799:\t\n1800:\t\tint lv = sizeof(int);\n1801:\t\tint len;\n1802:\t\n1803:\t\tif (copy_from_sockptr(\u0026len, optlen, sizeof(int)))\n1804:\t\t\treturn -EFAULT;\n1805:\t\tif (len \u003c 0)\n1806:\t\t\treturn -EINVAL;\n1807:\t\n1808:\t\tmemset(\u0026v, 0, sizeof(v));\n1809:\t\n1810:\t\tswitch (optname) {\n1811:\t\tcase SO_DEBUG:\n1812:\t\t\tv.val = sock_flag(sk, SOCK_DBG);\n1813:\t\t\tbreak;\n1814:\t\n1815:\t\tcase SO_DONTROUTE:\n1816:\t\t\tv.val = sock_flag(sk, SOCK_LOCALROUTE);\n1817:\t\t\tbreak;\n1818:\t\n1819:\t\tcase SO_BROADCAST:\n1820:\t\t\tv.val = sock_flag(sk, SOCK_BROADCAST);\n1821:\t\t\tbreak;\n1822:\t\n"
}

33/3 2026/10/05 15:16 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 31068
							output: 35
							thoughts: 331
						

Thoughts:
**Analyzing the `SO_PEERPIDFD` and Related Socket Options**

Alright, let's break this down. First, I see that the length (`len`) passed from user space is checked for negative values, returning `-EINVAL` if it is. That's good defensive programming. The code then refactors the handling of `SO_PEERPIDFD`, moving the core logic into `sk_getsockopt_peerpidfd` and reusing it for the `SO_PEERPIDFD_THREAD` case. Makes sense, reduces code duplication.

Now, focusing on `sk_getsockopt_peerpidfd`, the flow is: an integer `pidfd` is prepared using `pidfd_prepare` which gets an int (an fd or error) returned. And that `pidfd` is being copied into user space using `copy_to_sockptr`, ensuring that the copy is limited to at most `sizeof(pidfd)` bytes. Seems standard and safe.

Moving on to `SO_PASSPIDFD_THREAD` in `sk_getsockopt`, I see a crucial check: `if (!sk_is_unix(sk)) return -EOPNOTSUPP;`. That's important to restrict the feature to the right socket domain.

Then it assigns `sk->sk_scm_pidfd_thread` to `v.val` and break. The thing is, before doing this assignment, `v` was initialized with `memset(&v, 0, sizeof(v));`, so no chance of returning uninitialized memory. Very thorough.

Finally, I need to look into `scm_pidfd_recv` in `net/core/scm.c`, specifically around lines 486-550 to see how the data is received and handled. Gotta check the other side to complete this analysis.



34/3 2026/10/05 15:17 tool read-file
0m Args:
{
  "File": "net/core/scm.c",
  "FirstLine": 495,
  "LineCount": 60
}

Results:
{
  "Contents": " 495:\t\n 496:\tstatic bool scm_has_secdata(struct sock *sk)\n 497:\t{\n 498:\t\treturn false;\n 499:\t}\n 500:\t#endif\n 501:\t\n 502:\tstatic void scm_pidfd_recv(struct msghdr *msg, struct scm_cookie *scm,\n 503:\t\t\t\t   enum pid_type type, int cmsg_type)\n 504:\t{\n 505:\t\tunsigned int flags = PIDFD_STALE;\n 506:\t\tstruct file *pidfd_file = NULL;\n 507:\t\tstruct pid *pid;\n 508:\t\tint len, pidfd;\n 509:\t\n 510:\t\tpid = scm-\u003epid[type];\n 511:\t\n 512:\t\t/* put_cmsg() doesn't return an error if CMSG is truncated,\n 513:\t\t * that's why we need to opencode these checks here.\n 514:\t\t */\n 515:\t\tif (msg-\u003emsg_flags \u0026 MSG_CMSG_COMPAT)\n 516:\t\t\tlen = sizeof(struct compat_cmsghdr) + sizeof(int);\n 517:\t\telse\n 518:\t\t\tlen = sizeof(struct cmsghdr) + sizeof(int);\n 519:\t\n 520:\t\tif (msg-\u003emsg_controllen \u003c len) {\n 521:\t\t\tmsg-\u003emsg_flags |= MSG_CTRUNC;\n 522:\t\t\treturn;\n 523:\t\t}\n 524:\t\n 525:\t\tif (!pid)\n 526:\t\t\treturn;\n 527:\t\n 528:\t\tif (type == PIDTYPE_PID)\n 529:\t\t\tflags |= PIDFD_THREAD;\n 530:\t\n 531:\t\tpidfd = pidfd_prepare(pid, flags, \u0026pidfd_file);\n 532:\t\n 533:\t\tif (put_cmsg(msg, SOL_SOCKET, cmsg_type, sizeof(int), \u0026pidfd)) {\n 534:\t\t\tif (pidfd_file) {\n 535:\t\t\t\tput_unused_fd(pidfd);\n 536:\t\t\t\tfput(pidfd_file);\n 537:\t\t\t}\n 538:\t\n 539:\t\t\treturn;\n 540:\t\t}\n 541:\t\n 542:\t\tif (pidfd_file)\n 543:\t\t\tfd_install(pidfd, pidfd_file);\n 544:\t}\n 545:\t\n 546:\tstatic bool __scm_recv_common(struct sock *sk, struct msghdr *msg,\n 547:\t\t\t\t      struct scm_cookie *scm, int flags)\n 548:\t{\n 549:\t\tif (!msg-\u003emsg_control) {\n 550:\t\t\tif (sk-\u003esk_scm_credentials || sk-\u003esk_scm_pidfd ||\n 551:\t\t\t    sk-\u003esk_scm_pidfd_thread || scm-\u003efp || scm_has_secdata(sk))\n 552:\t\t\t\tmsg-\u003emsg_flags |= MSG_CTRUNC;\n 553:\t\n 554:\t\t\tscm_destroy(scm);\n"
}

35/3 2026/10/05 15:17 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 32689
							output: 35
							thoughts: 1194
						

Thoughts:
**Analyzing `pidfd` Transmission through SCM Credentials**

Okay, so I'm looking at how `pidfd` is handled when sent via control messages. The `pidfd` is being written into the control message buffer (`cmsg`) using `put_cmsg`, using `pidfd_prepare` to initialize it. My next question is how `scm_cookie` and related functions like `scm_send` and `scm_replace_pid` factor into this. Time to dig into `include/net/scm.h` and `net/core/scm.c`.

Right, in `include/net/scm.h`, `struct scm_cookie` stores credentials and associated information. I see `DECLARE_PIDS(pid, PIDTYPE_TGID)`, which is interesting; it holds PIDs. There's also `scm_fp_list` for file pointers and `scm_creds` which stores skb credentials. The `scm_set_cred` function is straightforward; it sets the PID, UID, and GID within the `scm_cookie`. And `scm_destroy_cred` calls `put_pids` to release references.

Now, `scm_send` is where things start to get interesting. It initializes the `scm_cookie` with `memset`, which is crucial. It sets the UID and GID to invalid values.  If `forcecreds` is set, it gets the current thread group ID (`task_tgid(current)`) and sets it into `scm->pid[PIDTYPE_TGID]`. Then it calls `unix_get_peersec_dgram`. Critically, it does *not* set `scm->pid[PIDTYPE_PID]`! That means after the `memset` operation, `scm->pid[PIDTYPE_PID]` will be 0 (NULL). This raises a red flag right away. I need to know what happens if this field is NULL when we eventually receive this control message. Specifically, how does this interact with `scm_pidfd_recv`?

Okay, let me check. Inside `scm_pidfd_recv`, `pid = scm->pid[type]` is called. Then we get a conditional check that simply returns if the pid is NULL. So, the receiver will not process this `pidfd`.

Now, if `scm_replace_pid` in `net/core/scm.c` *is* called, this could change the story. Let's look at `scm_replace_pid` and `__scm_send` together.

`scm_replace_pid` is used to replace existing PIDs. First, it calls `put_pids(scm->pid)` to drop the references from the existing cookie. Then, it attempts to register the PID with `pidfs_register_pid`. A sender naming its own thread-group sends from the current thread.

It *then* calls `pidfs_register_pid(thread_pid)`. Here's the critical bit: if *that* call fails, the function returns an error. Before the `pidfs_register_pid` calls, we dropped all references, so that both `scm->pid[PIDTYPE_TGID]` and `scm->pid[PIDTYPE_PID]` are NULL.

If this fails, what happens to `pid` passed to this function?

Also, the `__scm_send` function is called after `scm_replace_pid`. Need to verify how these two functions interact at the lower levels.



36/3 2026/10/05 15:17 tool read-file
0m Args:
{
  "File": "net/core/scm.c",
  "FirstLine": 180,
  "LineCount": 55
}

Results:
{
  "Contents": " 180:\t\tconst struct proto_ops *ops = READ_ONCE(sock-\u003eops);\n 181:\t\tstruct cmsghdr *cmsg;\n 182:\t\tint err;\n 183:\t\n 184:\t\tfor_each_cmsghdr(cmsg, msg) {\n 185:\t\t\terr = -EINVAL;\n 186:\t\n 187:\t\t\t/* Verify that cmsg_len is at least sizeof(struct cmsghdr) */\n 188:\t\t\t/* The first check was omitted in \u003c= 2.2.5. The reasoning was\n 189:\t\t\t   that parser checks cmsg_len in any case, so that\n 190:\t\t\t   additional check would be work duplication.\n 191:\t\t\t   But if cmsg_level is not SOL_SOCKET, we do not check\n 192:\t\t\t   for too short ancillary data object at all! Oops.\n 193:\t\t\t   OK, let's add it...\n 194:\t\t\t */\n 195:\t\t\tif (!CMSG_OK(msg, cmsg))\n 196:\t\t\t\tgoto error;\n 197:\t\n 198:\t\t\tif (cmsg-\u003ecmsg_level != SOL_SOCKET)\n 199:\t\t\t\tcontinue;\n 200:\t\n 201:\t\t\tswitch (cmsg-\u003ecmsg_type)\n 202:\t\t\t{\n 203:\t\t\tcase SCM_RIGHTS:\n 204:\t\t\t\tif (!ops || ops-\u003efamily != PF_UNIX)\n 205:\t\t\t\t\tgoto error;\n 206:\t\t\t\terr=scm_fp_copy(cmsg, \u0026p-\u003efp);\n 207:\t\t\t\tif (err\u003c0)\n 208:\t\t\t\t\tgoto error;\n 209:\t\t\t\tbreak;\n 210:\t\t\tcase SCM_CREDENTIALS:\n 211:\t\t\t{\n 212:\t\t\t\tstruct ucred creds;\n 213:\t\t\t\tkuid_t uid;\n 214:\t\t\t\tkgid_t gid;\n 215:\t\t\t\tif (cmsg-\u003ecmsg_len != CMSG_LEN(sizeof(struct ucred)))\n 216:\t\t\t\t\tgoto error;\n 217:\t\t\t\tmemcpy(\u0026creds, CMSG_DATA(cmsg), sizeof(struct ucred));\n 218:\t\t\t\terr = scm_check_creds(\u0026creds);\n 219:\t\t\t\tif (err)\n 220:\t\t\t\t\tgoto error;\n 221:\t\n 222:\t\t\t\tif (!p-\u003epid[PIDTYPE_TGID] ||\n 223:\t\t\t\t    pid_vnr(p-\u003epid[PIDTYPE_TGID]) != creds.pid) {\n 224:\t\t\t\t\tstruct pid *pid;\n 225:\t\t\t\t\terr = -ESRCH;\n 226:\t\t\t\t\tpid = find_get_pid(creds.pid);\n 227:\t\t\t\t\tif (!pid)\n 228:\t\t\t\t\t\tgoto error;\n 229:\t\n 230:\t\t\t\t\t/* pass a struct pid reference from\n 231:\t\t\t\t\t * find_get_pid() to scm_replace_pid().\n 232:\t\t\t\t\t */\n 233:\t\t\t\t\terr = scm_replace_pid(p, pid);\n 234:\t\t\t\t\tif (err) {\n"
}

37/3 2026/10/05 15:17 llm kmsan-evaluator
1m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 35502
							output: 35
							thoughts: 14
						

Thoughts:
Okay, I'm ready to dive into that specific section. Let's get this done and make sure the response is up to snuff. I'm focusing in on lines 235-245. Let's see what that section actually says.



38/3 2026/10/05 15:18 tool read-file
0m Args:
{
  "File": "net/core/scm.c",
  "FirstLine": 230,
  "LineCount": 20
}

Results:
{
  "Contents": " 230:\t\t\t\t\t/* pass a struct pid reference from\n 231:\t\t\t\t\t * find_get_pid() to scm_replace_pid().\n 232:\t\t\t\t\t */\n 233:\t\t\t\t\terr = scm_replace_pid(p, pid);\n 234:\t\t\t\t\tif (err) {\n 235:\t\t\t\t\t\tput_pid(pid);\n 236:\t\t\t\t\t\tgoto error;\n 237:\t\t\t\t\t}\n 238:\t\t\t\t}\n 239:\t\n 240:\t\t\t\terr = -EINVAL;\n 241:\t\t\t\tuid = make_kuid(current_user_ns(), creds.uid);\n 242:\t\t\t\tgid = make_kgid(current_user_ns(), creds.gid);\n 243:\t\t\t\tif (!uid_valid(uid) || !gid_valid(gid))\n 244:\t\t\t\t\tgoto error;\n 245:\t\n 246:\t\t\t\tp-\u003ecreds.uid = uid;\n 247:\t\t\t\tp-\u003ecreds.gid = gid;\n 248:\t\t\t\tbreak;\n 249:\t\t\t}\n"
}

39/3 2026/10/05 15:18 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 35894
							output: 33
							thoughts: 311
						

Thoughts:
**Analyzing Potential Refcount Issues in `scm_replace_pid` and UNIX Socket Handling**

Okay, so if `scm_replace_pid` throws an error, we immediately call `put_pid(pid)` and jump to `error`. Good, `__scm_destroy(p)` handles the cleanup on failure. So it seems like the refcounting is managed correctly in this path, which is what I'd expect. Remember, issues with refcounting and potential memory leaks are something KASAN or kmemleak would pick up on anyway, not KMSAN.

Now, let's switch gears and investigate UNIX socket handling. I'm going to look into `net/unix/af_unix.c`. Specifically, I want to scrutinize the `prepare_peercred` function, and how it handles credentials.

Okay, here's `prepare_peercred`:
```c
struct unix_peercred {
	DECLARE_PIDS(peer_pid, PIDTYPE_TGID);
	const struct cred *peer_cred;
};

static inline int prepare_peercred(struct unix_peercred *peercred)
{
	int err;

	get_task_pids(peercred->peer_pid, current);
	err = pidfs_register_pids(peercred->peer_pid);
	if (unlikely(err)) {
		put_pids(peercred->peer_pid);
		return err;
	}

	peercred->peer_cred = get_current_cred();
	return 0;
}
```

It looks like we're using `unix_peercred` to hold peer credentials and PIDs, and that `prepare_peercred` is responsible for setting them up. It uses `get_task_pids` to get the PIDs, registers them with `pidfs`, and then gets the current credentials. It *does* seem to have a `put_pids` in the error path, so that's good.

Now, I want to see *where* this `struct unix_peercred peercred` is actually used. Time to grep the `net/unix/af_unix.c` file for `prepare_peercred` and `unix_peercred` to get a complete picture.



40/3 2026/10/05 15:18 tool grepper
0m Args:
{
  "Expression": "unix_peercred",
  "PathPrefix": "net/unix/af_unix.c"
}

Results:
{
  "Output": "net/unix/af_unix.c=665=static void unix_release_sock(struct sock *sk, int embrion)\n--\nnet/unix/af_unix.c-738-\nnet/unix/af_unix.c:739:struct unix_peercred {\nnet/unix/af_unix.c-740-\tDECLARE_PIDS(peer_pid, PIDTYPE_TGID);\n--\nnet/unix/af_unix.c-743-\nnet/unix/af_unix.c:744:static inline int prepare_peercred(struct unix_peercred *peercred)\nnet/unix/af_unix.c-745-{\n--\nnet/unix/af_unix.c-758-\nnet/unix/af_unix.c:759:static void drop_peercred(struct unix_peercred *peercred)\nnet/unix/af_unix.c-760-{\n--\nnet/unix/af_unix.c=770=static inline void init_peercred(struct sock *sk,\nnet/unix/af_unix.c:771:\t\t\t\t const struct unix_peercred *peercred)\nnet/unix/af_unix.c-772-{\n--\nnet/unix/af_unix.c-776-\nnet/unix/af_unix.c:777:static void update_peercred(struct sock *sk, struct unix_peercred *peercred)\nnet/unix/af_unix.c-778-{\n--\nnet/unix/af_unix.c=806=static int unix_listen(struct socket *sock, int backlog)\n--\nnet/unix/af_unix.c-810-\tstruct unix_sock *u = unix_sk(sk);\nnet/unix/af_unix.c:811:\tstruct unix_peercred peercred = {};\nnet/unix/af_unix.c-812-\n--\nnet/unix/af_unix.c=1632=static int unix_stream_connect(struct socket *sock, struct sockaddr_unsized *uaddr,\n--\nnet/unix/af_unix.c-1637-\tstruct unix_sock *u = unix_sk(sk), *newu, *otheru;\nnet/unix/af_unix.c:1638:\tstruct unix_peercred peercred = {};\nnet/unix/af_unix.c-1639-\tstruct net *net = sock_net(sk);\n--\nnet/unix/af_unix.c=1812=static int unix_socketpair(struct socket *socka, struct socket *sockb)\nnet/unix/af_unix.c-1813-{\nnet/unix/af_unix.c:1814:\tstruct unix_peercred ska_peercred = {}, skb_peercred = {};\nnet/unix/af_unix.c-1815-\tstruct sock *ska = socka-\u003esk, *skb = sockb-\u003esk;\n"
}

41/3 2026/10/05 15:18 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 37472
							output: 37
							thoughts: 634
						

Thoughts:
**Analyzing AF_UNIX Credential Handling**

Okay, so I'm looking at how credentials are handled in the AF_UNIX domain. The first thing that jumps out is the ubiquitous initialization of `struct unix_peercred` using `= {}`. This is good; it's clean and ensures a known starting state. We see it used in various contexts, like `struct unix_peercred peercred = {};` and `struct unix_peercred ska_peercred = {}, skb_peercred = {};`. These are always zero-initialized, which suggests a deliberate design choice for safety.

The `prepare_peercred` function then goes on to populate some of this `unix_peercred` structure. Specifically, it calls `get_task_pids` to get information about the process. Diving into `__get_task_pids` confirms my understanding: it iterates through different PID types (PID, TGID, etc.) and populates the `peer_pid` array within `unix_peercred`. Both `peercred->peer_pid[PIDTYPE_PID]` and `peercred->peer_pid[PIDTYPE_TGID]` are initialized there.

Now, my attention shifts to the `UNIXCB(skb)` macro. I remember this is used to access the control block within the `sk_buff`. Looking at the definition, `UNIXCB(skb)` gives us a `struct unix_skb_parms`. It contains the credentials information, the PIDs, the UID, GID, and even things like security context and file pointers. Interesting.

I want to know how `UNIXCB(skb)` is populated. The obvious place to start is `unix_scm_to_skb`. This function seems to convert SCM (Socket Control Message) data into the `sk_buff`'s internal structure. And yes, I see it! `get_pids` is used to populate `UNIXCB(skb).pid`, the UID and GID are set and `fp` is reset to `NULL`.

Aha! `UNIXCB(skb)` is the `cb` member in the `sk_buff` structure! Now I need to see if it is initialized at all. I have to see who is calling `unix_scm_to_skb` and where the `skb` is prepared in AF_UNIX. I need to make sure that the control block is consistently initialized, and I can't be sure that `unix_scm_to_skb` is the only function that touches it. Let me grep for these functions in the source to ensure a good understanding of what happens.



42/3 2026/10/05 15:18 tool grepper
0m Args:
{
  "Expression": "unix_scm_to_skb",
  "PathPrefix": "net/unix/af_unix.c"
}

Results:
{
  "Output": "net/unix/af_unix.c=1982=static void unix_wfree(struct sk_buff *skb)\n--\nnet/unix/af_unix.c-1987-\nnet/unix/af_unix.c:1988:static int unix_scm_to_skb(struct scm_cookie *scm, struct sk_buff *skb, bool send_fds)\nnet/unix/af_unix.c-1989-{\n--\nnet/unix/af_unix.c=2094=static int unix_dgram_sendmsg(struct socket *sock, struct msghdr *msg,\n--\nnet/unix/af_unix.c-2153-\nnet/unix/af_unix.c:2154:\terr = unix_scm_to_skb(\u0026scm, skb, true);\nnet/unix/af_unix.c-2155-\tif (err \u003c 0)\n--\nnet/unix/af_unix.c=2328=static int queue_oob(struct sock *sk, struct msghdr *msg, struct sock *other,\n--\nnet/unix/af_unix.c-2339-\nnet/unix/af_unix.c:2340:\terr = unix_scm_to_skb(scm, skb, !fds_sent);\nnet/unix/af_unix.c-2341-\tif (err \u003c 0)\n--\nnet/unix/af_unix.c=2388=static int unix_stream_sendmsg(struct socket *sock, struct msghdr *msg,\n--\nnet/unix/af_unix.c-2455-\t\t/* Only send the fds in the first buffer */\nnet/unix/af_unix.c:2456:\t\terr = unix_scm_to_skb(\u0026scm, skb, !fds_sent);\nnet/unix/af_unix.c-2457-\t\tif (err \u003c 0)\n"
}

43/3 2026/10/05 15:18 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 39206
							output: 36
							thoughts: 79
						

Thoughts:
Okay, here's my thought process on this, distilled down to a first-person summary, keeping in mind my field of expertise:

**Investigating Potential Data Corruption in AF_UNIX Socket Buffer Allocation**

Alright, I'm digging into this potential issue with AF_UNIX sockets. My initial thought is to scrutinize how we're allocating and initializing socket buffers, specifically those destined for send operations. The suspicious bit is the potential for data corruption.

My gut tells me the place to start is either `alloc_skb` which is the low level buffer allocation primitive, or `sock_alloc_send_pskb`. I need to figure out which path we're taking, or both. That's likely where the `skb` gets initially setup, and where any initialization might be happening.

Hang on. I think I see a potential issue. There's a `memset(UNIXCB(skb), ...)` call that looks like it's initializing something. Now, I vaguely recall `UNIXCB` being a macro, and my experience says, it could be doing some critical setup for the `skb`'s control block, the `unix_sock` structure to be more precise. If this is getting initialized incorrectly, it could lead to the data corruption I'm trying to diagnose.

Okay, let's not waste any more time. I'll fire up `grep` and search the `net/unix/af_unix.c` file for instances of `UNIXCB`. That should quickly reveal all the places where this macro is used, and how that control block, and the `skb` is being managed. That's the quickest way to get a solid grasp of how `UNIXCB` impacts buffer management. Time to dig in!



44/3 2026/10/05 15:18 tool grepper
0m Args:
{
  "Expression": "UNIXCB\\(skb\\)",
  "PathPrefix": "net/unix/af_unix.c"
}

Results:
{
  "Output": "net/unix/af_unix.c=256=static void unix_get_secdata(struct scm_cookie *scm, struct sk_buff *skb)\nnet/unix/af_unix.c-257-{\nnet/unix/af_unix.c:258:\tUNIXCB(skb).secid = scm-\u003esecid;\nnet/unix/af_unix.c-259-}\n--\nnet/unix/af_unix.c=261=static inline void unix_set_secdata(struct scm_cookie *scm, struct sk_buff *skb)\nnet/unix/af_unix.c-262-{\nnet/unix/af_unix.c:263:\tscm-\u003esecid = UNIXCB(skb).secid;\nnet/unix/af_unix.c-264-}\n--\nnet/unix/af_unix.c=266=static inline bool unix_secdata_eq(struct scm_cookie *scm, struct sk_buff *skb)\nnet/unix/af_unix.c-267-{\nnet/unix/af_unix.c:268:\treturn (scm-\u003esecid == UNIXCB(skb).secid);\nnet/unix/af_unix.c-269-}\n--\nnet/unix/af_unix.c=660=static unsigned int unix_skb_len(const struct sk_buff *skb)\nnet/unix/af_unix.c-661-{\nnet/unix/af_unix.c:662:\treturn skb-\u003elen - UNIXCB(skb).consumed;\nnet/unix/af_unix.c-663-}\n--\nnet/unix/af_unix.c=1941=static int unix_attach_fds(struct scm_cookie *scm, struct sk_buff *skb)\n--\nnet/unix/af_unix.c-1945-\nnet/unix/af_unix.c:1946:\tUNIXCB(skb).fp = scm-\u003efp;\nnet/unix/af_unix.c-1947-\tscm-\u003efp = NULL;\nnet/unix/af_unix.c-1948-\nnet/unix/af_unix.c:1949:\tif (unix_prepare_fpl(UNIXCB(skb).fp))\nnet/unix/af_unix.c-1950-\t\treturn -ENOMEM;\n--\nnet/unix/af_unix.c=1955=static void unix_detach_fds(struct scm_cookie *scm, struct sk_buff *skb)\nnet/unix/af_unix.c-1956-{\nnet/unix/af_unix.c:1957:\tscm-\u003efp = UNIXCB(skb).fp;\nnet/unix/af_unix.c:1958:\tUNIXCB(skb).fp = NULL;\nnet/unix/af_unix.c-1959-\n--\nnet/unix/af_unix.c=1963=static void unix_peek_fds(struct scm_cookie *scm, struct sk_buff *skb)\nnet/unix/af_unix.c-1964-{\nnet/unix/af_unix.c:1965:\tscm-\u003efp = scm_fp_dup(UNIXCB(skb).fp);\nnet/unix/af_unix.c-1966-\n--\nnet/unix/af_unix.c=1970=static void unix_destruct_scm(struct sk_buff *skb)\n--\nnet/unix/af_unix.c-1973-\nnet/unix/af_unix.c:1974:\tswap_pids(scm.pid, UNIXCB(skb).pid);\nnet/unix/af_unix.c-1975-\nnet/unix/af_unix.c:1976:\tif (UNIXCB(skb).fp)\nnet/unix/af_unix.c-1977-\t\tunix_detach_fds(\u0026scm, skb);\n--\nnet/unix/af_unix.c=1988=static int unix_scm_to_skb(struct scm_cookie *scm, struct sk_buff *skb, bool send_fds)\n--\nnet/unix/af_unix.c-1991-\nnet/unix/af_unix.c:1992:\tget_pids(UNIXCB(skb).pid, scm-\u003epid);\nnet/unix/af_unix.c:1993:\tUNIXCB(skb).uid = scm-\u003ecreds.uid;\nnet/unix/af_unix.c:1994:\tUNIXCB(skb).gid = scm-\u003ecreds.gid;\nnet/unix/af_unix.c:1995:\tUNIXCB(skb).fp = NULL;\nnet/unix/af_unix.c-1996-\tunix_get_secdata(scm, skb);\n--\nnet/unix/af_unix.c=2004=static void unix_skb_to_scm(struct sk_buff *skb, struct scm_cookie *scm)\nnet/unix/af_unix.c-2005-{\nnet/unix/af_unix.c:2006:\tget_pids(scm-\u003epid, UNIXCB(skb).pid);\nnet/unix/af_unix.c:2007:\tscm_set_cred(scm, UNIXCB(skb).pid[PIDTYPE_TGID], UNIXCB(skb).uid, UNIXCB(skb).gid);\nnet/unix/af_unix.c-2008-\tunix_set_secdata(scm, skb);\n--\nnet/unix/af_unix.c=2024=static int unix_maybe_add_creds(struct sk_buff *skb, const struct sock *sk,\n--\nnet/unix/af_unix.c-2026-{\nnet/unix/af_unix.c:2027:\tif (UNIXCB(skb).pid[PIDTYPE_TGID])\nnet/unix/af_unix.c-2028-\t\treturn 0;\n--\nnet/unix/af_unix.c-2033-\nnet/unix/af_unix.c:2034:\t\tget_task_pids(UNIXCB(skb).pid, current);\nnet/unix/af_unix.c:2035:\t\terr = pidfs_register_pids(UNIXCB(skb).pid);\nnet/unix/af_unix.c-2036-\t\tif (unlikely(err)) {\nnet/unix/af_unix.c:2037:\t\t\tput_pids(UNIXCB(skb).pid);\nnet/unix/af_unix.c-2038-\t\t\treturn err;\n--\nnet/unix/af_unix.c-2040-\nnet/unix/af_unix.c:2041:\t\tcurrent_uid_gid(\u0026UNIXCB(skb).uid, \u0026UNIXCB(skb).gid);\nnet/unix/af_unix.c-2042-\t}\n--\nnet/unix/af_unix.c=2047=static bool unix_skb_scm_eq(const struct sock *sk, struct sk_buff *skb,\n--\nnet/unix/af_unix.c-2051-\tif (sk-\u003esk_scm_pidfd_thread \u0026\u0026\nnet/unix/af_unix.c:2052:\t    UNIXCB(skb).pid[PIDTYPE_PID] != scm-\u003epid[PIDTYPE_PID])\nnet/unix/af_unix.c-2053-\t\treturn false;\nnet/unix/af_unix.c-2054-\nnet/unix/af_unix.c:2055:\treturn UNIXCB(skb).pid[PIDTYPE_TGID] == scm-\u003epid[PIDTYPE_TGID] \u0026\u0026\nnet/unix/af_unix.c:2056:\t       uid_eq(UNIXCB(skb).uid, scm-\u003ecreds.uid) \u0026\u0026\nnet/unix/af_unix.c:2057:\t       gid_eq(UNIXCB(skb).gid, scm-\u003ecreds.gid) \u0026\u0026\nnet/unix/af_unix.c-2058-\t       unix_secdata_eq(scm, skb);\n--\nnet/unix/af_unix.c=2061=static void scm_stat_add(struct sock *sk, struct sk_buff *skb)\nnet/unix/af_unix.c-2062-{\nnet/unix/af_unix.c:2063:\tstruct scm_fp_list *fp = UNIXCB(skb).fp;\nnet/unix/af_unix.c-2064-\tstruct unix_sock *u = unix_sk(sk);\n--\nnet/unix/af_unix.c=2072=static void scm_stat_del(struct sock *sk, struct sk_buff *skb)\nnet/unix/af_unix.c-2073-{\nnet/unix/af_unix.c:2074:\tstruct scm_fp_list *fp = UNIXCB(skb).fp;\nnet/unix/af_unix.c-2075-\tstruct unix_sock *u = unix_sk(sk);\n--\nnet/unix/af_unix.c=2094=static int unix_dgram_sendmsg(struct socket *sock, struct msghdr *msg,\n--\nnet/unix/af_unix.c-2248-\nnet/unix/af_unix.c:2249:\tif (UNIXCB(skb).fp \u0026\u0026 !other-\u003esk_scm_rights) {\nnet/unix/af_unix.c-2250-\t\terr = -EPERM;\n--\nnet/unix/af_unix.c=2328=static int queue_oob(struct sock *sk, struct msghdr *msg, struct sock *other,\n--\nnet/unix/af_unix.c-2361-\nnet/unix/af_unix.c:2362:\tif (UNIXCB(skb).fp \u0026\u0026 !other-\u003esk_scm_rights) {\nnet/unix/af_unix.c-2363-\t\terr = -EPERM;\n--\nnet/unix/af_unix.c=2388=static int unix_stream_sendmsg(struct socket *sock, struct msghdr *msg,\n--\nnet/unix/af_unix.c-2488-\nnet/unix/af_unix.c:2489:\t\tif (UNIXCB(skb).fp \u0026\u0026 !other-\u003esk_scm_rights) {\nnet/unix/af_unix.c-2490-\t\t\tunix_state_unlock(other);\n--\nnet/unix/af_unix.c=2573=int __unix_dgram_recvmsg(struct sock *sk, struct msghdr *msg, size_t size,\n--\nnet/unix/af_unix.c-2647-\tif (!(flags \u0026 MSG_PEEK)) {\nnet/unix/af_unix.c:2648:\t\tif (UNIXCB(skb).fp)\nnet/unix/af_unix.c-2649-\t\t\tunix_detach_fds(\u0026scm, skb);\n--\nnet/unix/af_unix.c-2667-\nnet/unix/af_unix.c:2668:\t\tif (UNIXCB(skb).fp)\nnet/unix/af_unix.c-2669-\t\t\tunix_peek_fds(\u0026scm, skb);\n--\nnet/unix/af_unix.c=2922=static int unix_stream_read_generic(struct unix_stream_read_state *state,\n--\nnet/unix/af_unix.c-3067-\t\tif (!(flags \u0026 MSG_PEEK)) {\nnet/unix/af_unix.c:3068:\t\t\tUNIXCB(skb).consumed += chunk;\nnet/unix/af_unix.c-3069-\n--\nnet/unix/af_unix.c-3071-\nnet/unix/af_unix.c:3072:\t\t\tif (UNIXCB(skb).fp) {\nnet/unix/af_unix.c-3073-\t\t\t\tscm_stat_del(sk, skb);\n--\nnet/unix/af_unix.c-3092-\t\t\t */\nnet/unix/af_unix.c:3093:\t\t\tif (UNIXCB(skb).fp)\nnet/unix/af_unix.c-3094-\t\t\t\tunix_peek_fds(\u0026scm, skb);\n--\nnet/unix/af_unix.c-3097-\nnet/unix/af_unix.c:3098:\t\t\tif (UNIXCB(skb).fp)\nnet/unix/af_unix.c-3099-\t\t\t\tbreak;\n--\nnet/unix/af_unix.c=3131=static int unix_stream_read_actor(struct sk_buff *skb,\n--\nnet/unix/af_unix.c-3136-\nnet/unix/af_unix.c:3137:\tret = skb_copy_datagram_msg(skb, UNIXCB(skb).consumed + skip,\nnet/unix/af_unix.c-3138-\t\t\t\t    state-\u003emsg, chunk);\n--\nnet/unix/af_unix.c=3177=static int unix_stream_splice_actor(struct sk_buff *skb,\n--\nnet/unix/af_unix.c-3181-\treturn skb_splice_bits(skb, state-\u003esocket-\u003esk,\nnet/unix/af_unix.c:3182:\t\t\t       UNIXCB(skb).consumed + skip,\nnet/unix/af_unix.c-3183-\t\t\t       state-\u003epipe, chunk, state-\u003esplice_flags);\n"
}

45/3 2026/10/05 15:18 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 43169
							output: 31
							thoughts: 179
						

Thoughts:
**Analyzing Socket Buffer Allocation in Unix Domain Sockets**

Okay, so I'm digging into how socket buffers (`skb`) are handled in the Unix domain code, specifically within `unix_dgram_sendmsg` and `unix_stream_sendmsg`. The key function here is `unix_scm_to_skb`, and I need to understand how the `skb` is created and initialized, especially since we're dealing with ancillary data (`scm`). Right, so it looks like `sock_alloc_send_pskb` is the common point of allocation, which then calls `alloc_skb`. Now, in `sock_alloc_send_pskb`, after the `skb` is allocated with `alloc_skb`, there's a `memset(skb, 0, ...)`. That's important, but what *really* piques my interest is the subsequent zeroing of `skb->cb` using `memset(skb->cb, 0, sizeof(skb->cb))`. The control block is where the transport protocol and lower layers store their private data. I need to know exactly what's being initialized here, and the exact implications of this zeroing. To make *absolutely* sure, I should double check `__alloc_skb` in `net/core/skbuff.c` and confirm that I haven't missed anything about the internal allocation process.



46/3 2026/10/05 15:18 tool grepper
0m Error:
no matches

Args:
{
  "Expression": "memset.*cb",
  "PathPrefix": "net/core/skbuff.c"
}

Results:
{
  "error": "no matches"
}

47/3 2026/10/05 15:18 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 43695
							output: 33
							thoughts: 22
						

Thoughts:
Okay, let's dive into this. Here's my understanding, presented as a personal thought process, given my expertise:

**Diving into the SKB Allocation Process**

Alright, I'm thinking, where's the best place to start understanding the kernel's memory management for network packets? Well, the standard go-to is always the core, and in this case, the `sk_buff` structure is pretty much the central data structure for network buffers. So, let's take a look at how these things are actually *allocated*. My intuition tells me that `__alloc_skb` is the key function in `net/core/skbuff.c`. I expect this will be the low-level, internal function that handles the nitty-gritty of getting an `sk_buff` instance and its associated data blocks set up. I'm keen to see how the kernel handles the various buffer sizes, any considerations for DMA, and of course, how they deal with alignment and page boundaries. It's time to dig in and see how this foundational part of the networking stack is implemented. I'm looking for memory allocation strategies, how they're managing fragments, and any optimizations they've put in place to ensure maximum efficiency. Let's see how smart they are!



48/3 2026/10/05 15:18 tool grepper
0m Args:
{
  "Expression": "memset\\(skb,",
  "PathPrefix": "net/core/skbuff.c"
}

Results:
{
  "Output": "net/core/skbuff.c=316=static inline void skbuff_clear(struct sk_buff *skb)\nnet/core/skbuff.c-317-{\nnet/core/skbuff.c:318:\t/* Replace memset(skb, 0, offsetof(struct sk_buff, tail))\nnet/core/skbuff.c-319-\t * with two smaller memset(), with a barrier() between them.\n--\nnet/core/skbuff.c-322-\tBUILD_BUG_ON(offsetof(struct sk_buff, tail) \u003c= 128);\nnet/core/skbuff.c:323:\tmemset(skb, 0, 128);\nnet/core/skbuff.c-324-\tbarrier();\n"
}

49/3 2026/10/05 15:18 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 44225
							output: 36
							thoughts: 17
						

Thoughts:
Okay, let's dive into this code.

**Diving into skbuff_clear**

Alright, I'm going to navigate to line 316 and zero in on the `skbuff_clear` function. I'm already pretty familiar with the `sk_buff` structure and its role in handling network packets in the kernel, so I'm keen to see how this particular function is used. I'm expecting it to be responsible for cleaning up or resetting the state of an `sk_buff`, likely before reuse or deallocation. I'm curious about the specific fields it touches and the strategy it employs. Does it simply zero out memory, or does it perform more nuanced operations like decrementing reference counts or releasing associated resources? I'll look for any potential performance considerations, especially if it's called frequently in the data path. Let's see what's what.



50/3 2026/10/05 15:18 tool read-file
0m Args:
{
  "File": "net/core/skbuff.c",
  "FirstLine": 315,
  "LineCount": 25
}

Results:
{
  "Contents": " 315:\t */\n 316:\tstatic inline void skbuff_clear(struct sk_buff *skb)\n 317:\t{\n 318:\t\t/* Replace memset(skb, 0, offsetof(struct sk_buff, tail))\n 319:\t\t * with two smaller memset(), with a barrier() between them.\n 320:\t\t * This forces the compiler to inline both calls.\n 321:\t\t */\n 322:\t\tBUILD_BUG_ON(offsetof(struct sk_buff, tail) \u003c= 128);\n 323:\t\tmemset(skb, 0, 128);\n 324:\t\tbarrier();\n 325:\t\tmemset((void *)skb + 128, 0, offsetof(struct sk_buff, tail) - 128);\n 326:\t}\n 327:\t\n 328:\t/**\n 329:\t * napi_skb_cache_get_bulk - obtain a number of zeroed skb heads from the cache\n 330:\t * @skbs: pointer to an at least @n-sized array to fill with skb pointers\n 331:\t * @n: number of entries to provide\n 332:\t *\n 333:\t * Tries to obtain @n \u0026sk_buff entries from the NAPI percpu cache and writes\n 334:\t * the pointers into the provided array @skbs. If there are less entries\n 335:\t * available, tries to replenish the cache and bulk-allocates the diff from\n 336:\t * the MM layer if needed.\n 337:\t * The heads are being zeroed with either memset() or %__GFP_ZERO, so they are\n 338:\t * ready for {,__}build_skb_around() and don't have any data buffers attached.\n 339:\t * Must be called *only* from the BH context.\n"
}

51/3 2026/10/05 15:18 llm kmsan-evaluator
1m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 44956
							output: 32
							thoughts: 49
						

Thoughts:
Okay, here's my interpretation of those thoughts, tailored for an expert audience, presented in the first person:

**Navigating the SKB Structure**

Right, `skbuff_clear` is intended to zero a section of the `sk_buff` struct, specifically up to the `tail` member. The question is, where exactly is the control block, or `cb`, within the `struct sk_buff` layout? It's been a while since I last looked at this. I need to refresh my memory on the exact field ordering. The header files are my best bet. Time to head over to `include/linux/skbuff.h`. I'll find my answer there.



52/3 2026/10/05 15:20 tool grepper
0m Args:
{
  "Expression": "char.*cb\\[",
  "PathPrefix": "include/linux/skbuff.h"
}

Results:
{
  "Output": "include/linux/skbuff.h=886=struct sk_buff {\n--\ninclude/linux/skbuff.h-918-\t */\ninclude/linux/skbuff.h:919:\tchar\t\t\tcb[48] __aligned(8);\ninclude/linux/skbuff.h-920-\n"
}

53/3 2026/10/05 15:20 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 45274
							output: 38
							thoughts: 19
						

Thoughts:
Okay, here's my summary, tailored for an expert audience, as if it were my thought process:

**Where's That `tail` Pointer Hiding?**

Alright, I need to get a handle on the structure of `struct sk_buff`. Specifically, I'm trying to mentally map out how data is organized, and the location of the `tail` pointer is crucial. It's fundamental to understanding where newly added data is appended within the buffer. Let me dive in and check the definition of `struct sk_buff` to pinpoint its exact location and how it interacts with other key members like `data`, `len`, and `head`. Knowing the internal layout will immediately clarify the buffer management logic and potentially expose optimizations in whatever code I'm currently scrutinizing. Let me get that definition up...



54/3 2026/10/05 15:20 tool grepper
0m Args:
{
  "Expression": "sk_buff_data_t.*tail;",
  "PathPrefix": "include/linux/skbuff.h"
}

Results:
{
  "Output": "include/linux/skbuff.h=886=struct sk_buff {\n--\ninclude/linux/skbuff.h-1091-\t/* These elements must be at the end, see alloc_skb() for details.  */\ninclude/linux/skbuff.h:1092:\tsk_buff_data_t\t\ttail;\ninclude/linux/skbuff.h-1093-\tsk_buff_data_t\t\tend;\n"
}

55/3 2026/10/05 15:20 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 45622
							output: 30
							thoughts: 2173
						

Thoughts:
**Analyzing `sk_buff` and Peer Credentials in Kernel Bluetooth Code**

Okay, so I'm diving into this `sk_buff` and the way peer credentials are handled, particularly in the Bluetooth subsystem. My initial thought is that `cb` is located within the zeroed portion of `sk_buff`, specifically between offsets ~30-40, which falls well within the zeroed space given its maximum size. And, given that `sizeof(struct unix_skb_parms)` is only 44 bytes, the allocated `cb[48]` space should be more than sufficient.

Now, I'm checking the `sock_init_data_uid` function. It seems like the `sk->sk_peer_pid` is being zeroed out using `memset` during initialization and allocation. This initial zeroing is crucial to prevent any leaks of sensitive information. I'm taking note of this; it sets a baseline for understanding the state of these fields when a socket is first created.

Then, I'm looking at how this is used in the Bluetooth implementation. It appears that the Bluetooth code makes specific use of `sk->sk_peer_pid` in several places. Initially, in the `af_bluetooth.c` file, the `sk->sk_peer_pid` is being set to the current task's TGID (thread group ID) upon establishing peer information, but with the introduction of an array index `[PIDTYPE_TGID]`. Furthermore, the code then accesses the `sk->sk_peer_pid` array element `[PIDTYPE_TGID]` in `bt_accept_enqueue` and `hci_sock.c`. It's a key value for determining whether peer credentials are present. This `[PIDTYPE_TGID]` index is consistent throughout the Bluetooth code.

At this point, I need to check the macros and helpers in `include/linux/pid.h`, particularly related to the `DECLARE_PIDS` macro. It looks like `DECLARE_PIDS` is used to declare an array of `struct pid`s indexed by `pid_type`. I see how `PIDTYPE_PID`, `PIDTYPE_TGID`, `PIDTYPE_PGID`, and `PIDTYPE_SID` are enumerated, with a maximum `PIDTYPE_MAX` of 4. A `DECLARE_PIDS(pid, PIDTYPE_TGID)` would effectively declare an array of two `struct pid` pointers: `pid[0]` which corresponds to `PIDTYPE_PID` and `pid[1]` which is `PIDTYPE_TGID`. This helps me see that the indexing used in the Bluetooth code is correct for the intended array size, which supports tracking peer PIDs and TGIDs separately. All the `__*` functions and the like seem to be safe as the bounds are checked in the loop.

Now I am going to check how the values for the peer credentials are accessed. The `cred_to_ucred` function, especially within `sk_getsockopt`, accesses `sk->sk_peer_pid[PIDTYPE_TGID]` within a spinlock for SO_PEERCRED options. This provides a safe way to retrieve the peer's credentials. This means that the credential information is being retrieved based on the TGID, which is in the second array location. And I need to understand what `cred_to_ucred` actually *does*. This is the critical piece here, and I need to check how the data is populated from the `struct pid` array into the `ucred` structure.



56/3 2026/10/05 15:20 tool grepper
0m Args:
{
  "Expression": "cred_to_ucred",
  "PathPrefix": "net/core/"
}

Results:
{
  "Output": "net/core/sock.c=1700=static const struct cred *sk_get_peer_cred(struct sock *sk)\n--\nnet/core/sock.c-1710-\nnet/core/sock.c:1711:static void cred_to_ucred(struct pid *pid, const struct cred *cred,\nnet/core/sock.c-1712-\t\t\t  struct ucred *ucred)\n--\nnet/core/sock.c=1783=int sk_getsockopt(struct sock *sk, int level, int optname,\n--\nnet/core/sock.c-1967-\t\tspin_lock(\u0026sk-\u003esk_peer_lock);\nnet/core/sock.c:1968:\t\tcred_to_ucred(sk-\u003esk_peer_pid[PIDTYPE_TGID], sk-\u003esk_peer_cred,\nnet/core/sock.c-1969-\t\t\t      \u0026peercred);\n"
}

57/3 2026/10/05 15:20 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 48828
							output: 36
							thoughts: 22
						

Thoughts:
Okay, here's my take on those thoughts, tailored for an expert audience:

**Diving into `cred_to_ucred` in `net/core/sock.c`**

Alright, let's get down to brass tacks. Time to crack open `cred_to_ucred` in `net/core/sock.c`. We all know this function is critical. We're talking about the crucial translation – the bridge, if you will – between the kernel's credential representation (`cred`) and the user-space credential structure (`ucred`). This is where we examine that critical transition, understanding how kernel privileges are manifested (or constrained) for socket operations in user space. I'm keen to dissect the intricacies of this function, looking for any potential vulnerabilities or less-than-obvious behavior, especially when dealing with advanced network configurations. The goal is to fully grasp its role in securely bridging the kernel and user space and confirm there are no surprises. It's time to refresh the mental model of how credentials flow and get a handle on what this function is really doing under the hood. I suspect it'll be more nuanced than I remember.



58/3 2026/10/05 15:20 tool read-file
0m Args:
{
  "File": "net/core/sock.c",
  "FirstLine": 1710,
  "LineCount": 25
}

Results:
{
  "Contents": "1710:\t\n1711:\tstatic void cred_to_ucred(struct pid *pid, const struct cred *cred,\n1712:\t\t\t\t  struct ucred *ucred)\n1713:\t{\n1714:\t\tucred-\u003epid = pid_vnr(pid);\n1715:\t\tucred-\u003euid = ucred-\u003egid = -1;\n1716:\t\tif (cred) {\n1717:\t\t\tstruct user_namespace *current_ns = current_user_ns();\n1718:\t\n1719:\t\t\tucred-\u003euid = from_kuid_munged(current_ns, cred-\u003eeuid);\n1720:\t\t\tucred-\u003egid = from_kgid_munged(current_ns, cred-\u003eegid);\n1721:\t\t}\n1722:\t}\n1723:\t\n1724:\tstatic int groups_to_user(sockptr_t dst, const struct group_info *src)\n1725:\t{\n1726:\t\tstruct user_namespace *user_ns = current_user_ns();\n1727:\t\tint i;\n1728:\t\n1729:\t\tfor (i = 0; i \u003c src-\u003engroups; i++) {\n1730:\t\t\tgid_t gid = from_kgid_munged(user_ns, src-\u003egid[i]);\n1731:\t\n1732:\t\t\tif (copy_to_sockptr_offset(dst, i * sizeof(gid), \u0026gid, sizeof(gid)))\n1733:\t\t\t\treturn -EFAULT;\n1734:\t\t}\n"
}

59/3 2026/10/05 15:20 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 49555
							output: 33
							thoughts: 689
						

Thoughts:
**Analyzing Credential Handling in Socket Operations**

Okay, so I'm looking at how credentials are managed in this networking code. Specifically, I'm focusing on potential security implications around how process IDs, user IDs, group IDs, and credentials are shared across sockets. The initial thought was that `cred_to_ucred` seemed straightforward in initializing the `pid`, `uid`, and `gid` fields within `struct ucred`.

But then, the `SO_PEERPIDFD` and `SO_PEERPIDFD_THREAD` options immediately popped up. These options are crucial, because they allow us to hand out a pidfd to peer sockets. I need to be sure that this handoff is secure. So I re-read the relevant code block, `sk_getsockopt_peerpidfd`.

My analysis here is that `pidfd` is initialized by `pidfd_prepare`, and then its value gets copied to user space. The important thing is that `pidfd_prepare` returns a file descriptor for a pidfd, which encapsulates the peer's process. And the length is handled and then copied to user space. Therefore, there doesn't seem to be any uninitialized memory being leaked. The code seems to be explicitly handling the pidfd's lifecycle.

Okay, that seems relatively safe on the surface, but I can't be too hasty. I've got to consider all the pieces. What about `copy_peercred` in `net/unix/af_unix.c`? That function is where we transfer credentials from one socket to another. I need to understand exactly how that is implemented. I need to trace its usage to figure out what it actually does with peer credentials. The key point is to understand when and how this `copy_peercred` function is called, because the implications here could be significant.



60/3 2026/10/05 15:20 tool grepper
0m Args:
{
  "Expression": "copy_peercred",
  "PathPrefix": "net/unix/af_unix.c"
}

Results:
{
  "Output": "net/unix/af_unix.c=777=static void update_peercred(struct sock *sk, struct unix_peercred *peercred)\n--\nnet/unix/af_unix.c-789-\nnet/unix/af_unix.c:790:static void copy_peercred(struct sock *sk, struct sock *peersk)\nnet/unix/af_unix.c-791-{\n--\nnet/unix/af_unix.c=1632=static int unix_stream_connect(struct socket *sock, struct sockaddr_unsized *uaddr,\n--\nnet/unix/af_unix.c-1780-\t/* Set credentials */\nnet/unix/af_unix.c:1781:\tcopy_peercred(sk, other);\nnet/unix/af_unix.c-1782-\n"
}

61/3 2026/10/05 15:20 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 50885
							output: 38
							thoughts: 1375
						

Thoughts:
**Analyzing Peer Credential Handling in AF_UNIX Sockets**

Okay, let's break this down. I'm looking at how peer credentials, specifically process IDs (PIDs) and thread IDs, are managed in AF_UNIX sockets. My initial focus is on the `unix_stream_connect` function and its handling of `peercred`. It appears that `prepare_peercred` is called to initialize it, and then `update_peercred` and `copy_peercred` are used to propagate the credentials. The critical question is, what happens to the credentials of the "other" socket, especially in a listening socket or when using `socketpair`?

I then see that in the `unix_listen` function, the `sk_peer_pid` member is initialized correctly using `update_peercred` during the creation of a listening socket, which is good. For `socketpair`, both sides seem to have valid and initialized peer PIDs, using `update_peercred` as well. This is important to ensure correct behavior.

Now, I'm getting concerned about the edge cases. Can a socket's `sk_peer_pid` be accessed before it's properly initialized? The `sk_getsockopt_peerpidfd` function seems relevant here. I verified that if `sk_peer_pid[type]` is NULL, a `-ENODATA` error is returned. That's good, providing an expected behavior in such a case. I also considered `SO_PEERPIDFD_THREAD`, and it seems that if the socket isn't AF_UNIX or if it's called before the connection, the code handles the situation gracefully, returning an error.

Next, I turned my attention to the `scm_recv_unix` function. I'm wondering what happens if the receiver is configured to receive PIDFD thread information (`sk_scm_pidfd_thread`), but the sender doesn't send credentials or if the PID is NULL. My investigation quickly leads me to `scm_pidfd_recv`, where a NULL PID is handled harmlessly, as it just returns. Excellent, no unexpected crashes or security flaws here.

Finally, I'm checking `unix_stream_read_generic`. I'm focusing on the credential checking logic, specifically when `check_creds` is true. Here, the `unix_skb_scm_eq` function becomes relevant to ensure proper credential matching. I'm analyzing how `scm` is used and where it comes from. I verified that, in this specific case, `unix_skb_scm_eq` ensures the PID (for the case `sk_scm_pidfd_thread` is true), TGID, UID, and GID match between the sending and receiving sockets to prevent glueing messages from different writers. I also need to verify where `scm` comes from within this function in the larger context.

This analysis gives me a good overview of how credentials, PIDs, and thread information are handled in the AF_UNIX socket implementation and highlights potential areas where NULL pointers are handled appropriately. My next step will be analyzing how `scm` is populated and used within `unix_stream_read_generic`.



62/3 2026/10/05 15:20 tool read-file
0m Args:
{
  "File": "net/unix/af_unix.c",
  "FirstLine": 2920,
  "LineCount": 40
}

Results:
{
  "Contents": "2920:\t}\n2921:\t\n2922:\tstatic int unix_stream_read_generic(struct unix_stream_read_state *state,\n2923:\t\t\t\t\t    bool freezable)\n2924:\t{\n2925:\t\tint noblock = state-\u003eflags \u0026 MSG_DONTWAIT;\n2926:\t\tstruct socket *sock = state-\u003esocket;\n2927:\t\tstruct msghdr *msg = state-\u003emsg;\n2928:\t\tstruct sock *sk = sock-\u003esk;\n2929:\t\tsize_t size = state-\u003esize;\n2930:\t\tint flags = state-\u003eflags;\n2931:\t\tbool check_creds = false;\n2932:\t\tstruct scm_cookie scm;\n2933:\t\tstruct unix_sock *u;\n2934:\t\tint copied = 0;\n2935:\t\tint err = 0;\n2936:\t\tlong timeo;\n2937:\t\tint target;\n2938:\t\tint skip;\n2939:\t\n2940:\t\tif (unlikely(READ_ONCE(sk-\u003esk_state) != TCP_ESTABLISHED)) {\n2941:\t\t\terr = -EINVAL;\n2942:\t\t\tgoto out;\n2943:\t\t}\n2944:\t\n2945:\t\tif (unlikely(flags \u0026 MSG_OOB)) {\n2946:\t\t\terr = -EOPNOTSUPP;\n2947:\t#if IS_ENABLED(CONFIG_AF_UNIX_OOB)\n2948:\t\t\terr = unix_stream_recv_urg(state);\n2949:\t#endif\n2950:\t\t\tgoto out;\n2951:\t\t}\n2952:\t\n2953:\t\ttarget = sock_rcvlowat(sk, flags \u0026 MSG_WAITALL, size);\n2954:\t\ttimeo = sock_rcvtimeo(sk, noblock);\n2955:\t\n2956:\t\tmemset(\u0026scm, 0, sizeof(scm));\n2957:\t\n2958:\t\tu = unix_sk(sk);\n2959:\t\n"
}

63/3 2026/10/05 15:20 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 53546
							output: 36
							thoughts: 62
						

Thoughts:
Here's my analysis of the provided code snippet:

**Debugging Dive: Understanding `scm` Initialization and Usage**

Okay, so I'm digging into this code and my immediate attention is drawn to line 2956. Seeing `memset(&scm, 0, sizeof(scm));` makes me perk up. The variable `scm` is explicitly being zeroed out. This is a common pattern, but it always warrants a closer look – especially when dealing with socket control messages.

Now, my mind is already jumping ahead, thinking about how `scm` is being used downstream. I need to understand its role. My next step? I have to go check the function `unix_dgram_recvmsg`. That function is crucial for understanding how `scm` is being used. Where does it get assigned, and what specific control messages might we be looking for? It's all about verifying what information the `scm` struct potentially holds. I need to trace its usage within that function to ensure that zeroing it out is appropriate. Is this being set correctly? Is this being read correctly? That's the key question.



64/3 2026/10/05 15:20 tool grepper
0m Args:
{
  "Expression": "unix_dgram_recvmsg",
  "PathPrefix": "net/unix/af_unix.c"
}

Results:
{
  "Output": "net/unix/af_unix.c=862=static int unix_dgram_sendmsg(struct socket *, struct msghdr *, size_t);\nnet/unix/af_unix.c:863:static int unix_dgram_recvmsg(struct socket *, struct msghdr *, size_t, int);\nnet/unix/af_unix.c-864-static int unix_read_skb(struct sock *sk, skb_read_actor_t recv_actor);\n--\nnet/unix/af_unix.c=999=static const struct proto_ops unix_dgram_ops = {\n--\nnet/unix/af_unix.c-1017-\t.read_skb =\tunix_read_skb,\nnet/unix/af_unix.c:1018:\t.recvmsg =\tunix_dgram_recvmsg,\nnet/unix/af_unix.c-1019-\t.mmap =\t\tsock_no_mmap,\n--\nnet/unix/af_unix.c=2552=static int unix_seqpacket_recvmsg(struct socket *sock, struct msghdr *msg,\n--\nnet/unix/af_unix.c-2559-\nnet/unix/af_unix.c:2560:\treturn unix_dgram_recvmsg(sock, msg, size, flags);\nnet/unix/af_unix.c-2561-}\n--\nnet/unix/af_unix.c=2563=static void unix_copy_addr(struct msghdr *msg, struct sock *sk)\n--\nnet/unix/af_unix.c-2572-\nnet/unix/af_unix.c:2573:int __unix_dgram_recvmsg(struct sock *sk, struct msghdr *msg, size_t size,\nnet/unix/af_unix.c-2574-\t\t\t int flags)\n--\nnet/unix/af_unix.c-2681-\nnet/unix/af_unix.c:2682:static int unix_dgram_recvmsg(struct socket *sock, struct msghdr *msg, size_t size,\nnet/unix/af_unix.c-2683-\t\t\t      int flags)\n--\nnet/unix/af_unix.c-2692-#endif\nnet/unix/af_unix.c:2693:\treturn __unix_dgram_recvmsg(sk, msg, size, flags);\nnet/unix/af_unix.c-2694-}\n--\nnet/unix/af_unix.c=2922=static int unix_stream_read_generic(struct unix_stream_read_state *state,\n--\nnet/unix/af_unix.c-3090-\t\t} else {\nnet/unix/af_unix.c:3091:\t\t\t/* It is questionable, see note in unix_dgram_recvmsg.\nnet/unix/af_unix.c-3092-\t\t\t */\n"
}

65/3 2026/10/05 15:20 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 54623
							output: 38
							thoughts: 22
						

Thoughts:
Okay, let's dive into this. Here's how I'd approach that snippet of code, assuming my expertise:

**Examining `__unix_dgram_recvmsg`**

Alright, my immediate thought is to zero in on `__unix_dgram_recvmsg`, specifically around line 2573. It's often the case that you find critical details in these lower-level networking functions, particularly when dealing with datagrams and Unix sockets. I want to understand exactly how the kernel is handling the reception of messages at that stage. This is where the magic (or the problems!) usually happen with datagram socket operations. Line 2573 is just a starting point for the investigation. I'll be looking for nuances in memory handling, error conditions, and possible race conditions within that area. I need to understand what variables are being read and written around this point, how the socket data structures are being used, and if there are any potential buffer overflows or information leaks that could be hiding in the code. I anticipate seeing key calls related to copying data from the kernel's receive buffer to the user space buffer.



66/3 2026/10/05 15:21 tool read-file
0m Args:
{
  "File": "net/unix/af_unix.c",
  "FirstLine": 2573,
  "LineCount": 45
}

Results:
{
  "Contents": "2573:\tint __unix_dgram_recvmsg(struct sock *sk, struct msghdr *msg, size_t size,\n2574:\t\t\t\t int flags)\n2575:\t{\n2576:\t\tstruct scm_cookie scm;\n2577:\t\tstruct socket *sock = sk-\u003esk_socket;\n2578:\t\tstruct unix_sock *u = unix_sk(sk);\n2579:\t\tstruct sk_buff *skb, *last;\n2580:\t\tlong timeo;\n2581:\t\tint skip;\n2582:\t\tint err;\n2583:\t\n2584:\t\terr = -EOPNOTSUPP;\n2585:\t\tif (flags\u0026MSG_OOB)\n2586:\t\t\tgoto out;\n2587:\t\n2588:\t\ttimeo = sock_rcvtimeo(sk, flags \u0026 MSG_DONTWAIT);\n2589:\t\n2590:\t\tdo {\n2591:\t\t\tmutex_lock(\u0026u-\u003eiolock);\n2592:\t\n2593:\t\t\tskip = sk_peek_offset(sk, flags);\n2594:\t\t\tskb = __skb_try_recv_datagram(sk, \u0026sk-\u003esk_receive_queue, flags,\n2595:\t\t\t\t\t\t      \u0026skip, \u0026err, \u0026last);\n2596:\t\t\tif (skb) {\n2597:\t\t\t\tif (!(flags \u0026 MSG_PEEK))\n2598:\t\t\t\t\tscm_stat_del(sk, skb);\n2599:\t\t\t\tbreak;\n2600:\t\t\t}\n2601:\t\n2602:\t\t\tmutex_unlock(\u0026u-\u003eiolock);\n2603:\t\n2604:\t\t\tif (err != -EAGAIN)\n2605:\t\t\t\tbreak;\n2606:\t\t} while (timeo \u0026\u0026\n2607:\t\t\t !__skb_wait_for_more_packets(sk, \u0026sk-\u003esk_receive_queue,\n2608:\t\t\t\t\t\t      \u0026err, \u0026timeo, last));\n2609:\t\n2610:\t\tif (!skb) { /* implies iolock unlocked */\n2611:\t\t\t/* Signal EOF on disconnected non-blocking SEQPACKET socket. */\n2612:\t\t\tif (sk-\u003esk_type == SOCK_SEQPACKET \u0026\u0026 err == -EAGAIN \u0026\u0026\n2613:\t\t\t    (READ_ONCE(sk-\u003esk_shutdown) \u0026 RCV_SHUTDOWN))\n2614:\t\t\t\terr = 0;\n2615:\t\t\tgoto out;\n2616:\t\t}\n2617:\t\n"
}

67/3 2026/10/05 15:21 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 55604
							output: 38
							thoughts: 30
						

Thoughts:
Okay, let's dive into this. I'm focusing on the `__unix_dgram_recvmsg` function, specifically lines 2618 through 2670. I need to understand this block of code thoroughly, given its likely crucial role in datagram socket reception within the Unix domain. My goal is to grasp the flow, identify any potential optimizations, and look for any areas where subtle security vulnerabilities might exist. I'll need to pay close attention to the error handling, data copying, and any checks performed on the received message. I suspect there might be some interplay with buffer sizes and potential for fragmentation, so I will analyze those elements carefully. Let me begin...



68/3 2026/10/05 15:21 tool read-file
0m Args:
{
  "File": "net/unix/af_unix.c",
  "FirstLine": 2618,
  "LineCount": 55
}

Results:
{
  "Contents": "2618:\t\tif (wq_has_sleeper(\u0026u-\u003epeer_wait))\n2619:\t\t\twake_up_interruptible_sync_poll(\u0026u-\u003epeer_wait,\n2620:\t\t\t\t\t\t\tEPOLLOUT | EPOLLWRNORM |\n2621:\t\t\t\t\t\t\tEPOLLWRBAND);\n2622:\t\n2623:\t\tif (msg-\u003emsg_name) {\n2624:\t\t\tunix_copy_addr(msg, skb-\u003esk);\n2625:\t\n2626:\t\t\tBPF_CGROUP_RUN_PROG_UNIX_RECVMSG_LOCK(sk,\n2627:\t\t\t\t\t\t\t      msg-\u003emsg_name,\n2628:\t\t\t\t\t\t\t      \u0026msg-\u003emsg_namelen);\n2629:\t\t}\n2630:\t\n2631:\t\tif (size \u003e skb-\u003elen - skip)\n2632:\t\t\tsize = skb-\u003elen - skip;\n2633:\t\telse if (size \u003c skb-\u003elen - skip)\n2634:\t\t\tmsg-\u003emsg_flags |= MSG_TRUNC;\n2635:\t\n2636:\t\terr = skb_copy_datagram_msg(skb, skip, msg, size);\n2637:\t\tif (err)\n2638:\t\t\tgoto out_free;\n2639:\t\n2640:\t\tif (sock_flag(sk, SOCK_RCVTSTAMP))\n2641:\t\t\t__sock_recv_timestamp(msg, sk, skb);\n2642:\t\n2643:\t\tmemset(\u0026scm, 0, sizeof(scm));\n2644:\t\n2645:\t\tunix_skb_to_scm(skb, \u0026scm);\n2646:\t\n2647:\t\tif (!(flags \u0026 MSG_PEEK)) {\n2648:\t\t\tif (UNIXCB(skb).fp)\n2649:\t\t\t\tunix_detach_fds(\u0026scm, skb);\n2650:\t\n2651:\t\t\tsk_peek_offset_bwd(sk, skb-\u003elen);\n2652:\t\t} else {\n2653:\t\t\t/* It is questionable: on PEEK we could:\n2654:\t\t\t   - do not return fds - good, but too simple 8)\n2655:\t\t\t   - return fds, and do not return them on read (old strategy,\n2656:\t\t\t     apparently wrong)\n2657:\t\t\t   - clone fds (I chose it for now, it is the most universal\n2658:\t\t\t     solution)\n2659:\t\n2660:\t\t\t   POSIX 1003.1g does not actually define this clearly\n2661:\t\t\t   at all. POSIX 1003.1g doesn't define a lot of things\n2662:\t\t\t   clearly however!\n2663:\t\n2664:\t\t\t*/\n2665:\t\n2666:\t\t\tsk_peek_offset_fwd(sk, size);\n2667:\t\n2668:\t\t\tif (UNIXCB(skb).fp)\n2669:\t\t\t\tunix_peek_fds(\u0026scm, skb);\n2670:\t\t}\n2671:\t\terr = (flags \u0026 MSG_TRUNC) ? skb-\u003elen - skip : size;\n2672:\t\n"
}

69/3 2026/10/05 15:21 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 56668
							output: 27
							thoughts: 1289
						

Thoughts:
**Analyzing Potential NULL Pointer Dereference in Coredump Handling**

Okay, let's break this down. I'm looking at the flow of data within the Unix domain socket and coredump logic, and I see a potential issue. It starts with `memset(&scm, 0, sizeof(scm));` followed by a call to `unix_skb_to_scm(skb, &scm);` and then `scm_recv_unix`. The key here is what happens inside `unix_skb_to_scm`.

Specifically, `get_pids` is used to populate the `scm->pid` array with PIDs from the socket buffer. This then feeds into `scm_set_cred` setting the credentials.  Crucially, `get_pids` calls `get_pid()` on `UNIXCB(skb).pid[type]`, and `get_pid(NULL)` returns NULL. The `scm` struct holds these PIDs, and `put_pids` will later free these. So far, so good.

Now, let's switch gears to the coredump process. I'm looking at `pidfs_coredump_pid` which is called by `pidfs_coredump`. The structure of `pidfs_coredump_pid` relies on `pid->attr`, which is dereferenced. The critical question becomes: can `cprm->pid[type]` ever be NULL when `pidfs_coredump_pid` is called?

I traced it back to the `pidfs_coredump` function itself, within `fs/coredump.c`, which calls `umh_coredump_setup`. Inside `umh_coredump_setup`, there's a check: `if (cp->pid[PIDTYPE_TGID])`. If this condition is true, then `pidfs_coredump` *is* called. This is the crux.

I then followed the population of `cp->pid`. It's populated by `task_pids(cprm->pid, current);` within the `coredump_parse` function. This function uses a pattern, and the call to `task_pids` happens only if the pattern includes `%F`.

Now, the lightbulb goes off! If the coredump pattern does NOT include `%F`, then `task_pids` is *not* called, and `cp->pid[PIDTYPE_TGID]` remains NULL.  Since the earlier `if` statement checks for the PIDTYPE_TGID, if this check returns false then the `pidfs_coredump` function will not be called, and thus no null dereference, great!

But!  What about other callers of `pidfs_coredump`?  I need to make sure this coredump code is the only user.



70/3 2026/10/05 15:21 tool grepper
0m Args:
{
  "Expression": "pidfs_coredump",
  "PathPrefix": "fs/"
}

Results:
{
  "Output": "fs/coredump.c=623=static int umh_coredump_setup(struct subprocess_info *info, struct cred *new)\n--\nfs/coredump.c-639-\nfs/coredump.c:640:\t\tpidfs_coredump(cp);\nfs/coredump.c-641-\n--\nfs/coredump.c=672=static bool coredump_sock_connect(struct core_name *cn, struct coredump_params *cprm)\n--\nfs/coredump.c-714-\t */\nfs/coredump.c:715:\tpidfs_coredump(cprm);\nfs/coredump.c-716-\n--\nfs/pidfs.c=329=static inline bool pid_in_current_pidns(const struct pid *pid)\n--\nfs/pidfs.c-338-\nfs/pidfs.c:339:static __u32 pidfs_coredump_mask(enum task_dumpable dumpable)\nfs/pidfs.c-340-{\n--\nfs/pidfs.c=365=static long pidfd_info(struct file *file, unsigned int cmd, unsigned long arg)\n--\nfs/pidfs.c-433-\tif ((mask \u0026 PIDFD_INFO_COREDUMP) \u0026\u0026 !kinfo.coredump_mask) {\nfs/pidfs.c:434:\t\tkinfo.coredump_mask = pidfs_coredump_mask(task_exec_state_get_dumpable(task));\nfs/pidfs.c-435-\t\tkinfo.mask |= PIDFD_INFO_COREDUMP;\n--\nfs/pidfs.c=749=void pidfs_exit(struct task_struct *tsk)\n--\nfs/pidfs.c-795-#ifdef CONFIG_COREDUMP\nfs/pidfs.c:796:static void pidfs_coredump_pid(struct pid *pid,\nfs/pidfs.c-797-\t\t\t       const struct coredump_params *cprm)\n--\nfs/pidfs.c-806-\t/* Note how we were coredumped and that we coredumped. */\nfs/pidfs.c:807:\tattr-\u003ecoredump_mask = pidfs_coredump_mask(cprm-\u003edumpable) |\nfs/pidfs.c-808-\t\t\t      PIDFD_COREDUMPED;\n--\nfs/pidfs.c-817-\nfs/pidfs.c:818:void pidfs_coredump(const struct coredump_params *cprm)\nfs/pidfs.c-819-{\n--\nfs/pidfs.c-821-\tfor (enum pid_type type = PIDTYPE_PID; type \u003c= pids_last(cprm-\u003epid); type++)\nfs/pidfs.c:822:\t\tpidfs_coredump_pid(cprm-\u003epid[type], cprm);\nfs/pidfs.c-823-}\n"
}

71/3 2026/10/05 15:21 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 59338
							output: 34
							thoughts: 296
						

Thoughts:
**Analyzing the coredump_sock_connect Changes**

Okay, so I'm looking at this change in `coredump_sock_connect`. Interesting, they've shifted from using `task_tgid(current)` to `task_pids(cprm->pid, current)` for initializing the process ID. The original code was pretty straightforward: get the task group ID and register it. Now, it seems like we're pulling multiple PID types at once. This change impacts how the `cprm->pid` field is populated before `pidfs_coredump(cprm)` is called. The original code only assigned the TGID but the change assigns both the PID and TGID.

It seems the thinking here is to optimize and avoid unnecessary calls. The comment mentions both `cprm->pid[PIDTYPE_PID]` and `cprm->pid[PIDTYPE_TGID]` are initialized by this new `task_pids` function. And hey, even if `cprm->pid` were somehow NULL, a NULL pointer dereference should be caught by the standard crash handlers or KASAN builds, so we're covered there. This isn't a KMSAN issue.

But, hold on... what about the code path that uses `pidfs_coredump_pid`? Specifically, these lines:

```c
	attr->coredump_signal = cprm->siginfo->si_signo;
	attr->coredump_code = cprm->siginfo->si_code;
```

Are we potentially leaking sensitive signal information to user space through `attr`? I need to know where `attr` gets read and copied to user space to assess any potential security implications. Let's see... I need to search for references to `attr->coredump_signal` or, perhaps, the related `PIDFD_INFO_COREDUMP` constant, or maybe even `pidfs_info` in the surrounding code. Gotta trace where `attr` is used after these assignments. This could be a vulnerability, a potential information leak. Gotta check this out.



72/3 2026/10/05 15:21 tool grepper
0m Args:
{
  "Expression": "PIDFD_INFO_COREDUMP",
  "PathPrefix": "fs/pidfs.c"
}

Results:
{
  "Output": "fs/pidfs.c=339=static __u32 pidfs_coredump_mask(enum task_dumpable dumpable)\n--\nfs/pidfs.c-359-\t\t\t      PIDFD_INFO_EXIT | \\\nfs/pidfs.c:360:\t\t\t      PIDFD_INFO_COREDUMP | \\\nfs/pidfs.c-361-\t\t\t      PIDFD_INFO_SUPPORTED_MASK | \\\nfs/pidfs.c:362:\t\t\t      PIDFD_INFO_COREDUMP_SIGNAL | \\\nfs/pidfs.c:363:\t\t\t      PIDFD_INFO_COREDUMP_CODE)\nfs/pidfs.c-364-\nfs/pidfs.c=365=static long pidfd_info(struct file *file, unsigned int cmd, unsigned long arg)\n--\nfs/pidfs.c-406-\nfs/pidfs.c:407:\tif (mask \u0026 PIDFD_INFO_COREDUMP) {\nfs/pidfs.c-408-\t\tif (test_bit(PIDFS_ATTR_BIT_COREDUMP, \u0026attr-\u003eattr_mask)) {\nfs/pidfs.c-409-\t\t\tsmp_rmb();\nfs/pidfs.c:410:\t\t\tkinfo.mask |= PIDFD_INFO_COREDUMP | PIDFD_INFO_COREDUMP_SIGNAL | PIDFD_INFO_COREDUMP_CODE;\nfs/pidfs.c-411-\t\t\tkinfo.coredump_mask = attr-\u003ecoredump_mask;\n--\nfs/pidfs.c-432-\nfs/pidfs.c:433:\tif ((mask \u0026 PIDFD_INFO_COREDUMP) \u0026\u0026 !kinfo.coredump_mask) {\nfs/pidfs.c-434-\t\tkinfo.coredump_mask = pidfs_coredump_mask(task_exec_state_get_dumpable(task));\nfs/pidfs.c:435:\t\tkinfo.mask |= PIDFD_INFO_COREDUMP;\nfs/pidfs.c-436-\t\t/* No coredump actually took place, so no coredump signal. */\n"
}

73/3 2026/10/05 15:21 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 60648
							output: 34
							thoughts: 28
						

Thoughts:
Okay, let's dive into this. Here's my take on those lines of code:

**Diving Deep into `pidfd_info` in `fs/pidfs.c`**

Alright, time to get my hands dirty. I'm going to focus my attention specifically on the `pidfd_info` function, specifically lines 365 through 450 in `fs/pidfs.c`. This is where the magic (or the potential for problems) usually happens when dealing with PID file descriptors. I want to understand exactly how the system is gathering and presenting information about a process, and this function is likely a critical piece of that puzzle.

I'll be looking closely at how it handles the various data structures and interactions with the kernel. I'm keen to examine the logic flow, identifying any potential performance bottlenecks or edge cases that might be overlooked. I'll be paying attention to how it deals with the various states a process can be in. Specifically I'll be looking for any potential race conditions between a process's state and the retrieval of information. Are there any checks for process termination while the info is being read? Any deadlocks? Any subtle concurrency issues?

I also plan to dissect the error handling meticulously. I need to understand how the function handles cases where the PID file descriptor is invalid, the process has exited, or the system encounters some other unexpected condition. Error handling is always a weak point, so I'll really scrutinize that part. Good error handling is vital for robust and stable system software. I want to see how these errors are detected, reported, and handled. I'll also be mindful of any potential security implications, like any information leakage or vulnerabilities related to the retrieval of process information.



74/3 2026/10/05 15:21 tool read-file
0m Args:
{
  "File": "fs/pidfs.c",
  "FirstLine": 365,
  "LineCount": 85
}

Results:
{
  "Contents": " 365:\tstatic long pidfd_info(struct file *file, unsigned int cmd, unsigned long arg)\n 366:\t{\n 367:\t\tstruct pidfd_info __user *uinfo = (struct pidfd_info __user *)arg;\n 368:\t\tstruct task_struct *task __free(put_task) = NULL;\n 369:\t\tstruct pid *pid = pidfd_pid(file);\n 370:\t\tsize_t usize = _IOC_SIZE(cmd);\n 371:\t\tstruct pidfd_info kinfo = {};\n 372:\t\tstruct user_namespace *user_ns;\n 373:\t\tstruct pidfs_attr *attr;\n 374:\t\tconst struct cred *c;\n 375:\t\t__u64 mask;\n 376:\t\n 377:\t\tBUILD_BUG_ON(sizeof(struct pidfd_info) != PIDFD_INFO_SIZE_VER3);\n 378:\t\n 379:\t\tif (!uinfo)\n 380:\t\t\treturn -EINVAL;\n 381:\t\tif (usize \u003c PIDFD_INFO_SIZE_VER0)\n 382:\t\t\treturn -EINVAL; /* First version, no smaller struct possible */\n 383:\t\n 384:\t\tif (copy_from_user(\u0026mask, \u0026uinfo-\u003emask, sizeof(mask)))\n 385:\t\t\treturn -EFAULT;\n 386:\t\n 387:\t\t/*\n 388:\t\t * Restrict information retrieval to tasks within the caller's pid\n 389:\t\t * namespace hierarchy.\n 390:\t\t */\n 391:\t\tif (!pid_in_current_pidns(pid))\n 392:\t\t\treturn -EREMOTE;\n 393:\t\n 394:\t\tattr = READ_ONCE(pid-\u003eattr);\n 395:\t\tif (mask \u0026 PIDFD_INFO_EXIT) {\n 396:\t\t\tif (test_bit(PIDFS_ATTR_BIT_EXIT, \u0026attr-\u003eattr_mask)) {\n 397:\t\t\t\tsmp_rmb();\n 398:\t\t\t\tkinfo.mask |= PIDFD_INFO_EXIT;\n 399:\t#ifdef CONFIG_CGROUPS\n 400:\t\t\t\tkinfo.cgroupid = attr-\u003ecgroupid;\n 401:\t\t\t\tkinfo.mask |= PIDFD_INFO_CGROUPID;\n 402:\t#endif\n 403:\t\t\t\tkinfo.exit_code = attr-\u003eexit_code;\n 404:\t\t\t}\n 405:\t\t}\n 406:\t\n 407:\t\tif (mask \u0026 PIDFD_INFO_COREDUMP) {\n 408:\t\t\tif (test_bit(PIDFS_ATTR_BIT_COREDUMP, \u0026attr-\u003eattr_mask)) {\n 409:\t\t\t\tsmp_rmb();\n 410:\t\t\t\tkinfo.mask |= PIDFD_INFO_COREDUMP | PIDFD_INFO_COREDUMP_SIGNAL | PIDFD_INFO_COREDUMP_CODE;\n 411:\t\t\t\tkinfo.coredump_mask = attr-\u003ecoredump_mask;\n 412:\t\t\t\tkinfo.coredump_signal = attr-\u003ecoredump_signal;\n 413:\t\t\t\tkinfo.coredump_code = attr-\u003ecoredump_code;\n 414:\t\t\t}\n 415:\t\t}\n 416:\t\n 417:\t\ttask = get_pid_task(pid, PIDTYPE_PID);\n 418:\t\tif (!task) {\n 419:\t\t\t/*\n 420:\t\t\t * If the task has already been reaped, only exit\n 421:\t\t\t * information is available\n 422:\t\t\t */\n 423:\t\t\tif (!(mask \u0026 PIDFD_INFO_EXIT))\n 424:\t\t\t\treturn -ESRCH;\n 425:\t\n 426:\t\t\tgoto copy_out;\n 427:\t\t}\n 428:\t\n 429:\t\tc = get_task_cred(task);\n 430:\t\tif (!c)\n 431:\t\t\treturn -ESRCH;\n 432:\t\n 433:\t\tif ((mask \u0026 PIDFD_INFO_COREDUMP) \u0026\u0026 !kinfo.coredump_mask) {\n 434:\t\t\tkinfo.coredump_mask = pidfs_coredump_mask(task_exec_state_get_dumpable(task));\n 435:\t\t\tkinfo.mask |= PIDFD_INFO_COREDUMP;\n 436:\t\t\t/* No coredump actually took place, so no coredump signal. */\n 437:\t\t}\n 438:\t\n 439:\t\t/* Unconditionally return identifiers and credentials, the rest only on request */\n 440:\t\n 441:\t\tuser_ns = current_user_ns();\n 442:\t\tkinfo.ruid = from_kuid_munged(user_ns, c-\u003euid);\n 443:\t\tkinfo.rgid = from_kgid_munged(user_ns, c-\u003egid);\n 444:\t\tkinfo.euid = from_kuid_munged(user_ns, c-\u003eeuid);\n 445:\t\tkinfo.egid = from_kgid_munged(user_ns, c-\u003eegid);\n 446:\t\tkinfo.suid = from_kuid_munged(user_ns, c-\u003esuid);\n 447:\t\tkinfo.sgid = from_kgid_munged(user_ns, c-\u003esgid);\n 448:\t\tkinfo.fsuid = from_kuid_munged(user_ns, c-\u003efsuid);\n 449:\t\tkinfo.fsgid = from_kgid_munged(user_ns, c-\u003efsgid);\n"
}

75/3 2026/10/05 15:21 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 62559
							output: 32
							thoughts: 501
						

Thoughts:
**Analyzing the PID File System Registration Logic**

Okay, so I'm looking at this code and immediately I see `struct pidfd_info kinfo = {};` – right, they're zeroing out the struct before populating the fields. Good practice, nothing to see there, and this patch didn't even touch that part anyway. It just added that helper function `pidfs_coredump_pid`.

Now, let's dig into `pidfs_register_pid` and `__pidfs_register_pids`. I see this loop in `__pidfs_register_pids`:

```c
for (enum pid_type type = PIDTYPE_PID; type <= last; type++) {
	int ret = pidfs_register_pid(pids[type]);

	if (unlikely(ret))
		return ret;
}
```

Wait a second… if `pidfs_register_pid` fails for a `type` *after* PIDTYPE_PID, it immediately returns the error. Hmm, so where is `__pidfs_register_pids` actually *used*? Let's check:

1.  `umh_coredump_setup`: Calls it. Fine, returns the error.
2.  `coredump_sock_connect`: Also calls it. Returns false on error.
3.  `prepare_peercred`: Calls it. Uses `unlikely` and *also* `put_pids`. Interesting.
4.  `unix_maybe_add_creds`: Calls it. Uses `unlikely` and *also* `put_pids`.

Okay, so if the register fails in 3 and 4, we're releasing the PID references. This suggests we're expecting `pidfs_register_pid` to *not* handle NULL PIDs gracefully and that it might possibly have memory allocation or deallocation issues if passed a NULL pid. So, let's make sure we check the code for `pidfs_register_pid` itself to confirm whether this assumption is true.



76/3 2026/10/05 15:21 tool grepper
0m Args:
{
  "Expression": "pidfs_register_pid",
  "PathPrefix": "fs/pidfs.c"
}

Results:
{
  "Output": "fs/pidfs.c=749=void pidfs_exit(struct task_struct *tsk)\n--\nfs/pidfs.c-758-\nfs/pidfs.c:759:\t/* Synchronize with pidfs_register_pid(). */\nfs/pidfs.c-760-\tscoped_guard(spinlock_irq, \u0026pid-\u003ewait_pidfd.lock) {\n--\nfs/pidfs.c=1029=static void pidfs_put_data(void *data)\n--\nfs/pidfs.c-1035-/**\nfs/pidfs.c:1036: * pidfs_register_pid_gfp - register a struct pid in pidfs with custom GFP\nfs/pidfs.c-1037- * flags\n--\nfs/pidfs.c-1044- */\nfs/pidfs.c:1045:int pidfs_register_pid_gfp(struct pid *pid, gfp_t gfp)\nfs/pidfs.c-1046-{\n--\nfs/pidfs.c-1080-/* Register the pids up to pid type @last of @pids in pidfs. */\nfs/pidfs.c:1081:int __pidfs_register_pids(struct pid *const *pids, enum pid_type last)\nfs/pidfs.c-1082-{\n--\nfs/pidfs.c-1086-\tfor (enum pid_type type = PIDTYPE_PID; type \u003c= last; type++) {\nfs/pidfs.c:1087:\t\tint ret = pidfs_register_pid(pids[type]);\nfs/pidfs.c-1088-\n--\nfs/pidfs.c=1096=static struct dentry *pidfs_stash_dentry(struct dentry **stashed,\n--\nfs/pidfs.c-1103-\nfs/pidfs.c:1104:\tret = pidfs_register_pid(pid);\nfs/pidfs.c-1105-\tif (ret)\n"
}

77/3 2026/10/05 15:21 llm kmsan-evaluator
1m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 64061
							output: 35
							thoughts: 24
						

Thoughts:
Okay, let's dive into this. Here's my take on `pidfs_register_pid_gfp` in `fs/pidfs.c`, focusing on what's going on under the hood:

**Diving into pidfs_register_pid_gfp: A Detailed Look**

Right, so I need to understand how PIDs are registered with the pidfs filesystem, and specifically, the memory allocation aspect represented by the `gfp` flag. Looking at `pidfs_register_pid_gfp`, I'm expecting to see a fundamental interaction with the kernel's memory management. This function is the gateway, it's where the kernel begins to manage a PID.

I'll be looking for how the function handles the allocation of data structures to hold information about the PID. This likely involves some form of `kmalloc` or `kzalloc`, and I'll pay close attention to the `gfp` flags passed to these allocation calls. The flags are critical; they determine where the memory comes from and what the kernel can do if the allocation fails (e.g., whether the function can sleep, swap, etc). This could have implications for responsiveness and deadlock potential.

I anticipate seeing error handling. What happens if the memory allocation fails? How are resources cleaned up if the PID registration can't proceed? Are there retry mechanisms or is the registration simply aborted? Error checking is key to the stability of the PID namespace, so I'll be scrutinizing this part.

I'll also want to understand the concurrency aspects. Is this function protected by any locks? Does it utilize atomic operations to prevent race conditions during PID registration? I'm anticipating that this is a critical section of code and so it requires careful synchronization to prevent corruption of the PID structure.

Finally, I'm expecting to see the PID being associated with a specific inode, or a structure within the pidfs filesystem. The function must be managing the link between the kernel's internal PID representation and the representation visible to userspace through the pidfs filesystem. The link between these two entities, the actual PID and the "file", is key. It's likely involved in creating an inode and populating it with the PID-specific information. The interaction with the inode itself is what I'm looking for. Let's see how it's done...



78/3 2026/10/05 15:22 tool read-file
0m Args:
{
  "File": "fs/pidfs.c",
  "FirstLine": 1044,
  "LineCount": 35
}

Results:
{
  "Contents": "1044:\t */\n1045:\tint pidfs_register_pid_gfp(struct pid *pid, gfp_t gfp)\n1046:\t{\n1047:\t\tstruct pidfs_attr *new_attr __free(kfree) = NULL;\n1048:\t\tstruct pidfs_attr *attr;\n1049:\t\n1050:\t\tmight_sleep();\n1051:\t\n1052:\t\tif (!pid)\n1053:\t\t\treturn 0;\n1054:\t\n1055:\t\tattr = READ_ONCE(pid-\u003eattr);\n1056:\t\tif (unlikely(attr == PIDFS_PID_DEAD))\n1057:\t\t\treturn PTR_ERR(PIDFS_PID_DEAD);\n1058:\t\tif (attr)\n1059:\t\t\treturn 0;\n1060:\t\n1061:\t\tnew_attr = kmem_cache_zalloc(pidfs_attr_cachep, gfp);\n1062:\t\tif (!new_attr)\n1063:\t\t\treturn -ENOMEM;\n1064:\t\n1065:\t\tINIT_LIST_HEAD_RCU(\u0026new_attr-\u003exattrs);\n1066:\t\n1067:\t\t/* Synchronize with pidfs_exit(). */\n1068:\t\tguard(spinlock_irq)(\u0026pid-\u003ewait_pidfd.lock);\n1069:\t\n1070:\t\tattr = pid-\u003eattr;\n1071:\t\tif (unlikely(attr == PIDFS_PID_DEAD))\n1072:\t\t\treturn PTR_ERR(PIDFS_PID_DEAD);\n1073:\t\tif (unlikely(attr))\n1074:\t\t\treturn 0;\n1075:\t\n1076:\t\tpid-\u003eattr = no_free_ptr(new_attr);\n1077:\t\treturn 0;\n1078:\t}\n"
}

79/3 2026/10/05 15:22 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 65117
							output: 27
							thoughts: 99
						

Thoughts:
Here's my thought process, as if I were analyzing this code:

**Analyzing `pidfd` Initialization and Preparation**

Okay, let's dissect this. First, I'm checking out `pidfs_register_pid_gfp`. The immediate standout is that crucial null pointer check: `if (!pid) return 0;`. Excellent! That's a solid, defensive programming practice. Prevents a segfault if we somehow end up with a null `pid`. It's good to see that handled upfront. And, given that `new_attr` is being allocated with `kmem_cache_zalloc`, I'm comfortable that the data is zero-initialized. No garbage data lurking in uninitialized memory here, which can save a lot of debugging headaches down the road.

Now, I'm shifting my focus to `pidfd_prepare`. I need to figure out where that function is defined. Let me just grep for `pidfd_prepare` to locate its declaration. Gotta trace that call path.



80/3 2026/10/05 15:22 tool grepper
0m Args:
{
  "Expression": "pidfd_prepare\\(",
  "PathPrefix": "kernel/"
}

Results:
{
  "Output": "kernel/fork.c=1863=static inline void rcu_copy_process(struct task_struct *p)\n--\nkernel/fork.c-1908- */\nkernel/fork.c:1909:int pidfd_prepare(struct pid *pid, unsigned int flags, struct file **ret_file)\nkernel/fork.c-1910-{\n--\nkernel/fork.c=2013=__latent_entropy struct task_struct *copy_process(\n--\nkernel/fork.c-2351-\t\t */\nkernel/fork.c:2352:\t\tretval = pidfd_prepare(pid, flags, \u0026pidfile);\nkernel/fork.c-2353-\t\tif (retval \u003c 0)\n--\nkernel/pid.c=671=static int pidfd_create(struct pid *pid, unsigned int flags)\n--\nkernel/pid.c-675-\nkernel/pid.c:676:\tpidfd = pidfd_prepare(pid, flags, \u0026pidfd_file);\nkernel/pid.c-677-\tif (pidfd \u003c 0)\n"
}

81/3 2026/10/05 15:22 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 65745
							output: 34
							thoughts: 18
						

Thoughts:
Okay, let's dive into this. Here's my take on understanding `pidfd_prepare` in `kernel/fork.c`:

**Deep Dive into pidfd_prepare in kernel/fork.c**

Alright, I'm heading into `kernel/fork.c` to understand this `pidfd_prepare` function. My initial thought is, given its name, it's likely responsible for setting up or preparing something related to pidfds during a `fork` operation. The use of "prepare" suggests this is a setup stage, not the primary cloning or child process creation itself. I'm guessing that the function might involve allocating or initializing structures specific to the pidfd mechanism, handling potential error conditions, and perhaps validating or checking parameters before the main `fork` actions kick in. It's probably a crucial part of integrating pidfds smoothly into the process creation pipeline. I anticipate that it might interact closely with the overall process structure, perhaps modifying or checking fields within the task structure, or perhaps using helper functions that handle all the complex operations needed for pidfds. I expect to see interactions with the system call arguments, checking for invalid flags or arguments, and handling the allocation of relevant data structures. This is going to be interesting!



82/3 2026/10/05 15:22 tool read-file
0m Args:
{
  "File": "kernel/fork.c",
  "FirstLine": 1905,
  "LineCount": 45
}

Results:
{
  "Contents": "1905:\t *         pidfd file is returned in the last argument to the function. On\n1906:\t *         error, a negative error code is returned from the function and the\n1907:\t *         last argument remains unchanged.\n1908:\t */\n1909:\tint pidfd_prepare(struct pid *pid, unsigned int flags, struct file **ret_file)\n1910:\t{\n1911:\t\tstruct file *pidfs_file;\n1912:\t\n1913:\t\t/*\n1914:\t\t * PIDFD_STALE is only allowed to be passed if the caller knows\n1915:\t\t * that @pid is already registered in pidfs and thus\n1916:\t\t * PIDFD_INFO_EXIT information is guaranteed to be available.\n1917:\t\t */\n1918:\t\tif (!(flags \u0026 PIDFD_STALE)) {\n1919:\t\t\t/*\n1920:\t\t\t * While holding the pidfd waitqueue lock removing the\n1921:\t\t\t * task linkage for the thread-group leader pid\n1922:\t\t\t * (PIDTYPE_TGID) isn't possible. Thus, if there's still\n1923:\t\t\t * task linkage for PIDTYPE_PID not having thread-group\n1924:\t\t\t * leader linkage for the pid means it wasn't a\n1925:\t\t\t * thread-group leader in the first place.\n1926:\t\t\t */\n1927:\t\t\tguard(spinlock_irq)(\u0026pid-\u003ewait_pidfd.lock);\n1928:\t\n1929:\t\t\t/* Task has already been reaped. */\n1930:\t\t\tif (!pid_has_task(pid, PIDTYPE_PID))\n1931:\t\t\t\treturn -ESRCH;\n1932:\t\t\t/*\n1933:\t\t\t * If this struct pid isn't used as a thread-group\n1934:\t\t\t * leader but the caller requested to create a\n1935:\t\t\t * thread-group leader pidfd then report ENOENT.\n1936:\t\t\t */\n1937:\t\t\tif (!(flags \u0026 PIDFD_THREAD) \u0026\u0026 !pid_has_task(pid, PIDTYPE_TGID))\n1938:\t\t\t\treturn -ENOENT;\n1939:\t\t}\n1940:\t\n1941:\t\tCLASS(get_unused_fd, pidfd)(O_CLOEXEC);\n1942:\t\tif (pidfd \u003c 0)\n1943:\t\t\treturn pidfd;\n1944:\t\n1945:\t\tpidfs_file = pidfs_alloc_file(pid, flags | O_RDWR);\n1946:\t\tif (IS_ERR(pidfs_file))\n1947:\t\t\treturn PTR_ERR(pidfs_file);\n1948:\t\n1949:\t\t*ret_file = pidfs_file;\n"
}

83/3 2026/10/05 15:22 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 66782
							output: 19
							thoughts: 1003
						

Thoughts:
**Analyzing Potential Uninitialized Memory Issues in PIDFD Implementation**

Okay, let's break this down systematically. My primary concern is ensuring that the `pidfd` implementation doesn't introduce any vulnerabilities due to uninitialized memory being read, copied, or passed to userspace.

First, `pidfd_prepare` seems straightforward: it allocates an unused file descriptor, creates a `pidfs_file`, and returns the FD. No issues there, from what I can see.

Now, let's examine the data pathways, specifically the points at which information touches user space or where data is copied. I've identified several key areas:

1.  **`SO_PASSPIDFD_THREAD` Socket Option:** This sets a boolean flag. Simple assignment and retrieval, no immediate concerns.

2.  **`SO_PEERPIDFD_THREAD` Socket Option:** `sk_getsockopt_peerpidfd` copies the `pidfd` (an `int` returned from `pidfd_prepare`) to the user-provided buffer. The length check seems reasonable. No obvious problems here.

3.  **`SCM_PIDFD_THREAD` SCM Message:** This uses `put_cmsg` to send the `pidfd` (again, an `int` from `pidfd_prepare`). The size is specified, which seems safe.

4.  **`unix_peercred`:**  This structure is allocated on the stack with `{}` (zero-initialized) in multiple places: `unix_listen`, `unix_stream_connect`, and `unix_socketpair`. It's populated later using `get_task_pids` and `pidfs_register_pids`. Looks fine.

5.  **`UNIXCB(skb)`:** This part of the `skb` control block is zeroed upon allocation. The relevant fields are filled by either `unix_scm_to_skb` or `unix_maybe_add_creds`. `get_pids` and `put_pids` manage `struct pid *` pointers.

6.  **`coredump_params`:** `cprm` is initialized with `{ ... }` in `vfs_coredump`.  `task_pids` fills in the PID fields. `coredump_sock_connect` checks for PID equality.

I paused to check how `pids_equal` is implemented, and it simply compares pointer values. Nothing there is reading uninitialized memory. That's good.

However, I had a thought – what about `unix_destruct_scm`? This function seems like a potential area of concern. The `scm_cookie` is initialized to `{}` (zero-initialized). Then, `swap_pids` exchanges pointers between `scm.pid` and `UNIXCB(skb).pid`.  `scm_destroy_cred` then calls `put_pids` which sets the pointers in `scm.pid` to `NULL`.  But what about where this `UNIXCB(skb).pid` originally came from? If there's an earlier code path that doesn't initialize it, and if it could potentially be read, that could be a problem.

The code in `scm_destroy_cred` in `net/core/scm.c` also calls `put_pids`, setting the values to `NULL`.

Okay, next thought: are there other places in the kernel code where `sk_peer_pid` is accessed now that it's a `DECLARE_PIDS(sk_peer_pid, PIDTYPE_TGID)` array and not just a single pid? I need to search the entire kernel tree for that variable and see how it's used. Let's fire up `grep`.



84/3 2026/10/05 15:23 tool grepper
0m Args:
{
  "Expression": "sk_peer_pid"
}

Results:
{
  "Output": "fs/coredump.c=672=static bool coredump_sock_connect(struct core_name *cn, struct coredump_params *cprm)\n--\nfs/coredump.c-727-\nfs/coredump.c:728:\t/* ... and validate that @sk_peer_pid matches @cprm.pid. */\nfs/coredump.c:729:\tif (WARN_ON_ONCE(!pids_equal(unix_peer(socket-\u003esk)-\u003esk_peer_pid, cprm-\u003epid)))\nfs/coredump.c-730-\t\treturn false;\n--\ninclude/net/sock.h=242=struct sk_filter;\n--\ninclude/net/sock.h-302-  *\t@sk_protocol: which protocol this socket belongs in this network family\ninclude/net/sock.h:303:  *\t@sk_peer_lock: lock protecting @sk_peer_pid and @sk_peer_cred\ninclude/net/sock.h:304:  *\t@sk_peer_pid: \u0026struct pid for this socket's peer, by pid type\ninclude/net/sock.h-305-  *\t@sk_peer_cred: %SO_PEERCRED setting\n--\ninclude/net/sock.h=366=struct sock {\n--\ninclude/net/sock.h-548-\tint\t\t\tsk_bind_phc;\ninclude/net/sock.h:549:\tDECLARE_PIDS(sk_peer_pid, PIDTYPE_TGID);\ninclude/net/sock.h-550-\tconst struct cred\t*sk_peer_cred;\n--\ninclude/trace/events/landlock.h=994=TRACE_EVENT(landlock_deny_scope_abstract_unix_socket,\n--\ninclude/trace/events/landlock.h-1036-\t\t * peer's AF_UNIX state lock, serializing published peercred\ninclude/trace/events/landlock.h:1037:\t\t * updates.  The peer socket keeps a reference to sk_peer_pid\ninclude/trace/events/landlock.h-1038-\t\t * through pid_nr(); sun_path is the reliable identifier.\ninclude/trace/events/landlock.h-1039-\t\t */\ninclude/trace/events/landlock.h:1040:\t\tpeer_pid\t\t= READ_ONCE(peer-\u003esk_peer_pid[PIDTYPE_TGID]);\ninclude/trace/events/landlock.h-1041-\t\t__entry-\u003epeer_pid\t= peer_pid ? pid_nr(peer_pid) : 0;\n--\nnet/bluetooth/af_bluetooth.c=143=struct sock *bt_sock_alloc(struct net *net, struct socket *sock,\n--\nnet/bluetooth/af_bluetooth.c-163-\t\tspin_lock(\u0026sk-\u003esk_peer_lock);\nnet/bluetooth/af_bluetooth.c:164:\t\tsk-\u003esk_peer_pid[PIDTYPE_TGID]  = get_pid(task_tgid(current));\nnet/bluetooth/af_bluetooth.c-165-\t\tsk-\u003esk_peer_cred = get_current_cred();\n--\nnet/bluetooth/af_bluetooth.c=211=void bt_accept_enqueue(struct sock *parent, struct sock *sk, bool bh)\n--\nnet/bluetooth/af_bluetooth.c-237-\tspin_lock(\u0026sk-\u003esk_peer_lock);\nnet/bluetooth/af_bluetooth.c:238:\told_pid = sk-\u003esk_peer_pid[PIDTYPE_TGID];\nnet/bluetooth/af_bluetooth.c-239-\told_cred = sk-\u003esk_peer_cred;\nnet/bluetooth/af_bluetooth.c:240:\tsk-\u003esk_peer_pid[PIDTYPE_TGID] = get_pid(parent-\u003esk_peer_pid[PIDTYPE_TGID]);\nnet/bluetooth/af_bluetooth.c-241-\tsk-\u003esk_peer_cred = get_cred(parent-\u003esk_peer_cred);\n--\nnet/bluetooth/hci_sock.c=277=static void hci_sock_copy_creds(struct sock *sk, struct sk_buff *skb)\n--\nnet/bluetooth/hci_sock.c-286-\t/* Check if peer credentials is set */\nnet/bluetooth/hci_sock.c:287:\tif (!sk-\u003esk_peer_pid[PIDTYPE_TGID]) {\nnet/bluetooth/hci_sock.c-288-\t\t/* Check if parent peer credentials is set */\nnet/bluetooth/hci_sock.c:289:\t\tif (bt_sk(sk)-\u003eparent \u0026\u0026 bt_sk(sk)-\u003eparent-\u003esk_peer_pid[PIDTYPE_TGID])\nnet/bluetooth/hci_sock.c-290-\t\t\tsk = bt_sk(sk)-\u003eparent;\n--\nnet/bluetooth/hci_sock.c-295-\t/* Check if scm_creds already set */\nnet/bluetooth/hci_sock.c:296:\tif (creds-\u003epid == pid_vnr(sk-\u003esk_peer_pid[PIDTYPE_TGID]))\nnet/bluetooth/hci_sock.c-297-\t\treturn;\n--\nnet/bluetooth/hci_sock.c-300-\nnet/bluetooth/hci_sock.c:301:\tcreds-\u003epid = pid_vnr(sk-\u003esk_peer_pid[PIDTYPE_TGID]);\nnet/bluetooth/hci_sock.c-302-\tif (sk-\u003esk_peer_cred) {\n--\nnet/bluetooth/l2cap_sock.c=1889=static struct pid *l2cap_sock_get_peer_pid_cb(struct l2cap_chan *chan)\n--\nnet/bluetooth/l2cap_sock.c-1892-\nnet/bluetooth/l2cap_sock.c:1893:\treturn sk-\u003esk_peer_pid[PIDTYPE_TGID];\nnet/bluetooth/l2cap_sock.c-1894-}\n--\nnet/core/sock.c=1740=static int sk_getsockopt_peerpidfd(struct sock *sk, enum pid_type type,\n--\nnet/core/sock.c-1751-\tspin_lock(\u0026sk-\u003esk_peer_lock);\nnet/core/sock.c:1752:\tpeer_pid = get_pid(sk-\u003esk_peer_pid[type]);\nnet/core/sock.c-1753-\tspin_unlock(\u0026sk-\u003esk_peer_lock);\n--\nnet/core/sock.c=1783=int sk_getsockopt(struct sock *sk, int level, int optname,\n--\nnet/core/sock.c-1967-\t\tspin_lock(\u0026sk-\u003esk_peer_lock);\nnet/core/sock.c:1968:\t\tcred_to_ucred(sk-\u003esk_peer_pid[PIDTYPE_TGID], sk-\u003esk_peer_cred,\nnet/core/sock.c-1969-\t\t\t      \u0026peercred);\n--\nnet/core/sock.c=2378=static void __sk_destruct(struct rcu_head *head)\n--\nnet/core/sock.c-2410-\tput_cred(sk-\u003esk_peer_cred);\nnet/core/sock.c:2411:\tput_pids(sk-\u003esk_peer_pid);\nnet/core/sock.c-2412-\n--\nnet/core/sock.c=3781=void sock_init_data_uid(struct socket *sock, struct sock *sk, kuid_t uid)\n--\nnet/core/sock.c-3815-\nnet/core/sock.c:3816:\tmemset(sk-\u003esk_peer_pid, 0, sizeof(sk-\u003esk_peer_pid));\nnet/core/sock.c-3817-\tsk-\u003esk_peer_cred\t=\tNULL;\n--\nnet/unix/af_unix.c=770=static inline void init_peercred(struct sock *sk,\n--\nnet/unix/af_unix.c-772-{\nnet/unix/af_unix.c:773:\tmemcpy(sk-\u003esk_peer_pid, peercred-\u003epeer_pid, sizeof(sk-\u003esk_peer_pid));\nnet/unix/af_unix.c-774-\tsk-\u003esk_peer_cred = peercred-\u003epeer_cred;\n--\nnet/unix/af_unix.c=777=static void update_peercred(struct sock *sk, struct unix_peercred *peercred)\n--\nnet/unix/af_unix.c-781-\tspin_lock(\u0026sk-\u003esk_peer_lock);\nnet/unix/af_unix.c:782:\tswap_pids(sk-\u003esk_peer_pid, peercred-\u003epeer_pid);\nnet/unix/af_unix.c-783-\told_cred = sk-\u003esk_peer_cred;\n--\nnet/unix/af_unix.c=790=static void copy_peercred(struct sock *sk, struct sock *peersk)\n--\nnet/unix/af_unix.c-794-\tspin_lock(\u0026sk-\u003esk_peer_lock);\nnet/unix/af_unix.c:795:\tget_pids(sk-\u003esk_peer_pid, peersk-\u003esk_peer_pid);\nnet/unix/af_unix.c-796-\tsk-\u003esk_peer_cred = get_cred(peersk-\u003esk_peer_cred);\n"
}

85/3 2026/10/05 15:23 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 70981
							output: 21
							thoughts: 53
						

Thoughts:
Here's my summary, as the thought process unfolds:

**Kernel PID Updates: A Careful Audit**

Okay, so I've just finished going through the entire kernel source, and I'm pretty confident that every single instance of `sk_peer_pid` has been updated correctly. That's a huge relief, but you *never* assume anything in this game. Time for a verification pass.

My next step is to use grep to look for `scm->pid` and anything related to `UNIXCB.*pid`. This will cover any other related structures and fields. The goal is to make absolutely sure I haven't missed a single place where process IDs are handled, especially within the context of inter-process communication. It's easy to overlook things, and a thorough check is paramount. This will ensure no rogue, outdated code is lingering with old, incorrect pid handling.



86/3 2026/10/05 15:23 tool grepper
0m Args:
{
  "Expression": "\\bscm-\u003epid\\b"
}

Results:
{
  "Output": "include/net/scm.h=77=static __inline__ void scm_destroy_cred(struct scm_cookie *scm)\ninclude/net/scm.h-78-{\ninclude/net/scm.h:79:\tput_pids(scm-\u003epid);\ninclude/net/scm.h-80-}\n--\ninclude/net/scm.h=89=static __inline__ int scm_send(struct socket *sock, struct msghdr *msg,\n--\ninclude/net/scm.h-95-\tif (forcecreds) {\ninclude/net/scm.h:96:\t\tscm-\u003epid[PIDTYPE_TGID] = get_pid(task_tgid(current));\ninclude/net/scm.h-97-\t\tscm_set_cred(scm, task_tgid(current), current_uid(), current_gid());\n--\nnet/core/scm.c=150=static inline int scm_replace_pid(struct scm_cookie *scm, struct pid *pid)\n--\nnet/core/scm.c-171-\nnet/core/scm.c:172:\tscm-\u003epid[PIDTYPE_TGID] = pid;\nnet/core/scm.c:173:\tscm-\u003epid[PIDTYPE_PID] = get_pid(thread_pid);\nnet/core/scm.c-174-\tscm-\u003ecreds.pid = pid_vnr(pid);\n--\nnet/core/scm.c=502=static void scm_pidfd_recv(struct msghdr *msg, struct scm_cookie *scm,\n--\nnet/core/scm.c-509-\nnet/core/scm.c:510:\tpid = scm-\u003epid[type];\nnet/core/scm.c-511-\n--\nnet/unix/af_unix.c=1988=static int unix_scm_to_skb(struct scm_cookie *scm, struct sk_buff *skb, bool send_fds)\n--\nnet/unix/af_unix.c-1991-\nnet/unix/af_unix.c:1992:\tget_pids(UNIXCB(skb).pid, scm-\u003epid);\nnet/unix/af_unix.c-1993-\tUNIXCB(skb).uid = scm-\u003ecreds.uid;\n--\nnet/unix/af_unix.c=2004=static void unix_skb_to_scm(struct sk_buff *skb, struct scm_cookie *scm)\nnet/unix/af_unix.c-2005-{\nnet/unix/af_unix.c:2006:\tget_pids(scm-\u003epid, UNIXCB(skb).pid);\nnet/unix/af_unix.c-2007-\tscm_set_cred(scm, UNIXCB(skb).pid[PIDTYPE_TGID], UNIXCB(skb).uid, UNIXCB(skb).gid);\n--\nnet/unix/af_unix.c=2047=static bool unix_skb_scm_eq(const struct sock *sk, struct sk_buff *skb,\n--\nnet/unix/af_unix.c-2051-\tif (sk-\u003esk_scm_pidfd_thread \u0026\u0026\nnet/unix/af_unix.c:2052:\t    UNIXCB(skb).pid[PIDTYPE_PID] != scm-\u003epid[PIDTYPE_PID])\nnet/unix/af_unix.c-2053-\t\treturn false;\nnet/unix/af_unix.c-2054-\nnet/unix/af_unix.c:2055:\treturn UNIXCB(skb).pid[PIDTYPE_TGID] == scm-\u003epid[PIDTYPE_TGID] \u0026\u0026\nnet/unix/af_unix.c-2056-\t       uid_eq(UNIXCB(skb).uid, scm-\u003ecreds.uid) \u0026\u0026\n"
}

87/3 2026/10/05 15:23 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 72218
							output: 22
							thoughts: 25
						

Thoughts:
Okay, here's my take on those thoughts, tailored for an expert audience, presented in the first person:

**Checking PID Consistency**

Alright, so I've just been through and updated every instance of `scm->pid`. Now, the important thing is to ensure this change is consistently applied throughout the system. My immediate next step is to verify that any related data or dependencies are also updated. I'm going to quickly check any instances of `UNIXCB.*pid` to confirm that those references, whatever they represent in this context, have also been correctly addressed. It's crucial to cross-reference these identifiers now to maintain data integrity and prevent any potential discrepancies or regressions. This is a standard and critical check after a potentially impactful variable update like this. It's a quick sanity test to ensure nothing was missed during the initial change.



88/3 2026/10/05 15:23 tool grepper
0m Args:
{
  "Expression": "UNIXCB.*\\.pid\\b"
}

Results:
{
  "Output": "net/unix/af_unix.c=1970=static void unix_destruct_scm(struct sk_buff *skb)\n--\nnet/unix/af_unix.c-1973-\nnet/unix/af_unix.c:1974:\tswap_pids(scm.pid, UNIXCB(skb).pid);\nnet/unix/af_unix.c-1975-\n--\nnet/unix/af_unix.c=1988=static int unix_scm_to_skb(struct scm_cookie *scm, struct sk_buff *skb, bool send_fds)\n--\nnet/unix/af_unix.c-1991-\nnet/unix/af_unix.c:1992:\tget_pids(UNIXCB(skb).pid, scm-\u003epid);\nnet/unix/af_unix.c-1993-\tUNIXCB(skb).uid = scm-\u003ecreds.uid;\n--\nnet/unix/af_unix.c=2004=static void unix_skb_to_scm(struct sk_buff *skb, struct scm_cookie *scm)\nnet/unix/af_unix.c-2005-{\nnet/unix/af_unix.c:2006:\tget_pids(scm-\u003epid, UNIXCB(skb).pid);\nnet/unix/af_unix.c:2007:\tscm_set_cred(scm, UNIXCB(skb).pid[PIDTYPE_TGID], UNIXCB(skb).uid, UNIXCB(skb).gid);\nnet/unix/af_unix.c-2008-\tunix_set_secdata(scm, skb);\n--\nnet/unix/af_unix.c=2024=static int unix_maybe_add_creds(struct sk_buff *skb, const struct sock *sk,\n--\nnet/unix/af_unix.c-2026-{\nnet/unix/af_unix.c:2027:\tif (UNIXCB(skb).pid[PIDTYPE_TGID])\nnet/unix/af_unix.c-2028-\t\treturn 0;\n--\nnet/unix/af_unix.c-2033-\nnet/unix/af_unix.c:2034:\t\tget_task_pids(UNIXCB(skb).pid, current);\nnet/unix/af_unix.c:2035:\t\terr = pidfs_register_pids(UNIXCB(skb).pid);\nnet/unix/af_unix.c-2036-\t\tif (unlikely(err)) {\nnet/unix/af_unix.c:2037:\t\t\tput_pids(UNIXCB(skb).pid);\nnet/unix/af_unix.c-2038-\t\t\treturn err;\n--\nnet/unix/af_unix.c=2047=static bool unix_skb_scm_eq(const struct sock *sk, struct sk_buff *skb,\n--\nnet/unix/af_unix.c-2051-\tif (sk-\u003esk_scm_pidfd_thread \u0026\u0026\nnet/unix/af_unix.c:2052:\t    UNIXCB(skb).pid[PIDTYPE_PID] != scm-\u003epid[PIDTYPE_PID])\nnet/unix/af_unix.c-2053-\t\treturn false;\nnet/unix/af_unix.c-2054-\nnet/unix/af_unix.c:2055:\treturn UNIXCB(skb).pid[PIDTYPE_TGID] == scm-\u003epid[PIDTYPE_TGID] \u0026\u0026\nnet/unix/af_unix.c-2056-\t       uid_eq(UNIXCB(skb).uid, scm-\u003ecreds.uid) \u0026\u0026\n"
}

89/3 2026/10/05 15:23 llm kmsan-evaluator
0m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 73391
							output: 29
							thoughts: 16
						

Thoughts:
Okay, here's my interpretation of that thought process, tailored for an expert audience, and presented as if it were my own internal monologue:

**Checking Process IDs (PIDs)**

Alright, so I'm trying to diagnose something here, and the likely culprit is related to process management. I need to get some PIDs to verify a hypothesis. Hmm, given the context, I think the `cprm` structure is the most likely source of the process ID I need... but then again, it could be in the `cp` structure. I'll need to check the process ID in one or the other to verify my thoughts. Let's start by looking at `cprm->pid` and `cp->pid` and get this sorted out.



90/3 2026/10/05 15:23 tool grepper
0m Args:
{
  "Expression": "\\bcprm-\u003epid\\b|\\bcp-\u003epid\\b"
}

Results:
{
  "Output": "fs/coredump.c=239=static bool coredump_parse(struct core_name *cn, struct coredump_params *cprm,\n--\nfs/coredump.c-456-\t\t\t\t */\nfs/coredump.c:457:\t\t\t\ttask_pids(cprm-\u003epid, current);\nfs/coredump.c-458-\t\t\t\terr = cn_printf(cn, \"%d\", COREDUMP_PIDFD_NUMBER);\n--\nfs/coredump.c=623=static int umh_coredump_setup(struct subprocess_info *info, struct cred *new)\n--\nfs/coredump.c-628-\nfs/coredump.c:629:\tif (cp-\u003epid[PIDTYPE_TGID]) {\nfs/coredump.c-630-\t\tstruct file *pidfs_file __free(fput) = NULL;\nfs/coredump.c-631-\nfs/coredump.c:632:\t\tpidfs_file = pidfs_alloc_file(cp-\u003epid[PIDTYPE_TGID], 0);\nfs/coredump.c-633-\t\tif (IS_ERR(pidfs_file))\n--\nfs/coredump.c-635-\nfs/coredump.c:636:\t\terr = pidfs_register_pids(cp-\u003epid);\nfs/coredump.c-637-\t\tif (err)\n--\nfs/coredump.c=672=static bool coredump_sock_connect(struct core_name *cn, struct coredump_params *cprm)\n--\nfs/coredump.c-705-\t */\nfs/coredump.c:706:\ttask_pids(cprm-\u003epid, current);\nfs/coredump.c:707:\tretval = pidfs_register_pids(cprm-\u003epid);\nfs/coredump.c-708-\tif (retval)\n--\nfs/coredump.c-728-\t/* ... and validate that @sk_peer_pid matches @cprm.pid. */\nfs/coredump.c:729:\tif (WARN_ON_ONCE(!pids_equal(unix_peer(socket-\u003esk)-\u003esk_peer_pid, cprm-\u003epid)))\nfs/coredump.c-730-\t\treturn false;\n--\nfs/pidfs.c=818=void pidfs_coredump(const struct coredump_params *cprm)\n--\nfs/pidfs.c-820-\t/* The dumping thread's pidfd reports the coredump as well. */\nfs/pidfs.c:821:\tfor (enum pid_type type = PIDTYPE_PID; type \u003c= pids_last(cprm-\u003epid); type++)\nfs/pidfs.c:822:\t\tpidfs_coredump_pid(cprm-\u003epid[type], cprm);\nfs/pidfs.c-823-}\n"
}

91/3 2026/10/05 15:23 llm kmsan-evaluator
1m Model:
gemini-3.8-flash

Args:
null

Results:
null

Tokens:
							input: 74324
							output: 557
							thoughts: 1118
						

Thoughts:
**KMSAN Assessment: No Uninitialized Memory Risks Detected**

Alright, let's break this down. My goal is to determine if this patch introduces any risks related to uninitialized memory being accessed by user space. Everything is meticulously tracked and assessed.

First, I checked for any uninitialized memory risks. I systematically investigated the four criteria that trigger the `NeedsKMSAN=true` flag.

1.  **Data sent to user space:** I carefully examined all code paths where kernel structures are exposed to user space (copy\_to\_user, put\_user, netlink, ioctl, etc.). Specifically, in `sk_getsockopt_peerpidfd`, the patch copies a scalar `int` (`pidfd`) and its length (`len`, which is also an `int`). In `sk_getsockopt`, `v` is zeroed via `memset` before use, and in `scm_pidfd_recv`, `put_cmsg` only writes a scalar `int pidfd`. There are no structures containing potentially uninitialized fields or padding passed to user space. This is good.

2.  **Conditional logic depending on uninitialized data:** Next, I reviewed all conditional logic and branching to ensure no decisions are made based on uninitialized variables. `scm_cookie` is zeroed before use in all relevant functions, `unix_peercred` is initialized with `= {}`, `coredump_params` is initialized with `= { ... }`, and `sk_peer_pid` is zeroed by relevant functions. No conditional logic depends on uninitialized data. Perfect.

3.  **Complex data structure allocation/initialization:** I looked at all new complex data structures and ensured that all fields are initialized before being read. The `DECLARE_PIDS` helper and the related arrays are initialized to zero (via `memset`, `= {}`, or `kzalloc`/`skbuff_clear`) before being populated or read. The array accesses are also bounds checked. No issues.

4.  **Buffer bounds and length issues:** I verified that the patch doesn't introduce any weakened bounds checks or logic that might allow surrounding code to access uninitialized bytes. This seems safe.

After this thorough review, I also considered other potential issues. I looked at potential refcounting bugs and error handling but quickly realized that these would be caught by KASAN or standard debuggers (refcount\_t, kasan, kmemleak) and would not be relevant to KMSAN.

Therefore, `NeedsKMSAN` should be `false`.

Now, for a more detailed explanation:

*   I distinguished between KASAN and KMSAN. KMSAN detects uninitialized memory reads (stack, heap, or page allocations) and information leaks to user space. KASAN and standard debuggers handle memory corruptions, UAF, refcounting issues, out-of-bounds accesses, NULL pointer dereferences, and locking bugs.
*   I carefully looked at the copied data for the new `SO_PASSPIDFD_THREAD`, `SO_PEERPIDFD_THREAD`, and `SCM_PIDFD_THREAD` features. The copied data consists of a scalar `int` (`pidfd`) and its length (`len`). In `sk_getsockopt`, `v` is zeroed before use, and in `scm_pidfd_recv`, `put_cmsg` writes the integer `pidfd`.
*   I confirmed that the new `DECLARE_PIDS` helper arrays in related structures are always initialized to zero before being populated or read.
*   I confirmed that all branching and helper functions (e.g., `__get_pids`, `__put_pids`) operate on well-defined array bounds up to `last` and do not depend on uninitialized fields.
*   I concluded that issues such as PID refcounting in error paths or socket state handling are strictly standard debugging territory and not a concern for KMSAN.

With this solid understanding, I'm confident that a dedicated KMSAN fuzzing session isn't warranted. I will now set the final results.



92/3 2026/10/05 15:25 tool set-results
0m Args:
{
  "KMSANReasoning": "The patch series extends PIDFD passing across AF_UNIX sockets and coredump logic to support thread-level pidfds (SO_PASSPIDFD_THREAD, SO_PEERPIDFD_THREAD, SCM_PIDFD_THREAD) using a new DECLARE_PIDS macro and associated helper functions.\n\nAnalysis of KMSAN vs KASAN applicability:\n1. Kernel data copied to user space:\n   - In `sk_getsockopt_peerpidfd`, the pidfd integer and length are scalars fully initialized prior to `copy_to_sockptr`.\n   - In `sk_getsockopt`, the union `v` is zero-initialized via `memset(\u0026v, 0, sizeof(v))` before writing `v.val = sk-\u003esk_scm_pidfd_thread`.\n   - In `scm_pidfd_recv`, `put_cmsg` writes a single scalar `int pidfd` prepared by `pidfd_prepare`.\n   - There are no uninitialized struct paddings or data buffers exposed to userspace.\n\n2. Structure allocation and initialization:\n   - `struct unix_peercred` instances are zero-initialized via `= {}`.\n   - `struct scm_cookie` instances are zero-initialized via `memset` or `= {}` in all code paths (`scm_send`, `unix_stream_read_generic`, `unix_dgram_recvmsg`, `unix_destruct_scm`).\n   - `struct unix_skb_parms` resides in `skb-\u003ecb`, which is zeroed on skb allocation.\n   - `sk-\u003esk_peer_pid` is explicitly zeroed via `memset` in `sock_init_data_uid` and zero-allocated in `sk_alloc`.\n   - `struct coredump_params` is zero-initialized at declaration in `vfs_coredump`.\n\n3. Branching and control flow:\n   - All helper macros (`get_pids`, `put_pids`, `swap_pids`, `pids_equal`, `task_pids`) operate on bounds bounded strictly by compile-time checks (`pids_last()`), and no branches depend on uninitialized values.\n\nAny risks introduced by this patch primarily concern struct pid refcounting, error unwinding, or socket state, which are detected by KASAN, refcount_t sanity checks, and kmemleak. A dedicated KMSAN fuzzing session is not warranted.",
  "NeedsKMSAN": false
}

Results:
{
  "KMSANReasoning": "The patch series extends PIDFD passing across AF_UNIX sockets and coredump logic to support thread-level pidfds (SO_PASSPIDFD_THREAD, SO_PEERPIDFD_THREAD, SCM_PIDFD_THREAD) using a new DECLARE_PIDS macro and associated helper functions.\n\nAnalysis of KMSAN vs KASAN applicability:\n1. Kernel data copied to user space:\n   - In `sk_getsockopt_peerpidfd`, the pidfd integer and length are scalars fully initialized prior to `copy_to_sockptr`.\n   - In `sk_getsockopt`, the union `v` is zero-initialized via `memset(\u0026v, 0, sizeof(v))` before writing `v.val = sk-\u003esk_scm_pidfd_thread`.\n   - In `scm_pidfd_recv`, `put_cmsg` writes a single scalar `int pidfd` prepared by `pidfd_prepare`.\n   - There are no uninitialized struct paddings or data buffers exposed to userspace.\n\n2. Structure allocation and initialization:\n   - `struct unix_peercred` instances are zero-initialized via `= {}`.\n   - `struct scm_cookie` instances are zero-initialized via `memset` or `= {}` in all code paths (`scm_send`, `unix_stream_read_generic`, `unix_dgram_recvmsg`, `unix_destruct_scm`).\n   - `struct unix_skb_parms` resides in `skb-\u003ecb`, which is zeroed on skb allocation.\n   - `sk-\u003esk_peer_pid` is explicitly zeroed via `memset` in `sock_init_data_uid` and zero-allocated in `sk_alloc`.\n   - `struct coredump_params` is zero-initialized at declaration in `vfs_coredump`.\n\n3. Branching and control flow:\n   - All helper macros (`get_pids`, `put_pids`, `swap_pids`, `pids_equal`, `task_pids`) operate on bounds bounded strictly by compile-time checks (`pids_last()`), and no branches depend on uninitialized values.\n\nAny risks introduced by this patch primarily concern struct pid refcounting, error unwinding, or socket state, which are detected by KASAN, refcount_t sanity checks, and kmemleak. A dedicated KMSAN fuzzing session is not warranted.",
  "NeedsKMSAN": false
}

LLM Calls Summary:
Total Calls Total Tokens Avg Tokens Total Duration (Seconds) Avg Duration (Seconds)
Tool Calls Summary:
Total Calls Total Duration (Seconds) Avg Duration (Seconds)