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