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