net: add SO_PASSPIDFD_THREAD to get a thread-specific SCM_PIDFD

Currently, SCM_PIDFD carries a pidfd for the thread-group leader. A
broker or the coredump server cannot learn the identity of the specific
thread that sent a given message. Now that both struct pids are recorded
a receiver can ask for the specific identity it needs.

So add SO_PASSPIDFD_THREAD as a sibling of SO_PASSPIDFD.
SO_PASSPIDFD_THREAD delivers an SCM_PIDFD_THREAD, i.e. scoped
to the specific thread sending. This is completely independent of
SO_PASSPIDFD, meaning you can use both options and get both cmsgs.

On SOCK_STREAM sockets recvmsg() only stops merging data at a thread
boundary when the receiver asked for a thread pidfd. For SO_PASSCRED and
SO_PASSPIDFD receivers all threads of one process remain a single
writer.

Co-developed-by: Andrew Halaney <andrew@amutable.com>
Signed-off-by: Andrew Halaney <andrew@amutable.com>
Link: https://patch.msgid.link/20261005-work-unix-passpidfd-v4-3-350183b6e02a@amutable.com
Reviewed-by: Kuniyuki Iwashima <kuniyu@google.com>
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
This commit is contained in:
Christian Brauner 2026-10-05 10:09:33 -05:00
parent c48ae5bcfc
commit be3a5e06b9
No known key found for this signature in database
GPG Key ID: 91C61BC06578DCA2
10 changed files with 58 additions and 14 deletions

View File

@ -157,6 +157,8 @@
#define SO_RIGHTS_NOTRUNC 85
#define SO_PASSPIDFD_THREAD 86
#if !defined(__KERNEL__)
#if __BITS_PER_LONG == 64

View File

@ -168,6 +168,8 @@
#define SO_RIGHTS_NOTRUNC 85
#define SO_PASSPIDFD_THREAD 86
#if !defined(__KERNEL__)
#if __BITS_PER_LONG == 64

View File

@ -149,6 +149,8 @@
#define SO_RIGHTS_NOTRUNC 0x4053
#define SO_PASSPIDFD_THREAD 0x4054
#if !defined(__KERNEL__)
#if __BITS_PER_LONG == 64

View File

@ -150,6 +150,8 @@
#define SO_RIGHTS_NOTRUNC 0x005e
#define SO_PASSPIDFD_THREAD 0x005f
#if !defined(__KERNEL__)

View File

@ -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;

View File

@ -356,6 +356,7 @@ struct sk_filter;
* @sk_scm_security: flagged by SO_PASSSEC to recv SCM_SECURITY
* @sk_scm_pidfd: flagged by SO_PASSPIDFD to recv SCM_PIDFD
* @sk_scm_rights: flagged by SO_PASSRIGHTS to recv SCM_RIGHTS
* @sk_scm_pidfd_thread: flagged by SO_PASSPIDFD_THREAD to recv a thread SCM_PIDFD
* @sk_scm_unused: unused flags for scm_recv()
* @ns_tracker: tracker for netns reference
* @sk_user_frags: xarray of pages the user is holding a reference on.
@ -562,7 +563,8 @@ struct sock {
sk_scm_security : 1,
sk_scm_pidfd : 1,
sk_scm_rights : 1,
sk_scm_unused : 4;
sk_scm_pidfd_thread : 1,
sk_scm_unused : 3;
};
};
u8 sk_clockid;

View File

@ -152,6 +152,8 @@
#define SO_RIGHTS_NOTRUNC 85
#define SO_PASSPIDFD_THREAD 86
#if !defined(__KERNEL__)
#if __BITS_PER_LONG == 64 || (defined(__x86_64__) && defined(__ILP32__))

View File

@ -499,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.
*/
@ -517,12 +522,15 @@ static void scm_pidfd_recv(struct msghdr *msg, struct scm_cookie *scm)
return;
}
if (!scm->pid[PIDTYPE_TGID])
if (!pid)
return;
pidfd = pidfd_prepare(scm->pid[PIDTYPE_TGID], PIDFD_STALE, &pidfd_file);
if (type == PIDTYPE_PID)
flags |= PIDFD_THREAD;
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);
@ -540,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);
@ -587,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);
}

View File

@ -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;
@ -1893,6 +1900,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;

View File

@ -803,7 +803,8 @@ static void copy_peercred(struct sock *sk, struct sock *peersk)
static bool unix_may_passcred(const struct sock *sk)
{
return sk->sk_scm_credentials || sk->sk_scm_pidfd;
return sk->sk_scm_credentials || sk->sk_scm_pidfd ||
sk->sk_scm_pidfd_thread;
}
static int unix_listen(struct socket *sock, int backlog)
@ -2046,9 +2047,14 @@ static int unix_maybe_add_creds(struct sk_buff *skb, const struct sock *sk,
return 0;
}
static bool unix_skb_scm_eq(struct sk_buff *skb,
static bool unix_skb_scm_eq(const struct sock *sk, struct sk_buff *skb,
struct scm_cookie *scm)
{
/* Only a thread pidfd receiver can tell threads of one process apart. */
if (sk->sk_scm_pidfd_thread &&
UNIXCB(skb).pid[PIDTYPE_PID] != scm->pid[PIDTYPE_PID])
return false;
return UNIXCB(skb).pid[PIDTYPE_TGID] == scm->pid[PIDTYPE_TGID] &&
uid_eq(UNIXCB(skb).uid, scm->creds.uid) &&
gid_eq(UNIXCB(skb).gid, scm->creds.gid) &&
@ -3031,7 +3037,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 */