#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(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);
usleep(200000);
ret = waitpid(pid, NULL, WNOHANG);
ASSERT_EQ(ret, -1);
ASSERT_EQ(errno, ECHILD);
}
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(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(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);
}
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);
pfd.fd = pidfd;
pfd.events = POLLIN;
ret = poll(&pfd, 1, 5000);
ASSERT_EQ(ret, 1);
ASSERT_TRUE(pfd.revents & POLLIN);
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), 42);
ret = waitpid(pid, NULL, WNOHANG);
ASSERT_EQ(ret, -1);
ASSERT_EQ(errno, ECHILD);
close(pidfd);
}
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);
ret = sys_pidfd_send_signal(pidfd, SIGKILL, NULL, 0);
ASSERT_EQ(ret, 0);
pfd.fd = pidfd;
pfd.events = POLLIN;
ret = poll(&pfd, 1, 5000);
ASSERT_EQ(ret, 1);
ASSERT_TRUE(pfd.revents & POLLIN);
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);
ret = waitpid(pid, NULL, WNOHANG);
ASSERT_EQ(ret, -1);
ASSERT_EQ(errno, ECHILD);
close(pidfd);
}
TEST(autoreap_reparent)
{
int ipc_sockets[2], ret;
int pidfd = -1;
struct pollfd pfd;
pid_t mid_pid, grandchild_pid;
char buf[32] = {};
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) {
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) {
close(ipc_sockets[1]);
if (gc_pidfd >= 0)
close(gc_pidfd);
pause();
_exit(0);
}
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);
_exit(0);
}
close(ipc_sockets[1]);
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);
ret = waitpid(mid_pid, NULL, 0);
ASSERT_EQ(ret, mid_pid);
pidfd = sys_pidfd_open(grandchild_pid, 0);
ASSERT_GE(pidfd, 0);
ret = sys_pidfd_send_signal(pidfd, SIGKILL, NULL, 0);
ASSERT_EQ(ret, 0);
pfd.fd = pidfd;
pfd.events = POLLIN;
ret = poll(&pfd, 1, 5000);
ASSERT_EQ(ret, 1);
ASSERT_TRUE(pfd.revents & POLLIN);
ret = waitpid(grandchild_pid, NULL, WNOHANG);
EXPECT_EQ(ret, -1);
EXPECT_EQ(errno, ECHILD);
close(pidfd);
prctl(PR_SET_CHILD_SUBREAPER, 0);
}
static int thread_sock_fd;
static void *thread_func(void *arg)
{
write_nointr(thread_sock_fd, "1", 1);
usleep(200000);
return NULL;
}
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]);
thread_sock_fd = ipc_sockets[1];
pthread_create(&thread, NULL, thread_func, NULL);
pthread_detach(thread);
usleep(100000);
_exit(99);
}
close(ipc_sockets[1]);
ret = read_nointr(ipc_sockets[0], &c, 1);
close(ipc_sockets[0]);
ASSERT_EQ(ret, 1);
pfd.fd = pidfd;
pfd.events = POLLIN;
ret = poll(&pfd, 1, 5000);
ASSERT_EQ(ret, 1);
ASSERT_TRUE(pfd.revents & POLLIN);
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);
ret = waitpid(pid, NULL, WNOHANG);
ASSERT_EQ(ret, -1);
ASSERT_EQ(errno, ECHILD);
close(pidfd);
}
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]);
gc = fork();
if (gc < 0) {
write_nointr(ipc_sockets[1], "E", 1);
_exit(1);
}
if (gc == 0) {
close(ipc_sockets[1]);
_exit(77);
}
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);
ASSERT_EQ(buf[0], 'P');
pfd.fd = pidfd;
pfd.events = POLLIN;
ret = poll(&pfd, 1, 5000);
ASSERT_EQ(ret, 1);
ret = waitpid(pid, NULL, WNOHANG);
ASSERT_EQ(ret, -1);
ASSERT_EQ(errno, ECHILD);
close(pidfd);
}
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;
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) {
ret = prctl(PR_GET_NO_NEW_PRIVS, 0, 0, 0, 0);
_exit(ret == 1 ? 0 : 1);
}
ASSERT_GE(pidfd, 0);
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");
}
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("Child did not have no_new_privs set");
}
close(pidfd);
}
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(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) {
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);
}
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));
}
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);
pollfd_fd = sys_pidfd_open(pid, 0);
ASSERT_GE(pollfd_fd, 0);
close(pidfd);
pfd.fd = pollfd_fd;
pfd.events = POLLIN;
ret = poll(&pfd, 1, 5000);
ASSERT_EQ(ret, 1);
ASSERT_TRUE(pfd.revents & POLLIN);
usleep(100000);
ret = waitpid(pid, NULL, WNOHANG);
ASSERT_EQ(ret, -1);
ASSERT_EQ(errno, ECHILD);
close(pollfd_fd);
}
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);
}
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);
}
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(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_fd = sys_pidfd_open(pid, 0);
ASSERT_GE(open_fd, 0);
close(open_fd);
usleep(200000);
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_fd = sys_pidfd_open(pid, 0);
ASSERT_GE(open_fd, 0);
close(pidfd);
pfd.fd = open_fd;
pfd.events = POLLIN;
ret = poll(&pfd, 1, 5000);
ASSERT_EQ(ret, 1);
ASSERT_TRUE(pfd.revents & POLLIN);
usleep(100000);
ret = waitpid(pid, NULL, WNOHANG);
ASSERT_EQ(ret, -1);
ASSERT_EQ(errno, ECHILD);
close(open_fd);
}
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;
ret = drop_all_caps();
ASSERT_EQ(ret, 0);
pid = sys_clone3(&args, sizeof(args));
ASSERT_EQ(pid, -1);
ASSERT_EQ(errno, EPERM);
}
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);
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