diff options
author | Andrew G. Morgan <morgan@kernel.org> | 2020-10-24 20:57:11 -0700 |
---|---|---|
committer | Andrew G. Morgan <morgan@kernel.org> | 2020-10-24 21:00:59 -0700 |
commit | 481ca7b508ad0245468146343b1d3be94470a024 (patch) | |
tree | 3f039f29c1c6e12648eaaf167cb3a8c469f7b758 | |
parent | a614aa669d6ec0f12849f9a698f242cf89b44c6c (diff) | |
download | libcap-481ca7b508ad0245468146343b1d3be94470a024.tar.gz |
Demonstrate why libpsx is important for multithreaded C code.
I've heard a number of folk ask why one might need libpsx for anything
other than Go program linking, so this demonstrates the class of exploit
that is possible when libcap is linked with -lpthread and not -lpsx.
Signed-off-by: Andrew G. Morgan <morgan@kernel.org>
-rw-r--r-- | tests/.gitignore | 2 | ||||
-rw-r--r-- | tests/Makefile | 22 | ||||
-rw-r--r-- | tests/exploit.c | 154 |
3 files changed, 177 insertions, 1 deletions
diff --git a/tests/.gitignore b/tests/.gitignore index 43118ef..ac7ffb0 100644 --- a/tests/.gitignore +++ b/tests/.gitignore @@ -3,3 +3,5 @@ psx_test libcap_psx_test libcap_launch_test libcap_psx_launch_test +exploit +noexploit diff --git a/tests/Makefile b/tests/Makefile index 4d13441..9ab9766 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -26,7 +26,7 @@ $(DEPS): test: run_libcap_psx_test -sudotest: test run_psx_test run_libcap_launch_test run_libcap_psx_test +sudotest: test run_psx_test run_libcap_launch_test run_libcap_psx_test run_exploit_test install: all @@ -49,6 +49,25 @@ run_libcap_launch_test: libcap_launch_test libcap_psx_launch_test noop libcap_launch_test: libcap_launch_test.c $(DEPS) $(CC) $(CFLAGS) $(IPATH) $< -o $@ $(LINKEXTRA) $(LIBCAPLIB) $(LDFLAGS) +# 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.o: exploit.c + $(CC) $(CFLAGS) $(IPATH) -c $< + +exploit: exploit.o $(DEPS) + $(CC) $(CFLAGS) $(IPATH) $< -o $@ $(LINKEXTRA) $(LIBCAPLIB) -lpthread $(LDFLAGS) + +# Note, for some reason, the order of libraries is important to avoid the exploit working +# for dynamic linking. +noexploit: exploit.o $(DEPS) + $(CC) $(CFLAGS) $(IPATH) $< -o $@ $(LINKEXTRA) $(LIBPSXLIB) $(LIBCAPLIB) $(LDFLAGS) + # 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. @@ -62,3 +81,4 @@ noop: noop.c clean: rm -f psx_test libcap_psx_test libcap_launch_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..28bac88 --- /dev/null +++ b/tests/exploit.c @@ -0,0 +1,154 @@ +/* + * 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. + */ +#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); + } else { + printf("exploit failed\n"); + } +} |