label_file.c revision a4af847dc6f52688a25bb0323ff3b84b13dded67
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 <regex.h> 20#include "callbacks.h" 21#include "label_internal.h" 22 23/* 24 * Internals, mostly moved over from matchpathcon.c 25 */ 26 27/* A file security context specification. */ 28typedef struct spec { 29 struct selabel_lookup_rec lr; /* holds contexts for lookup result */ 30 char *regex_str; /* regular expession string for diagnostics */ 31 char *type_str; /* type string for diagnostic messages */ 32 regex_t regex; /* compiled regular expression */ 33 char regcomp; /* regex_str has been compiled to regex */ 34 mode_t mode; /* mode format value */ 35 int matches; /* number of matching pathnames */ 36 int hasMetaChars; /* regular expression has meta-chars */ 37 int stem_id; /* indicates which stem-compression item */ 38} spec_t; 39 40/* A regular expression stem */ 41typedef struct stem { 42 char *buf; 43 int len; 44} stem_t; 45 46/* Our stored configuration */ 47struct 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 */ 66static 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 */ 82static 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). */ 94static 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 = 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 = 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. */ 129static 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 */ 149static 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 COMPAT_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 COMPAT_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. */ 186static void spec_hasMetaChars(struct spec *spec) 187{ 188 char *c; 189 int 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 198 /* Look at each character in the RE specification string for a 199 * meta character. Return when any meta character reached. */ 200 while (c != end) { 201 switch (*c) { 202 case '.': 203 case '^': 204 case '$': 205 case '?': 206 case '*': 207 case '+': 208 case '|': 209 case '[': 210 case '(': 211 case '{': 212 spec->hasMetaChars = 1; 213 return; 214 case '\\': /* skip the next character */ 215 c++; 216 break; 217 default: 218 break; 219 220 } 221 c++; 222 } 223 return; 224} 225 226static int compile_regex(struct saved_data *data, spec_t *spec, char **errbuf) 227{ 228 char *reg_buf, *anchored_regex, *cp; 229 stem_t *stem_arr = data->stem_arr; 230 size_t len; 231 int regerr; 232 233 if (spec->regcomp) 234 return 0; /* already done */ 235 236 data->ncomp++; /* how many compiled regexes required */ 237 238 /* Skip the fixed stem. */ 239 reg_buf = spec->regex_str; 240 if (spec->stem_id >= 0) 241 reg_buf += stem_arr[spec->stem_id].len; 242 243 /* Anchor the regular expression. */ 244 len = strlen(reg_buf); 245 cp = anchored_regex = malloc(len + 3); 246 if (!anchored_regex) 247 return -1; 248 /* Create ^...$ regexp. */ 249 *cp++ = '^'; 250 cp = mempcpy(cp, reg_buf, len); 251 *cp++ = '$'; 252 *cp = '\0'; 253 254 /* Compile the regular expression. */ 255 regerr = regcomp(&spec->regex, anchored_regex, 256 REG_EXTENDED | REG_NOSUB); 257 if (regerr != 0) { 258 size_t errsz = 0; 259 errsz = regerror(regerr, &spec->regex, NULL, 0); 260 if (errsz && errbuf) 261 *errbuf = malloc(errsz); 262 if (errbuf && *errbuf) 263 (void)regerror(regerr, &spec->regex, 264 *errbuf, errsz); 265 266 free(anchored_regex); 267 return -1; 268 } 269 free(anchored_regex); 270 271 /* Done. */ 272 spec->regcomp = 1; 273 274 return 0; 275} 276 277 278static int process_line(struct selabel_handle *rec, 279 const char *path, const char *prefix, 280 char *line_buf, int pass, unsigned lineno) 281{ 282 int items, len; 283 char *buf_p, *regex, *type, *context; 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, "%as %as %as", ®ex, &type, &context); 298 if (items < 2) { 299 COMPAT_LOG(SELINUX_WARNING, 300 "%s: line %d is missing fields, skipping\n", path, 301 lineno); 302 if (items == 1) 303 free(regex); 304 return 0; 305 } else if (items == 2) { 306 /* The type field is optional. */ 307 free(context); 308 context = type; 309 type = 0; 310 } 311 312 len = get_stem_from_spec(regex); 313 if (len && prefix && strncmp(prefix, regex, len)) { 314 /* Stem of regex does not match requested prefix, discard. */ 315 free(regex); 316 free(type); 317 free(context); 318 return 0; 319 } 320 321 if (pass == 1) { 322 /* On the second pass, process and store the specification in spec. */ 323 char *errbuf = NULL; 324 spec_arr[nspec].stem_id = find_stem_from_spec(data, regex); 325 spec_arr[nspec].regex_str = regex; 326 if (rec->validating && compile_regex(data, &spec_arr[nspec], &errbuf)) { 327 COMPAT_LOG(SELINUX_WARNING, 328 "%s: line %d has invalid regex %s: %s\n", 329 path, lineno, regex, 330 (errbuf ? errbuf : "out of memory")); 331 } 332 333 /* Convert the type string to a mode format */ 334 spec_arr[nspec].type_str = type; 335 spec_arr[nspec].mode = 0; 336 if (!type) 337 goto skip_type; 338 len = strlen(type); 339 if (type[0] != '-' || len != 2) { 340 COMPAT_LOG(SELINUX_WARNING, 341 "%s: line %d has invalid file type %s\n", 342 path, lineno, type); 343 return 0; 344 } 345 switch (type[1]) { 346 case 'b': 347 spec_arr[nspec].mode = S_IFBLK; 348 break; 349 case 'c': 350 spec_arr[nspec].mode = S_IFCHR; 351 break; 352 case 'd': 353 spec_arr[nspec].mode = S_IFDIR; 354 break; 355 case 'p': 356 spec_arr[nspec].mode = S_IFIFO; 357 break; 358 case 'l': 359 spec_arr[nspec].mode = S_IFLNK; 360 break; 361 case 's': 362 spec_arr[nspec].mode = S_IFSOCK; 363 break; 364 case '-': 365 spec_arr[nspec].mode = S_IFREG; 366 break; 367 default: 368 COMPAT_LOG(SELINUX_WARNING, 369 "%s: line %d has invalid file type %s\n", 370 path, lineno, type); 371 return 0; 372 } 373 374 skip_type: 375 spec_arr[nspec].lr.ctx_raw = context; 376 377 /* Determine if specification has 378 * any meta characters in the RE */ 379 spec_hasMetaChars(&spec_arr[nspec]); 380 381 if (strcmp(context, "<<none>>") && rec->validating) 382 compat_validate(rec, &spec_arr[nspec].lr, path, lineno); 383 } 384 385 data->nspec = ++nspec; 386 if (pass == 0) { 387 free(regex); 388 if (type) 389 free(type); 390 free(context); 391 } 392 return 0; 393} 394 395static int init(struct selabel_handle *rec, 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 = NULL; 407 size_t line_len = 0; 408 unsigned int lineno, pass, i, j, maxnspec; 409 spec_t *spec_copy = NULL; 410 int status = -1, baseonly = 0; 411 struct stat sb; 412 413 /* Process arguments */ 414 while (n--) 415 switch(opts[n].type) { 416 case SELABEL_OPT_PATH: 417 path = opts[n].value; 418 break; 419 case SELABEL_OPT_SUBSET: 420 prefix = opts[n].value; 421 break; 422 case SELABEL_OPT_BASEONLY: 423 baseonly = !!opts[n].value; 424 break; 425 } 426 427 /* Open the specification file. */ 428 if (!path) 429 path = selinux_file_context_path(); 430 if ((fp = fopen(path, "r")) == NULL) 431 return -1; 432 __fsetlocking(fp, FSETLOCKING_BYCALLER); 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 if (homedirfp != NULL) 446 __fsetlocking(homedirfp, FSETLOCKING_BYCALLER); 447 448 snprintf(local_path, sizeof(local_path), "%s.local", path); 449 localfp = fopen(local_path, "r"); 450 if (localfp != NULL) 451 __fsetlocking(localfp, FSETLOCKING_BYCALLER); 452 } 453 454 /* 455 * Perform two passes over the specification file. 456 * The first pass counts the number of specifications and 457 * performs simple validation of the input. At the end 458 * of the first pass, the spec array is allocated. 459 * The second pass performs detailed validation of the input 460 * and fills in the spec array. 461 */ 462 maxnspec = UINT_MAX / sizeof(spec_t); 463 for (pass = 0; pass < 2; pass++) { 464 lineno = 0; 465 data->nspec = 0; 466 data->ncomp = 0; 467 while (getline(&line_buf, &line_len, fp) > 0 468 && data->nspec < maxnspec) { 469 if (process_line(rec, path, prefix, line_buf, 470 pass, ++lineno) != 0) 471 goto finish; 472 } 473 if (pass == 1) { 474 status = nodups_specs(data, path); 475 if (status) 476 goto finish; 477 } 478 lineno = 0; 479 if (homedirfp) 480 while (getline(&line_buf, &line_len, homedirfp) > 0 481 && data->nspec < maxnspec) { 482 if (process_line 483 (rec, homedir_path, prefix, 484 line_buf, pass, ++lineno) != 0) 485 goto finish; 486 } 487 488 lineno = 0; 489 if (localfp) 490 while (getline(&line_buf, &line_len, localfp) > 0 491 && data->nspec < maxnspec) { 492 if (process_line 493 (rec, local_path, prefix, line_buf, 494 pass, ++lineno) != 0) 495 goto finish; 496 } 497 498 if (pass == 0) { 499 if (data->nspec == 0) { 500 status = 0; 501 goto finish; 502 } 503 if (NULL == (data->spec_arr = 504 malloc(sizeof(spec_t) * data->nspec))) 505 goto finish; 506 memset(data->spec_arr, 0, sizeof(spec_t)*data->nspec); 507 maxnspec = data->nspec; 508 rewind(fp); 509 if (homedirfp) 510 rewind(homedirfp); 511 if (localfp) 512 rewind(localfp); 513 } 514 } 515 free(line_buf); 516 517 /* Move exact pathname specifications to the end. */ 518 spec_copy = malloc(sizeof(spec_t) * data->nspec); 519 if (!spec_copy) 520 goto finish; 521 j = 0; 522 for (i = 0; i < data->nspec; i++) 523 if (data->spec_arr[i].hasMetaChars) 524 memcpy(&spec_copy[j++], 525 &data->spec_arr[i], sizeof(spec_t)); 526 for (i = 0; i < data->nspec; i++) 527 if (!data->spec_arr[i].hasMetaChars) 528 memcpy(&spec_copy[j++], 529 &data->spec_arr[i], sizeof(spec_t)); 530 free(data->spec_arr); 531 data->spec_arr = spec_copy; 532 533 status = 0; 534finish: 535 fclose(fp); 536 if (data->spec_arr != spec_copy) 537 free(data->spec_arr); 538 if (homedirfp) 539 fclose(homedirfp); 540 if (localfp) 541 fclose(localfp); 542 return status; 543} 544 545/* 546 * Backend interface routines 547 */ 548static void close(struct selabel_handle *rec) 549{ 550 struct saved_data *data = (struct saved_data *)rec->data; 551 struct spec *spec; 552 struct stem *stem; 553 unsigned int i; 554 555 for (i = 0; i < data->nspec; i++) { 556 spec = &data->spec_arr[i]; 557 free(spec->regex_str); 558 free(spec->type_str); 559 free(spec->lr.ctx_raw); 560 free(spec->lr.ctx_trans); 561 regfree(&spec->regex); 562 } 563 564 for (i = 0; i < (unsigned int)data->num_stems; i++) { 565 stem = &data->stem_arr[i]; 566 free(stem->buf); 567 } 568 569 if (data->spec_arr) 570 free(data->spec_arr); 571 if (data->stem_arr) 572 free(data->stem_arr); 573 574 free(data); 575} 576 577static struct selabel_lookup_rec *lookup(struct selabel_handle *rec, 578 const char *key, int type) 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; 583 mode_t mode = (mode_t)type; 584 const char *buf = key; 585 586 if (!data->nspec) { 587 errno = ENOENT; 588 return NULL; 589 } 590 591 file_stem = find_stem_from_file(data, &buf); 592 mode &= S_IFMT; 593 594 /* 595 * Check for matching specifications in reverse order, so that 596 * the last matching specification is used. 597 */ 598 for (i = data->nspec - 1; i >= 0; i--) { 599 /* if the spec in question matches no stem or has the same 600 * stem as the file AND if the spec in question has no mode 601 * specified or if the mode matches the file mode then we do 602 * a regex check */ 603 if ((spec_arr[i].stem_id == -1 604 || spec_arr[i].stem_id == file_stem) 605 && (!mode || !spec_arr[i].mode 606 || mode == spec_arr[i].mode)) { 607 if (compile_regex(data, &spec_arr[i], NULL) < 0) 608 return NULL; 609 if (spec_arr[i].stem_id == -1) 610 rc = regexec(&spec_arr[i].regex, key, 0, 0, 0); 611 else 612 rc = regexec(&spec_arr[i].regex, buf, 0, 0, 0); 613 614 if (rc == 0) { 615 spec_arr[i].matches++; 616 break; 617 } 618 if (rc == REG_NOMATCH) 619 continue; 620 /* else it's an error */ 621 return NULL; 622 } 623 } 624 625 if (i < 0 || strcmp(spec_arr[i].lr.ctx_raw, "<<none>>") == 0) { 626 /* No matching specification. */ 627 errno = ENOENT; 628 return NULL; 629 } 630 631 return &spec_arr[i].lr; 632} 633 634static void stats(struct selabel_handle *rec) 635{ 636 struct saved_data *data = (struct saved_data *)rec->data; 637 unsigned int i, nspec = data->nspec; 638 spec_t *spec_arr = data->spec_arr; 639 640 for (i = 0; i < nspec; i++) { 641 if (spec_arr[i].matches == 0) { 642 if (spec_arr[i].type_str) { 643 COMPAT_LOG(SELINUX_WARNING, 644 "Warning! No matches for (%s, %s, %s)\n", 645 spec_arr[i].regex_str, 646 spec_arr[i].type_str, 647 spec_arr[i].lr.ctx_raw); 648 } else { 649 COMPAT_LOG(SELINUX_WARNING, 650 "Warning! No matches for (%s, %s)\n", 651 spec_arr[i].regex_str, 652 spec_arr[i].lr.ctx_raw); 653 } 654 } 655 } 656} 657 658int selabel_file_init(struct selabel_handle *rec, struct selinux_opt *opts, 659 unsigned nopts) 660{ 661 struct saved_data *data; 662 663 data = (struct saved_data *)malloc(sizeof(*data)); 664 if (!data) 665 return -1; 666 memset(data, 0, sizeof(*data)); 667 668 rec->data = data; 669 rec->func_close = &close; 670 rec->func_stats = &stats; 671 rec->func_lookup = &lookup; 672 673 return init(rec, opts, nopts); 674} 675