1*2810ac1bSKiyoung Kim /*
2*2810ac1bSKiyoung Kim * Copyright (c) 2020 Andrew G Morgan <[email protected]>
3*2810ac1bSKiyoung Kim *
4*2810ac1bSKiyoung Kim * This program exploit demonstrates why libcap alone in a
5*2810ac1bSKiyoung Kim * multithreaded C/C++ program is inherently vulnerable to privilege
6*2810ac1bSKiyoung Kim * escalation.
7*2810ac1bSKiyoung Kim *
8*2810ac1bSKiyoung Kim * The code also serves as a demonstration of how linking with libpsx
9*2810ac1bSKiyoung Kim * can eliminate this vulnerability by maintaining a process wide
10*2810ac1bSKiyoung Kim * common security state.
11*2810ac1bSKiyoung Kim *
12*2810ac1bSKiyoung Kim * The basic idea (which is well known and why POSIX stipulates "posix
13*2810ac1bSKiyoung Kim * semantics" for security relevant state at the abstraction of a
14*2810ac1bSKiyoung Kim * process) is that, because of shared memory, if a single thread alone
15*2810ac1bSKiyoung Kim * is vulnerable to code injection, then it can cause any other thread
16*2810ac1bSKiyoung Kim * to execute arbitrary code. As such, if all but one thread drops
17*2810ac1bSKiyoung Kim * privilege, privilege escalation is somewhat trivial.
18*2810ac1bSKiyoung Kim */
19*2810ac1bSKiyoung Kim
20*2810ac1bSKiyoung Kim /* as per "man sigaction" */
21*2810ac1bSKiyoung Kim #define _POSIX_C_SOURCE 200809L
22*2810ac1bSKiyoung Kim
23*2810ac1bSKiyoung Kim #include <pthread.h>
24*2810ac1bSKiyoung Kim #include <signal.h>
25*2810ac1bSKiyoung Kim #include <stdio.h>
26*2810ac1bSKiyoung Kim #include <stdlib.h>
27*2810ac1bSKiyoung Kim #include <sys/capability.h>
28*2810ac1bSKiyoung Kim #include <sys/types.h>
29*2810ac1bSKiyoung Kim
30*2810ac1bSKiyoung Kim /* thread coordination */
31*2810ac1bSKiyoung Kim pthread_mutex_t mu;
32*2810ac1bSKiyoung Kim pthread_cond_t cond;
33*2810ac1bSKiyoung Kim int hits;
34*2810ac1bSKiyoung Kim
35*2810ac1bSKiyoung Kim /* evidence of highest privilege attained */
36*2810ac1bSKiyoung Kim ssize_t greatest_len;
37*2810ac1bSKiyoung Kim char *text;
38*2810ac1bSKiyoung Kim
39*2810ac1bSKiyoung Kim /*
40*2810ac1bSKiyoung Kim * interrupt handler - potentially watching for an opportunity to
41*2810ac1bSKiyoung Kim * perform an exploit when invoked as a privileged thread.
42*2810ac1bSKiyoung Kim */
handler(int signum,siginfo_t * info,void * ignore)43*2810ac1bSKiyoung Kim static void handler(int signum, siginfo_t *info, void *ignore) {
44*2810ac1bSKiyoung Kim ssize_t length;
45*2810ac1bSKiyoung Kim char *working;
46*2810ac1bSKiyoung Kim pthread_mutex_lock(&mu);
47*2810ac1bSKiyoung Kim
48*2810ac1bSKiyoung Kim cap_t caps = cap_get_proc();
49*2810ac1bSKiyoung Kim working = cap_to_text(caps, &length);
50*2810ac1bSKiyoung Kim if (length > greatest_len) {
51*2810ac1bSKiyoung Kim /*
52*2810ac1bSKiyoung Kim * This is where the exploit code might go.
53*2810ac1bSKiyoung Kim */
54*2810ac1bSKiyoung Kim cap_free(text);
55*2810ac1bSKiyoung Kim text = working;
56*2810ac1bSKiyoung Kim greatest_len = length;
57*2810ac1bSKiyoung Kim }
58*2810ac1bSKiyoung Kim cap_free(caps);
59*2810ac1bSKiyoung Kim hits++;
60*2810ac1bSKiyoung Kim
61*2810ac1bSKiyoung Kim pthread_cond_signal(&cond);
62*2810ac1bSKiyoung Kim pthread_mutex_unlock(&mu);
63*2810ac1bSKiyoung Kim
64*2810ac1bSKiyoung Kim }
65*2810ac1bSKiyoung Kim
66*2810ac1bSKiyoung Kim /*
67*2810ac1bSKiyoung Kim * privileged thread code (imagine it doing whatever needs privilege).
68*2810ac1bSKiyoung Kim */
victim(void * args)69*2810ac1bSKiyoung Kim static void *victim(void *args) {
70*2810ac1bSKiyoung Kim pthread_mutex_lock(&mu);
71*2810ac1bSKiyoung Kim hits = 1;
72*2810ac1bSKiyoung Kim printf("started privileged thread\n");
73*2810ac1bSKiyoung Kim pthread_cond_signal(&cond);
74*2810ac1bSKiyoung Kim pthread_mutex_unlock(&mu);
75*2810ac1bSKiyoung Kim
76*2810ac1bSKiyoung Kim pthread_mutex_lock(&mu);
77*2810ac1bSKiyoung Kim while (hits < 2) {
78*2810ac1bSKiyoung Kim pthread_cond_wait(&cond, &mu);
79*2810ac1bSKiyoung Kim }
80*2810ac1bSKiyoung Kim pthread_mutex_unlock(&mu);
81*2810ac1bSKiyoung Kim
82*2810ac1bSKiyoung Kim return NULL;
83*2810ac1bSKiyoung Kim }
84*2810ac1bSKiyoung Kim
main(int argc,char ** argv)85*2810ac1bSKiyoung Kim int main(int argc, char **argv) {
86*2810ac1bSKiyoung Kim pthread_t peer;
87*2810ac1bSKiyoung Kim cap_t caps = cap_init();
88*2810ac1bSKiyoung Kim struct sigaction sig_action;
89*2810ac1bSKiyoung Kim
90*2810ac1bSKiyoung Kim printf("program starting\n");
91*2810ac1bSKiyoung Kim if (pthread_create(&peer, NULL, victim, NULL)) {
92*2810ac1bSKiyoung Kim perror("unable to start the victim thread");
93*2810ac1bSKiyoung Kim exit(1);
94*2810ac1bSKiyoung Kim }
95*2810ac1bSKiyoung Kim
96*2810ac1bSKiyoung Kim /*
97*2810ac1bSKiyoung Kim * Wait until the peer thread is fully up.
98*2810ac1bSKiyoung Kim */
99*2810ac1bSKiyoung Kim pthread_mutex_lock(&mu);
100*2810ac1bSKiyoung Kim while (hits < 1) {
101*2810ac1bSKiyoung Kim pthread_cond_wait(&cond, &mu);
102*2810ac1bSKiyoung Kim }
103*2810ac1bSKiyoung Kim pthread_mutex_unlock(&mu);
104*2810ac1bSKiyoung Kim
105*2810ac1bSKiyoung Kim printf("dropping privilege from main process thread\n");
106*2810ac1bSKiyoung Kim
107*2810ac1bSKiyoung Kim if (cap_set_proc(caps)) {
108*2810ac1bSKiyoung Kim perror("unable to drop capabilities from main process thread");
109*2810ac1bSKiyoung Kim exit(1);
110*2810ac1bSKiyoung Kim }
111*2810ac1bSKiyoung Kim cap_free(caps);
112*2810ac1bSKiyoung Kim
113*2810ac1bSKiyoung Kim /* confirm the low privilege of the process' main thread */
114*2810ac1bSKiyoung Kim
115*2810ac1bSKiyoung Kim caps = cap_get_proc();
116*2810ac1bSKiyoung Kim text = cap_to_text(caps, &greatest_len);
117*2810ac1bSKiyoung Kim cap_free(caps);
118*2810ac1bSKiyoung Kim
119*2810ac1bSKiyoung Kim printf("no privilege in main process thread: len:%ld, caps:\"%s\"\n",
120*2810ac1bSKiyoung Kim greatest_len, text);
121*2810ac1bSKiyoung Kim if (greatest_len != 1) {
122*2810ac1bSKiyoung Kim printf("failed to lower privilege as expected\n");
123*2810ac1bSKiyoung Kim exit(1);
124*2810ac1bSKiyoung Kim }
125*2810ac1bSKiyoung Kim
126*2810ac1bSKiyoung Kim /*
127*2810ac1bSKiyoung Kim * So, we have confirmed that this running thread has no
128*2810ac1bSKiyoung Kim * privilege. From this thread we setup an interrupt handler and
129*2810ac1bSKiyoung Kim * then trigger it on the privileged peer thread.
130*2810ac1bSKiyoung Kim */
131*2810ac1bSKiyoung Kim
132*2810ac1bSKiyoung Kim sig_action.sa_sigaction = &handler;
133*2810ac1bSKiyoung Kim sigemptyset(&sig_action.sa_mask);
134*2810ac1bSKiyoung Kim sig_action.sa_flags = SA_SIGINFO | SA_RESTART;;
135*2810ac1bSKiyoung Kim sigaction(SIGRTMIN, &sig_action, NULL);
136*2810ac1bSKiyoung Kim
137*2810ac1bSKiyoung Kim pthread_kill(peer, SIGRTMIN);
138*2810ac1bSKiyoung Kim
139*2810ac1bSKiyoung Kim /*
140*2810ac1bSKiyoung Kim * Wait for the thread to exit.
141*2810ac1bSKiyoung Kim */
142*2810ac1bSKiyoung Kim pthread_join(peer, NULL);
143*2810ac1bSKiyoung Kim
144*2810ac1bSKiyoung Kim /*
145*2810ac1bSKiyoung Kim * Let's see how we did with the exploit.
146*2810ac1bSKiyoung Kim */
147*2810ac1bSKiyoung Kim
148*2810ac1bSKiyoung Kim printf("greatest privilege in main process thread: len:%ld, caps:\"%s\"\n",
149*2810ac1bSKiyoung Kim greatest_len, text);
150*2810ac1bSKiyoung Kim
151*2810ac1bSKiyoung Kim cap_free(text);
152*2810ac1bSKiyoung Kim if (greatest_len != 1) {
153*2810ac1bSKiyoung Kim printf("exploit succeeded\n");
154*2810ac1bSKiyoung Kim exit(1);
155*2810ac1bSKiyoung Kim }
156*2810ac1bSKiyoung Kim
157*2810ac1bSKiyoung Kim printf("exploit failed\n");
158*2810ac1bSKiyoung Kim exit(0);
159*2810ac1bSKiyoung Kim }
160