Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
@@ -0,0 +1,15 @@
# SPDX-License-Identifier: GPL-2.0-only
pidfd_open_test
pidfd_poll_test
pidfd_test
pidfd_wait
pidfd_fdinfo_test
pidfd_getfd_test
pidfd_setns_test
pidfd_file_handle_test
pidfd_bind_mount
pidfd_info_test
pidfd_exec_helper
pidfd_xattr_test
pidfd_setattr_test
pidfd_autoreap_test
@@ -0,0 +1,12 @@
# SPDX-License-Identifier: GPL-2.0-only
CFLAGS += -g $(KHDR_INCLUDES) $(TOOLS_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_info_test \
pidfd_xattr_test pidfd_setattr_test pidfd_autoreap_test
TEST_GEN_PROGS_EXTENDED := pidfd_exec_helper
include ../lib.mk
@@ -0,0 +1,8 @@
CONFIG_UTS_NS=y
CONFIG_IPC_NS=y
CONFIG_USER_NS=y
CONFIG_PID_NS=y
CONFIG_NET_NS=y
CONFIG_TIME_NS=y
CONFIG_CGROUPS=y
CONFIG_CHECKPOINT_RESTORE=y
@@ -0,0 +1,315 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef __PIDFD_H
#define __PIDFD_H
#define _GNU_SOURCE
#include <errno.h>
#include <fcntl.h>
#include <sched.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syscall.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/wait.h>
/*
* Remove the userspace definitions of the following preprocessor symbols
* to avoid duplicate-definition warnings from the subsequent in-kernel
* definitions.
*/
#undef SCHED_NORMAL
#undef SCHED_FLAG_KEEP_ALL
#undef SCHED_FLAG_UTIL_CLAMP
#include "kselftest.h"
#include "../clone3/clone3_selftests.h"
#ifndef FD_PIDFS_ROOT
#define FD_PIDFS_ROOT -10002
#endif
#ifndef P_PIDFD
#define P_PIDFD 3
#endif
#ifndef CLONE_NEWTIME
#define CLONE_NEWTIME 0x00000080
#endif
#ifndef CLONE_PIDFD
#define CLONE_PIDFD 0x00001000
#endif
#ifndef __NR_pidfd_open
#define __NR_pidfd_open 434
#endif
#ifndef __NR_pidfd_send_signal
#define __NR_pidfd_send_signal 424
#endif
#ifndef __NR_clone3
#define __NR_clone3 435
#endif
#ifndef __NR_pidfd_getfd
#define __NR_pidfd_getfd 438
#endif
#ifndef PIDFD_NONBLOCK
#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 -10001 /* 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_INFO_COREDUMP
#define PIDFD_INFO_COREDUMP (1UL << 4)
#endif
#ifndef PIDFD_INFO_SUPPORTED_MASK
#define PIDFD_INFO_SUPPORTED_MASK (1UL << 5)
#endif
#ifndef PIDFD_INFO_COREDUMP_SIGNAL
#define PIDFD_INFO_COREDUMP_SIGNAL (1UL << 6)
#endif
#ifndef PIDFD_INFO_COREDUMP_CODE
#define PIDFD_INFO_COREDUMP_CODE (1UL << 7)
#endif
#ifndef PIDFD_COREDUMPED
#define PIDFD_COREDUMPED (1U << 0) /* Did crash and... */
#endif
#ifndef PIDFD_COREDUMP_SKIP
#define PIDFD_COREDUMP_SKIP (1U << 1) /* coredumping generation was skipped. */
#endif
#ifndef PIDFD_COREDUMP_USER
#define PIDFD_COREDUMP_USER (1U << 2) /* coredump was done as the user. */
#endif
#ifndef PIDFD_COREDUMP_ROOT
#define PIDFD_COREDUMP_ROOT (1U << 3) /* coredump was done as root. */
#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;
struct {
__u32 coredump_mask;
__u32 coredump_signal;
__u32 coredump_code;
};
__u64 supported_mask;
};
/*
* The kernel reserves 300 pids via RESERVED_PIDS in kernel/pid.c
* That means, when it wraps around any pid < 300 will be skipped.
* So we need to use a pid > 300 in order to test recycling.
*/
#define PID_RECYCLE 1000
/*
* Define a few custom error codes for the child process to clearly indicate
* what is happening. This way we can tell the difference between a system
* error, a test error, etc.
*/
#define PIDFD_PASS 0
#define PIDFD_FAIL 1
#define PIDFD_ERROR 2
#define PIDFD_SKIP 3
#define PIDFD_XFAIL 4
static inline int sys_waitid(int which, pid_t pid, siginfo_t *info, int options)
{
return syscall(__NR_waitid, which, pid, info, options, NULL);
}
static inline int wait_for_pid(pid_t pid)
{
int status, ret;
again:
ret = waitpid(pid, &status, 0);
if (ret == -1) {
if (errno == EINTR)
goto again;
ksft_print_msg("waitpid returned -1, errno=%d\n", errno);
return -1;
}
if (!WIFEXITED(status)) {
ksft_print_msg(
"waitpid !WIFEXITED, WIFSIGNALED=%d, WTERMSIG=%d\n",
WIFSIGNALED(status), WTERMSIG(status));
return -1;
}
ret = WEXITSTATUS(status);
return ret;
}
static inline int sys_pidfd_open(pid_t pid, unsigned int flags)
{
return syscall(__NR_pidfd_open, pid, flags);
}
static inline int sys_pidfd_send_signal(int pidfd, int sig, siginfo_t *info,
unsigned int flags)
{
return syscall(__NR_pidfd_send_signal, pidfd, sig, info, flags);
}
static inline int sys_pidfd_getfd(int pidfd, int fd, int flags)
{
return syscall(__NR_pidfd_getfd, pidfd, fd, flags);
}
static inline int sys_memfd_create(const char *name, unsigned int flags)
{
return syscall(__NR_memfd_create, name, flags);
}
static inline pid_t create_child(int *pidfd, unsigned flags)
{
struct __clone_args args = {
.flags = CLONE_PIDFD | flags,
.exit_signal = SIGCHLD,
.pidfd = ptr_to_u64(pidfd),
};
return sys_clone3(&args, sizeof(struct __clone_args));
}
static inline ssize_t read_nointr(int fd, void *buf, size_t count)
{
ssize_t ret;
do {
ret = read(fd, buf, count);
} while (ret < 0 && errno == EINTR);
return ret;
}
static inline ssize_t write_nointr(int fd, const void *buf, size_t count)
{
ssize_t ret;
do {
ret = write(fd, buf, count);
} while (ret < 0 && errno == EINTR);
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 */
@@ -0,0 +1,900 @@
// SPDX-License-Identifier: GPL-2.0
// Copyright (c) 2026 Christian Brauner <brauner@kernel.org>
#define _GNU_SOURCE
#include <errno.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/ioctl.h>
#include <sys/prctl.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include "pidfd.h"
#include "kselftest_harness.h"
#ifndef CLONE_AUTOREAP
#define CLONE_AUTOREAP (1ULL << 34)
#endif
#ifndef CLONE_NNP
#define CLONE_NNP (1ULL << 35)
#endif
#ifndef CLONE_PIDFD_AUTOKILL
#define CLONE_PIDFD_AUTOKILL (1ULL << 36)
#endif
#ifndef _LINUX_CAPABILITY_VERSION_3
#define _LINUX_CAPABILITY_VERSION_3 0x20080522
#endif
struct cap_header {
__u32 version;
int pid;
};
struct cap_data {
__u32 effective;
__u32 permitted;
__u32 inheritable;
};
static int drop_all_caps(void)
{
struct cap_header hdr = { .version = _LINUX_CAPABILITY_VERSION_3 };
struct cap_data data[2] = {};
return syscall(__NR_capset, &hdr, data);
}
static pid_t create_autoreap_child(int *pidfd)
{
struct __clone_args args = {
.flags = CLONE_PIDFD | CLONE_AUTOREAP,
.exit_signal = 0,
.pidfd = ptr_to_u64(pidfd),
};
return sys_clone3(&args, sizeof(args));
}
/*
* Test that CLONE_AUTOREAP works without CLONE_PIDFD (fire-and-forget).
*/
TEST(autoreap_without_pidfd)
{
struct __clone_args args = {
.flags = CLONE_AUTOREAP,
.exit_signal = 0,
};
pid_t pid;
int ret;
pid = sys_clone3(&args, sizeof(args));
if (pid < 0 && errno == EINVAL)
SKIP(return, "CLONE_AUTOREAP not supported");
ASSERT_GE(pid, 0);
if (pid == 0)
_exit(0);
/*
* Give the child a moment to exit and be autoreaped.
* Then verify no zombie remains.
*/
usleep(200000);
ret = waitpid(pid, NULL, WNOHANG);
ASSERT_EQ(ret, -1);
ASSERT_EQ(errno, ECHILD);
}
/*
* Test that CLONE_AUTOREAP with a non-zero exit_signal fails.
*/
TEST(autoreap_rejects_exit_signal)
{
struct __clone_args args = {
.flags = CLONE_AUTOREAP,
.exit_signal = SIGCHLD,
};
pid_t pid;
pid = sys_clone3(&args, sizeof(args));
ASSERT_EQ(pid, -1);
ASSERT_EQ(errno, EINVAL);
}
/*
* Test that CLONE_AUTOREAP with CLONE_PARENT fails.
*/
TEST(autoreap_rejects_parent)
{
struct __clone_args args = {
.flags = CLONE_AUTOREAP | CLONE_PARENT,
.exit_signal = 0,
};
pid_t pid;
pid = sys_clone3(&args, sizeof(args));
ASSERT_EQ(pid, -1);
ASSERT_EQ(errno, EINVAL);
}
/*
* Test that CLONE_AUTOREAP with CLONE_THREAD fails.
*/
TEST(autoreap_rejects_thread)
{
struct __clone_args args = {
.flags = CLONE_AUTOREAP | CLONE_THREAD |
CLONE_SIGHAND | CLONE_VM,
.exit_signal = 0,
};
pid_t pid;
pid = sys_clone3(&args, sizeof(args));
ASSERT_EQ(pid, -1);
ASSERT_EQ(errno, EINVAL);
}
/*
* Basic test: create an autoreap child, let it exit, verify:
* - pidfd becomes readable (poll returns POLLIN)
* - PIDFD_GET_INFO returns the correct exit code
* - waitpid() returns -1/ECHILD (no zombie)
*/
TEST(autoreap_basic)
{
struct pidfd_info info = { .mask = PIDFD_INFO_EXIT };
int pidfd = -1, ret;
struct pollfd pfd;
pid_t pid;
pid = create_autoreap_child(&pidfd);
if (pid < 0 && errno == EINVAL)
SKIP(return, "CLONE_AUTOREAP not supported");
ASSERT_GE(pid, 0);
if (pid == 0)
_exit(42);
ASSERT_GE(pidfd, 0);
/* Wait for the child to exit via pidfd poll. */
pfd.fd = pidfd;
pfd.events = POLLIN;
ret = poll(&pfd, 1, 5000);
ASSERT_EQ(ret, 1);
ASSERT_TRUE(pfd.revents & POLLIN);
/* Verify exit info via PIDFD_GET_INFO. */
ret = ioctl(pidfd, PIDFD_GET_INFO, &info);
ASSERT_EQ(ret, 0);
ASSERT_TRUE(info.mask & PIDFD_INFO_EXIT);
/*
* exit_code is in waitpid format: for _exit(42),
* WIFEXITED is true and WEXITSTATUS is 42.
*/
ASSERT_TRUE(WIFEXITED(info.exit_code));
ASSERT_EQ(WEXITSTATUS(info.exit_code), 42);
/* Verify no zombie: waitpid should fail with ECHILD. */
ret = waitpid(pid, NULL, WNOHANG);
ASSERT_EQ(ret, -1);
ASSERT_EQ(errno, ECHILD);
close(pidfd);
}
/*
* Test that an autoreap child killed by a signal reports
* the correct exit info.
*/
TEST(autoreap_signaled)
{
struct pidfd_info info = { .mask = PIDFD_INFO_EXIT };
int pidfd = -1, ret;
struct pollfd pfd;
pid_t pid;
pid = create_autoreap_child(&pidfd);
if (pid < 0 && errno == EINVAL)
SKIP(return, "CLONE_AUTOREAP not supported");
ASSERT_GE(pid, 0);
if (pid == 0) {
pause();
_exit(1);
}
ASSERT_GE(pidfd, 0);
/* Kill the child. */
ret = sys_pidfd_send_signal(pidfd, SIGKILL, NULL, 0);
ASSERT_EQ(ret, 0);
/* Wait for exit via pidfd. */
pfd.fd = pidfd;
pfd.events = POLLIN;
ret = poll(&pfd, 1, 5000);
ASSERT_EQ(ret, 1);
ASSERT_TRUE(pfd.revents & POLLIN);
/* Verify signal info. */
ret = ioctl(pidfd, PIDFD_GET_INFO, &info);
ASSERT_EQ(ret, 0);
ASSERT_TRUE(info.mask & PIDFD_INFO_EXIT);
ASSERT_TRUE(WIFSIGNALED(info.exit_code));
ASSERT_EQ(WTERMSIG(info.exit_code), SIGKILL);
/* No zombie. */
ret = waitpid(pid, NULL, WNOHANG);
ASSERT_EQ(ret, -1);
ASSERT_EQ(errno, ECHILD);
close(pidfd);
}
/*
* Test autoreap survives reparenting: middle process creates an
* autoreap grandchild, then exits. The grandchild gets reparented
* to us (the grandparent, which is a subreaper). When the grandchild
* exits, it should still be autoreaped - no zombie under us.
*/
TEST(autoreap_reparent)
{
int ipc_sockets[2], ret;
int pidfd = -1;
struct pollfd pfd;
pid_t mid_pid, grandchild_pid;
char buf[32] = {};
/* Make ourselves a subreaper so reparented children come to us. */
ret = prctl(PR_SET_CHILD_SUBREAPER, 1);
ASSERT_EQ(ret, 0);
ret = socketpair(AF_LOCAL, SOCK_STREAM | SOCK_CLOEXEC, 0, ipc_sockets);
ASSERT_EQ(ret, 0);
mid_pid = fork();
ASSERT_GE(mid_pid, 0);
if (mid_pid == 0) {
/* Middle child: create an autoreap grandchild. */
int gc_pidfd = -1;
close(ipc_sockets[0]);
grandchild_pid = create_autoreap_child(&gc_pidfd);
if (grandchild_pid < 0) {
write_nointr(ipc_sockets[1], "E", 1);
close(ipc_sockets[1]);
_exit(1);
}
if (grandchild_pid == 0) {
/* Grandchild: wait for signal to exit. */
close(ipc_sockets[1]);
if (gc_pidfd >= 0)
close(gc_pidfd);
pause();
_exit(0);
}
/* Send grandchild PID to grandparent. */
snprintf(buf, sizeof(buf), "%d", grandchild_pid);
write_nointr(ipc_sockets[1], buf, strlen(buf));
close(ipc_sockets[1]);
if (gc_pidfd >= 0)
close(gc_pidfd);
/* Middle child exits, grandchild gets reparented. */
_exit(0);
}
close(ipc_sockets[1]);
/* Read grandchild's PID. */
ret = read_nointr(ipc_sockets[0], buf, sizeof(buf) - 1);
close(ipc_sockets[0]);
ASSERT_GT(ret, 0);
if (buf[0] == 'E') {
waitpid(mid_pid, NULL, 0);
prctl(PR_SET_CHILD_SUBREAPER, 0);
SKIP(return, "CLONE_AUTOREAP not supported");
}
grandchild_pid = atoi(buf);
ASSERT_GT(grandchild_pid, 0);
/* Wait for the middle child to exit. */
ret = waitpid(mid_pid, NULL, 0);
ASSERT_EQ(ret, mid_pid);
/*
* Now the grandchild is reparented to us (subreaper).
* Open a pidfd for the grandchild and kill it.
*/
pidfd = sys_pidfd_open(grandchild_pid, 0);
ASSERT_GE(pidfd, 0);
ret = sys_pidfd_send_signal(pidfd, SIGKILL, NULL, 0);
ASSERT_EQ(ret, 0);
/* Wait for it to exit via pidfd poll. */
pfd.fd = pidfd;
pfd.events = POLLIN;
ret = poll(&pfd, 1, 5000);
ASSERT_EQ(ret, 1);
ASSERT_TRUE(pfd.revents & POLLIN);
/*
* The grandchild should have been autoreaped even though
* we (the new parent) haven't set SA_NOCLDWAIT.
* waitpid should return -1/ECHILD.
*/
ret = waitpid(grandchild_pid, NULL, WNOHANG);
EXPECT_EQ(ret, -1);
EXPECT_EQ(errno, ECHILD);
close(pidfd);
/* Clean up subreaper status. */
prctl(PR_SET_CHILD_SUBREAPER, 0);
}
static int thread_sock_fd;
static void *thread_func(void *arg)
{
/* Signal parent we're running. */
write_nointr(thread_sock_fd, "1", 1);
/* Give main thread time to call _exit() first. */
usleep(200000);
return NULL;
}
/*
* Test that an autoreap child with multiple threads is properly
* autoreaped only after all threads have exited.
*/
TEST(autoreap_multithreaded)
{
struct pidfd_info info = { .mask = PIDFD_INFO_EXIT };
int ipc_sockets[2], ret;
int pidfd = -1;
struct pollfd pfd;
pid_t pid;
char c;
ret = socketpair(AF_LOCAL, SOCK_STREAM | SOCK_CLOEXEC, 0, ipc_sockets);
ASSERT_EQ(ret, 0);
pid = create_autoreap_child(&pidfd);
if (pid < 0 && errno == EINVAL) {
close(ipc_sockets[0]);
close(ipc_sockets[1]);
SKIP(return, "CLONE_AUTOREAP not supported");
}
ASSERT_GE(pid, 0);
if (pid == 0) {
pthread_t thread;
close(ipc_sockets[0]);
/*
* Create a sub-thread that outlives the main thread.
* The thread signals readiness, then sleeps.
* The main thread waits briefly, then calls _exit().
*/
thread_sock_fd = ipc_sockets[1];
pthread_create(&thread, NULL, thread_func, NULL);
pthread_detach(thread);
/* Wait for thread to be running. */
usleep(100000);
/* Main thread exits; sub-thread is still alive. */
_exit(99);
}
close(ipc_sockets[1]);
/* Wait for the sub-thread to signal readiness. */
ret = read_nointr(ipc_sockets[0], &c, 1);
close(ipc_sockets[0]);
ASSERT_EQ(ret, 1);
/* Wait for the process to fully exit via pidfd poll. */
pfd.fd = pidfd;
pfd.events = POLLIN;
ret = poll(&pfd, 1, 5000);
ASSERT_EQ(ret, 1);
ASSERT_TRUE(pfd.revents & POLLIN);
/* Verify exit info. */
ret = ioctl(pidfd, PIDFD_GET_INFO, &info);
ASSERT_EQ(ret, 0);
ASSERT_TRUE(info.mask & PIDFD_INFO_EXIT);
ASSERT_TRUE(WIFEXITED(info.exit_code));
ASSERT_EQ(WEXITSTATUS(info.exit_code), 99);
/* No zombie. */
ret = waitpid(pid, NULL, WNOHANG);
ASSERT_EQ(ret, -1);
ASSERT_EQ(errno, ECHILD);
close(pidfd);
}
/*
* Test that autoreap is NOT inherited by grandchildren.
*/
TEST(autoreap_no_inherit)
{
int ipc_sockets[2], ret;
int pidfd = -1;
pid_t pid;
char buf[2] = {};
struct pollfd pfd;
ret = socketpair(AF_LOCAL, SOCK_STREAM | SOCK_CLOEXEC, 0, ipc_sockets);
ASSERT_EQ(ret, 0);
pid = create_autoreap_child(&pidfd);
if (pid < 0 && errno == EINVAL) {
close(ipc_sockets[0]);
close(ipc_sockets[1]);
SKIP(return, "CLONE_AUTOREAP not supported");
}
ASSERT_GE(pid, 0);
if (pid == 0) {
pid_t gc;
int status;
close(ipc_sockets[0]);
/* Autoreap child forks a grandchild (without autoreap). */
gc = fork();
if (gc < 0) {
write_nointr(ipc_sockets[1], "E", 1);
_exit(1);
}
if (gc == 0) {
/* Grandchild: exit immediately. */
close(ipc_sockets[1]);
_exit(77);
}
/*
* The grandchild should become a regular zombie
* since it was NOT created with CLONE_AUTOREAP.
* Wait for it to verify.
*/
ret = waitpid(gc, &status, 0);
if (ret == gc && WIFEXITED(status) &&
WEXITSTATUS(status) == 77) {
write_nointr(ipc_sockets[1], "P", 1);
} else {
write_nointr(ipc_sockets[1], "F", 1);
}
close(ipc_sockets[1]);
_exit(0);
}
close(ipc_sockets[1]);
ret = read_nointr(ipc_sockets[0], buf, 1);
close(ipc_sockets[0]);
ASSERT_EQ(ret, 1);
/*
* 'P' means the autoreap child was able to waitpid() its
* grandchild (correct - grandchild should be a normal zombie,
* not autoreaped).
*/
ASSERT_EQ(buf[0], 'P');
/* Wait for the autoreap child to exit. */
pfd.fd = pidfd;
pfd.events = POLLIN;
ret = poll(&pfd, 1, 5000);
ASSERT_EQ(ret, 1);
/* Autoreap child itself should be autoreaped. */
ret = waitpid(pid, NULL, WNOHANG);
ASSERT_EQ(ret, -1);
ASSERT_EQ(errno, ECHILD);
close(pidfd);
}
/*
* Test that CLONE_NNP sets no_new_privs on the child.
* The child checks via prctl(PR_GET_NO_NEW_PRIVS) and reports back.
* The parent must NOT have no_new_privs set afterwards.
*/
TEST(nnp_sets_no_new_privs)
{
struct __clone_args args = {
.flags = CLONE_PIDFD | CLONE_AUTOREAP | CLONE_NNP,
.exit_signal = 0,
};
struct pidfd_info info = { .mask = PIDFD_INFO_EXIT };
int pidfd = -1, ret;
struct pollfd pfd;
pid_t pid;
/* Ensure parent does not already have no_new_privs. */
ret = prctl(PR_GET_NO_NEW_PRIVS, 0, 0, 0, 0);
ASSERT_EQ(ret, 0) {
TH_LOG("Parent already has no_new_privs set, cannot run test");
}
args.pidfd = ptr_to_u64(&pidfd);
pid = sys_clone3(&args, sizeof(args));
if (pid < 0 && errno == EINVAL)
SKIP(return, "CLONE_NNP not supported");
ASSERT_GE(pid, 0);
if (pid == 0) {
/*
* Child: check no_new_privs. Exit 0 if set, 1 if not.
*/
ret = prctl(PR_GET_NO_NEW_PRIVS, 0, 0, 0, 0);
_exit(ret == 1 ? 0 : 1);
}
ASSERT_GE(pidfd, 0);
/* Parent must still NOT have no_new_privs. */
ret = prctl(PR_GET_NO_NEW_PRIVS, 0, 0, 0, 0);
ASSERT_EQ(ret, 0) {
TH_LOG("Parent got no_new_privs after creating CLONE_NNP child");
}
/* Wait for child to exit. */
pfd.fd = pidfd;
pfd.events = POLLIN;
ret = poll(&pfd, 1, 5000);
ASSERT_EQ(ret, 1);
/* Verify child exited with 0 (no_new_privs was set). */
ret = ioctl(pidfd, PIDFD_GET_INFO, &info);
ASSERT_EQ(ret, 0);
ASSERT_TRUE(info.mask & PIDFD_INFO_EXIT);
ASSERT_TRUE(WIFEXITED(info.exit_code));
ASSERT_EQ(WEXITSTATUS(info.exit_code), 0) {
TH_LOG("Child did not have no_new_privs set");
}
close(pidfd);
}
/*
* Test that CLONE_NNP with CLONE_THREAD fails with EINVAL.
*/
TEST(nnp_rejects_thread)
{
struct __clone_args args = {
.flags = CLONE_NNP | CLONE_THREAD |
CLONE_SIGHAND | CLONE_VM,
.exit_signal = 0,
};
pid_t pid;
pid = sys_clone3(&args, sizeof(args));
ASSERT_EQ(pid, -1);
ASSERT_EQ(errno, EINVAL);
}
/*
* Test that a plain CLONE_AUTOREAP child does NOT get no_new_privs.
* Only CLONE_NNP should set it.
*/
TEST(autoreap_no_new_privs_unset)
{
struct pidfd_info info = { .mask = PIDFD_INFO_EXIT };
int pidfd = -1, ret;
struct pollfd pfd;
pid_t pid;
pid = create_autoreap_child(&pidfd);
if (pid < 0 && errno == EINVAL)
SKIP(return, "CLONE_AUTOREAP not supported");
ASSERT_GE(pid, 0);
if (pid == 0) {
/*
* Child: check no_new_privs. Exit 0 if NOT set, 1 if set.
*/
ret = prctl(PR_GET_NO_NEW_PRIVS, 0, 0, 0, 0);
_exit(ret == 0 ? 0 : 1);
}
ASSERT_GE(pidfd, 0);
pfd.fd = pidfd;
pfd.events = POLLIN;
ret = poll(&pfd, 1, 5000);
ASSERT_EQ(ret, 1);
ret = ioctl(pidfd, PIDFD_GET_INFO, &info);
ASSERT_EQ(ret, 0);
ASSERT_TRUE(info.mask & PIDFD_INFO_EXIT);
ASSERT_TRUE(WIFEXITED(info.exit_code));
ASSERT_EQ(WEXITSTATUS(info.exit_code), 0) {
TH_LOG("Plain autoreap child unexpectedly has no_new_privs");
}
close(pidfd);
}
/*
* Helper: create a child with CLONE_PIDFD | CLONE_PIDFD_AUTOKILL | CLONE_AUTOREAP | CLONE_NNP.
*/
static pid_t create_autokill_child(int *pidfd)
{
struct __clone_args args = {
.flags = CLONE_PIDFD | CLONE_PIDFD_AUTOKILL |
CLONE_AUTOREAP | CLONE_NNP,
.exit_signal = 0,
.pidfd = ptr_to_u64(pidfd),
};
return sys_clone3(&args, sizeof(args));
}
/*
* Basic autokill test: child blocks in pause(), parent closes the
* clone3 pidfd, child should be killed and autoreaped.
*/
TEST(autokill_basic)
{
int pidfd = -1, pollfd_fd = -1, ret;
struct pollfd pfd;
pid_t pid;
pid = create_autokill_child(&pidfd);
if (pid < 0 && errno == EINVAL)
SKIP(return, "CLONE_PIDFD_AUTOKILL not supported");
ASSERT_GE(pid, 0);
if (pid == 0) {
pause();
_exit(1);
}
ASSERT_GE(pidfd, 0);
/*
* Open a second pidfd via pidfd_open() so we can observe the
* child's death after closing the clone3 pidfd.
*/
pollfd_fd = sys_pidfd_open(pid, 0);
ASSERT_GE(pollfd_fd, 0);
/* Close the clone3 pidfd — this should trigger autokill. */
close(pidfd);
/* Wait for the child to die via the pidfd_open'd fd. */
pfd.fd = pollfd_fd;
pfd.events = POLLIN;
ret = poll(&pfd, 1, 5000);
ASSERT_EQ(ret, 1);
ASSERT_TRUE(pfd.revents & POLLIN);
/* Child should be autoreaped — no zombie. */
usleep(100000);
ret = waitpid(pid, NULL, WNOHANG);
ASSERT_EQ(ret, -1);
ASSERT_EQ(errno, ECHILD);
close(pollfd_fd);
}
/*
* CLONE_PIDFD_AUTOKILL without CLONE_PIDFD must fail with EINVAL.
*/
TEST(autokill_requires_pidfd)
{
struct __clone_args args = {
.flags = CLONE_PIDFD_AUTOKILL | CLONE_AUTOREAP,
.exit_signal = 0,
};
pid_t pid;
pid = sys_clone3(&args, sizeof(args));
ASSERT_EQ(pid, -1);
ASSERT_EQ(errno, EINVAL);
}
/*
* CLONE_PIDFD_AUTOKILL without CLONE_AUTOREAP must fail with EINVAL.
*/
TEST(autokill_requires_autoreap)
{
int pidfd = -1;
struct __clone_args args = {
.flags = CLONE_PIDFD | CLONE_PIDFD_AUTOKILL,
.exit_signal = 0,
.pidfd = ptr_to_u64(&pidfd),
};
pid_t pid;
pid = sys_clone3(&args, sizeof(args));
ASSERT_EQ(pid, -1);
ASSERT_EQ(errno, EINVAL);
}
/*
* CLONE_PIDFD_AUTOKILL with CLONE_THREAD must fail with EINVAL.
*/
TEST(autokill_rejects_thread)
{
int pidfd = -1;
struct __clone_args args = {
.flags = CLONE_PIDFD | CLONE_PIDFD_AUTOKILL |
CLONE_AUTOREAP | CLONE_THREAD |
CLONE_SIGHAND | CLONE_VM,
.exit_signal = 0,
.pidfd = ptr_to_u64(&pidfd),
};
pid_t pid;
pid = sys_clone3(&args, sizeof(args));
ASSERT_EQ(pid, -1);
ASSERT_EQ(errno, EINVAL);
}
/*
* Test that only the clone3 pidfd triggers autokill, not pidfd_open().
* Close the pidfd_open'd fd first — child should survive.
* Then close the clone3 pidfd — child should be killed and autoreaped.
*/
TEST(autokill_pidfd_open_no_effect)
{
int pidfd = -1, open_fd = -1, ret;
struct pollfd pfd;
pid_t pid;
pid = create_autokill_child(&pidfd);
if (pid < 0 && errno == EINVAL)
SKIP(return, "CLONE_PIDFD_AUTOKILL not supported");
ASSERT_GE(pid, 0);
if (pid == 0) {
pause();
_exit(1);
}
ASSERT_GE(pidfd, 0);
/* Open a second pidfd via pidfd_open(). */
open_fd = sys_pidfd_open(pid, 0);
ASSERT_GE(open_fd, 0);
/*
* Close the pidfd_open'd fd — child should survive because
* only the clone3 pidfd has autokill.
*/
close(open_fd);
usleep(200000);
/* Verify child is still alive by polling the clone3 pidfd. */
pfd.fd = pidfd;
pfd.events = POLLIN;
ret = poll(&pfd, 1, 0);
ASSERT_EQ(ret, 0) {
TH_LOG("Child died after closing pidfd_open fd — should still be alive");
}
/* Open another observation fd before triggering autokill. */
open_fd = sys_pidfd_open(pid, 0);
ASSERT_GE(open_fd, 0);
/* Now close the clone3 pidfd — this triggers autokill. */
close(pidfd);
pfd.fd = open_fd;
pfd.events = POLLIN;
ret = poll(&pfd, 1, 5000);
ASSERT_EQ(ret, 1);
ASSERT_TRUE(pfd.revents & POLLIN);
/* Child should be autoreaped — no zombie. */
usleep(100000);
ret = waitpid(pid, NULL, WNOHANG);
ASSERT_EQ(ret, -1);
ASSERT_EQ(errno, ECHILD);
close(open_fd);
}
/*
* Test that CLONE_PIDFD_AUTOKILL without CLONE_NNP fails with EPERM
* for an unprivileged caller.
*/
TEST(autokill_requires_cap_sys_admin)
{
int pidfd = -1, ret;
struct __clone_args args = {
.flags = CLONE_PIDFD | CLONE_PIDFD_AUTOKILL |
CLONE_AUTOREAP,
.exit_signal = 0,
.pidfd = ptr_to_u64(&pidfd),
};
pid_t pid;
/* Drop all capabilities so we lack CAP_SYS_ADMIN. */
ret = drop_all_caps();
ASSERT_EQ(ret, 0);
pid = sys_clone3(&args, sizeof(args));
ASSERT_EQ(pid, -1);
ASSERT_EQ(errno, EPERM);
}
/*
* Test that CLONE_PIDFD_AUTOKILL without CLONE_NNP succeeds with
* CAP_SYS_ADMIN.
*/
TEST(autokill_without_nnp_with_cap)
{
struct __clone_args args = {
.flags = CLONE_PIDFD | CLONE_PIDFD_AUTOKILL |
CLONE_AUTOREAP,
.exit_signal = 0,
};
struct pidfd_info info = { .mask = PIDFD_INFO_EXIT };
int pidfd = -1, ret;
struct pollfd pfd;
pid_t pid;
if (geteuid() != 0)
SKIP(return, "Need root/CAP_SYS_ADMIN");
args.pidfd = ptr_to_u64(&pidfd);
pid = sys_clone3(&args, sizeof(args));
if (pid < 0 && errno == EINVAL)
SKIP(return, "CLONE_PIDFD_AUTOKILL not supported");
ASSERT_GE(pid, 0);
if (pid == 0)
_exit(0);
ASSERT_GE(pidfd, 0);
/* Wait for child to exit. */
pfd.fd = pidfd;
pfd.events = POLLIN;
ret = poll(&pfd, 1, 5000);
ASSERT_EQ(ret, 1);
ret = ioctl(pidfd, PIDFD_GET_INFO, &info);
ASSERT_EQ(ret, 0);
ASSERT_TRUE(info.mask & PIDFD_INFO_EXIT);
ASSERT_TRUE(WIFEXITED(info.exit_code));
ASSERT_EQ(WEXITSTATUS(info.exit_code), 0);
close(pidfd);
}
TEST_HARNESS_MAIN
@@ -0,0 +1,116 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright (c) 2024 Christian Brauner <brauner@kernel.org>
#define _GNU_SOURCE
#include <fcntl.h>
#include <limits.h>
#include <sched.h>
#include <stdio.h>
#include <string.h>
#include <linux/fs.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/mount.h>
#include <unistd.h>
#include "pidfd.h"
#include "kselftest_harness.h"
#include "../filesystems/wrappers.h"
FIXTURE(pidfd_bind_mount) {
char template[PATH_MAX];
int fd_tmp;
int pidfd;
struct stat st1;
struct stat st2;
__u32 gen1;
__u32 gen2;
bool must_unmount;
};
FIXTURE_SETUP(pidfd_bind_mount)
{
self->fd_tmp = -EBADF;
self->must_unmount = false;
ASSERT_EQ(unshare(CLONE_NEWNS), 0);
ASSERT_LE(snprintf(self->template, PATH_MAX, "%s", P_tmpdir "/pidfd_bind_mount_XXXXXX"), PATH_MAX);
self->fd_tmp = mkstemp(self->template);
ASSERT_GE(self->fd_tmp, 0);
self->pidfd = sys_pidfd_open(getpid(), 0);
ASSERT_GE(self->pidfd, 0);
ASSERT_GE(fstat(self->pidfd, &self->st1), 0);
ASSERT_EQ(ioctl(self->pidfd, FS_IOC_GETVERSION, &self->gen1), 0);
}
FIXTURE_TEARDOWN(pidfd_bind_mount)
{
ASSERT_EQ(close(self->fd_tmp), 0);
if (self->must_unmount)
ASSERT_EQ(umount2(self->template, 0), 0);
ASSERT_EQ(unlink(self->template), 0);
}
/*
* Test that a detached mount can be created for a pidfd and then
* attached to the filesystem hierarchy.
*/
TEST_F(pidfd_bind_mount, bind_mount)
{
int fd_tree;
fd_tree = sys_open_tree(self->pidfd, "", OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC | AT_EMPTY_PATH);
ASSERT_GE(fd_tree, 0);
ASSERT_EQ(move_mount(fd_tree, "", self->fd_tmp, "", MOVE_MOUNT_F_EMPTY_PATH | MOVE_MOUNT_T_EMPTY_PATH), 0);
self->must_unmount = true;
ASSERT_EQ(close(fd_tree), 0);
}
/* Test that a pidfd can be reopened through procfs. */
TEST_F(pidfd_bind_mount, reopen)
{
int pidfd;
char proc_path[PATH_MAX];
sprintf(proc_path, "/proc/self/fd/%d", self->pidfd);
pidfd = open(proc_path, O_RDONLY | O_NOCTTY | O_CLOEXEC);
ASSERT_GE(pidfd, 0);
ASSERT_GE(fstat(self->pidfd, &self->st2), 0);
ASSERT_EQ(ioctl(self->pidfd, FS_IOC_GETVERSION, &self->gen2), 0);
ASSERT_TRUE(self->st1.st_dev == self->st2.st_dev && self->st1.st_ino == self->st2.st_ino);
ASSERT_TRUE(self->gen1 == self->gen2);
ASSERT_EQ(close(pidfd), 0);
}
/*
* Test that a detached mount can be created for a pidfd and then
* attached to the filesystem hierarchy and reopened.
*/
TEST_F(pidfd_bind_mount, bind_mount_reopen)
{
int fd_tree, fd_pidfd_mnt;
fd_tree = sys_open_tree(self->pidfd, "", OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC | AT_EMPTY_PATH);
ASSERT_GE(fd_tree, 0);
ASSERT_EQ(move_mount(fd_tree, "", self->fd_tmp, "", MOVE_MOUNT_F_EMPTY_PATH | MOVE_MOUNT_T_EMPTY_PATH), 0);
self->must_unmount = true;
fd_pidfd_mnt = openat(-EBADF, self->template, O_RDONLY | O_NOCTTY | O_CLOEXEC);
ASSERT_GE(fd_pidfd_mnt, 0);
ASSERT_GE(fstat(fd_tree, &self->st2), 0);
ASSERT_EQ(ioctl(fd_pidfd_mnt, FS_IOC_GETVERSION, &self->gen2), 0);
ASSERT_TRUE(self->st1.st_dev == self->st2.st_dev && self->st1.st_ino == self->st2.st_ino);
ASSERT_TRUE(self->gen1 == self->gen2);
ASSERT_EQ(close(fd_tree), 0);
ASSERT_EQ(close(fd_pidfd_mnt), 0);
}
TEST_HARNESS_MAIN
@@ -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);
}
@@ -0,0 +1,312 @@
// SPDX-License-Identifier: GPL-2.0
#define _GNU_SOURCE
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <linux/types.h>
#include <sched.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syscall.h>
#include <sys/wait.h>
#include <sys/mman.h>
#include <sys/mount.h>
#include "pidfd.h"
#include "kselftest.h"
struct error {
int code;
char msg[512];
};
static int error_set(struct error *err, int code, const char *fmt, ...)
{
va_list args;
int r;
if (code == PIDFD_PASS || !err || err->code != PIDFD_PASS)
return code;
err->code = code;
va_start(args, fmt);
r = vsnprintf(err->msg, sizeof(err->msg), fmt, args);
assert((size_t)r < sizeof(err->msg));
va_end(args);
return code;
}
static void error_report(struct error *err, const char *test_name)
{
switch (err->code) {
case PIDFD_ERROR:
ksft_exit_fail_msg("%s test: Fatal: %s\n", test_name, err->msg);
break;
case PIDFD_FAIL:
/* will be: not ok %d # error %s test: %s */
ksft_test_result_error("%s test: %s\n", test_name, err->msg);
break;
case PIDFD_SKIP:
/* will be: not ok %d # SKIP %s test: %s */
ksft_test_result_skip("%s test: %s\n", test_name, err->msg);
break;
case PIDFD_XFAIL:
ksft_test_result_pass("%s test: Expected failure: %s\n",
test_name, err->msg);
break;
case PIDFD_PASS:
ksft_test_result_pass("%s test: Passed\n", test_name);
break;
default:
ksft_exit_fail_msg("%s test: Unknown code: %d %s\n",
test_name, err->code, err->msg);
break;
}
}
static inline int error_check(struct error *err, const char *test_name)
{
/* In case of error we bail out and terminate the test program */
if (err->code == PIDFD_ERROR)
error_report(err, test_name);
return err->code;
}
#define CHILD_STACK_SIZE 8192
struct child {
char *stack;
pid_t pid;
int fd;
};
static struct child clone_newns(int (*fn)(void *), void *args,
struct error *err)
{
static int flags = CLONE_PIDFD | CLONE_NEWPID | CLONE_NEWNS | SIGCHLD;
struct child ret;
if (!(flags & CLONE_NEWUSER) && geteuid() != 0)
flags |= CLONE_NEWUSER;
ret.stack = mmap(NULL, CHILD_STACK_SIZE, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK, -1, 0);
if (ret.stack == MAP_FAILED) {
error_set(err, -1, "mmap of stack failed (errno %d)", errno);
return ret;
}
#ifdef __ia64__
ret.pid = __clone2(fn, ret.stack, CHILD_STACK_SIZE, flags, args, &ret.fd);
#else
ret.pid = clone(fn, ret.stack + CHILD_STACK_SIZE, flags, args, &ret.fd);
#endif
if (ret.pid < 0) {
error_set(err, PIDFD_ERROR, "clone failed (ret %d, errno %d)",
ret.fd, errno);
return ret;
}
ksft_print_msg("New child: %d, fd: %d\n", ret.pid, ret.fd);
return ret;
}
static inline void child_close(struct child *child)
{
close(child->fd);
}
static inline int child_join(struct child *child, struct error *err)
{
int r;
r = wait_for_pid(child->pid);
if (r < 0)
error_set(err, PIDFD_ERROR, "waitpid failed (ret %d, errno %d)",
r, errno);
else if (r > 0)
error_set(err, r, "child %d reported: %d", child->pid, r);
if (munmap(child->stack, CHILD_STACK_SIZE)) {
error_set(err, -1, "munmap of child stack failed (errno %d)", errno);
r = -1;
}
ksft_print_msg("waitpid WEXITSTATUS=%d\n", r);
return r;
}
static inline int child_join_close(struct child *child, struct error *err)
{
child_close(child);
return child_join(child, err);
}
static inline void trim_newline(char *str)
{
char *pos = strrchr(str, '\n');
if (pos)
*pos = '\0';
}
static int verify_fdinfo(int pidfd, struct error *err, const char *prefix,
size_t prefix_len, const char *expect, ...)
{
char buffer[512] = {0, };
char path[512] = {0, };
va_list args;
FILE *f;
char *line = NULL;
size_t n = 0;
int found = 0;
int r;
va_start(args, expect);
r = vsnprintf(buffer, sizeof(buffer), expect, args);
assert((size_t)r < sizeof(buffer));
va_end(args);
snprintf(path, sizeof(path), "/proc/self/fdinfo/%d", pidfd);
f = fopen(path, "re");
if (!f)
return error_set(err, PIDFD_ERROR, "fdinfo open failed for %d",
pidfd);
while (getline(&line, &n, f) != -1) {
char *val;
if (strncmp(line, prefix, prefix_len))
continue;
found = 1;
val = line + prefix_len;
r = strcmp(val, buffer);
if (r != 0) {
trim_newline(line);
trim_newline(buffer);
error_set(err, PIDFD_FAIL, "%s '%s' != '%s'",
prefix, val, buffer);
}
break;
}
free(line);
fclose(f);
if (found == 0)
return error_set(err, PIDFD_FAIL, "%s not found for fd %d",
prefix, pidfd);
return PIDFD_PASS;
}
static int child_fdinfo_nspid_test(void *args)
{
struct error err;
int pidfd;
int r;
/* if we got no fd for the sibling, we are done */
if (!args)
return PIDFD_PASS;
/* verify that we can not resolve the pidfd for a process
* in a sibling pid namespace, i.e. a pid namespace it is
* not in our or a descended namespace
*/
r = mount(NULL, "/", NULL, MS_REC | MS_PRIVATE, 0);
if (r < 0) {
ksft_print_msg("Failed to remount / private\n");
return PIDFD_ERROR;
}
(void)umount2("/proc", MNT_DETACH);
r = mount("proc", "/proc", "proc", 0, NULL);
if (r < 0) {
ksft_print_msg("Failed to remount /proc\n");
return PIDFD_ERROR;
}
pidfd = *(int *)args;
r = verify_fdinfo(pidfd, &err, "NSpid:", 6, "\t0\n");
if (r != PIDFD_PASS)
ksft_print_msg("NSpid fdinfo check failed: %s\n", err.msg);
return r;
}
static void test_pidfd_fdinfo_nspid(void)
{
struct child a, b;
struct error err = {0, };
const char *test_name = "pidfd check for NSpid in fdinfo";
/* Create a new child in a new pid and mount namespace */
a = clone_newns(child_fdinfo_nspid_test, NULL, &err);
error_check(&err, test_name);
/* Pass the pidfd representing the first child to the
* second child, which will be in a sibling pid namespace,
* which means that the fdinfo NSpid entry for the pidfd
* should only contain '0'.
*/
b = clone_newns(child_fdinfo_nspid_test, &a.fd, &err);
error_check(&err, test_name);
/* The children will have pid 1 in the new pid namespace,
* so the line must be 'NSPid:\t<pid>\t1'.
*/
verify_fdinfo(a.fd, &err, "NSpid:", 6, "\t%d\t%d\n", a.pid, 1);
verify_fdinfo(b.fd, &err, "NSpid:", 6, "\t%d\t%d\n", b.pid, 1);
/* wait for the process, check the exit status and set
* 'err' accordingly, if it is not already set.
*/
child_join_close(&a, &err);
child_join_close(&b, &err);
error_report(&err, test_name);
}
static void test_pidfd_dead_fdinfo(void)
{
struct child a;
struct error err = {0, };
const char *test_name = "pidfd check fdinfo for dead process";
/* Create a new child in a new pid and mount namespace */
a = clone_newns(child_fdinfo_nspid_test, NULL, &err);
error_check(&err, test_name);
child_join(&a, &err);
verify_fdinfo(a.fd, &err, "Pid:", 4, "\t-1\n");
verify_fdinfo(a.fd, &err, "NSpid:", 6, "\t-1\n");
child_close(&a);
error_report(&err, test_name);
}
int main(int argc, char **argv)
{
ksft_print_header();
ksft_set_plan(2);
test_pidfd_fdinfo_nspid();
test_pidfd_dead_fdinfo();
ksft_exit_pass();
}
@@ -0,0 +1,564 @@
// 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 <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(file_handle)
{
pid_t pid;
int pidfd;
pid_t child_pid1;
int child_pidfd1;
pid_t child_pid2;
int child_pidfd2;
pid_t child_pid3;
int child_pidfd3;
};
FIXTURE_SETUP(file_handle)
{
int ret;
int ipc_sockets[2];
char c;
self->pid = getpid();
self->pidfd = sys_pidfd_open(self->pid, 0);
ASSERT_GE(self->pidfd, 0);
ret = socketpair(AF_LOCAL, SOCK_STREAM | SOCK_CLOEXEC, 0, ipc_sockets);
EXPECT_EQ(ret, 0);
self->child_pid1 = create_child(&self->child_pidfd1, CLONE_NEWUSER);
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);
}
close(ipc_sockets[1]);
ASSERT_EQ(read_nointr(ipc_sockets[0], &c, 1), 1);
close(ipc_sockets[0]);
ret = socketpair(AF_LOCAL, SOCK_STREAM | SOCK_CLOEXEC, 0, ipc_sockets);
EXPECT_EQ(ret, 0);
self->child_pid2 = create_child(&self->child_pidfd2, CLONE_NEWUSER | CLONE_NEWPID);
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);
}
close(ipc_sockets[1]);
ASSERT_EQ(read_nointr(ipc_sockets[0], &c, 1), 1);
close(ipc_sockets[0]);
ret = socketpair(AF_LOCAL, SOCK_STREAM | SOCK_CLOEXEC, 0, ipc_sockets);
EXPECT_EQ(ret, 0);
self->child_pid3 = create_child(&self->child_pidfd3, CLONE_NEWUSER | CLONE_NEWPID);
EXPECT_GE(self->child_pid3, 0);
if (self->child_pid3 == 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);
}
close(ipc_sockets[1]);
ASSERT_EQ(read_nointr(ipc_sockets[0], &c, 1), 1);
close(ipc_sockets[0]);
}
FIXTURE_TEARDOWN(file_handle)
{
EXPECT_EQ(close(self->pidfd), 0);
EXPECT_EQ(sys_pidfd_send_signal(self->child_pidfd1, SIGKILL, NULL, 0), 0);
if (self->child_pidfd1 >= 0)
EXPECT_EQ(0, close(self->child_pidfd1));
EXPECT_EQ(sys_waitid(P_PID, self->child_pid1, NULL, WEXITED), 0);
EXPECT_EQ(sys_pidfd_send_signal(self->child_pidfd2, SIGKILL, NULL, 0), 0);
if (self->child_pidfd2 >= 0)
EXPECT_EQ(0, close(self->child_pidfd2));
EXPECT_EQ(sys_waitid(P_PID, self->child_pid2, NULL, WEXITED), 0);
if (self->child_pidfd3 >= 0) {
EXPECT_EQ(sys_pidfd_send_signal(self->child_pidfd3, SIGKILL, NULL, 0), 0);
EXPECT_EQ(0, close(self->child_pidfd3));
EXPECT_EQ(sys_waitid(P_PID, self->child_pid3, NULL, WEXITED), 0);
}
}
/*
* Test that we can decode a pidfs file handle in the same pid
* namespace.
*/
TEST_F(file_handle, file_handle_same_pidns)
{
int mnt_id;
struct file_handle *fh;
int pidfd = -EBADF;
struct stat st1, st2;
fh = malloc(sizeof(struct file_handle) + MAX_HANDLE_SZ);
ASSERT_NE(fh, NULL);
memset(fh, 0, sizeof(struct file_handle) + MAX_HANDLE_SZ);
fh->handle_bytes = MAX_HANDLE_SZ;
ASSERT_EQ(name_to_handle_at(self->child_pidfd1, "", fh, &mnt_id, AT_EMPTY_PATH), 0);
ASSERT_EQ(fstat(self->child_pidfd1, &st1), 0);
pidfd = open_by_handle_at(self->pidfd, fh, 0);
ASSERT_GE(pidfd, 0);
ASSERT_EQ(fstat(pidfd, &st2), 0);
ASSERT_TRUE(st1.st_dev == st2.st_dev && st1.st_ino == st2.st_ino);
ASSERT_EQ(close(pidfd), 0);
pidfd = open_by_handle_at(self->pidfd, fh, O_CLOEXEC);
ASSERT_GE(pidfd, 0);
ASSERT_EQ(fstat(pidfd, &st2), 0);
ASSERT_TRUE(st1.st_dev == st2.st_dev && st1.st_ino == st2.st_ino);
ASSERT_EQ(close(pidfd), 0);
pidfd = open_by_handle_at(self->pidfd, fh, O_NONBLOCK);
ASSERT_GE(pidfd, 0);
ASSERT_EQ(fstat(pidfd, &st2), 0);
ASSERT_TRUE(st1.st_dev == st2.st_dev && st1.st_ino == st2.st_ino);
ASSERT_EQ(close(pidfd), 0);
free(fh);
}
/*
* Test that we can decode a pidfs file handle from a child pid
* namespace.
*/
TEST_F(file_handle, file_handle_child_pidns)
{
int mnt_id;
struct file_handle *fh;
int pidfd = -EBADF;
struct stat st1, st2;
fh = malloc(sizeof(struct file_handle) + MAX_HANDLE_SZ);
ASSERT_NE(fh, NULL);
memset(fh, 0, sizeof(struct file_handle) + MAX_HANDLE_SZ);
fh->handle_bytes = MAX_HANDLE_SZ;
ASSERT_EQ(name_to_handle_at(self->child_pidfd2, "", fh, &mnt_id, AT_EMPTY_PATH), 0);
ASSERT_EQ(fstat(self->child_pidfd2, &st1), 0);
pidfd = open_by_handle_at(self->pidfd, fh, 0);
ASSERT_GE(pidfd, 0);
ASSERT_EQ(fstat(pidfd, &st2), 0);
ASSERT_TRUE(st1.st_dev == st2.st_dev && st1.st_ino == st2.st_ino);
ASSERT_EQ(close(pidfd), 0);
pidfd = open_by_handle_at(self->pidfd, fh, O_CLOEXEC);
ASSERT_GE(pidfd, 0);
ASSERT_EQ(fstat(pidfd, &st2), 0);
ASSERT_TRUE(st1.st_dev == st2.st_dev && st1.st_ino == st2.st_ino);
ASSERT_EQ(close(pidfd), 0);
pidfd = open_by_handle_at(self->pidfd, fh, O_NONBLOCK);
ASSERT_GE(pidfd, 0);
ASSERT_EQ(fstat(pidfd, &st2), 0);
ASSERT_TRUE(st1.st_dev == st2.st_dev && st1.st_ino == st2.st_ino);
ASSERT_EQ(close(pidfd), 0);
free(fh);
}
/*
* Test that we fail to decode a pidfs file handle from an ancestor
* child pid namespace.
*/
TEST_F(file_handle, file_handle_foreign_pidns)
{
int mnt_id;
struct file_handle *fh;
pid_t pid;
fh = malloc(sizeof(struct file_handle) + MAX_HANDLE_SZ);
ASSERT_NE(fh, NULL);
memset(fh, 0, sizeof(struct file_handle) + MAX_HANDLE_SZ);
fh->handle_bytes = MAX_HANDLE_SZ;
ASSERT_EQ(name_to_handle_at(self->pidfd, "", fh, &mnt_id, AT_EMPTY_PATH), 0);
ASSERT_EQ(setns(self->child_pidfd2, CLONE_NEWUSER | CLONE_NEWPID), 0);
pid = fork();
ASSERT_GE(pid, 0);
if (pid == 0) {
int pidfd = open_by_handle_at(self->pidfd, fh, 0);
if (pidfd >= 0) {
TH_LOG("Managed to open pidfd outside of the caller's pid namespace hierarchy");
_exit(1);
}
_exit(0);
}
ASSERT_EQ(wait_for_pid(pid), 0);
free(fh);
}
/*
* Test that we can decode a pidfs file handle of a process that has
* exited but not been reaped.
*/
TEST_F(file_handle, pid_has_exited)
{
int mnt_id, pidfd, child_pidfd3;
struct file_handle *fh;
struct stat st1, st2;
fh = malloc(sizeof(struct file_handle) + MAX_HANDLE_SZ);
ASSERT_NE(fh, NULL);
memset(fh, 0, sizeof(struct file_handle) + MAX_HANDLE_SZ);
fh->handle_bytes = MAX_HANDLE_SZ;
ASSERT_EQ(name_to_handle_at(self->child_pidfd3, "", fh, &mnt_id, AT_EMPTY_PATH), 0);
ASSERT_EQ(fstat(self->child_pidfd3, &st1), 0);
pidfd = open_by_handle_at(self->pidfd, fh, 0);
ASSERT_GE(pidfd, 0);
ASSERT_EQ(fstat(pidfd, &st2), 0);
ASSERT_TRUE(st1.st_dev == st2.st_dev && st1.st_ino == st2.st_ino);
ASSERT_EQ(close(pidfd), 0);
child_pidfd3 = self->child_pidfd3;
self->child_pidfd3 = -EBADF;
EXPECT_EQ(sys_pidfd_send_signal(child_pidfd3, SIGKILL, NULL, 0), 0);
EXPECT_EQ(close(child_pidfd3), 0);
EXPECT_EQ(sys_waitid(P_PID, self->child_pid3, NULL, WEXITED | WNOWAIT), 0);
pidfd = open_by_handle_at(self->pidfd, fh, 0);
ASSERT_GE(pidfd, 0);
EXPECT_EQ(sys_waitid(P_PID, self->child_pid3, NULL, WEXITED), 0);
}
/*
* Test that we fail to decode a pidfs file handle of a process that has
* already been reaped.
*/
TEST_F(file_handle, pid_has_been_reaped)
{
int mnt_id, pidfd, child_pidfd3;
struct file_handle *fh;
struct stat st1, st2;
fh = malloc(sizeof(struct file_handle) + MAX_HANDLE_SZ);
ASSERT_NE(fh, NULL);
memset(fh, 0, sizeof(struct file_handle) + MAX_HANDLE_SZ);
fh->handle_bytes = MAX_HANDLE_SZ;
ASSERT_EQ(name_to_handle_at(self->child_pidfd3, "", fh, &mnt_id, AT_EMPTY_PATH), 0);
ASSERT_EQ(fstat(self->child_pidfd3, &st1), 0);
pidfd = open_by_handle_at(self->pidfd, fh, 0);
ASSERT_GE(pidfd, 0);
ASSERT_EQ(fstat(pidfd, &st2), 0);
ASSERT_TRUE(st1.st_dev == st2.st_dev && st1.st_ino == st2.st_ino);
ASSERT_EQ(close(pidfd), 0);
child_pidfd3 = self->child_pidfd3;
self->child_pidfd3 = -EBADF;
EXPECT_EQ(sys_pidfd_send_signal(child_pidfd3, SIGKILL, NULL, 0), 0);
EXPECT_EQ(close(child_pidfd3), 0);
EXPECT_EQ(sys_waitid(P_PID, self->child_pid3, NULL, WEXITED), 0);
pidfd = open_by_handle_at(self->pidfd, fh, 0);
ASSERT_LT(pidfd, 0);
}
/*
* Test valid flags to open a pidfd file handle. Note, that
* PIDFD_NONBLOCK is defined as O_NONBLOCK and O_NONBLOCK is an alias to
* O_NDELAY. Also note that PIDFD_THREAD is an alias for O_EXCL.
*/
TEST_F(file_handle, open_by_handle_at_valid_flags)
{
int mnt_id;
struct file_handle *fh;
int pidfd = -EBADF;
struct stat st1, st2;
fh = malloc(sizeof(struct file_handle) + MAX_HANDLE_SZ);
ASSERT_NE(fh, NULL);
memset(fh, 0, sizeof(struct file_handle) + MAX_HANDLE_SZ);
fh->handle_bytes = MAX_HANDLE_SZ;
ASSERT_EQ(name_to_handle_at(self->child_pidfd2, "", fh, &mnt_id, AT_EMPTY_PATH), 0);
ASSERT_EQ(fstat(self->child_pidfd2, &st1), 0);
pidfd = open_by_handle_at(self->pidfd, fh,
O_RDONLY |
O_WRONLY |
O_RDWR |
O_NONBLOCK |
O_NDELAY |
O_CLOEXEC |
O_EXCL);
ASSERT_GE(pidfd, 0);
ASSERT_NE(fcntl(pidfd, F_GETFL) & PIDFD_THREAD, 0);
ASSERT_EQ(fstat(pidfd, &st2), 0);
ASSERT_TRUE(st1.st_dev == st2.st_dev && st1.st_ino == st2.st_ino);
ASSERT_EQ(close(pidfd), 0);
}
/*
* Test that invalid flags passed to open a pidfd file handle are
* rejected.
*/
TEST_F(file_handle, open_by_handle_at_invalid_flags)
{
int mnt_id;
struct file_handle *fh;
int pidfd = -EBADF;
static const struct invalid_pidfs_file_handle_flags {
int oflag;
const char *oflag_name;
} invalid_pidfs_file_handle_flags[] = {
{ FASYNC, "FASYNC" },
{ O_CREAT, "O_CREAT" },
{ O_NOCTTY, "O_NOCTTY" },
{ O_CREAT, "O_CREAT" },
{ O_TRUNC, "O_TRUNC" },
{ O_APPEND, "O_APPEND" },
{ O_SYNC, "O_SYNC" },
{ O_DSYNC, "O_DSYNC" },
{ O_DIRECT, "O_DIRECT" },
{ O_DIRECTORY, "O_DIRECTORY" },
{ O_NOFOLLOW, "O_NOFOLLOW" },
{ O_NOATIME, "O_NOATIME" },
{ O_PATH, "O_PATH" },
{ O_TMPFILE, "O_TMPFILE" },
/*
* O_LARGEFILE is added implicitly by
* open_by_handle_at() so pidfs simply masks it off.
*/
};
fh = malloc(sizeof(struct file_handle) + MAX_HANDLE_SZ);
ASSERT_NE(fh, NULL);
memset(fh, 0, sizeof(struct file_handle) + MAX_HANDLE_SZ);
fh->handle_bytes = MAX_HANDLE_SZ;
ASSERT_EQ(name_to_handle_at(self->child_pidfd2, "", fh, &mnt_id, AT_EMPTY_PATH), 0);
for (int i = 0; i < ARRAY_SIZE(invalid_pidfs_file_handle_flags); i++) {
pidfd = open_by_handle_at(self->pidfd, fh, invalid_pidfs_file_handle_flags[i].oflag);
ASSERT_LT(pidfd, 0) {
TH_LOG("open_by_handle_at() succeeded with invalid flags: %s", invalid_pidfs_file_handle_flags[i].oflag_name);
}
}
}
/* Test that lookup fails. */
TEST_F(file_handle, lookup_must_fail)
{
int mnt_id;
struct file_handle *fh;
fh = malloc(sizeof(struct file_handle) + MAX_HANDLE_SZ);
ASSERT_NE(fh, NULL);
memset(fh, 0, sizeof(struct file_handle) + MAX_HANDLE_SZ);
fh->handle_bytes = MAX_HANDLE_SZ;
ASSERT_NE(name_to_handle_at(self->child_pidfd2, "lookup-is-not-possible-with-pidfs", fh, &mnt_id, AT_EMPTY_PATH), 0);
ASSERT_EQ(errno, ENOTDIR);
ASSERT_NE(name_to_handle_at(self->child_pidfd2, "lookup-is-not-possible-with-pidfs", fh, &mnt_id, 0), 0);
ASSERT_EQ(errno, ENOTDIR);
}
#ifndef AT_HANDLE_CONNECTABLE
#define AT_HANDLE_CONNECTABLE 0x002
#endif
/*
* Test that AT_HANDLE_CONNECTABLE is rejected. Connectable file handles
* don't make sense for pidfs. Note that currently AT_HANDLE_CONNECTABLE
* is rejected because it is incompatible with AT_EMPTY_PATH which is
* required with pidfds as we don't support lookup.
*/
TEST_F(file_handle, invalid_name_to_handle_at_flags)
{
int mnt_id;
struct file_handle *fh;
fh = malloc(sizeof(struct file_handle) + MAX_HANDLE_SZ);
ASSERT_NE(fh, NULL);
memset(fh, 0, sizeof(struct file_handle) + MAX_HANDLE_SZ);
fh->handle_bytes = MAX_HANDLE_SZ;
ASSERT_NE(name_to_handle_at(self->child_pidfd2, "", fh, &mnt_id, AT_EMPTY_PATH | AT_HANDLE_CONNECTABLE), 0);
}
#ifndef AT_HANDLE_FID
#define AT_HANDLE_FID 0x200
#endif
/*
* Test that a request with AT_HANDLE_FID always leads to decodable file
* handle as pidfs always provides export operations.
*/
TEST_F(file_handle, valid_name_to_handle_at_flags)
{
int mnt_id, pidfd;
struct file_handle *fh;
struct stat st1, st2;
fh = malloc(sizeof(struct file_handle) + MAX_HANDLE_SZ);
ASSERT_NE(fh, NULL);
memset(fh, 0, sizeof(struct file_handle) + MAX_HANDLE_SZ);
fh->handle_bytes = MAX_HANDLE_SZ;
ASSERT_EQ(name_to_handle_at(self->child_pidfd2, "", fh, &mnt_id, AT_EMPTY_PATH | AT_HANDLE_FID), 0);
ASSERT_EQ(fstat(self->child_pidfd2, &st1), 0);
pidfd = open_by_handle_at(self->pidfd, fh, 0);
ASSERT_GE(pidfd, 0);
ASSERT_EQ(fstat(pidfd, &st2), 0);
ASSERT_TRUE(st1.st_dev == st2.st_dev && st1.st_ino == st2.st_ino);
ASSERT_EQ(close(pidfd), 0);
}
/*
* That we decode a file handle without having to pass a pidfd.
*/
TEST_F(file_handle, decode_purely_based_on_file_handle)
{
int mnt_id;
struct file_handle *fh;
int pidfd = -EBADF;
struct stat st1, st2;
fh = malloc(sizeof(struct file_handle) + MAX_HANDLE_SZ);
ASSERT_NE(fh, NULL);
memset(fh, 0, sizeof(struct file_handle) + MAX_HANDLE_SZ);
fh->handle_bytes = MAX_HANDLE_SZ;
ASSERT_EQ(name_to_handle_at(self->child_pidfd1, "", fh, &mnt_id, AT_EMPTY_PATH), 0);
ASSERT_EQ(fstat(self->child_pidfd1, &st1), 0);
pidfd = open_by_handle_at(FD_PIDFS_ROOT, fh, 0);
ASSERT_GE(pidfd, 0);
ASSERT_EQ(fstat(pidfd, &st2), 0);
ASSERT_TRUE(st1.st_dev == st2.st_dev && st1.st_ino == st2.st_ino);
ASSERT_EQ(close(pidfd), 0);
pidfd = open_by_handle_at(FD_PIDFS_ROOT, fh, O_CLOEXEC);
ASSERT_GE(pidfd, 0);
ASSERT_EQ(fstat(pidfd, &st2), 0);
ASSERT_TRUE(st1.st_dev == st2.st_dev && st1.st_ino == st2.st_ino);
ASSERT_EQ(close(pidfd), 0);
pidfd = open_by_handle_at(FD_PIDFS_ROOT, fh, O_NONBLOCK);
ASSERT_GE(pidfd, 0);
ASSERT_EQ(fstat(pidfd, &st2), 0);
ASSERT_TRUE(st1.st_dev == st2.st_dev && st1.st_ino == st2.st_ino);
ASSERT_EQ(close(pidfd), 0);
pidfd = open_by_handle_at(self->pidfd, fh, 0);
ASSERT_GE(pidfd, 0);
ASSERT_EQ(fstat(pidfd, &st2), 0);
ASSERT_TRUE(st1.st_dev == st2.st_dev && st1.st_ino == st2.st_ino);
ASSERT_EQ(close(pidfd), 0);
pidfd = open_by_handle_at(-EBADF, fh, 0);
ASSERT_LT(pidfd, 0);
pidfd = open_by_handle_at(AT_FDCWD, fh, 0);
ASSERT_LT(pidfd, 0);
free(fh);
}
TEST_HARNESS_MAIN
@@ -0,0 +1,275 @@
// 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 <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 "pidfd.h"
#include "kselftest_harness.h"
/*
* UNKNOWN_FD is an fd number that should never exist in the child, as it is
* used to check the negative case.
*/
#define UNKNOWN_FD 111
#define UID_NOBODY 65535
static int sys_kcmp(pid_t pid1, pid_t pid2, int type, unsigned long idx1,
unsigned long idx2)
{
return syscall(__NR_kcmp, pid1, pid2, type, idx1, idx2);
}
static int __child(int sk, int memfd)
{
int ret;
char buf;
/*
* Ensure we don't leave around a bunch of orphaned children if our
* tests fail.
*/
ret = prctl(PR_SET_PDEATHSIG, SIGKILL);
if (ret) {
fprintf(stderr, "%s: Child could not set DEATHSIG\n",
strerror(errno));
return -1;
}
ret = send(sk, &memfd, sizeof(memfd), 0);
if (ret != sizeof(memfd)) {
fprintf(stderr, "%s: Child failed to send fd number\n",
strerror(errno));
return -1;
}
/*
* The fixture setup is completed at this point. The tests will run.
*
* This blocking recv enables the parent to message the child.
* Either we will read 'P' off of the sk, indicating that we need
* to disable ptrace, or we will read a 0, indicating that the other
* side has closed the sk. This occurs during fixture teardown time,
* indicating that the child should exit.
*/
while ((ret = recv(sk, &buf, sizeof(buf), 0)) > 0) {
if (buf == 'P') {
ret = prctl(PR_SET_DUMPABLE, 0);
if (ret < 0) {
fprintf(stderr,
"%s: Child failed to disable ptrace\n",
strerror(errno));
return -1;
}
} else {
fprintf(stderr, "Child received unknown command %c\n",
buf);
return -1;
}
ret = send(sk, &buf, sizeof(buf), 0);
if (ret != 1) {
fprintf(stderr, "%s: Child failed to ack\n",
strerror(errno));
return -1;
}
}
if (ret < 0) {
fprintf(stderr, "%s: Child failed to read from socket\n",
strerror(errno));
return -1;
}
return 0;
}
static int child(int sk)
{
int memfd, ret;
memfd = sys_memfd_create("test", 0);
if (memfd < 0) {
fprintf(stderr, "%s: Child could not create memfd\n",
strerror(errno));
ret = -1;
} else {
ret = __child(sk, memfd);
close(memfd);
}
close(sk);
return ret;
}
FIXTURE(child)
{
/*
* remote_fd is the number of the FD which we are trying to retrieve
* from the child.
*/
int remote_fd;
/* pid points to the child which we are fetching FDs from */
pid_t pid;
/* pidfd is the pidfd of the child */
int pidfd;
/*
* sk is our side of the socketpair used to communicate with the child.
* When it is closed, the child will exit.
*/
int sk;
bool ignore_child_result;
};
FIXTURE_SETUP(child)
{
int ret, sk_pair[2];
ASSERT_EQ(0, socketpair(PF_LOCAL, SOCK_SEQPACKET, 0, sk_pair)) {
TH_LOG("%s: failed to create socketpair", strerror(errno));
}
self->sk = sk_pair[0];
self->pid = fork();
ASSERT_GE(self->pid, 0);
if (self->pid == 0) {
close(sk_pair[0]);
if (child(sk_pair[1]))
_exit(EXIT_FAILURE);
_exit(EXIT_SUCCESS);
}
close(sk_pair[1]);
self->pidfd = sys_pidfd_open(self->pid, 0);
ASSERT_GE(self->pidfd, 0);
/*
* Wait for the child to complete setup. It'll send the remote memfd's
* number when ready.
*/
ret = recv(sk_pair[0], &self->remote_fd, sizeof(self->remote_fd), 0);
ASSERT_EQ(sizeof(self->remote_fd), ret);
}
FIXTURE_TEARDOWN(child)
{
int ret;
EXPECT_EQ(0, close(self->pidfd));
EXPECT_EQ(0, close(self->sk));
ret = wait_for_pid(self->pid);
if (!self->ignore_child_result)
EXPECT_EQ(0, ret);
}
TEST_F(child, disable_ptrace)
{
int uid, fd;
char c;
/*
* Turn into nobody if we're root, to avoid CAP_SYS_PTRACE
*
* The tests should run in their own process, so even this test fails,
* it shouldn't result in subsequent tests failing.
*/
uid = getuid();
if (uid == 0)
ASSERT_EQ(0, seteuid(UID_NOBODY));
ASSERT_EQ(1, send(self->sk, "P", 1, 0));
ASSERT_EQ(1, recv(self->sk, &c, 1, 0));
fd = sys_pidfd_getfd(self->pidfd, self->remote_fd, 0);
EXPECT_EQ(-1, fd);
EXPECT_EQ(EPERM, errno);
if (uid == 0)
ASSERT_EQ(0, seteuid(0));
}
TEST_F(child, fetch_fd)
{
int fd, ret;
fd = sys_pidfd_getfd(self->pidfd, self->remote_fd, 0);
ASSERT_GE(fd, 0);
ret = sys_kcmp(getpid(), self->pid, KCMP_FILE, fd, self->remote_fd);
if (ret < 0 && errno == ENOSYS)
SKIP(return, "kcmp() syscall not supported");
EXPECT_EQ(ret, 0);
ret = fcntl(fd, F_GETFD);
ASSERT_GE(ret, 0);
EXPECT_GE(ret & FD_CLOEXEC, 0);
close(fd);
}
TEST_F(child, test_unknown_fd)
{
int fd;
fd = sys_pidfd_getfd(self->pidfd, UNKNOWN_FD, 0);
EXPECT_EQ(-1, fd) {
TH_LOG("getfd succeeded while fetching unknown fd");
};
EXPECT_EQ(EBADF, errno) {
TH_LOG("%s: getfd did not get EBADF", strerror(errno));
}
}
TEST(flags_set)
{
ASSERT_EQ(-1, sys_pidfd_getfd(0, 0, 1));
EXPECT_EQ(errno, EINVAL);
}
TEST_F(child, no_strange_EBADF)
{
struct pollfd fds;
self->ignore_child_result = true;
fds.fd = self->pidfd;
fds.events = POLLIN;
ASSERT_EQ(kill(self->pid, SIGKILL), 0);
ASSERT_EQ(poll(&fds, 1, 5000), 1);
/*
* It used to be that pidfd_getfd() could race with the exiting thread
* between exit_files() and release_task(), and get a non-null task
* with a NULL files struct, and you'd get EBADF, which was slightly
* confusing.
*/
errno = 0;
EXPECT_EQ(sys_pidfd_getfd(self->pidfd, self->remote_fd, 0), -1);
EXPECT_EQ(errno, ESRCH);
}
#if __NR_pidfd_getfd == -1
int main(void)
{
fprintf(stderr, "__NR_pidfd_getfd undefined. The pidfd_getfd syscall is unavailable. Test aborting\n");
return KSFT_SKIP;
}
#else
TEST_HARNESS_MAIN
#endif
@@ -0,0 +1,767 @@
// 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 until 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);
ASSERT_EQ(errno, ENOENT);
/* 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));
/* Even though the thread-group exited successfully it will still report the group exit code. */
ASSERT_TRUE(WIFSIGNALED(info.exit_code));
ASSERT_EQ(WTERMSIG(info.exit_code), SIGKILL);
/*
* 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));
/* Even though the thread-group exited successfully it will still report the group exit code. */
ASSERT_TRUE(WIFSIGNALED(info2.exit_code));
ASSERT_EQ(WTERMSIG(info2.exit_code), SIGKILL);
/* 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: PIDFD_INFO_SUPPORTED_MASK field
*
* Verify that when PIDFD_INFO_SUPPORTED_MASK is requested, the kernel
* returns the supported_mask field indicating which flags the kernel supports.
*/
TEST(supported_mask_field)
{
struct pidfd_info info = {
.mask = PIDFD_INFO_SUPPORTED_MASK,
};
int pidfd;
pid_t pid;
pid = create_child(&pidfd, 0);
ASSERT_GE(pid, 0);
if (pid == 0)
pause();
/* Request supported_mask field */
ASSERT_EQ(ioctl(pidfd, PIDFD_GET_INFO, &info), 0);
/* Verify PIDFD_INFO_SUPPORTED_MASK is set in the reply */
ASSERT_TRUE(!!(info.mask & PIDFD_INFO_SUPPORTED_MASK));
/* Verify supported_mask contains expected flags */
ASSERT_TRUE(!!(info.supported_mask & PIDFD_INFO_PID));
ASSERT_TRUE(!!(info.supported_mask & PIDFD_INFO_CREDS));
ASSERT_TRUE(!!(info.supported_mask & PIDFD_INFO_CGROUPID));
ASSERT_TRUE(!!(info.supported_mask & PIDFD_INFO_EXIT));
ASSERT_TRUE(!!(info.supported_mask & PIDFD_INFO_COREDUMP));
ASSERT_TRUE(!!(info.supported_mask & PIDFD_INFO_SUPPORTED_MASK));
ASSERT_TRUE(!!(info.supported_mask & PIDFD_INFO_COREDUMP_SIGNAL));
ASSERT_TRUE(!!(info.supported_mask & PIDFD_INFO_COREDUMP_CODE));
/* Clean up */
sys_pidfd_send_signal(pidfd, SIGKILL, NULL, 0);
sys_waitid(P_PIDFD, pidfd, NULL, WEXITED);
close(pidfd);
}
/*
* Test: PIDFD_INFO_SUPPORTED_MASK always available
*
* Verify that supported_mask is returned even when other fields are requested.
*/
TEST(supported_mask_with_other_fields)
{
struct pidfd_info info = {
.mask = PIDFD_INFO_CGROUPID | PIDFD_INFO_SUPPORTED_MASK,
};
int pidfd;
pid_t pid;
pid = create_child(&pidfd, 0);
ASSERT_GE(pid, 0);
if (pid == 0)
pause();
ASSERT_EQ(ioctl(pidfd, PIDFD_GET_INFO, &info), 0);
/* Both fields should be present */
ASSERT_TRUE(!!(info.mask & PIDFD_INFO_CGROUPID));
ASSERT_TRUE(!!(info.mask & PIDFD_INFO_SUPPORTED_MASK));
ASSERT_NE(info.supported_mask, 0);
/* Clean up */
sys_pidfd_send_signal(pidfd, SIGKILL, NULL, 0);
sys_waitid(P_PIDFD, pidfd, NULL, WEXITED);
close(pidfd);
}
TEST_HARNESS_MAIN
@@ -0,0 +1,219 @@
// SPDX-License-Identifier: GPL-2.0
#define _GNU_SOURCE
#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
#include <limits.h>
#include <linux/types.h>
#include <sched.h>
#include <signal.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syscall.h>
#include <sys/ioctl.h>
#include <sys/mount.h>
#include <sys/prctl.h>
#include <sys/wait.h>
#include <unistd.h>
#include "pidfd.h"
#include "kselftest.h"
static int safe_int(const char *numstr, int *converted)
{
char *err = NULL;
long sli;
errno = 0;
sli = strtol(numstr, &err, 0);
if (errno == ERANGE && (sli == LONG_MAX || sli == LONG_MIN))
return -ERANGE;
if (errno != 0 && sli == 0)
return -EINVAL;
if (err == numstr || *err != '\0')
return -EINVAL;
if (sli > INT_MAX || sli < INT_MIN)
return -ERANGE;
*converted = (int)sli;
return 0;
}
static int char_left_gc(const char *buffer, size_t len)
{
size_t i;
for (i = 0; i < len; i++) {
if (buffer[i] == ' ' ||
buffer[i] == '\t')
continue;
return i;
}
return 0;
}
static int char_right_gc(const char *buffer, size_t len)
{
int i;
for (i = len - 1; i >= 0; i--) {
if (buffer[i] == ' ' ||
buffer[i] == '\t' ||
buffer[i] == '\n' ||
buffer[i] == '\0')
continue;
return i + 1;
}
return 0;
}
static char *trim_whitespace_in_place(char *buffer)
{
buffer += char_left_gc(buffer, strlen(buffer));
buffer[char_right_gc(buffer, strlen(buffer))] = '\0';
return buffer;
}
static pid_t get_pid_from_fdinfo_file(int pidfd, const char *key, size_t keylen)
{
int ret;
char path[512];
FILE *f;
size_t n = 0;
pid_t result = -1;
char *line = NULL;
snprintf(path, sizeof(path), "/proc/self/fdinfo/%d", pidfd);
f = fopen(path, "re");
if (!f)
return -1;
while (getline(&line, &n, f) != -1) {
char *numstr;
if (strncmp(line, key, keylen))
continue;
numstr = trim_whitespace_in_place(line + 4);
ret = safe_int(numstr, &result);
if (ret < 0)
goto out;
break;
}
out:
free(line);
fclose(f);
return result;
}
int main(int argc, char **argv)
{
struct pidfd_info info = {
.mask = PIDFD_INFO_CGROUPID,
};
int pidfd = -1, ret = 1;
pid_t pid;
ksft_set_plan(4);
pidfd = sys_pidfd_open(-1, 0);
if (pidfd >= 0) {
ksft_print_msg(
"%s - succeeded to open pidfd for invalid pid -1\n",
strerror(errno));
goto on_error;
}
ksft_test_result_pass("do not allow invalid pid test: passed\n");
pidfd = sys_pidfd_open(getpid(), 1);
if (pidfd >= 0) {
ksft_print_msg(
"%s - succeeded to open pidfd with invalid flag value specified\n",
strerror(errno));
goto on_error;
}
ksft_test_result_pass("do not allow invalid flag test: passed\n");
pidfd = sys_pidfd_open(getpid(), 0);
if (pidfd < 0) {
ksft_print_msg("%s - failed to open pidfd\n", strerror(errno));
goto on_error;
}
ksft_test_result_pass("open a new pidfd test: passed\n");
pid = get_pid_from_fdinfo_file(pidfd, "Pid:", sizeof("Pid:") - 1);
ksft_print_msg("pidfd %d refers to process with pid %d\n", pidfd, pid);
if (ioctl(pidfd, PIDFD_GET_INFO, &info) < 0) {
ksft_print_msg("%s - failed to get info from pidfd\n", strerror(errno));
goto on_error;
}
if (info.pid != pid) {
ksft_print_msg("pid from fdinfo file %d does not match pid from ioctl %d\n",
pid, info.pid);
goto on_error;
}
if (info.ppid != getppid()) {
ksft_print_msg("ppid %d does not match ppid from ioctl %d\n",
pid, info.pid);
goto on_error;
}
if (info.ruid != getuid()) {
ksft_print_msg("uid %d does not match uid from ioctl %d\n",
getuid(), info.ruid);
goto on_error;
}
if (info.rgid != getgid()) {
ksft_print_msg("gid %d does not match gid from ioctl %d\n",
getgid(), info.rgid);
goto on_error;
}
if (info.euid != geteuid()) {
ksft_print_msg("euid %d does not match euid from ioctl %d\n",
geteuid(), info.euid);
goto on_error;
}
if (info.egid != getegid()) {
ksft_print_msg("egid %d does not match egid from ioctl %d\n",
getegid(), info.egid);
goto on_error;
}
if (info.suid != geteuid()) {
ksft_print_msg("suid %d does not match suid from ioctl %d\n",
geteuid(), info.suid);
goto on_error;
}
if (info.sgid != getegid()) {
ksft_print_msg("sgid %d does not match sgid from ioctl %d\n",
getegid(), info.sgid);
goto on_error;
}
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;
}
ksft_test_result_pass("get info from pidfd test: passed\n");
ret = 0;
on_error:
if (pidfd >= 0)
close(pidfd);
if (ret)
ksft_exit_fail();
ksft_exit_pass();
}
@@ -0,0 +1,116 @@
// SPDX-License-Identifier: GPL-2.0
#define _GNU_SOURCE
#include <errno.h>
#include <linux/types.h>
#include <poll.h>
#include <signal.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syscall.h>
#include <sys/wait.h>
#include <unistd.h>
#include "pidfd.h"
#include "kselftest.h"
static bool timeout;
static void handle_alarm(int sig)
{
timeout = true;
}
int main(int argc, char **argv)
{
struct pollfd fds;
int iter, nevents;
int nr_iterations = 10000;
fds.events = POLLIN;
if (argc > 2)
ksft_exit_fail_msg("Unexpected command line argument\n");
if (argc == 2) {
nr_iterations = atoi(argv[1]);
if (nr_iterations <= 0)
ksft_exit_fail_msg("invalid input parameter %s\n",
argv[1]);
}
ksft_print_msg("running pidfd poll test for %d iterations\n",
nr_iterations);
for (iter = 0; iter < nr_iterations; iter++) {
int pidfd;
int child_pid = fork();
if (child_pid < 0) {
if (errno == EAGAIN) {
iter--;
continue;
}
ksft_exit_fail_msg(
"%s - failed to fork a child process\n",
strerror(errno));
}
if (child_pid == 0) {
/* Child process just sleeps for a min and exits */
sleep(60);
exit(EXIT_SUCCESS);
}
/* Parent kills the child and waits for its death */
pidfd = sys_pidfd_open(child_pid, 0);
if (pidfd < 0)
ksft_exit_fail_msg("%s - pidfd_open failed\n",
strerror(errno));
/* Setup 3 sec alarm - plenty of time */
if (signal(SIGALRM, handle_alarm) == SIG_ERR)
ksft_exit_fail_msg("%s - signal failed\n",
strerror(errno));
alarm(3);
/* Send SIGKILL to the child */
if (sys_pidfd_send_signal(pidfd, SIGKILL, NULL, 0))
ksft_exit_fail_msg("%s - pidfd_send_signal failed\n",
strerror(errno));
/* Wait for the death notification */
fds.fd = pidfd;
nevents = poll(&fds, 1, -1);
/* Check for error conditions */
if (nevents < 0)
ksft_exit_fail_msg("%s - poll failed\n",
strerror(errno));
if (nevents != 1)
ksft_exit_fail_msg("unexpected poll result: %d\n",
nevents);
if (!(fds.revents & POLLIN))
ksft_exit_fail_msg(
"unexpected event type received: 0x%x\n",
fds.revents);
if (timeout)
ksft_exit_fail_msg(
"death notification wait timeout\n");
close(pidfd);
/* Wait for child to prevent zombies */
if (waitpid(child_pid, NULL, 0) < 0)
ksft_exit_fail_msg("%s - waitpid failed\n",
strerror(errno));
}
ksft_test_result_pass("pidfd poll test: pass\n");
ksft_exit_pass();
}
@@ -0,0 +1,69 @@
// 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 <sys/xattr.h>
#include "pidfd.h"
#include "kselftest_harness.h"
FIXTURE(pidfs_setattr)
{
pid_t child_pid;
int child_pidfd;
};
FIXTURE_SETUP(pidfs_setattr)
{
self->child_pid = create_child(&self->child_pidfd, CLONE_NEWUSER | CLONE_NEWPID);
EXPECT_GE(self->child_pid, 0);
if (self->child_pid == 0)
_exit(EXIT_SUCCESS);
}
FIXTURE_TEARDOWN(pidfs_setattr)
{
sys_waitid(P_PID, self->child_pid, NULL, WEXITED);
EXPECT_EQ(close(self->child_pidfd), 0);
}
TEST_F(pidfs_setattr, no_chown)
{
ASSERT_LT(fchown(self->child_pidfd, 1234, 5678), 0);
ASSERT_EQ(errno, EOPNOTSUPP);
}
TEST_F(pidfs_setattr, no_chmod)
{
ASSERT_LT(fchmod(self->child_pidfd, 0777), 0);
ASSERT_EQ(errno, EOPNOTSUPP);
}
TEST_F(pidfs_setattr, no_exec)
{
char *const argv[] = { NULL };
char *const envp[] = { NULL };
ASSERT_LT(execveat(self->child_pidfd, "", argv, envp, AT_EMPTY_PATH), 0);
ASSERT_EQ(errno, EACCES);
}
TEST_HARNESS_MAIN
@@ -0,0 +1,671 @@
// SPDX-License-Identifier: GPL-2.0
#define _GNU_SOURCE
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <linux/types.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 <sys/stat.h>
#include "pidfd.h"
#include "kselftest_harness.h"
enum {
PIDFD_NS_USER,
PIDFD_NS_MNT,
PIDFD_NS_PID,
PIDFD_NS_UTS,
PIDFD_NS_IPC,
PIDFD_NS_NET,
PIDFD_NS_CGROUP,
PIDFD_NS_PIDCLD,
PIDFD_NS_TIME,
PIDFD_NS_TIMECLD,
PIDFD_NS_MAX
};
const struct ns_info {
const char *name;
int flag;
unsigned int pidfd_ioctl;
} ns_info[] = {
[PIDFD_NS_USER] = { "user", CLONE_NEWUSER, PIDFD_GET_USER_NAMESPACE, },
[PIDFD_NS_MNT] = { "mnt", CLONE_NEWNS, PIDFD_GET_MNT_NAMESPACE, },
[PIDFD_NS_PID] = { "pid", CLONE_NEWPID, PIDFD_GET_PID_NAMESPACE, },
[PIDFD_NS_UTS] = { "uts", CLONE_NEWUTS, PIDFD_GET_UTS_NAMESPACE, },
[PIDFD_NS_IPC] = { "ipc", CLONE_NEWIPC, PIDFD_GET_IPC_NAMESPACE, },
[PIDFD_NS_NET] = { "net", CLONE_NEWNET, PIDFD_GET_NET_NAMESPACE, },
[PIDFD_NS_CGROUP] = { "cgroup", CLONE_NEWCGROUP, PIDFD_GET_CGROUP_NAMESPACE, },
[PIDFD_NS_TIME] = { "time", CLONE_NEWTIME, PIDFD_GET_TIME_NAMESPACE, },
[PIDFD_NS_PIDCLD] = { "pid_for_children", 0, PIDFD_GET_PID_FOR_CHILDREN_NAMESPACE, },
[PIDFD_NS_TIMECLD] = { "time_for_children", 0, PIDFD_GET_TIME_FOR_CHILDREN_NAMESPACE, },
};
FIXTURE(current_nsset)
{
pid_t pid;
int pidfd;
int nsfds[PIDFD_NS_MAX];
int child_pidfd_derived_nsfds[PIDFD_NS_MAX];
pid_t child_pid_exited;
int child_pidfd_exited;
pid_t child_pid1;
int child_pidfd1;
int child_nsfds1[PIDFD_NS_MAX];
int child_pidfd_derived_nsfds1[PIDFD_NS_MAX];
pid_t child_pid2;
int child_pidfd2;
int child_nsfds2[PIDFD_NS_MAX];
int child_pidfd_derived_nsfds2[PIDFD_NS_MAX];
};
static bool switch_timens(void)
{
int fd, ret;
if (unshare(CLONE_NEWTIME))
return false;
fd = open("/proc/self/ns/time_for_children", O_RDONLY | O_CLOEXEC);
if (fd < 0)
return false;
ret = setns(fd, CLONE_NEWTIME);
close(fd);
return ret == 0;
}
FIXTURE_SETUP(current_nsset)
{
int i, proc_fd, ret;
int ipc_sockets[2];
char c;
for (i = 0; i < PIDFD_NS_MAX; i++) {
self->nsfds[i] = -EBADF;
self->child_nsfds1[i] = -EBADF;
self->child_nsfds2[i] = -EBADF;
self->child_pidfd_derived_nsfds[i] = -EBADF;
self->child_pidfd_derived_nsfds1[i] = -EBADF;
self->child_pidfd_derived_nsfds2[i] = -EBADF;
}
proc_fd = open("/proc/self/ns", O_DIRECTORY | O_CLOEXEC);
ASSERT_GE(proc_fd, 0) {
TH_LOG("%m - Failed to open /proc/self/ns");
}
self->pid = getpid();
self->pidfd = sys_pidfd_open(self->pid, 0);
EXPECT_GT(self->pidfd, 0) {
TH_LOG("%m - Failed to open pidfd for process %d", self->pid);
}
for (i = 0; i < PIDFD_NS_MAX; i++) {
const struct ns_info *info = &ns_info[i];
self->nsfds[i] = openat(proc_fd, info->name, O_RDONLY | O_CLOEXEC);
if (self->nsfds[i] < 0) {
EXPECT_EQ(errno, ENOENT) {
TH_LOG("%m - Failed to open %s namespace for process %d",
info->name, self->pid);
}
}
self->child_pidfd_derived_nsfds[i] = ioctl(self->pidfd, info->pidfd_ioctl, 0);
if (self->child_pidfd_derived_nsfds[i] < 0) {
EXPECT_EQ(errno, EOPNOTSUPP) {
TH_LOG("%m - Failed to derive %s namespace from pidfd of process %d",
info->name, self->pid);
}
}
}
/* Create task that exits right away. */
self->child_pid_exited = create_child(&self->child_pidfd_exited, 0);
EXPECT_GE(self->child_pid_exited, 0);
if (self->child_pid_exited == 0) {
if (self->nsfds[PIDFD_NS_USER] >= 0 && unshare(CLONE_NEWUSER) < 0)
_exit(EXIT_FAILURE);
if (self->nsfds[PIDFD_NS_NET] >= 0 && unshare(CLONE_NEWNET) < 0)
_exit(EXIT_FAILURE);
_exit(EXIT_SUCCESS);
}
ASSERT_EQ(sys_waitid(P_PID, self->child_pid_exited, NULL, WEXITED | WNOWAIT), 0);
self->pidfd = sys_pidfd_open(self->pid, 0);
EXPECT_GE(self->pidfd, 0) {
TH_LOG("%m - Failed to open pidfd for process %d", self->pid);
}
ret = socketpair(AF_LOCAL, SOCK_STREAM | SOCK_CLOEXEC, 0, ipc_sockets);
EXPECT_EQ(ret, 0);
/* Create tasks that will be stopped. */
if (self->nsfds[PIDFD_NS_USER] >= 0 && self->nsfds[PIDFD_NS_PID] >= 0)
self->child_pid1 = create_child(&self->child_pidfd1, CLONE_NEWUSER | CLONE_NEWPID);
else if (self->nsfds[PIDFD_NS_PID] >= 0)
self->child_pid1 = create_child(&self->child_pidfd1, CLONE_NEWPID);
else if (self->nsfds[PIDFD_NS_USER] >= 0)
self->child_pid1 = create_child(&self->child_pidfd1, CLONE_NEWUSER);
else
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 (self->nsfds[PIDFD_NS_MNT] >= 0 && unshare(CLONE_NEWNS) < 0) {
TH_LOG("%m - Failed to unshare mount namespace for process %d", self->pid);
_exit(EXIT_FAILURE);
}
if (self->nsfds[PIDFD_NS_CGROUP] >= 0 && unshare(CLONE_NEWCGROUP) < 0) {
TH_LOG("%m - Failed to unshare cgroup namespace for process %d", self->pid);
_exit(EXIT_FAILURE);
}
if (self->nsfds[PIDFD_NS_IPC] >= 0 && unshare(CLONE_NEWIPC) < 0) {
TH_LOG("%m - Failed to unshare ipc namespace for process %d", self->pid);
_exit(EXIT_FAILURE);
}
if (self->nsfds[PIDFD_NS_UTS] >= 0 && unshare(CLONE_NEWUTS) < 0) {
TH_LOG("%m - Failed to unshare uts namespace for process %d", self->pid);
_exit(EXIT_FAILURE);
}
if (self->nsfds[PIDFD_NS_NET] >= 0 && unshare(CLONE_NEWNET) < 0) {
TH_LOG("%m - Failed to unshare net namespace for process %d", self->pid);
_exit(EXIT_FAILURE);
}
if (self->nsfds[PIDFD_NS_TIME] >= 0 && !switch_timens()) {
TH_LOG("%m - Failed to unshare time namespace for process %d", self->pid);
_exit(EXIT_FAILURE);
}
if (write_nointr(ipc_sockets[1], "1", 1) < 0)
_exit(EXIT_FAILURE);
close(ipc_sockets[1]);
pause();
_exit(EXIT_SUCCESS);
}
close(ipc_sockets[1]);
ASSERT_EQ(read_nointr(ipc_sockets[0], &c, 1), 1);
close(ipc_sockets[0]);
ret = socketpair(AF_LOCAL, SOCK_STREAM | SOCK_CLOEXEC, 0, ipc_sockets);
EXPECT_EQ(ret, 0);
if (self->nsfds[PIDFD_NS_USER] >= 0 && self->nsfds[PIDFD_NS_PID] >= 0)
self->child_pid2 = create_child(&self->child_pidfd2, CLONE_NEWUSER | CLONE_NEWPID);
else if (self->nsfds[PIDFD_NS_PID] >= 0)
self->child_pid2 = create_child(&self->child_pidfd2, CLONE_NEWPID);
else if (self->nsfds[PIDFD_NS_USER] >= 0)
self->child_pid2 = create_child(&self->child_pidfd2, CLONE_NEWUSER);
else
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 (self->nsfds[PIDFD_NS_MNT] >= 0 && unshare(CLONE_NEWNS) < 0) {
TH_LOG("%m - Failed to unshare mount namespace for process %d", self->pid);
_exit(EXIT_FAILURE);
}
if (self->nsfds[PIDFD_NS_CGROUP] >= 0 && unshare(CLONE_NEWCGROUP) < 0) {
TH_LOG("%m - Failed to unshare cgroup namespace for process %d", self->pid);
_exit(EXIT_FAILURE);
}
if (self->nsfds[PIDFD_NS_IPC] >= 0 && unshare(CLONE_NEWIPC) < 0) {
TH_LOG("%m - Failed to unshare ipc namespace for process %d", self->pid);
_exit(EXIT_FAILURE);
}
if (self->nsfds[PIDFD_NS_UTS] >= 0 && unshare(CLONE_NEWUTS) < 0) {
TH_LOG("%m - Failed to unshare uts namespace for process %d", self->pid);
_exit(EXIT_FAILURE);
}
if (self->nsfds[PIDFD_NS_NET] >= 0 && unshare(CLONE_NEWNET) < 0) {
TH_LOG("%m - Failed to unshare net namespace for process %d", self->pid);
_exit(EXIT_FAILURE);
}
if (self->nsfds[PIDFD_NS_TIME] >= 0 && !switch_timens()) {
TH_LOG("%m - Failed to unshare time namespace for process %d", self->pid);
_exit(EXIT_FAILURE);
}
if (write_nointr(ipc_sockets[1], "1", 1) < 0)
_exit(EXIT_FAILURE);
close(ipc_sockets[1]);
pause();
_exit(EXIT_SUCCESS);
}
close(ipc_sockets[1]);
ASSERT_EQ(read_nointr(ipc_sockets[0], &c, 1), 1);
close(ipc_sockets[0]);
for (i = 0; i < PIDFD_NS_MAX; i++) {
char p[100];
const struct ns_info *info = &ns_info[i];
self->nsfds[i] = openat(proc_fd, info->name, O_RDONLY | O_CLOEXEC);
if (self->nsfds[i] < 0) {
EXPECT_EQ(errno, ENOENT) {
TH_LOG("%m - Failed to open %s namespace for process %d",
info->name, self->pid);
}
}
ret = snprintf(p, sizeof(p), "/proc/%d/ns/%s",
self->child_pid1, info->name);
EXPECT_GT(ret, 0);
EXPECT_LT(ret, sizeof(p));
self->child_nsfds1[i] = open(p, O_RDONLY | O_CLOEXEC);
if (self->child_nsfds1[i] < 0) {
EXPECT_EQ(errno, ENOENT) {
TH_LOG("%m - Failed to open %s namespace for process %d",
info->name, self->child_pid1);
}
}
ret = snprintf(p, sizeof(p), "/proc/%d/ns/%s",
self->child_pid2, info->name);
EXPECT_GT(ret, 0);
EXPECT_LT(ret, sizeof(p));
self->child_nsfds2[i] = open(p, O_RDONLY | O_CLOEXEC);
if (self->child_nsfds2[i] < 0) {
EXPECT_EQ(errno, ENOENT) {
TH_LOG("%m - Failed to open %s namespace for process %d",
info->name, self->child_pid1);
}
}
self->child_pidfd_derived_nsfds1[i] = ioctl(self->child_pidfd1, info->pidfd_ioctl, 0);
if (self->child_pidfd_derived_nsfds1[i] < 0) {
EXPECT_EQ(errno, EOPNOTSUPP) {
TH_LOG("%m - Failed to derive %s namespace from pidfd of process %d",
info->name, self->child_pid1);
}
}
self->child_pidfd_derived_nsfds2[i] = ioctl(self->child_pidfd2, info->pidfd_ioctl, 0);
if (self->child_pidfd_derived_nsfds2[i] < 0) {
EXPECT_EQ(errno, EOPNOTSUPP) {
TH_LOG("%m - Failed to derive %s namespace from pidfd of process %d",
info->name, self->child_pid2);
}
}
}
close(proc_fd);
}
FIXTURE_TEARDOWN(current_nsset)
{
int i;
ASSERT_EQ(sys_pidfd_send_signal(self->child_pidfd1,
SIGKILL, NULL, 0), 0);
ASSERT_EQ(sys_pidfd_send_signal(self->child_pidfd2,
SIGKILL, NULL, 0), 0);
for (i = 0; i < PIDFD_NS_MAX; i++) {
if (self->nsfds[i] >= 0)
close(self->nsfds[i]);
if (self->child_nsfds1[i] >= 0)
close(self->child_nsfds1[i]);
if (self->child_nsfds2[i] >= 0)
close(self->child_nsfds2[i]);
if (self->child_pidfd_derived_nsfds[i] >= 0)
close(self->child_pidfd_derived_nsfds[i]);
if (self->child_pidfd_derived_nsfds1[i] >= 0)
close(self->child_pidfd_derived_nsfds1[i]);
if (self->child_pidfd_derived_nsfds2[i] >= 0)
close(self->child_pidfd_derived_nsfds2[i]);
}
if (self->child_pidfd1 >= 0)
EXPECT_EQ(0, close(self->child_pidfd1));
if (self->child_pidfd2 >= 0)
EXPECT_EQ(0, close(self->child_pidfd2));
ASSERT_EQ(sys_waitid(P_PID, self->child_pid_exited, NULL, WEXITED), 0);
ASSERT_EQ(sys_waitid(P_PID, self->child_pid1, NULL, WEXITED), 0);
ASSERT_EQ(sys_waitid(P_PID, self->child_pid2, NULL, WEXITED), 0);
}
static int preserve_ns(const int pid, const char *ns)
{
int ret;
char path[50];
ret = snprintf(path, sizeof(path), "/proc/%d/ns/%s", pid, ns);
if (ret < 0 || (size_t)ret >= sizeof(path))
return -EIO;
return open(path, O_RDONLY | O_CLOEXEC);
}
static int in_same_namespace(int ns_fd1, pid_t pid2, const char *ns)
{
int ns_fd2 = -EBADF;
int ret = -1;
struct stat ns_st1, ns_st2;
ret = fstat(ns_fd1, &ns_st1);
if (ret < 0)
return -1;
ns_fd2 = preserve_ns(pid2, ns);
if (ns_fd2 < 0)
return -1;
ret = fstat(ns_fd2, &ns_st2);
close(ns_fd2);
if (ret < 0)
return -1;
/* processes are in the same namespace */
if ((ns_st1.st_dev == ns_st2.st_dev) &&
(ns_st1.st_ino == ns_st2.st_ino))
return 1;
/* processes are in different namespaces */
return 0;
}
/* Test that we can't pass garbage to the kernel. */
TEST_F(current_nsset, invalid_flags)
{
ASSERT_NE(setns(self->pidfd, 0), 0);
EXPECT_EQ(errno, EINVAL);
ASSERT_NE(setns(self->pidfd, -1), 0);
EXPECT_EQ(errno, EINVAL);
ASSERT_NE(setns(self->pidfd, CLONE_VM), 0);
EXPECT_EQ(errno, EINVAL);
ASSERT_NE(setns(self->pidfd, CLONE_NEWUSER | CLONE_VM), 0);
EXPECT_EQ(errno, EINVAL);
}
/* Test that we can't attach to a task that has already exited. */
TEST_F(current_nsset, pidfd_exited_child)
{
int i;
pid_t pid;
ASSERT_NE(setns(self->child_pidfd_exited, CLONE_NEWUSER | CLONE_NEWNET),
0);
EXPECT_EQ(errno, ESRCH);
pid = getpid();
for (i = 0; i < PIDFD_NS_MAX; i++) {
const struct ns_info *info = &ns_info[i];
/* Verify that we haven't changed any namespaces. */
if (self->nsfds[i] >= 0)
ASSERT_EQ(in_same_namespace(self->nsfds[i], pid, info->name), 1);
}
}
TEST_F(current_nsset, pidfd_incremental_setns)
{
int i;
pid_t pid;
pid = getpid();
for (i = 0; i < PIDFD_NS_MAX; i++) {
const struct ns_info *info = &ns_info[i];
int nsfd;
if (self->child_nsfds1[i] < 0)
continue;
if (info->flag) {
ASSERT_EQ(setns(self->child_pidfd1, info->flag), 0) {
TH_LOG("%m - Failed to setns to %s namespace of %d via pidfd %d",
info->name, self->child_pid1,
self->child_pidfd1);
}
}
/* Verify that we have changed to the correct namespaces. */
if (info->flag == CLONE_NEWPID)
nsfd = self->nsfds[i];
else
nsfd = self->child_nsfds1[i];
ASSERT_EQ(in_same_namespace(nsfd, pid, info->name), 1) {
TH_LOG("setns failed to place us correctly into %s namespace of %d via pidfd %d",
info->name, self->child_pid1,
self->child_pidfd1);
}
TH_LOG("Managed to correctly setns to %s namespace of %d via pidfd %d",
info->name, self->child_pid1, self->child_pidfd1);
}
}
TEST_F(current_nsset, nsfd_incremental_setns)
{
int i;
pid_t pid;
pid = getpid();
for (i = 0; i < PIDFD_NS_MAX; i++) {
const struct ns_info *info = &ns_info[i];
int nsfd;
if (self->child_nsfds1[i] < 0)
continue;
if (info->flag) {
ASSERT_EQ(setns(self->child_nsfds1[i], info->flag), 0) {
TH_LOG("%m - Failed to setns to %s namespace of %d via nsfd %d",
info->name, self->child_pid1,
self->child_nsfds1[i]);
}
}
/* Verify that we have changed to the correct namespaces. */
if (info->flag == CLONE_NEWPID)
nsfd = self->nsfds[i];
else
nsfd = self->child_nsfds1[i];
ASSERT_EQ(in_same_namespace(nsfd, pid, info->name), 1) {
TH_LOG("setns failed to place us correctly into %s namespace of %d via nsfd %d",
info->name, self->child_pid1,
self->child_nsfds1[i]);
}
TH_LOG("Managed to correctly setns to %s namespace of %d via nsfd %d",
info->name, self->child_pid1, self->child_nsfds1[i]);
}
}
TEST_F(current_nsset, pidfd_derived_nsfd_incremental_setns)
{
int i;
pid_t pid;
pid = getpid();
for (i = 0; i < PIDFD_NS_MAX; i++) {
const struct ns_info *info = &ns_info[i];
int nsfd;
if (self->child_pidfd_derived_nsfds1[i] < 0)
continue;
if (info->flag) {
ASSERT_EQ(setns(self->child_pidfd_derived_nsfds1[i], info->flag), 0) {
TH_LOG("%m - Failed to setns to %s namespace of %d via nsfd %d",
info->name, self->child_pid1,
self->child_pidfd_derived_nsfds1[i]);
}
}
/* Verify that we have changed to the correct namespaces. */
if (info->flag == CLONE_NEWPID)
nsfd = self->child_pidfd_derived_nsfds[i];
else
nsfd = self->child_pidfd_derived_nsfds1[i];
ASSERT_EQ(in_same_namespace(nsfd, pid, info->name), 1) {
TH_LOG("setns failed to place us correctly into %s namespace of %d via nsfd %d",
info->name, self->child_pid1,
self->child_pidfd_derived_nsfds1[i]);
}
TH_LOG("Managed to correctly setns to %s namespace of %d via nsfd %d",
info->name, self->child_pid1, self->child_pidfd_derived_nsfds1[i]);
}
}
TEST_F(current_nsset, pidfd_one_shot_setns)
{
unsigned flags = 0;
int i;
pid_t pid;
for (i = 0; i < PIDFD_NS_MAX; i++) {
const struct ns_info *info = &ns_info[i];
if (self->child_nsfds1[i] < 0)
continue;
flags |= info->flag;
TH_LOG("Adding %s namespace of %d to list of namespaces to attach to",
info->name, self->child_pid1);
}
ASSERT_EQ(setns(self->child_pidfd1, flags), 0) {
TH_LOG("%m - Failed to setns to namespaces of %d",
self->child_pid1);
}
pid = getpid();
for (i = 0; i < PIDFD_NS_MAX; i++) {
const struct ns_info *info = &ns_info[i];
int nsfd;
if (self->child_nsfds1[i] < 0)
continue;
/* Verify that we have changed to the correct namespaces. */
if (info->flag == CLONE_NEWPID)
nsfd = self->nsfds[i];
else
nsfd = self->child_nsfds1[i];
ASSERT_EQ(in_same_namespace(nsfd, pid, info->name), 1) {
TH_LOG("setns failed to place us correctly into %s namespace of %d",
info->name, self->child_pid1);
}
TH_LOG("Managed to correctly setns to %s namespace of %d",
info->name, self->child_pid1);
}
}
TEST_F(current_nsset, no_foul_play)
{
unsigned flags = 0;
int i;
for (i = 0; i < PIDFD_NS_MAX; i++) {
const struct ns_info *info = &ns_info[i];
if (self->child_nsfds1[i] < 0)
continue;
flags |= info->flag;
if (info->flag) /* No use logging pid_for_children. */
TH_LOG("Adding %s namespace of %d to list of namespaces to attach to",
info->name, self->child_pid1);
}
ASSERT_EQ(setns(self->child_pidfd1, flags), 0) {
TH_LOG("%m - Failed to setns to namespaces of %d vid pidfd %d",
self->child_pid1, self->child_pidfd1);
}
/*
* Can't setns to a user namespace outside of our hierarchy since we
* don't have caps in there and didn't create it. That means that under
* no circumstances should we be able to setns to any of the other
* ones since they aren't owned by our user namespace.
*/
for (i = 0; i < PIDFD_NS_MAX; i++) {
const struct ns_info *info = &ns_info[i];
if (self->child_nsfds2[i] < 0 || !info->flag)
continue;
ASSERT_NE(setns(self->child_pidfd2, info->flag), 0) {
TH_LOG("Managed to setns to %s namespace of %d via pidfd %d",
info->name, self->child_pid2,
self->child_pidfd2);
}
TH_LOG("%m - Correctly failed to setns to %s namespace of %d via pidfd %d",
info->name, self->child_pid2,
self->child_pidfd2);
ASSERT_NE(setns(self->child_nsfds2[i], info->flag), 0) {
TH_LOG("Managed to setns to %s namespace of %d via nsfd %d",
info->name, self->child_pid2,
self->child_nsfds2[i]);
}
TH_LOG("%m - Correctly failed to setns to %s namespace of %d via nsfd %d",
info->name, self->child_pid2,
self->child_nsfds2[i]);
}
/*
* Can't setns to a user namespace outside of our hierarchy since we
* don't have caps in there and didn't create it. That means that under
* no circumstances should we be able to setns to any of the other
* ones since they aren't owned by our user namespace.
*/
for (i = 0; i < PIDFD_NS_MAX; i++) {
const struct ns_info *info = &ns_info[i];
if (self->child_pidfd_derived_nsfds2[i] < 0 || !info->flag)
continue;
ASSERT_NE(setns(self->child_pidfd_derived_nsfds2[i], info->flag), 0) {
TH_LOG("Managed to setns to %s namespace of %d via nsfd %d",
info->name, self->child_pid2,
self->child_pidfd_derived_nsfds2[i]);
}
TH_LOG("%m - Correctly failed to setns to %s namespace of %d via nsfd %d",
info->name, self->child_pid2,
self->child_pidfd_derived_nsfds2[i]);
}
}
TEST(setns_einval)
{
int fd;
fd = sys_memfd_create("rostock", 0);
EXPECT_GT(fd, 0);
ASSERT_NE(setns(fd, 0), 0);
EXPECT_EQ(errno, EINVAL);
close(fd);
}
TEST_HARNESS_MAIN
@@ -0,0 +1,628 @@
/* SPDX-License-Identifier: GPL-2.0 */
#define _GNU_SOURCE
#include <errno.h>
#include <fcntl.h>
#include <linux/types.h>
#include <pthread.h>
#include <sched.h>
#include <signal.h>
#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <syscall.h>
#include <sys/epoll.h>
#include <sys/mman.h>
#include <sys/mount.h>
#include <sys/wait.h>
#include <time.h>
#include <unistd.h>
#include "pidfd.h"
#include "kselftest.h"
#define str(s) _str(s)
#define _str(s) #s
#define CHILD_THREAD_MIN_WAIT 3 /* seconds */
#define MAX_EVENTS 5
static bool have_pidfd_send_signal;
static pid_t pidfd_clone(int flags, int *pidfd, int (*fn)(void *))
{
size_t stack_size = 1024;
char *stack[1024] = { 0 };
#ifdef __ia64__
return __clone2(fn, stack, stack_size, flags | SIGCHLD, NULL, pidfd);
#else
return clone(fn, stack + stack_size, flags | SIGCHLD, NULL, pidfd);
#endif
}
static pthread_t signal_received;
static void set_signal_received_on_sigusr1(int sig)
{
if (sig == SIGUSR1)
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;
}
/*
* Straightforward test to see whether pidfd_send_signal() works is to send
* a signal to ourself.
*/
static int test_pidfd_send_signal_simple_success(void)
{
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(
"%s test: pidfd_send_signal() syscall not supported\n",
test_name);
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);
err = send_signal(pidfd);
if (err)
ksft_exit_fail_msg(
"%s test: Error %d on sending pidfd signal\n",
test_name, err);
close(pidfd);
/* 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);
ksft_test_result_pass("%s test: Sent signal\n", test_name);
return 0;
}
static int test_pidfd_send_signal_exited_fail(void)
{
int pidfd, ret, saved_errno;
char buf[256];
pid_t pid;
const char *test_name = "pidfd_send_signal signal exited process";
if (!have_pidfd_send_signal) {
ksft_test_result_skip(
"%s test: pidfd_send_signal() syscall not supported\n",
test_name);
return 0;
}
pid = fork();
if (pid < 0)
ksft_exit_fail_msg("%s test: Failed to create new process\n",
test_name);
if (pid == 0)
_exit(EXIT_SUCCESS);
snprintf(buf, sizeof(buf), "/proc/%d", pid);
pidfd = open(buf, O_DIRECTORY | O_CLOEXEC);
ret = wait_for_pid(pid);
ksft_print_msg("waitpid WEXITSTATUS=%d\n", ret);
if (pidfd < 0)
ksft_exit_fail_msg(
"%s test: Failed to open process file descriptor\n",
test_name);
ret = sys_pidfd_send_signal(pidfd, 0, NULL, 0);
saved_errno = errno;
close(pidfd);
if (ret == 0)
ksft_exit_fail_msg(
"%s test: Managed to send signal to process even though it should have failed\n",
test_name);
if (saved_errno != ESRCH)
ksft_exit_fail_msg(
"%s test: Expected to receive ESRCH as errno value but received %d instead\n",
test_name, saved_errno);
ksft_test_result_pass("%s test: Failed to send signal as expected\n",
test_name);
return 0;
}
/*
* Maximum number of cycles we allow. This is equivalent to PID_MAX_DEFAULT.
* If users set a higher limit or we have cycled PIDFD_MAX_DEFAULT number of
* times then we skip the test to not go into an infinite loop or block for a
* long time.
*/
#define PIDFD_MAX_DEFAULT 0x8000
static int test_pidfd_send_signal_recycled_pid_fail(void)
{
int i, ret;
pid_t pid1;
const char *test_name = "pidfd_send_signal signal recycled pid";
if (!have_pidfd_send_signal) {
ksft_test_result_skip(
"%s test: pidfd_send_signal() syscall not supported\n",
test_name);
return 0;
}
ret = unshare(CLONE_NEWPID);
if (ret < 0) {
if (errno == EPERM) {
ksft_test_result_skip("%s test: Unsharing pid namespace not permitted\n",
test_name);
return 0;
}
ksft_exit_fail_msg("%s test: Failed to unshare pid namespace\n",
test_name);
}
ret = unshare(CLONE_NEWNS);
if (ret < 0) {
if (errno == EPERM) {
ksft_test_result_skip("%s test: Unsharing mount namespace not permitted\n",
test_name);
return 0;
}
ksft_exit_fail_msg("%s test: Failed to unshare mount namespace\n",
test_name);
}
ret = mount(NULL, "/", NULL, MS_REC | MS_PRIVATE, 0);
if (ret < 0)
ksft_exit_fail_msg("%s test: Failed to remount / private\n",
test_name);
/* pid 1 in new pid namespace */
pid1 = fork();
if (pid1 < 0)
ksft_exit_fail_msg("%s test: Failed to create new process\n",
test_name);
if (pid1 == 0) {
char buf[256];
pid_t pid2;
int pidfd = -1;
(void)umount2("/proc", MNT_DETACH);
ret = mount("proc", "/proc", "proc", 0, NULL);
if (ret < 0)
_exit(PIDFD_ERROR);
/* grab pid PID_RECYCLE */
for (i = 0; i <= PIDFD_MAX_DEFAULT; i++) {
pid2 = fork();
if (pid2 < 0)
_exit(PIDFD_ERROR);
if (pid2 == 0)
_exit(PIDFD_PASS);
if (pid2 == PID_RECYCLE) {
snprintf(buf, sizeof(buf), "/proc/%d", pid2);
ksft_print_msg("pid to recycle is %d\n", pid2);
pidfd = open(buf, O_DIRECTORY | O_CLOEXEC);
}
if (wait_for_pid(pid2))
_exit(PIDFD_ERROR);
if (pid2 >= PID_RECYCLE)
break;
}
/*
* We want to be as predictable as we can so if we haven't been
* able to grab pid PID_RECYCLE skip the test.
*/
if (pid2 != PID_RECYCLE) {
/* skip test */
close(pidfd);
_exit(PIDFD_SKIP);
}
if (pidfd < 0)
_exit(PIDFD_ERROR);
for (i = 0; i <= PIDFD_MAX_DEFAULT; i++) {
char c;
int pipe_fds[2];
pid_t recycled_pid;
int child_ret = PIDFD_PASS;
ret = pipe2(pipe_fds, O_CLOEXEC);
if (ret < 0)
_exit(PIDFD_ERROR);
recycled_pid = fork();
if (recycled_pid < 0)
_exit(PIDFD_ERROR);
if (recycled_pid == 0) {
close(pipe_fds[1]);
(void)read(pipe_fds[0], &c, 1);
close(pipe_fds[0]);
_exit(PIDFD_PASS);
}
/*
* Stop the child so we can inspect whether we have
* recycled pid PID_RECYCLE.
*/
close(pipe_fds[0]);
ret = kill(recycled_pid, SIGSTOP);
close(pipe_fds[1]);
if (ret) {
(void)wait_for_pid(recycled_pid);
_exit(PIDFD_ERROR);
}
/*
* We have recycled the pid. Try to signal it. This
* needs to fail since this is a different process than
* the one the pidfd refers to.
*/
if (recycled_pid == PID_RECYCLE) {
ret = sys_pidfd_send_signal(pidfd, SIGCONT,
NULL, 0);
if (ret && errno == ESRCH)
child_ret = PIDFD_XFAIL;
else
child_ret = PIDFD_FAIL;
}
/* let the process move on */
ret = kill(recycled_pid, SIGCONT);
if (ret)
(void)kill(recycled_pid, SIGKILL);
if (wait_for_pid(recycled_pid))
_exit(PIDFD_ERROR);
switch (child_ret) {
case PIDFD_FAIL:
/* fallthrough */
case PIDFD_XFAIL:
_exit(child_ret);
case PIDFD_PASS:
break;
default:
/* not reached */
_exit(PIDFD_ERROR);
}
/*
* If the user set a custom pid_max limit we could be
* in the millions.
* Skip the test in this case.
*/
if (recycled_pid > PIDFD_MAX_DEFAULT)
_exit(PIDFD_SKIP);
}
/* failed to recycle pid */
_exit(PIDFD_SKIP);
}
ret = wait_for_pid(pid1);
switch (ret) {
case PIDFD_FAIL:
ksft_exit_fail_msg(
"%s test: Managed to signal recycled pid %d\n",
test_name, PID_RECYCLE);
case PIDFD_PASS:
ksft_exit_fail_msg("%s test: Failed to recycle pid %d\n",
test_name, PID_RECYCLE);
case PIDFD_SKIP:
ksft_test_result_skip("%s test: Skipping test\n", test_name);
ret = 0;
break;
case PIDFD_XFAIL:
ksft_test_result_pass(
"%s test: Failed to signal recycled pid as expected\n",
test_name);
ret = 0;
break;
default /* PIDFD_ERROR */:
ksft_exit_fail_msg("%s test: Error while running tests\n",
test_name);
}
return ret;
}
static int test_pidfd_send_signal_syscall_support(void)
{
int pidfd, ret;
const char *test_name = "pidfd_send_signal check for support";
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);
ret = sys_pidfd_send_signal(pidfd, 0, NULL, 0);
if (ret < 0) {
if (errno == ENOSYS) {
ksft_test_result_skip(
"%s test: pidfd_send_signal() syscall not supported\n",
test_name);
return 0;
}
ksft_exit_fail_msg("%s test: Failed to send signal\n",
test_name);
}
have_pidfd_send_signal = true;
close(pidfd);
ksft_test_result_pass(
"%s test: pidfd_send_signal() syscall is supported. Tests can be executed\n",
test_name);
return 0;
}
static void *test_pidfd_poll_exec_thread(void *priv)
{
ksft_print_msg("Child Thread: starting. pid %d tid %ld ; and sleeping\n",
getpid(), syscall(SYS_gettid));
ksft_print_msg("Child Thread: doing exec of sleep\n");
execl("/bin/sleep", "sleep", str(CHILD_THREAD_MIN_WAIT), (char *)NULL);
ksft_print_msg("Child Thread: DONE. pid %d tid %ld\n",
getpid(), syscall(SYS_gettid));
return NULL;
}
static void poll_pidfd(const char *test_name, int pidfd)
{
int c;
int epoll_fd = epoll_create1(EPOLL_CLOEXEC);
struct epoll_event event, events[MAX_EVENTS];
if (epoll_fd == -1)
ksft_exit_fail_msg("%s test: Failed to create epoll file descriptor "
"(errno %d)\n",
test_name, errno);
event.events = EPOLLIN;
event.data.fd = pidfd;
if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, pidfd, &event)) {
ksft_exit_fail_msg("%s test: Failed to add epoll file descriptor "
"(errno %d)\n",
test_name, errno);
}
c = epoll_wait(epoll_fd, events, MAX_EVENTS, 5000);
if (c != 1 || !(events[0].events & EPOLLIN))
ksft_exit_fail_msg("%s test: Unexpected epoll_wait result (c=%d, events=%x) "
"(errno %d)\n",
test_name, c, events[0].events, errno);
close(epoll_fd);
return;
}
static int child_poll_exec_test(void *args)
{
pthread_t t1;
ksft_print_msg("Child (pidfd): starting. pid %d tid %ld\n", getpid(),
syscall(SYS_gettid));
pthread_create(&t1, NULL, test_pidfd_poll_exec_thread, NULL);
/*
* Exec in the non-leader thread will destroy the leader immediately.
* If the wait in the parent returns too soon, the test fails.
*/
while (1)
sleep(1);
return 0;
}
static void test_pidfd_poll_exec(int use_waitpid)
{
int pid, pidfd = 0;
int status, ret;
time_t prog_start = time(NULL);
const char *test_name = "pidfd_poll check for premature notification on child thread exec";
ksft_print_msg("Parent: pid: %d\n", getpid());
pid = pidfd_clone(CLONE_PIDFD, &pidfd, child_poll_exec_test);
if (pid < 0)
ksft_exit_fail_msg("%s test: pidfd_clone failed (ret %d, errno %d)\n",
test_name, pid, errno);
ksft_print_msg("Parent: Waiting for Child (%d) to complete.\n", pid);
if (use_waitpid) {
ret = waitpid(pid, &status, 0);
if (ret == -1)
ksft_print_msg("Parent: error\n");
if (ret == pid)
ksft_print_msg("Parent: Child process waited for.\n");
} else {
poll_pidfd(test_name, pidfd);
}
time_t prog_time = time(NULL) - prog_start;
ksft_print_msg("Time waited for child: %lu\n", prog_time);
close(pidfd);
if (prog_time < CHILD_THREAD_MIN_WAIT || prog_time > CHILD_THREAD_MIN_WAIT + 2)
ksft_exit_fail_msg("%s test: Failed\n", test_name);
else
ksft_test_result_pass("%s test: Passed\n", test_name);
}
static void *test_pidfd_poll_leader_exit_thread(void *priv)
{
ksft_print_msg("Child Thread: starting. pid %d tid %ld ; and sleeping\n",
getpid(), syscall(SYS_gettid));
sleep(CHILD_THREAD_MIN_WAIT);
ksft_print_msg("Child Thread: DONE. pid %d tid %ld\n", getpid(), syscall(SYS_gettid));
return NULL;
}
static time_t *child_exit_secs;
static int child_poll_leader_exit_test(void *args)
{
pthread_t t1, t2;
ksft_print_msg("Child: starting. pid %d tid %ld\n", getpid(), syscall(SYS_gettid));
pthread_create(&t1, NULL, test_pidfd_poll_leader_exit_thread, NULL);
pthread_create(&t2, NULL, test_pidfd_poll_leader_exit_thread, NULL);
/*
* glibc exit calls exit_group syscall, so explicitly call exit only
* so that only the group leader exits, leaving the threads alone.
*/
*child_exit_secs = time(NULL);
syscall(SYS_exit, 0);
/* Never reached, but appeases compiler thinking we should return. */
exit(0);
}
static void test_pidfd_poll_leader_exit(int use_waitpid)
{
int pid, pidfd = 0;
int status, ret = 0;
const char *test_name = "pidfd_poll check for premature notification on non-empty"
"group leader exit";
child_exit_secs = mmap(NULL, sizeof *child_exit_secs, PROT_READ | PROT_WRITE,
MAP_SHARED | MAP_ANONYMOUS, -1, 0);
if (child_exit_secs == MAP_FAILED)
ksft_exit_fail_msg("%s test: mmap failed (errno %d)\n",
test_name, errno);
ksft_print_msg("Parent: pid: %d\n", getpid());
pid = pidfd_clone(CLONE_PIDFD, &pidfd, child_poll_leader_exit_test);
if (pid < 0)
ksft_exit_fail_msg("%s test: pidfd_clone failed (ret %d, errno %d)\n",
test_name, pid, errno);
ksft_print_msg("Parent: Waiting for Child (%d) to complete.\n", pid);
if (use_waitpid) {
ret = waitpid(pid, &status, 0);
if (ret == -1)
ksft_print_msg("Parent: error\n");
} else {
/*
* This sleep tests for the case where if the child exits, and is in
* EXIT_ZOMBIE, but the thread group leader is non-empty, then the poll
* doesn't prematurely return even though there are active threads
*/
sleep(1);
poll_pidfd(test_name, pidfd);
}
if (ret == pid)
ksft_print_msg("Parent: Child process waited for.\n");
time_t since_child_exit = time(NULL) - *child_exit_secs;
ksft_print_msg("Time since child exit: %lu\n", since_child_exit);
close(pidfd);
if (since_child_exit < CHILD_THREAD_MIN_WAIT ||
since_child_exit > CHILD_THREAD_MIN_WAIT + 2)
ksft_exit_fail_msg("%s test: Failed\n", test_name);
else
ksft_test_result_pass("%s test: Passed\n", test_name);
}
int main(int argc, char **argv)
{
ksft_print_header();
ksft_set_plan(8);
test_pidfd_poll_exec(0);
test_pidfd_poll_exec(1);
test_pidfd_poll_leader_exit(0);
test_pidfd_poll_leader_exit(1);
test_pidfd_send_signal_syscall_support();
test_pidfd_send_signal_simple_success();
test_pidfd_send_signal_exited_fail();
test_pidfd_send_signal_recycled_pid_fail();
ksft_exit_pass();
}
@@ -0,0 +1,222 @@
/* SPDX-License-Identifier: GPL-2.0 */
#define _GNU_SOURCE
#include <errno.h>
#include <linux/sched.h>
#include <linux/types.h>
#include <signal.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <sched.h>
#include <string.h>
#include <sys/resource.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include "pidfd.h"
#include "kselftest_harness.h"
#define ptr_to_u64(ptr) ((__u64)((uintptr_t)(ptr)))
/* Attempt to de-conflict with the selftests tree. */
#ifndef SKIP
#define SKIP(s, ...) XFAIL(s, ##__VA_ARGS__)
#endif
TEST(wait_simple)
{
int pidfd = -1;
pid_t parent_tid = -1;
struct __clone_args args = {
.parent_tid = ptr_to_u64(&parent_tid),
.pidfd = ptr_to_u64(&pidfd),
.flags = CLONE_PIDFD | CLONE_PARENT_SETTID,
.exit_signal = SIGCHLD,
};
pid_t pid;
siginfo_t info = {
.si_signo = 0,
};
pidfd = open("/proc/self", O_DIRECTORY | O_RDONLY | O_CLOEXEC);
ASSERT_GE(pidfd, 0);
pid = sys_waitid(P_PIDFD, pidfd, &info, WEXITED);
ASSERT_NE(pid, 0);
EXPECT_EQ(close(pidfd), 0);
pidfd = -1;
pidfd = open("/dev/null", O_RDONLY | O_CLOEXEC);
ASSERT_GE(pidfd, 0);
pid = sys_waitid(P_PIDFD, pidfd, &info, WEXITED);
ASSERT_NE(pid, 0);
EXPECT_EQ(close(pidfd), 0);
pidfd = -1;
pid = sys_clone3(&args, sizeof(args));
ASSERT_GE(pid, 0);
if (pid == 0)
exit(EXIT_SUCCESS);
pid = sys_waitid(P_PIDFD, pidfd, &info, WEXITED);
ASSERT_GE(pid, 0);
ASSERT_EQ(WIFEXITED(info.si_status), true);
ASSERT_EQ(WEXITSTATUS(info.si_status), 0);
EXPECT_EQ(close(pidfd), 0);
ASSERT_EQ(info.si_signo, SIGCHLD);
ASSERT_EQ(info.si_code, CLD_EXITED);
ASSERT_EQ(info.si_pid, parent_tid);
}
TEST(wait_states)
{
int pidfd = -1;
pid_t parent_tid = -1;
struct __clone_args args = {
.parent_tid = ptr_to_u64(&parent_tid),
.pidfd = ptr_to_u64(&pidfd),
.flags = CLONE_PIDFD | CLONE_PARENT_SETTID,
.exit_signal = SIGCHLD,
};
int pfd[2];
pid_t pid;
siginfo_t info = {
.si_signo = 0,
};
ASSERT_EQ(pipe(pfd), 0);
pid = sys_clone3(&args, sizeof(args));
ASSERT_GE(pid, 0);
if (pid == 0) {
char buf[2];
close(pfd[1]);
kill(getpid(), SIGSTOP);
ASSERT_EQ(read(pfd[0], buf, 1), 1);
close(pfd[0]);
kill(getpid(), SIGSTOP);
exit(EXIT_SUCCESS);
}
close(pfd[0]);
ASSERT_EQ(sys_waitid(P_PIDFD, pidfd, &info, WSTOPPED), 0);
ASSERT_EQ(info.si_signo, SIGCHLD);
ASSERT_EQ(info.si_code, CLD_STOPPED);
ASSERT_EQ(info.si_pid, parent_tid);
ASSERT_EQ(sys_pidfd_send_signal(pidfd, SIGCONT, NULL, 0), 0);
ASSERT_EQ(sys_waitid(P_PIDFD, pidfd, &info, WCONTINUED), 0);
ASSERT_EQ(write(pfd[1], "C", 1), 1);
close(pfd[1]);
ASSERT_EQ(info.si_signo, SIGCHLD);
ASSERT_EQ(info.si_code, CLD_CONTINUED);
ASSERT_EQ(info.si_pid, parent_tid);
ASSERT_EQ(sys_waitid(P_PIDFD, pidfd, &info, WUNTRACED), 0);
ASSERT_EQ(info.si_signo, SIGCHLD);
ASSERT_EQ(info.si_code, CLD_STOPPED);
ASSERT_EQ(info.si_pid, parent_tid);
ASSERT_EQ(sys_pidfd_send_signal(pidfd, SIGKILL, NULL, 0), 0);
ASSERT_EQ(sys_waitid(P_PIDFD, pidfd, &info, WEXITED), 0);
ASSERT_EQ(info.si_signo, SIGCHLD);
ASSERT_EQ(info.si_code, CLD_KILLED);
ASSERT_EQ(info.si_pid, parent_tid);
EXPECT_EQ(close(pidfd), 0);
}
TEST(wait_nonblock)
{
int pidfd;
unsigned int flags = 0;
pid_t parent_tid = -1;
struct __clone_args args = {
.parent_tid = ptr_to_u64(&parent_tid),
.flags = CLONE_PARENT_SETTID,
.exit_signal = SIGCHLD,
};
int ret;
pid_t pid;
siginfo_t info = {
.si_signo = 0,
};
/*
* Callers need to see ECHILD with non-blocking pidfds when no child
* processes exists.
*/
pidfd = sys_pidfd_open(getpid(), PIDFD_NONBLOCK);
EXPECT_GE(pidfd, 0) {
/* pidfd_open() doesn't support PIDFD_NONBLOCK. */
ASSERT_EQ(errno, EINVAL);
SKIP(return, "Skipping PIDFD_NONBLOCK test");
}
ret = sys_waitid(P_PIDFD, pidfd, &info, WEXITED);
ASSERT_LT(ret, 0);
ASSERT_EQ(errno, ECHILD);
EXPECT_EQ(close(pidfd), 0);
pid = sys_clone3(&args, sizeof(args));
ASSERT_GE(pid, 0);
if (pid == 0) {
kill(getpid(), SIGSTOP);
exit(EXIT_SUCCESS);
}
pidfd = sys_pidfd_open(pid, PIDFD_NONBLOCK);
EXPECT_GE(pidfd, 0) {
/* pidfd_open() doesn't support PIDFD_NONBLOCK. */
ASSERT_EQ(errno, EINVAL);
SKIP(return, "Skipping PIDFD_NONBLOCK test");
}
flags = fcntl(pidfd, F_GETFL, 0);
ASSERT_GT(flags, 0);
ASSERT_GT((flags & O_NONBLOCK), 0);
/*
* Callers need to see EAGAIN/EWOULDBLOCK with non-blocking pidfd when
* child processes exist but none have exited.
*/
ret = sys_waitid(P_PIDFD, pidfd, &info, WEXITED);
ASSERT_LT(ret, 0);
ASSERT_EQ(errno, EAGAIN);
/*
* Callers need to continue seeing 0 with non-blocking pidfd and
* WNOHANG raised explicitly when child processes exist but none have
* exited.
*/
ret = sys_waitid(P_PIDFD, pidfd, &info, WEXITED | WNOHANG);
ASSERT_EQ(ret, 0);
ASSERT_EQ(fcntl(pidfd, F_SETFL, (flags & ~O_NONBLOCK)), 0);
ASSERT_EQ(sys_waitid(P_PIDFD, pidfd, &info, WSTOPPED), 0);
ASSERT_EQ(info.si_signo, SIGCHLD);
ASSERT_EQ(info.si_code, CLD_STOPPED);
ASSERT_EQ(info.si_pid, parent_tid);
ASSERT_EQ(sys_pidfd_send_signal(pidfd, SIGCONT, NULL, 0), 0);
ASSERT_EQ(sys_waitid(P_PIDFD, pidfd, &info, WEXITED), 0);
ASSERT_EQ(info.si_signo, SIGCHLD);
ASSERT_EQ(info.si_code, CLD_EXITED);
ASSERT_EQ(info.si_pid, parent_tid);
EXPECT_EQ(close(pidfd), 0);
}
TEST_HARNESS_MAIN
@@ -0,0 +1,132 @@
// 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 <sys/xattr.h>
#include "pidfd.h"
#include "kselftest_harness.h"
FIXTURE(pidfs_xattr)
{
pid_t child_pid;
int child_pidfd;
};
FIXTURE_SETUP(pidfs_xattr)
{
self->child_pid = create_child(&self->child_pidfd, CLONE_NEWUSER | CLONE_NEWPID);
EXPECT_GE(self->child_pid, 0);
if (self->child_pid == 0)
_exit(EXIT_SUCCESS);
}
FIXTURE_TEARDOWN(pidfs_xattr)
{
sys_waitid(P_PID, self->child_pid, NULL, WEXITED);
}
TEST_F(pidfs_xattr, set_get_list_xattr_multiple)
{
int ret, i;
char xattr_name[32];
char xattr_value[32];
char buf[32];
const int num_xattrs = 10;
char list[PATH_MAX] = {};
for (i = 0; i < num_xattrs; i++) {
snprintf(xattr_name, sizeof(xattr_name), "trusted.testattr%d", i);
snprintf(xattr_value, sizeof(xattr_value), "testvalue%d", i);
ret = fsetxattr(self->child_pidfd, xattr_name, xattr_value, strlen(xattr_value), 0);
ASSERT_EQ(ret, 0);
}
for (i = 0; i < num_xattrs; i++) {
snprintf(xattr_name, sizeof(xattr_name), "trusted.testattr%d", i);
snprintf(xattr_value, sizeof(xattr_value), "testvalue%d", i);
memset(buf, 0, sizeof(buf));
ret = fgetxattr(self->child_pidfd, xattr_name, buf, sizeof(buf));
ASSERT_EQ(ret, strlen(xattr_value));
ASSERT_EQ(strcmp(buf, xattr_value), 0);
}
ret = flistxattr(self->child_pidfd, list, sizeof(list));
ASSERT_GT(ret, 0);
for (i = 0; i < num_xattrs; i++) {
snprintf(xattr_name, sizeof(xattr_name), "trusted.testattr%d", i);
bool found = false;
for (char *it = list; it < list + ret; it += strlen(it) + 1) {
if (strcmp(it, xattr_name))
continue;
found = true;
break;
}
ASSERT_TRUE(found);
}
for (i = 0; i < num_xattrs; i++) {
snprintf(xattr_name, sizeof(xattr_name), "trusted.testattr%d", i);
ret = fremovexattr(self->child_pidfd, xattr_name);
ASSERT_EQ(ret, 0);
ret = fgetxattr(self->child_pidfd, xattr_name, buf, sizeof(buf));
ASSERT_EQ(ret, -1);
ASSERT_EQ(errno, ENODATA);
}
}
TEST_F(pidfs_xattr, set_get_list_xattr_persistent)
{
int ret;
char buf[32];
char list[PATH_MAX] = {};
ret = fsetxattr(self->child_pidfd, "trusted.persistent", "persistent value", strlen("persistent value"), 0);
ASSERT_EQ(ret, 0);
memset(buf, 0, sizeof(buf));
ret = fgetxattr(self->child_pidfd, "trusted.persistent", buf, sizeof(buf));
ASSERT_EQ(ret, strlen("persistent value"));
ASSERT_EQ(strcmp(buf, "persistent value"), 0);
ret = flistxattr(self->child_pidfd, list, sizeof(list));
ASSERT_GT(ret, 0);
ASSERT_EQ(strcmp(list, "trusted.persistent"), 0)
ASSERT_EQ(close(self->child_pidfd), 0);
self->child_pidfd = -EBADF;
sleep(2);
self->child_pidfd = sys_pidfd_open(self->child_pid, 0);
ASSERT_GE(self->child_pidfd, 0);
memset(buf, 0, sizeof(buf));
ret = fgetxattr(self->child_pidfd, "trusted.persistent", buf, sizeof(buf));
ASSERT_EQ(ret, strlen("persistent value"));
ASSERT_EQ(strcmp(buf, "persistent value"), 0);
ret = flistxattr(self->child_pidfd, list, sizeof(list));
ASSERT_GT(ret, 0);
ASSERT_EQ(strcmp(list, "trusted.persistent"), 0);
}
TEST_HARNESS_MAIN