Certain process-level linux features like subreapers, and PDEATHSIG, depend on the guest's process hierarchy being emulated correctly on the host. This change adds a test that makes sure PDEATHSIG works for a guest process created with `clone`.
Signed-off-by: Josh Kunz <j...@google.com> --- tests/tcg/multiarch/Makefile.target | 3 + tests/tcg/multiarch/linux-test.c | 160 ++++++++++++++++++++++++++-- 2 files changed, 153 insertions(+), 10 deletions(-) diff --git a/tests/tcg/multiarch/Makefile.target b/tests/tcg/multiarch/Makefile.target index cb49cc9ccb..d937b4c59b 100644 --- a/tests/tcg/multiarch/Makefile.target +++ b/tests/tcg/multiarch/Makefile.target @@ -60,3 +60,6 @@ endif # Update TESTS TESTS += $(MULTIARCH_TESTS) + +# linux-test.c depends on -pthread. +LDFLAGS += -pthread diff --git a/tests/tcg/multiarch/linux-test.c b/tests/tcg/multiarch/linux-test.c index a7723556c2..1824a5a0c2 100644 --- a/tests/tcg/multiarch/linux-test.c +++ b/tests/tcg/multiarch/linux-test.c @@ -20,16 +20,19 @@ #include <stdarg.h> #include <stdlib.h> #include <stdio.h> +#include <stdbool.h> #include <unistd.h> #include <fcntl.h> #include <inttypes.h> #include <string.h> #include <sys/types.h> +#include <syscall.h> #include <sys/stat.h> #include <sys/wait.h> #include <errno.h> #include <utime.h> #include <time.h> +#include <sys/prctl.h> #include <sys/time.h> #include <sys/resource.h> #include <sys/uio.h> @@ -41,6 +44,7 @@ #include <setjmp.h> #include <sys/shm.h> #include <assert.h> +#include <pthread.h> #define STACK_SIZE 16384 @@ -368,14 +372,12 @@ static void test_pipe(void) chk_error(close(fds[1])); } -static int thread1_res; -static int thread2_res; - static int thread1_func(void *arg) { + int *res = (int *) arg; int i; for(i=0;i<5;i++) { - thread1_res++; + (*res)++; usleep(10 * 1000); } return 0; @@ -383,9 +385,10 @@ static int thread1_func(void *arg) static int thread2_func(void *arg) { + int *res = (int *) arg; int i; for(i=0;i<6;i++) { - thread2_res++; + (*res)++; usleep(10 * 1000); } return 0; @@ -405,25 +408,27 @@ static void test_clone(void) uint8_t *stack1, *stack2; pid_t pid1, pid2; + int t1 = 0, t2 = 0; + stack1 = malloc(STACK_SIZE); pid1 = chk_error(clone(thread1_func, stack1 + STACK_SIZE, CLONE_VM | SIGCHLD, - "hello1")); + &t1)); stack2 = malloc(STACK_SIZE); pid2 = chk_error(clone(thread2_func, stack2 + STACK_SIZE, CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND | CLONE_SYSVSEM | SIGCHLD, - "hello2")); + &t2)); wait_for_child(pid1); free(stack1); wait_for_child(pid2); free(stack2); - if (thread1_res != 5 || - thread2_res != 6) + if (t1 != 5 || t2 != 6) { error("clone"); + } } /***********************************/ @@ -562,6 +567,7 @@ static void test_clone_signal_count(void) * SIGCHLD. */ chk_error(waitpid(pid, &status, __WCLONE)); + free(child_stack); chk_error(sigaction(SIGRTMIN, &prev, NULL)); @@ -571,6 +577,139 @@ static void test_clone_signal_count(void) } } +struct test_clone_pdeathsig_info { + uint8_t *child_stack; + pthread_mutex_t notify_test_mutex; + pthread_cond_t notify_test_cond; + pthread_mutex_t notify_parent_mutex; + pthread_cond_t notify_parent_cond; + bool signal_received; +}; + +static int test_clone_pdeathsig_child(void *arg) +{ + struct test_clone_pdeathsig_info *info = + (struct test_clone_pdeathsig_info *) arg; + sigset_t wait_on, block_all; + siginfo_t sinfo; + struct timespec timeout; + int ret; + + /* Block all signals, so SIGUSR1 will be pending when we wait on it. */ + sigfillset(&block_all); + chk_error(sigprocmask(SIG_BLOCK, &block_all, NULL)); + + chk_error(prctl(PR_SET_PDEATHSIG, SIGUSR1)); + + pthread_mutex_lock(&info->notify_parent_mutex); + pthread_cond_broadcast(&info->notify_parent_cond); + pthread_mutex_unlock(&info->notify_parent_mutex); + + sigemptyset(&wait_on); + sigaddset(&wait_on, SIGUSR1); + timeout.tv_sec = 0; + timeout.tv_nsec = 300 * 1000 * 1000; /* 300ms */ + + ret = sigtimedwait(&wait_on, &sinfo, &timeout); + + if (ret < 0 && errno != EAGAIN) { + error("%m (ret=%d, errno=%d/%s)", ret, errno, strerror(errno)); + } + if (ret == SIGUSR1) { + info->signal_received = true; + } + pthread_mutex_lock(&info->notify_test_mutex); + pthread_cond_broadcast(&info->notify_test_cond); + pthread_mutex_unlock(&info->notify_test_mutex); + _exit(0); +} + +static int test_clone_pdeathsig_parent(void *arg) +{ + struct test_clone_pdeathsig_info *info = + (struct test_clone_pdeathsig_info *) arg; + + pthread_mutex_lock(&info->notify_parent_mutex); + + chk_error(clone( + test_clone_pdeathsig_child, + info->child_stack + STACK_SIZE, + CLONE_VM, + info + )); + + /* No need to reap the child, it will get reaped by init. */ + + /* Wait for the child to signal that they have set up PDEATHSIG. */ + pthread_cond_wait(&info->notify_parent_cond, &info->notify_parent_mutex); + pthread_mutex_unlock(&info->notify_parent_mutex); /* avoid UB on destroy */ + + _exit(0); +} + +/* + * This checks that cloned children have the correct parent/child + * relationship using PDEATHSIG. PDEATHSIG is based on kernel task hierarchy, + * rather than "process" hierarchy, so it should be pretty sensitive to + * breakages. PDEATHSIG is also a widely used feature, so it's important + * it's correct. + * + * This test works by spawning a child process (parent) which then spawns it's + * own child (the child). The child registers a PDEATHSIG handler, and then + * notifies the parent which exits. The child then waits for the PDEATHSIG + * signal it regsitered. The child reports whether or not the signal is + * received within a small time window, and then notifies the test runner + * (this function) that the test is finished. + */ +static void test_clone_pdeathsig(void) +{ + uint8_t *parent_stack; + struct test_clone_pdeathsig_info info; + pid_t pid; + int status; + + memset(&info, 0, sizeof(info)); + + /* + * Setup condition variables, so we can be notified once the final child + * observes the PDEATHSIG signal from it's parent exiting. When the parent + * exits, the child will be orphaned, so we can't use `wait*` to wait for + * it to finish. + */ + chk_error(pthread_mutex_init(&info.notify_test_mutex, NULL)); + chk_error(pthread_cond_init(&info.notify_test_cond, NULL)); + chk_error(pthread_mutex_init(&info.notify_parent_mutex, NULL)); + chk_error(pthread_cond_init(&info.notify_parent_cond, NULL)); + + parent_stack = malloc(STACK_SIZE); + info.child_stack = malloc(STACK_SIZE); + + pthread_mutex_lock(&info.notify_test_mutex); + + pid = chk_error(clone( + test_clone_pdeathsig_parent, + parent_stack + STACK_SIZE, + CLONE_VM, + &info + )); + + pthread_cond_wait(&info.notify_test_cond, &info.notify_test_mutex); + pthread_mutex_unlock(&info.notify_test_mutex); + chk_error(waitpid(pid, &status, __WCLONE)); /* reap the parent */ + + free(parent_stack); + free(info.child_stack); + + pthread_cond_destroy(&info.notify_parent_cond); + pthread_mutex_destroy(&info.notify_parent_mutex); + pthread_cond_destroy(&info.notify_test_cond); + pthread_mutex_destroy(&info.notify_test_mutex); + + if (!info.signal_received) { + error("child did not receive PDEATHSIG on parent death"); + } +} + int main(int argc, char **argv) { test_file(); @@ -580,8 +719,9 @@ int main(int argc, char **argv) test_socket(); test_clone(); test_clone_signal_count(); - + test_clone_pdeathsig(); test_signal(); test_shm(); + return 0; } -- 2.27.0.290.gba653c62da-goog