1/*
2 * Copyright (c) 2008-11 Andrew G. Morgan <morgan@kernel.org>
3 *
4 * This is a simple 'bash' wrapper program that can be used to
5 * raise and lower both the bset and pI capabilities before invoking
6 * /bin/bash (hardcoded right now).
7 *
8 * The --print option can be used as a quick test whether various
9 * capability manipulations work as expected (or not).
10 */
11
12#include <stdio.h>
13#include <string.h>
14#include <stdlib.h>
15#include <sys/prctl.h>
16#include <sys/types.h>
17#include <unistd.h>
18#include <pwd.h>
19#include <grp.h>
20#include <errno.h>
21#include <ctype.h>
22#include <sys/capability.h>
23#include <sys/securebits.h>
24#include <sys/wait.h>
25#include <sys/prctl.h>
26
27#define MAX_GROUPS       100   /* max number of supplementary groups for user */
28
29static const cap_value_t raise_setpcap[1] = { CAP_SETPCAP };
30static const cap_value_t raise_chroot[1] = { CAP_SYS_CHROOT };
31
32static char *binary(unsigned long value)
33{
34    static char string[8*sizeof(unsigned long) + 1];
35    unsigned i;
36
37    i = sizeof(string);
38    string[--i] = '\0';
39    do {
40	string[--i] = (value & 1) ? '1' : '0';
41	value >>= 1;
42    } while ((i > 0) && value);
43    return string + i;
44}
45
46int main(int argc, char *argv[], char *envp[])
47{
48    pid_t child;
49    unsigned i;
50
51    child = 0;
52
53    for (i=1; i<argc; ++i) {
54	if (!memcmp("--drop=", argv[i], 4)) {
55	    char *ptr;
56	    cap_t orig, raised_for_setpcap;
57
58	    /*
59	     * We need to do this here because --inh=XXX may have reset
60	     * orig and it isn't until we are within the --drop code that
61	     * we know what the prevailing (orig) pI value is.
62	     */
63	    orig = cap_get_proc();
64	    if (orig == NULL) {
65		perror("Capabilities not available");
66		exit(1);
67	    }
68
69	    raised_for_setpcap = cap_dup(orig);
70	    if (raised_for_setpcap == NULL) {
71		fprintf(stderr, "BSET modification requires CAP_SETPCAP\n");
72		exit(1);
73	    }
74
75	    if (cap_set_flag(raised_for_setpcap, CAP_EFFECTIVE, 1,
76			     raise_setpcap, CAP_SET) != 0) {
77		perror("unable to select CAP_SETPCAP");
78		exit(1);
79	    }
80
81	    if (strcmp("all", argv[i]+7) == 0) {
82		unsigned j = 0;
83		while (CAP_IS_SUPPORTED(j)) {
84		    if (cap_drop_bound(j) != 0) {
85			char *name_ptr;
86
87			name_ptr = cap_to_name(j);
88			fprintf(stderr,
89				"Unable to drop bounding capability [%s]\n",
90				name_ptr);
91			cap_free(name_ptr);
92			exit(1);
93		    }
94		    j++;
95		}
96	    } else {
97		for (ptr = argv[i]+7; (ptr = strtok(ptr, ",")); ptr = NULL) {
98		    /* find name for token */
99		    cap_value_t cap;
100		    int status;
101
102		    if (cap_from_name(ptr, &cap) != 0) {
103			fprintf(stderr,
104				"capability [%s] is unknown to libcap\n",
105				ptr);
106			exit(1);
107		    }
108		    if (cap_set_proc(raised_for_setpcap) != 0) {
109			perror("unable to raise CAP_SETPCAP for BSET changes");
110			exit(1);
111		    }
112		    status = prctl(PR_CAPBSET_DROP, cap);
113		    if (cap_set_proc(orig) != 0) {
114			perror("unable to lower CAP_SETPCAP post BSET change");
115			exit(1);
116		    }
117		    if (status) {
118			fprintf(stderr, "failed to drop [%s=%u]\n", ptr, cap);
119			exit(1);
120		    }
121		}
122	    }
123	    cap_free(raised_for_setpcap);
124	    cap_free(orig);
125	} else if (!memcmp("--inh=", argv[i], 6)) {
126	    cap_t all, raised_for_setpcap;
127	    char *text;
128	    char *ptr;
129
130	    all = cap_get_proc();
131	    if (all == NULL) {
132		perror("Capabilities not available");
133		exit(1);
134	    }
135	    if (cap_clear_flag(all, CAP_INHERITABLE) != 0) {
136		perror("libcap:cap_clear_flag() internal error");
137		exit(1);
138	    }
139
140	    raised_for_setpcap = cap_dup(all);
141	    if ((raised_for_setpcap != NULL)
142		&& (cap_set_flag(raised_for_setpcap, CAP_EFFECTIVE, 1,
143				 raise_setpcap, CAP_SET) != 0)) {
144		cap_free(raised_for_setpcap);
145		raised_for_setpcap = NULL;
146	    }
147
148	    text = cap_to_text(all, NULL);
149	    cap_free(all);
150	    if (text == NULL) {
151		perror("Fatal error concerning process capabilities");
152		exit(1);
153	    }
154	    ptr = malloc(10 + strlen(argv[i]+6) + strlen(text));
155	    if (ptr == NULL) {
156		perror("Out of memory for inh set");
157		exit(1);
158	    }
159	    if (argv[i][6] && strcmp("none", argv[i]+6)) {
160		sprintf(ptr, "%s %s+i", text, argv[i]+6);
161	    } else {
162		strcpy(ptr, text);
163	    }
164
165	    all = cap_from_text(ptr);
166	    if (all == NULL) {
167		perror("Fatal error internalizing capabilities");
168		exit(1);
169	    }
170	    cap_free(text);
171	    free(ptr);
172
173	    if (raised_for_setpcap != NULL) {
174		/*
175		 * This is only for the case that pP does not contain
176		 * the requested change to pI.. Failing here is not
177		 * indicative of the cap_set_proc(all) failing (always).
178		 */
179		(void) cap_set_proc(raised_for_setpcap);
180		cap_free(raised_for_setpcap);
181		raised_for_setpcap = NULL;
182	    }
183
184	    if (cap_set_proc(all) != 0) {
185		perror("Unable to set inheritable capabilities");
186		exit(1);
187	    }
188	    /*
189	     * Since status is based on orig, we don't want to restore
190	     * the previous value of 'all' again here!
191	     */
192
193	    cap_free(all);
194	} else if (!memcmp("--caps=", argv[i], 7)) {
195	    cap_t all, raised_for_setpcap;
196
197	    raised_for_setpcap = cap_get_proc();
198	    if (raised_for_setpcap == NULL) {
199		perror("Capabilities not available");
200		exit(1);
201	    }
202
203	    if ((raised_for_setpcap != NULL)
204		&& (cap_set_flag(raised_for_setpcap, CAP_EFFECTIVE, 1,
205				 raise_setpcap, CAP_SET) != 0)) {
206		cap_free(raised_for_setpcap);
207		raised_for_setpcap = NULL;
208	    }
209
210	    all = cap_from_text(argv[i]+7);
211	    if (all == NULL) {
212		fprintf(stderr, "unable to interpret [%s]\n", argv[i]);
213		exit(1);
214	    }
215
216	    if (raised_for_setpcap != NULL) {
217		/*
218		 * This is only for the case that pP does not contain
219		 * the requested change to pI.. Failing here is not
220		 * indicative of the cap_set_proc(all) failing (always).
221		 */
222		(void) cap_set_proc(raised_for_setpcap);
223		cap_free(raised_for_setpcap);
224		raised_for_setpcap = NULL;
225	    }
226
227	    if (cap_set_proc(all) != 0) {
228		fprintf(stderr, "Unable to set capabilities [%s]\n", argv[i]);
229		exit(1);
230	    }
231	    /*
232	     * Since status is based on orig, we don't want to restore
233	     * the previous value of 'all' again here!
234	     */
235
236	    cap_free(all);
237	} else if (!memcmp("--keep=", argv[i], 7)) {
238	    unsigned value;
239	    int set;
240
241	    value = strtoul(argv[i]+7, NULL, 0);
242	    set = prctl(PR_SET_KEEPCAPS, value);
243	    if (set < 0) {
244		fprintf(stderr, "prctl(PR_SET_KEEPCAPS, %u) failed: %s\n",
245			value, strerror(errno));
246		exit(1);
247	    }
248	} else if (!memcmp("--chroot=", argv[i], 9)) {
249	    int status;
250	    cap_t orig, raised_for_chroot;
251
252	    orig = cap_get_proc();
253	    if (orig == NULL) {
254		perror("Capabilities not available");
255		exit(1);
256	    }
257
258	    raised_for_chroot = cap_dup(orig);
259	    if (raised_for_chroot == NULL) {
260		perror("Unable to duplicate capabilities");
261		exit(1);
262	    }
263
264	    if (cap_set_flag(raised_for_chroot, CAP_EFFECTIVE, 1, raise_chroot,
265			     CAP_SET) != 0) {
266		perror("unable to select CAP_SET_SYS_CHROOT");
267		exit(1);
268	    }
269
270	    if (cap_set_proc(raised_for_chroot) != 0) {
271		perror("unable to raise CAP_SYS_CHROOT");
272		exit(1);
273	    }
274	    cap_free(raised_for_chroot);
275
276	    status = chroot(argv[i]+9);
277	    if (cap_set_proc(orig) != 0) {
278		perror("unable to lower CAP_SYS_CHROOT");
279		exit(1);
280	    }
281	    /*
282	     * Given we are now in a new directory tree, its good practice
283	     * to start off in a sane location
284	     */
285	    status = chdir("/");
286
287	    cap_free(orig);
288
289	    if (status != 0) {
290		fprintf(stderr, "Unable to chroot/chdir to [%s]", argv[i]+9);
291		exit(1);
292	    }
293	} else if (!memcmp("--secbits=", argv[i], 10)) {
294	    unsigned value;
295	    int status;
296
297	    value = strtoul(argv[i]+10, NULL, 0);
298	    status = prctl(PR_SET_SECUREBITS, value);
299	    if (status < 0) {
300		fprintf(stderr, "failed to set securebits to 0%o/0x%x\n",
301			value, value);
302		exit(1);
303	    }
304	} else if (!memcmp("--forkfor=", argv[i], 10)) {
305	    unsigned value;
306
307	    value = strtoul(argv[i]+10, NULL, 0);
308	    if (value == 0) {
309		goto usage;
310	    }
311	    child = fork();
312	    if (child < 0) {
313		perror("unable to fork()");
314	    } else if (!child) {
315		sleep(value);
316		exit(0);
317	    }
318	} else if (!memcmp("--killit=", argv[i], 9)) {
319	    int retval, status;
320	    pid_t result;
321	    unsigned value;
322
323	    value = strtoul(argv[i]+9, NULL, 0);
324	    if (!child) {
325		fprintf(stderr, "no forked process to kill\n");
326		exit(1);
327	    }
328	    retval = kill(child, value);
329	    if (retval != 0) {
330		perror("Unable to kill child process");
331		exit(1);
332	    }
333	    result = waitpid(child, &status, 0);
334	    if (result != child) {
335		fprintf(stderr, "waitpid didn't match child: %u != %u\n",
336			child, result);
337		exit(1);
338	    }
339	    if (WTERMSIG(status) != value) {
340		fprintf(stderr, "child terminated with odd signal (%d != %d)\n"
341			, value, WTERMSIG(status));
342		exit(1);
343	    }
344	} else if (!memcmp("--uid=", argv[i], 6)) {
345	    unsigned value;
346	    int status;
347
348	    value = strtoul(argv[i]+6, NULL, 0);
349	    status = setuid(value);
350	    if (status < 0) {
351		fprintf(stderr, "Failed to set uid=%u: %s\n",
352			value, strerror(errno));
353		exit(1);
354	    }
355	} else if (!memcmp("--gid=", argv[i], 6)) {
356	    unsigned value;
357	    int status;
358
359	    value = strtoul(argv[i]+6, NULL, 0);
360	    status = setgid(value);
361	    if (status < 0) {
362		fprintf(stderr, "Failed to set gid=%u: %s\n",
363			value, strerror(errno));
364		exit(1);
365	    }
366        } else if (!memcmp("--groups=", argv[i], 9)) {
367	  char *ptr, *buf;
368	  long length, max_groups;
369	  gid_t *group_list;
370	  int g_count;
371
372	  length = sysconf(_SC_GETGR_R_SIZE_MAX);
373	  buf = calloc(1, length);
374	  if (NULL == buf) {
375	    fprintf(stderr, "No memory for [%s] operation\n", argv[i]);
376	    exit(1);
377	  }
378
379	  max_groups = sysconf(_SC_NGROUPS_MAX);
380	  group_list = calloc(max_groups, sizeof(gid_t));
381	  if (NULL == group_list) {
382	    fprintf(stderr, "No memory for gid list\n");
383	    exit(1);
384	  }
385
386	  g_count = 0;
387	  for (ptr = argv[i] + 9; (ptr = strtok(ptr, ","));
388	       ptr = NULL, g_count++) {
389	    if (max_groups <= g_count) {
390	      fprintf(stderr, "Too many groups specified (%d)\n", g_count);
391	      exit(1);
392	    }
393	    if (!isdigit(*ptr)) {
394	      struct group *g, grp;
395	      getgrnam_r(ptr, &grp, buf, length, &g);
396	      if (NULL == g) {
397		fprintf(stderr, "Failed to identify gid for group [%s]\n", ptr);
398		exit(1);
399	      }
400	      group_list[g_count] = g->gr_gid;
401	    } else {
402	      group_list[g_count] = strtoul(ptr, NULL, 0);
403	    }
404	  }
405	  free(buf);
406	  if (setgroups(g_count, group_list) != 0) {
407	    fprintf(stderr, "Failed to setgroups.\n");
408	    exit(1);
409	  }
410	  free(group_list);
411	} else if (!memcmp("--user=", argv[i], 7)) {
412	    struct passwd *pwd;
413	    const char *user;
414	    gid_t groups[MAX_GROUPS];
415	    int status, ngroups;
416
417	    user = argv[i] + 7;
418	    pwd = getpwnam(user);
419	    if (pwd == NULL) {
420	      fprintf(stderr, "User [%s] not known\n", user);
421	      exit(1);
422	    }
423	    ngroups = MAX_GROUPS;
424	    status = getgrouplist(user, pwd->pw_gid, groups, &ngroups);
425	    if (status < 1) {
426	      perror("Unable to get group list for user");
427	      exit(1);
428	    }
429	    status = setgroups(ngroups, groups);
430	    if (status != 0) {
431	      perror("Unable to set group list for user");
432	      exit(1);
433	    }
434	    status = setgid(pwd->pw_gid);
435	    if (status < 0) {
436		fprintf(stderr, "Failed to set gid=%u(user=%s): %s\n",
437			pwd->pw_gid, user, strerror(errno));
438		exit(1);
439	    }
440	    status = setuid(pwd->pw_uid);
441	    if (status < 0) {
442		fprintf(stderr, "Failed to set uid=%u(user=%s): %s\n",
443			pwd->pw_uid, user, strerror(errno));
444		exit(1);
445	    }
446	} else if (!memcmp("--decode=", argv[i], 9)) {
447	    unsigned long long value;
448	    unsigned cap;
449	    const char *sep = "";
450
451	    /* Note, if capabilities become longer than 64-bits we'll need
452	       to fixup the following code.. */
453	    value = strtoull(argv[i]+9, NULL, 16);
454	    printf("0x%016llx=", value);
455
456	    for (cap=0; (cap < 64) && (value >> cap); ++cap) {
457		if (value & (1ULL << cap)) {
458		    char *ptr;
459
460		    ptr = cap_to_name(cap);
461		    if (ptr != NULL) {
462			printf("%s%s", sep, ptr);
463			cap_free(ptr);
464		    } else {
465			printf("%s%u", sep, cap);
466		    }
467		    sep = ",";
468		}
469	    }
470	    printf("\n");
471        } else if (!memcmp("--supports=", argv[i], 11)) {
472	    cap_value_t cap;
473
474	    if (cap_from_name(argv[i] + 11, &cap) < 0) {
475		fprintf(stderr, "cap[%s] not recognized by library\n",
476			argv[i] + 11);
477		exit(1);
478	    }
479	    if (!CAP_IS_SUPPORTED(cap)) {
480		fprintf(stderr, "cap[%s=%d] not supported by kernel\n",
481			argv[i] + 11, cap);
482		exit(1);
483	    }
484	} else if (!strcmp("--print", argv[i])) {
485	    unsigned cap;
486	    int set, status, j;
487	    cap_t all;
488	    char *text;
489	    const char *sep;
490	    struct group *g;
491	    gid_t groups[MAX_GROUPS], gid;
492	    uid_t uid;
493	    struct passwd *u;
494
495	    all = cap_get_proc();
496	    text = cap_to_text(all, NULL);
497	    printf("Current: %s\n", text);
498	    cap_free(text);
499	    cap_free(all);
500
501	    printf("Bounding set =");
502 	    sep = "";
503	    for (cap=0; (set = cap_get_bound(cap)) >= 0; cap++) {
504		char *ptr;
505		if (!set) {
506		    continue;
507		}
508
509		ptr = cap_to_name(cap);
510		if (ptr == NULL) {
511		    printf("%s%u", sep, cap);
512		} else {
513		    printf("%s%s", sep, ptr);
514		    cap_free(ptr);
515		}
516		sep = ",";
517	    }
518	    printf("\n");
519	    set = prctl(PR_GET_SECUREBITS);
520	    if (set >= 0) {
521		const char *b;
522		b = binary(set);  /* use verilog convention for binary string */
523		printf("Securebits: 0%o/0x%x/%u'b%s\n", set, set,
524		       (unsigned) strlen(b), b);
525		printf(" secure-noroot: %s (%s)\n",
526		       (set & 1) ? "yes":"no",
527		       (set & 2) ? "locked":"unlocked");
528		printf(" secure-no-suid-fixup: %s (%s)\n",
529		       (set & 4) ? "yes":"no",
530		       (set & 8) ? "locked":"unlocked");
531		printf(" secure-keep-caps: %s (%s)\n",
532		       (set & 16) ? "yes":"no",
533		       (set & 32) ? "locked":"unlocked");
534	    } else {
535		printf("[Securebits ABI not supported]\n");
536		set = prctl(PR_GET_KEEPCAPS);
537		if (set >= 0) {
538		    printf(" prctl-keep-caps: %s (locking not supported)\n",
539			   set ? "yes":"no");
540		} else {
541		    printf("[Keepcaps ABI not supported]\n");
542		}
543	    }
544	    uid = getuid();
545	    u = getpwuid(uid);
546	    printf("uid=%u(%s)\n", getuid(), u ? u->pw_name : "???");
547	    gid = getgid();
548	    g = getgrgid(gid);
549	    printf("gid=%u(%s)\n", gid, g ? g->gr_name : "???");
550	    printf("groups=");
551	    status = getgroups(MAX_GROUPS, groups);
552	    sep = "";
553	    for (j=0; j < status; j++) {
554		g = getgrgid(groups[j]);
555		printf("%s%u(%s)", sep, groups[j], g ? g->gr_name : "???");
556		sep = ",";
557	    }
558	    printf("\n");
559	} else if ((!strcmp("--", argv[i])) || (!strcmp("==", argv[i]))) {
560	    argv[i] = strdup(argv[i][0] == '-' ? "/bin/bash" : argv[0]);
561	    argv[argc] = NULL;
562	    execve(argv[i], argv+i, envp);
563	    fprintf(stderr, "execve /bin/bash failed!\n");
564	    exit(1);
565	} else {
566	usage:
567	    printf("usage: %s [args ...]\n"
568		   "  --help         this message (or try 'man capsh')\n"
569		   "  --print        display capability relevant state\n"
570		   "  --decode=xxx   decode a hex string to a list of caps\n"
571		   "  --supports=xxx exit 1 if capability xxx unsupported\n"
572		   "  --drop=xxx     remove xxx,.. capabilities from bset\n"
573		   "  --caps=xxx     set caps as per cap_from_text()\n"
574		   "  --inh=xxx      set xxx,.. inheritiable set\n"
575		   "  --secbits=<n>  write a new value for securebits\n"
576		   "  --keep=<n>     set keep-capabability bit to <n>\n"
577		   "  --uid=<n>      set uid to <n> (hint: id <username>)\n"
578		   "  --gid=<n>      set gid to <n> (hint: id <username>)\n"
579		   "  --groups=g,... set the supplemental groups\n"
580                   "  --user=<name>  set uid,gid and groups to that of user\n"
581		   "  --chroot=path  chroot(2) to this path\n"
582		   "  --killit=<n>   send signal(n) to child\n"
583		   "  --forkfor=<n>  fork and make child sleep for <n> sec\n"
584		   "  ==             re-exec(capsh) with args as for --\n"
585		   "  --             remaing arguments are for /bin/bash\n"
586		   "                 (without -- [%s] will simply exit(0))\n",
587		   argv[0], argv[0]);
588
589	    exit(strcmp("--help", argv[i]) != 0);
590	}
591    }
592
593    exit(0);
594}
595