From 740378dc7834bc511ac1ecb2157696681d2a1d32 Mon Sep 17 00:00:00 2001 From: "Joel Fernandes (Google)" Date: Tue, 30 Apr 2019 12:21:54 -0400 Subject: pidfd: add polling selftests Other than verifying pidfd based polling, the tests make sure that wait semantics are preserved with the pidfd poll. Notably the 2 cases: 1. If a thread group leader exits while threads still there, then no pidfd poll notifcation should happen. 2. If a non-thread group leader does an execve, then the thread group leader is signaled to exit and is replaced with the execing thread as the new leader, however the parent is not notified in this case. Signed-off-by: Joel Fernandes (Google) Signed-off-by: Christian Brauner --- tools/testing/selftests/pidfd/Makefile | 2 +- tools/testing/selftests/pidfd/pidfd_test.c | 210 +++++++++++++++++++++++++++++ 2 files changed, 211 insertions(+), 1 deletion(-) (limited to 'tools') diff --git a/tools/testing/selftests/pidfd/Makefile b/tools/testing/selftests/pidfd/Makefile index deaf8073bc06..4b31c14f273c 100644 --- a/tools/testing/selftests/pidfd/Makefile +++ b/tools/testing/selftests/pidfd/Makefile @@ -1,4 +1,4 @@ -CFLAGS += -g -I../../../../usr/include/ +CFLAGS += -g -I../../../../usr/include/ -lpthread TEST_GEN_PROGS := pidfd_test diff --git a/tools/testing/selftests/pidfd/pidfd_test.c b/tools/testing/selftests/pidfd/pidfd_test.c index 5bae1792e3d6..9929dc6fa2d6 100644 --- a/tools/testing/selftests/pidfd/pidfd_test.c +++ b/tools/testing/selftests/pidfd/pidfd_test.c @@ -4,18 +4,47 @@ #include #include #include +#include #include #include #include #include #include #include +#include +#include #include #include +#include #include #include "../kselftest.h" +#define str(s) _str(s) +#define _str(s) #s +#define CHILD_THREAD_MIN_WAIT 3 /* seconds */ + +#define MAX_EVENTS 5 +#ifndef __NR_pidfd_send_signal +#define __NR_pidfd_send_signal 424 +#endif + +#ifndef CLONE_PIDFD +#define CLONE_PIDFD 0x00001000 +#endif + +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 inline int sys_pidfd_send_signal(int pidfd, int sig, siginfo_t *info, unsigned int flags) { @@ -368,11 +397,192 @@ static int test_pidfd_send_signal_syscall_support(void) return 0; } +static void *test_pidfd_poll_exec_thread(void *priv) +{ + ksft_print_msg("Child Thread: starting. pid %d tid %d ; 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 %d\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 %d\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); +} + +static void test_pidfd_poll_exec(int use_waitpid) +{ + int pid, pidfd = 0; + int status, ret; + pthread_t t1; + 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 %d ; and sleeping\n", + getpid(), syscall(SYS_gettid)); + sleep(CHILD_THREAD_MIN_WAIT); + ksft_print_msg("Child Thread: DONE. pid %d tid %d\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 %d\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 explicity call exit only + * so that only the group leader exits, leaving the threads alone. + */ + *child_exit_secs = time(NULL); + syscall(SYS_exit, 0); +} + +static void test_pidfd_poll_leader_exit(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 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(4); + 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(); -- cgit v1.2.3-59-g8ed1b From 172bb24a4f480c180bee646f6616f714ac4bcab2 Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Sat, 23 Mar 2019 12:24:21 +0100 Subject: tests: add pidfd_open() tests This adds testing for the new pidfd_open() syscalls. Specifically, we test: - that no invalid flags can be passed to pidfd_open() - that no invalid pid can be passed to pidfd_open() - that a pidfd can be retrieved with pidfd_open() - that the retrieved pidfd references the correct pid Signed-off-by: Christian Brauner Cc: Arnd Bergmann Cc: "Eric W. Biederman" Cc: Kees Cook Cc: Joel Fernandes (Google) Cc: Thomas Gleixner Cc: Jann Horn Cc: David Howells Cc: "Michael Kerrisk (man-pages)" Cc: Andy Lutomirsky Cc: Andrew Morton Cc: Oleg Nesterov Cc: Aleksa Sarai Cc: Linus Torvalds Cc: Al Viro Cc: linux-api@vger.kernel.org --- tools/testing/selftests/pidfd/.gitignore | 1 + tools/testing/selftests/pidfd/Makefile | 3 +- tools/testing/selftests/pidfd/pidfd.h | 57 ++++++++ tools/testing/selftests/pidfd/pidfd_open_test.c | 169 ++++++++++++++++++++++++ tools/testing/selftests/pidfd/pidfd_test.c | 48 +------ 5 files changed, 234 insertions(+), 44 deletions(-) create mode 100644 tools/testing/selftests/pidfd/pidfd.h create mode 100644 tools/testing/selftests/pidfd/pidfd_open_test.c (limited to 'tools') diff --git a/tools/testing/selftests/pidfd/.gitignore b/tools/testing/selftests/pidfd/.gitignore index 822a1e63d045..16d84d117bc0 100644 --- a/tools/testing/selftests/pidfd/.gitignore +++ b/tools/testing/selftests/pidfd/.gitignore @@ -1 +1,2 @@ +pidfd_open_test pidfd_test diff --git a/tools/testing/selftests/pidfd/Makefile b/tools/testing/selftests/pidfd/Makefile index 4b31c14f273c..720b2d884b3c 100644 --- a/tools/testing/selftests/pidfd/Makefile +++ b/tools/testing/selftests/pidfd/Makefile @@ -1,6 +1,7 @@ +# SPDX-License-Identifier: GPL-2.0-only CFLAGS += -g -I../../../../usr/include/ -lpthread -TEST_GEN_PROGS := pidfd_test +TEST_GEN_PROGS := pidfd_test pidfd_open_test include ../lib.mk diff --git a/tools/testing/selftests/pidfd/pidfd.h b/tools/testing/selftests/pidfd/pidfd.h new file mode 100644 index 000000000000..8452e910463f --- /dev/null +++ b/tools/testing/selftests/pidfd/pidfd.h @@ -0,0 +1,57 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#ifndef __PIDFD_H +#define __PIDFD_H + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../kselftest.h" + +/* + * 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 + +int wait_for_pid(pid_t pid) +{ + int status, ret; + +again: + ret = waitpid(pid, &status, 0); + if (ret == -1) { + if (errno == EINTR) + goto again; + + return -1; + } + + if (!WIFEXITED(status)) + return -1; + + return WEXITSTATUS(status); +} + + +#endif /* __PIDFD_H */ diff --git a/tools/testing/selftests/pidfd/pidfd_open_test.c b/tools/testing/selftests/pidfd/pidfd_open_test.c new file mode 100644 index 000000000000..0377133dd6dc --- /dev/null +++ b/tools/testing/selftests/pidfd/pidfd_open_test.c @@ -0,0 +1,169 @@ +// SPDX-License-Identifier: GPL-2.0 + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "pidfd.h" +#include "../kselftest.h" + +static inline int sys_pidfd_open(pid_t pid, unsigned int flags) +{ + return syscall(__NR_pidfd_open, pid, flags); +} + +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) +{ + int pidfd = -1, ret = 1; + pid_t pid; + + ksft_set_plan(3); + + 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); + + ret = 0; + +on_error: + if (pidfd >= 0) + close(pidfd); + + return !ret ? ksft_exit_pass() : ksft_exit_fail(); +} diff --git a/tools/testing/selftests/pidfd/pidfd_test.c b/tools/testing/selftests/pidfd/pidfd_test.c index 9929dc6fa2d6..7eaa8a3de262 100644 --- a/tools/testing/selftests/pidfd/pidfd_test.c +++ b/tools/testing/selftests/pidfd/pidfd_test.c @@ -18,16 +18,18 @@ #include #include +#include "pidfd.h" #include "../kselftest.h" +#ifndef __NR_pidfd_send_signal +#define __NR_pidfd_send_signal -1 +#endif + #define str(s) _str(s) #define _str(s) #s #define CHILD_THREAD_MIN_WAIT 3 /* seconds */ #define MAX_EVENTS 5 -#ifndef __NR_pidfd_send_signal -#define __NR_pidfd_send_signal 424 -#endif #ifndef CLONE_PIDFD #define CLONE_PIDFD 0x00001000 @@ -91,28 +93,6 @@ static int test_pidfd_send_signal_simple_success(void) return 0; } -static int wait_for_pid(pid_t pid) -{ - int status, ret; - -again: - ret = waitpid(pid, &status, 0); - if (ret == -1) { - if (errno == EINTR) - goto again; - - return -1; - } - - if (ret != pid) - goto again; - - if (!WIFEXITED(status)) - return -1; - - return WEXITSTATUS(status); -} - static int test_pidfd_send_signal_exited_fail(void) { int pidfd, ret, saved_errno; @@ -157,13 +137,6 @@ static int test_pidfd_send_signal_exited_fail(void) return 0; } -/* - * 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 - /* * 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 @@ -172,17 +145,6 @@ static int test_pidfd_send_signal_exited_fail(void) */ #define PIDFD_MAX_DEFAULT 0x8000 -/* - * 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 int test_pidfd_send_signal_recycled_pid_fail(void) { int i, ret; -- cgit v1.2.3-59-g8ed1b