1 #include "restore.h" 2 #include <glob.h> 3 #include <selinux/context.h> 4 5 #define SKIP -2 6 #define ERR -1 7 #define MAX_EXCLUDES 1000 8 9 /* 10 * The hash table of associations, hashed by inode number. 11 * Chaining is used for collisions, with elements ordered 12 * by inode number in each bucket. Each hash bucket has a dummy 13 * header. 14 */ 15 #define HASH_BITS 16 16 #define HASH_BUCKETS (1 << HASH_BITS) 17 #define HASH_MASK (HASH_BUCKETS-1) 18 19 /* 20 * An association between an inode and a context. 21 */ 22 typedef struct file_spec { 23 ino_t ino; /* inode number */ 24 char *con; /* matched context */ 25 char *file; /* full pathname */ 26 struct file_spec *next; /* next association in hash bucket chain */ 27 } file_spec_t; 28 29 struct edir { 30 char *directory; 31 size_t size; 32 }; 33 34 35 static file_spec_t *fl_head; 36 static int filespec_add(ino_t ino, const security_context_t con, const char *file); 37 struct restore_opts *r_opts = NULL; 38 static void filespec_destroy(void); 39 static void filespec_eval(void); 40 static int excludeCtr = 0; 41 static struct edir excludeArray[MAX_EXCLUDES]; 42 43 void remove_exclude(const char *directory) 44 { 45 int i = 0; 46 for (i = 0; i < excludeCtr; i++) { 47 if (strcmp(directory, excludeArray[i].directory) == 0) { 48 free(excludeArray[i].directory); 49 if (i != excludeCtr-1) 50 excludeArray[i] = excludeArray[excludeCtr-1]; 51 excludeCtr--; 52 return; 53 } 54 } 55 return; 56 } 57 58 void restore_init(struct restore_opts *opts) 59 { 60 r_opts = opts; 61 struct selinux_opt selinux_opts[] = { 62 { SELABEL_OPT_VALIDATE, r_opts->selabel_opt_validate }, 63 { SELABEL_OPT_PATH, r_opts->selabel_opt_path } 64 }; 65 r_opts->hnd = selabel_open(SELABEL_CTX_FILE, selinux_opts, 2); 66 if (!r_opts->hnd) { 67 perror(r_opts->selabel_opt_path); 68 exit(1); 69 } 70 } 71 72 void restore_finish() 73 { 74 int i; 75 for (i = 0; i < excludeCtr; i++) { 76 free(excludeArray[i].directory); 77 } 78 } 79 80 static int match(const char *name, struct stat *sb, char **con) 81 { 82 if (!(r_opts->hard_links) && !S_ISDIR(sb->st_mode) && (sb->st_nlink > 1)) { 83 fprintf(stderr, "Warning! %s refers to a file with more than one hard link, not fixing hard links.\n", 84 name); 85 return -1; 86 } 87 88 if (NULL != r_opts->rootpath) { 89 if (0 != strncmp(r_opts->rootpath, name, r_opts->rootpathlen)) { 90 fprintf(stderr, "%s: %s is not located in %s\n", 91 r_opts->progname, name, r_opts->rootpath); 92 return -1; 93 } 94 name += r_opts->rootpathlen; 95 } 96 97 if (r_opts->rootpath != NULL && name[0] == '\0') 98 /* this is actually the root dir of the alt root */ 99 return selabel_lookup_raw(r_opts->hnd, con, "/", sb->st_mode); 100 else 101 return selabel_lookup_raw(r_opts->hnd, con, name, sb->st_mode); 102 } 103 static int restore(FTSENT *ftsent, int recurse) 104 { 105 char *my_file = strdupa(ftsent->fts_path); 106 int ret = -1; 107 security_context_t curcon = NULL, newcon = NULL; 108 float progress; 109 if (match(my_file, ftsent->fts_statp, &newcon) < 0) { 110 if ((errno == ENOENT) && ((!recurse) || (r_opts->verbose))) 111 fprintf(stderr, "%s: Warning no default label for %s\n", r_opts->progname, my_file); 112 113 /* Check for no matching specification. */ 114 return (errno == ENOENT) ? 0 : -1; 115 } 116 117 if (r_opts->progress) { 118 r_opts->count++; 119 if (r_opts->count % STAR_COUNT == 0) { 120 if (r_opts->progress == 1) { 121 fprintf(stdout, "\r%luk", (size_t) r_opts->count / STAR_COUNT ); 122 } else { 123 if (r_opts->nfile > 0) { 124 progress = (r_opts->count < r_opts->nfile) ? (100.0 * r_opts->count / r_opts->nfile) : 100; 125 fprintf(stdout, "\r%-.1f%%", progress); 126 } 127 } 128 fflush(stdout); 129 } 130 } 131 132 /* 133 * Try to add an association between this inode and 134 * this specification. If there is already an association 135 * for this inode and it conflicts with this specification, 136 * then use the last matching specification. 137 */ 138 if (r_opts->add_assoc) { 139 ret = filespec_add(ftsent->fts_statp->st_ino, newcon, my_file); 140 if (ret < 0) 141 goto err; 142 143 if (ret > 0) 144 /* There was already an association and it took precedence. */ 145 goto out; 146 } 147 148 if (r_opts->debug) { 149 printf("%s: %s matched by %s\n", r_opts->progname, my_file, newcon); 150 } 151 152 /* 153 * Do not relabel if their is no default specification for this file 154 */ 155 156 if (strcmp(newcon, "<<none>>") == 0) { 157 goto out; 158 } 159 160 /* Get the current context of the file. */ 161 ret = lgetfilecon_raw(ftsent->fts_accpath, &curcon); 162 if (ret < 0) { 163 if (errno == ENODATA) { 164 curcon = NULL; 165 } else { 166 fprintf(stderr, "%s get context on %s failed: '%s'\n", 167 r_opts->progname, my_file, strerror(errno)); 168 goto err; 169 } 170 } 171 172 /* lgetfilecon returns number of characters and ret needs to be reset 173 * to 0. 174 */ 175 ret = 0; 176 177 /* 178 * Do not relabel the file if the file is already labeled according to 179 * the specification. 180 */ 181 if (curcon && (strcmp(curcon, newcon) == 0)) { 182 goto out; 183 } 184 185 if (!r_opts->force && curcon && (is_context_customizable(curcon) > 0)) { 186 if (r_opts->verbose > 1) { 187 fprintf(stderr, 188 "%s: %s not reset customized by admin to %s\n", 189 r_opts->progname, my_file, curcon); 190 } 191 goto out; 192 } 193 194 /* 195 * Do not change label unless this is a force or the type is different 196 */ 197 if (!r_opts->force && curcon) { 198 int types_differ = 0; 199 context_t cona; 200 context_t conb; 201 int err = 0; 202 cona = context_new(curcon); 203 if (! cona) { 204 goto out; 205 } 206 conb = context_new(newcon); 207 if (! conb) { 208 context_free(cona); 209 goto out; 210 } 211 212 types_differ = strcmp(context_type_get(cona), context_type_get(conb)); 213 if (types_differ) { 214 err |= context_user_set(conb, context_user_get(cona)); 215 err |= context_role_set(conb, context_role_get(cona)); 216 err |= context_range_set(conb, context_range_get(cona)); 217 if (!err) { 218 freecon(newcon); 219 newcon = strdup(context_str(conb)); 220 } 221 } 222 context_free(cona); 223 context_free(conb); 224 225 if (!types_differ || err) { 226 goto out; 227 } 228 } 229 230 if (r_opts->verbose) { 231 printf("%s reset %s context %s->%s\n", 232 r_opts->progname, my_file, curcon ?: "", newcon); 233 } 234 235 if (r_opts->logging && r_opts->change) { 236 if (curcon) 237 syslog(LOG_INFO, "relabeling %s from %s to %s\n", 238 my_file, curcon, newcon); 239 else 240 syslog(LOG_INFO, "labeling %s to %s\n", 241 my_file, newcon); 242 } 243 244 if (r_opts->outfile) 245 fprintf(r_opts->outfile, "%s\n", my_file); 246 247 /* 248 * Do not relabel the file if -n was used. 249 */ 250 if (!r_opts->change) 251 goto out; 252 253 /* 254 * Relabel the file to the specified context. 255 */ 256 ret = lsetfilecon(ftsent->fts_accpath, newcon); 257 if (ret) { 258 fprintf(stderr, "%s set context %s->%s failed:'%s'\n", 259 r_opts->progname, my_file, newcon, strerror(errno)); 260 goto skip; 261 } 262 ret = 0; 263 out: 264 freecon(curcon); 265 freecon(newcon); 266 return ret; 267 skip: 268 freecon(curcon); 269 freecon(newcon); 270 return SKIP; 271 err: 272 freecon(curcon); 273 freecon(newcon); 274 return ERR; 275 } 276 /* 277 * Apply the last matching specification to a file. 278 * This function is called by fts on each file during 279 * the directory traversal. 280 */ 281 static int apply_spec(FTSENT *ftsent, int recurse) 282 { 283 if (ftsent->fts_info == FTS_DNR) { 284 fprintf(stderr, "%s: unable to read directory %s\n", 285 r_opts->progname, ftsent->fts_path); 286 return SKIP; 287 } 288 289 int rc = restore(ftsent, recurse); 290 if (rc == ERR) { 291 if (!r_opts->abort_on_error) 292 return SKIP; 293 } 294 return rc; 295 } 296 297 #include <sys/statvfs.h> 298 299 static int process_one(char *name, int recurse_this_path) 300 { 301 int rc = 0; 302 const char *namelist[2] = {name, NULL}; 303 dev_t dev_num = 0; 304 FTS *fts_handle = NULL; 305 FTSENT *ftsent = NULL; 306 307 if (r_opts == NULL){ 308 fprintf(stderr, 309 "Must call initialize first!"); 310 goto err; 311 } 312 313 fts_handle = fts_open((char **)namelist, r_opts->fts_flags, NULL); 314 if (fts_handle == NULL) { 315 fprintf(stderr, 316 "%s: error while labeling %s: %s\n", 317 r_opts->progname, namelist[0], strerror(errno)); 318 goto err; 319 } 320 321 322 ftsent = fts_read(fts_handle); 323 if (ftsent == NULL) { 324 fprintf(stderr, 325 "%s: error while labeling %s: %s\n", 326 r_opts->progname, namelist[0], strerror(errno)); 327 goto err; 328 } 329 330 /* Keep the inode of the first one. */ 331 dev_num = ftsent->fts_statp->st_dev; 332 333 do { 334 rc = 0; 335 /* Skip the post order nodes. */ 336 if (ftsent->fts_info == FTS_DP) 337 continue; 338 /* If the XDEV flag is set and the device is different */ 339 if (ftsent->fts_statp->st_dev != dev_num && 340 FTS_XDEV == (r_opts->fts_flags & FTS_XDEV)) 341 continue; 342 if (excludeCtr > 0) { 343 if (exclude(ftsent->fts_path)) { 344 fts_set(fts_handle, ftsent, FTS_SKIP); 345 continue; 346 } 347 } 348 349 rc = apply_spec(ftsent, recurse_this_path); 350 if (rc == SKIP) 351 fts_set(fts_handle, ftsent, FTS_SKIP); 352 if (rc == ERR) 353 goto err; 354 if (!recurse_this_path) 355 break; 356 } while ((ftsent = fts_read(fts_handle)) != NULL); 357 358 out: 359 if (r_opts->add_assoc) { 360 if (!r_opts->quiet) 361 filespec_eval(); 362 filespec_destroy(); 363 } 364 if (fts_handle) 365 fts_close(fts_handle); 366 return rc; 367 368 err: 369 rc = -1; 370 goto out; 371 } 372 373 int process_glob(char *name, int recurse) { 374 glob_t globbuf; 375 size_t i = 0; 376 int errors; 377 memset(&globbuf, 0, sizeof(globbuf)); 378 errors = glob(name, GLOB_TILDE | GLOB_PERIOD | GLOB_NOCHECK | GLOB_BRACE, NULL, &globbuf); 379 if (errors) 380 return errors; 381 382 for (i = 0; i < globbuf.gl_pathc; i++) { 383 int len = strlen(globbuf.gl_pathv[i]) -2; 384 if (len > 0 && strcmp(&globbuf.gl_pathv[i][len--], "/.") == 0) 385 continue; 386 if (len > 0 && strcmp(&globbuf.gl_pathv[i][len], "/..") == 0) 387 continue; 388 int rc = process_one_realpath(globbuf.gl_pathv[i], recurse); 389 if (rc < 0) 390 errors = rc; 391 } 392 globfree(&globbuf); 393 return errors; 394 } 395 396 int process_one_realpath(char *name, int recurse) 397 { 398 int rc = 0; 399 char *p; 400 struct stat64 sb; 401 402 if (r_opts == NULL){ 403 fprintf(stderr, 404 "Must call initialize first!"); 405 return -1; 406 } 407 408 if (!r_opts->expand_realpath) { 409 return process_one(name, recurse); 410 } else { 411 rc = lstat64(name, &sb); 412 if (rc < 0) { 413 if (r_opts->ignore_enoent && errno == ENOENT) 414 return 0; 415 fprintf(stderr, "%s: lstat(%s) failed: %s\n", 416 r_opts->progname, name, strerror(errno)); 417 return -1; 418 } 419 420 if (S_ISLNK(sb.st_mode)) { 421 char path[PATH_MAX + 1]; 422 423 rc = realpath_not_final(name, path); 424 if (rc < 0) 425 return rc; 426 rc = process_one(path, 0); 427 if (rc < 0) 428 return rc; 429 430 p = realpath(name, NULL); 431 if (p) { 432 rc = process_one(p, recurse); 433 free(p); 434 } 435 return rc; 436 } else { 437 p = realpath(name, NULL); 438 if (!p) { 439 fprintf(stderr, "realpath(%s) failed %s\n", name, 440 strerror(errno)); 441 return -1; 442 } 443 rc = process_one(p, recurse); 444 free(p); 445 return rc; 446 } 447 } 448 } 449 450 int exclude(const char *file) 451 { 452 int i = 0; 453 for (i = 0; i < excludeCtr; i++) { 454 if (strncmp 455 (file, excludeArray[i].directory, 456 excludeArray[i].size) == 0) { 457 if (file[excludeArray[i].size] == 0 458 || file[excludeArray[i].size] == '/') { 459 return 1; 460 } 461 } 462 } 463 return 0; 464 } 465 466 int add_exclude(const char *directory) 467 { 468 size_t len = 0; 469 470 if (directory == NULL || directory[0] != '/') { 471 fprintf(stderr, "Full path required for exclude: %s.\n", 472 directory); 473 return 1; 474 } 475 if (excludeCtr == MAX_EXCLUDES) { 476 fprintf(stderr, "Maximum excludes %d exceeded.\n", 477 MAX_EXCLUDES); 478 return 1; 479 } 480 481 len = strlen(directory); 482 while (len > 1 && directory[len - 1] == '/') { 483 len--; 484 } 485 excludeArray[excludeCtr].directory = strndup(directory, len); 486 487 if (excludeArray[excludeCtr].directory == NULL) { 488 fprintf(stderr, "Out of memory.\n"); 489 return 1; 490 } 491 excludeArray[excludeCtr++].size = len; 492 493 return 0; 494 } 495 496 /* 497 * Evaluate the association hash table distribution. 498 */ 499 static void filespec_eval(void) 500 { 501 file_spec_t *fl; 502 int h, used, nel, len, longest; 503 504 if (!fl_head) 505 return; 506 507 used = 0; 508 longest = 0; 509 nel = 0; 510 for (h = 0; h < HASH_BUCKETS; h++) { 511 len = 0; 512 for (fl = fl_head[h].next; fl; fl = fl->next) { 513 len++; 514 } 515 if (len) 516 used++; 517 if (len > longest) 518 longest = len; 519 nel += len; 520 } 521 522 if (r_opts->verbose > 1) 523 printf 524 ("%s: hash table stats: %d elements, %d/%d buckets used, longest chain length %d\n", 525 __FUNCTION__, nel, used, HASH_BUCKETS, longest); 526 } 527 528 /* 529 * Destroy the association hash table. 530 */ 531 static void filespec_destroy(void) 532 { 533 file_spec_t *fl, *tmp; 534 int h; 535 536 if (!fl_head) 537 return; 538 539 for (h = 0; h < HASH_BUCKETS; h++) { 540 fl = fl_head[h].next; 541 while (fl) { 542 tmp = fl; 543 fl = fl->next; 544 freecon(tmp->con); 545 free(tmp->file); 546 free(tmp); 547 } 548 fl_head[h].next = NULL; 549 } 550 free(fl_head); 551 fl_head = NULL; 552 } 553 /* 554 * Try to add an association between an inode and a context. 555 * If there is a different context that matched the inode, 556 * then use the first context that matched. 557 */ 558 static int filespec_add(ino_t ino, const security_context_t con, const char *file) 559 { 560 file_spec_t *prevfl, *fl; 561 int h, ret; 562 struct stat64 sb; 563 564 if (!fl_head) { 565 fl_head = malloc(sizeof(file_spec_t) * HASH_BUCKETS); 566 if (!fl_head) 567 goto oom; 568 memset(fl_head, 0, sizeof(file_spec_t) * HASH_BUCKETS); 569 } 570 571 h = (ino + (ino >> HASH_BITS)) & HASH_MASK; 572 for (prevfl = &fl_head[h], fl = fl_head[h].next; fl; 573 prevfl = fl, fl = fl->next) { 574 if (ino == fl->ino) { 575 ret = lstat64(fl->file, &sb); 576 if (ret < 0 || sb.st_ino != ino) { 577 freecon(fl->con); 578 free(fl->file); 579 fl->file = strdup(file); 580 if (!fl->file) 581 goto oom; 582 fl->con = strdup(con); 583 if (!fl->con) 584 goto oom; 585 return 1; 586 } 587 588 if (strcmp(fl->con, con) == 0) 589 return 1; 590 591 fprintf(stderr, 592 "%s: conflicting specifications for %s and %s, using %s.\n", 593 __FUNCTION__, file, fl->file, fl->con); 594 free(fl->file); 595 fl->file = strdup(file); 596 if (!fl->file) 597 goto oom; 598 return 1; 599 } 600 601 if (ino > fl->ino) 602 break; 603 } 604 605 fl = malloc(sizeof(file_spec_t)); 606 if (!fl) 607 goto oom; 608 fl->ino = ino; 609 fl->con = strdup(con); 610 if (!fl->con) 611 goto oom_freefl; 612 fl->file = strdup(file); 613 if (!fl->file) 614 goto oom_freefl; 615 fl->next = prevfl->next; 616 prevfl->next = fl; 617 return 0; 618 oom_freefl: 619 free(fl); 620 oom: 621 fprintf(stderr, 622 "%s: insufficient memory for file label entry for %s\n", 623 __FUNCTION__, file); 624 return -1; 625 } 626 627 #include <sys/utsname.h> 628 int file_system_count(char *name) { 629 struct statvfs statvfs_buf; 630 int nfile = 0; 631 memset(&statvfs_buf, 0, sizeof(statvfs_buf)); 632 if (!statvfs(name, &statvfs_buf)) { 633 nfile = statvfs_buf.f_files - statvfs_buf.f_ffree; 634 } 635 return nfile; 636 } 637 638 /* 639 Search /proc/mounts for all file systems that do not support extended 640 attributes and add them to the exclude directory table. File systems 641 that support security labels have the seclabel option, return total file count 642 */ 643 int exclude_non_seclabel_mounts() 644 { 645 struct utsname uts; 646 FILE *fp; 647 size_t len; 648 ssize_t num; 649 int index = 0, found = 0; 650 char *mount_info[4]; 651 char *buf = NULL, *item; 652 int nfile = 0; 653 /* Check to see if the kernel supports seclabel */ 654 if (uname(&uts) == 0 && strverscmp(uts.release, "2.6.30") < 0) 655 return 0; 656 if (is_selinux_enabled() <= 0) 657 return 0; 658 659 fp = fopen("/proc/mounts", "r"); 660 if (!fp) 661 return 0; 662 663 while ((num = getline(&buf, &len, fp)) != -1) { 664 found = 0; 665 index = 0; 666 item = strtok(buf, " "); 667 while (item != NULL) { 668 mount_info[index] = item; 669 if (index == 3) 670 break; 671 index++; 672 item = strtok(NULL, " "); 673 } 674 if (index < 3) { 675 fprintf(stderr, 676 "/proc/mounts record \"%s\" has incorrect format.\n", 677 buf); 678 continue; 679 } 680 681 /* remove pre-existing entry */ 682 remove_exclude(mount_info[1]); 683 684 item = strtok(mount_info[3], ","); 685 while (item != NULL) { 686 if (strcmp(item, "seclabel") == 0) { 687 found = 1; 688 nfile += file_system_count(mount_info[1]); 689 break; 690 } 691 item = strtok(NULL, ","); 692 } 693 694 /* exclude mount points without the seclabel option */ 695 if (!found) 696 add_exclude(mount_info[1]); 697 } 698 699 free(buf); 700 fclose(fp); 701 /* return estimated #Files + 5% for directories and hard links */ 702 return nfile * 1.05; 703 } 704 705