diff options
Diffstat (limited to 'tests')
-rw-r--r-- | tests/.gitignore | 8 | ||||
-rw-r--r-- | tests/Makefile | 121 | ||||
-rw-r--r-- | tests/exploit.c | 159 | ||||
-rw-r--r-- | tests/libcap_launch_test.c | 239 | ||||
-rw-r--r-- | tests/libcap_psx_test.c | 65 | ||||
-rw-r--r-- | tests/noop.c | 5 | ||||
-rw-r--r-- | tests/psx_test.c | 150 | ||||
-rw-r--r-- | tests/uns_test.c | 171 |
8 files changed, 918 insertions, 0 deletions
diff --git a/tests/.gitignore b/tests/.gitignore new file mode 100644 index 0000000..d0b3f15 --- /dev/null +++ b/tests/.gitignore @@ -0,0 +1,8 @@ +noop +psx_test +libcap_psx_test +libcap_launch_test +libcap_psx_launch_test +exploit +noexploit +uns_test diff --git a/tests/Makefile b/tests/Makefile new file mode 100644 index 0000000..ecb7d1b --- /dev/null +++ b/tests/Makefile @@ -0,0 +1,121 @@ +# +# NOTE the built tests are all designed to be run from this +# working directory when built DYNAMIC=yes. That is, they +# link to the shared libraries in ../libcap/ . +# +topdir=$(shell pwd)/.. +include ../Make.Rules +# + +all: + @echo leave test building to test target + +install: + @echo nothing to install from tests + +ifeq ($(DYNAMIC),yes) +LINKEXTRA=-Wl,-rpath,../libcap +DEPS=../libcap/libcap.so +ifeq ($(PTHREADS),yes) +DEPS += ../libcap/libpsx.so +endif +else +# For this build variant override the LDFLAGS to link statically from +# libraries within the build tree. If you never want this, use +# make DYNAMIC=yes ... +LDFLAGS = --static +DEPS=../libcap/libcap.a +ifeq ($(PTHREADS),yes) +DEPS += ../libcap/libpsx.a +endif +endif + +../libcap/libcap.so: + $(MAKE) -C ../libcap libcap.so + +../libcap/libcap.a: + $(MAKE) -C ../libcap libcap.a + +ifeq ($(PTHREADS),yes) +../libcap/libpsx.so: + $(MAKE) -C ../libcap libpsx.so + +../libcap/libpsx.a: + $(MAKE) -C ../libcap libpsx.a +endif + +../progs/tcapsh-static: + $(MAKE) -C ../progs tcapsh-static + +test: +ifeq ($(PTHREADS),yes) + $(MAKE) run_psx_test run_libcap_psx_test +endif + +sudotest: test + $(MAKE) run_uns_test + $(MAKE) run_libcap_launch_test +ifeq ($(PTHREADS),yes) + $(MAKE) run_libcap_psx_launch_test run_exploit_test +endif + +# unprivileged +run_psx_test: psx_test + ./psx_test + +psx_test: psx_test.c $(DEPS) + $(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) $< -o $@ $(LINKEXTRA) $(LIBPSXLIB) + +run_libcap_psx_test: libcap_psx_test + ./libcap_psx_test + +libcap_psx_test: libcap_psx_test.c $(DEPS) + $(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) $< -o $@ $(LINKEXTRA) $(LIBCAPLIB) $(LIBPSXLIB) + +# privileged +uns_test: uns_test.c $(DEPS) + $(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) $< -o $@ $(LINKEXTRA) $(LIBCAPLIB) + +run_uns_test: uns_test + echo exit | $(SUDO) ./uns_test + +run_libcap_launch_test: libcap_launch_test noop ../progs/tcapsh-static + $(SUDO) ./libcap_launch_test + +run_libcap_psx_launch_test: libcap_psx_launch_test ../progs/tcapsh-static + $(SUDO) ./libcap_psx_launch_test + +libcap_launch_test: libcap_launch_test.c $(DEPS) + $(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) $< -o $@ $(LINKEXTRA) $(LIBCAPLIB) + +# This varies only slightly from the above insofar as it currently +# only links in the pthreads fork support. TODO() we need to change +# the source to do something interesting with pthreads. +libcap_psx_launch_test: libcap_launch_test.c $(DEPS) + $(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) -DWITH_PTHREADS $< -o $@ $(LINKEXTRA) $(LIBCAPLIB) $(LIBPSXLIB) + + +# This test demonstrates that libpsx is needed to secure multithreaded +# programs that link against libcap. +run_exploit_test: exploit noexploit + @echo exploit should succeed + $(SUDO) ./exploit ; if [ $$? -ne 0 ]; then exit 0; else exit 1 ; fi + @echo exploit should fail + $(SUDO) ./noexploit ; if [ $$? -eq 0 ]; then exit 0; else exit 1 ; fi + +exploit: exploit.o $(DEPS) + $(CC) $(CFLAGS) $(LDFLAGS) $< -o $@ $(LINKEXTRA) $(LIBCAPLIB) -lpthread + +# Note, for some reason, the order of libraries is important to avoid +# the exploit working for dynamic linking. +noexploit: exploit.o $(DEPS) + $(CC) $(CFLAGS) $(LDFLAGS) $< -o $@ $(LINKEXTRA) $(LIBPSXLIB) $(LIBCAPLIB) + +# This one runs in a chroot with no shared library files. +noop: noop.c + $(CC) $(CFLAGS) $(CPPFLAGS) $< -o $@ --static + +clean: + rm -f psx_test libcap_psx_test libcap_launch_test uns_test *~ + rm -f libcap_launch_test libcap_psx_launch_test core noop + rm -f exploit noexploit exploit.o diff --git a/tests/exploit.c b/tests/exploit.c new file mode 100644 index 0000000..814337c --- /dev/null +++ b/tests/exploit.c @@ -0,0 +1,159 @@ +/* + * Copyright (c) 2020 Andrew G Morgan <morgan@kernel.org> + * + * This program exploit demonstrates why libcap alone in a + * multithreaded C/C++ program is inherently vulnerable to privilege + * escalation. + * + * The code also serves as a demonstration of how linking with libpsx + * can eliminate this vulnerability by maintaining a process wide + * common security state. + * + * The basic idea (which is well known and why POSIX stipulates "posix + * semantics" for security relevant state at the abstraction of a + * process) is that, because of shared memory, if a single thread alone + * is vulnerable to code injection, then it can cause any other thread + * to execute arbitrary code. As such, if all but one thread drops + * privilege, privilege escalation is somewhat trivial. + */ + +/* as per "man sigaction" */ +#define _POSIX_C_SOURCE 200809L + +#include <pthread.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <sys/capability.h> +#include <sys/types.h> + +/* thread coordination */ +pthread_mutex_t mu; +pthread_cond_t cond; +int hits; + +/* evidence of highest privilege attained */ +ssize_t greatest_len; +char *text; + +/* + * interrupt handler - potentially watching for an opportunity to + * perform an exploit when invoked as a privileged thread. + */ +static void handler(int signum, siginfo_t *info, void *ignore) { + ssize_t length; + char *working; + pthread_mutex_lock(&mu); + + cap_t caps = cap_get_proc(); + working = cap_to_text(caps, &length); + if (length > greatest_len) { + /* + * This is where the exploit code might go. + */ + cap_free(text); + text = working; + greatest_len = length; + } + cap_free(caps); + hits++; + + pthread_cond_signal(&cond); + pthread_mutex_unlock(&mu); + +} + +/* + * privileged thread code (imagine it doing whatever needs privilege). + */ +static void *victim(void *args) { + pthread_mutex_lock(&mu); + hits = 1; + printf("started privileged thread\n"); + pthread_cond_signal(&cond); + pthread_mutex_unlock(&mu); + + pthread_mutex_lock(&mu); + while (hits < 2) { + pthread_cond_wait(&cond, &mu); + } + pthread_mutex_unlock(&mu); + + return NULL; +} + +int main(int argc, char **argv) { + pthread_t peer; + cap_t caps = cap_init(); + struct sigaction sig_action; + + printf("program starting\n"); + if (pthread_create(&peer, NULL, victim, NULL)) { + perror("unable to start the victim thread"); + exit(1); + } + + /* + * Wait until the peer thread is fully up. + */ + pthread_mutex_lock(&mu); + while (hits < 1) { + pthread_cond_wait(&cond, &mu); + } + pthread_mutex_unlock(&mu); + + printf("dropping privilege from main process thread\n"); + + if (cap_set_proc(caps)) { + perror("unable to drop capabilities from main process thread"); + exit(1); + } + cap_free(caps); + + /* confirm the low privilege of the process' main thread */ + + caps = cap_get_proc(); + text = cap_to_text(caps, &greatest_len); + cap_free(caps); + + printf("no privilege in main process thread: len:%ld, caps:\"%s\"\n", + greatest_len, text); + if (greatest_len != 1) { + printf("failed to lower privilege as expected\n"); + exit(1); + } + + /* + * So, we have confirmed that this running thread has no + * privilege. From this thread we setup an interrupt handler and + * then trigger it on the privileged peer thread. + */ + + sig_action.sa_sigaction = &handler; + sigemptyset(&sig_action.sa_mask); + sig_action.sa_flags = SA_SIGINFO | SA_RESTART;; + sigaction(SIGRTMIN, &sig_action, NULL); + + pthread_kill(peer, SIGRTMIN); + + /* + * Wait for the thread to exit. + */ + pthread_join(peer, NULL); + + /* + * Let's see how we did with the exploit. + */ + + printf("greatest privilege in main process thread: len:%ld, caps:\"%s\"\n", + greatest_len, text); + + cap_free(text); + if (greatest_len != 1) { + printf("exploit succeeded\n"); + exit(1); + } + + printf("exploit failed\n"); + exit(0); +} diff --git a/tests/libcap_launch_test.c b/tests/libcap_launch_test.c new file mode 100644 index 0000000..b982573 --- /dev/null +++ b/tests/libcap_launch_test.c @@ -0,0 +1,239 @@ +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/capability.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <unistd.h> + +/* + * tests for cap_launch. + */ + +#define MORE_THAN_ENOUGH 20 +#define NO_MORE 1 + +struct test_case_s { + int pass_on; + const char *chroot; + uid_t uid; + gid_t gid; + int ngroups; + const gid_t groups[MORE_THAN_ENOUGH]; + const char *args[MORE_THAN_ENOUGH]; + const char **envp; + const char *iab; + cap_mode_t mode; + int launch_abort; + int result; + int (*callback_fn)(void *detail); +}; + +#ifdef WITH_PTHREADS +#include <pthread.h> +#else /* WITH_PTHREADS */ +#endif /* WITH_PTHREADS */ + +/* + * clean_out drops all process capabilities. + */ +static int clean_out(void *data) { + cap_t empty; + empty = cap_init(); + if (cap_set_proc(empty) != 0) { + _exit(1); + } + cap_free(empty); + return 0; +} + +int main(int argc, char **argv) { + static struct test_case_s vs[] = { + { + .args = { "../progs/tcapsh-static", "--", "-c", "echo hello" }, + .result = 0 + }, + { + .args = { "../progs/tcapsh-static", "--", "-c", "echo hello" }, + .callback_fn = &clean_out, + .result = 0 + }, + { + .callback_fn = &clean_out, + .result = 0 + }, + { + .args = { "../progs/tcapsh-static", "--is-uid=123" }, + .result = 256 + }, + { + .args = { "/", "won't", "work" }, + .launch_abort = 1, + }, + { + .args = { "../progs/tcapsh-static", "--is-uid=123" }, + .uid = 123, + .result = 0, + }, + { + .args = { "../progs/tcapsh-static", "--is-uid=123" }, + .callback_fn = &clean_out, + .uid = 123, + .launch_abort = 1, + }, + { + .args = { "../progs/tcapsh-static", "--is-gid=123" }, + .result = 0, + .gid = 123, + .ngroups = 1, + .groups = { 456 }, + .iab = "", + }, + { + .args = { "../progs/tcapsh-static", "--dropped=cap_chown", + "--has-i=cap_chown" }, + .result = 0, + .iab = "!%cap_chown" + }, + { + .args = { "../progs/tcapsh-static", "--dropped=cap_chown", + "--has-i=cap_chown", "--is-uid=234", + "--has-a=cap_chown", "--has-p=cap_chown" }, + .uid = 234, + .result = 0, + .iab = "!^cap_chown" + }, + { + .args = { "../progs/tcapsh-static", "--inmode=NOPRIV", + "--has-no-new-privs" }, + .result = 0, + .mode = CAP_MODE_NOPRIV + }, + { + .args = { "/noop" }, + .result = 0, + .chroot = ".", + }, + { + .pass_on = NO_MORE + }, + }; + + if (errno != 0) { + perror("unexpected initial value for errno"); + exit(1); + } + + cap_t orig = cap_get_proc(); + if (orig == NULL) { + perror("failed to get process capabilities"); + exit(1); + } + + int success = 1, i; + for (i=0; vs[i].pass_on != NO_MORE; i++) { + cap_launch_t attr = NULL; + const struct test_case_s *v = &vs[i]; + if (cap_launch(attr, NULL) != -1) { + perror("NULL launch didn't fail"); + exit(1); + } + printf("[%d] test should %s\n", i, + v->result || v->launch_abort ? "generate error" : "work"); + if (v->args[0] != NULL) { + attr = cap_new_launcher(v->args[0], v->args, v->envp); + if (attr == NULL) { + perror("failed to obtain launcher"); + exit(1); + } + if (v->callback_fn != NULL) { + cap_launcher_callback(attr, v->callback_fn); + } + } else { + attr = cap_func_launcher(v->callback_fn); + } + if (v->chroot) { + cap_launcher_set_chroot(attr, v->chroot); + } + if (v->uid) { + cap_launcher_setuid(attr, v->uid); + } + if (v->gid) { + cap_launcher_setgroups(attr, v->gid, v->ngroups, v->groups); + } + if (v->iab) { + cap_iab_t iab = cap_iab_from_text(v->iab); + if (iab == NULL) { + fprintf(stderr, "[%d] failed to decode iab [%s]", i, v->iab); + perror(":"); + success = 0; + continue; + } + cap_iab_t old = cap_launcher_set_iab(attr, iab); + if (cap_free(old)) { + fprintf(stderr, "[%d] failed to decode iab [%s]", i, v->iab); + perror(":"); + success = 0; + continue; + } + } + if (v->mode) { + cap_launcher_set_mode(attr, v->mode); + } + + pid_t child = cap_launch(attr, NULL); + + if (child <= 0) { + fprintf(stderr, "[%d] failed to launch: ", i); + perror(""); + if (!v->launch_abort) { + success = 0; + } + continue; + } + if (cap_free(attr)) { + fprintf(stderr, "[%d] failed to free launcher: ", i); + perror(""); + success = 0; + } + int result; + int ret = waitpid(child, &result, 0); + if (ret != child) { + fprintf(stderr, "[%d] failed to wait: ", i); + perror(""); + success = 0; + continue; + } + if (result != v->result) { + fprintf(stderr, "[%d] bad result: got=%d want=%d: ", i, result, + v->result); + perror(""); + success = 0; + continue; + } + } + + cap_t final = cap_get_proc(); + if (final == NULL) { + perror("unable to get final capabilities"); + exit(1); + } + if (cap_compare(orig, final)) { + char *was = cap_to_text(orig, NULL); + char *is = cap_to_text(final, NULL); + printf("cap_launch_test: orig:'%s' != final:'%s'\n", was, is); + cap_free(is); + cap_free(was); + success = 0; + } + cap_free(final); + cap_free(orig); + + if (!success) { + printf("cap_launch_test: FAILED\n"); + exit(1); + } + printf("cap_launch_test: PASSED\n"); + exit(0); +} diff --git a/tests/libcap_psx_test.c b/tests/libcap_psx_test.c new file mode 100644 index 0000000..9ef8cac --- /dev/null +++ b/tests/libcap_psx_test.c @@ -0,0 +1,65 @@ +#ifndef _DEFAULT_SOURCE +#define _DEFAULT_SOURCE +#endif + +#include <errno.h> +#include <pthread.h> +#include <stdio.h> +#include <stdlib.h> +#include <sys/capability.h> +#include <sys/psx_syscall.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <unistd.h> + +static void *thread_fork_exit(void *data) { + usleep(1234); + pid_t pid = fork(); + cap_t start = cap_get_proc(); + if (start == NULL) { + perror("FAILED: unable to start"); + exit(1); + } + if (pid == 0) { + if (cap_set_proc(start)) { + perror("setting empty caps failed"); + exit(1); + } + exit(0); + } + int res; + if (waitpid(pid, &res, 0) != pid || res != 0) { + printf("FAILED: pid=%d wait returned %d and/or error: %d\n", + pid, res, errno); + exit(1); + } + cap_set_proc(start); + cap_free(start); + return NULL; +} + +int main(int argc, char **argv) { + int i; + printf("hello libcap and libpsx "); + fflush(stdout); + cap_t start = cap_get_proc(); + if (start == NULL) { + perror("FAILED: to actually start"); + exit(1); + } + pthread_t ignored[10]; + for (i = 0; i < 10; i++) { + pthread_create(&ignored[i], NULL, thread_fork_exit, NULL); + } + for (i = 0; i < 10; i++) { + printf("."); /* because of fork, this may print double */ + fflush(stdout); /* try to limit the above effect */ + if (cap_set_proc(start)) { + perror("failed to set proc"); + exit(1); + } + usleep(1000); + } + printf(" PASSED\n"); + exit(0); +} diff --git a/tests/noop.c b/tests/noop.c new file mode 100644 index 0000000..ad51c89 --- /dev/null +++ b/tests/noop.c @@ -0,0 +1,5 @@ +#include <stdlib.h> + +int main(int argc, char *argv[]) { + exit(0); +} diff --git a/tests/psx_test.c b/tests/psx_test.c new file mode 100644 index 0000000..7f16175 --- /dev/null +++ b/tests/psx_test.c @@ -0,0 +1,150 @@ +#ifndef _DEFAULT_SOURCE +#define _DEFAULT_SOURCE +#endif + +#include <pthread.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <sys/prctl.h> +#include <sys/psx_syscall.h> +#include <sys/syscall.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <time.h> +#include <unistd.h> + +typedef union tp { + long long unsigned raw; + pthread_t pt; +} thread_ptr; + +static void say_hello_expecting(const char *title, int n, int kept) { + int keeper = prctl(PR_GET_KEEPCAPS); + thread_ptr tp; + tp.pt = pthread_self(); + + printf("hello [%d], %s<%d> %llx (keepcaps=%d vs. want=%d)\n", + getpid(), title, n, tp.raw, keeper, kept); + if (keeper != kept) { + printf("--> FAILURE %s thread=%llx has wrong keepcaps: got=%d want=%d\n", + title, tp.raw, keeper, kept); + exit(1); + } +} + +pthread_mutex_t mu; +pthread_cond_t cond; + +int global_kept = 0; +int step = 0; +int replies = 0; +int launched = 0; +int started = 0; + +static void *say_hello(void *args) { + int count = 0; + + pthread_mutex_lock(&mu); + started++; + int this_step = step+1; + pthread_cond_broadcast(&cond); + pthread_mutex_unlock(&mu); + + pthread_mutex_lock(&mu); + do { + while (this_step > step) { + pthread_cond_wait(&cond, &mu); + } + say_hello_expecting("thread", count, global_kept); + + replies++; + pthread_cond_broadcast(&cond); + pthread_mutex_unlock(&mu); + + this_step++; + pthread_mutex_lock(&mu); + } while (++count != 3); + pthread_mutex_unlock(&mu); + + return NULL; +} + +int main(int argc, char **argv) { + pthread_t tid[3]; + int i; + pid_t child = 0; + char * const stop_argv[3] = { argv[0], strdup("stop"), NULL }; + + if (argc != 1) { + printf("child %d starting\n", getpid()); + usleep(2000); + printf("child %d exiting\n", getpid()); + exit(0); + } + + for (i = 0; i<10; i++) { + printf("iteration [%d]: %d\n", getpid(), i); + + pthread_mutex_lock(&mu); + global_kept = !global_kept; + replies = 0; + step = i; + pthread_mutex_unlock(&mu); + + psx_syscall(SYS_prctl, PR_SET_KEEPCAPS, global_kept); + + pthread_mutex_lock(&mu); + step++; + pthread_cond_broadcast(&cond); + pthread_mutex_unlock(&mu); + + say_hello_expecting("main", i, global_kept); + + pthread_mutex_lock(&mu); + while (replies < launched) { + pthread_cond_wait(&cond, &mu); + } + pthread_mutex_unlock(&mu); + + if (i < 3) { + if (!child) { + child = fork(); + if (!child) { + usleep(2000); + execve(argv[0], stop_argv, NULL); + perror("failed to exec"); + exit(1); + } else { + printf("pid=%d forked -> %d\n", getpid(), child); + } + } + launched++; + pthread_create(&tid[i], NULL, say_hello, NULL); + /* Confirm that the thread is started. */ + pthread_mutex_lock(&mu); + while (started < launched) { + printf("[%d] started=%d vs %d\n", getpid(), started, launched); + pthread_cond_wait(&cond, &mu); + } + printf("[%d] started=%d vs %d\n", getpid(), started, launched); + pthread_cond_broadcast(&cond); + pthread_mutex_unlock(&mu); + } else if (i < 6) { + /* Confirm one thread has finished. */ + pthread_join(tid[i-3], NULL); + launched--; + } + } + + if (child) { + int status; + waitpid(child, &status, 0); + if (status) { + printf("child %d FAILED: %d\n", child, status); + exit(1); + } + } + printf("%s PASSED\n", argv[0]); + exit(0); +} diff --git a/tests/uns_test.c b/tests/uns_test.c new file mode 100644 index 0000000..3fe73af --- /dev/null +++ b/tests/uns_test.c @@ -0,0 +1,171 @@ +/* + * Try unsharing where we remap the root user by rotating uids (0,1,2) + * and the corresponding gids too. + */ + +#define _GNU_SOURCE + +#include <errno.h> +#include <sched.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/capability.h> +#include <sys/mman.h> +#include <sys/prctl.h> +#include <sys/wait.h> +#include <unistd.h> + +#define STACK_RESERVED 10*1024 + +struct my_pipe { + int to[2]; + int from[2]; +}; + +static int child(void *data) { + struct my_pipe *fdsp = data; + static const char * const args[] = {"bash", NULL}; + + close(fdsp->to[1]); + close(fdsp->from[0]); + if (write(fdsp->from[1], "1", 1) != 1) { + fprintf(stderr, "failed to confirm setuid(1)\n"); + exit(1); + } + close(fdsp->from[1]); + + char datum[1]; + if (read(fdsp->to[0], datum, 1) != 1) { + fprintf(stderr, "failed to wait for parent\n"); + exit(1); + } + close(fdsp->to[0]); + if (datum[0] == '!') { + /* parent failed */ + exit(0); + } + + setsid(); + + execv("/bin/bash", (const void *) args); + perror("execv failed"); + exit(1); +} + +int main(int argc, char **argv) +{ + static const char *file_formats[] = { + "/proc/%d/uid_map", + "/proc/%d/gid_map" + }; + static const char id_map[] = "0 1 1\n1 2 1\n2 0 1\n3 3 49999997\n"; + cap_value_t fscap = CAP_SETFCAP; + cap_t orig = cap_get_proc(); + cap_flag_value_t present; + + if (cap_get_flag(orig, CAP_SYS_ADMIN, CAP_EFFECTIVE, &present) != 0) { + perror("failed to read a capability flag"); + exit(1); + } + if (present != CAP_SET) { + fprintf(stderr, + "environment missing cap_sys_admin - exploit not testable\n"); + exit(0); + } + + /* Run with this one lowered */ + cap_set_flag(orig, CAP_EFFECTIVE, 1, &fscap, CAP_CLEAR); + + struct my_pipe fds; + if (pipe(&fds.from[0]) || pipe(&fds.to[0])) { + perror("no pipes"); + exit(1); + } + + char *stack = mmap(NULL, STACK_RESERVED, PROT_READ|PROT_WRITE, + MAP_ANONYMOUS|MAP_PRIVATE|MAP_STACK, -1, 0); + if (stack == MAP_FAILED) { + perror("no map for stack"); + exit(1); + } + + if (cap_setuid(1)) { + perror("failed to cap_setuid(1)"); + exit(1); + } + + if (cap_set_proc(orig)) { + perror("failed to raise caps again"); + exit(1); + } + + pid_t pid = clone(&child, stack+STACK_RESERVED, CLONE_NEWUSER|SIGCHLD, &fds); + if (pid == -1) { + perror("clone failed"); + exit(1); + } + + close(fds.from[1]); + close(fds.to[0]); + + if (cap_setuid(0)) { + perror("failed to cap_setuid(0)"); + exit(1); + } + + if (cap_set_proc(orig)) { + perror("failed to raise caps again"); + exit(1); + } + + char datum[1]; + if (read(fds.from[0], datum, 1) != 1 || datum[0] != '1') { + fprintf(stderr, "failed to read child status\n"); + exit(1); + } + close(fds.from[0]); + + int i; + for (i=0; i<2; i++) { + char *map_file; + if (asprintf(&map_file, file_formats[i], pid) < 0) { + perror("allocate string"); + exit(1); + } + + FILE *f = fopen(map_file, "w"); + free(map_file); + if (f == NULL) { + perror("fopen failed"); + exit(1); + } + int len = fwrite(id_map, 1, strlen(id_map), f); + if (len != strlen(id_map)) { + goto bailok; + } + if (fclose(f)) { + goto bailok; + } + } + + if (write(fds.to[1], ".", 1) != 1) { + perror("failed to write '.'"); + exit(1); + } + close(fds.to[1]); + + fprintf(stderr, "user namespace launched exploit worked - upgrade kernel\n"); + if (wait(NULL) == pid) { + exit(1); + } + perror("launch failed"); + exit(1); + +bailok: + fprintf(stderr, "exploit attempt failed\n"); + if (write(fds.to[1], "!", 1) != 1) { + perror("failed to inform child [ignored]"); + } + exit(0); +} |