1 /*
2 * Worked example for a shared object with a file capability on it
3 * leveraging itself for preprogrammed functionality.
4 *
5 * This example implements a shared library that can bind to
6 * the privileged port. ":80".
7 *
8 * The shared library needs to be installed with
9 * cap_net_bind_service=p. As a shared library, it provides the
10 * function bind80().
11 */
12
13 #define _GNU_SOURCE
14
15 #include <dlfcn.h>
16 #include <netdb.h>
17 #include <stdio.h>
18 #include <stdlib.h>
19 #include <string.h>
20 #include <sys/capability.h>
21 #include <sys/socket.h>
22 #include <sys/types.h>
23 #include <sys/un.h>
24 #include <sys/wait.h>
25 #include <unistd.h>
26
27 #include "capso.h"
28
29 extern char **environ;
30
31 /*
32 * fake_exploit is some dedicated code to simulate a shell escape type
33 * exploit. This is obviously not something serious to include in code
34 * that has actually been audited for security, but we use it to
35 * demonstrate an aspect of file capabilities vs. setuid root for
36 * granting privilege.
37 */
fake_exploit(void)38 static void fake_exploit(void) {
39 #ifdef ALLOW_EXPLOIT
40 const char *exploit = getenv("TRIGGER_EXPLOIT");
41 if (exploit == NULL) {
42 return;
43 }
44
45 switch (*exploit) {
46 case '^':
47 case '%':
48 exploit++;
49 cap_value_t caps = CAP_NET_BIND_SERVICE;
50 cap_t c = cap_get_proc();
51 cap_set_flag(c, CAP_INHERITABLE, 1, &caps, CAP_SET);
52 if (cap_set_proc(c)) {
53 perror("Failed to raise inheritable capability");
54 exit(1);
55 }
56 if (*(exploit-1) == '%') {
57 break;
58 }
59 cap_free(c);
60 if (cap_set_ambient(caps, CAP_SET) != 0) {
61 perror("Unable to raise ambient capability");
62 exit(1);
63 }
64 break;
65 }
66
67 char *ts = strdup(exploit);
68 if (ts == NULL) {
69 perror("Failed to duplicate exploit string");
70 exit(1);
71 }
72
73 int i, j, n = 1;
74 for (i = 0; ts[i]; i++) {
75 switch (ts[i]) {
76 case ' ':
77 case '\t':
78 n++;
79 ts[i] = '\0';
80 }
81 }
82 char **argv = calloc(n, sizeof(char *));
83 for (i = 0, j = 0; j < n; j++) {
84 char *s = ts+i;
85 argv[j] = s;
86 i += 1 + strlen(s);
87 printf("execv argv[%d] = \"%s\"\n", j, s);
88 }
89
90 execv(argv[0], argv);
91 perror("Execv failed");
92 exit(1);
93 #endif /* def ALLOW_EXPLOIT */
94 }
95
96 /*
97 * where_am_i determines the full path for the shared libary that
98 * contains this function. It allocates the path in strdup()d memory
99 * that should be free()d by the caller. If it can't find itself, it
100 * returns NULL.
101 */
where_am_i(void)102 static char *where_am_i(void)
103 {
104 Dl_info info;
105 if (dladdr(where_am_i, &info) == 0) {
106 return NULL;
107 }
108 return strdup(info.dli_fname);
109 }
110
111 /*
112 * try_bind80 attempts to reuseably bind to port 80 with the given
113 * hostname. It returns a bound filedescriptor or -1 on error.
114 */
try_bind80(const char * hostname)115 static int try_bind80(const char *hostname)
116 {
117 struct addrinfo *conf, *detail = NULL;
118 int err, ret = -1, one = 1;
119
120 conf = calloc(1, sizeof(*conf));
121 if (conf == NULL) {
122 return -1;
123 }
124
125 conf->ai_family = PF_UNSPEC;
126 conf->ai_socktype = SOCK_STREAM;
127 conf->ai_protocol = 0;
128 conf->ai_flags = AI_PASSIVE | AI_ADDRCONFIG;
129
130 err = getaddrinfo(hostname, "80", conf, &detail);
131 if (err != 0) {
132 goto done;
133 }
134
135 ret = socket(detail->ai_family, detail->ai_socktype, detail->ai_protocol);
136 if (ret == -1) {
137 goto done;
138 }
139
140 if (setsockopt(ret, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one))) {
141 close(ret);
142 ret = -1;
143 goto done;
144 }
145
146 if (bind(ret, detail->ai_addr, detail->ai_addrlen)) {
147 close(ret);
148 ret = -1;
149 goto done;
150 }
151
152 done:
153 if (detail != NULL) {
154 freeaddrinfo(detail);
155 }
156 free(conf);
157
158 return ret;
159 }
160
161 /*
162 * set_fd3 forces file descriptor 3 to be associated with a unix
163 * socket that can be used to send a file descriptor back to the
164 * parent program.
165 */
set_fd3(void * detail)166 static int set_fd3(void *detail)
167 {
168 int *sp = detail;
169
170 close(sp[0]);
171 if (dup2(sp[1], 3) != 3) {
172 return -1;
173 }
174 close(sp[1]);
175
176 return 0;
177 }
178
179 /*
180 * bind80 returns a socket filedescriptor that is bound to port 80 of
181 * the provided service address.
182 *
183 * Example:
184 *
185 * int fd = bind80("localhost");
186 *
187 * fd < 0 in the case of error.
188 */
bind80(const char * hostname)189 int bind80(const char *hostname)
190 {
191 cap_launch_t helper;
192 pid_t child;
193 char const *args[3];
194 char *path;
195 int fd, ignored;
196 int sp[2];
197 char junk[1];
198 const int rec_buf_len = CMSG_SPACE(sizeof(int));
199 char *rec_buf[CMSG_SPACE(sizeof(int))];
200 struct iovec *iov;
201 struct msghdr *msg;
202
203 fd = try_bind80(hostname);
204 if (fd >= 0) {
205 return fd;
206 }
207
208 #ifdef CAPSO_DEBUG
209 printf("application bind80(%s) attempt failed\n", hostname);
210 sleep(30);
211 #endif
212
213 iov = calloc(1, sizeof(struct iovec));
214 if (iov == NULL) {
215 return -1;
216 }
217 msg = calloc(1, sizeof(struct msghdr));
218 if (msg == NULL) {
219 free(iov);
220 return -1;
221 }
222
223 /*
224 * Initial attempt didn't work, so try launching the shared
225 * library as an executable and getting it to yield a bound
226 * filedescriptor for us via a unix socket pair.
227 */
228 path = where_am_i();
229 if (path == NULL) {
230 perror("Unable to find self");
231 goto drop_alloc;
232 }
233
234 args[0] = "bind80-helper";
235 args[1] = hostname;
236 args[2] = NULL;
237
238 helper = cap_new_launcher(path, args, (void *) environ);
239 if (helper == NULL) {
240 goto drop_path;
241 }
242
243 if (socketpair(AF_UNIX, SOCK_DGRAM, 0, sp)) {
244 goto drop_helper;
245 }
246
247 cap_launcher_callback(helper, set_fd3);
248 child = cap_launch(helper, sp);
249 close(sp[1]);
250
251 if (child <= 0) {
252 goto drop_sp;
253 }
254
255 iov[0].iov_base = junk;
256 iov[0].iov_len = 1;
257
258 msg->msg_name = NULL;
259 msg->msg_namelen = 0;
260 msg->msg_iov = iov;
261 msg->msg_iovlen = 1;
262 msg->msg_control = rec_buf;
263 msg->msg_controllen = rec_buf_len;
264
265 if (recvmsg(sp[0], msg, 0) != -1) {
266 fd = * (int *) CMSG_DATA(CMSG_FIRSTHDR(msg));
267 }
268 waitpid(child, &ignored, 0);
269
270 drop_sp:
271 close(sp[0]);
272
273 drop_helper:
274 cap_free(helper);
275
276 drop_path:
277 free(path);
278
279 drop_alloc:
280 free(msg);
281 free(iov);
282
283 return fd;
284 }
285
286 #include "../../libcap/execable.h"
287 //#define SO_MAIN int main
288
SO_MAIN(int argc,char ** argv)289 SO_MAIN(int argc, char **argv)
290 {
291 const char *cmd = "<capso.so>";
292 const cap_value_t cap_net_bind_service = CAP_NET_BIND_SERVICE;
293 cap_t working;
294 int fd;
295 struct msghdr msg;
296 struct cmsghdr *ctrl;
297 struct iovec payload;
298 char data[CMSG_SPACE(sizeof(fd))];
299 char junk[1];
300
301 #ifdef CAPSO_DEBUG
302 printf("invoking %s standalone\n", argv[0]);
303 sleep(30);
304 #endif
305
306 if (argv != NULL) {
307 cmd = argv[0];
308 }
309
310 if (argc != 2 || argv[1] == NULL || !strcmp(argv[1], "--help")) {
311 fprintf(stderr, "usage: %s <hostname>\n", cmd);
312 exit(1);
313 }
314
315 working = cap_get_proc();
316 if (working == NULL) {
317 perror("Unable to read capabilities");
318 exit(1);
319 }
320
321 if (cap_set_flag(working, CAP_EFFECTIVE, 1,
322 &cap_net_bind_service, CAP_SET) != 0) {
323 perror("Unable to raise CAP_NET_BIND_SERVICE");
324 exit(1);
325 }
326
327 if (cap_set_proc(working) != 0) {
328 perror("Problem with cap_set_proc");
329 fprintf(stderr, "Try: sudo setcap cap_net_bind_service=p %s\n",
330 argv[0]);
331 exit(1);
332 }
333
334 fd = try_bind80(argv[1]);
335
336 memset(data, 0, sizeof(data));
337 memset(&payload, 0, sizeof(payload));
338
339 payload.iov_base = junk;
340 payload.iov_len = 1;
341
342 msg.msg_name = NULL;
343 msg.msg_namelen = 0;
344 msg.msg_iov = &payload;
345 msg.msg_iovlen = 1;
346 msg.msg_control = data;
347 msg.msg_controllen = sizeof(data);
348
349 ctrl = CMSG_FIRSTHDR(&msg);
350 ctrl->cmsg_level = SOL_SOCKET;
351 ctrl->cmsg_type = SCM_RIGHTS;
352 ctrl->cmsg_len = CMSG_LEN(sizeof(fd));
353
354 *((int *) CMSG_DATA(ctrl)) = fd;
355
356 if (sendmsg(3, &msg, 0) < 0) {
357 perror("Failed to write fd");
358 }
359
360 fake_exploit();
361
362 #ifdef CAPSO_DEBUG
363 printf("exiting standalone %s\n", argv[0]);
364 sleep(30);
365 #endif
366
367 exit(0);
368 }
369