1/* 2 setfiles: based on policycoreutils 2.0.19 3 policycoreutils was released under GPL 2. 4 Port to BusyBox (c) 2007 by Yuichi Nakamura <ynakam@hitachisoft.jp> 5*/ 6 7#include "libbb.h" 8#if ENABLE_FEATURE_SETFILES_CHECK_OPTION 9#include <sepol/sepol.h> 10#endif 11 12#define MAX_EXCLUDES 50 13 14struct edir { 15 char *directory; 16 size_t size; 17}; 18 19struct globals { 20 FILE *outfile; 21 char *policyfile; 22 char *rootpath; 23 int rootpathlen; 24 unsigned count; 25 int excludeCtr; 26 int errors; 27 int verbose; /* getopt32 uses it, has to be int */ 28 smallint recurse; /* Recursive descent */ 29 smallint follow_mounts; 30 /* Behavior flags determined based on setfiles vs. restorecon */ 31 smallint expand_realpath; /* Expand paths via realpath */ 32 smallint abort_on_error; /* Abort the file tree walk upon an error */ 33 int add_assoc; /* Track inode associations for conflict detection */ 34 int matchpathcon_flags; /* Flags to matchpathcon */ 35 dev_t dev_id; /* Device id where target file exists */ 36 int nerr; 37 struct edir excludeArray[MAX_EXCLUDES]; 38}; 39 40#define G (*(struct globals*)&bb_common_bufsiz1) 41void BUG_setfiles_globals_too_big(void); 42#define INIT_G() do { \ 43 if (sizeof(G) > COMMON_BUFSIZE) \ 44 BUG_setfiles_globals_too_big(); \ 45 /* memset(&G, 0, sizeof(G)); - already is */ \ 46} while (0) 47#define outfile (G.outfile ) 48#define policyfile (G.policyfile ) 49#define rootpath (G.rootpath ) 50#define rootpathlen (G.rootpathlen ) 51#define count (G.count ) 52#define excludeCtr (G.excludeCtr ) 53#define errors (G.errors ) 54#define verbose (G.verbose ) 55#define recurse (G.recurse ) 56#define follow_mounts (G.follow_mounts ) 57#define expand_realpath (G.expand_realpath ) 58#define abort_on_error (G.abort_on_error ) 59#define add_assoc (G.add_assoc ) 60#define matchpathcon_flags (G.matchpathcon_flags) 61#define dev_id (G.dev_id ) 62#define nerr (G.nerr ) 63#define excludeArray (G.excludeArray ) 64 65/* Must match getopt32 string! */ 66enum { 67 OPT_d = (1 << 0), 68 OPT_e = (1 << 1), 69 OPT_f = (1 << 2), 70 OPT_i = (1 << 3), 71 OPT_l = (1 << 4), 72 OPT_n = (1 << 5), 73 OPT_p = (1 << 6), 74 OPT_q = (1 << 7), 75 OPT_r = (1 << 8), 76 OPT_s = (1 << 9), 77 OPT_v = (1 << 10), 78 OPT_o = (1 << 11), 79 OPT_F = (1 << 12), 80 OPT_W = (1 << 13), 81 OPT_c = (1 << 14), /* c only for setfiles */ 82 OPT_R = (1 << 14), /* R only for restorecon */ 83}; 84#define FLAG_d_debug (option_mask32 & OPT_d) 85#define FLAG_e (option_mask32 & OPT_e) 86#define FLAG_f (option_mask32 & OPT_f) 87#define FLAG_i_ignore_enoent (option_mask32 & OPT_i) 88#define FLAG_l_take_log (option_mask32 & OPT_l) 89#define FLAG_n_dry_run (option_mask32 & OPT_n) 90#define FLAG_p_progress (option_mask32 & OPT_p) 91#define FLAG_q_quiet (option_mask32 & OPT_q) 92#define FLAG_r (option_mask32 & OPT_r) 93#define FLAG_s (option_mask32 & OPT_s) 94#define FLAG_v (option_mask32 & OPT_v) 95#define FLAG_o (option_mask32 & OPT_o) 96#define FLAG_F_force (option_mask32 & OPT_F) 97#define FLAG_W_warn_no_match (option_mask32 & OPT_W) 98#define FLAG_c (option_mask32 & OPT_c) 99#define FLAG_R (option_mask32 & OPT_R) 100 101 102static void qprintf(const char *fmt, ...) 103{ 104 /* quiet, do nothing */ 105} 106 107static void inc_err(void) 108{ 109 nerr++; 110 if (nerr > 9 && !FLAG_d_debug) { 111 bb_error_msg_and_die("exiting after 10 errors"); 112 } 113} 114 115static void add_exclude(const char *const directory) 116{ 117 struct stat sb; 118 size_t len; 119 120 if (directory == NULL || directory[0] != '/') { 121 bb_error_msg_and_die("full path required for exclude: %s", directory); 122 123 } 124 if (lstat(directory, &sb)) { 125 bb_error_msg("directory \"%s\" not found, ignoring", directory); 126 return; 127 } 128 if ((sb.st_mode & S_IFDIR) == 0) { 129 bb_error_msg("\"%s\" is not a directory: mode %o, ignoring", 130 directory, sb.st_mode); 131 return; 132 } 133 if (excludeCtr == MAX_EXCLUDES) { 134 bb_error_msg_and_die("maximum excludes %d exceeded", MAX_EXCLUDES); 135 } 136 137 len = strlen(directory); 138 while (len > 1 && directory[len - 1] == '/') { 139 len--; 140 } 141 excludeArray[excludeCtr].directory = xstrndup(directory, len); 142 excludeArray[excludeCtr++].size = len; 143} 144 145static bool exclude(const char *file) 146{ 147 int i = 0; 148 for (i = 0; i < excludeCtr; i++) { 149 if (strncmp(file, excludeArray[i].directory, 150 excludeArray[i].size) == 0) { 151 if (file[excludeArray[i].size] == '\0' 152 || file[excludeArray[i].size] == '/') { 153 return 1; 154 } 155 } 156 } 157 return 0; 158} 159 160static int match(const char *name, struct stat *sb, char **con) 161{ 162 int ret; 163 char path[PATH_MAX + 1]; 164 char *tmp_path = xstrdup(name); 165 166 if (excludeCtr > 0 && exclude(name)) { 167 goto err; 168 } 169 ret = lstat(name, sb); 170 if (ret) { 171 if (FLAG_i_ignore_enoent && errno == ENOENT) { 172 free(tmp_path); 173 return 0; 174 } 175 bb_error_msg("stat(%s)", name); 176 goto err; 177 } 178 179 if (expand_realpath) { 180 if (S_ISLNK(sb->st_mode)) { 181 char *p = NULL; 182 char *file_sep; 183 184 size_t len = 0; 185 186 if (verbose > 1) 187 bb_error_msg("warning! %s refers to a symbolic link, not following last component", name); 188 189 file_sep = strrchr(tmp_path, '/'); 190 if (file_sep == tmp_path) { 191 file_sep++; 192 path[0] = '\0'; 193 p = path; 194 } else if (file_sep) { 195 *file_sep++ = '\0'; 196 p = realpath(tmp_path, path); 197 } else { 198 file_sep = tmp_path; 199 p = realpath("./", path); 200 } 201 if (p) 202 len = strlen(p); 203 if (!p || len + strlen(file_sep) + 2 > PATH_MAX) { 204 bb_perror_msg("realpath(%s) failed", name); 205 goto err; 206 } 207 p += len; 208 /* ensure trailing slash of directory name */ 209 if (len == 0 || p[-1] != '/') { 210 *p++ = '/'; 211 } 212 strcpy(p, file_sep); 213 name = path; 214 if (excludeCtr > 0 && exclude(name)) 215 goto err; 216 217 } else { 218 char *p; 219 p = realpath(name, path); 220 if (!p) { 221 bb_perror_msg("realpath(%s)", name); 222 goto err; 223 } 224 name = p; 225 if (excludeCtr > 0 && exclude(name)) 226 goto err; 227 } 228 } 229 230 /* name will be what is matched in the policy */ 231 if (NULL != rootpath) { 232 if (0 != strncmp(rootpath, name, rootpathlen)) { 233 bb_error_msg("%s is not located in %s", 234 name, rootpath); 235 goto err; 236 } 237 name += rootpathlen; 238 } 239 240 free(tmp_path); 241 if (rootpath != NULL && name[0] == '\0') 242 /* this is actually the root dir of the alt root */ 243 return matchpathcon_index("/", sb->st_mode, con); 244 return matchpathcon_index(name, sb->st_mode, con); 245 err: 246 free(tmp_path); 247 return -1; 248} 249 250/* Compare two contexts to see if their differences are "significant", 251 * or whether the only difference is in the user. */ 252static bool only_changed_user(const char *a, const char *b) 253{ 254 if (FLAG_F_force) 255 return 0; 256 if (!a || !b) 257 return 0; 258 a = strchr(a, ':'); /* Rest of the context after the user */ 259 b = strchr(b, ':'); 260 if (!a || !b) 261 return 0; 262 return (strcmp(a, b) == 0); 263} 264 265static int restore(const char *file) 266{ 267 char *my_file; 268 struct stat my_sb; 269 int i, j, ret; 270 char *context = NULL; 271 char *newcon = NULL; 272 bool user_only_changed = 0; 273 int retval = 0; 274 275 my_file = bb_simplify_path(file); 276 277 i = match(my_file, &my_sb, &newcon); 278 279 if (i < 0) /* No matching specification. */ 280 goto out; 281 282 if (FLAG_p_progress) { 283 count++; 284 if (count % 0x400 == 0) { /* every 1024 times */ 285 count = (count % (80*0x400)); 286 if (count == 0) 287 fputc('\n', stdout); 288 fputc('*', stdout); 289 fflush(stdout); 290 } 291 } 292 293 /* 294 * Try to add an association between this inode and 295 * this specification. If there is already an association 296 * for this inode and it conflicts with this specification, 297 * then use the last matching specification. 298 */ 299 if (add_assoc) { 300 j = matchpathcon_filespec_add(my_sb.st_ino, i, my_file); 301 if (j < 0) 302 goto err; 303 304 if (j != i) { 305 /* There was already an association and it took precedence. */ 306 goto out; 307 } 308 } 309 310 if (FLAG_d_debug) 311 printf("%s: %s matched by %s\n", applet_name, my_file, newcon); 312 313 /* Get the current context of the file. */ 314 ret = lgetfilecon_raw(my_file, &context); 315 if (ret < 0) { 316 if (errno == ENODATA) { 317 context = NULL; /* paranoia */ 318 } else { 319 bb_perror_msg("lgetfilecon_raw on %s", my_file); 320 goto err; 321 } 322 user_only_changed = 0; 323 } else 324 user_only_changed = only_changed_user(context, newcon); 325 326 /* 327 * Do not relabel the file if the matching specification is 328 * <<none>> or the file is already labeled according to the 329 * specification. 330 */ 331 if ((strcmp(newcon, "<<none>>") == 0) 332 || (context && (strcmp(context, newcon) == 0) && !FLAG_F_force)) { 333 goto out; 334 } 335 336 if (!FLAG_F_force && context && (is_context_customizable(context) > 0)) { 337 if (verbose > 1) { 338 bb_error_msg("skipping %s. %s is customizable_types", 339 my_file, context); 340 } 341 goto out; 342 } 343 344 if (verbose) { 345 /* If we're just doing "-v", trim out any relabels where 346 * the user has changed but the role and type are the 347 * same. For "-vv", emit everything. */ 348 if (verbose > 1 || !user_only_changed) { 349 bb_info_msg("%s: reset %s context %s->%s", 350 applet_name, my_file, context ?: "", newcon); 351 } 352 } 353 354 if (FLAG_l_take_log && !user_only_changed) { 355 if (context) 356 bb_info_msg("relabeling %s from %s to %s", my_file, context, newcon); 357 else 358 bb_info_msg("labeling %s to %s", my_file, newcon); 359 } 360 361 if (outfile && !user_only_changed) 362 fprintf(outfile, "%s\n", my_file); 363 364 /* 365 * Do not relabel the file if -n was used. 366 */ 367 if (FLAG_n_dry_run || user_only_changed) 368 goto out; 369 370 /* 371 * Relabel the file to the specified context. 372 */ 373 ret = lsetfilecon(my_file, newcon); 374 if (ret) { 375 bb_perror_msg("lsetfileconon(%s,%s)", my_file, newcon); 376 goto err; 377 } 378 379 out: 380 freecon(context); 381 freecon(newcon); 382 free(my_file); 383 return retval; 384 err: 385 retval--; /* -1 */ 386 goto out; 387} 388 389/* 390 * Apply the last matching specification to a file. 391 * This function is called by recursive_action on each file during 392 * the directory traversal. 393 */ 394static int apply_spec(const char *file, 395 struct stat *sb, void *userData, int depth) 396{ 397 if (!follow_mounts) { 398 /* setfiles does not process across different mount points */ 399 if (sb->st_dev != dev_id) { 400 return SKIP; 401 } 402 } 403 errors |= restore(file); 404 if (abort_on_error && errors) 405 return FALSE; 406 return TRUE; 407} 408 409 410static int canoncon(const char *path, unsigned lineno, char **contextp) 411{ 412 static const char err_msg[] ALIGN1 = "%s: line %u has invalid context %s"; 413 414 char *tmpcon; 415 char *context = *contextp; 416 int invalid = 0; 417 418#if ENABLE_FEATURE_SETFILES_CHECK_OPTION 419 if (policyfile) { 420 if (sepol_check_context(context) >= 0) 421 return 0; 422 /* Exit immediately if we're in checking mode. */ 423 bb_error_msg_and_die(err_msg, path, lineno, context); 424 } 425#endif 426 427 if (security_canonicalize_context_raw(context, &tmpcon) < 0) { 428 if (errno != ENOENT) { 429 invalid = 1; 430 inc_err(); 431 } 432 } else { 433 free(context); 434 *contextp = tmpcon; 435 } 436 437 if (invalid) { 438 bb_error_msg(err_msg, path, lineno, context); 439 } 440 441 return invalid; 442} 443 444static int process_one(char *name) 445{ 446 struct stat sb; 447 int rc; 448 449 rc = lstat(name, &sb); 450 if (rc < 0) { 451 if (FLAG_i_ignore_enoent && errno == ENOENT) 452 return 0; 453 bb_perror_msg("stat(%s)", name); 454 goto err; 455 } 456 dev_id = sb.st_dev; 457 458 if (S_ISDIR(sb.st_mode) && recurse) { 459 if (recursive_action(name, 460 ACTION_RECURSE, 461 apply_spec, 462 apply_spec, 463 NULL, 0) != TRUE) { 464 bb_error_msg("error while labeling %s", name); 465 goto err; 466 } 467 } else { 468 rc = restore(name); 469 if (rc) 470 goto err; 471 } 472 473 out: 474 if (add_assoc) { 475 if (FLAG_q_quiet) 476 set_matchpathcon_printf(&qprintf); 477 matchpathcon_filespec_eval(); 478 set_matchpathcon_printf(NULL); 479 matchpathcon_filespec_destroy(); 480 } 481 482 return rc; 483 484 err: 485 rc = -1; 486 goto out; 487} 488 489int setfiles_main(int argc, char **argv); 490int setfiles_main(int argc, char **argv) 491{ 492 struct stat sb; 493 int rc, i = 0; 494 const char *input_filename = NULL; 495 char *buf = NULL; 496 size_t buf_len; 497 int flags; 498 llist_t *exclude_dir = NULL; 499 char *out_filename = NULL; 500 501 INIT_G(); 502 503 if (applet_name[0] == 's') { /* "setfiles" */ 504 /* 505 * setfiles: 506 * Recursive descent, 507 * Does not expand paths via realpath, 508 * Aborts on errors during the file tree walk, 509 * Try to track inode associations for conflict detection, 510 * Does not follow mounts, 511 * Validates all file contexts at init time. 512 */ 513 recurse = 1; 514 abort_on_error = 1; 515 add_assoc = 1; 516 /* follow_mounts = 0; - already is */ 517 matchpathcon_flags = MATCHPATHCON_VALIDATE | MATCHPATHCON_NOTRANS; 518 } else { 519 /* 520 * restorecon: 521 * No recursive descent unless -r/-R, 522 * Expands paths via realpath, 523 * Do not abort on errors during the file tree walk, 524 * Do not try to track inode associations for conflict detection, 525 * Follows mounts, 526 * Does lazy validation of contexts upon use. 527 */ 528 expand_realpath = 1; 529 follow_mounts = 1; 530 matchpathcon_flags = MATCHPATHCON_NOTRANS; 531 /* restorecon only */ 532 selinux_or_die(); 533 } 534 535 set_matchpathcon_flags(matchpathcon_flags); 536 537 opt_complementary = "e::vv:v--p:p--v:v--q:q--v"; 538 /* Option order must match OPT_x definitions! */ 539 if (applet_name[0] == 'r') { /* restorecon */ 540 flags = getopt32(argv, "de:f:ilnpqrsvo:FWR", 541 &exclude_dir, &input_filename, &out_filename, &verbose); 542 } else { /* setfiles */ 543 flags = getopt32(argv, "de:f:ilnpqr:svo:FW" 544 USE_FEATURE_SETFILES_CHECK_OPTION("c:"), 545 &exclude_dir, &input_filename, &rootpath, &out_filename, 546 USE_FEATURE_SETFILES_CHECK_OPTION(&policyfile,) 547 &verbose); 548 } 549 550#if ENABLE_FEATURE_SETFILES_CHECK_OPTION 551 if ((applet_name[0] == 's') && (flags & OPT_c)) { 552 FILE *policystream; 553 554 policystream = xfopen(policyfile, "r"); 555 if (sepol_set_policydb_from_file(policystream) < 0) { 556 bb_error_msg_and_die("sepol_set_policydb_from_file on %s", policyfile); 557 } 558 fclose(policystream); 559 560 /* Only process the specified file_contexts file, not 561 any .homedirs or .local files, and do not perform 562 context translations. */ 563 set_matchpathcon_flags(MATCHPATHCON_BASEONLY | 564 MATCHPATHCON_NOTRANS | 565 MATCHPATHCON_VALIDATE); 566 } 567#endif 568 569 while (exclude_dir) 570 add_exclude(llist_pop(&exclude_dir)); 571 572 if (flags & OPT_o) { 573 outfile = stdout; 574 if (NOT_LONE_CHAR(out_filename, '-')) { 575 outfile = xfopen(out_filename, "w"); 576 } 577 } 578 if (applet_name[0] == 'r') { /* restorecon */ 579 if (flags & (OPT_r | OPT_R)) 580 recurse = 1; 581 } else { /* setfiles */ 582 if (flags & OPT_r) 583 rootpathlen = strlen(rootpath); 584 } 585 if (flags & OPT_s) { 586 input_filename = "-"; 587 add_assoc = 0; 588 } 589 590 if (applet_name[0] == 's') { /* setfiles */ 591 /* Use our own invalid context checking function so that 592 we can support either checking against the active policy or 593 checking against a binary policy file. */ 594 set_matchpathcon_canoncon(&canoncon); 595 if (argc == 1) 596 bb_show_usage(); 597 if (stat(argv[optind], &sb) < 0) { 598 bb_perror_msg_and_die("%s", argv[optind]); 599 } 600 if (!S_ISREG(sb.st_mode)) { 601 bb_error_msg_and_die("spec file %s is not a regular file", argv[optind]); 602 } 603 /* Load the file contexts configuration and check it. */ 604 rc = matchpathcon_init(argv[optind]); 605 if (rc < 0) { 606 bb_perror_msg_and_die("%s", argv[optind]); 607 } 608 609 optind++; 610 611 if (nerr) 612 exit(1); 613 } 614 615 if (input_filename) { 616 ssize_t len; 617 FILE *f = stdin; 618 619 if (NOT_LONE_CHAR(input_filename, '-')) 620 f = xfopen(input_filename, "r"); 621 while ((len = getline(&buf, &buf_len, f)) > 0) { 622 buf[len - 1] = '\0'; 623 errors |= process_one(buf); 624 } 625 if (ENABLE_FEATURE_CLEAN_UP) 626 fclose_if_not_stdin(f); 627 } else { 628 if (optind >= argc) 629 bb_show_usage(); 630 for (i = optind; i < argc; i++) { 631 errors |= process_one(argv[i]); 632 } 633 } 634 635 if (FLAG_W_warn_no_match) 636 matchpathcon_checkmatches(argv[0]); 637 638 if (ENABLE_FEATURE_CLEAN_UP && outfile) 639 fclose(outfile); 640 641 return errors; 642} 643