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