xref: /aosp_15_r20/external/libcap/contrib/capso/capso.c (revision 2810ac1b38eead2603277920c78344c84ddf3aff)
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