1 /* 2 * File contexts backend for labeling system 3 * 4 * Author : Eamon Walsh <ewalsh (at) tycho.nsa.gov> 5 * Author : Stephen Smalley <sds (at) tycho.nsa.gov> 6 * 7 * This library derived in part from setfiles and the setfiles.pl script 8 * developed by Secure Computing Corporation. 9 */ 10 11 #include <assert.h> 12 #include <fcntl.h> 13 #include <stdarg.h> 14 #include <string.h> 15 #include <stdio.h> 16 #include <stdio_ext.h> 17 #include <ctype.h> 18 #include <errno.h> 19 #include <limits.h> 20 #include <stdint.h> 21 #include <pcre.h> 22 23 #include <linux/limits.h> 24 25 #include <sys/mman.h> 26 #include <sys/types.h> 27 #include <sys/stat.h> 28 #include <unistd.h> 29 #include "callbacks.h" 30 #include "label_internal.h" 31 #include "label_file.h" 32 33 /* 34 * Internals, mostly moved over from matchpathcon.c 35 */ 36 37 /* return the length of the text that is the stem of a file name */ 38 static int get_stem_from_file_name(const char *const buf) 39 { 40 const char *tmp = strchr(buf + 1, '/'); 41 42 if (!tmp) 43 return 0; 44 return tmp - buf; 45 } 46 47 /* find the stem of a file name, returns the index into stem_arr (or -1 if 48 * there is no match - IE for a file in the root directory or a regex that is 49 * too complex for us). Makes buf point to the text AFTER the stem. */ 50 static int find_stem_from_file(struct saved_data *data, const char **buf) 51 { 52 int i; 53 int stem_len = get_stem_from_file_name(*buf); 54 55 if (!stem_len) 56 return -1; 57 for (i = 0; i < data->num_stems; i++) { 58 if (stem_len == data->stem_arr[i].len 59 && !strncmp(*buf, data->stem_arr[i].buf, stem_len)) { 60 *buf += stem_len; 61 return i; 62 } 63 } 64 return -1; 65 } 66 67 /* 68 * Warn about duplicate specifications. 69 */ 70 static int nodups_specs(struct saved_data *data, const char *path) 71 { 72 int rc = 0; 73 unsigned int ii, jj; 74 struct spec *curr_spec, *spec_arr = data->spec_arr; 75 76 for (ii = 0; ii < data->nspec; ii++) { 77 curr_spec = &spec_arr[ii]; 78 for (jj = ii + 1; jj < data->nspec; jj++) { 79 if ((!strcmp(spec_arr[jj].regex_str, curr_spec->regex_str)) 80 && (!spec_arr[jj].mode || !curr_spec->mode 81 || spec_arr[jj].mode == curr_spec->mode)) { 82 rc = -1; 83 errno = EINVAL; 84 if (strcmp(spec_arr[jj].lr.ctx_raw, curr_spec->lr.ctx_raw)) { 85 COMPAT_LOG 86 (SELINUX_ERROR, 87 "%s: Multiple different specifications for %s (%s and %s).\n", 88 path, curr_spec->regex_str, 89 spec_arr[jj].lr.ctx_raw, 90 curr_spec->lr.ctx_raw); 91 } else { 92 COMPAT_LOG 93 (SELINUX_ERROR, 94 "%s: Multiple same specifications for %s.\n", 95 path, curr_spec->regex_str); 96 } 97 } 98 } 99 } 100 return rc; 101 } 102 103 static int compile_regex(struct saved_data *data, struct spec *spec, const char **errbuf) 104 { 105 const char *tmperrbuf; 106 char *reg_buf, *anchored_regex, *cp; 107 struct stem *stem_arr = data->stem_arr; 108 size_t len; 109 int erroff; 110 111 if (spec->regcomp) 112 return 0; /* already done */ 113 114 /* Skip the fixed stem. */ 115 reg_buf = spec->regex_str; 116 if (spec->stem_id >= 0) 117 reg_buf += stem_arr[spec->stem_id].len; 118 119 /* Anchor the regular expression. */ 120 len = strlen(reg_buf); 121 cp = anchored_regex = malloc(len + 3); 122 if (!anchored_regex) 123 return -1; 124 125 /* Create ^...$ regexp. */ 126 *cp++ = '^'; 127 cp = mempcpy(cp, reg_buf, len); 128 *cp++ = '$'; 129 *cp = '\0'; 130 131 /* Compile the regular expression. */ 132 spec->regex = pcre_compile(anchored_regex, PCRE_DOTALL, &tmperrbuf, &erroff, NULL); 133 free(anchored_regex); 134 if (!spec->regex) { 135 if (errbuf) 136 *errbuf=tmperrbuf; 137 return -1; 138 } 139 140 spec->sd = pcre_study(spec->regex, 0, &tmperrbuf); 141 if (!spec->sd && tmperrbuf) { 142 if (errbuf) 143 *errbuf=tmperrbuf; 144 return -1; 145 } 146 147 /* Done. */ 148 spec->regcomp = 1; 149 150 return 0; 151 } 152 153 static int process_line(struct selabel_handle *rec, 154 const char *path, const char *prefix, 155 char *line_buf, unsigned lineno) 156 { 157 int items, len, rc; 158 char *buf_p, *regex, *type, *context; 159 struct saved_data *data = (struct saved_data *)rec->data; 160 struct spec *spec_arr; 161 unsigned int nspec = data->nspec; 162 const char *errbuf = NULL; 163 164 len = strlen(line_buf); 165 if (line_buf[len - 1] == '\n') 166 line_buf[len - 1] = 0; 167 buf_p = line_buf; 168 while (isspace(*buf_p)) 169 buf_p++; 170 /* Skip comment lines and empty lines. */ 171 if (*buf_p == '#' || *buf_p == 0) 172 return 0; 173 items = sscanf(line_buf, "%ms %ms %ms", ®ex, &type, &context); 174 if (items < 2) { 175 COMPAT_LOG(SELINUX_WARNING, 176 "%s: line %u is missing fields, skipping\n", path, 177 lineno); 178 if (items == 1) 179 free(regex); 180 return 0; 181 } else if (items == 2) { 182 /* The type field is optional. */ 183 free(context); 184 context = type; 185 type = 0; 186 } 187 188 len = get_stem_from_spec(regex); 189 if (len && prefix && strncmp(prefix, regex, len)) { 190 /* Stem of regex does not match requested prefix, discard. */ 191 free(regex); 192 free(type); 193 free(context); 194 return 0; 195 } 196 197 rc = grow_specs(data); 198 if (rc) 199 return rc; 200 201 spec_arr = data->spec_arr; 202 203 /* process and store the specification in spec. */ 204 spec_arr[nspec].stem_id = find_stem_from_spec(data, regex); 205 spec_arr[nspec].regex_str = regex; 206 if (rec->validating && compile_regex(data, &spec_arr[nspec], &errbuf)) { 207 COMPAT_LOG(SELINUX_WARNING, "%s: line %u has invalid regex %s: %s\n", 208 path, lineno, regex, (errbuf ? errbuf : "out of memory")); 209 } 210 211 /* Convert the type string to a mode format */ 212 spec_arr[nspec].type_str = type; 213 spec_arr[nspec].mode = 0; 214 if (type) { 215 mode_t mode = string_to_mode(type); 216 if (mode == (mode_t)-1) { 217 COMPAT_LOG(SELINUX_WARNING, "%s: line %u has invalid file type %s\n", 218 path, lineno, type); 219 mode = 0; 220 } 221 spec_arr[nspec].mode = mode; 222 } 223 224 spec_arr[nspec].lr.ctx_raw = context; 225 226 /* Determine if specification has 227 * any meta characters in the RE */ 228 spec_hasMetaChars(&spec_arr[nspec]); 229 230 if (strcmp(context, "<<none>>") && rec->validating) 231 compat_validate(rec, &spec_arr[nspec].lr, path, lineno); 232 233 data->nspec = ++nspec; 234 235 return 0; 236 } 237 238 static int load_mmap(struct selabel_handle *rec, const char *path, struct stat *sb) 239 { 240 struct saved_data *data = (struct saved_data *)rec->data; 241 char mmap_path[PATH_MAX + 1]; 242 int mmapfd; 243 int rc; 244 struct stat mmap_stat; 245 char *addr; 246 size_t len; 247 int stem_map_len, *stem_map; 248 struct mmap_area *mmap_area; 249 250 uint32_t i; 251 uint32_t *magic; 252 uint32_t *section_len; 253 uint32_t *plen; 254 255 rc = snprintf(mmap_path, sizeof(mmap_path), "%s.bin", path); 256 if (rc >= (int)sizeof(mmap_path)) 257 return -1; 258 259 mmapfd = open(mmap_path, O_RDONLY | O_CLOEXEC); 260 if (mmapfd < 0) 261 return -1; 262 263 rc = fstat(mmapfd, &mmap_stat); 264 if (rc < 0) { 265 close(mmapfd); 266 return -1; 267 } 268 269 /* if mmap is old, ignore it */ 270 if (mmap_stat.st_mtime < sb->st_mtime) { 271 close(mmapfd); 272 return -1; 273 } 274 275 if (mmap_stat.st_mtime == sb->st_mtime && 276 mmap_stat.st_mtim.tv_nsec < sb->st_mtim.tv_nsec) { 277 close(mmapfd); 278 return -1; 279 } 280 281 /* ok, read it in... */ 282 len = mmap_stat.st_size; 283 len += (sysconf(_SC_PAGE_SIZE) - 1); 284 len &= ~(sysconf(_SC_PAGE_SIZE) - 1); 285 286 mmap_area = malloc(sizeof(*mmap_area)); 287 if (!mmap_area) { 288 close(mmapfd); 289 return -1; 290 } 291 292 addr = mmap(NULL, len, PROT_READ, MAP_PRIVATE, mmapfd, 0); 293 close(mmapfd); 294 if (addr == MAP_FAILED) { 295 free(mmap_area); 296 perror("mmap"); 297 return -1; 298 } 299 300 /* save where we mmap'd the file to cleanup on close() */ 301 mmap_area->addr = addr; 302 mmap_area->len = len; 303 mmap_area->next = data->mmap_areas; 304 data->mmap_areas = mmap_area; 305 306 /* check if this looks like an fcontext file */ 307 magic = (uint32_t *)addr; 308 if (*magic != SELINUX_MAGIC_COMPILED_FCONTEXT) 309 return -1; 310 addr += sizeof(uint32_t); 311 312 /* check if this version is higher than we understand */ 313 section_len = (uint32_t *)addr; 314 if (*section_len > SELINUX_COMPILED_FCONTEXT_MAX_VERS) 315 return -1; 316 addr += sizeof(uint32_t); 317 318 if (*section_len >= SELINUX_COMPILED_FCONTEXT_PCRE_VERS) { 319 len = strlen(pcre_version()); 320 plen = (uint32_t *)addr; 321 if (*plen > mmap_area->len) 322 return -1; /* runs off the end of the map */ 323 if (len != *plen) 324 return -1; /* pcre version length mismatch */ 325 addr += sizeof(uint32_t); 326 if (memcmp((char *)addr, pcre_version(), len)) 327 return -1; /* pcre version content mismatch */ 328 if (addr + *plen >= (char *)mmap_area->addr + mmap_area->len) 329 return -1; /* Buffer over-run */ 330 addr += *plen; 331 } 332 333 /* allocate the stems_data array */ 334 section_len = (uint32_t *)addr; 335 addr += sizeof(uint32_t); 336 337 /* 338 * map indexed by the stem # in the mmap file and contains the stem 339 * number in the data stem_arr 340 */ 341 stem_map_len = *section_len; 342 stem_map = calloc(stem_map_len, sizeof(*stem_map)); 343 if (!stem_map) 344 return -1; 345 346 for (i = 0; i < *section_len; i++) { 347 char *buf; 348 uint32_t stem_len; 349 int newid; 350 351 /* the length does not inlude the nul */ 352 plen = (uint32_t *)addr; 353 addr += sizeof(uint32_t); 354 355 stem_len = *plen; 356 buf = (char *)addr; 357 addr += (stem_len + 1); // +1 is the nul 358 359 /* store the mapping between old and new */ 360 newid = find_stem(data, buf, stem_len); 361 if (newid < 0) { 362 newid = store_stem(data, buf, stem_len); 363 if (newid < 0) { 364 rc = newid; 365 goto err; 366 } 367 data->stem_arr[newid].from_mmap = 1; 368 } 369 stem_map[i] = newid; 370 } 371 372 /* allocate the regex array */ 373 section_len = (uint32_t *)addr; 374 addr += sizeof(*section_len); 375 376 for (i = 0; i < *section_len; i++) { 377 struct spec *spec; 378 int32_t stem_id; 379 380 rc = grow_specs(data); 381 if (rc < 0) 382 goto err; 383 384 spec = &data->spec_arr[data->nspec]; 385 spec->from_mmap = 1; 386 spec->regcomp = 1; 387 388 plen = (uint32_t *)addr; 389 addr += sizeof(uint32_t); 390 rc = -1; 391 spec->lr.ctx_raw = strdup((char *)addr); 392 if (!spec->lr.ctx_raw) 393 goto err; 394 395 if (addr + *plen >= (char *)mmap_area->addr + mmap_area->len) 396 return -1; 397 addr += *plen; 398 399 plen = (uint32_t *)addr; 400 addr += sizeof(uint32_t); 401 spec->regex_str = (char *)addr; 402 if (addr + *plen >= (char *)mmap_area->addr + mmap_area->len) 403 return -1; 404 addr += *plen; 405 406 spec->mode = *(mode_t *)addr; 407 addr += sizeof(mode_t); 408 409 /* map the stem id from the mmap file to the data->stem_arr */ 410 stem_id = *(int32_t *)addr; 411 if (stem_id == -1 || stem_id >= stem_map_len) 412 spec->stem_id = -1; 413 else 414 spec->stem_id = stem_map[stem_id]; 415 addr += sizeof(int32_t); 416 417 /* retrieve the hasMetaChars bit */ 418 spec->hasMetaChars = *(uint32_t *)addr; 419 addr += sizeof(uint32_t); 420 421 plen = (uint32_t *)addr; 422 addr += sizeof(uint32_t); 423 spec->regex = (pcre *)addr; 424 if (addr + *plen >= (char *)mmap_area->addr + mmap_area->len) 425 return -1; 426 addr += *plen; 427 428 plen = (uint32_t *)addr; 429 addr += sizeof(uint32_t); 430 spec->lsd.study_data = (void *)addr; 431 spec->lsd.flags |= PCRE_EXTRA_STUDY_DATA; 432 if (addr + *plen >= (char *)mmap_area->addr + mmap_area->len) 433 return -1; 434 addr += *plen; 435 436 data->nspec++; 437 } 438 /* win */ 439 rc = 0; 440 err: 441 free(stem_map); 442 443 return rc; 444 } 445 446 static int process_file(const char *path, const char *suffix, struct selabel_handle *rec, const char *prefix) 447 { 448 FILE *fp; 449 struct stat sb; 450 unsigned int lineno; 451 size_t line_len; 452 char *line_buf = NULL; 453 int rc; 454 char stack_path[PATH_MAX + 1]; 455 456 /* append the path suffix if we have one */ 457 if (suffix) { 458 rc = snprintf(stack_path, sizeof(stack_path), "%s.%s", path, suffix); 459 if (rc >= (int)sizeof(stack_path)) { 460 errno = ENAMETOOLONG; 461 return -1; 462 } 463 path = stack_path; 464 } 465 466 /* Open the specification file. */ 467 if ((fp = fopen(path, "r")) == NULL) 468 return -1; 469 __fsetlocking(fp, FSETLOCKING_BYCALLER); 470 471 if (fstat(fileno(fp), &sb) < 0) 472 return -1; 473 if (!S_ISREG(sb.st_mode)) { 474 errno = EINVAL; 475 return -1; 476 } 477 478 rc = load_mmap(rec, path, &sb); 479 if (rc == 0) 480 goto out; 481 482 /* 483 * The do detailed validation of the input and fill the spec array 484 */ 485 lineno = 0; 486 while (getline(&line_buf, &line_len, fp) > 0) { 487 rc = process_line(rec, path, prefix, line_buf, ++lineno); 488 if (rc) 489 return rc; 490 } 491 out: 492 free(line_buf); 493 fclose(fp); 494 495 return 0; 496 } 497 498 static int init(struct selabel_handle *rec, struct selinux_opt *opts, 499 unsigned n) 500 { 501 struct saved_data *data = (struct saved_data *)rec->data; 502 const char *path = NULL; 503 const char *prefix = NULL; 504 char subs_file[PATH_MAX + 1]; 505 int status = -1, baseonly = 0; 506 507 /* Process arguments */ 508 while (n--) 509 switch(opts[n].type) { 510 case SELABEL_OPT_PATH: 511 path = opts[n].value; 512 break; 513 case SELABEL_OPT_SUBSET: 514 prefix = opts[n].value; 515 break; 516 case SELABEL_OPT_BASEONLY: 517 baseonly = !!opts[n].value; 518 break; 519 } 520 521 /* Process local and distribution substitution files */ 522 if (!path) { 523 rec->dist_subs = selabel_subs_init(selinux_file_context_subs_dist_path(), rec->dist_subs); 524 rec->subs = selabel_subs_init(selinux_file_context_subs_path(), rec->subs); 525 path = selinux_file_context_path(); 526 } else { 527 snprintf(subs_file, sizeof(subs_file), "%s.subs_dist", path); 528 rec->dist_subs = selabel_subs_init(subs_file, rec->dist_subs); 529 snprintf(subs_file, sizeof(subs_file), "%s.subs", path); 530 rec->subs = selabel_subs_init(subs_file, rec->subs); 531 } 532 533 rec->spec_file = strdup(path); 534 535 /* 536 * The do detailed validation of the input and fill the spec array 537 */ 538 status = process_file(path, NULL, rec, prefix); 539 if (status) 540 goto finish; 541 542 if (rec->validating) { 543 status = nodups_specs(data, path); 544 if (status) 545 goto finish; 546 } 547 548 if (!baseonly) { 549 status = process_file(path, "homedirs", rec, prefix); 550 if (status && errno != ENOENT) 551 goto finish; 552 553 status = process_file(path, "local", rec, prefix); 554 if (status && errno != ENOENT) 555 goto finish; 556 } 557 558 status = sort_specs(data); 559 560 status = 0; 561 finish: 562 if (status) 563 free(data->spec_arr); 564 return status; 565 } 566 567 /* 568 * Backend interface routines 569 */ 570 static void closef(struct selabel_handle *rec) 571 { 572 struct saved_data *data = (struct saved_data *)rec->data; 573 struct mmap_area *area, *last_area; 574 struct spec *spec; 575 struct stem *stem; 576 unsigned int i; 577 578 for (i = 0; i < data->nspec; i++) { 579 spec = &data->spec_arr[i]; 580 free(spec->lr.ctx_trans); 581 free(spec->lr.ctx_raw); 582 if (spec->from_mmap) 583 continue; 584 free(spec->regex_str); 585 free(spec->type_str); 586 if (spec->regcomp) { 587 pcre_free(spec->regex); 588 pcre_free_study(spec->sd); 589 } 590 } 591 592 for (i = 0; i < (unsigned int)data->num_stems; i++) { 593 stem = &data->stem_arr[i]; 594 if (stem->from_mmap) 595 continue; 596 free(stem->buf); 597 } 598 599 if (data->spec_arr) 600 free(data->spec_arr); 601 if (data->stem_arr) 602 free(data->stem_arr); 603 604 area = data->mmap_areas; 605 while (area) { 606 munmap(area->addr, area->len); 607 last_area = area; 608 area = area->next; 609 free(last_area); 610 } 611 free(data); 612 } 613 614 static struct selabel_lookup_rec *lookup(struct selabel_handle *rec, 615 const char *key, int type) 616 { 617 struct saved_data *data = (struct saved_data *)rec->data; 618 struct spec *spec_arr = data->spec_arr; 619 int i, rc, file_stem; 620 mode_t mode = (mode_t)type; 621 const char *buf; 622 struct selabel_lookup_rec *ret = NULL; 623 char *clean_key = NULL; 624 const char *prev_slash, *next_slash; 625 unsigned int sofar = 0; 626 627 if (!data->nspec) { 628 errno = ENOENT; 629 goto finish; 630 } 631 632 /* Remove duplicate slashes */ 633 if ((next_slash = strstr(key, "//"))) { 634 clean_key = malloc(strlen(key) + 1); 635 if (!clean_key) 636 goto finish; 637 prev_slash = key; 638 while (next_slash) { 639 memcpy(clean_key + sofar, prev_slash, next_slash - prev_slash); 640 sofar += next_slash - prev_slash; 641 prev_slash = next_slash + 1; 642 next_slash = strstr(prev_slash, "//"); 643 } 644 strcpy(clean_key + sofar, prev_slash); 645 key = clean_key; 646 } 647 648 buf = key; 649 file_stem = find_stem_from_file(data, &buf); 650 mode &= S_IFMT; 651 652 /* 653 * Check for matching specifications in reverse order, so that 654 * the last matching specification is used. 655 */ 656 for (i = data->nspec - 1; i >= 0; i--) { 657 struct spec *spec = &spec_arr[i]; 658 /* if the spec in question matches no stem or has the same 659 * stem as the file AND if the spec in question has no mode 660 * specified or if the mode matches the file mode then we do 661 * a regex check */ 662 if ((spec->stem_id == -1 || spec->stem_id == file_stem) && 663 (!mode || !spec->mode || mode == spec->mode)) { 664 if (compile_regex(data, spec, NULL) < 0) 665 goto finish; 666 if (spec->stem_id == -1) 667 rc = pcre_exec(spec->regex, get_pcre_extra(spec), key, strlen(key), 0, 0, NULL, 0); 668 else 669 rc = pcre_exec(spec->regex, get_pcre_extra(spec), buf, strlen(buf), 0, 0, NULL, 0); 670 671 if (rc == 0) { 672 spec->matches++; 673 break; 674 } else if (rc == PCRE_ERROR_NOMATCH) 675 continue; 676 677 errno = ENOENT; 678 /* else it's an error */ 679 goto finish; 680 } 681 } 682 683 if (i < 0 || strcmp(spec_arr[i].lr.ctx_raw, "<<none>>") == 0) { 684 /* No matching specification. */ 685 errno = ENOENT; 686 goto finish; 687 } 688 689 errno = 0; 690 ret = &spec_arr[i].lr; 691 692 finish: 693 free(clean_key); 694 return ret; 695 } 696 697 static void stats(struct selabel_handle *rec) 698 { 699 struct saved_data *data = (struct saved_data *)rec->data; 700 unsigned int i, nspec = data->nspec; 701 struct spec *spec_arr = data->spec_arr; 702 703 for (i = 0; i < nspec; i++) { 704 if (spec_arr[i].matches == 0) { 705 if (spec_arr[i].type_str) { 706 COMPAT_LOG(SELINUX_WARNING, 707 "Warning! No matches for (%s, %s, %s)\n", 708 spec_arr[i].regex_str, 709 spec_arr[i].type_str, 710 spec_arr[i].lr.ctx_raw); 711 } else { 712 COMPAT_LOG(SELINUX_WARNING, 713 "Warning! No matches for (%s, %s)\n", 714 spec_arr[i].regex_str, 715 spec_arr[i].lr.ctx_raw); 716 } 717 } 718 } 719 } 720 721 int selabel_file_init(struct selabel_handle *rec, struct selinux_opt *opts, 722 unsigned nopts) 723 { 724 struct saved_data *data; 725 726 data = (struct saved_data *)malloc(sizeof(*data)); 727 if (!data) 728 return -1; 729 memset(data, 0, sizeof(*data)); 730 731 rec->data = data; 732 rec->func_close = &closef; 733 rec->func_stats = &stats; 734 rec->func_lookup = &lookup; 735 736 return init(rec, opts, nopts); 737 } 738