// Copyright 2026 The gVisor Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "gmock/gmock.h" #include "gtest/gtest.h" #include "absl/flags/flag.h" #include "absl/strings/str_cat.h" #include "absl/time/clock.h" #include "absl/time/time.h" #include "test/util/cleanup.h" #include "test/util/file_descriptor.h" #include "test/util/linux_capability_util.h" #include "test/util/logging.h" #include "test/util/memory_util.h" #include "test/util/multiprocess_util.h" #include "test/util/posix_error.h" #include "test/util/save_util.h" #include "test/util/test_util.h" #include "test/util/thread_util.h" #if defined(__x86_64__) #ifndef SYS_pidfd_send_signal #define SYS_pidfd_send_signal 424 #endif #ifndef SYS_pidfd_open #define SYS_pidfd_open 434 #endif #ifndef SYS_clone3 #define SYS_clone3 435 #endif #ifndef SYS_pidfd_getfd #define SYS_pidfd_getfd 438 #endif #elif defined(__aarch64__) #ifndef SYS_pidfd_send_signal #define SYS_pidfd_send_signal 424 #endif #ifndef SYS_pidfd_open #define SYS_pidfd_open 434 #endif #ifndef SYS_clone3 #define SYS_clone3 435 #endif #ifndef SYS_pidfd_getfd #define SYS_pidfd_getfd 438 #endif #else #error "Unknown architecture" #endif #ifndef SYS_pidfd_open #define SYS_pidfd_open 434 #endif #ifndef SYS_clone3 #define SYS_clone3 435 #endif #ifndef SYS_pidfd_send_signal #define SYS_pidfd_send_signal 424 #endif #ifndef SYS_pidfd_getfd #define SYS_pidfd_getfd 438 #endif // A flag for clone3(). #ifndef CLONE_PIDFD #define CLONE_PIDFD 0x00001000 #endif // A flag for waitid(). #ifndef P_PIDFD #define P_PIDFD static_cast(3) #endif // A flag for pidfd_open(). #ifndef PIDFD_THREAD #define PIDFD_THREAD O_EXCL #endif // A flag for pidfd_send_signal(). #ifndef PIDFD_SIGNAL_THREAD #define PIDFD_SIGNAL_THREAD (1UL << 0) #endif ABSL_FLAG(std::string, pidfd_helper, "", "The name of the helper logic to run."); ABSL_FLAG(int, pidfd_pipe_fd, -1, "The FD of a pipe for coordination."); ABSL_FLAG(int, pidfd_tid_pipe_fd, -1, "Another pipe FD for TID reporting."); namespace gvisor { namespace testing { // RunLeaderDelayedHelper implements the helper logic for the test // PidfdThreadLeaderDelayedNotification. Threadgroup leader creates a subthread // that waits on a pipe, and then exits immediately. The subthread sticks around // until signalled. void RunLeaderDelayedHelper() { int rfd = absl::GetFlag(FLAGS_pidfd_pipe_fd); // Use raw pthread_create instead of ScopedThread to avoid dependency on the // (shortly exiting) leader. pthread_t pt; TEST_PCHECK(pthread_create( &pt, nullptr, [](void* arg) -> void* { int fd = static_cast(reinterpret_cast(arg)); char buf; (void)read(fd, &buf, 1); // Wait to be signaled. close(fd); _exit(0); return nullptr; }, reinterpret_cast(static_cast(rfd))) == 0); syscall(SYS_exit, 0); // Leader (just the thread) exits. } // RunSubthreadNotificationHelper implements the helper logic for // PidfdThreadSubthreadNotification. The thread-group leader creates a subthread // that reports its TID on a pipe and then waits on another pipe. The leader // then pauses. void RunSubthreadNotificationHelper() { int rfd = absl::GetFlag(FLAGS_pidfd_pipe_fd); int wfd = absl::GetFlag(FLAGS_pidfd_tid_pipe_fd); ScopedThread thread([rfd, wfd] { pid_t tid = gettid(); (void)write(wfd, &tid, sizeof(tid)); // Report TID. char buf; (void)read(rfd, &buf, 1); // Wait to be signaled. close(rfd); close(wfd); }); pause(); // Leader pauses. } // RunExecRevivalHelper implements the child process for the // PidfdThreadExecRevival test. The leader spawns a subthread and then pauses. // The subthread reports its TID, and then execve()s to a program that pauses. void RunExecRevivalHelper() { int rfd = absl::GetFlag(FLAGS_pidfd_pipe_fd); int wfd = absl::GetFlag(FLAGS_pidfd_tid_pipe_fd); ScopedThread t([rfd, wfd] { pid_t tid = gettid(); (void)write(wfd, &tid, sizeof(tid)); // Report TID. char buf; (void)read(rfd, &buf, 1); // Wait to be signaled. close(rfd); close(wfd); char* const argv[] = {const_cast("/proc/self/exe"), const_cast("--pidfd_helper=RunPauseHelper"), nullptr}; execve(argv[0], argv, nullptr); // execve() to become leader. _exit(errno); }); pause(); // Leader pauses. } // RunPauseHelper implements a simple helper that pauses indefinitely. void RunPauseHelper() { pause(); } void RunSubthreadTracedHelper() { int rfd = absl::GetFlag(FLAGS_pidfd_pipe_fd); int tid_wfd = absl::GetFlag(FLAGS_pidfd_tid_pipe_fd); ScopedThread t([rfd, tid_wfd] { pid_t tid = gettid(); TEST_PCHECK(write(tid_wfd, &tid, sizeof(tid)) == sizeof(tid)); char buf; (void)read(rfd, &buf, 1); // Wait to exit. }); } namespace { class ScopedChildReaper { public: explicit ScopedChildReaper(pid_t child) : child_(child) {} ~ScopedChildReaper() { if (child_ <= 0) return; kill(child_, SIGKILL); kill(child_, SIGCONT); int status; RetryEINTR(waitpid)(child_, &status, 0); } void Release() { child_ = -1; } private: pid_t child_; }; PosixErrorOr PidfdOpen(pid_t pid, unsigned int flags) { int fd = syscall(SYS_pidfd_open, pid, flags); if (fd < 0) { return PosixError(errno, "pidfd_open failed"); } return FileDescriptor(fd); } int PidfdSendSignal(int pidfd, int sig, siginfo_t* info, unsigned int flags) { return syscall(SYS_pidfd_send_signal, pidfd, sig, info, flags); } PosixErrorOr PidfdGetfd(int pidfd, int targetfd, unsigned int flags) { int fd = syscall(SYS_pidfd_getfd, pidfd, targetfd, flags); if (fd < 0) { return PosixError(errno, "pidfd_getfd failed"); } return FileDescriptor(fd); } template PosixErrorOr Clone3Pidfd(pid_t& child, ChildFunc&& child_func) { clone_args ca = {}; ca.flags = CLONE_PIDFD; ca.exit_signal = SIGCHLD; int fd = -1; ca.pidfd = static_cast(reinterpret_cast(&fd)); child = syscall(SYS_clone3, &ca, sizeof(ca)); if (child == 0) { child_func(); _exit(0); } if (child < 0) { return PosixError(errno, "clone3 failed"); } if (fd < 0) { return PosixError(errno, "clone3 succeeded but no pidfd returned"); } return FileDescriptor(fd); } TEST(PidfdTest, PidfdOpenInvalid) { EXPECT_THAT(PidfdOpen(-1, 0), PosixErrorIs(EINVAL)); EXPECT_THAT(PidfdOpen(getpid(), 1), PosixErrorIs(EINVAL)); } TEST(PidfdTest, OpenPidfdSelfSucceeds) { auto pidfd = ASSERT_NO_ERRNO_AND_VALUE(PidfdOpen(getpid(), 0)); int flags = fcntl(pidfd.get(), F_GETFD, 0); EXPECT_TRUE(flags & FD_CLOEXEC); } static std::atomic signal_received{0}; static void sigusr1_handler(int sig) { if (sig == SIGUSR1) signal_received.store(1, std::memory_order_relaxed); } // "PidfdOpenPidfd" refers to a pidfd obtained via pidfd_open(). TEST(PidfdTest, SendSignalViaPidfdOpenPidfdToSelfSucceeds) { auto pidfd = ASSERT_NO_ERRNO_AND_VALUE(PidfdOpen(getpid(), 0)); auto sighandler = signal(SIGUSR1, sigusr1_handler); // Restore old handler after the test. auto cleanup = Cleanup([sighandler] { signal(SIGUSR1, sighandler); }); signal_received.store(0, std::memory_order_relaxed); EXPECT_THAT(PidfdSendSignal(pidfd.get(), SIGUSR1, nullptr, 0), SyscallSucceeds()); absl::Time start = absl::Now(); while (signal_received.load(std::memory_order_relaxed) == 0) { if (absl::Now() - start > absl::Seconds(2)) break; sched_yield(); } EXPECT_EQ(signal_received.load(std::memory_order_relaxed), 1); } TEST(PidfdTest, SendSignalViaPidfdOpenPidfdToDeadChildFails) { pid_t child = fork(); if (child == 0) { _exit(0); } ASSERT_THAT(child, SyscallSucceeds()); ScopedChildReaper cleanup(child); auto pidfd = ASSERT_NO_ERRNO_AND_VALUE(PidfdOpen(child, 0)); int status; ASSERT_THAT(RetryEINTR(waitpid)(child, &status, 0), SyscallSucceedsWithValue(child)); cleanup.Release(); EXPECT_THAT(PidfdSendSignal(pidfd.get(), 0, nullptr, 0), SyscallFailsWithErrno(ESRCH)); } TEST(PidfdTest, Clone3InvalidFdAddrFails) { // Unmapped or PROT_NONE pointer EFAULTs. { auto mapping = ASSERT_NO_ERRNO_AND_VALUE(MmapAnon(kPageSize, PROT_NONE, MAP_PRIVATE)); clone_args ca = {}; ca.flags = CLONE_PIDFD; ca.exit_signal = SIGCHLD; ca.pidfd = static_cast(mapping.addr()); pid_t child = syscall(SYS_clone3, &ca, sizeof(ca)); ASSERT_LT(child, 0); EXPECT_EQ(errno, EFAULT); } // Read-only pointer EFAULTs. { auto mapping = ASSERT_NO_ERRNO_AND_VALUE(MmapAnon(kPageSize, PROT_READ, MAP_PRIVATE)); clone_args ca = {}; ca.flags = CLONE_PIDFD; ca.exit_signal = SIGCHLD; ca.pidfd = static_cast(mapping.addr()); pid_t child = syscall(SYS_clone3, &ca, sizeof(ca)); ASSERT_LT(child, 0); EXPECT_EQ(errno, EFAULT); } } TEST(PidfdTest, SendSignalToDeadChildFails) { pid_t child = -1; auto pidfd = ASSERT_NO_ERRNO_AND_VALUE(Clone3Pidfd(child, []() { _exit(0); })); ScopedChildReaper cleanup(child); int status; ASSERT_THAT(RetryEINTR(waitpid)(child, &status, 0), SyscallSucceedsWithValue(child)); cleanup.Release(); EXPECT_THAT(PidfdSendSignal(pidfd.get(), 0, nullptr, 0), SyscallFailsWithErrno(ESRCH)); } TEST(PidfdTest, SendSignalToZombieSucceeds) { pid_t child = -1; auto pidfd = ASSERT_NO_ERRNO_AND_VALUE(Clone3Pidfd(child, []() { _exit(0); })); ScopedChildReaper cleanup(child); // Wait for child to become a zombie. siginfo_t info = {}; ASSERT_THAT(RetryEINTR(waitid)(P_PID, child, &info, WEXITED | WNOWAIT), SyscallSucceeds()); // Target process is a zombie. pidfd_send_signal should fail with ESRCH. EXPECT_THAT(PidfdSendSignal(pidfd.get(), 0, nullptr, 0), SyscallSucceeds()); } TEST(PidfdTest, SendSignalToStoppedChildSucceeds) { pid_t child = -1; auto pidfd = ASSERT_NO_ERRNO_AND_VALUE(Clone3Pidfd(child, []() { raise(SIGSTOP); _exit(0); })); ScopedChildReaper cleanup(child); int status; ASSERT_THAT(RetryEINTR(waitpid)(child, &status, WSTOPPED), SyscallSucceedsWithValue(child)); ASSERT_TRUE(WIFSTOPPED(status)); EXPECT_EQ(WSTOPSIG(status), SIGSTOP); EXPECT_THAT(PidfdSendSignal(pidfd.get(), SIGCONT, nullptr, 0), SyscallSucceeds()); ASSERT_THAT(RetryEINTR(waitpid)(child, &status, 0), SyscallSucceedsWithValue(child)); EXPECT_TRUE(WIFEXITED(status)); EXPECT_EQ(WEXITSTATUS(status), 0); cleanup.Release(); } TEST(PidfdTest, SendSignalToPausedChildFailsWhenUnprivileged) { SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SETUID))); pid_t child = -1; auto pidfd = ASSERT_NO_ERRNO_AND_VALUE(Clone3Pidfd(child, []() { pause(); _exit(0); })); ScopedChildReaper cleanup(child); int pidfd_raw = pidfd.get(); constexpr int kFeebleUid = 65534; EXPECT_THAT(InForkedProcess([pidfd_raw] { TEST_PCHECK(setresuid(kFeebleUid, kFeebleUid, kFeebleUid) == 0); TEST_PCHECK_MSG(!HaveCapability(CAP_KILL).ValueOrDie(), "Still have CAP_KILL"); TEST_CHECK_ERRNO( syscall(SYS_pidfd_send_signal, pidfd_raw, 0, nullptr, 0), EPERM); }), IsPosixErrorOkAndHolds(0)); } TEST(PidfdTest, SendSignalRejection) { pid_t child = -1; auto pidfd = ASSERT_NO_ERRNO_AND_VALUE(Clone3Pidfd(child, []() { pause(); _exit(0); })); ScopedChildReaper cleanup(child); // Signo mismatch. siginfo_t info = {}; info.si_signo = SIGUSR1; EXPECT_THAT(PidfdSendSignal(pidfd.get(), SIGUSR2, &info, 0), SyscallFailsWithErrno(EINVAL)); // Spoofing protection. info = {}; info.si_signo = SIGUSR1; info.si_code = SI_TKILL; EXPECT_THAT(PidfdSendSignal(pidfd.get(), SIGUSR1, &info, 0), SyscallFailsWithErrno(EPERM)); info = {}; info.si_signo = SIGUSR1; info.si_code = 0; EXPECT_THAT(PidfdSendSignal(pidfd.get(), SIGUSR1, &info, 0), SyscallFailsWithErrno(EPERM)); // But can spoof self. auto self_pidfd = ASSERT_NO_ERRNO_AND_VALUE(PidfdOpen(getpid(), 0)); info = {}; info.si_signo = 0; info.si_code = SI_TKILL; EXPECT_THAT(PidfdSendSignal(self_pidfd.get(), 0, &info, 0), SyscallSucceeds()); // Valid signal delivery. EXPECT_THAT(PidfdSendSignal(pidfd.get(), SIGUSR1, nullptr, 0), SyscallSucceeds()); info = {}; info.si_signo = SIGUSR2; info.si_code = SI_QUEUE; EXPECT_THAT(PidfdSendSignal(pidfd.get(), SIGUSR2, &info, 0), SyscallSucceeds()); } TEST(PidfdTest, GetfdUnknown) { pid_t child = -1; auto pidfd = ASSERT_NO_ERRNO_AND_VALUE(Clone3Pidfd(child, []() { pause(); _exit(0); })); ScopedChildReaper cleanup(child); // The child is very unlikely to have an fd at 666. EXPECT_THAT(PidfdGetfd(pidfd.get(), 666, 0), PosixErrorIs(EBADF)); } TEST(PidfdTest, GetfdInvalidFlags) { pid_t child = -1; auto pidfd = ASSERT_NO_ERRNO_AND_VALUE(Clone3Pidfd(child, []() { pause(); _exit(0); })); ScopedChildReaper cleanup(child); EXPECT_THAT(PidfdGetfd(pidfd.get(), 0, 1), PosixErrorIs(EINVAL)); } TEST(PidfdTest, GetfdWorks) { int pfd[2]; ASSERT_THAT(pipe(pfd), SyscallSucceeds()); FileDescriptor read_pipe(pfd[0]); FileDescriptor write_pipe(pfd[1]); pid_t child = -1; auto pidfd = ASSERT_NO_ERRNO_AND_VALUE(Clone3Pidfd(child, []() { pause(); _exit(0); })); ScopedChildReaper cleanup(child); // Close our read end of the pipe. int childs_read_fd = read_pipe.get(); read_pipe.reset(); // And get the child's copy instead. auto duped_readfd = ASSERT_NO_ERRNO_AND_VALUE(PidfdGetfd(pidfd.get(), childs_read_fd, 0)); int flags = fcntl(duped_readfd.get(), F_GETFD, 0); EXPECT_TRUE(flags & FD_CLOEXEC); // Verify that the stolen read fd works. const char write_buf[] = "hello pidfd"; EXPECT_THAT(write(write_pipe.get(), write_buf, sizeof(write_buf)), SyscallSucceedsWithValue(sizeof(write_buf))); char read_buf[sizeof(write_buf)] = {}; EXPECT_THAT(read(duped_readfd.get(), read_buf, sizeof(read_buf)), SyscallSucceedsWithValue(sizeof(read_buf))); EXPECT_STREQ(read_buf, write_buf); } TEST(PidfdTest, GetfdFailsWhenUnprivileged) { pid_t child = -1; auto pidfd = ASSERT_NO_ERRNO_AND_VALUE(Clone3Pidfd(child, []() { pause(); _exit(0); })); ScopedChildReaper cleanup(child); int stdin_fd = 0; // We can get at the child's stdin. auto duped_fd = ASSERT_NO_ERRNO_AND_VALUE(PidfdGetfd(pidfd.get(), stdin_fd, 0)); // But our unprivileged other child cannot. EXPECT_THAT(InForkedProcess([&pidfd, stdin_fd] { TEST_CHECK_SUCCESS(syscall(SYS_unshare, CLONE_NEWUSER)); TEST_CHECK_ERRNO( syscall(SYS_pidfd_getfd, pidfd.get(), stdin_fd, 0), EPERM); }), IsPosixErrorOkAndHolds(0)); } TEST(PidfdTest, InvalidPidfd) { constexpr int kNotAPidfd = 0; EXPECT_THAT(PidfdSendSignal(kNotAPidfd, SIGKILL, nullptr, 0), SyscallFailsWithErrno(EBADF)); EXPECT_THAT(PidfdGetfd(kNotAPidfd, 0, 0), PosixErrorIs(EBADF)); } TEST(PidfdTest, WaitSingleState) { pid_t child = -1; auto pidfd = ASSERT_NO_ERRNO_AND_VALUE(Clone3Pidfd(child, []() { _exit(66); })); ScopedChildReaper cleanup(child); siginfo_t info = {}; int ret = RetryEINTR(waitid)(P_PIDFD, pidfd.get(), &info, WEXITED); ASSERT_THAT(ret, SyscallSucceeds()); cleanup.Release(); EXPECT_EQ(info.si_signo, SIGCHLD); EXPECT_EQ(info.si_code, CLD_EXITED); EXPECT_EQ(info.si_status, 66); EXPECT_EQ(info.si_pid, child); } TEST(PidfdTest, WaitMultipleStates) { int pfd[2]; ASSERT_THAT(pipe(pfd), SyscallSucceeds()); FileDescriptor read_pipe(pfd[0]); FileDescriptor write_pipe(pfd[1]); pid_t child = -1; auto pidfd = ASSERT_NO_ERRNO_AND_VALUE(Clone3Pidfd(child, [&write_pipe, &read_pipe]() { write_pipe.reset(); TEST_CHECK_SUCCESS(kill(getpid(), SIGSTOP)); char buf; TEST_PCHECK(read(read_pipe.get(), &buf, 1) == 1); read_pipe.reset(); TEST_CHECK_SUCCESS(kill(getpid(), SIGSTOP)); _exit(0); })); ScopedChildReaper cleanup(child); read_pipe.reset(); siginfo_t info = {}; ASSERT_THAT(RetryEINTR(waitid)(P_PIDFD, pidfd.get(), &info, WSTOPPED), SyscallSucceeds()); EXPECT_EQ(info.si_code, CLD_STOPPED); EXPECT_THAT(PidfdSendSignal(pidfd.get(), SIGCONT, nullptr, 0), SyscallSucceeds()); info = {}; ASSERT_THAT(RetryEINTR(waitid)(P_PIDFD, pidfd.get(), &info, WCONTINUED), SyscallSucceeds()); EXPECT_EQ(info.si_code, CLD_CONTINUED); info = {}; ASSERT_THAT(write(write_pipe.get(), "C", 1), SyscallSucceedsWithValue(1)); write_pipe.reset(); ASSERT_THAT(RetryEINTR(waitid)(P_PIDFD, pidfd.get(), &info, WSTOPPED), SyscallSucceeds()); EXPECT_EQ(info.si_code, CLD_STOPPED); info = {}; EXPECT_THAT(PidfdSendSignal(pidfd.get(), SIGKILL, nullptr, 0), SyscallSucceeds()); ASSERT_THAT(RetryEINTR(waitid)(P_PIDFD, pidfd.get(), &info, WEXITED), SyscallSucceeds()); EXPECT_EQ(info.si_code, CLD_KILLED); cleanup.Release(); } TEST(PidfdTest, WaitNonblock) { pid_t child = -1; auto pidfd = ASSERT_NO_ERRNO_AND_VALUE(Clone3Pidfd(child, []() { pause(); _exit(0); })); ScopedChildReaper cleanup(child); int flags = fcntl(pidfd.get(), F_GETFL, 0); ASSERT_THAT(fcntl(pidfd.get(), F_SETFL, flags | O_NONBLOCK), SyscallSucceeds()); siginfo_t info = {}; EXPECT_THAT(RetryEINTR(waitid)(P_PIDFD, pidfd.get(), &info, WEXITED), SyscallFailsWithErrno(EAGAIN)); EXPECT_THAT( RetryEINTR(waitid)(P_PIDFD, pidfd.get(), &info, WEXITED | WNOHANG), SyscallSucceedsWithValue(0)); kill(child, SIGKILL); ASSERT_THAT(fcntl(pidfd.get(), F_SETFL, flags), SyscallSucceeds()); ASSERT_THAT(RetryEINTR(waitid)(P_PIDFD, pidfd.get(), &info, WEXITED), SyscallSucceeds()); EXPECT_EQ(info.si_code, CLD_KILLED); cleanup.Release(); } TEST(PidfdTest, Poll) { pid_t child = -1; auto pidfd = ASSERT_NO_ERRNO_AND_VALUE(Clone3Pidfd(child, []() { _exit(0); })); ScopedChildReaper cleanup(child); // Wait for the child to become a zombie. siginfo_t info = {}; ASSERT_THAT(RetryEINTR(waitid)(P_PID, child, &info, WEXITED | WNOWAIT), SyscallSucceeds()); // The child is already a zombie, POLLIN should be immediately available. struct pollfd fds = { .fd = pidfd.get(), .events = POLLIN, }; ASSERT_THAT(poll(&fds, 1, 0), SyscallSucceedsWithValue(1)); EXPECT_TRUE(fds.revents & POLLIN); // Reap the child to observe POLLHUP. int status; ASSERT_THAT(RetryEINTR(waitpid)(child, &status, 0), SyscallSucceedsWithValue(child)); cleanup.Release(); fds.revents = 0; ASSERT_THAT(poll(&fds, 1, 0), SyscallSucceedsWithValue(1)); EXPECT_TRUE(fds.revents & POLLIN); KernelVersion version = ASSERT_NO_ERRNO_AND_VALUE(GetKernelVersion()); if (IsRunningOnGvisor() || (version.major > 6 || (version.major == 6 && version.minor >= 10))) { EXPECT_TRUE(fds.revents & POLLHUP); } // Create a second child that only exits when told to do so. int pfd[2]; ASSERT_THAT(pipe(pfd), SyscallSucceeds()); FileDescriptor read_pipe(pfd[0]); FileDescriptor write_pipe(pfd[1]); pid_t child2 = -1; int rfd = read_pipe.get(); auto pidfd2 = ASSERT_NO_ERRNO_AND_VALUE(Clone3Pidfd(child2, [rfd]() { char buf; read(rfd, &buf, 1); _exit(0); })); ScopedChildReaper cleanup2(child2); read_pipe.reset(); // Not pollable yet. struct pollfd fds2 = { .fd = pidfd2.get(), .events = POLLIN, }; ASSERT_THAT(poll(&fds2, 1, 100), SyscallSucceedsWithValue(0)); // Instruct child2 to exit. ASSERT_THAT(write(write_pipe.get(), "a", 1), SyscallSucceedsWithValue(1)); write_pipe.reset(); // Now pollable. ASSERT_THAT(poll(&fds2, 1, 5000), SyscallSucceedsWithValue(1)); EXPECT_TRUE(fds2.revents & POLLIN); } TEST(PidfdTest, Epoll) { pid_t child = -1; auto pidfd = ASSERT_NO_ERRNO_AND_VALUE(Clone3Pidfd(child, []() { _exit(0); })); ScopedChildReaper cleanup(child); // Wait for the child to become a zombie. siginfo_t info = {}; ASSERT_THAT(RetryEINTR(waitid)(P_PID, child, &info, WEXITED | WNOWAIT), SyscallSucceeds()); int epfd = epoll_create1(0); ASSERT_THAT(epfd, SyscallSucceeds()); FileDescriptor epollfd(epfd); struct epoll_event ev = {}; ev.events = EPOLLIN; ev.data.fd = pidfd.get(); ASSERT_THAT(epoll_ctl(epollfd.get(), EPOLL_CTL_ADD, pidfd.get(), &ev), SyscallSucceeds()); struct epoll_event events[1]; // The child is already a zombie, EPOLLIN should be immediately available. ASSERT_THAT(epoll_wait(epollfd.get(), events, 1, 0), SyscallSucceedsWithValue(1)); EXPECT_TRUE(events[0].events & EPOLLIN); // Reap the child to observe EPOLLHUP. int status; ASSERT_THAT(RetryEINTR(waitpid)(child, &status, 0), SyscallSucceedsWithValue(child)); cleanup.Release(); events[0].events = 0; ASSERT_THAT(epoll_wait(epollfd.get(), events, 1, 0), SyscallSucceedsWithValue(1)); EXPECT_TRUE(events[0].events & EPOLLIN); KernelVersion version = ASSERT_NO_ERRNO_AND_VALUE(GetKernelVersion()); if (IsRunningOnGvisor() || (version.major > 6 || (version.major == 6 && version.minor >= 10))) { EXPECT_TRUE(events[0].events & EPOLLHUP); } } TEST(PidfdTest, SetnsInvalidFlags) { SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); pid_t child = -1; auto pidfd = ASSERT_NO_ERRNO_AND_VALUE(Clone3Pidfd(child, []() { pause(); _exit(0); })); ScopedChildReaper cleanup(child); EXPECT_THAT(setns(pidfd.get(), 0), SyscallFailsWithErrno(EINVAL)); EXPECT_THAT(setns(pidfd.get(), -1), SyscallFailsWithErrno(EINVAL)); EXPECT_THAT(setns(pidfd.get(), CLONE_VM), SyscallFailsWithErrno(EINVAL)); } TEST(PidfdTest, SetnsPtraceEPERM) { SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); AutoCapability cap(CAP_SYS_PTRACE, false); int before; ASSERT_THAT(before = prctl(PR_GET_DUMPABLE), SyscallSucceeds()); auto cleanup = Cleanup([before] { ASSERT_THAT(prctl(PR_SET_DUMPABLE, before), SyscallSucceeds()); }); // Mark ourselves as not dumpable. constexpr int kSuidDumpDisable = 0; TEST_PCHECK(prctl(PR_SET_DUMPABLE, kSuidDumpDisable, 0, 0, 0) == 0); auto pidfd = ASSERT_NO_ERRNO_AND_VALUE(PidfdOpen(getpid(), 0)); EXPECT_THAT(InForkedProcess([&pidfd] { TEST_CHECK_SUCCESS(syscall(SYS_unshare, CLONE_NEWNET)); TEST_CHECK_ERRNO(syscall(SYS_setns, pidfd.get(), CLONE_NEWNET), EPERM); }), IsPosixErrorOkAndHolds(0)); } TEST(PidfdTest, SetnsExitedChild) { SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); pid_t child = -1; auto pidfd = ASSERT_NO_ERRNO_AND_VALUE(Clone3Pidfd(child, []() { _exit(0); })); ScopedChildReaper cleanup(child); int std_status; ASSERT_THAT(RetryEINTR(waitpid)(child, &std_status, 0), SyscallSucceedsWithValue(child)); cleanup.Release(); EXPECT_THAT(setns(pidfd.get(), CLONE_NEWNET), SyscallFailsWithErrno(ESRCH)); } TEST(PidfdTest, SetnsZombieChild) { SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); pid_t child = -1; auto pidfd = ASSERT_NO_ERRNO_AND_VALUE(Clone3Pidfd(child, []() { _exit(0); })); ScopedChildReaper cleanup(child); // Wait for the child to become a zombie. siginfo_t info = {}; ASSERT_THAT(RetryEINTR(waitid)(P_PID, child, &info, WEXITED | WNOWAIT), SyscallSucceeds()); EXPECT_THAT(setns(pidfd.get(), CLONE_NEWNET), SyscallFailsWithErrno(ESRCH)); } TEST(PidfdTest, SetnsSingleNamespaceWorks) { SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))); int pfd[2]; ASSERT_THAT(pipe(pfd), SyscallSucceeds()); FileDescriptor pipe_read(pfd[0]); FileDescriptor pipe_write(pfd[1]); pid_t child = -1; constexpr char kNewHostname[] = "pidfd-uts-test"; auto pidfd = ASSERT_NO_ERRNO_AND_VALUE( Clone3Pidfd(child, [&pipe_write, &kNewHostname]() { TEST_CHECK_SUCCESS(syscall(SYS_unshare, CLONE_NEWUTS)); TEST_CHECK_SUCCESS(sethostname(kNewHostname, sizeof(kNewHostname) - 1)); TEST_CHECK_SUCCESS(write(pipe_write.get(), "R", 1)); pause(); _exit(0); })); ScopedChildReaper cleanup(child); char buf; ASSERT_THAT(read(pipe_read.get(), &buf, 1), SyscallSucceedsWithValue(1)); EXPECT_THAT( InForkedProcess([&pidfd, &kNewHostname] { TEST_CHECK_SUCCESS(syscall(SYS_setns, pidfd.get(), CLONE_NEWUTS)); char current_hostname[256] = {}; TEST_CHECK_SUCCESS( gethostname(current_hostname, sizeof(current_hostname))); TEST_PCHECK_MSG(strcmp(current_hostname, kNewHostname) == 0, "hostname mismatch"); }), IsPosixErrorOkAndHolds(0)); } TEST(PidfdTest, PidfdThreadTracksSingleThread) { std::atomic stop_sibling{false}; std::atomic tid{-1}; auto t = std::make_unique([&]() { tid.store(syscall(SYS_gettid)); while (!stop_sibling.load()) { sched_yield(); } }); auto cleanup = Cleanup([&t, &stop_sibling] { stop_sibling.store(true); t.reset(); }); // Wait till the sibling tells us its tid. while (tid.load() == -1) { sched_yield(); } int pidfd_raw = syscall(SYS_pidfd_open, tid.load(), PIDFD_THREAD); if (pidfd_raw == -1 && errno == EINVAL) { GTEST_SKIP() << "PIDFD_THREAD not supported on this kernel"; } ASSERT_THAT(pidfd_raw, SyscallSucceeds()); FileDescriptor pidfd(pidfd_raw); // In the absence of PIDFD_THREAD, we should see ENOENT (EINVAL on older // kernels). int want_errno = ENOENT; if (!IsRunningOnGvisor()) { KernelVersion version = ASSERT_NO_ERRNO_AND_VALUE(GetKernelVersion()); if (version.major < 6 || (version.major == 6 && version.minor < 15)) { want_errno = EINVAL; } } ASSERT_THAT(syscall(SYS_pidfd_open, tid.load(), 0), SyscallFailsWithErrno(want_errno)); struct pollfd pfd = { .fd = pidfd.get(), .events = POLLIN, }; // The sibling thread is alive, so the pidfd should NOT be readable. ASSERT_THAT(RetryEINTR(poll)(&pfd, 1, 0), SyscallSucceedsWithValue(0)); // And we should be able to send it signals. EXPECT_THAT(PidfdSendSignal(pidfd_raw, 0, nullptr, PIDFD_SIGNAL_THREAD), SyscallSucceedsWithValue(0)); // Stop the sibling thread to make the poll report POLLIN. stop_sibling.store(true); t.reset(); cleanup.Release(); pfd.revents = 0; ASSERT_THAT(RetryEINTR(poll)(&pfd, 1, 1000), SyscallSucceedsWithValue(1)); EXPECT_TRUE(pfd.revents & POLLIN); } // PidfdThreadLeaderDelayedNotification verifies that a PIDFD_THREAD for a // thread group leader only notifies when the entire thread group exits, even // if the leader thread itself has already exited. TEST(PidfdTest, PidfdThreadLeaderDelayedNotification) { if (!IsRunningOnGvisor()) { // Test only on kernels bearing this patch: // https://github.com/torvalds/linux/commit/0fb482728ba1 KernelVersion version = ASSERT_NO_ERRNO_AND_VALUE(GetKernelVersion()); if (version.major < 6 || (version.major == 6 && version.minor < 15)) { GTEST_SKIP() << "PIDFD_THREAD behavior requires kernel 6.15+"; } } int pipe_fds[2]; ASSERT_THAT(pipe(pipe_fds), SyscallSucceeds()); FileDescriptor read_pipe(pipe_fds[0]); FileDescriptor write_pipe(pipe_fds[1]); pid_t child; int exec_errno; auto cleanup = ASSERT_NO_ERRNO_AND_VALUE( ForkAndExec("/proc/self/exe", {"/proc/self/exe", "--pidfd_helper=RunLeaderDelayedHelper", absl::StrCat("--pidfd_pipe_fd=", read_pipe.get())}, {}, &child, &exec_errno)); ASSERT_EQ(exec_errno, 0); ASSERT_THAT(child, SyscallSucceeds()); read_pipe.reset(); auto leader_pidfd = ASSERT_NO_ERRNO_AND_VALUE(PidfdOpen(child, PIDFD_THREAD)); // Poll should timeout on the leader thread pidfd as long as the subthread is // alive. struct pollfd pfd = { .fd = leader_pidfd.get(), .events = POLLIN, }; EXPECT_THAT(poll(&pfd, 1, 1000), SyscallSucceedsWithValue(0)); // Let the subthread exit. ASSERT_THAT(write(write_pipe.get(), "a", 1), SyscallSucceedsWithValue(1)); write_pipe.reset(); // Now poll should succeed. EXPECT_THAT(poll(&pfd, 1, 5000), SyscallSucceedsWithValue(1)); EXPECT_TRUE(pfd.revents & POLLIN); } // PidfdThreadSubthreadNotification verifies that a PIDFD_THREAD for a // non-leader thread notifies as soon as that thread exits, even if other // threads (including the leader) in the same group are still alive. TEST(PidfdTest, PidfdThreadSubthreadNotification) { if (!IsRunningOnGvisor()) { // Test only on kernels bearing this patch: // https://github.com/torvalds/linux/commit/0fb482728ba1 KernelVersion version = ASSERT_NO_ERRNO_AND_VALUE(GetKernelVersion()); if (version.major < 6 || (version.major == 6 && version.minor < 15)) { GTEST_SKIP() << "PIDFD_THREAD behavior requires kernel 6.15+"; } } int pipe_fds[2]; ASSERT_THAT(pipe(pipe_fds), SyscallSucceeds()); FileDescriptor read_pipe(pipe_fds[0]); FileDescriptor write_pipe(pipe_fds[1]); int tid_pipe_fds[2]; ASSERT_THAT(pipe(tid_pipe_fds), SyscallSucceeds()); FileDescriptor tid_read_pipe(tid_pipe_fds[0]); FileDescriptor tid_write_pipe(tid_pipe_fds[1]); pid_t child; int exec_errno; auto cleanup = ASSERT_NO_ERRNO_AND_VALUE(ForkAndExec( "/proc/self/exe", {"/proc/self/exe", "--pidfd_helper=RunSubthreadNotificationHelper", absl::StrCat("--pidfd_pipe_fd=", read_pipe.get()), absl::StrCat("--pidfd_tid_pipe_fd=", tid_write_pipe.get())}, {}, &child, &exec_errno)); ASSERT_EQ(exec_errno, 0); ASSERT_THAT(child, SyscallSucceeds()); tid_write_pipe.reset(); read_pipe.reset(); pid_t subthread_tid; ASSERT_THAT(read(tid_read_pipe.get(), &subthread_tid, sizeof(subthread_tid)), SyscallSucceedsWithValue(sizeof(subthread_tid))); auto subthread_pidfd = ASSERT_NO_ERRNO_AND_VALUE(PidfdOpen(subthread_tid, PIDFD_THREAD)); // Poll should timeout on the subthread pidfd as long as the subthread is // alive. struct pollfd pfd = { .fd = subthread_pidfd.get(), .events = POLLIN, }; EXPECT_THAT(poll(&pfd, 1, 1000), SyscallSucceedsWithValue(0)); // Let subthread exit. ASSERT_THAT(write(write_pipe.get(), "a", 1), SyscallSucceedsWithValue(1)); write_pipe.reset(); // Poll on subthread pidfd should succeed even while leader is alive. EXPECT_THAT(poll(&pfd, 1, 5000), SyscallSucceedsWithValue(1)); EXPECT_TRUE(pfd.revents & POLLIN); } // PidfdThreadExecRevival verifies that an execve() "revives" a thread group // and prevents a PIDFD_THREAD for the leader PID from notifying, even if the // original leader has exited. TEST(PidfdTest, PidfdThreadExecRevival) { if (!IsRunningOnGvisor()) { // Test only on kernels bearing this patch: // https://github.com/torvalds/linux/commit/0fb482728ba1 KernelVersion version = ASSERT_NO_ERRNO_AND_VALUE(GetKernelVersion()); if (version.major < 6 || (version.major == 6 && version.minor < 15)) { GTEST_SKIP() << "PIDFD_THREAD behavior requires kernel 6.15+"; } } int pipe_fds[2]; ASSERT_THAT(pipe(pipe_fds), SyscallSucceeds()); FileDescriptor read_pipe(pipe_fds[0]); FileDescriptor write_pipe(pipe_fds[1]); int tid_pipe_fds[2]; ASSERT_THAT(pipe(tid_pipe_fds), SyscallSucceeds()); FileDescriptor tid_read_pipe(tid_pipe_fds[0]); FileDescriptor tid_write_pipe(tid_pipe_fds[1]); pid_t child; int exec_errno; auto cleanup = ASSERT_NO_ERRNO_AND_VALUE( ForkAndExec("/proc/self/exe", {"/proc/self/exe", "--pidfd_helper=RunExecRevivalHelper", absl::StrCat("--pidfd_pipe_fd=", read_pipe.get()), absl::StrCat("--pidfd_tid_pipe_fd=", tid_write_pipe.get())}, {}, &child, &exec_errno)); ASSERT_EQ(exec_errno, 0); ASSERT_THAT(child, SyscallSucceeds()); tid_write_pipe.reset(); read_pipe.reset(); pid_t subthread_tid; ASSERT_THAT(read(tid_read_pipe.get(), &subthread_tid, sizeof(subthread_tid)), SyscallSucceedsWithValue(sizeof(subthread_tid))); auto leader_pidfd = ASSERT_NO_ERRNO_AND_VALUE(PidfdOpen(child, PIDFD_THREAD)); auto subthread_pidfd = ASSERT_NO_ERRNO_AND_VALUE(PidfdOpen(subthread_tid, PIDFD_THREAD)); // Signal subthread to exec. ASSERT_THAT(write(write_pipe.get(), "a", 1), SyscallSucceedsWithValue(1)); write_pipe.reset(); // The subthread should notify because it is killed by the above exec. struct pollfd sub_pfd = { .fd = subthread_pidfd.get(), .events = POLLIN, }; EXPECT_THAT(poll(&sub_pfd, 1, 5000), SyscallSucceedsWithValue(1)); EXPECT_TRUE(sub_pfd.revents & POLLIN); // The leader pidfd should NOT notify because it is "revived" by the exec. struct pollfd leader_pfd = { .fd = leader_pidfd.get(), .events = POLLIN, }; EXPECT_THAT(poll(&leader_pfd, 1, 1000), SyscallSucceedsWithValue(0)); // Now kill the revived process and verify the leader pidfd finally notifies. EXPECT_THAT(kill(child, SIGKILL), SyscallSucceeds()); EXPECT_THAT(poll(&leader_pfd, 1, 5000), SyscallSucceedsWithValue(1)); EXPECT_TRUE(leader_pfd.revents & POLLIN); } TEST(PidfdTest, PidfdThreadTracedSubthreadNotification) { if (!IsRunningOnGvisor()) { // Test only on kernels bearing this patch: // https://github.com/torvalds/linux/commit/0fb482728ba1 KernelVersion version = ASSERT_NO_ERRNO_AND_VALUE(GetKernelVersion()); if (version.major < 6 || (version.major == 6 && version.minor < 15)) { GTEST_SKIP() << "PIDFD_THREAD behavior requires kernel 6.15+"; } } int pipe_fds[2]; ASSERT_THAT(pipe(pipe_fds), SyscallSucceeds()); FileDescriptor read_pipe(pipe_fds[0]); FileDescriptor write_pipe(pipe_fds[1]); int tid_pipe_fds[2]; ASSERT_THAT(pipe(tid_pipe_fds), SyscallSucceeds()); FileDescriptor tid_read_pipe(tid_pipe_fds[0]); FileDescriptor tid_write_pipe(tid_pipe_fds[1]); pid_t child; int exec_errno; auto cleanup = ASSERT_NO_ERRNO_AND_VALUE( ForkAndExec("/proc/self/exe", {"/proc/self/exe", "--pidfd_helper=RunSubthreadTracedHelper", absl::StrCat("--pidfd_pipe_fd=", read_pipe.get()), absl::StrCat("--pidfd_tid_pipe_fd=", tid_write_pipe.get())}, {}, &child, &exec_errno)); ASSERT_EQ(exec_errno, 0); ASSERT_THAT(child, SyscallSucceeds()); tid_write_pipe.reset(); read_pipe.reset(); // Wait for the TID of the subthread and open a pidfd. pid_t subthread_tid; ASSERT_THAT(read(tid_read_pipe.get(), &subthread_tid, sizeof(subthread_tid)), SyscallSucceedsWithValue(sizeof(subthread_tid))); auto subthread_pidfd = ASSERT_NO_ERRNO_AND_VALUE(PidfdOpen(subthread_tid, PIDFD_THREAD)); // Start tracing the subthread. ASSERT_THAT(ptrace(PTRACE_ATTACH, subthread_tid, nullptr, nullptr), SyscallSucceeds()); int status; ASSERT_THAT(waitpid(subthread_tid, &status, 0), SyscallSucceedsWithValue(subthread_tid)); ASSERT_TRUE(WIFSTOPPED(status) && WSTOPSIG(status) == SIGSTOP); ASSERT_THAT(ptrace(PTRACE_CONT, subthread_tid, nullptr, nullptr), SyscallSucceeds()); // Tell the thread to exit. ASSERT_THAT(write(write_pipe.get(), "a", 1), SyscallSucceedsWithValue(1)); // The pidfd should notify even though the tracer has not yet acked the exit. struct pollfd pfd = { .fd = subthread_pidfd.get(), .events = POLLIN, }; EXPECT_THAT(poll(&pfd, 1, 1000), SyscallSucceedsWithValue(1)); EXPECT_TRUE(pfd.revents & POLLIN); // Tracer's ack of the exit. ASSERT_THAT(waitpid(subthread_tid, &status, 0), SyscallSucceedsWithValue(subthread_tid)); ASSERT_TRUE(WIFEXITED(status)); } // PidfdThreadTracedLeaderNotification verifies that a PIDFD_THREAD for a // traced thread group leader is delayed until the entire thread group exits, // but once triggered, the notification occurs before the tracer reaps the // leader. TEST(PidfdTest, PidfdThreadTracedLeaderNotification) { if (!IsRunningOnGvisor()) { // Test behavior on kernels with PIDFD_THREAD. KernelVersion version = ASSERT_NO_ERRNO_AND_VALUE(GetKernelVersion()); if (version.major < 6 || (version.major == 6 && version.minor < 15)) { GTEST_SKIP() << "PIDFD_THREAD behavior requires kernel 6.15+"; } } int pipe_fds[2]; ASSERT_THAT(pipe(pipe_fds), SyscallSucceeds()); FileDescriptor read_pipe(pipe_fds[0]); FileDescriptor write_pipe(pipe_fds[1]); pid_t child; int exec_errno; auto cleanup = ASSERT_NO_ERRNO_AND_VALUE( ForkAndExec("/proc/self/exe", {"/proc/self/exe", "--pidfd_helper=RunLeaderDelayedHelper", absl::StrCat("--pidfd_pipe_fd=", read_pipe.get())}, {}, &child, &exec_errno)); ASSERT_EQ(exec_errno, 0); ASSERT_THAT(child, SyscallSucceeds()); read_pipe.reset(); auto leader_pidfd = ASSERT_NO_ERRNO_AND_VALUE(PidfdOpen(child, PIDFD_THREAD)); // Start tracing the leader. ASSERT_THAT(ptrace(PTRACE_ATTACH, child, nullptr, nullptr), SyscallSucceeds()); int status; ASSERT_THAT(waitpid(child, &status, 0), SyscallSucceedsWithValue(child)); ASSERT_TRUE(WIFSTOPPED(status) && WSTOPSIG(status) == SIGSTOP); ASSERT_THAT(ptrace(PTRACE_CONT, child, nullptr, nullptr), SyscallSucceeds()); // The leader should exit quickly as it calls syscall(SYS_exit). // Poll should timeout as long as the subthread is alive, confirming the // delay is preserved even for traced leaders. struct pollfd pfd = { .fd = leader_pidfd.get(), .events = POLLIN, }; EXPECT_THAT(poll(&pfd, 1, 1000), SyscallSucceedsWithValue(0)); // Now let the subthread exit. ASSERT_THAT(write(write_pipe.get(), "a", 1), SyscallSucceedsWithValue(1)); write_pipe.reset(); // Now the leader's pidfd should notify, even though the tracer has not yet // acked the exit. EXPECT_THAT(poll(&pfd, 1, 5000), SyscallSucceedsWithValue(1)); EXPECT_TRUE(pfd.revents & POLLIN); // Tracer's ack of the exit. ASSERT_THAT(waitpid(child, &status, 0), SyscallSucceedsWithValue(child)); ASSERT_TRUE(WIFEXITED(status)); } TEST(PidfdTest, FasyncIsUnsupported) { pid_t child; auto pidfd = ASSERT_NO_ERRNO_AND_VALUE(Clone3Pidfd(child, []() { pause(); _exit(0); })); ScopedChildReaper cleanup(child); int flags = fcntl(pidfd.get(), F_GETFL); ASSERT_THAT(flags, SyscallSucceeds()); ASSERT_THAT(fcntl(pidfd.get(), F_SETFL, flags | FASYNC), SyscallSucceeds()); int who = getpid(); int want_errno = EINVAL; if (!IsRunningOnGvisor()) { KernelVersion version = ASSERT_NO_ERRNO_AND_VALUE(GetKernelVersion()); if (version.major < 6 || (version.major == 6 && version.minor < 9)) { want_errno = ENOTTY; } } EXPECT_THAT(ioctl(pidfd.get(), FIOSETOWN, &who), SyscallFailsWithErrno(want_errno)); } // This reproduces the race condition where pidfd_send_signal can target // a cloned task whose struct task is initialized but has not // yet completed `updateInfoLocked()` (initializing `logPrefix`), triggering // a nil pointer dereference on the target task's Debugf/Infof logging paths. TEST(PidfdTest, SendSignalToStartingTaskRace) { DisableSave ds; // Violent test that hopes to trigger a crash. std::atomic shared_pidfd{-1}; std::atomic stop_signal_thread{false}; // These threads will try to race with clone(CLONE_PIDFD) // to get at the pidfd FD before the corresponding task is // fully initialized. std::unique_ptr signallers[4]; for (int i = 0; i < 4; ++i) { signallers[i] = std::make_unique([&shared_pidfd, &stop_signal_thread]() { while (!stop_signal_thread.load(std::memory_order_relaxed)) { int fd = shared_pidfd.load(std::memory_order_relaxed); if (fd != -1) { syscall(SYS_pidfd_send_signal, fd, SIGUSR1, nullptr, 0); } } }); } for (int iter = 0; iter < 20; ++iter) { shared_pidfd.store(-1, std::memory_order_relaxed); clone_args ca = {}; ca.flags = CLONE_PIDFD; ca.exit_signal = SIGCHLD; ca.pidfd = reinterpret_cast(&shared_pidfd); pid_t child = syscall(SYS_clone3, &ca, sizeof(ca)); if (child == 0) { // Child immediately exits. _exit(0); } ASSERT_GT(child, 0); int status; EXPECT_THAT(RetryEINTR(waitpid)(child, &status, 0), SyscallSucceedsWithValue(child)); int fd = shared_pidfd.load(std::memory_order_relaxed); if (fd != -1) { close(fd); } } stop_signal_thread.store(true, std::memory_order_relaxed); } TEST(PidfdTest, PidfdGetfdNonDumpableExitingRace) { // Skip when running natively because the host kernel may be vulnerable to // CVE-2026-46333, which would cause the test to fail. We only want to verify // the fix inside the gVisor sandbox. SKIP_IF(!IsRunningOnGvisor()); AutoCapability cap(CAP_SYS_PTRACE, false); for (int iter = 0; iter < 100; ++iter) { int pfd[2]; ASSERT_THAT(pipe(pfd), SyscallSucceeds()); FileDescriptor read_pipe(pfd[0]); FileDescriptor write_pipe(pfd[1]); pid_t child = -1; auto pidfd = ASSERT_NO_ERRNO_AND_VALUE( Clone3Pidfd(child, [&write_pipe, &read_pipe]() { read_pipe.reset(); TEST_PCHECK(prctl(PR_SET_DUMPABLE, 0, 0, 0, 0) == 0); char c = 'r'; TEST_PCHECK(write(write_pipe.get(), &c, 1) == 1); write_pipe.reset(); _exit(0); })); ScopedChildReaper reaper(child); write_pipe.reset(); char c; ASSERT_THAT(read(read_pipe.get(), &c, 1), SyscallSucceedsWithValue(1)); read_pipe.reset(); bool succeeded = false; for (;;) { auto res = PidfdGetfd(pidfd.get(), 0, 0); if (res.ok()) { succeeded = true; break; } else { if (res.error().errno_value() == ESRCH) { break; } } } EXPECT_FALSE(succeeded) << "Unexpectedly succeeded calling pidfd_getfd on " "non-dumpable exiting process at iteration " << iter; if (succeeded) { break; } } } } // namespace } // namespace testing } // namespace gvisor int main(int argc, char** argv) { gvisor::testing::TestInit(&argc, &argv); std::string helper = absl::GetFlag(FLAGS_pidfd_helper); if (!helper.empty()) { if (helper == "RunLeaderDelayedHelper") { gvisor::testing::RunLeaderDelayedHelper(); } else if (helper == "RunSubthreadNotificationHelper") { gvisor::testing::RunSubthreadNotificationHelper(); } else if (helper == "RunExecRevivalHelper") { gvisor::testing::RunExecRevivalHelper(); } else if (helper == "RunSubthreadTracedHelper") { gvisor::testing::RunSubthreadTracedHelper(); } else if (helper == "RunPauseHelper") { gvisor::testing::RunPauseHelper(); } return 0; } return gvisor::testing::RunAllTests(); }