1 /* 2 * File contexts backend for labeling system 3 * 4 * Author : Eamon Walsh <ewalsh (at) tycho.nsa.gov> 5 */ 6 7 #include <fcntl.h> 8 #include <stdarg.h> 9 #include <string.h> 10 #include <stdio.h> 11 #include <ctype.h> 12 #include <errno.h> 13 #include <limits.h> 14 #include <sys/types.h> 15 #include <sys/stat.h> 16 #include <unistd.h> 17 #include "callbacks.h" 18 #include "label_internal.h" 19 #include <pcre.h> 20 21 /* 22 * Internals, mostly moved over from matchpathcon.c 23 */ 24 25 /* A file security context specification. */ 26 typedef struct spec { 27 struct selabel_lookup_rec lr; /* holds contexts for lookup result */ 28 char *regex_str; /* regular expession string for diagnostics */ 29 char *type_str; /* type string for diagnostic messages */ 30 pcre *regex; /* compiled regular expression */ 31 pcre_extra *sd; /* pcre study result */ 32 char regcomp; /* regex_str has been compiled to regex */ 33 mode_t mode; /* mode format value */ 34 int matches; /* number of matching pathnames */ 35 int hasMetaChars; /* regular expression has meta-chars */ 36 int stem_id; /* indicates which stem-compression item */ 37 size_t prefix_len; /* length of fixed path prefix */ 38 } spec_t; 39 40 /* A regular expression stem */ 41 typedef struct stem { 42 char *buf; 43 int len; 44 } stem_t; 45 46 /* Our stored configuration */ 47 struct saved_data { 48 /* 49 * The array of specifications, initially in the same order as in 50 * the specification file. Sorting occurs based on hasMetaChars. 51 */ 52 spec_t *spec_arr; 53 unsigned int nspec; 54 unsigned int ncomp; 55 56 /* 57 * The array of regular expression stems. 58 */ 59 stem_t *stem_arr; 60 int num_stems; 61 int alloc_stems; 62 }; 63 64 /* Return the length of the text that can be considered the stem, returns 0 65 * if there is no identifiable stem */ 66 static int get_stem_from_spec(const char *const buf) 67 { 68 const char *tmp = strchr(buf + 1, '/'); 69 const char *ind; 70 71 if (!tmp) 72 return 0; 73 74 for (ind = buf; ind < tmp; ind++) { 75 if (strchr(".^$?*+|[({", (int)*ind)) 76 return 0; 77 } 78 return tmp - buf; 79 } 80 81 /* return the length of the text that is the stem of a file name */ 82 static int get_stem_from_file_name(const char *const buf) 83 { 84 const char *tmp = strchr(buf + 1, '/'); 85 86 if (!tmp) 87 return 0; 88 return tmp - buf; 89 } 90 91 /* find the stem of a file spec, returns the index into stem_arr for a new 92 * or existing stem, (or -1 if there is no possible stem - IE for a file in 93 * the root directory or a regex that is too complex for us). */ 94 static int find_stem_from_spec(struct saved_data *data, const char *buf) 95 { 96 int i, num = data->num_stems; 97 int stem_len = get_stem_from_spec(buf); 98 99 if (!stem_len) 100 return -1; 101 for (i = 0; i < num; i++) { 102 if (stem_len == data->stem_arr[i].len 103 && !strncmp(buf, data->stem_arr[i].buf, stem_len)) 104 return i; 105 } 106 if (data->alloc_stems == num) { 107 stem_t *tmp_arr; 108 data->alloc_stems = data->alloc_stems * 2 + 16; 109 tmp_arr = (stem_t *) realloc(data->stem_arr, 110 sizeof(stem_t) * data->alloc_stems); 111 if (!tmp_arr) 112 return -1; 113 data->stem_arr = tmp_arr; 114 } 115 data->stem_arr[num].len = stem_len; 116 data->stem_arr[num].buf = (char *) malloc(stem_len + 1); 117 if (!data->stem_arr[num].buf) 118 return -1; 119 memcpy(data->stem_arr[num].buf, buf, stem_len); 120 data->stem_arr[num].buf[stem_len] = '\0'; 121 data->num_stems++; 122 buf += stem_len; 123 return num; 124 } 125 126 /* find the stem of a file name, returns the index into stem_arr (or -1 if 127 * there is no match - IE for a file in the root directory or a regex that is 128 * too complex for us). Makes buf point to the text AFTER the stem. */ 129 static int find_stem_from_file(struct saved_data *data, const char **buf) 130 { 131 int i; 132 int stem_len = get_stem_from_file_name(*buf); 133 134 if (!stem_len) 135 return -1; 136 for (i = 0; i < data->num_stems; i++) { 137 if (stem_len == data->stem_arr[i].len 138 && !strncmp(*buf, data->stem_arr[i].buf, stem_len)) { 139 *buf += stem_len; 140 return i; 141 } 142 } 143 return -1; 144 } 145 146 /* 147 * Warn about duplicate specifications. 148 */ 149 static int nodups_specs(struct saved_data *data, const char *path) 150 { 151 int rc = 0; 152 unsigned int ii, jj; 153 struct spec *curr_spec, *spec_arr = data->spec_arr; 154 155 for (ii = 0; ii < data->nspec; ii++) { 156 curr_spec = &spec_arr[ii]; 157 for (jj = ii + 1; jj < data->nspec; jj++) { 158 if ((!strcmp 159 (spec_arr[jj].regex_str, curr_spec->regex_str)) 160 && (!spec_arr[jj].mode || !curr_spec->mode 161 || spec_arr[jj].mode == curr_spec->mode)) { 162 rc = -1; 163 errno = EINVAL; 164 if (strcmp 165 (spec_arr[jj].lr.ctx_raw, 166 curr_spec->lr.ctx_raw)) { 167 selinux_log 168 (SELINUX_ERROR, 169 "%s: Multiple different specifications for %s (%s and %s).\n", 170 path, curr_spec->regex_str, 171 spec_arr[jj].lr.ctx_raw, 172 curr_spec->lr.ctx_raw); 173 } else { 174 selinux_log 175 (SELINUX_ERROR, 176 "%s: Multiple same specifications for %s.\n", 177 path, curr_spec->regex_str); 178 } 179 } 180 } 181 } 182 return rc; 183 } 184 185 /* Determine if the regular expression specification has any meta characters. */ 186 static void spec_hasMetaChars(struct spec *spec) 187 { 188 char *c; 189 size_t len; 190 char *end; 191 192 c = spec->regex_str; 193 len = strlen(spec->regex_str); 194 end = c + len; 195 196 spec->hasMetaChars = 0; 197 spec->prefix_len = len; 198 199 /* Look at each character in the RE specification string for a 200 * meta character. Return when any meta character reached. */ 201 while (c != end) { 202 switch (*c) { 203 case '.': 204 case '^': 205 case '$': 206 case '?': 207 case '*': 208 case '+': 209 case '|': 210 case '[': 211 case '(': 212 case '{': 213 spec->hasMetaChars = 1; 214 spec->prefix_len = c - spec->regex_str; 215 return; 216 case '\\': /* skip the next character */ 217 c++; 218 break; 219 default: 220 break; 221 222 } 223 c++; 224 } 225 return; 226 } 227 228 static int compile_regex(struct saved_data *data, spec_t *spec, const char **errbuf) 229 { 230 const char *tmperrbuf; 231 char *reg_buf, *anchored_regex, *cp; 232 stem_t *stem_arr = data->stem_arr; 233 size_t len; 234 int erroff; 235 236 if (spec->regcomp) 237 return 0; /* already done */ 238 239 data->ncomp++; /* how many compiled regexes required */ 240 241 /* Skip the fixed stem. */ 242 reg_buf = spec->regex_str; 243 if (spec->stem_id >= 0) 244 reg_buf += stem_arr[spec->stem_id].len; 245 246 /* Anchor the regular expression. */ 247 len = strlen(reg_buf); 248 cp = anchored_regex = (char *) malloc(len + 3); 249 if (!anchored_regex) 250 return -1; 251 /* Create ^...$ regexp. */ 252 *cp++ = '^'; 253 memcpy(cp, reg_buf, len); 254 cp += len; 255 *cp++ = '$'; 256 *cp = '\0'; 257 258 /* Compile the regular expression. */ 259 spec->regex = pcre_compile(anchored_regex, 0, &tmperrbuf, &erroff, NULL); 260 free(anchored_regex); 261 if (!spec->regex) { 262 if (errbuf) 263 *errbuf=tmperrbuf; 264 return -1; 265 } 266 267 spec->sd = pcre_study(spec->regex, 0, &tmperrbuf); 268 if (!spec->sd) { 269 if (errbuf) 270 *errbuf=tmperrbuf; 271 return -1; 272 } 273 274 /* Done. */ 275 spec->regcomp = 1; 276 277 return 0; 278 } 279 280 281 static int process_line(struct selabel_handle *rec, 282 const char *path, const char *prefix, 283 char *line_buf, int pass, unsigned lineno) 284 { 285 int items, len; 286 char buf1[BUFSIZ], buf2[BUFSIZ], buf3[BUFSIZ]; 287 char *buf_p, *regex = buf1, *type = buf2, *context = buf3; 288 struct saved_data *data = (struct saved_data *)rec->data; 289 spec_t *spec_arr = data->spec_arr; 290 unsigned int nspec = data->nspec; 291 292 len = strlen(line_buf); 293 if (line_buf[len - 1] == '\n') 294 line_buf[len - 1] = 0; 295 buf_p = line_buf; 296 while (isspace(*buf_p)) 297 buf_p++; 298 /* Skip comment lines and empty lines. */ 299 if (*buf_p == '#' || *buf_p == 0) 300 return 0; 301 items = sscanf(line_buf, "%255s %255s %255s", regex, type, context); 302 if (items < 2) { 303 selinux_log(SELINUX_WARNING, 304 "%s: line %d is missing fields, skipping\n", path, 305 lineno); 306 return 0; 307 } else if (items == 2) { 308 /* The type field is optional. */ 309 context = type; 310 type = NULL; 311 } 312 313 len = get_stem_from_spec(regex); 314 if (len && prefix && strncmp(prefix, regex, len)) { 315 /* Stem of regex does not match requested prefix, discard. */ 316 return 0; 317 } 318 319 if (pass == 1) { 320 /* On the second pass, process and store the specification in spec. */ 321 const char *errbuf = NULL; 322 spec_arr[nspec].stem_id = find_stem_from_spec(data, regex); 323 spec_arr[nspec].regex_str = strdup(regex); 324 if (!spec_arr[nspec].regex_str) { 325 selinux_log(SELINUX_WARNING, 326 "%s: out of memory at line %d on regex %s\n", 327 path, lineno, regex); 328 return -1; 329 330 } 331 if (rec->validating && compile_regex(data, &spec_arr[nspec], &errbuf)) { 332 selinux_log(SELINUX_WARNING, 333 "%s: line %d has invalid regex %s: %s\n", 334 path, lineno, regex, 335 (errbuf ? errbuf : "out of memory")); 336 } 337 338 /* Convert the type string to a mode format */ 339 spec_arr[nspec].mode = 0; 340 if (!type) 341 goto skip_type; 342 spec_arr[nspec].type_str = strdup(type); 343 len = strlen(type); 344 if (type[0] != '-' || len != 2) { 345 selinux_log(SELINUX_WARNING, 346 "%s: line %d has invalid file type %s\n", 347 path, lineno, type); 348 return 0; 349 } 350 switch (type[1]) { 351 case 'b': 352 spec_arr[nspec].mode = S_IFBLK; 353 break; 354 case 'c': 355 spec_arr[nspec].mode = S_IFCHR; 356 break; 357 case 'd': 358 spec_arr[nspec].mode = S_IFDIR; 359 break; 360 case 'p': 361 spec_arr[nspec].mode = S_IFIFO; 362 break; 363 case 'l': 364 spec_arr[nspec].mode = S_IFLNK; 365 break; 366 case 's': 367 spec_arr[nspec].mode = S_IFSOCK; 368 break; 369 case '-': 370 spec_arr[nspec].mode = S_IFREG; 371 break; 372 default: 373 selinux_log(SELINUX_WARNING, 374 "%s: line %d has invalid file type %s\n", 375 path, lineno, type); 376 return 0; 377 } 378 379 skip_type: 380 spec_arr[nspec].lr.ctx_raw = strdup(context); 381 382 if (strcmp(context, "<<none>>") && rec->validating) { 383 if (selabel_validate(rec, &spec_arr[nspec].lr) < 0) { 384 selinux_log(SELINUX_WARNING, 385 "%s: line %d has invalid context %s\n", 386 path, lineno, spec_arr[nspec].lr.ctx_raw); 387 } 388 } 389 390 /* Determine if specification has 391 * any meta characters in the RE */ 392 spec_hasMetaChars(&spec_arr[nspec]); 393 } 394 395 data->nspec = ++nspec; 396 return 0; 397 } 398 399 static int init(struct selabel_handle *rec, const struct selinux_opt *opts, 400 unsigned n) 401 { 402 struct saved_data *data = (struct saved_data *)rec->data; 403 const char *path = NULL; 404 const char *prefix = NULL; 405 FILE *fp; 406 FILE *localfp = NULL; 407 FILE *homedirfp = NULL; 408 char local_path[PATH_MAX + 1]; 409 char homedir_path[PATH_MAX + 1]; 410 char line_buf[BUFSIZ]; 411 unsigned int lineno, pass, i, j, maxnspec; 412 spec_t *spec_copy = NULL; 413 int status = -1, baseonly = 0; 414 struct stat sb; 415 416 /* Process arguments */ 417 while (n--) 418 switch(opts[n].type) { 419 case SELABEL_OPT_PATH: 420 path = opts[n].value; 421 break; 422 case SELABEL_OPT_SUBSET: 423 prefix = opts[n].value; 424 break; 425 case SELABEL_OPT_BASEONLY: 426 baseonly = !!opts[n].value; 427 break; 428 } 429 430 /* Open the specification file. */ 431 if ((fp = fopen(path, "r")) == NULL) 432 return -1; 433 434 if (fstat(fileno(fp), &sb) < 0) 435 return -1; 436 if (!S_ISREG(sb.st_mode)) { 437 errno = EINVAL; 438 return -1; 439 } 440 441 if (!baseonly) { 442 snprintf(homedir_path, sizeof(homedir_path), "%s.homedirs", 443 path); 444 homedirfp = fopen(homedir_path, "r"); 445 446 snprintf(local_path, sizeof(local_path), "%s.local", path); 447 localfp = fopen(local_path, "r"); 448 } 449 450 /* 451 * Perform two passes over the specification file. 452 * The first pass counts the number of specifications and 453 * performs simple validation of the input. At the end 454 * of the first pass, the spec array is allocated. 455 * The second pass performs detailed validation of the input 456 * and fills in the spec array. 457 */ 458 maxnspec = UINT_MAX / sizeof(spec_t); 459 for (pass = 0; pass < 2; pass++) { 460 lineno = 0; 461 data->nspec = 0; 462 data->ncomp = 0; 463 while (fgets(line_buf, sizeof line_buf - 1, fp) 464 && data->nspec < maxnspec) { 465 if (process_line(rec, path, prefix, line_buf, 466 pass, ++lineno) != 0) 467 goto finish; 468 } 469 if (pass == 1) { 470 status = nodups_specs(data, path); 471 if (status) 472 goto finish; 473 } 474 lineno = 0; 475 if (homedirfp) 476 while (fgets(line_buf, sizeof line_buf - 1, homedirfp) 477 && data->nspec < maxnspec) { 478 if (process_line 479 (rec, homedir_path, prefix, 480 line_buf, pass, ++lineno) != 0) 481 goto finish; 482 } 483 484 lineno = 0; 485 if (localfp) 486 while (fgets(line_buf, sizeof line_buf - 1, localfp) 487 && data->nspec < maxnspec) { 488 if (process_line 489 (rec, local_path, prefix, line_buf, 490 pass, ++lineno) != 0) 491 goto finish; 492 } 493 494 if (pass == 0) { 495 if (data->nspec == 0) { 496 status = 0; 497 goto finish; 498 } 499 if (NULL == (data->spec_arr = 500 (spec_t *) malloc(sizeof(spec_t) * data->nspec))) 501 goto finish; 502 memset(data->spec_arr, 0, sizeof(spec_t)*data->nspec); 503 maxnspec = data->nspec; 504 rewind(fp); 505 if (homedirfp) 506 rewind(homedirfp); 507 if (localfp) 508 rewind(localfp); 509 } 510 } 511 512 /* Move exact pathname specifications to the end. */ 513 spec_copy = (spec_t *) malloc(sizeof(spec_t) * data->nspec); 514 if (!spec_copy) 515 goto finish; 516 j = 0; 517 for (i = 0; i < data->nspec; i++) 518 if (data->spec_arr[i].hasMetaChars) 519 memcpy(&spec_copy[j++], 520 &data->spec_arr[i], sizeof(spec_t)); 521 for (i = 0; i < data->nspec; i++) 522 if (!data->spec_arr[i].hasMetaChars) 523 memcpy(&spec_copy[j++], 524 &data->spec_arr[i], sizeof(spec_t)); 525 free(data->spec_arr); 526 data->spec_arr = spec_copy; 527 528 status = 0; 529 finish: 530 fclose(fp); 531 if (data->spec_arr != spec_copy) 532 free(data->spec_arr); 533 if (homedirfp) 534 fclose(homedirfp); 535 if (localfp) 536 fclose(localfp); 537 return status; 538 } 539 540 /* 541 * Backend interface routines 542 */ 543 static void closef(struct selabel_handle *rec) 544 { 545 struct saved_data *data = (struct saved_data *)rec->data; 546 struct spec *spec; 547 struct stem *stem; 548 unsigned int i; 549 550 for (i = 0; i < data->nspec; i++) { 551 spec = &data->spec_arr[i]; 552 free(spec->regex_str); 553 free(spec->type_str); 554 free(spec->lr.ctx_raw); 555 free(spec->lr.ctx_trans); 556 if (spec->regcomp) { 557 pcre_free(spec->regex); 558 pcre_free_study(spec->sd); 559 } 560 } 561 562 for (i = 0; i < (unsigned int)data->num_stems; i++) { 563 stem = &data->stem_arr[i]; 564 free(stem->buf); 565 } 566 567 if (data->spec_arr) 568 free(data->spec_arr); 569 if (data->stem_arr) 570 free(data->stem_arr); 571 572 free(data); 573 } 574 575 static spec_t *lookup_common(struct selabel_handle *rec, 576 const char *key, 577 int type, 578 bool partial) 579 { 580 struct saved_data *data = (struct saved_data *)rec->data; 581 spec_t *spec_arr = data->spec_arr; 582 int i, rc, file_stem, pcre_options = 0; 583 mode_t mode = (mode_t)type; 584 const char *buf; 585 spec_t *ret = NULL; 586 char *clean_key = NULL; 587 const char *prev_slash, *next_slash; 588 unsigned int sofar = 0; 589 590 if (!data->nspec) { 591 errno = ENOENT; 592 goto finish; 593 } 594 595 /* Remove duplicate slashes */ 596 if ((next_slash = strstr(key, "//"))) { 597 clean_key = (char *) malloc(strlen(key) + 1); 598 if (!clean_key) 599 goto finish; 600 prev_slash = key; 601 while (next_slash) { 602 memcpy(clean_key + sofar, prev_slash, next_slash - prev_slash); 603 sofar += next_slash - prev_slash; 604 prev_slash = next_slash + 1; 605 next_slash = strstr(prev_slash, "//"); 606 } 607 strcpy(clean_key + sofar, prev_slash); 608 key = clean_key; 609 } 610 611 buf = key; 612 file_stem = find_stem_from_file(data, &buf); 613 mode &= S_IFMT; 614 615 if (partial) 616 pcre_options |= PCRE_PARTIAL_SOFT; 617 618 /* 619 * Check for matching specifications in reverse order, so that 620 * the last matching specification is used. 621 */ 622 for (i = data->nspec - 1; i >= 0; i--) { 623 /* if the spec in question matches no stem or has the same 624 * stem as the file AND if the spec in question has no mode 625 * specified or if the mode matches the file mode then we do 626 * a regex check */ 627 if ((spec_arr[i].stem_id == -1 628 || spec_arr[i].stem_id == file_stem) 629 && (!mode || !spec_arr[i].mode 630 || mode == spec_arr[i].mode)) { 631 if (compile_regex(data, &spec_arr[i], NULL) < 0) 632 goto finish; 633 if (spec_arr[i].stem_id == -1) 634 rc = pcre_exec(spec_arr[i].regex, spec_arr[i].sd, key, strlen(key), 0, pcre_options, NULL, 0); 635 else 636 rc = pcre_exec(spec_arr[i].regex, spec_arr[i].sd, buf, strlen(buf), 0, pcre_options, NULL, 0); 637 638 if (rc == 0) { 639 spec_arr[i].matches++; 640 break; 641 } else if (partial && rc == PCRE_ERROR_PARTIAL) 642 break; 643 644 if (rc == PCRE_ERROR_NOMATCH) 645 continue; 646 /* else it's an error */ 647 goto finish; 648 } 649 } 650 651 if (i < 0 || strcmp(spec_arr[i].lr.ctx_raw, "<<none>>") == 0) { 652 /* No matching specification. */ 653 errno = ENOENT; 654 goto finish; 655 } 656 657 ret = &spec_arr[i]; 658 659 finish: 660 free(clean_key); 661 return ret; 662 } 663 664 static struct selabel_lookup_rec *lookup(struct selabel_handle *rec, 665 const char *key, int type) 666 { 667 spec_t *spec; 668 spec = lookup_common(rec, key, type, false); 669 if (spec) 670 return &spec->lr; 671 return NULL; 672 } 673 674 static bool partial_match(struct selabel_handle *rec, const char *key) 675 { 676 return lookup_common(rec, key, 0, true) ? true : false; 677 } 678 679 static struct selabel_lookup_rec *lookup_best_match(struct selabel_handle *rec, 680 const char *key, 681 const char **aliases, 682 int type) 683 { 684 size_t n, i; 685 int best = -1; 686 spec_t **specs; 687 size_t prefix_len = 0; 688 struct selabel_lookup_rec *lr = NULL; 689 690 if (!aliases || !aliases[0]) 691 return lookup(rec, key, type); 692 693 for (n = 0; aliases[n]; n++) 694 ; 695 696 specs = calloc(n+1, sizeof(spec_t *)); 697 if (!specs) 698 return NULL; 699 specs[0] = lookup_common(rec, key, type, false); 700 if (specs[0]) { 701 if (!specs[0]->hasMetaChars) { 702 /* exact match on key */ 703 lr = &specs[0]->lr; 704 goto out; 705 } 706 best = 0; 707 prefix_len = specs[0]->prefix_len; 708 } 709 for (i = 1; i <= n; i++) { 710 specs[i] = lookup_common(rec, aliases[i-1], type, false); 711 if (specs[i]) { 712 if (!specs[i]->hasMetaChars) { 713 /* exact match on alias */ 714 lr = &specs[i]->lr; 715 goto out; 716 } 717 if (specs[i]->prefix_len > prefix_len) { 718 best = i; 719 prefix_len = specs[i]->prefix_len; 720 } 721 } 722 } 723 724 if (best >= 0) { 725 /* longest fixed prefix match on key or alias */ 726 lr = &specs[best]->lr; 727 } 728 729 out: 730 free(specs); 731 return lr; 732 } 733 734 static void stats(struct selabel_handle *rec) 735 { 736 struct saved_data *data = (struct saved_data *)rec->data; 737 unsigned int i, nspec = data->nspec; 738 spec_t *spec_arr = data->spec_arr; 739 740 for (i = 0; i < nspec; i++) { 741 if (spec_arr[i].matches == 0) { 742 if (spec_arr[i].type_str) { 743 selinux_log(SELINUX_WARNING, 744 "Warning! No matches for (%s, %s, %s)\n", 745 spec_arr[i].regex_str, 746 spec_arr[i].type_str, 747 spec_arr[i].lr.ctx_raw); 748 } else { 749 selinux_log(SELINUX_WARNING, 750 "Warning! No matches for (%s, %s)\n", 751 spec_arr[i].regex_str, 752 spec_arr[i].lr.ctx_raw); 753 } 754 } 755 } 756 } 757 758 int selabel_file_init(struct selabel_handle *rec, const struct selinux_opt *opts, 759 unsigned nopts) 760 { 761 struct saved_data *data; 762 763 data = (struct saved_data *)malloc(sizeof(*data)); 764 if (!data) 765 return -1; 766 memset(data, 0, sizeof(*data)); 767 768 rec->data = data; 769 rec->func_close = &closef; 770 rec->func_stats = &stats; 771 rec->func_lookup = &lookup; 772 rec->func_partial_match = &partial_match; 773 rec->func_lookup_best_match = &lookup_best_match; 774 775 return init(rec, opts, nopts); 776 } 777