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