/* * Copyright (c) 2020 Andrew G Morgan * * 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 #include #include #include #include #include /* 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); }