| /* |
| * Copyright (c) 2020 Andrew G Morgan <[email protected]> |
| * |
| * 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"); |
| } |
| } |