diff options
author | Ákos Uzonyi <uzonyi.akos@gmail.com> | 2020-06-26 20:13:28 +0200 |
---|---|---|
committer | Dmitry V. Levin <ldv@altlinux.org> | 2020-08-25 14:29:30 +0000 |
commit | 7ecee07bbcdc54e97a6ff8e8ea84e440f2872bea (patch) | |
tree | ed62b3548e96a799518f6b815baed014e075f93a /tests | |
parent | 18c2208b05f5b7bcca5b000d601a1e332a63c850 (diff) | |
download | strace-7ecee07bbcdc54e97a6ff8e8ea84e440f2872bea.tar.gz |
Implement testing framework for pidns
* tests/pidns.c: New file.
* tests/pidns.h: New file.
* tests/Makefile.am (libtests_a_SOURCES): Add pidns.c, pidns.h.
* tests/init.sh (test_pidns, test_pidns_run_strace): New functions.
Diffstat (limited to 'tests')
-rw-r--r-- | tests/Makefile.am | 2 | ||||
-rw-r--r-- | tests/init.sh | 30 | ||||
-rw-r--r-- | tests/pidns.c | 237 | ||||
-rw-r--r-- | tests/pidns.h | 56 |
4 files changed, 325 insertions, 0 deletions
diff --git a/tests/Makefile.am b/tests/Makefile.am index 28d95e398..7a583a3a5 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -44,6 +44,8 @@ libtests_a_SOURCES = \ libsocketcall.c \ lock_file.c \ overflowuid.c \ + pidns.c \ + pidns.h \ pipe_maxfd.c \ print_quoted_string.c \ print_time.c \ diff --git a/tests/init.sh b/tests/init.sh index d78e697bc..524170514 100644 --- a/tests/init.sh +++ b/tests/init.sh @@ -387,6 +387,36 @@ test_prog_set() test_pure_prog_set "$@" < "$srcdir/$NAME.in" } +test_pidns_run_strace() +{ + local parent_pid init_pid + + check_prog tail + check_prog cut + check_prog grep + + run_prog > /dev/null + run_strace --pidns-translation -f $@ $args > "$EXP" + + # filter out logs made by the parent or init process of the pidns test + parent_pid="$(tail -n 2 $LOG | head -n 1 | cut -d' ' -f1)" + init_pid="$(tail -n 1 $LOG | cut -d' ' -f1)" + grep -E -v "^($parent_pid|$init_pid) " "$LOG" > "$OUT" + match_diff "$OUT" "$EXP" +} + +test_pidns() +{ + check_prog unshare + unshare -Urpf true || framework_skip_ "unshare -Urpf true failed" + + test_pidns_run_strace "$@" + + # test PID translation when /proc is mounted from an other namespace + STRACE="unshare -Urpf $STRACE" + test_pidns_run_strace "$@" +} + check_prog cat check_prog rm diff --git a/tests/pidns.c b/tests/pidns.c new file mode 100644 index 000000000..4fe5b223b --- /dev/null +++ b/tests/pidns.c @@ -0,0 +1,237 @@ +/* + * Testing framework for PID namespace translation + * + * Copyright (c) 2020 Ákos Uzonyi <uzonyi.akos@gmail.com> + * All rights reserved. + * + * SPDX-License-Identifier: LGPL-2.1-or-later + */ +#include "tests.h" +#include "pidns.h" +#include "nsfs.h" + +#include <errno.h> +#include <stdio.h> +#include <string.h> +#include <sys/types.h> +#include <signal.h> +#include <stdlib.h> +#include <sched.h> +#include <unistd.h> +#include <sys/wait.h> +#include <linux/sched.h> +#include <fcntl.h> +#include <sys/ioctl.h> + +#ifndef CLONE_NEWUSER +# define CLONE_NEWUSER 0x10000000 +#endif + +#ifndef CLONE_NEWPID +# define CLONE_NEWPID 0x20000000 +#endif + +static bool pidns_translation = false; +static bool pidns_unshared = false; + +/* Our PIDs in strace's namespace */ +static pid_t pidns_strace_ids[PT_COUNT]; + +void +pidns_print_leader(void) +{ + if (pidns_translation) + printf("%-5d ", pidns_strace_ids[PT_TID]); +} + +const char * +pidns_pid2str(enum pid_type type) +{ + static const char format[] = " /* %d in strace's PID NS */"; + static char buf[PT_COUNT][sizeof(format) + sizeof(int) * 3]; + + if (type < 0 || type >= PT_COUNT) + return ""; + + if (!pidns_unshared || !pidns_strace_ids[type]) + return ""; + + snprintf(buf[type], sizeof(buf[type]), format, pidns_strace_ids[type]); + return buf[type]; +} + +/** + * This function is like fork, but does a few more things. It sets up the + * child's PGID and SID according to the parameters. Also it fills the + * pidns_strace_ids array in the child's memory with the PIDs of the child in + * parent's PID namespace. In the parent it waits for the child to terminate + * (but leaves the zombie to use it later as a process group). If the child + * terminates with nonzero exit status, the test is failed. + * + * @param pgid The process group the child should be moved to. It's expected + * to be a PID of a zombie process (will be reaped). If + * negative, leave the child in the process group of the parent. + * If 0, move the process to its own process group. + * @param new_sid Wheather child should be moved to a new session. + */ +static pid_t +pidns_fork(pid_t pgid, bool new_sid) +{ + int strace_ids_pipe[2]; + if (pipe(strace_ids_pipe) < 0) + perror_msg_and_fail("pipe"); + + fflush(stdout); + pid_t pid = fork(); + if (pid < 0) + perror_msg_and_fail("fork"); + + if (!pid) { + close(strace_ids_pipe[1]); + + ssize_t len = read(strace_ids_pipe[0], pidns_strace_ids, + sizeof(pidns_strace_ids)); + if (len < 0) + perror_msg_and_fail("read"); + if (len != sizeof(pidns_strace_ids)) + error_msg_and_fail("read returned < sizeof(pidns_strace_ids)"); + + close(strace_ids_pipe[0]); + + if (pidns_strace_ids[PT_SID]) + setsid(); + + return 0; + } + + pidns_strace_ids[PT_TID] = pid; + pidns_strace_ids[PT_TGID] = pid; + pidns_strace_ids[PT_PGID] = 0; + pidns_strace_ids[PT_SID] = 0; + + if (!pgid) + pgid = pid; + + if (pgid > 0) { + if (setpgid(pid, pgid) < 0) + perror_msg_and_fail("setpgid"); + + pidns_strace_ids[PT_PGID] = pgid; + } + + /* Reap group leader to test PGID decoding */ + if (pgid > 0 && pgid != pid) { + int ret = waitpid(pgid, NULL, WNOHANG); + if (ret < 0) + perror_msg_and_fail("wait"); + if (!ret) + error_msg_and_fail("could not reap group leader"); + } + + if (new_sid) { + pidns_strace_ids[PT_SID] = pid; + pidns_strace_ids[PT_PGID] = pid; + } + + ssize_t len = write(strace_ids_pipe[1], pidns_strace_ids, + sizeof(pidns_strace_ids)); + if (len < 0) + perror_msg_and_fail("write"); + if (len != sizeof(pidns_strace_ids)) + error_msg_and_fail("write returned < sizeof(pidns_strace_ids)"); + + close(strace_ids_pipe[0]); + close(strace_ids_pipe[1]); + + /* WNOWAIT: leave the zombie, to be able to use it as a process group */ + siginfo_t siginfo; + if (waitid(P_PID, pid, &siginfo, WEXITED | WNOWAIT) < 0) + perror_msg_and_fail("wait"); + if (siginfo.si_code != CLD_EXITED || siginfo.si_status) + error_msg_and_fail("child terminated with nonzero exit status"); + + return pid; +} + +static void +create_init_process(void) +{ + int child_pipe[2]; + if (pipe(child_pipe) < 0) + perror_msg_and_fail("pipe"); + + pid_t pid = fork(); + if (pid < 0) + perror_msg_and_fail("fork"); + + if (!pid) { + close(child_pipe[1]); + if (read(child_pipe[0], &child_pipe[1], sizeof(int)) != 0) + _exit(1); + _exit(0); + } + + close(child_pipe[0]); +} + +void +check_ns_ioctl(void) +{ + int fd = open("/proc/self/ns/pid", O_RDONLY); + if (fd < 0) { + if (errno == ENOENT) + perror_msg_and_skip("opening /proc/self/ns/pid"); + else + perror_msg_and_fail("opening /proc/self/ns/pid"); + } + + int userns_fd = ioctl(fd, NS_GET_USERNS); + if (userns_fd < 0) { + if (errno == ENOTTY) + error_msg_and_skip("NS_* ioctl commands are not " + "supported by the kernel"); + else + perror_msg_and_fail("ioctl(NS_GET_USERNS)"); + } + + close(userns_fd); + close(fd); +} + +void +pidns_test_init(void) +{ + pidns_translation = true; + + check_ns_ioctl(); + + if (!pidns_fork(-1, false)) + return; + + /* Unshare user namespace too, so we do not need to be root */ + if (unshare(CLONE_NEWUSER | CLONE_NEWPID) < 0) { + if (errno == EPERM) + perror_msg_and_skip("unshare"); + + perror_msg_and_fail("unshare"); + } + + pidns_unshared = true; + + create_init_process(); + + if (!pidns_fork(-1, false)) + return; + + if (!pidns_fork(-1, true)) + return; + + pid_t pgid; + if (!(pgid = pidns_fork(0, false))) + return; + + if (!pidns_fork(pgid, false)) + return; + + exit(0); +} diff --git a/tests/pidns.h b/tests/pidns.h new file mode 100644 index 000000000..76963eb36 --- /dev/null +++ b/tests/pidns.h @@ -0,0 +1,56 @@ +/* + * Test PID namespace translation + * + * Copyright (c) 2020 Ákos Uzonyi <uzonyi.akos@gmail.com> + * All rights reserved. + * + * SPDX-License-Identifier: LGPL-2.1-or-later + */ +#ifndef STRACE_PIDNS_H +#define STRACE_PIDNS_H + +#ifdef PIDNS_TRANSLATION +# define PIDNS_TEST_INIT pidns_test_init() +#else +# define PIDNS_TEST_INIT +#endif + +#include <sys/types.h> + +enum pid_type { + PT_TID, + PT_TGID, + PT_PGID, + PT_SID, + + PT_COUNT, + PT_NONE = -1 +}; + +/* Prints leader (process tid) if pidns_test_init was called */ +void pidns_print_leader(void); + +/* + * Returns a static buffer containing the translation string of our PID. + */ +const char *pidns_pid2str(enum pid_type type); + +/** + * Skips the test if NS_* ioctl commands are not supported by the kernel. + */ +void check_ns_ioctl(void); + +/** + * Init pidns testing. + * + * Should be called at the beginning of the test's main function + * + * This function calls fork a couple of times, and returns in the child + * processes. These child processes are in a new PID namespace with different + * PID configurations (group leader, session leader, ...). If any child + * terminates with nonzero exit status the test is failed. Otherwise the test is + * succesful, and the parent process exits with 0. + */ +void pidns_test_init(void); + +#endif
\ No newline at end of file |