blob: 4c1a470b9228cf3b1af88d455d5beddd55592fc7 [file] [log] [blame]
/* -*- Mode: C; tab-width: 8; c-basic-offset: 2; indent-tabs-mode: nil; -*- */
#include "nsutils.h"
#include "util.h"
#define NUM_THREADS 10
static int trigger_last_thread_pipe[2];
static int trigger_pid_ns_init_exit_pipe[2];
static int toplevel_exit_pipe[2];
static void* do_thread(void* p) {
int index = (intptr_t)p;
/* Reduce thread priority so rr doesn't schedule these threads unless we want it to */
setpriority(PRIO_PROCESS, 0, 5);
if (index == NUM_THREADS - 1) {
char ch;
read(trigger_last_thread_pipe[0], &ch, 1);
/* Kick off the init process exit ...
after our exit_group has started. */
write(trigger_pid_ns_init_exit_pipe[1], "x", 1);
exit(1);
}
pause();
return NULL;
}
/**
* 1) make the last thread of 'child' do an 'exit_group', initiating kill of all threads
* in particular 'child's main thread, which advances to PTRACE_EVENT_EXIT
* 2) then have the pid_ns_init process exit, triggering kernel's zap_pid_ns_processes
* for 'child' and 'inner_child'
* 3) this kicks 'child' out of PTRACE_EVENT_EXIT (without rr having seen it)
* 4) 'child' then enters its own zap_pid_ns_processes and waits on the exit of
* 'inner_child'
* 5) rr sees that 'child' has been reaped, but it's not in STOPPED state nor
* is it in EXITED state.
*/
static int do_pid_ns_init(void) {
pid_t child;
char ch;
pipe(trigger_last_thread_pipe);
unshare(CLONE_NEWPID);
child = fork();
if (!child) {
pid_t inner_child;
for (int i = 0; i < NUM_THREADS; ++i) {
pthread_t thread;
pthread_create(&thread, NULL, do_thread, (void*)(intptr_t)i);
}
test_assert(0 == unshare(CLONE_NEWPID));
inner_child = fork();
if (!inner_child) {
pause();
return 0;
}
write(trigger_last_thread_pipe[1], "y", 1);
pause();
return 0;
}
read(trigger_pid_ns_init_exit_pipe[0], &ch, 1);
write(toplevel_exit_pipe[1], "q", 1);
return 77;
}
static void do_detect_glibc_bug(void) {
pid_t child;
int status;
unshare(CLONE_NEWPID);
child = fork();
if (!child) {
exit(0);
}
wait(&status);
exit(WIFSIGNALED(status));
}
static int detect_glibc_bug_inner(void) {
int status;
pid_t pid = fork();
test_assert(pid >= 0);
if (!pid) {
do_detect_glibc_bug();
}
wait(&status);
return WEXITSTATUS(status) != 0;
}
/* Detect https://sourceware.org/legacy-ml/libc-alpha/2017-05/msg00378.html */
static int detect_glibc_bug(void) {
int status;
pid_t pid = fork();
if (!pid) {
close(STDERR_FILENO);
if (-1 == try_setup_ns(CLONE_NEWPID)) {
exit(0);
}
exit(detect_glibc_bug_inner());
}
wait(&status);
return WEXITSTATUS(status);
}
int main(void) {
pid_t pid;
char ch;
if (detect_glibc_bug()) {
atomic_puts("EXIT-SUCCESS");
return 0;
}
if (-1 == try_setup_ns(CLONE_NEWPID)) {
/* We may not have permission to set up namespaces, so bail. */
atomic_puts("Insufficient permissions, skipping test");
atomic_puts("EXIT-SUCCESS");
return 0;
}
atomic_puts("EXIT-SUCCESS");
pipe(trigger_pid_ns_init_exit_pipe);
pipe(toplevel_exit_pipe);
pid = fork();
test_assert(pid >= 0);
if (!pid) {
return do_pid_ns_init();
}
read(toplevel_exit_pipe[0], &ch, 1);
return 0;
}