You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
155 lines
3.8 KiB
155 lines
3.8 KiB
/*
|
|
* 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");
|
|
}
|
|
}
|