blob: 28bac883255e756c878f360170e78586b9c610c4 [file] [log] [blame]
/*
* 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");
}
}