Home | History | Annotate | Download | only in progs
      1 /*
      2  * Copyright (c) 2008-11 Andrew G. Morgan <morgan (at) 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 
     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 
     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