mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/
synced 2025-04-19 20:58:31 +09:00
vfs-6.15-rc1.pidfs
-----BEGIN PGP SIGNATURE----- iHUEABYKAB0WIQRAhzRXHqcMeLMyaSiRxhvAZXjcogUCZ90pqgAKCRCRxhvAZXjc oqVsAP9Aq/fMCI14HeXehPCezKQZPu1HTrPPo2clLHXoSnafawEAsA3YfWTT4Heb iexzqvAEUOMYOVN66QEc+6AAwtMLrwc= =0eYo -----END PGP SIGNATURE----- Merge tag 'vfs-6.15-rc1.pidfs' of git://git.kernel.org/pub/scm/linux/kernel/git/vfs/vfs Pull vfs pidfs updates from Christian Brauner: - Allow retrieving exit information after a process has been reaped through pidfds via the new PIDFD_INTO_EXIT extension for the PIDFD_GET_INFO ioctl. Various tools need access to information about a process/task even after it has already been reaped. Pidfd polling allows waiting on either task exit or for a task to have been reaped. The contract for PIDFD_INFO_EXIT is simply that EPOLLHUP must be observed before exit information can be retrieved, i.e., exit information is only provided once the task has been reaped and then can be retrieved as long as the pidfd is open. - Add PIDFD_SELF_{THREAD,THREAD_GROUP} sentinels allowing userspace to forgo allocating a file descriptor for their own process. This is useful in scenarios where users want to act on their own process through pidfds and is akin to AT_FDCWD. - Improve premature thread-group leader and subthread exec behavior when polling on pidfds: (1) During a multi-threaded exec by a subthread, i.e., non-thread-group leader thread, all other threads in the thread-group including the thread-group leader are killed and the struct pid of the thread-group leader will be taken over by the subthread that called exec. IOW, two tasks change their TIDs. (2) A premature thread-group leader exit means that the thread-group leader exited before all of the other subthreads in the thread-group have exited. Both cases lead to inconsistencies for pidfd polling with PIDFD_THREAD. Any caller that holds a PIDFD_THREAD pidfd to the current thread-group leader may or may not see an exit notification on the file descriptor depending on when poll is performed. If the poll is performed before the exec of the subthread has concluded an exit notification is generated for the old thread-group leader. If the poll is performed after the exec of the subthread has concluded no exit notification is generated for the old thread-group leader. The correct behavior is to simply not generate an exit notification on the struct pid of a subhthread exec because the struct pid is taken over by the subthread and thus remains alive. But this is difficult to handle because a thread-group may exit premature as mentioned in (2). In that case an exit notification is reliably generated but the subthreads may continue to run for an indeterminate amount of time and thus also may exec at some point. After this pull no exit notifications will be generated for a PIDFD_THREAD pidfd for a thread-group leader until all subthreads have been reaped. If a subthread should exec before no exit notification will be generated until that task exits or it creates subthreads and repeates the cycle. This means an exit notification indicates the ability for the father to reap the child. * tag 'vfs-6.15-rc1.pidfs' of git://git.kernel.org/pub/scm/linux/kernel/git/vfs/vfs: (25 commits) selftests/pidfd: third test for multi-threaded exec polling selftests/pidfd: second test for multi-threaded exec polling selftests/pidfd: first test for multi-threaded exec polling pidfs: improve multi-threaded exec and premature thread-group leader exit polling pidfs: ensure that PIDFS_INFO_EXIT is available selftests/pidfd: add seventh PIDFD_INFO_EXIT selftest selftests/pidfd: add sixth PIDFD_INFO_EXIT selftest selftests/pidfd: add fifth PIDFD_INFO_EXIT selftest selftests/pidfd: add fourth PIDFD_INFO_EXIT selftest selftests/pidfd: add third PIDFD_INFO_EXIT selftest selftests/pidfd: add second PIDFD_INFO_EXIT selftest selftests/pidfd: add first PIDFD_INFO_EXIT selftest selftests/pidfd: expand common pidfd header pidfs/selftests: ensure correct headers for ioctl handling selftests/pidfd: fix header inclusion pidfs: allow to retrieve exit information pidfs: record exit code and cgroupid at exit pidfs: use private inode slab cache pidfs: move setting flags into pidfs_alloc_file() pidfd: rely on automatic cleanup in __pidfd_prepare() ...
This commit is contained in:
commit
df00ded23a
@ -324,6 +324,7 @@ struct stashed_operations {
|
||||
int path_from_stashed(struct dentry **stashed, struct vfsmount *mnt, void *data,
|
||||
struct path *path);
|
||||
void stashed_dentry_prune(struct dentry *dentry);
|
||||
struct dentry *stashed_dentry_get(struct dentry **stashed);
|
||||
/**
|
||||
* path_mounted - check whether path is mounted
|
||||
* @path: path to check
|
||||
|
@ -2113,7 +2113,7 @@ struct timespec64 simple_inode_init_ts(struct inode *inode)
|
||||
}
|
||||
EXPORT_SYMBOL(simple_inode_init_ts);
|
||||
|
||||
static inline struct dentry *get_stashed_dentry(struct dentry **stashed)
|
||||
struct dentry *stashed_dentry_get(struct dentry **stashed)
|
||||
{
|
||||
struct dentry *dentry;
|
||||
|
||||
@ -2215,7 +2215,7 @@ int path_from_stashed(struct dentry **stashed, struct vfsmount *mnt, void *data,
|
||||
const struct stashed_operations *sops = mnt->mnt_sb->s_fs_info;
|
||||
|
||||
/* See if dentry can be reused. */
|
||||
path->dentry = get_stashed_dentry(stashed);
|
||||
path->dentry = stashed_dentry_get(stashed);
|
||||
if (path->dentry) {
|
||||
sops->put_data(data);
|
||||
goto out_path;
|
||||
|
247
fs/pidfs.c
247
fs/pidfs.c
@ -24,6 +24,28 @@
|
||||
#include "internal.h"
|
||||
#include "mount.h"
|
||||
|
||||
static struct kmem_cache *pidfs_cachep __ro_after_init;
|
||||
|
||||
/*
|
||||
* Stashes information that userspace needs to access even after the
|
||||
* process has been reaped.
|
||||
*/
|
||||
struct pidfs_exit_info {
|
||||
__u64 cgroupid;
|
||||
__s32 exit_code;
|
||||
};
|
||||
|
||||
struct pidfs_inode {
|
||||
struct pidfs_exit_info __pei;
|
||||
struct pidfs_exit_info *exit_info;
|
||||
struct inode vfs_inode;
|
||||
};
|
||||
|
||||
static inline struct pidfs_inode *pidfs_i(struct inode *inode)
|
||||
{
|
||||
return container_of(inode, struct pidfs_inode, vfs_inode);
|
||||
}
|
||||
|
||||
static struct rb_root pidfs_ino_tree = RB_ROOT;
|
||||
|
||||
#if BITS_PER_LONG == 32
|
||||
@ -188,36 +210,48 @@ static void pidfd_show_fdinfo(struct seq_file *m, struct file *f)
|
||||
static __poll_t pidfd_poll(struct file *file, struct poll_table_struct *pts)
|
||||
{
|
||||
struct pid *pid = pidfd_pid(file);
|
||||
bool thread = file->f_flags & PIDFD_THREAD;
|
||||
struct task_struct *task;
|
||||
__poll_t poll_flags = 0;
|
||||
|
||||
poll_wait(file, &pid->wait_pidfd, pts);
|
||||
/*
|
||||
* Depending on PIDFD_THREAD, inform pollers when the thread
|
||||
* or the whole thread-group exits.
|
||||
* Don't wake waiters if the thread-group leader exited
|
||||
* prematurely. They either get notified when the last subthread
|
||||
* exits or not at all if one of the remaining subthreads execs
|
||||
* and assumes the struct pid of the old thread-group leader.
|
||||
*/
|
||||
guard(rcu)();
|
||||
task = pid_task(pid, PIDTYPE_PID);
|
||||
if (!task)
|
||||
poll_flags = EPOLLIN | EPOLLRDNORM | EPOLLHUP;
|
||||
else if (task->exit_state && (thread || thread_group_empty(task)))
|
||||
else if (task->exit_state && !delay_group_leader(task))
|
||||
poll_flags = EPOLLIN | EPOLLRDNORM;
|
||||
|
||||
return poll_flags;
|
||||
}
|
||||
|
||||
static long pidfd_info(struct task_struct *task, unsigned int cmd, unsigned long arg)
|
||||
static inline bool pid_in_current_pidns(const struct pid *pid)
|
||||
{
|
||||
const struct pid_namespace *ns = task_active_pid_ns(current);
|
||||
|
||||
if (ns->level <= pid->level)
|
||||
return pid->numbers[ns->level].ns == ns;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static long pidfd_info(struct file *file, unsigned int cmd, unsigned long arg)
|
||||
{
|
||||
struct pidfd_info __user *uinfo = (struct pidfd_info __user *)arg;
|
||||
struct inode *inode = file_inode(file);
|
||||
struct pid *pid = pidfd_pid(file);
|
||||
size_t usize = _IOC_SIZE(cmd);
|
||||
struct pidfd_info kinfo = {};
|
||||
struct pidfs_exit_info *exit_info;
|
||||
struct user_namespace *user_ns;
|
||||
struct task_struct *task;
|
||||
const struct cred *c;
|
||||
__u64 mask;
|
||||
#ifdef CONFIG_CGROUPS
|
||||
struct cgroup *cgrp;
|
||||
#endif
|
||||
|
||||
if (!uinfo)
|
||||
return -EINVAL;
|
||||
@ -227,6 +261,37 @@ static long pidfd_info(struct task_struct *task, unsigned int cmd, unsigned long
|
||||
if (copy_from_user(&mask, &uinfo->mask, sizeof(mask)))
|
||||
return -EFAULT;
|
||||
|
||||
/*
|
||||
* Restrict information retrieval to tasks within the caller's pid
|
||||
* namespace hierarchy.
|
||||
*/
|
||||
if (!pid_in_current_pidns(pid))
|
||||
return -ESRCH;
|
||||
|
||||
if (mask & PIDFD_INFO_EXIT) {
|
||||
exit_info = READ_ONCE(pidfs_i(inode)->exit_info);
|
||||
if (exit_info) {
|
||||
kinfo.mask |= PIDFD_INFO_EXIT;
|
||||
#ifdef CONFIG_CGROUPS
|
||||
kinfo.cgroupid = exit_info->cgroupid;
|
||||
kinfo.mask |= PIDFD_INFO_CGROUPID;
|
||||
#endif
|
||||
kinfo.exit_code = exit_info->exit_code;
|
||||
}
|
||||
}
|
||||
|
||||
task = get_pid_task(pid, PIDTYPE_PID);
|
||||
if (!task) {
|
||||
/*
|
||||
* If the task has already been reaped, only exit
|
||||
* information is available
|
||||
*/
|
||||
if (!(mask & PIDFD_INFO_EXIT))
|
||||
return -ESRCH;
|
||||
|
||||
goto copy_out;
|
||||
}
|
||||
|
||||
c = get_task_cred(task);
|
||||
if (!c)
|
||||
return -ESRCH;
|
||||
@ -246,11 +311,15 @@ static long pidfd_info(struct task_struct *task, unsigned int cmd, unsigned long
|
||||
put_cred(c);
|
||||
|
||||
#ifdef CONFIG_CGROUPS
|
||||
rcu_read_lock();
|
||||
cgrp = task_dfl_cgroup(task);
|
||||
kinfo.cgroupid = cgroup_id(cgrp);
|
||||
kinfo.mask |= PIDFD_INFO_CGROUPID;
|
||||
rcu_read_unlock();
|
||||
if (!kinfo.cgroupid) {
|
||||
struct cgroup *cgrp;
|
||||
|
||||
rcu_read_lock();
|
||||
cgrp = task_dfl_cgroup(task);
|
||||
kinfo.cgroupid = cgroup_id(cgrp);
|
||||
kinfo.mask |= PIDFD_INFO_CGROUPID;
|
||||
rcu_read_unlock();
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
@ -270,16 +339,14 @@ static long pidfd_info(struct task_struct *task, unsigned int cmd, unsigned long
|
||||
if (kinfo.pid == 0 || kinfo.tgid == 0 || (kinfo.ppid == 0 && kinfo.pid != 1))
|
||||
return -ESRCH;
|
||||
|
||||
copy_out:
|
||||
/*
|
||||
* If userspace and the kernel have the same struct size it can just
|
||||
* be copied. If userspace provides an older struct, only the bits that
|
||||
* userspace knows about will be copied. If userspace provides a new
|
||||
* struct, only the bits that the kernel knows about will be copied.
|
||||
*/
|
||||
if (copy_to_user(uinfo, &kinfo, min(usize, sizeof(kinfo))))
|
||||
return -EFAULT;
|
||||
|
||||
return 0;
|
||||
return copy_struct_to_user(uinfo, usize, &kinfo, sizeof(kinfo), NULL);
|
||||
}
|
||||
|
||||
static bool pidfs_ioctl_valid(unsigned int cmd)
|
||||
@ -317,7 +384,6 @@ static long pidfd_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
|
||||
{
|
||||
struct task_struct *task __free(put_task) = NULL;
|
||||
struct nsproxy *nsp __free(put_nsproxy) = NULL;
|
||||
struct pid *pid = pidfd_pid(file);
|
||||
struct ns_common *ns_common = NULL;
|
||||
struct pid_namespace *pid_ns;
|
||||
|
||||
@ -332,13 +398,13 @@ static long pidfd_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
|
||||
return put_user(file_inode(file)->i_generation, argp);
|
||||
}
|
||||
|
||||
task = get_pid_task(pid, PIDTYPE_PID);
|
||||
if (!task)
|
||||
return -ESRCH;
|
||||
|
||||
/* Extensible IOCTL that does not open namespace FDs, take a shortcut */
|
||||
if (_IOC_NR(cmd) == _IOC_NR(PIDFD_GET_INFO))
|
||||
return pidfd_info(task, cmd, arg);
|
||||
return pidfd_info(file, cmd, arg);
|
||||
|
||||
task = get_pid_task(pidfd_pid(file), PIDTYPE_PID);
|
||||
if (!task)
|
||||
return -ESRCH;
|
||||
|
||||
if (arg)
|
||||
return -EINVAL;
|
||||
@ -450,6 +516,49 @@ struct pid *pidfd_pid(const struct file *file)
|
||||
return file_inode(file)->i_private;
|
||||
}
|
||||
|
||||
/*
|
||||
* We're called from release_task(). We know there's at least one
|
||||
* reference to struct pid being held that won't be released until the
|
||||
* task has been reaped which cannot happen until we're out of
|
||||
* release_task().
|
||||
*
|
||||
* If this struct pid is referred to by a pidfd then
|
||||
* stashed_dentry_get() will return the dentry and inode for that struct
|
||||
* pid. Since we've taken a reference on it there's now an additional
|
||||
* reference from the exit path on it. Which is fine. We're going to put
|
||||
* it again in a second and we know that the pid is kept alive anyway.
|
||||
*
|
||||
* Worst case is that we've filled in the info and immediately free the
|
||||
* dentry and inode afterwards since the pidfd has been closed. Since
|
||||
* pidfs_exit() currently is placed after exit_task_work() we know that
|
||||
* it cannot be us aka the exiting task holding a pidfd to ourselves.
|
||||
*/
|
||||
void pidfs_exit(struct task_struct *tsk)
|
||||
{
|
||||
struct dentry *dentry;
|
||||
|
||||
might_sleep();
|
||||
|
||||
dentry = stashed_dentry_get(&task_pid(tsk)->stashed);
|
||||
if (dentry) {
|
||||
struct inode *inode = d_inode(dentry);
|
||||
struct pidfs_exit_info *exit_info = &pidfs_i(inode)->__pei;
|
||||
#ifdef CONFIG_CGROUPS
|
||||
struct cgroup *cgrp;
|
||||
|
||||
rcu_read_lock();
|
||||
cgrp = task_dfl_cgroup(tsk);
|
||||
exit_info->cgroupid = cgroup_id(cgrp);
|
||||
rcu_read_unlock();
|
||||
#endif
|
||||
exit_info->exit_code = tsk->exit_code;
|
||||
|
||||
/* Ensure that PIDFD_GET_INFO sees either all or nothing. */
|
||||
smp_store_release(&pidfs_i(inode)->exit_info, &pidfs_i(inode)->__pei);
|
||||
dput(dentry);
|
||||
}
|
||||
}
|
||||
|
||||
static struct vfsmount *pidfs_mnt __ro_after_init;
|
||||
|
||||
/*
|
||||
@ -505,9 +614,30 @@ static void pidfs_evict_inode(struct inode *inode)
|
||||
put_pid(pid);
|
||||
}
|
||||
|
||||
static struct inode *pidfs_alloc_inode(struct super_block *sb)
|
||||
{
|
||||
struct pidfs_inode *pi;
|
||||
|
||||
pi = alloc_inode_sb(sb, pidfs_cachep, GFP_KERNEL);
|
||||
if (!pi)
|
||||
return NULL;
|
||||
|
||||
memset(&pi->__pei, 0, sizeof(pi->__pei));
|
||||
pi->exit_info = NULL;
|
||||
|
||||
return &pi->vfs_inode;
|
||||
}
|
||||
|
||||
static void pidfs_free_inode(struct inode *inode)
|
||||
{
|
||||
kmem_cache_free(pidfs_cachep, pidfs_i(inode));
|
||||
}
|
||||
|
||||
static const struct super_operations pidfs_sops = {
|
||||
.alloc_inode = pidfs_alloc_inode,
|
||||
.drop_inode = generic_delete_inode,
|
||||
.evict_inode = pidfs_evict_inode,
|
||||
.free_inode = pidfs_free_inode,
|
||||
.statfs = simple_statfs,
|
||||
};
|
||||
|
||||
@ -633,8 +763,49 @@ static int pidfs_export_permission(struct handle_to_path_ctx *ctx,
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline bool pidfs_pid_valid(struct pid *pid, const struct path *path,
|
||||
unsigned int flags)
|
||||
{
|
||||
enum pid_type type;
|
||||
|
||||
if (flags & PIDFD_CLONE)
|
||||
return true;
|
||||
|
||||
/*
|
||||
* Make sure that if a pidfd is created PIDFD_INFO_EXIT
|
||||
* information will be available. So after an inode for the
|
||||
* pidfd has been allocated perform another check that the pid
|
||||
* is still alive. If it is exit information is available even
|
||||
* if the task gets reaped before the pidfd is returned to
|
||||
* userspace. The only exception is PIDFD_CLONE where no task
|
||||
* linkage has been established for @pid yet and the kernel is
|
||||
* in the middle of process creation so there's nothing for
|
||||
* pidfs to miss.
|
||||
*/
|
||||
if (flags & PIDFD_THREAD)
|
||||
type = PIDTYPE_PID;
|
||||
else
|
||||
type = PIDTYPE_TGID;
|
||||
|
||||
/*
|
||||
* Since pidfs_exit() is called before struct pid's task linkage
|
||||
* is removed the case where the task got reaped but a dentry
|
||||
* was already attached to struct pid and exit information was
|
||||
* recorded and published can be handled correctly.
|
||||
*/
|
||||
if (unlikely(!pid_has_task(pid, type))) {
|
||||
struct inode *inode = d_inode(path->dentry);
|
||||
return !!READ_ONCE(pidfs_i(inode)->exit_info);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static struct file *pidfs_export_open(struct path *path, unsigned int oflags)
|
||||
{
|
||||
if (!pidfs_pid_valid(d_inode(path->dentry)->i_private, path, oflags))
|
||||
return ERR_PTR(-ESRCH);
|
||||
|
||||
/*
|
||||
* Clear O_LARGEFILE as open_by_handle_at() forces it and raise
|
||||
* O_RDWR as pidfds always are.
|
||||
@ -698,22 +869,46 @@ static struct file_system_type pidfs_type = {
|
||||
|
||||
struct file *pidfs_alloc_file(struct pid *pid, unsigned int flags)
|
||||
{
|
||||
|
||||
struct file *pidfd_file;
|
||||
struct path path;
|
||||
struct path path __free(path_put) = {};
|
||||
int ret;
|
||||
|
||||
/*
|
||||
* Ensure that PIDFD_CLONE can be passed as a flag without
|
||||
* overloading other uapi pidfd flags.
|
||||
*/
|
||||
BUILD_BUG_ON(PIDFD_CLONE == PIDFD_THREAD);
|
||||
BUILD_BUG_ON(PIDFD_CLONE == PIDFD_NONBLOCK);
|
||||
|
||||
ret = path_from_stashed(&pid->stashed, pidfs_mnt, get_pid(pid), &path);
|
||||
if (ret < 0)
|
||||
return ERR_PTR(ret);
|
||||
|
||||
if (!pidfs_pid_valid(pid, &path, flags))
|
||||
return ERR_PTR(-ESRCH);
|
||||
|
||||
flags &= ~PIDFD_CLONE;
|
||||
pidfd_file = dentry_open(&path, flags, current_cred());
|
||||
path_put(&path);
|
||||
/* Raise PIDFD_THREAD explicitly as do_dentry_open() strips it. */
|
||||
if (!IS_ERR(pidfd_file))
|
||||
pidfd_file->f_flags |= (flags & PIDFD_THREAD);
|
||||
|
||||
return pidfd_file;
|
||||
}
|
||||
|
||||
static void pidfs_inode_init_once(void *data)
|
||||
{
|
||||
struct pidfs_inode *pi = data;
|
||||
|
||||
inode_init_once(&pi->vfs_inode);
|
||||
}
|
||||
|
||||
void __init pidfs_init(void)
|
||||
{
|
||||
pidfs_cachep = kmem_cache_create("pidfs_cache", sizeof(struct pidfs_inode), 0,
|
||||
(SLAB_HWCACHE_ALIGN | SLAB_RECLAIM_ACCOUNT |
|
||||
SLAB_ACCOUNT | SLAB_PANIC),
|
||||
pidfs_inode_init_once);
|
||||
pidfs_mnt = kern_mount(&pidfs_type);
|
||||
if (IS_ERR(pidfs_mnt))
|
||||
panic("Failed to mount pidfs pseudo filesystem");
|
||||
|
@ -6,6 +6,7 @@ struct file *pidfs_alloc_file(struct pid *pid, unsigned int flags);
|
||||
void __init pidfs_init(void);
|
||||
void pidfs_add_pid(struct pid *pid);
|
||||
void pidfs_remove_pid(struct pid *pid);
|
||||
void pidfs_exit(struct task_struct *tsk);
|
||||
extern const struct dentry_operations pidfs_dentry_operations;
|
||||
|
||||
#endif /* _LINUX_PID_FS_H */
|
||||
|
@ -10,6 +10,10 @@
|
||||
/* Flags for pidfd_open(). */
|
||||
#define PIDFD_NONBLOCK O_NONBLOCK
|
||||
#define PIDFD_THREAD O_EXCL
|
||||
#ifdef __KERNEL__
|
||||
#include <linux/sched.h>
|
||||
#define PIDFD_CLONE CLONE_PIDFD
|
||||
#endif
|
||||
|
||||
/* Flags for pidfd_send_signal(). */
|
||||
#define PIDFD_SIGNAL_THREAD (1UL << 0)
|
||||
@ -20,9 +24,34 @@
|
||||
#define PIDFD_INFO_PID (1UL << 0) /* Always returned, even if not requested */
|
||||
#define PIDFD_INFO_CREDS (1UL << 1) /* Always returned, even if not requested */
|
||||
#define PIDFD_INFO_CGROUPID (1UL << 2) /* Always returned if available, even if not requested */
|
||||
#define PIDFD_INFO_EXIT (1UL << 3) /* Only returned if requested. */
|
||||
|
||||
#define PIDFD_INFO_SIZE_VER0 64 /* sizeof first published struct */
|
||||
|
||||
/*
|
||||
* The concept of process and threads in userland and the kernel is a confusing
|
||||
* one - within the kernel every thread is a 'task' with its own individual PID,
|
||||
* however from userland's point of view threads are grouped by a single PID,
|
||||
* which is that of the 'thread group leader', typically the first thread
|
||||
* spawned.
|
||||
*
|
||||
* To cut the Gideon knot, for internal kernel usage, we refer to
|
||||
* PIDFD_SELF_THREAD to refer to the current thread (or task from a kernel
|
||||
* perspective), and PIDFD_SELF_THREAD_GROUP to refer to the current thread
|
||||
* group leader...
|
||||
*/
|
||||
#define PIDFD_SELF_THREAD -10000 /* Current thread. */
|
||||
#define PIDFD_SELF_THREAD_GROUP -20000 /* Current thread group leader. */
|
||||
|
||||
/*
|
||||
* ...and for userland we make life simpler - PIDFD_SELF refers to the current
|
||||
* thread, PIDFD_SELF_PROCESS refers to the process thread group leader.
|
||||
*
|
||||
* For nearly all practical uses, a user will want to use PIDFD_SELF.
|
||||
*/
|
||||
#define PIDFD_SELF PIDFD_SELF_THREAD
|
||||
#define PIDFD_SELF_PROCESS PIDFD_SELF_THREAD_GROUP
|
||||
|
||||
struct pidfd_info {
|
||||
/*
|
||||
* This mask is similar to the request_mask in statx(2).
|
||||
@ -62,7 +91,7 @@ struct pidfd_info {
|
||||
__u32 sgid;
|
||||
__u32 fsuid;
|
||||
__u32 fsgid;
|
||||
__u32 spare0[1];
|
||||
__s32 exit_code;
|
||||
};
|
||||
|
||||
#define PIDFS_IOCTL_MAGIC 0xFF
|
||||
|
@ -69,6 +69,7 @@
|
||||
#include <linux/sysfs.h>
|
||||
#include <linux/user_events.h>
|
||||
#include <linux/uaccess.h>
|
||||
#include <linux/pidfs.h>
|
||||
|
||||
#include <uapi/linux/wait.h>
|
||||
|
||||
@ -249,6 +250,7 @@ repeat:
|
||||
dec_rlimit_ucounts(task_ucounts(p), UCOUNT_RLIMIT_NPROC, 1);
|
||||
rcu_read_unlock();
|
||||
|
||||
pidfs_exit(p);
|
||||
cgroup_release(p);
|
||||
|
||||
write_lock_irq(&tasklist_lock);
|
||||
@ -741,10 +743,10 @@ static void exit_notify(struct task_struct *tsk, int group_dead)
|
||||
|
||||
tsk->exit_state = EXIT_ZOMBIE;
|
||||
/*
|
||||
* sub-thread or delay_group_leader(), wake up the
|
||||
* PIDFD_THREAD waiters.
|
||||
* Ignore thread-group leaders that exited before all
|
||||
* subthreads did.
|
||||
*/
|
||||
if (!thread_group_empty(tsk))
|
||||
if (!delay_group_leader(tsk))
|
||||
do_notify_pidfd(tsk);
|
||||
|
||||
if (unlikely(tsk->ptrace)) {
|
||||
|
@ -2032,25 +2032,18 @@ static inline void rcu_copy_process(struct task_struct *p)
|
||||
*/
|
||||
static int __pidfd_prepare(struct pid *pid, unsigned int flags, struct file **ret)
|
||||
{
|
||||
int pidfd;
|
||||
struct file *pidfd_file;
|
||||
|
||||
pidfd = get_unused_fd_flags(O_CLOEXEC);
|
||||
CLASS(get_unused_fd, pidfd)(O_CLOEXEC);
|
||||
if (pidfd < 0)
|
||||
return pidfd;
|
||||
|
||||
pidfd_file = pidfs_alloc_file(pid, flags | O_RDWR);
|
||||
if (IS_ERR(pidfd_file)) {
|
||||
put_unused_fd(pidfd);
|
||||
if (IS_ERR(pidfd_file))
|
||||
return PTR_ERR(pidfd_file);
|
||||
}
|
||||
/*
|
||||
* anon_inode_getfile() ignores everything outside of the
|
||||
* O_ACCMODE | O_NONBLOCK mask, set PIDFD_THREAD manually.
|
||||
*/
|
||||
pidfd_file->f_flags |= (flags & PIDFD_THREAD);
|
||||
|
||||
*ret = pidfd_file;
|
||||
return pidfd;
|
||||
return take_fd(pidfd);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -2432,8 +2425,11 @@ __latent_entropy struct task_struct *copy_process(
|
||||
if (clone_flags & CLONE_PIDFD) {
|
||||
int flags = (clone_flags & CLONE_THREAD) ? PIDFD_THREAD : 0;
|
||||
|
||||
/* Note that no task has been attached to @pid yet. */
|
||||
retval = __pidfd_prepare(pid, flags, &pidfile);
|
||||
/*
|
||||
* Note that no task has been attached to @pid yet indicate
|
||||
* that via CLONE_PIDFD.
|
||||
*/
|
||||
retval = __pidfd_prepare(pid, flags | PIDFD_CLONE, &pidfile);
|
||||
if (retval < 0)
|
||||
goto bad_fork_free_pid;
|
||||
pidfd = retval;
|
||||
|
24
kernel/pid.c
24
kernel/pid.c
@ -564,15 +564,29 @@ struct pid *pidfd_get_pid(unsigned int fd, unsigned int *flags)
|
||||
*/
|
||||
struct task_struct *pidfd_get_task(int pidfd, unsigned int *flags)
|
||||
{
|
||||
unsigned int f_flags;
|
||||
unsigned int f_flags = 0;
|
||||
struct pid *pid;
|
||||
struct task_struct *task;
|
||||
enum pid_type type;
|
||||
|
||||
pid = pidfd_get_pid(pidfd, &f_flags);
|
||||
if (IS_ERR(pid))
|
||||
return ERR_CAST(pid);
|
||||
switch (pidfd) {
|
||||
case PIDFD_SELF_THREAD:
|
||||
type = PIDTYPE_PID;
|
||||
pid = get_task_pid(current, type);
|
||||
break;
|
||||
case PIDFD_SELF_THREAD_GROUP:
|
||||
type = PIDTYPE_TGID;
|
||||
pid = get_task_pid(current, type);
|
||||
break;
|
||||
default:
|
||||
pid = pidfd_get_pid(pidfd, &f_flags);
|
||||
if (IS_ERR(pid))
|
||||
return ERR_CAST(pid);
|
||||
type = PIDTYPE_TGID;
|
||||
break;
|
||||
}
|
||||
|
||||
task = get_pid_task(pid, PIDTYPE_TGID);
|
||||
task = get_pid_task(pid, type);
|
||||
put_pid(pid);
|
||||
if (!task)
|
||||
return ERR_PTR(-ESRCH);
|
||||
|
120
kernel/signal.c
120
kernel/signal.c
@ -2180,8 +2180,7 @@ bool do_notify_parent(struct task_struct *tsk, int sig)
|
||||
WARN_ON_ONCE(!tsk->ptrace &&
|
||||
(tsk->group_leader != tsk || !thread_group_empty(tsk)));
|
||||
/*
|
||||
* tsk is a group leader and has no threads, wake up the
|
||||
* non-PIDFD_THREAD waiters.
|
||||
* Notify for thread-group leaders without subthreads.
|
||||
*/
|
||||
if (thread_group_empty(tsk))
|
||||
do_notify_pidfd(tsk);
|
||||
@ -4009,56 +4008,12 @@ static struct pid *pidfd_to_pid(const struct file *file)
|
||||
(PIDFD_SIGNAL_THREAD | PIDFD_SIGNAL_THREAD_GROUP | \
|
||||
PIDFD_SIGNAL_PROCESS_GROUP)
|
||||
|
||||
/**
|
||||
* sys_pidfd_send_signal - Signal a process through a pidfd
|
||||
* @pidfd: file descriptor of the process
|
||||
* @sig: signal to send
|
||||
* @info: signal info
|
||||
* @flags: future flags
|
||||
*
|
||||
* Send the signal to the thread group or to the individual thread depending
|
||||
* on PIDFD_THREAD.
|
||||
* In the future extension to @flags may be used to override the default scope
|
||||
* of @pidfd.
|
||||
*
|
||||
* Return: 0 on success, negative errno on failure
|
||||
*/
|
||||
SYSCALL_DEFINE4(pidfd_send_signal, int, pidfd, int, sig,
|
||||
siginfo_t __user *, info, unsigned int, flags)
|
||||
static int do_pidfd_send_signal(struct pid *pid, int sig, enum pid_type type,
|
||||
siginfo_t __user *info, unsigned int flags)
|
||||
{
|
||||
int ret;
|
||||
struct pid *pid;
|
||||
kernel_siginfo_t kinfo;
|
||||
enum pid_type type;
|
||||
|
||||
/* Enforce flags be set to 0 until we add an extension. */
|
||||
if (flags & ~PIDFD_SEND_SIGNAL_FLAGS)
|
||||
return -EINVAL;
|
||||
|
||||
/* Ensure that only a single signal scope determining flag is set. */
|
||||
if (hweight32(flags & PIDFD_SEND_SIGNAL_FLAGS) > 1)
|
||||
return -EINVAL;
|
||||
|
||||
CLASS(fd, f)(pidfd);
|
||||
if (fd_empty(f))
|
||||
return -EBADF;
|
||||
|
||||
/* Is this a pidfd? */
|
||||
pid = pidfd_to_pid(fd_file(f));
|
||||
if (IS_ERR(pid))
|
||||
return PTR_ERR(pid);
|
||||
|
||||
if (!access_pidfd_pidns(pid))
|
||||
return -EINVAL;
|
||||
|
||||
switch (flags) {
|
||||
case 0:
|
||||
/* Infer scope from the type of pidfd. */
|
||||
if (fd_file(f)->f_flags & PIDFD_THREAD)
|
||||
type = PIDTYPE_PID;
|
||||
else
|
||||
type = PIDTYPE_TGID;
|
||||
break;
|
||||
case PIDFD_SIGNAL_THREAD:
|
||||
type = PIDTYPE_PID;
|
||||
break;
|
||||
@ -4071,6 +4026,8 @@ SYSCALL_DEFINE4(pidfd_send_signal, int, pidfd, int, sig,
|
||||
}
|
||||
|
||||
if (info) {
|
||||
int ret;
|
||||
|
||||
ret = copy_siginfo_from_user_any(&kinfo, info);
|
||||
if (unlikely(ret))
|
||||
return ret;
|
||||
@ -4088,8 +4045,71 @@ SYSCALL_DEFINE4(pidfd_send_signal, int, pidfd, int, sig,
|
||||
|
||||
if (type == PIDTYPE_PGID)
|
||||
return kill_pgrp_info(sig, &kinfo, pid);
|
||||
else
|
||||
return kill_pid_info_type(sig, &kinfo, pid, type);
|
||||
|
||||
return kill_pid_info_type(sig, &kinfo, pid, type);
|
||||
}
|
||||
|
||||
/**
|
||||
* sys_pidfd_send_signal - Signal a process through a pidfd
|
||||
* @pidfd: file descriptor of the process
|
||||
* @sig: signal to send
|
||||
* @info: signal info
|
||||
* @flags: future flags
|
||||
*
|
||||
* Send the signal to the thread group or to the individual thread depending
|
||||
* on PIDFD_THREAD.
|
||||
* In the future extension to @flags may be used to override the default scope
|
||||
* of @pidfd.
|
||||
*
|
||||
* Return: 0 on success, negative errno on failure
|
||||
*/
|
||||
SYSCALL_DEFINE4(pidfd_send_signal, int, pidfd, int, sig,
|
||||
siginfo_t __user *, info, unsigned int, flags)
|
||||
{
|
||||
struct pid *pid;
|
||||
enum pid_type type;
|
||||
|
||||
/* Enforce flags be set to 0 until we add an extension. */
|
||||
if (flags & ~PIDFD_SEND_SIGNAL_FLAGS)
|
||||
return -EINVAL;
|
||||
|
||||
/* Ensure that only a single signal scope determining flag is set. */
|
||||
if (hweight32(flags & PIDFD_SEND_SIGNAL_FLAGS) > 1)
|
||||
return -EINVAL;
|
||||
|
||||
switch (pidfd) {
|
||||
case PIDFD_SELF_THREAD:
|
||||
pid = get_task_pid(current, PIDTYPE_PID);
|
||||
type = PIDTYPE_PID;
|
||||
break;
|
||||
case PIDFD_SELF_THREAD_GROUP:
|
||||
pid = get_task_pid(current, PIDTYPE_TGID);
|
||||
type = PIDTYPE_TGID;
|
||||
break;
|
||||
default: {
|
||||
CLASS(fd, f)(pidfd);
|
||||
if (fd_empty(f))
|
||||
return -EBADF;
|
||||
|
||||
/* Is this a pidfd? */
|
||||
pid = pidfd_to_pid(fd_file(f));
|
||||
if (IS_ERR(pid))
|
||||
return PTR_ERR(pid);
|
||||
|
||||
if (!access_pidfd_pidns(pid))
|
||||
return -EINVAL;
|
||||
|
||||
/* Infer scope from the type of pidfd. */
|
||||
if (fd_file(f)->f_flags & PIDFD_THREAD)
|
||||
type = PIDTYPE_PID;
|
||||
else
|
||||
type = PIDTYPE_TGID;
|
||||
|
||||
return do_pidfd_send_signal(pid, sig, type, info, flags);
|
||||
}
|
||||
}
|
||||
|
||||
return do_pidfd_send_signal(pid, sig, type, info, flags);
|
||||
}
|
||||
|
||||
static int
|
||||
|
@ -19,6 +19,8 @@
|
||||
#include <sys/uio.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "../pidfd/pidfd.h"
|
||||
|
||||
/*
|
||||
* Ignore the checkpatch warning, as per the C99 standard, section 7.14.1.1:
|
||||
*
|
||||
@ -50,11 +52,6 @@ static void handle_fatal(int c)
|
||||
siglongjmp(signal_jmp_buf, c);
|
||||
}
|
||||
|
||||
static int pidfd_open(pid_t pid, unsigned int flags)
|
||||
{
|
||||
return syscall(SYS_pidfd_open, pid, flags);
|
||||
}
|
||||
|
||||
static ssize_t sys_process_madvise(int pidfd, const struct iovec *iovec,
|
||||
size_t n, int advice, unsigned int flags)
|
||||
{
|
||||
@ -370,14 +367,10 @@ TEST_F(guard_pages, multi_vma)
|
||||
TEST_F(guard_pages, process_madvise)
|
||||
{
|
||||
const unsigned long page_size = self->page_size;
|
||||
pid_t pid = getpid();
|
||||
int pidfd = pidfd_open(pid, 0);
|
||||
char *ptr_region, *ptr1, *ptr2, *ptr3;
|
||||
ssize_t count;
|
||||
struct iovec vec[6];
|
||||
|
||||
ASSERT_NE(pidfd, -1);
|
||||
|
||||
/* Reserve region to map over. */
|
||||
ptr_region = mmap(NULL, 100 * page_size, PROT_NONE,
|
||||
MAP_ANON | MAP_PRIVATE, -1, 0);
|
||||
@ -425,7 +418,7 @@ TEST_F(guard_pages, process_madvise)
|
||||
ASSERT_EQ(munmap(&ptr_region[99 * page_size], page_size), 0);
|
||||
|
||||
/* Now guard in one step. */
|
||||
count = sys_process_madvise(pidfd, vec, 6, MADV_GUARD_INSTALL, 0);
|
||||
count = sys_process_madvise(PIDFD_SELF, vec, 6, MADV_GUARD_INSTALL, 0);
|
||||
|
||||
/* OK we don't have permission to do this, skip. */
|
||||
if (count == -1 && errno == EPERM)
|
||||
@ -446,7 +439,7 @@ TEST_F(guard_pages, process_madvise)
|
||||
ASSERT_FALSE(try_read_write_buf(&ptr3[19 * page_size]));
|
||||
|
||||
/* Now do the same with unguard... */
|
||||
count = sys_process_madvise(pidfd, vec, 6, MADV_GUARD_REMOVE, 0);
|
||||
count = sys_process_madvise(PIDFD_SELF, vec, 6, MADV_GUARD_REMOVE, 0);
|
||||
|
||||
/* ...and everything should now succeed. */
|
||||
|
||||
@ -463,7 +456,6 @@ TEST_F(guard_pages, process_madvise)
|
||||
ASSERT_EQ(munmap(ptr1, 10 * page_size), 0);
|
||||
ASSERT_EQ(munmap(ptr2, 5 * page_size), 0);
|
||||
ASSERT_EQ(munmap(ptr3, 20 * page_size), 0);
|
||||
close(pidfd);
|
||||
}
|
||||
|
||||
/* Assert that unmapping ranges does not leave guard markers behind. */
|
||||
|
2
tools/testing/selftests/pidfd/.gitignore
vendored
2
tools/testing/selftests/pidfd/.gitignore
vendored
@ -8,3 +8,5 @@ pidfd_getfd_test
|
||||
pidfd_setns_test
|
||||
pidfd_file_handle_test
|
||||
pidfd_bind_mount
|
||||
pidfd_info_test
|
||||
pidfd_exec_helper
|
||||
|
@ -3,7 +3,9 @@ CFLAGS += -g $(KHDR_INCLUDES) -pthread -Wall
|
||||
|
||||
TEST_GEN_PROGS := pidfd_test pidfd_fdinfo_test pidfd_open_test \
|
||||
pidfd_poll_test pidfd_wait pidfd_getfd_test pidfd_setns_test \
|
||||
pidfd_file_handle_test pidfd_bind_mount
|
||||
pidfd_file_handle_test pidfd_bind_mount pidfd_info_test
|
||||
|
||||
TEST_GEN_PROGS_EXTENDED := pidfd_exec_helper
|
||||
|
||||
include ../lib.mk
|
||||
|
||||
|
@ -12,6 +12,7 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <syscall.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
@ -50,6 +51,107 @@
|
||||
#define PIDFD_NONBLOCK O_NONBLOCK
|
||||
#endif
|
||||
|
||||
#ifndef PIDFD_SELF_THREAD
|
||||
#define PIDFD_SELF_THREAD -10000 /* Current thread. */
|
||||
#endif
|
||||
|
||||
#ifndef PIDFD_SELF_THREAD_GROUP
|
||||
#define PIDFD_SELF_THREAD_GROUP -20000 /* Current thread group leader. */
|
||||
#endif
|
||||
|
||||
#ifndef PIDFD_SELF
|
||||
#define PIDFD_SELF PIDFD_SELF_THREAD
|
||||
#endif
|
||||
|
||||
#ifndef PIDFD_SELF_PROCESS
|
||||
#define PIDFD_SELF_PROCESS PIDFD_SELF_THREAD_GROUP
|
||||
#endif
|
||||
|
||||
#ifndef PIDFS_IOCTL_MAGIC
|
||||
#define PIDFS_IOCTL_MAGIC 0xFF
|
||||
#endif
|
||||
|
||||
#ifndef PIDFD_GET_CGROUP_NAMESPACE
|
||||
#define PIDFD_GET_CGROUP_NAMESPACE _IO(PIDFS_IOCTL_MAGIC, 1)
|
||||
#endif
|
||||
|
||||
#ifndef PIDFD_GET_IPC_NAMESPACE
|
||||
#define PIDFD_GET_IPC_NAMESPACE _IO(PIDFS_IOCTL_MAGIC, 2)
|
||||
#endif
|
||||
|
||||
#ifndef PIDFD_GET_MNT_NAMESPACE
|
||||
#define PIDFD_GET_MNT_NAMESPACE _IO(PIDFS_IOCTL_MAGIC, 3)
|
||||
#endif
|
||||
|
||||
#ifndef PIDFD_GET_NET_NAMESPACE
|
||||
#define PIDFD_GET_NET_NAMESPACE _IO(PIDFS_IOCTL_MAGIC, 4)
|
||||
#endif
|
||||
|
||||
#ifndef PIDFD_GET_PID_NAMESPACE
|
||||
#define PIDFD_GET_PID_NAMESPACE _IO(PIDFS_IOCTL_MAGIC, 5)
|
||||
#endif
|
||||
|
||||
#ifndef PIDFD_GET_PID_FOR_CHILDREN_NAMESPACE
|
||||
#define PIDFD_GET_PID_FOR_CHILDREN_NAMESPACE _IO(PIDFS_IOCTL_MAGIC, 6)
|
||||
#endif
|
||||
|
||||
#ifndef PIDFD_GET_TIME_NAMESPACE
|
||||
#define PIDFD_GET_TIME_NAMESPACE _IO(PIDFS_IOCTL_MAGIC, 7)
|
||||
#endif
|
||||
|
||||
#ifndef PIDFD_GET_TIME_FOR_CHILDREN_NAMESPACE
|
||||
#define PIDFD_GET_TIME_FOR_CHILDREN_NAMESPACE _IO(PIDFS_IOCTL_MAGIC, 8)
|
||||
#endif
|
||||
|
||||
#ifndef PIDFD_GET_USER_NAMESPACE
|
||||
#define PIDFD_GET_USER_NAMESPACE _IO(PIDFS_IOCTL_MAGIC, 9)
|
||||
#endif
|
||||
|
||||
#ifndef PIDFD_GET_UTS_NAMESPACE
|
||||
#define PIDFD_GET_UTS_NAMESPACE _IO(PIDFS_IOCTL_MAGIC, 10)
|
||||
#endif
|
||||
|
||||
#ifndef PIDFD_GET_INFO
|
||||
#define PIDFD_GET_INFO _IOWR(PIDFS_IOCTL_MAGIC, 11, struct pidfd_info)
|
||||
#endif
|
||||
|
||||
#ifndef PIDFD_INFO_PID
|
||||
#define PIDFD_INFO_PID (1UL << 0) /* Always returned, even if not requested */
|
||||
#endif
|
||||
|
||||
#ifndef PIDFD_INFO_CREDS
|
||||
#define PIDFD_INFO_CREDS (1UL << 1) /* Always returned, even if not requested */
|
||||
#endif
|
||||
|
||||
#ifndef PIDFD_INFO_CGROUPID
|
||||
#define PIDFD_INFO_CGROUPID (1UL << 2) /* Always returned if available, even if not requested */
|
||||
#endif
|
||||
|
||||
#ifndef PIDFD_INFO_EXIT
|
||||
#define PIDFD_INFO_EXIT (1UL << 3) /* Always returned if available, even if not requested */
|
||||
#endif
|
||||
|
||||
#ifndef PIDFD_THREAD
|
||||
#define PIDFD_THREAD O_EXCL
|
||||
#endif
|
||||
|
||||
struct pidfd_info {
|
||||
__u64 mask;
|
||||
__u64 cgroupid;
|
||||
__u32 pid;
|
||||
__u32 tgid;
|
||||
__u32 ppid;
|
||||
__u32 ruid;
|
||||
__u32 rgid;
|
||||
__u32 euid;
|
||||
__u32 egid;
|
||||
__u32 suid;
|
||||
__u32 sgid;
|
||||
__u32 fsuid;
|
||||
__u32 fsgid;
|
||||
__s32 exit_code;
|
||||
};
|
||||
|
||||
/*
|
||||
* The kernel reserves 300 pids via RESERVED_PIDS in kernel/pid.c
|
||||
* That means, when it wraps around any pid < 300 will be skipped.
|
||||
@ -152,4 +254,11 @@ static inline ssize_t write_nointr(int fd, const void *buf, size_t count)
|
||||
return ret;
|
||||
}
|
||||
|
||||
static inline int sys_execveat(int dirfd, const char *pathname,
|
||||
char *const argv[], char *const envp[],
|
||||
int flags)
|
||||
{
|
||||
return syscall(__NR_execveat, dirfd, pathname, argv, envp, flags);
|
||||
}
|
||||
|
||||
#endif /* __PIDFD_H */
|
||||
|
12
tools/testing/selftests/pidfd/pidfd_exec_helper.c
Normal file
12
tools/testing/selftests/pidfd/pidfd_exec_helper.c
Normal file
@ -0,0 +1,12 @@
|
||||
#define _GNU_SOURCE
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
if (pause())
|
||||
_exit(EXIT_FAILURE);
|
||||
|
||||
_exit(EXIT_SUCCESS);
|
||||
}
|
@ -13,6 +13,7 @@
|
||||
#include <syscall.h>
|
||||
#include <sys/wait.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/mount.h>
|
||||
|
||||
#include "pidfd.h"
|
||||
#include "../kselftest.h"
|
||||
|
692
tools/testing/selftests/pidfd/pidfd_info_test.c
Normal file
692
tools/testing/selftests/pidfd/pidfd_info_test.c
Normal file
@ -0,0 +1,692 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
#define _GNU_SOURCE
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <limits.h>
|
||||
#include <linux/types.h>
|
||||
#include <poll.h>
|
||||
#include <pthread.h>
|
||||
#include <sched.h>
|
||||
#include <signal.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <syscall.h>
|
||||
#include <sys/prctl.h>
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/socket.h>
|
||||
#include <linux/kcmp.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include "pidfd.h"
|
||||
#include "../kselftest_harness.h"
|
||||
|
||||
FIXTURE(pidfd_info)
|
||||
{
|
||||
pid_t child_pid1;
|
||||
int child_pidfd1;
|
||||
|
||||
pid_t child_pid2;
|
||||
int child_pidfd2;
|
||||
|
||||
pid_t child_pid3;
|
||||
int child_pidfd3;
|
||||
|
||||
pid_t child_pid4;
|
||||
int child_pidfd4;
|
||||
};
|
||||
|
||||
FIXTURE_SETUP(pidfd_info)
|
||||
{
|
||||
int ret;
|
||||
int ipc_sockets[2];
|
||||
char c;
|
||||
|
||||
ret = socketpair(AF_LOCAL, SOCK_STREAM | SOCK_CLOEXEC, 0, ipc_sockets);
|
||||
EXPECT_EQ(ret, 0);
|
||||
|
||||
self->child_pid1 = create_child(&self->child_pidfd1, 0);
|
||||
EXPECT_GE(self->child_pid1, 0);
|
||||
|
||||
if (self->child_pid1 == 0) {
|
||||
close(ipc_sockets[0]);
|
||||
|
||||
if (write_nointr(ipc_sockets[1], "1", 1) < 0)
|
||||
_exit(EXIT_FAILURE);
|
||||
|
||||
close(ipc_sockets[1]);
|
||||
|
||||
pause();
|
||||
_exit(EXIT_SUCCESS);
|
||||
}
|
||||
|
||||
EXPECT_EQ(close(ipc_sockets[1]), 0);
|
||||
ASSERT_EQ(read_nointr(ipc_sockets[0], &c, 1), 1);
|
||||
EXPECT_EQ(close(ipc_sockets[0]), 0);
|
||||
|
||||
/* SIGKILL but don't reap. */
|
||||
EXPECT_EQ(sys_pidfd_send_signal(self->child_pidfd1, SIGKILL, NULL, 0), 0);
|
||||
|
||||
ret = socketpair(AF_LOCAL, SOCK_STREAM | SOCK_CLOEXEC, 0, ipc_sockets);
|
||||
EXPECT_EQ(ret, 0);
|
||||
|
||||
self->child_pid2 = create_child(&self->child_pidfd2, 0);
|
||||
EXPECT_GE(self->child_pid2, 0);
|
||||
|
||||
if (self->child_pid2 == 0) {
|
||||
close(ipc_sockets[0]);
|
||||
|
||||
if (write_nointr(ipc_sockets[1], "1", 1) < 0)
|
||||
_exit(EXIT_FAILURE);
|
||||
|
||||
close(ipc_sockets[1]);
|
||||
|
||||
pause();
|
||||
_exit(EXIT_SUCCESS);
|
||||
}
|
||||
|
||||
EXPECT_EQ(close(ipc_sockets[1]), 0);
|
||||
ASSERT_EQ(read_nointr(ipc_sockets[0], &c, 1), 1);
|
||||
EXPECT_EQ(close(ipc_sockets[0]), 0);
|
||||
|
||||
/* SIGKILL and reap. */
|
||||
EXPECT_EQ(sys_pidfd_send_signal(self->child_pidfd2, SIGKILL, NULL, 0), 0);
|
||||
EXPECT_EQ(sys_waitid(P_PID, self->child_pid2, NULL, WEXITED), 0);
|
||||
|
||||
self->child_pid3 = create_child(&self->child_pidfd3, CLONE_NEWUSER | CLONE_NEWPID);
|
||||
EXPECT_GE(self->child_pid3, 0);
|
||||
|
||||
if (self->child_pid3 == 0)
|
||||
_exit(EXIT_SUCCESS);
|
||||
|
||||
self->child_pid4 = create_child(&self->child_pidfd4, CLONE_NEWUSER | CLONE_NEWPID);
|
||||
EXPECT_GE(self->child_pid4, 0);
|
||||
|
||||
if (self->child_pid4 == 0)
|
||||
_exit(EXIT_SUCCESS);
|
||||
|
||||
EXPECT_EQ(sys_waitid(P_PID, self->child_pid4, NULL, WEXITED), 0);
|
||||
}
|
||||
|
||||
FIXTURE_TEARDOWN(pidfd_info)
|
||||
{
|
||||
sys_pidfd_send_signal(self->child_pidfd1, SIGKILL, NULL, 0);
|
||||
if (self->child_pidfd1 >= 0)
|
||||
EXPECT_EQ(0, close(self->child_pidfd1));
|
||||
|
||||
sys_waitid(P_PID, self->child_pid1, NULL, WEXITED);
|
||||
|
||||
sys_pidfd_send_signal(self->child_pidfd2, SIGKILL, NULL, 0);
|
||||
if (self->child_pidfd2 >= 0)
|
||||
EXPECT_EQ(0, close(self->child_pidfd2));
|
||||
|
||||
sys_waitid(P_PID, self->child_pid2, NULL, WEXITED);
|
||||
sys_waitid(P_PID, self->child_pid3, NULL, WEXITED);
|
||||
sys_waitid(P_PID, self->child_pid4, NULL, WEXITED);
|
||||
}
|
||||
|
||||
TEST_F(pidfd_info, sigkill_exit)
|
||||
{
|
||||
struct pidfd_info info = {
|
||||
.mask = PIDFD_INFO_CGROUPID,
|
||||
};
|
||||
|
||||
/* Process has exited but not been reaped so this must work. */
|
||||
ASSERT_EQ(ioctl(self->child_pidfd1, PIDFD_GET_INFO, &info), 0);
|
||||
|
||||
info.mask = PIDFD_INFO_CGROUPID | PIDFD_INFO_EXIT;
|
||||
ASSERT_EQ(ioctl(self->child_pidfd1, PIDFD_GET_INFO, &info), 0);
|
||||
ASSERT_TRUE(!!(info.mask & PIDFD_INFO_CREDS));
|
||||
/* Process has exited but not been reaped, so no PIDFD_INFO_EXIT information yet. */
|
||||
ASSERT_FALSE(!!(info.mask & PIDFD_INFO_EXIT));
|
||||
}
|
||||
|
||||
TEST_F(pidfd_info, sigkill_reaped)
|
||||
{
|
||||
struct pidfd_info info = {
|
||||
.mask = PIDFD_INFO_CGROUPID,
|
||||
};
|
||||
|
||||
/* Process has already been reaped and PIDFD_INFO_EXIT hasn't been set. */
|
||||
ASSERT_NE(ioctl(self->child_pidfd2, PIDFD_GET_INFO, &info), 0);
|
||||
ASSERT_EQ(errno, ESRCH);
|
||||
|
||||
info.mask = PIDFD_INFO_CGROUPID | PIDFD_INFO_EXIT;
|
||||
ASSERT_EQ(ioctl(self->child_pidfd2, PIDFD_GET_INFO, &info), 0);
|
||||
ASSERT_FALSE(!!(info.mask & PIDFD_INFO_CREDS));
|
||||
ASSERT_TRUE(!!(info.mask & PIDFD_INFO_EXIT));
|
||||
ASSERT_TRUE(WIFSIGNALED(info.exit_code));
|
||||
ASSERT_EQ(WTERMSIG(info.exit_code), SIGKILL);
|
||||
}
|
||||
|
||||
TEST_F(pidfd_info, success_exit)
|
||||
{
|
||||
struct pidfd_info info = {
|
||||
.mask = PIDFD_INFO_CGROUPID,
|
||||
};
|
||||
|
||||
/* Process has exited but not been reaped so this must work. */
|
||||
ASSERT_EQ(ioctl(self->child_pidfd3, PIDFD_GET_INFO, &info), 0);
|
||||
|
||||
info.mask = PIDFD_INFO_CGROUPID | PIDFD_INFO_EXIT;
|
||||
ASSERT_EQ(ioctl(self->child_pidfd3, PIDFD_GET_INFO, &info), 0);
|
||||
ASSERT_TRUE(!!(info.mask & PIDFD_INFO_CREDS));
|
||||
/* Process has exited but not been reaped, so no PIDFD_INFO_EXIT information yet. */
|
||||
ASSERT_FALSE(!!(info.mask & PIDFD_INFO_EXIT));
|
||||
}
|
||||
|
||||
TEST_F(pidfd_info, success_reaped)
|
||||
{
|
||||
struct pidfd_info info = {
|
||||
.mask = PIDFD_INFO_CGROUPID,
|
||||
};
|
||||
|
||||
/* Process has already been reaped and PIDFD_INFO_EXIT hasn't been set. */
|
||||
ASSERT_NE(ioctl(self->child_pidfd4, PIDFD_GET_INFO, &info), 0);
|
||||
ASSERT_EQ(errno, ESRCH);
|
||||
|
||||
info.mask = PIDFD_INFO_CGROUPID | PIDFD_INFO_EXIT;
|
||||
ASSERT_EQ(ioctl(self->child_pidfd4, PIDFD_GET_INFO, &info), 0);
|
||||
ASSERT_FALSE(!!(info.mask & PIDFD_INFO_CREDS));
|
||||
ASSERT_TRUE(!!(info.mask & PIDFD_INFO_EXIT));
|
||||
ASSERT_TRUE(WIFEXITED(info.exit_code));
|
||||
ASSERT_EQ(WEXITSTATUS(info.exit_code), 0);
|
||||
}
|
||||
|
||||
TEST_F(pidfd_info, success_reaped_poll)
|
||||
{
|
||||
struct pidfd_info info = {
|
||||
.mask = PIDFD_INFO_CGROUPID | PIDFD_INFO_EXIT,
|
||||
};
|
||||
struct pollfd fds = {};
|
||||
int nevents;
|
||||
|
||||
fds.events = POLLIN;
|
||||
fds.fd = self->child_pidfd2;
|
||||
|
||||
nevents = poll(&fds, 1, -1);
|
||||
ASSERT_EQ(nevents, 1);
|
||||
ASSERT_TRUE(!!(fds.revents & POLLIN));
|
||||
ASSERT_TRUE(!!(fds.revents & POLLHUP));
|
||||
|
||||
ASSERT_EQ(ioctl(self->child_pidfd2, PIDFD_GET_INFO, &info), 0);
|
||||
ASSERT_FALSE(!!(info.mask & PIDFD_INFO_CREDS));
|
||||
ASSERT_TRUE(!!(info.mask & PIDFD_INFO_EXIT));
|
||||
ASSERT_TRUE(WIFSIGNALED(info.exit_code));
|
||||
ASSERT_EQ(WTERMSIG(info.exit_code), SIGKILL);
|
||||
}
|
||||
|
||||
static void *pidfd_info_pause_thread(void *arg)
|
||||
{
|
||||
pid_t pid_thread = gettid();
|
||||
int ipc_socket = *(int *)arg;
|
||||
|
||||
/* Inform the grand-parent what the tid of this thread is. */
|
||||
if (write_nointr(ipc_socket, &pid_thread, sizeof(pid_thread)) != sizeof(pid_thread))
|
||||
return NULL;
|
||||
|
||||
close(ipc_socket);
|
||||
|
||||
/* Sleep untill we're killed. */
|
||||
pause();
|
||||
return NULL;
|
||||
}
|
||||
|
||||
TEST_F(pidfd_info, thread_group)
|
||||
{
|
||||
pid_t pid_leader, pid_poller, pid_thread;
|
||||
pthread_t thread;
|
||||
int nevents, pidfd_leader, pidfd_thread, pidfd_leader_thread, ret;
|
||||
int ipc_sockets[2];
|
||||
struct pollfd fds = {};
|
||||
struct pidfd_info info = {
|
||||
.mask = PIDFD_INFO_CGROUPID | PIDFD_INFO_EXIT,
|
||||
}, info2;
|
||||
|
||||
ret = socketpair(AF_LOCAL, SOCK_STREAM | SOCK_CLOEXEC, 0, ipc_sockets);
|
||||
EXPECT_EQ(ret, 0);
|
||||
|
||||
pid_leader = create_child(&pidfd_leader, 0);
|
||||
EXPECT_GE(pid_leader, 0);
|
||||
|
||||
if (pid_leader == 0) {
|
||||
close(ipc_sockets[0]);
|
||||
|
||||
/* The thread will outlive the thread-group leader. */
|
||||
if (pthread_create(&thread, NULL, pidfd_info_pause_thread, &ipc_sockets[1]))
|
||||
syscall(__NR_exit, EXIT_FAILURE);
|
||||
|
||||
/* Make the thread-group leader exit prematurely. */
|
||||
syscall(__NR_exit, EXIT_SUCCESS);
|
||||
}
|
||||
|
||||
/*
|
||||
* Opening a PIDFD_THREAD aka thread-specific pidfd based on a
|
||||
* thread-group leader must succeed.
|
||||
*/
|
||||
pidfd_leader_thread = sys_pidfd_open(pid_leader, PIDFD_THREAD);
|
||||
ASSERT_GE(pidfd_leader_thread, 0);
|
||||
|
||||
pid_poller = fork();
|
||||
ASSERT_GE(pid_poller, 0);
|
||||
if (pid_poller == 0) {
|
||||
/*
|
||||
* We can't poll and wait for the old thread-group
|
||||
* leader to exit using a thread-specific pidfd. The
|
||||
* thread-group leader exited prematurely and
|
||||
* notification is delayed until all subthreads have
|
||||
* exited.
|
||||
*/
|
||||
fds.events = POLLIN;
|
||||
fds.fd = pidfd_leader_thread;
|
||||
nevents = poll(&fds, 1, 10000 /* wait 5 seconds */);
|
||||
if (nevents != 0)
|
||||
_exit(EXIT_FAILURE);
|
||||
if (fds.revents & POLLIN)
|
||||
_exit(EXIT_FAILURE);
|
||||
if (fds.revents & POLLHUP)
|
||||
_exit(EXIT_FAILURE);
|
||||
_exit(EXIT_SUCCESS);
|
||||
}
|
||||
|
||||
/* Retrieve the tid of the thread. */
|
||||
EXPECT_EQ(close(ipc_sockets[1]), 0);
|
||||
ASSERT_EQ(read_nointr(ipc_sockets[0], &pid_thread, sizeof(pid_thread)), sizeof(pid_thread));
|
||||
EXPECT_EQ(close(ipc_sockets[0]), 0);
|
||||
|
||||
/* Opening a thread as a thread-group leader must fail. */
|
||||
pidfd_thread = sys_pidfd_open(pid_thread, 0);
|
||||
ASSERT_LT(pidfd_thread, 0);
|
||||
|
||||
/* Opening a thread as a PIDFD_THREAD must succeed. */
|
||||
pidfd_thread = sys_pidfd_open(pid_thread, PIDFD_THREAD);
|
||||
ASSERT_GE(pidfd_thread, 0);
|
||||
|
||||
ASSERT_EQ(wait_for_pid(pid_poller), 0);
|
||||
|
||||
/*
|
||||
* Note that pidfd_leader is a thread-group pidfd, so polling on it
|
||||
* would only notify us once all thread in the thread-group have
|
||||
* exited. So we can't poll before we have taken down the whole
|
||||
* thread-group.
|
||||
*/
|
||||
|
||||
/* Get PIDFD_GET_INFO using the thread-group leader pidfd. */
|
||||
ASSERT_EQ(ioctl(pidfd_leader, PIDFD_GET_INFO, &info), 0);
|
||||
ASSERT_TRUE(!!(info.mask & PIDFD_INFO_CREDS));
|
||||
/* Process has exited but not been reaped, so no PIDFD_INFO_EXIT information yet. */
|
||||
ASSERT_FALSE(!!(info.mask & PIDFD_INFO_EXIT));
|
||||
ASSERT_EQ(info.pid, pid_leader);
|
||||
|
||||
/*
|
||||
* Now retrieve the same info using the thread specific pidfd
|
||||
* for the thread-group leader.
|
||||
*/
|
||||
info2.mask = PIDFD_INFO_CGROUPID | PIDFD_INFO_EXIT;
|
||||
ASSERT_EQ(ioctl(pidfd_leader_thread, PIDFD_GET_INFO, &info2), 0);
|
||||
ASSERT_TRUE(!!(info2.mask & PIDFD_INFO_CREDS));
|
||||
/* Process has exited but not been reaped, so no PIDFD_INFO_EXIT information yet. */
|
||||
ASSERT_FALSE(!!(info2.mask & PIDFD_INFO_EXIT));
|
||||
ASSERT_EQ(info2.pid, pid_leader);
|
||||
|
||||
/* Now try the thread-specific pidfd. */
|
||||
ASSERT_EQ(ioctl(pidfd_thread, PIDFD_GET_INFO, &info), 0);
|
||||
ASSERT_TRUE(!!(info.mask & PIDFD_INFO_CREDS));
|
||||
/* The thread hasn't exited, so no PIDFD_INFO_EXIT information yet. */
|
||||
ASSERT_FALSE(!!(info.mask & PIDFD_INFO_EXIT));
|
||||
ASSERT_EQ(info.pid, pid_thread);
|
||||
|
||||
/*
|
||||
* Take down the whole thread-group. The thread-group leader
|
||||
* exited successfully but the thread will now be SIGKILLed.
|
||||
* This must be reflected in the recorded exit information.
|
||||
*/
|
||||
EXPECT_EQ(sys_pidfd_send_signal(pidfd_leader, SIGKILL, NULL, 0), 0);
|
||||
EXPECT_EQ(sys_waitid(P_PIDFD, pidfd_leader, NULL, WEXITED), 0);
|
||||
|
||||
fds.events = POLLIN;
|
||||
fds.fd = pidfd_leader;
|
||||
nevents = poll(&fds, 1, -1);
|
||||
ASSERT_EQ(nevents, 1);
|
||||
ASSERT_TRUE(!!(fds.revents & POLLIN));
|
||||
/* The thread-group leader has been reaped. */
|
||||
ASSERT_TRUE(!!(fds.revents & POLLHUP));
|
||||
|
||||
/*
|
||||
* Retrieve exit information for the thread-group leader via the
|
||||
* thread-group leader pidfd.
|
||||
*/
|
||||
info.mask = PIDFD_INFO_CGROUPID | PIDFD_INFO_EXIT;
|
||||
ASSERT_EQ(ioctl(pidfd_leader, PIDFD_GET_INFO, &info), 0);
|
||||
ASSERT_FALSE(!!(info.mask & PIDFD_INFO_CREDS));
|
||||
ASSERT_TRUE(!!(info.mask & PIDFD_INFO_EXIT));
|
||||
/* The thread-group leader exited successfully. Only the specific thread was SIGKILLed. */
|
||||
ASSERT_TRUE(WIFEXITED(info.exit_code));
|
||||
ASSERT_EQ(WEXITSTATUS(info.exit_code), 0);
|
||||
|
||||
/*
|
||||
* Retrieve exit information for the thread-group leader via the
|
||||
* thread-specific pidfd.
|
||||
*/
|
||||
info2.mask = PIDFD_INFO_CGROUPID | PIDFD_INFO_EXIT;
|
||||
ASSERT_EQ(ioctl(pidfd_leader_thread, PIDFD_GET_INFO, &info2), 0);
|
||||
ASSERT_FALSE(!!(info2.mask & PIDFD_INFO_CREDS));
|
||||
ASSERT_TRUE(!!(info2.mask & PIDFD_INFO_EXIT));
|
||||
|
||||
/* The thread-group leader exited successfully. Only the specific thread was SIGKILLed. */
|
||||
ASSERT_TRUE(WIFEXITED(info2.exit_code));
|
||||
ASSERT_EQ(WEXITSTATUS(info2.exit_code), 0);
|
||||
|
||||
/* Retrieve exit information for the thread. */
|
||||
info.mask = PIDFD_INFO_CGROUPID | PIDFD_INFO_EXIT;
|
||||
ASSERT_EQ(ioctl(pidfd_thread, PIDFD_GET_INFO, &info), 0);
|
||||
ASSERT_FALSE(!!(info.mask & PIDFD_INFO_CREDS));
|
||||
ASSERT_TRUE(!!(info.mask & PIDFD_INFO_EXIT));
|
||||
|
||||
/* The thread got SIGKILLed. */
|
||||
ASSERT_TRUE(WIFSIGNALED(info.exit_code));
|
||||
ASSERT_EQ(WTERMSIG(info.exit_code), SIGKILL);
|
||||
|
||||
EXPECT_EQ(close(pidfd_leader), 0);
|
||||
EXPECT_EQ(close(pidfd_thread), 0);
|
||||
}
|
||||
|
||||
static void *pidfd_info_thread_exec(void *arg)
|
||||
{
|
||||
pid_t pid_thread = gettid();
|
||||
int ipc_socket = *(int *)arg;
|
||||
|
||||
/* Inform the grand-parent what the tid of this thread is. */
|
||||
if (write_nointr(ipc_socket, &pid_thread, sizeof(pid_thread)) != sizeof(pid_thread))
|
||||
return NULL;
|
||||
|
||||
if (read_nointr(ipc_socket, &pid_thread, sizeof(pid_thread)) != sizeof(pid_thread))
|
||||
return NULL;
|
||||
|
||||
close(ipc_socket);
|
||||
|
||||
sys_execveat(AT_FDCWD, "pidfd_exec_helper", NULL, NULL, 0);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
TEST_F(pidfd_info, thread_group_exec)
|
||||
{
|
||||
pid_t pid_leader, pid_poller, pid_thread;
|
||||
pthread_t thread;
|
||||
int nevents, pidfd_leader, pidfd_leader_thread, pidfd_thread, ret;
|
||||
int ipc_sockets[2];
|
||||
struct pollfd fds = {};
|
||||
struct pidfd_info info = {
|
||||
.mask = PIDFD_INFO_CGROUPID | PIDFD_INFO_EXIT,
|
||||
};
|
||||
|
||||
ret = socketpair(AF_LOCAL, SOCK_STREAM | SOCK_CLOEXEC, 0, ipc_sockets);
|
||||
EXPECT_EQ(ret, 0);
|
||||
|
||||
pid_leader = create_child(&pidfd_leader, 0);
|
||||
EXPECT_GE(pid_leader, 0);
|
||||
|
||||
if (pid_leader == 0) {
|
||||
close(ipc_sockets[0]);
|
||||
|
||||
/* The thread will outlive the thread-group leader. */
|
||||
if (pthread_create(&thread, NULL, pidfd_info_thread_exec, &ipc_sockets[1]))
|
||||
syscall(__NR_exit, EXIT_FAILURE);
|
||||
|
||||
/* Make the thread-group leader exit prematurely. */
|
||||
syscall(__NR_exit, EXIT_SUCCESS);
|
||||
}
|
||||
|
||||
/* Open a thread-specific pidfd for the thread-group leader. */
|
||||
pidfd_leader_thread = sys_pidfd_open(pid_leader, PIDFD_THREAD);
|
||||
ASSERT_GE(pidfd_leader_thread, 0);
|
||||
|
||||
pid_poller = fork();
|
||||
ASSERT_GE(pid_poller, 0);
|
||||
if (pid_poller == 0) {
|
||||
/*
|
||||
* We can't poll and wait for the old thread-group
|
||||
* leader to exit using a thread-specific pidfd. The
|
||||
* thread-group leader exited prematurely and
|
||||
* notification is delayed until all subthreads have
|
||||
* exited.
|
||||
*
|
||||
* When the thread has execed it will taken over the old
|
||||
* thread-group leaders struct pid. Calling poll after
|
||||
* the thread execed will thus block again because a new
|
||||
* thread-group has started.
|
||||
*/
|
||||
fds.events = POLLIN;
|
||||
fds.fd = pidfd_leader_thread;
|
||||
nevents = poll(&fds, 1, 10000 /* wait 5 seconds */);
|
||||
if (nevents != 0)
|
||||
_exit(EXIT_FAILURE);
|
||||
if (fds.revents & POLLIN)
|
||||
_exit(EXIT_FAILURE);
|
||||
if (fds.revents & POLLHUP)
|
||||
_exit(EXIT_FAILURE);
|
||||
_exit(EXIT_SUCCESS);
|
||||
}
|
||||
|
||||
/* Retrieve the tid of the thread. */
|
||||
EXPECT_EQ(close(ipc_sockets[1]), 0);
|
||||
ASSERT_EQ(read_nointr(ipc_sockets[0], &pid_thread, sizeof(pid_thread)), sizeof(pid_thread));
|
||||
|
||||
/* Opening a thread as a PIDFD_THREAD must succeed. */
|
||||
pidfd_thread = sys_pidfd_open(pid_thread, PIDFD_THREAD);
|
||||
ASSERT_GE(pidfd_thread, 0);
|
||||
|
||||
/* Now that we've opened a thread-specific pidfd the thread can exec. */
|
||||
ASSERT_EQ(write_nointr(ipc_sockets[0], &pid_thread, sizeof(pid_thread)), sizeof(pid_thread));
|
||||
EXPECT_EQ(close(ipc_sockets[0]), 0);
|
||||
|
||||
ASSERT_EQ(wait_for_pid(pid_poller), 0);
|
||||
|
||||
/* Wait until the kernel has SIGKILLed the thread. */
|
||||
fds.events = POLLHUP;
|
||||
fds.fd = pidfd_thread;
|
||||
nevents = poll(&fds, 1, -1);
|
||||
ASSERT_EQ(nevents, 1);
|
||||
/* The thread has been reaped. */
|
||||
ASSERT_TRUE(!!(fds.revents & POLLHUP));
|
||||
|
||||
/* Retrieve thread-specific exit info from pidfd. */
|
||||
ASSERT_EQ(ioctl(pidfd_thread, PIDFD_GET_INFO, &info), 0);
|
||||
ASSERT_FALSE(!!(info.mask & PIDFD_INFO_CREDS));
|
||||
ASSERT_TRUE(!!(info.mask & PIDFD_INFO_EXIT));
|
||||
/*
|
||||
* While the kernel will have SIGKILLed the whole thread-group
|
||||
* during exec it will cause the individual threads to exit
|
||||
* cleanly.
|
||||
*/
|
||||
ASSERT_TRUE(WIFEXITED(info.exit_code));
|
||||
ASSERT_EQ(WEXITSTATUS(info.exit_code), 0);
|
||||
|
||||
/*
|
||||
* The thread-group leader is still alive, the thread has taken
|
||||
* over its struct pid and thus its pid number.
|
||||
*/
|
||||
info.mask = PIDFD_INFO_CGROUPID | PIDFD_INFO_EXIT;
|
||||
ASSERT_EQ(ioctl(pidfd_leader, PIDFD_GET_INFO, &info), 0);
|
||||
ASSERT_TRUE(!!(info.mask & PIDFD_INFO_CREDS));
|
||||
ASSERT_FALSE(!!(info.mask & PIDFD_INFO_EXIT));
|
||||
ASSERT_EQ(info.pid, pid_leader);
|
||||
|
||||
/* Take down the thread-group leader. */
|
||||
EXPECT_EQ(sys_pidfd_send_signal(pidfd_leader, SIGKILL, NULL, 0), 0);
|
||||
|
||||
/*
|
||||
* Afte the exec we're dealing with an empty thread-group so now
|
||||
* we must see an exit notification on the thread-specific pidfd
|
||||
* for the thread-group leader as there's no subthread that can
|
||||
* revive the struct pid.
|
||||
*/
|
||||
fds.events = POLLIN;
|
||||
fds.fd = pidfd_leader_thread;
|
||||
nevents = poll(&fds, 1, -1);
|
||||
ASSERT_EQ(nevents, 1);
|
||||
ASSERT_TRUE(!!(fds.revents & POLLIN));
|
||||
ASSERT_FALSE(!!(fds.revents & POLLHUP));
|
||||
|
||||
EXPECT_EQ(sys_waitid(P_PIDFD, pidfd_leader, NULL, WEXITED), 0);
|
||||
|
||||
/* Retrieve exit information for the thread-group leader. */
|
||||
info.mask = PIDFD_INFO_CGROUPID | PIDFD_INFO_EXIT;
|
||||
ASSERT_EQ(ioctl(pidfd_leader, PIDFD_GET_INFO, &info), 0);
|
||||
ASSERT_FALSE(!!(info.mask & PIDFD_INFO_CREDS));
|
||||
ASSERT_TRUE(!!(info.mask & PIDFD_INFO_EXIT));
|
||||
|
||||
EXPECT_EQ(close(pidfd_leader), 0);
|
||||
EXPECT_EQ(close(pidfd_thread), 0);
|
||||
}
|
||||
|
||||
static void *pidfd_info_thread_exec_sane(void *arg)
|
||||
{
|
||||
pid_t pid_thread = gettid();
|
||||
int ipc_socket = *(int *)arg;
|
||||
|
||||
/* Inform the grand-parent what the tid of this thread is. */
|
||||
if (write_nointr(ipc_socket, &pid_thread, sizeof(pid_thread)) != sizeof(pid_thread))
|
||||
return NULL;
|
||||
|
||||
if (read_nointr(ipc_socket, &pid_thread, sizeof(pid_thread)) != sizeof(pid_thread))
|
||||
return NULL;
|
||||
|
||||
close(ipc_socket);
|
||||
|
||||
sys_execveat(AT_FDCWD, "pidfd_exec_helper", NULL, NULL, 0);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
TEST_F(pidfd_info, thread_group_exec_thread)
|
||||
{
|
||||
pid_t pid_leader, pid_poller, pid_thread;
|
||||
pthread_t thread;
|
||||
int nevents, pidfd_leader, pidfd_leader_thread, pidfd_thread, ret;
|
||||
int ipc_sockets[2];
|
||||
struct pollfd fds = {};
|
||||
struct pidfd_info info = {
|
||||
.mask = PIDFD_INFO_CGROUPID | PIDFD_INFO_EXIT,
|
||||
};
|
||||
|
||||
ret = socketpair(AF_LOCAL, SOCK_STREAM | SOCK_CLOEXEC, 0, ipc_sockets);
|
||||
EXPECT_EQ(ret, 0);
|
||||
|
||||
pid_leader = create_child(&pidfd_leader, 0);
|
||||
EXPECT_GE(pid_leader, 0);
|
||||
|
||||
if (pid_leader == 0) {
|
||||
close(ipc_sockets[0]);
|
||||
|
||||
/* The thread will outlive the thread-group leader. */
|
||||
if (pthread_create(&thread, NULL, pidfd_info_thread_exec_sane, &ipc_sockets[1]))
|
||||
syscall(__NR_exit, EXIT_FAILURE);
|
||||
|
||||
/*
|
||||
* Pause the thread-group leader. It will be killed once
|
||||
* the subthread execs.
|
||||
*/
|
||||
pause();
|
||||
syscall(__NR_exit, EXIT_SUCCESS);
|
||||
}
|
||||
|
||||
/* Retrieve the tid of the thread. */
|
||||
EXPECT_EQ(close(ipc_sockets[1]), 0);
|
||||
ASSERT_EQ(read_nointr(ipc_sockets[0], &pid_thread, sizeof(pid_thread)), sizeof(pid_thread));
|
||||
|
||||
/* Opening a thread as a PIDFD_THREAD must succeed. */
|
||||
pidfd_thread = sys_pidfd_open(pid_thread, PIDFD_THREAD);
|
||||
ASSERT_GE(pidfd_thread, 0);
|
||||
|
||||
/* Open a thread-specific pidfd for the thread-group leader. */
|
||||
pidfd_leader_thread = sys_pidfd_open(pid_leader, PIDFD_THREAD);
|
||||
ASSERT_GE(pidfd_leader_thread, 0);
|
||||
|
||||
pid_poller = fork();
|
||||
ASSERT_GE(pid_poller, 0);
|
||||
if (pid_poller == 0) {
|
||||
/*
|
||||
* The subthread will now exec. The struct pid of the old
|
||||
* thread-group leader will be assumed by the subthread which
|
||||
* becomes the new thread-group leader. So no exit notification
|
||||
* must be generated. Wait for 5 seconds and call it a success
|
||||
* if no notification has been received.
|
||||
*/
|
||||
fds.events = POLLIN;
|
||||
fds.fd = pidfd_leader_thread;
|
||||
nevents = poll(&fds, 1, 10000 /* wait 5 seconds */);
|
||||
if (nevents != 0)
|
||||
_exit(EXIT_FAILURE);
|
||||
if (fds.revents & POLLIN)
|
||||
_exit(EXIT_FAILURE);
|
||||
if (fds.revents & POLLHUP)
|
||||
_exit(EXIT_FAILURE);
|
||||
_exit(EXIT_SUCCESS);
|
||||
}
|
||||
|
||||
/* Now that we've opened a thread-specific pidfd the thread can exec. */
|
||||
ASSERT_EQ(write_nointr(ipc_sockets[0], &pid_thread, sizeof(pid_thread)), sizeof(pid_thread));
|
||||
EXPECT_EQ(close(ipc_sockets[0]), 0);
|
||||
ASSERT_EQ(wait_for_pid(pid_poller), 0);
|
||||
|
||||
/* Wait until the kernel has SIGKILLed the thread. */
|
||||
fds.events = POLLHUP;
|
||||
fds.fd = pidfd_thread;
|
||||
nevents = poll(&fds, 1, -1);
|
||||
ASSERT_EQ(nevents, 1);
|
||||
/* The thread has been reaped. */
|
||||
ASSERT_TRUE(!!(fds.revents & POLLHUP));
|
||||
|
||||
/* Retrieve thread-specific exit info from pidfd. */
|
||||
ASSERT_EQ(ioctl(pidfd_thread, PIDFD_GET_INFO, &info), 0);
|
||||
ASSERT_FALSE(!!(info.mask & PIDFD_INFO_CREDS));
|
||||
ASSERT_TRUE(!!(info.mask & PIDFD_INFO_EXIT));
|
||||
/*
|
||||
* While the kernel will have SIGKILLed the whole thread-group
|
||||
* during exec it will cause the individual threads to exit
|
||||
* cleanly.
|
||||
*/
|
||||
ASSERT_TRUE(WIFEXITED(info.exit_code));
|
||||
ASSERT_EQ(WEXITSTATUS(info.exit_code), 0);
|
||||
|
||||
/*
|
||||
* The thread-group leader is still alive, the thread has taken
|
||||
* over its struct pid and thus its pid number.
|
||||
*/
|
||||
info.mask = PIDFD_INFO_CGROUPID | PIDFD_INFO_EXIT;
|
||||
ASSERT_EQ(ioctl(pidfd_leader, PIDFD_GET_INFO, &info), 0);
|
||||
ASSERT_TRUE(!!(info.mask & PIDFD_INFO_CREDS));
|
||||
ASSERT_FALSE(!!(info.mask & PIDFD_INFO_EXIT));
|
||||
ASSERT_EQ(info.pid, pid_leader);
|
||||
|
||||
/* Take down the thread-group leader. */
|
||||
EXPECT_EQ(sys_pidfd_send_signal(pidfd_leader, SIGKILL, NULL, 0), 0);
|
||||
|
||||
/*
|
||||
* Afte the exec we're dealing with an empty thread-group so now
|
||||
* we must see an exit notification on the thread-specific pidfd
|
||||
* for the thread-group leader as there's no subthread that can
|
||||
* revive the struct pid.
|
||||
*/
|
||||
fds.events = POLLIN;
|
||||
fds.fd = pidfd_leader_thread;
|
||||
nevents = poll(&fds, 1, -1);
|
||||
ASSERT_EQ(nevents, 1);
|
||||
ASSERT_TRUE(!!(fds.revents & POLLIN));
|
||||
ASSERT_FALSE(!!(fds.revents & POLLHUP));
|
||||
|
||||
EXPECT_EQ(sys_waitid(P_PIDFD, pidfd_leader, NULL, WEXITED), 0);
|
||||
|
||||
/* Retrieve exit information for the thread-group leader. */
|
||||
info.mask = PIDFD_INFO_CGROUPID | PIDFD_INFO_EXIT;
|
||||
ASSERT_EQ(ioctl(pidfd_leader, PIDFD_GET_INFO, &info), 0);
|
||||
ASSERT_FALSE(!!(info.mask & PIDFD_INFO_CREDS));
|
||||
ASSERT_TRUE(!!(info.mask & PIDFD_INFO_EXIT));
|
||||
|
||||
EXPECT_EQ(close(pidfd_leader), 0);
|
||||
EXPECT_EQ(close(pidfd_thread), 0);
|
||||
}
|
||||
|
||||
TEST_HARNESS_MAIN
|
@ -22,32 +22,6 @@
|
||||
#include "pidfd.h"
|
||||
#include "../kselftest.h"
|
||||
|
||||
#ifndef PIDFS_IOCTL_MAGIC
|
||||
#define PIDFS_IOCTL_MAGIC 0xFF
|
||||
#endif
|
||||
|
||||
#ifndef PIDFD_GET_INFO
|
||||
#define PIDFD_GET_INFO _IOWR(PIDFS_IOCTL_MAGIC, 11, struct pidfd_info)
|
||||
#define PIDFD_INFO_CGROUPID (1UL << 0)
|
||||
|
||||
struct pidfd_info {
|
||||
__u64 request_mask;
|
||||
__u64 cgroupid;
|
||||
__u32 pid;
|
||||
__u32 tgid;
|
||||
__u32 ppid;
|
||||
__u32 ruid;
|
||||
__u32 rgid;
|
||||
__u32 euid;
|
||||
__u32 egid;
|
||||
__u32 suid;
|
||||
__u32 sgid;
|
||||
__u32 fsuid;
|
||||
__u32 fsgid;
|
||||
__u32 spare0[1];
|
||||
};
|
||||
#endif
|
||||
|
||||
static int safe_int(const char *numstr, int *converted)
|
||||
{
|
||||
char *err = NULL;
|
||||
@ -148,7 +122,7 @@ out:
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
struct pidfd_info info = {
|
||||
.request_mask = PIDFD_INFO_CGROUPID,
|
||||
.mask = PIDFD_INFO_CGROUPID,
|
||||
};
|
||||
int pidfd = -1, ret = 1;
|
||||
pid_t pid;
|
||||
@ -227,7 +201,7 @@ int main(int argc, char **argv)
|
||||
getegid(), info.sgid);
|
||||
goto on_error;
|
||||
}
|
||||
if ((info.request_mask & PIDFD_INFO_CGROUPID) && info.cgroupid == 0) {
|
||||
if ((info.mask & PIDFD_INFO_CGROUPID) && info.cgroupid == 0) {
|
||||
ksft_print_msg("cgroupid should not be 0 when PIDFD_INFO_CGROUPID is set\n");
|
||||
goto on_error;
|
||||
}
|
||||
|
@ -16,55 +16,10 @@
|
||||
#include <unistd.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/stat.h>
|
||||
#include <linux/ioctl.h>
|
||||
|
||||
#include "pidfd.h"
|
||||
#include "../kselftest_harness.h"
|
||||
|
||||
#ifndef PIDFS_IOCTL_MAGIC
|
||||
#define PIDFS_IOCTL_MAGIC 0xFF
|
||||
#endif
|
||||
|
||||
#ifndef PIDFD_GET_CGROUP_NAMESPACE
|
||||
#define PIDFD_GET_CGROUP_NAMESPACE _IO(PIDFS_IOCTL_MAGIC, 1)
|
||||
#endif
|
||||
|
||||
#ifndef PIDFD_GET_IPC_NAMESPACE
|
||||
#define PIDFD_GET_IPC_NAMESPACE _IO(PIDFS_IOCTL_MAGIC, 2)
|
||||
#endif
|
||||
|
||||
#ifndef PIDFD_GET_MNT_NAMESPACE
|
||||
#define PIDFD_GET_MNT_NAMESPACE _IO(PIDFS_IOCTL_MAGIC, 3)
|
||||
#endif
|
||||
|
||||
#ifndef PIDFD_GET_NET_NAMESPACE
|
||||
#define PIDFD_GET_NET_NAMESPACE _IO(PIDFS_IOCTL_MAGIC, 4)
|
||||
#endif
|
||||
|
||||
#ifndef PIDFD_GET_PID_NAMESPACE
|
||||
#define PIDFD_GET_PID_NAMESPACE _IO(PIDFS_IOCTL_MAGIC, 5)
|
||||
#endif
|
||||
|
||||
#ifndef PIDFD_GET_PID_FOR_CHILDREN_NAMESPACE
|
||||
#define PIDFD_GET_PID_FOR_CHILDREN_NAMESPACE _IO(PIDFS_IOCTL_MAGIC, 6)
|
||||
#endif
|
||||
|
||||
#ifndef PIDFD_GET_TIME_NAMESPACE
|
||||
#define PIDFD_GET_TIME_NAMESPACE _IO(PIDFS_IOCTL_MAGIC, 7)
|
||||
#endif
|
||||
|
||||
#ifndef PIDFD_GET_TIME_FOR_CHILDREN_NAMESPACE
|
||||
#define PIDFD_GET_TIME_FOR_CHILDREN_NAMESPACE _IO(PIDFS_IOCTL_MAGIC, 8)
|
||||
#endif
|
||||
|
||||
#ifndef PIDFD_GET_USER_NAMESPACE
|
||||
#define PIDFD_GET_USER_NAMESPACE _IO(PIDFS_IOCTL_MAGIC, 9)
|
||||
#endif
|
||||
|
||||
#ifndef PIDFD_GET_UTS_NAMESPACE
|
||||
#define PIDFD_GET_UTS_NAMESPACE _IO(PIDFS_IOCTL_MAGIC, 10)
|
||||
#endif
|
||||
|
||||
enum {
|
||||
PIDFD_NS_USER,
|
||||
PIDFD_NS_MNT,
|
||||
|
@ -42,12 +42,41 @@ static pid_t pidfd_clone(int flags, int *pidfd, int (*fn)(void *))
|
||||
#endif
|
||||
}
|
||||
|
||||
static int signal_received;
|
||||
static pthread_t signal_received;
|
||||
|
||||
static void set_signal_received_on_sigusr1(int sig)
|
||||
{
|
||||
if (sig == SIGUSR1)
|
||||
signal_received = 1;
|
||||
signal_received = pthread_self();
|
||||
}
|
||||
|
||||
static int send_signal(int pidfd)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
if (sys_pidfd_send_signal(pidfd, SIGUSR1, NULL, 0) < 0) {
|
||||
ret = -EINVAL;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
if (signal_received != pthread_self()) {
|
||||
ret = -EINVAL;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
exit:
|
||||
signal_received = 0;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void *send_signal_worker(void *arg)
|
||||
{
|
||||
int pidfd = (int)(intptr_t)arg;
|
||||
int ret;
|
||||
|
||||
/* We forward any errors for the caller to handle. */
|
||||
ret = send_signal(pidfd);
|
||||
return (void *)(intptr_t)ret;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -56,8 +85,11 @@ static void set_signal_received_on_sigusr1(int sig)
|
||||
*/
|
||||
static int test_pidfd_send_signal_simple_success(void)
|
||||
{
|
||||
int pidfd, ret;
|
||||
int pidfd;
|
||||
const char *test_name = "pidfd_send_signal send SIGUSR1";
|
||||
pthread_t thread;
|
||||
void *thread_res;
|
||||
int err;
|
||||
|
||||
if (!have_pidfd_send_signal) {
|
||||
ksft_test_result_skip(
|
||||
@ -66,25 +98,45 @@ static int test_pidfd_send_signal_simple_success(void)
|
||||
return 0;
|
||||
}
|
||||
|
||||
signal(SIGUSR1, set_signal_received_on_sigusr1);
|
||||
|
||||
/* Try sending a signal to ourselves via /proc/self. */
|
||||
pidfd = open("/proc/self", O_DIRECTORY | O_CLOEXEC);
|
||||
if (pidfd < 0)
|
||||
ksft_exit_fail_msg(
|
||||
"%s test: Failed to open process file descriptor\n",
|
||||
test_name);
|
||||
|
||||
signal(SIGUSR1, set_signal_received_on_sigusr1);
|
||||
|
||||
ret = sys_pidfd_send_signal(pidfd, SIGUSR1, NULL, 0);
|
||||
err = send_signal(pidfd);
|
||||
if (err)
|
||||
ksft_exit_fail_msg(
|
||||
"%s test: Error %d on sending pidfd signal\n",
|
||||
test_name, err);
|
||||
close(pidfd);
|
||||
if (ret < 0)
|
||||
ksft_exit_fail_msg("%s test: Failed to send signal\n",
|
||||
test_name);
|
||||
|
||||
if (signal_received != 1)
|
||||
ksft_exit_fail_msg("%s test: Failed to receive signal\n",
|
||||
test_name);
|
||||
/* Now try the same thing only using PIDFD_SELF_THREAD_GROUP. */
|
||||
err = send_signal(PIDFD_SELF_THREAD_GROUP);
|
||||
if (err)
|
||||
ksft_exit_fail_msg(
|
||||
"%s test: Error %d on PIDFD_SELF_THREAD_GROUP signal\n",
|
||||
test_name, err);
|
||||
|
||||
/*
|
||||
* Now try the same thing in a thread and assert thread ID is equal to
|
||||
* worker thread ID.
|
||||
*/
|
||||
if (pthread_create(&thread, NULL, send_signal_worker,
|
||||
(void *)(intptr_t)PIDFD_SELF_THREAD))
|
||||
ksft_exit_fail_msg("%s test: Failed to create thread\n",
|
||||
test_name);
|
||||
if (pthread_join(thread, &thread_res))
|
||||
ksft_exit_fail_msg("%s test: Failed to join thread\n",
|
||||
test_name);
|
||||
err = (int)(intptr_t)thread_res;
|
||||
if (err)
|
||||
ksft_exit_fail_msg(
|
||||
"%s test: Error %d on PIDFD_SELF_THREAD signal\n",
|
||||
test_name, err);
|
||||
|
||||
signal_received = 0;
|
||||
ksft_test_result_pass("%s test: Sent signal\n", test_name);
|
||||
return 0;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user