Skip to content

Commit 56f235d

Browse files
committed
selftests/pidfd: add seventh PIDFD_INFO_EXIT selftest
Add a selftest for PIDFD_INFO_EXIT behavior. Link: https://lore.kernel.org/r/20250305-work-pidfs-kill_on_last_close-v3-16-c8c3d8361705@kernel.org Signed-off-by: Christian Brauner <brauner@kernel.org>
1 parent 1c4f2db commit 56f235d

5 files changed

Lines changed: 147 additions & 0 deletions

File tree

tools/testing/selftests/pidfd/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,4 @@ pidfd_setns_test
99
pidfd_file_handle_test
1010
pidfd_bind_mount
1111
pidfd_info_test
12+
pidfd_exec_helper

tools/testing/selftests/pidfd/Makefile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,7 @@ TEST_GEN_PROGS := pidfd_test pidfd_fdinfo_test pidfd_open_test \
55
pidfd_poll_test pidfd_wait pidfd_getfd_test pidfd_setns_test \
66
pidfd_file_handle_test pidfd_bind_mount pidfd_info_test
77

8+
TEST_GEN_PROGS_EXTENDED := pidfd_exec_helper
9+
810
include ../lib.mk
911

tools/testing/selftests/pidfd/pidfd.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,4 +254,11 @@ static inline ssize_t write_nointr(int fd, const void *buf, size_t count)
254254
return ret;
255255
}
256256

257+
static inline int sys_execveat(int dirfd, const char *pathname,
258+
char *const argv[], char *const envp[],
259+
int flags)
260+
{
261+
return syscall(__NR_execveat, dirfd, pathname, argv, envp, flags);
262+
}
263+
257264
#endif /* __PIDFD_H */
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
#define _GNU_SOURCE
2+
#include <stdio.h>
3+
#include <stdlib.h>
4+
#include <unistd.h>
5+
6+
int main(int argc, char *argv[])
7+
{
8+
if (pause())
9+
_exit(EXIT_FAILURE);
10+
11+
_exit(EXIT_SUCCESS);
12+
}

tools/testing/selftests/pidfd/pidfd_info_test.c

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -369,4 +369,129 @@ TEST_F(pidfd_info, thread_group)
369369
EXPECT_EQ(close(pidfd_thread), 0);
370370
}
371371

372+
static void *pidfd_info_thread_exec(void *arg)
373+
{
374+
pid_t pid_thread = gettid();
375+
int ipc_socket = *(int *)arg;
376+
377+
/* Inform the grand-parent what the tid of this thread is. */
378+
if (write_nointr(ipc_socket, &pid_thread, sizeof(pid_thread)) != sizeof(pid_thread))
379+
return NULL;
380+
381+
if (read_nointr(ipc_socket, &pid_thread, sizeof(pid_thread)) != sizeof(pid_thread))
382+
return NULL;
383+
384+
close(ipc_socket);
385+
386+
sys_execveat(AT_FDCWD, "pidfd_exec_helper", NULL, NULL, 0);
387+
return NULL;
388+
}
389+
390+
TEST_F(pidfd_info, thread_group_exec)
391+
{
392+
pid_t pid_leader, pid_thread;
393+
pthread_t thread;
394+
int nevents, pidfd_leader, pidfd_leader_thread, pidfd_thread, ret;
395+
int ipc_sockets[2];
396+
struct pollfd fds = {};
397+
struct pidfd_info info = {
398+
.mask = PIDFD_INFO_CGROUPID | PIDFD_INFO_EXIT,
399+
};
400+
401+
ret = socketpair(AF_LOCAL, SOCK_STREAM | SOCK_CLOEXEC, 0, ipc_sockets);
402+
EXPECT_EQ(ret, 0);
403+
404+
pid_leader = create_child(&pidfd_leader, 0);
405+
EXPECT_GE(pid_leader, 0);
406+
407+
if (pid_leader == 0) {
408+
close(ipc_sockets[0]);
409+
410+
/* The thread will outlive the thread-group leader. */
411+
if (pthread_create(&thread, NULL, pidfd_info_thread_exec, &ipc_sockets[1]))
412+
syscall(__NR_exit, EXIT_FAILURE);
413+
414+
/* Make the thread-group leader exit prematurely. */
415+
syscall(__NR_exit, EXIT_SUCCESS);
416+
}
417+
418+
/* Retrieve the tid of the thread. */
419+
EXPECT_EQ(close(ipc_sockets[1]), 0);
420+
ASSERT_EQ(read_nointr(ipc_sockets[0], &pid_thread, sizeof(pid_thread)), sizeof(pid_thread));
421+
422+
/* Opening a thread as a PIDFD_THREAD must succeed. */
423+
pidfd_thread = sys_pidfd_open(pid_thread, PIDFD_THREAD);
424+
ASSERT_GE(pidfd_thread, 0);
425+
426+
/* Open a thread-specific pidfd for the thread-group leader. */
427+
pidfd_leader_thread = sys_pidfd_open(pid_leader, PIDFD_THREAD);
428+
ASSERT_GE(pidfd_leader_thread, 0);
429+
430+
/*
431+
* We can poll and wait for the old thread-group leader to exit
432+
* using a thread-specific pidfd.
433+
*
434+
* This only works until the thread has execed. When the thread
435+
* has execed it will have taken over the old thread-group
436+
* leaders struct pid. Calling poll after the thread execed will
437+
* thus block again because a new thread-group has started (Yes,
438+
* it's fscked.).
439+
*/
440+
fds.events = POLLIN;
441+
fds.fd = pidfd_leader_thread;
442+
nevents = poll(&fds, 1, -1);
443+
ASSERT_EQ(nevents, 1);
444+
/* The thread-group leader has exited. */
445+
ASSERT_TRUE(!!(fds.revents & POLLIN));
446+
/* The thread-group leader hasn't been reaped. */
447+
ASSERT_FALSE(!!(fds.revents & POLLHUP));
448+
449+
/* Now that we've opened a thread-specific pidfd the thread can exec. */
450+
ASSERT_EQ(write_nointr(ipc_sockets[0], &pid_thread, sizeof(pid_thread)), sizeof(pid_thread));
451+
EXPECT_EQ(close(ipc_sockets[0]), 0);
452+
453+
/* Wait until the kernel has SIGKILLed the thread. */
454+
fds.events = POLLHUP;
455+
fds.fd = pidfd_thread;
456+
nevents = poll(&fds, 1, -1);
457+
ASSERT_EQ(nevents, 1);
458+
/* The thread has been reaped. */
459+
ASSERT_TRUE(!!(fds.revents & POLLHUP));
460+
461+
/* Retrieve thread-specific exit info from pidfd. */
462+
ASSERT_EQ(ioctl(pidfd_thread, PIDFD_GET_INFO, &info), 0);
463+
ASSERT_FALSE(!!(info.mask & PIDFD_INFO_CREDS));
464+
ASSERT_TRUE(!!(info.mask & PIDFD_INFO_EXIT));
465+
/*
466+
* While the kernel will have SIGKILLed the whole thread-group
467+
* during exec it will cause the individual threads to exit
468+
* cleanly.
469+
*/
470+
ASSERT_TRUE(WIFEXITED(info.exit_code));
471+
ASSERT_EQ(WEXITSTATUS(info.exit_code), 0);
472+
473+
/*
474+
* The thread-group leader is still alive, the thread has taken
475+
* over its struct pid and thus its pid number.
476+
*/
477+
info.mask = PIDFD_INFO_CGROUPID | PIDFD_INFO_EXIT;
478+
ASSERT_EQ(ioctl(pidfd_leader, PIDFD_GET_INFO, &info), 0);
479+
ASSERT_TRUE(!!(info.mask & PIDFD_INFO_CREDS));
480+
ASSERT_FALSE(!!(info.mask & PIDFD_INFO_EXIT));
481+
ASSERT_EQ(info.pid, pid_leader);
482+
483+
/* Take down the thread-group leader. */
484+
EXPECT_EQ(sys_pidfd_send_signal(pidfd_leader, SIGKILL, NULL, 0), 0);
485+
EXPECT_EQ(sys_waitid(P_PIDFD, pidfd_leader, NULL, WEXITED), 0);
486+
487+
/* Retrieve exit information for the thread-group leader. */
488+
info.mask = PIDFD_INFO_CGROUPID | PIDFD_INFO_EXIT;
489+
ASSERT_EQ(ioctl(pidfd_leader, PIDFD_GET_INFO, &info), 0);
490+
ASSERT_FALSE(!!(info.mask & PIDFD_INFO_CREDS));
491+
ASSERT_TRUE(!!(info.mask & PIDFD_INFO_EXIT));
492+
493+
EXPECT_EQ(close(pidfd_leader), 0);
494+
EXPECT_EQ(close(pidfd_thread), 0);
495+
}
496+
372497
TEST_HARNESS_MAIN

0 commit comments

Comments
 (0)