vfs-7.4-rc1.shared.net-next.pidfd

Please consider pulling these changes from the signed vfs-7.4-rc1.shared.net-next.pidfd tag.
 
 Thanks!
 Christian
 -----BEGIN PGP SIGNATURE-----
 
 iHUEABYKAB0WIQRAhzRXHqcMeLMyaSiRxhvAZXjcogUCatHrlwAKCRCRxhvAZXjc
 ooS3AP9/x0Naa93wtIOgcrAYPdb61lcqtH9ktLlKMsi5jhE64wEAiFS7qzGjZMJ4
 H7sq/Qmjt63nyB44LFrHI0z0K62IHAQ=
 =Z9oc
 -----END PGP SIGNATURE-----

Merge tag 'vfs-7.4-rc1.shared.net-next.pidfd' of git://git.kernel.org/pub/scm/linux/kernel/git/vfs/vfs

Pull pidfd updates from Christian Brauner:
 "This adds thread specific pidfds to af_unix sockets.

  SO_PASSPIDFD and SO_PEERPIDFD hand out a pidfd for the thread-group
  leader. A coredump server on the coredump socket can't get a handle on
  the thread that took the signal and is writing the dump, and anything
  doing per-thread authentication has the same problem. The sending and
  the connecting thread are recorded in addition to the thread-group
  leader and sk_peer_pid becomes an array indexed by pid type.

  SO_PASSPIDFD_THREAD works like SO_PASSPIDFD but delivers an
  SCM_PIDFD_THREAD cmsg for the sending thread. Both options are
  independent so a receiver can ask for both. On SOCK_STREAM sockets
  recvmsg() only stops merging data at a thread boundary when the
  receiver asked for a thread pidfd. SO_PEERPIDFD_THREAD returns a pidfd
  for the thread that called connect(), listen() or socketpair(). pidfs
  records the coredump on the dumping thread's struct pid too so its
  pidfd reports it like the leader's does. Selftests for af_unix and the
  coredump socket are included"

* tag 'vfs-7.4-rc1.shared.net-next.pidfd' of git://git.kernel.org/pub/scm/linux/kernel/git/vfs/vfs:
  selftests/coredump: check the dumping thread's pidfd
  pidfs: record the coredump on the dumping thread's pid too
  selftests/net: SO_PEERPIDFD_THREAD
  net: add SO_PEERPIDFD_THREAD to get a thread-specific pidfd
  af_unix: record the pid of the connecting thread
  net: turn sk_peer_pid into an array indexed by pid type
  selftests/net: SO_PASSPIDFD_THREAD
  net: add SO_PASSPIDFD_THREAD to get a thread-specific SCM_PIDFD
  af_unix: record the pid of the sending thread
  pid: add helpers to operate on a struct pid array
This commit is contained in:
Linus Torvalds 2026-10-19 20:32:52 -10:00
commit a6656aefd3
29 changed files with 1136 additions and 113 deletions

View File

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

View File

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

View File

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

View File

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

View File

@ -459,7 +459,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;
}
@ -631,13 +631,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);
/*
@ -709,12 +713,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;
@ -736,7 +740,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;

View File

@ -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)
{

View File

@ -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 <uapi/linux/coredump.h>
#include <asm/siginfo.h>
@ -51,7 +52,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;

View File

@ -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)
{

View File

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

View File

@ -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 */

View File

@ -697,6 +697,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];

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

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

View File

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

View File

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

View File

@ -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__))

View File

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

View File

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

View File

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

View File

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

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;
@ -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;
return sk_getsockopt_peerpidfd(sk, PIDTYPE_TGID, optval, optlen, len);
if (len > sizeof(pidfd))
len = sizeof(pidfd);
case SO_PEERPIDFD_THREAD:
/* Only AF_UNIX records the peer's connecting thread. */
if (!sk_is_unix(sk))
return -EOPNOTSUPP;
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;
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;
}
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);

View File

@ -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 */

View File

@ -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 */

View File

@ -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"),

View File

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

View File

@ -40,6 +40,10 @@
#define COREDUMP_ELFCLASS ELFCLASS32
#endif
#ifndef SO_PEERPIDFD_THREAD
#define SO_PEERPIDFD_THREAD 87
#endif
void *do_nothing(void *arg)
{
(void)arg;
@ -1248,6 +1252,36 @@ ssize_t recv_coredump_bytes(int fd_coredump, int fd_core_file)
return received;
}
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;
@ -1328,6 +1362,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;

View File

@ -39,6 +39,7 @@
/* Shared helper function declarations */
void *do_nothing(void *arg);
void crashing_child(void);
void crashing_child_thread(void);
void crashing_child_sparse(size_t size);
void crashing_child_memory(__u64 task_filter, int fd_addr);
bool find_coredump_segment(int fd, __u64 vaddr, ElfW(Phdr) *segment);
@ -59,6 +60,7 @@ 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);
/* Protocol helper function declarations */

View File

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

View File

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