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 /* Determine if specification has 379 * any meta characters in the RE */ 380 spec_hasMetaChars(&spec_arr[nspec]); 381 } 382 383 data->nspec = ++nspec; 384 return 0; 385 } 386 387 static int init(struct selabel_handle *rec, const struct selinux_opt *opts, 388 unsigned n) 389 { 390 struct saved_data *data = (struct saved_data *)rec->data; 391 const char *path = NULL; 392 const char *prefix = NULL; 393 FILE *fp; 394 FILE *localfp = NULL; 395 FILE *homedirfp = NULL; 396 char local_path[PATH_MAX + 1]; 397 char homedir_path[PATH_MAX + 1]; 398 char line_buf[BUFSIZ]; 399 unsigned int lineno, pass, i, j, maxnspec; 400 spec_t *spec_copy = NULL; 401 int status = -1, baseonly = 0; 402 struct stat sb; 403 404 /* Process arguments */ 405 while (n--) 406 switch(opts[n].type) { 407 case SELABEL_OPT_PATH: 408 path = opts[n].value; 409 break; 410 case SELABEL_OPT_SUBSET: 411 prefix = opts[n].value; 412 break; 413 case SELABEL_OPT_BASEONLY: 414 baseonly = !!opts[n].value; 415 break; 416 } 417 418 /* Open the specification file. */ 419 if ((fp = fopen(path, "r")) == NULL) 420 return -1; 421 422 if (fstat(fileno(fp), &sb) < 0) 423 return -1; 424 if (!S_ISREG(sb.st_mode)) { 425 errno = EINVAL; 426 return -1; 427 } 428 429 if (!baseonly) { 430 snprintf(homedir_path, sizeof(homedir_path), "%s.homedirs", 431 path); 432 homedirfp = fopen(homedir_path, "r"); 433 434 snprintf(local_path, sizeof(local_path), "%s.local", path); 435 localfp = fopen(local_path, "r"); 436 } 437 438 /* 439 * Perform two passes over the specification file. 440 * The first pass counts the number of specifications and 441 * performs simple validation of the input. At the end 442 * of the first pass, the spec array is allocated. 443 * The second pass performs detailed validation of the input 444 * and fills in the spec array. 445 */ 446 maxnspec = UINT_MAX / sizeof(spec_t); 447 for (pass = 0; pass < 2; pass++) { 448 lineno = 0; 449 data->nspec = 0; 450 data->ncomp = 0; 451 while (fgets(line_buf, sizeof line_buf - 1, fp) 452 && data->nspec < maxnspec) { 453 if (process_line(rec, path, prefix, line_buf, 454 pass, ++lineno) != 0) 455 goto finish; 456 } 457 if (pass == 1) { 458 status = nodups_specs(data, path); 459 if (status) 460 goto finish; 461 } 462 lineno = 0; 463 if (homedirfp) 464 while (fgets(line_buf, sizeof line_buf - 1, homedirfp) 465 && data->nspec < maxnspec) { 466 if (process_line 467 (rec, homedir_path, prefix, 468 line_buf, pass, ++lineno) != 0) 469 goto finish; 470 } 471 472 lineno = 0; 473 if (localfp) 474 while (fgets(line_buf, sizeof line_buf - 1, localfp) 475 && data->nspec < maxnspec) { 476 if (process_line 477 (rec, local_path, prefix, line_buf, 478 pass, ++lineno) != 0) 479 goto finish; 480 } 481 482 if (pass == 0) { 483 if (data->nspec == 0) { 484 status = 0; 485 goto finish; 486 } 487 if (NULL == (data->spec_arr = 488 malloc(sizeof(spec_t) * data->nspec))) 489 goto finish; 490 memset(data->spec_arr, 0, sizeof(spec_t)*data->nspec); 491 maxnspec = data->nspec; 492 rewind(fp); 493 if (homedirfp) 494 rewind(homedirfp); 495 if (localfp) 496 rewind(localfp); 497 } 498 } 499 500 /* Move exact pathname specifications to the end. */ 501 spec_copy = malloc(sizeof(spec_t) * data->nspec); 502 if (!spec_copy) 503 goto finish; 504 j = 0; 505 for (i = 0; i < data->nspec; i++) 506 if (data->spec_arr[i].hasMetaChars) 507 memcpy(&spec_copy[j++], 508 &data->spec_arr[i], sizeof(spec_t)); 509 for (i = 0; i < data->nspec; i++) 510 if (!data->spec_arr[i].hasMetaChars) 511 memcpy(&spec_copy[j++], 512 &data->spec_arr[i], sizeof(spec_t)); 513 free(data->spec_arr); 514 data->spec_arr = spec_copy; 515 516 status = 0; 517 finish: 518 fclose(fp); 519 if (data->spec_arr != spec_copy) 520 free(data->spec_arr); 521 if (homedirfp) 522 fclose(homedirfp); 523 if (localfp) 524 fclose(localfp); 525 return status; 526 } 527 528 /* 529 * Backend interface routines 530 */ 531 static void closef(struct selabel_handle *rec) 532 { 533 struct saved_data *data = (struct saved_data *)rec->data; 534 struct spec *spec; 535 struct stem *stem; 536 unsigned int i; 537 538 for (i = 0; i < data->nspec; i++) { 539 spec = &data->spec_arr[i]; 540 free(spec->regex_str); 541 free(spec->type_str); 542 free(spec->lr.ctx_raw); 543 free(spec->lr.ctx_trans); 544 if (spec->regcomp) 545 regfree(&spec->regex); 546 } 547 548 for (i = 0; i < (unsigned int)data->num_stems; i++) { 549 stem = &data->stem_arr[i]; 550 free(stem->buf); 551 } 552 553 if (data->spec_arr) 554 free(data->spec_arr); 555 if (data->stem_arr) 556 free(data->stem_arr); 557 558 free(data); 559 } 560 561 static struct selabel_lookup_rec *lookup(struct selabel_handle *rec, 562 const char *key, int type) 563 { 564 struct saved_data *data = (struct saved_data *)rec->data; 565 spec_t *spec_arr = data->spec_arr; 566 int i, rc, file_stem; 567 mode_t mode = (mode_t)type; 568 const char *buf; 569 struct selabel_lookup_rec *ret = NULL; 570 char *clean_key = NULL; 571 const char *prev_slash, *next_slash; 572 unsigned int sofar = 0; 573 574 if (!data->nspec) { 575 errno = ENOENT; 576 goto finish; 577 } 578 579 /* Remove duplicate slashes */ 580 if ((next_slash = strstr(key, "//"))) { 581 clean_key = malloc(strlen(key) + 1); 582 if (!clean_key) 583 goto finish; 584 prev_slash = key; 585 while (next_slash) { 586 memcpy(clean_key + sofar, prev_slash, next_slash - prev_slash); 587 sofar += next_slash - prev_slash; 588 prev_slash = next_slash + 1; 589 next_slash = strstr(prev_slash, "//"); 590 } 591 strcpy(clean_key + sofar, prev_slash); 592 key = clean_key; 593 } 594 595 buf = key; 596 file_stem = find_stem_from_file(data, &buf); 597 mode &= S_IFMT; 598 599 /* 600 * Check for matching specifications in reverse order, so that 601 * the last matching specification is used. 602 */ 603 for (i = data->nspec - 1; i >= 0; i--) { 604 /* if the spec in question matches no stem or has the same 605 * stem as the file AND if the spec in question has no mode 606 * specified or if the mode matches the file mode then we do 607 * a regex check */ 608 if ((spec_arr[i].stem_id == -1 609 || spec_arr[i].stem_id == file_stem) 610 && (!mode || !spec_arr[i].mode 611 || mode == spec_arr[i].mode)) { 612 if (compile_regex(data, &spec_arr[i], NULL) < 0) 613 goto finish; 614 if (spec_arr[i].stem_id == -1) 615 rc = regexec(&spec_arr[i].regex, key, 0, 0, 0); 616 else 617 rc = regexec(&spec_arr[i].regex, buf, 0, 0, 0); 618 619 if (rc == 0) { 620 spec_arr[i].matches++; 621 break; 622 } 623 if (rc == REG_NOMATCH) 624 continue; 625 /* else it's an error */ 626 goto finish; 627 } 628 } 629 630 if (i < 0 || strcmp(spec_arr[i].lr.ctx_raw, "<<none>>") == 0) { 631 /* No matching specification. */ 632 errno = ENOENT; 633 goto finish; 634 } 635 636 ret = &spec_arr[i].lr; 637 638 finish: 639 free(clean_key); 640 return ret; 641 } 642 643 static void stats(struct selabel_handle *rec) 644 { 645 struct saved_data *data = (struct saved_data *)rec->data; 646 unsigned int i, nspec = data->nspec; 647 spec_t *spec_arr = data->spec_arr; 648 649 for (i = 0; i < nspec; i++) { 650 if (spec_arr[i].matches == 0) { 651 if (spec_arr[i].type_str) { 652 selinux_log(SELINUX_WARNING, 653 "Warning! No matches for (%s, %s, %s)\n", 654 spec_arr[i].regex_str, 655 spec_arr[i].type_str, 656 spec_arr[i].lr.ctx_raw); 657 } else { 658 selinux_log(SELINUX_WARNING, 659 "Warning! No matches for (%s, %s)\n", 660 spec_arr[i].regex_str, 661 spec_arr[i].lr.ctx_raw); 662 } 663 } 664 } 665 } 666 667 int selabel_file_init(struct selabel_handle *rec, const struct selinux_opt *opts, 668 unsigned nopts) 669 { 670 struct saved_data *data; 671 672 data = (struct saved_data *)malloc(sizeof(*data)); 673 if (!data) 674 return -1; 675 memset(data, 0, sizeof(*data)); 676 677 rec->data = data; 678 rec->func_close = &closef; 679 rec->func_stats = &stats; 680 rec->func_lookup = &lookup; 681 682 return init(rec, opts, nopts); 683 } 684