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
29 static const cap_value_t raise_setpcap[1] = { CAP_SETPCAP };
30 static const cap_value_t raise_chroot[1] = { CAP_SYS_CHROOT };
31
binary(unsigned long value)32 static 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
main(int argc,char * argv[],char * envp[])46 int 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