1 #include <stdio.h> 2 #include <stdarg.h> 3 #include <ctype.h> 4 #include <stdio.h> 5 #include <stdlib.h> 6 #include <unistd.h> 7 #include <string.h> 8 #include <errno.h> 9 #include <stdint.h> 10 #include <search.h> 11 #include <stdbool.h> 12 #include <sepol/sepol.h> 13 #include <sepol/policydb/policydb.h> 14 #include <pcre.h> 15 16 #define TABLE_SIZE 1024 17 #define KVP_NUM_OF_RULES (sizeof(rules) / sizeof(key_map)) 18 #define log_set_verbose() do { logging_verbose = 1; log_info("Enabling verbose\n"); } while(0) 19 #define log_error(fmt, ...) log_msg(stderr, "Error: ", fmt, ##__VA_ARGS__) 20 #define log_warn(fmt, ...) log_msg(stderr, "Warning: ", fmt, ##__VA_ARGS__) 21 #define log_info(fmt, ...) if (logging_verbose ) { log_msg(stdout, "Info: ", fmt, ##__VA_ARGS__); } 22 23 /** 24 * Initializes an empty, static list. 25 */ 26 #define list_init(free_fn) { .head = NULL, .tail = NULL, .freefn = free_fn } 27 28 /** 29 * given an item in the list, finds the offset for the container 30 * it was stored in. 31 * 32 * @element The element from the list 33 * @type The container type ie what you allocated that has the list_element structure in it. 34 * @name The name of the field that is the list_element 35 * 36 */ 37 #define list_entry(element, type, name) \ 38 (type *)(((uint8_t *)element) - (uint8_t *)&(((type *)NULL)->name)) 39 40 /** 41 * Iterates over the list, do not free elements from the list when using this. 42 * @list The list head to walk 43 * @var The variable name for the cursor 44 */ 45 #define list_for_each(list, var) \ 46 for(var = (list)->head; var != NULL; var = var->next) 47 48 49 typedef struct hash_entry hash_entry; 50 typedef enum key_dir key_dir; 51 typedef enum data_type data_type; 52 typedef enum rule_map_switch rule_map_switch; 53 typedef enum map_match map_match; 54 typedef struct key_map key_map; 55 typedef struct kvp kvp; 56 typedef struct rule_map rule_map; 57 typedef struct policy_info policy_info; 58 typedef struct list_element list_element; 59 typedef struct list list; 60 typedef struct key_map_regex key_map_regex; 61 typedef struct file_info file_info; 62 63 enum map_match { 64 map_no_matches, 65 map_input_matched, 66 map_matched 67 }; 68 69 const char *map_match_str[] = { 70 "do not match", 71 "match on all inputs", 72 "match on everything" 73 }; 74 75 /** 76 * Whether or not the "key" from a key vaue pair is considered an 77 * input or an output. 78 */ 79 enum key_dir { 80 dir_in, dir_out 81 }; 82 83 struct list_element { 84 list_element *next; 85 }; 86 87 struct list { 88 list_element *head; 89 list_element *tail; 90 void (*freefn)(list_element *e); 91 }; 92 93 struct key_map_regex { 94 pcre *compiled; 95 pcre_extra *extra; 96 }; 97 98 /** 99 * The workhorse of the logic. This struct maps key value pairs to 100 * an associated set of meta data maintained in rule_map_new() 101 */ 102 struct key_map { 103 char *name; 104 key_dir dir; 105 char *data; 106 key_map_regex regex; 107 bool (*fn_validate)(char *value, char **errmsg); 108 }; 109 110 /** 111 * Key value pair struct, this represents the raw kvp values coming 112 * from the rules files. 113 */ 114 struct kvp { 115 char *key; 116 char *value; 117 }; 118 119 /** 120 * Rules are made up of meta data and an associated set of kvp stored in a 121 * key_map array. 122 */ 123 struct rule_map { 124 bool is_never_allow; 125 list violations; 126 list_element listify; 127 char *key; /** key value before hashing */ 128 size_t length; /** length of the key map */ 129 int lineno; /** Line number rule was encounter on */ 130 char *filename; /** File it was found in */ 131 key_map m[]; /** key value mapping */ 132 }; 133 134 struct hash_entry { 135 list_element listify; 136 rule_map *r; /** The rule map to store at that location */ 137 }; 138 139 /** 140 * Data associated for a policy file 141 */ 142 struct policy_info { 143 144 char *policy_file_name; /** policy file path name */ 145 FILE *policy_file; /** file handle to the policy file */ 146 sepol_policydb_t *db; 147 sepol_policy_file_t *pf; 148 sepol_handle_t *handle; 149 sepol_context_t *con; 150 }; 151 152 struct file_info { 153 FILE *file; /** file itself */ 154 const char *name; /** name of file. do not free, these are not alloc'd */ 155 list_element listify; 156 }; 157 158 static void input_file_list_freefn(list_element *e); 159 static void line_order_list_freefn(list_element *e); 160 static void rule_map_free(rule_map *rm, bool is_in_htable); 161 162 /** Set to !0 to enable verbose logging */ 163 static int logging_verbose = 0; 164 165 /** file handle to the output file */ 166 static file_info out_file; 167 168 static list input_file_list = list_init(input_file_list_freefn); 169 170 static policy_info pol = { 171 .policy_file_name = NULL, 172 .policy_file = NULL, 173 .db = NULL, 174 .pf = NULL, 175 .handle = NULL, 176 .con = NULL 177 }; 178 179 /** 180 * Head pointer to a linked list of 181 * rule map table entries (hash_entry), used for 182 * preserving the order of entries 183 * based on "first encounter" 184 */ 185 static list line_order_list = list_init(line_order_list_freefn); 186 187 /* 188 * List of hash_entrys for never allow rules. 189 */ 190 static list nallow_list = list_init(line_order_list_freefn); 191 192 /* validation call backs */ 193 static bool validate_bool(char *value, char **errmsg); 194 static bool validate_levelFrom(char *value, char **errmsg); 195 static bool validate_selinux_type(char *value, char **errmsg); 196 static bool validate_selinux_level(char *value, char **errmsg); 197 198 /** 199 * The heart of the mapping process, this must be updated if a new key value pair is added 200 * to a rule. 201 */ 202 key_map rules[] = { 203 /*Inputs*/ 204 { .name = "isSystemServer", .dir = dir_in, .fn_validate = validate_bool }, 205 { .name = "isAutoPlayApp", .dir = dir_in, .fn_validate = validate_bool }, 206 { .name = "isOwner", .dir = dir_in, .fn_validate = validate_bool }, 207 { .name = "user", .dir = dir_in, }, 208 { .name = "seinfo", .dir = dir_in, }, 209 { .name = "name", .dir = dir_in, }, 210 { .name = "path", .dir = dir_in, }, 211 { .name = "isPrivApp", .dir = dir_in, .fn_validate = validate_bool }, 212 /*Outputs*/ 213 { .name = "domain", .dir = dir_out, .fn_validate = validate_selinux_type }, 214 { .name = "type", .dir = dir_out, .fn_validate = validate_selinux_type }, 215 { .name = "levelFromUid", .dir = dir_out, .fn_validate = validate_bool }, 216 { .name = "levelFrom", .dir = dir_out, .fn_validate = validate_levelFrom }, 217 { .name = "level", .dir = dir_out, .fn_validate = validate_selinux_level }, 218 }; 219 220 /** 221 * Appends to the end of the list. 222 * @list The list to append to 223 * @e the element to append 224 */ 225 void list_append(list *list, list_element *e) { 226 227 memset(e, 0, sizeof(*e)); 228 229 if (list->head == NULL ) { 230 list->head = list->tail = e; 231 return; 232 } 233 234 list->tail->next = e; 235 list->tail = e; 236 return; 237 } 238 239 /** 240 * Free's all the elements in the specified list. 241 * @list The list to free 242 */ 243 static void list_free(list *list) { 244 245 list_element *tmp; 246 list_element *cursor = list->head; 247 248 while (cursor) { 249 tmp = cursor; 250 cursor = cursor->next; 251 if (list->freefn) { 252 list->freefn(tmp); 253 } 254 } 255 } 256 257 /* 258 * called when the lists are freed 259 */ 260 static void line_order_list_freefn(list_element *e) { 261 hash_entry *h = list_entry(e, typeof(*h), listify); 262 rule_map_free(h->r, true); 263 free(h); 264 } 265 266 static void input_file_list_freefn(list_element *e) { 267 file_info *f = list_entry(e, typeof(*f), listify); 268 269 if (f->file) { 270 fclose(f->file); 271 } 272 free(f); 273 } 274 275 /** 276 * Send a logging message to a file 277 * @param out 278 * Output file to send message too 279 * @param prefix 280 * A special prefix to write to the file, such as "Error:" 281 * @param fmt 282 * The printf style formatter to use, such as "%d" 283 */ 284 static void __attribute__ ((format(printf, 3, 4))) 285 log_msg(FILE *out, const char *prefix, const char *fmt, ...) { 286 287 fprintf(out, "%s", prefix); 288 va_list args; 289 va_start(args, fmt); 290 vfprintf(out, fmt, args); 291 va_end(args); 292 } 293 294 /** 295 * Checks for a type in the policy. 296 * @param db 297 * The policy db to search 298 * @param type 299 * The type to search for 300 * @return 301 * 1 if the type is found, 0 otherwise. 302 * @warning 303 * This function always returns 1 if libsepol is not linked 304 * statically to this executable and LINK_SEPOL_STATIC is not 305 * defined. 306 */ 307 static int check_type(sepol_policydb_t *db, char *type) { 308 309 int rc = 1; 310 #if defined(LINK_SEPOL_STATIC) 311 policydb_t *d = (policydb_t *)db; 312 hashtab_datum_t dat; 313 dat = hashtab_search(d->p_types.table, type); 314 rc = (dat == NULL) ? 0 : 1; 315 #endif 316 return rc; 317 } 318 319 static bool match_regex(key_map *assert, const key_map *check) { 320 321 char *tomatch = check->data; 322 323 int ret = pcre_exec(assert->regex.compiled, assert->regex.extra, tomatch, 324 strlen(tomatch), 0, 0, NULL, 0); 325 326 /* 0 from pcre_exec means matched */ 327 return !ret; 328 } 329 330 static bool compile_regex(key_map *km, const char **errbuf, int *erroff) { 331 332 size_t size; 333 char *anchored; 334 335 /* 336 * Explicitly anchor all regex's 337 * The size is the length of the string to anchor (km->data), the anchor 338 * characters ^ and $ and the null byte. Hence strlen(km->data) + 3 339 */ 340 size = strlen(km->data) + 3; 341 anchored = alloca(size); 342 sprintf(anchored, "^%s$", km->data); 343 344 km->regex.compiled = pcre_compile(anchored, PCRE_DOTALL, errbuf, erroff, 345 NULL ); 346 if (!km->regex.compiled) { 347 return false; 348 } 349 350 km->regex.extra = pcre_study(km->regex.compiled, 0, errbuf); 351 return true; 352 } 353 354 static bool validate_bool(char *value, char **errmsg) { 355 356 if (!strcmp("true", value) || !strcmp("false", value)) { 357 return true; 358 } 359 360 *errmsg = "Expecting \"true\" or \"false\""; 361 return false; 362 } 363 364 static bool validate_levelFrom(char *value, char **errmsg) { 365 366 if(strcasecmp(value, "none") && strcasecmp(value, "all") && 367 strcasecmp(value, "app") && strcasecmp(value, "user")) { 368 *errmsg = "Expecting one of: \"none\", \"all\", \"app\" or \"user\""; 369 return false; 370 } 371 return true; 372 } 373 374 static bool validate_selinux_type(char *value, char **errmsg) { 375 376 /* 377 * No policy file present means we cannot check 378 * SE Linux types 379 */ 380 if (!pol.policy_file) { 381 return true; 382 } 383 384 if(!check_type(pol.db, value)) { 385 *errmsg = "Expecting a valid SELinux type"; 386 return false; 387 } 388 389 return true; 390 } 391 392 static bool validate_selinux_level(char *value, char **errmsg) { 393 394 /* 395 * No policy file present means we cannot check 396 * SE Linux MLS 397 */ 398 if (!pol.policy_file) { 399 return true; 400 } 401 402 int ret = sepol_mls_check(pol.handle, pol.db, value); 403 if (ret < 0) { 404 *errmsg = "Expecting a valid SELinux MLS value"; 405 return false; 406 } 407 408 return true; 409 } 410 411 /** 412 * Validates a key_map against a set of enforcement rules, this 413 * function exits the application on a type that cannot be properly 414 * checked 415 * 416 * @param m 417 * The key map to check 418 * @param lineno 419 * The line number in the source file for the corresponding key map 420 * @return 421 * true if valid, false if invalid 422 */ 423 static bool key_map_validate(key_map *m, const char *filename, int lineno, 424 bool is_neverallow) { 425 426 int erroff; 427 const char *errbuf; 428 bool rc = true; 429 char *key = m->name; 430 char *value = m->data; 431 char *errmsg = NULL; 432 433 log_info("Validating %s=%s\n", key, value); 434 435 /* 436 * Neverallows are completely skipped from sanity checking so you can match 437 * un-unspecified inputs. 438 */ 439 if (is_neverallow) { 440 if (!m->regex.compiled) { 441 rc = compile_regex(m, &errbuf, &erroff); 442 if (!rc) { 443 log_error("Invalid regex on line %d : %s PCRE error: %s at offset %d", 444 lineno, value, errbuf, erroff); 445 } 446 } 447 goto out; 448 } 449 450 /* If the key has a validation routine, call it */ 451 if (m->fn_validate) { 452 rc = m->fn_validate(value, &errmsg); 453 454 if (!rc) { 455 log_error("Could not validate key \"%s\" for value \"%s\" on line: %d in file: \"%s\": %s\n", key, value, 456 lineno, filename, errmsg); 457 } 458 } 459 460 out: 461 log_info("Key map validate returning: %d\n", rc); 462 return rc; 463 } 464 465 /** 466 * Prints a rule map back to a file 467 * @param fp 468 * The file handle to print too 469 * @param r 470 * The rule map to print 471 */ 472 static void rule_map_print(FILE *fp, rule_map *r) { 473 474 size_t i; 475 key_map *m; 476 477 for (i = 0; i < r->length; i++) { 478 m = &(r->m[i]); 479 if (i < r->length - 1) 480 fprintf(fp, "%s=%s ", m->name, m->data); 481 else 482 fprintf(fp, "%s=%s", m->name, m->data); 483 } 484 } 485 486 /** 487 * Compare two rule maps for equality 488 * @param rmA 489 * a rule map to check 490 * @param rmB 491 * a rule map to check 492 * @return 493 * a map_match enum indicating the result 494 */ 495 static map_match rule_map_cmp(rule_map *rmA, rule_map *rmB) { 496 497 size_t i; 498 size_t j; 499 int inputs_found = 0; 500 int num_of_matched_inputs = 0; 501 int input_mode = 0; 502 size_t matches = 0; 503 key_map *mA; 504 key_map *mB; 505 506 for (i = 0; i < rmA->length; i++) { 507 mA = &(rmA->m[i]); 508 509 for (j = 0; j < rmB->length; j++) { 510 mB = &(rmB->m[j]); 511 input_mode = 0; 512 513 if (strcmp(mA->name, mB->name)) 514 continue; 515 516 if (strcmp(mA->data, mB->data)) 517 continue; 518 519 if (mB->dir != mA->dir) 520 continue; 521 else if (mB->dir == dir_in) { 522 input_mode = 1; 523 inputs_found++; 524 } 525 526 if (input_mode) { 527 log_info("Matched input lines: name=%s data=%s\n", mA->name, mA->data); 528 num_of_matched_inputs++; 529 } 530 531 /* Match found, move on */ 532 log_info("Matched lines: name=%s data=%s", mA->name, mA->data); 533 matches++; 534 break; 535 } 536 } 537 538 /* If they all matched*/ 539 if (matches == rmA->length) { 540 log_info("Rule map cmp MATCH\n"); 541 return map_matched; 542 } 543 544 /* They didn't all match but the input's did */ 545 else if (num_of_matched_inputs == inputs_found) { 546 log_info("Rule map cmp INPUT MATCH\n"); 547 return map_input_matched; 548 } 549 550 /* They didn't all match, and the inputs didn't match, ie it didn't 551 * match */ 552 else { 553 log_info("Rule map cmp NO MATCH\n"); 554 return map_no_matches; 555 } 556 } 557 558 /** 559 * Frees a rule map 560 * @param rm 561 * rule map to be freed. 562 * @is_in_htable 563 * True if the rule map has been added to the hash table, false 564 * otherwise. 565 */ 566 static void rule_map_free(rule_map *rm, bool is_in_htable) { 567 568 size_t i; 569 size_t len = rm->length; 570 for (i = 0; i < len; i++) { 571 key_map *m = &(rm->m[i]); 572 free(m->data); 573 574 if (m->regex.compiled) { 575 pcre_free(m->regex.compiled); 576 } 577 578 if (m->regex.extra) { 579 pcre_free_study(m->regex.extra); 580 } 581 } 582 583 /* 584 * hdestroy() frees comparsion keys for non glibc 585 * on GLIBC we always free on NON-GLIBC we free if 586 * it is not in the htable. 587 */ 588 if (rm->key) { 589 #ifdef __GLIBC__ 590 /* silence unused warning */ 591 (void)is_in_htable; 592 free(rm->key); 593 #else 594 if (!is_in_htable) { 595 free(rm->key); 596 } 597 #endif 598 } 599 600 free(rm->filename); 601 free(rm); 602 } 603 604 static void free_kvp(kvp *k) { 605 free(k->key); 606 free(k->value); 607 } 608 609 /** 610 * Checks a rule_map for any variation of KVP's that shouldn't be allowed. 611 * It builds an assertion failure list for each rule map. 612 * Note that this function logs all errors. 613 * 614 * Current Checks: 615 * 1. That a specified name entry should have a specified seinfo entry as well. 616 * 2. That no rule violates a neverallow 617 * @param rm 618 * The rule map to check for validity. 619 */ 620 static void rule_map_validate(rule_map *rm) { 621 622 size_t i, j; 623 const key_map *rule; 624 key_map *nrule; 625 hash_entry *e; 626 rule_map *assert; 627 list_element *cursor; 628 629 list_for_each(&nallow_list, cursor) { 630 e = list_entry(cursor, typeof(*e), listify); 631 assert = e->r; 632 633 size_t cnt = 0; 634 635 for (j = 0; j < assert->length; j++) { 636 nrule = &(assert->m[j]); 637 638 // mark that nrule->name is for a null check 639 bool is_null_check = !strcmp(nrule->data, "\"\""); 640 641 for (i = 0; i < rm->length; i++) { 642 rule = &(rm->m[i]); 643 644 if (!strcmp(rule->name, nrule->name)) { 645 646 /* the name was found, (data cannot be false) then it was specified */ 647 is_null_check = false; 648 649 if (match_regex(nrule, rule)) { 650 cnt++; 651 } 652 } 653 } 654 655 /* 656 * the nrule was marked in a null check and we never found a match on nrule, thus 657 * it matched and we update the cnt 658 */ 659 if (is_null_check) { 660 cnt++; 661 } 662 } 663 if (cnt == assert->length) { 664 list_append(&rm->violations, &assert->listify); 665 } 666 } 667 } 668 669 /** 670 * Given a set of key value pairs, this will construct a new rule map. 671 * On error this function calls exit. 672 * @param keys 673 * Keys from a rule line to map 674 * @param num_of_keys 675 * The length of the keys array 676 * @param lineno 677 * The line number the keys were extracted from 678 * @return 679 * A rule map pointer. 680 */ 681 static rule_map *rule_map_new(kvp keys[], size_t num_of_keys, int lineno, 682 const char *filename, bool is_never_allow) { 683 684 size_t i = 0, j = 0; 685 rule_map *new_map = NULL; 686 kvp *k = NULL; 687 key_map *r = NULL, *x = NULL; 688 bool seen[KVP_NUM_OF_RULES]; 689 690 for (i = 0; i < KVP_NUM_OF_RULES; i++) 691 seen[i] = false; 692 693 new_map = calloc(1, (num_of_keys * sizeof(key_map)) + sizeof(rule_map)); 694 if (!new_map) 695 goto oom; 696 697 new_map->is_never_allow = is_never_allow; 698 new_map->length = num_of_keys; 699 new_map->lineno = lineno; 700 new_map->filename = strdup(filename); 701 if (!new_map->filename) { 702 goto oom; 703 } 704 705 /* For all the keys in a rule line*/ 706 for (i = 0; i < num_of_keys; i++) { 707 k = &(keys[i]); 708 r = &(new_map->m[i]); 709 710 for (j = 0; j < KVP_NUM_OF_RULES; j++) { 711 x = &(rules[j]); 712 713 /* Only assign key name to map name */ 714 if (strcasecmp(k->key, x->name)) { 715 if (i == KVP_NUM_OF_RULES) { 716 log_error("No match for key: %s\n", k->key); 717 goto err; 718 } 719 continue; 720 } 721 722 if (seen[j]) { 723 log_error("Duplicated key: %s\n", k->key); 724 goto err; 725 } 726 seen[j] = true; 727 728 memcpy(r, x, sizeof(key_map)); 729 730 /* Assign rule map value to one from file */ 731 r->data = strdup(k->value); 732 if (!r->data) 733 goto oom; 734 735 /* Enforce type check*/ 736 log_info("Validating keys!\n"); 737 if (!key_map_validate(r, filename, lineno, new_map->is_never_allow)) { 738 log_error("Could not validate\n"); 739 goto err; 740 } 741 742 /* 743 * Only build key off of inputs with the exception of neverallows. 744 * Neverallows are keyed off of all key value pairs, 745 */ 746 if (r->dir == dir_in || new_map->is_never_allow) { 747 char *tmp; 748 int key_len = strlen(k->key); 749 int val_len = strlen(k->value); 750 int l = (new_map->key) ? strlen(new_map->key) : 0; 751 l = l + key_len + val_len; 752 l += 1; 753 754 tmp = realloc(new_map->key, l); 755 if (!tmp) 756 goto oom; 757 758 if (!new_map->key) 759 memset(tmp, 0, l); 760 761 new_map->key = tmp; 762 763 strncat(new_map->key, k->key, key_len); 764 strncat(new_map->key, k->value, val_len); 765 } 766 break; 767 } 768 free_kvp(k); 769 } 770 771 if (new_map->key == NULL) { 772 log_error("Strange, no keys found, input file corrupt perhaps?\n"); 773 goto err; 774 } 775 776 return new_map; 777 778 oom: 779 log_error("Out of memory!\n"); 780 err: 781 if(new_map) { 782 rule_map_free(new_map, false); 783 for (; i < num_of_keys; i++) { 784 k = &(keys[i]); 785 free_kvp(k); 786 } 787 } 788 return NULL; 789 } 790 791 /** 792 * Print the usage of the program 793 */ 794 static void usage() { 795 printf( 796 "checkseapp [options] <input file>\n" 797 "Processes an seapp_contexts file specified by argument <input file> (default stdin) " 798 "and allows later declarations to override previous ones on a match.\n" 799 "Options:\n" 800 "-h - print this help message\n" 801 "-v - enable verbose debugging informations\n" 802 "-p policy file - specify policy file for strict checking of output selectors against the policy\n" 803 "-o output file - specify output file or - for stdout. No argument runs in silent mode and outputs nothing\n"); 804 } 805 806 static void init() { 807 808 bool has_out_file; 809 list_element *cursor; 810 file_info *tmp; 811 812 /* input files if the list is empty, use stdin */ 813 if (!input_file_list.head) { 814 log_info("Using stdin for input\n"); 815 tmp = malloc(sizeof(*tmp)); 816 if (!tmp) { 817 log_error("oom"); 818 exit(EXIT_FAILURE); 819 } 820 tmp->name = "stdin"; 821 tmp->file = stdin; 822 list_append(&input_file_list, &(tmp->listify)); 823 } 824 else { 825 list_for_each(&input_file_list, cursor) { 826 tmp = list_entry(cursor, typeof(*tmp), listify); 827 828 log_info("Opening input file: \"%s\"\n", tmp->name); 829 tmp->file = fopen(tmp->name, "r"); 830 if (!tmp->file) { 831 log_error("Could not open file: %s error: %s\n", tmp->name, 832 strerror(errno)); 833 exit(EXIT_FAILURE); 834 } 835 } 836 } 837 838 has_out_file = out_file.name != NULL; 839 840 /* If output file is -, then use stdout, else open the path */ 841 if (has_out_file && !strcmp(out_file.name, "-")) { 842 out_file.file = stdout; 843 out_file.name = "stdout"; 844 } 845 else if (has_out_file) { 846 out_file.file = fopen(out_file.name, "w+"); 847 } 848 849 if (has_out_file && !out_file.file) { 850 log_error("Could not open file: \"%s\" error: \"%s\"\n", out_file.name, 851 strerror(errno)); 852 exit(EXIT_FAILURE); 853 } 854 855 if (pol.policy_file_name) { 856 log_info("Opening policy file: %s\n", pol.policy_file_name); 857 pol.policy_file = fopen(pol.policy_file_name, "rb"); 858 if (!pol.policy_file) { 859 log_error("Could not open file: %s error: %s\n", 860 pol.policy_file_name, strerror(errno)); 861 exit(EXIT_FAILURE); 862 } 863 864 pol.handle = sepol_handle_create(); 865 if (!pol.handle) { 866 log_error("Could not create sepolicy handle: %s\n", 867 strerror(errno)); 868 exit(EXIT_FAILURE); 869 } 870 871 if (sepol_policy_file_create(&pol.pf) < 0) { 872 log_error("Could not create sepolicy file: %s!\n", 873 strerror(errno)); 874 exit(EXIT_FAILURE); 875 } 876 877 sepol_policy_file_set_fp(pol.pf, pol.policy_file); 878 sepol_policy_file_set_handle(pol.pf, pol.handle); 879 880 if (sepol_policydb_create(&pol.db) < 0) { 881 log_error("Could not create sepolicy db: %s!\n", 882 strerror(errno)); 883 exit(EXIT_FAILURE); 884 } 885 886 if (sepol_policydb_read(pol.db, pol.pf) < 0) { 887 log_error("Could not lod policy file to db: %s!\n", 888 strerror(errno)); 889 exit(EXIT_FAILURE); 890 } 891 } 892 893 list_for_each(&input_file_list, cursor) { 894 tmp = list_entry(cursor, typeof(*tmp), listify); 895 log_info("Input file set to: \"%s\"\n", tmp->name); 896 } 897 898 log_info("Policy file set to: \"%s\"\n", 899 (pol.policy_file_name == NULL) ? "None" : pol.policy_file_name); 900 log_info("Output file set to: \"%s\"\n", out_file.name); 901 902 #if !defined(LINK_SEPOL_STATIC) 903 log_warn("LINK_SEPOL_STATIC is not defined\n""Not checking types!"); 904 #endif 905 906 } 907 908 /** 909 * Handle parsing and setting the global flags for the command line 910 * options. This function calls exit on failure. 911 * @param argc 912 * argument count 913 * @param argv 914 * argument list 915 */ 916 static void handle_options(int argc, char *argv[]) { 917 918 int c; 919 file_info *input_file; 920 921 while ((c = getopt(argc, argv, "ho:p:v")) != -1) { 922 switch (c) { 923 case 'h': 924 usage(); 925 exit(EXIT_SUCCESS); 926 case 'o': 927 out_file.name = optarg; 928 break; 929 case 'p': 930 pol.policy_file_name = optarg; 931 break; 932 case 'v': 933 log_set_verbose(); 934 break; 935 case '?': 936 if (optopt == 'o' || optopt == 'p') 937 log_error("Option -%c requires an argument.\n", optopt); 938 else if (isprint (optopt)) 939 log_error("Unknown option `-%c'.\n", optopt); 940 else { 941 log_error( 942 "Unknown option character `\\x%x'.\n", 943 optopt); 944 } 945 default: 946 exit(EXIT_FAILURE); 947 } 948 } 949 950 for (c = optind; c < argc; c++) { 951 952 input_file = calloc(1, sizeof(*input_file)); 953 if (!input_file) { 954 log_error("oom"); 955 exit(EXIT_FAILURE); 956 } 957 input_file->name = argv[c]; 958 list_append(&input_file_list, &input_file->listify); 959 } 960 } 961 962 /** 963 * Adds a rule to the hash table and to the ordered list if needed. 964 * @param rm 965 * The rule map to add. 966 */ 967 static void rule_add(rule_map *rm) { 968 969 map_match cmp; 970 ENTRY e; 971 ENTRY *f; 972 hash_entry *entry; 973 hash_entry *tmp; 974 list *list_to_addto; 975 976 e.key = rm->key; 977 978 log_info("Searching for key: %s\n", e.key); 979 /* Check to see if it has already been added*/ 980 f = hsearch(e, FIND); 981 982 /* 983 * Since your only hashing on a partial key, the inputs we need to handle 984 * when you want to override the outputs for a given input set, as well as 985 * checking for duplicate entries. 986 */ 987 if(f) { 988 log_info("Existing entry found!\n"); 989 tmp = (hash_entry *)f->data; 990 cmp = rule_map_cmp(rm, tmp->r); 991 log_error("Duplicate line detected in file: %s\n" 992 "Lines %d and %d %s!\n", 993 rm->filename, tmp->r->lineno, rm->lineno, 994 map_match_str[cmp]); 995 rule_map_free(rm, false); 996 goto err; 997 } 998 /* It wasn't found, just add the rule map to the table */ 999 else { 1000 1001 entry = malloc(sizeof(hash_entry)); 1002 if (!entry) 1003 goto oom; 1004 1005 entry->r = rm; 1006 e.data = entry; 1007 1008 f = hsearch(e, ENTER); 1009 if(f == NULL) { 1010 goto oom; 1011 } 1012 1013 /* new entries must be added to the ordered list */ 1014 entry->r = rm; 1015 list_to_addto = rm->is_never_allow ? &nallow_list : &line_order_list; 1016 list_append(list_to_addto, &entry->listify); 1017 } 1018 1019 return; 1020 oom: 1021 if (e.key) 1022 free(e.key); 1023 if (entry) 1024 free(entry); 1025 if (rm) 1026 free(rm); 1027 log_error("Out of memory in function: %s\n", __FUNCTION__); 1028 err: 1029 exit(EXIT_FAILURE); 1030 } 1031 1032 static void parse_file(file_info *in_file) { 1033 1034 char *p; 1035 size_t len; 1036 char *token; 1037 char *saveptr; 1038 bool is_never_allow; 1039 bool found_whitespace; 1040 1041 size_t lineno = 0; 1042 char *name = NULL; 1043 char *value = NULL; 1044 size_t token_cnt = 0; 1045 1046 char line_buf[BUFSIZ]; 1047 kvp keys[KVP_NUM_OF_RULES]; 1048 1049 while (fgets(line_buf, sizeof(line_buf) - 1, in_file->file)) { 1050 lineno++; 1051 is_never_allow = false; 1052 found_whitespace = false; 1053 log_info("Got line %zu\n", lineno); 1054 len = strlen(line_buf); 1055 if (line_buf[len - 1] == '\n') 1056 line_buf[len - 1] = '\0'; 1057 p = line_buf; 1058 1059 /* neverallow lines must start with neverallow (ie ^neverallow) */ 1060 if (!strncasecmp(p, "neverallow", strlen("neverallow"))) { 1061 p += strlen("neverallow"); 1062 is_never_allow = true; 1063 } 1064 1065 /* strip trailing whitespace skip comments */ 1066 while (isspace(*p)) { 1067 p++; 1068 found_whitespace = true; 1069 } 1070 if (*p == '#' || *p == '\0') 1071 continue; 1072 1073 token = strtok_r(p, " \t", &saveptr); 1074 if (!token) 1075 goto err; 1076 1077 token_cnt = 0; 1078 memset(keys, 0, sizeof(kvp) * KVP_NUM_OF_RULES); 1079 while (1) { 1080 1081 name = token; 1082 value = strchr(name, '='); 1083 if (!value) 1084 goto err; 1085 *value++ = 0; 1086 1087 keys[token_cnt].key = strdup(name); 1088 if (!keys[token_cnt].key) 1089 goto oom; 1090 1091 keys[token_cnt].value = strdup(value); 1092 if (!keys[token_cnt].value) 1093 goto oom; 1094 1095 token_cnt++; 1096 1097 token = strtok_r(NULL, " \t", &saveptr); 1098 if (!token) 1099 break; 1100 1101 } /*End token parsing */ 1102 1103 rule_map *r = rule_map_new(keys, token_cnt, lineno, in_file->name, is_never_allow); 1104 if (!r) 1105 goto err; 1106 rule_add(r); 1107 1108 } /* End file parsing */ 1109 return; 1110 1111 err: 1112 log_error("Reading file: \"%s\" line: %zu name: \"%s\" value: \"%s\"\n", 1113 in_file->name, lineno, name, value); 1114 if(found_whitespace && name && !strcasecmp(name, "neverallow")) { 1115 log_error("perhaps whitespace before neverallow\n"); 1116 } 1117 exit(EXIT_FAILURE); 1118 oom: 1119 log_error("In function %s: Out of memory\n", __FUNCTION__); 1120 exit(EXIT_FAILURE); 1121 } 1122 1123 /** 1124 * Parses the seapp_contexts file and neverallow file 1125 * and adds them to the hash table and ordered list entries 1126 * when it encounters them. 1127 * Calls exit on failure. 1128 */ 1129 static void parse() { 1130 1131 file_info *current; 1132 list_element *cursor; 1133 list_for_each(&input_file_list, cursor) { 1134 current = list_entry(cursor, typeof(*current), listify); 1135 parse_file(current); 1136 } 1137 } 1138 1139 static void validate() { 1140 1141 list_element *cursor, *v; 1142 bool found_issues = false; 1143 hash_entry *e; 1144 rule_map *r; 1145 list_for_each(&line_order_list, cursor) { 1146 e = list_entry(cursor, typeof(*e), listify); 1147 rule_map_validate(e->r); 1148 } 1149 1150 list_for_each(&line_order_list, cursor) { 1151 e = list_entry(cursor, typeof(*e), listify); 1152 r = e->r; 1153 list_for_each(&r->violations, v) { 1154 found_issues = true; 1155 log_error("Rule in File \"%s\" on line %d: \"", e->r->filename, e->r->lineno); 1156 rule_map_print(stderr, e->r); 1157 r = list_entry(v, rule_map, listify); 1158 fprintf(stderr, "\" violates neverallow in File \"%s\" on line %d: \"", r->filename, r->lineno); 1159 rule_map_print(stderr, r); 1160 fprintf(stderr, "\"\n"); 1161 } 1162 } 1163 1164 if (found_issues) { 1165 exit(EXIT_FAILURE); 1166 } 1167 } 1168 1169 /** 1170 * Should be called after parsing to cause the printing of the rule_maps 1171 * stored in the ordered list, head first, which preserves the "first encountered" 1172 * ordering. 1173 */ 1174 static void output() { 1175 1176 hash_entry *e; 1177 list_element *cursor; 1178 1179 if (!out_file.file) { 1180 log_info("No output file, not outputting.\n"); 1181 return; 1182 } 1183 1184 list_for_each(&line_order_list, cursor) { 1185 e = list_entry(cursor, hash_entry, listify); 1186 rule_map_print(out_file.file, e->r); 1187 fprintf(out_file.file, "\n"); 1188 } 1189 } 1190 1191 /** 1192 * This function is registered to the at exit handler and should clean up 1193 * the programs dynamic resources, such as memory and fd's. 1194 */ 1195 static void cleanup() { 1196 1197 /* Only close this when it was opened by me and not the crt */ 1198 if (out_file.name && strcmp(out_file.name, "stdout") && out_file.file) { 1199 log_info("Closing file: %s\n", out_file.name); 1200 fclose(out_file.file); 1201 } 1202 1203 if (pol.policy_file) { 1204 1205 log_info("Closing file: %s\n", pol.policy_file_name); 1206 fclose(pol.policy_file); 1207 1208 if (pol.db) 1209 sepol_policydb_free(pol.db); 1210 1211 if (pol.pf) 1212 sepol_policy_file_free(pol.pf); 1213 1214 if (pol.handle) 1215 sepol_handle_destroy(pol.handle); 1216 } 1217 1218 log_info("Freeing lists\n"); 1219 list_free(&input_file_list); 1220 list_free(&line_order_list); 1221 list_free(&nallow_list); 1222 hdestroy(); 1223 } 1224 1225 int main(int argc, char *argv[]) { 1226 if (!hcreate(TABLE_SIZE)) { 1227 log_error("Could not create hash table: %s\n", strerror(errno)); 1228 exit(EXIT_FAILURE); 1229 } 1230 atexit(cleanup); 1231 handle_options(argc, argv); 1232 init(); 1233 log_info("Starting to parse\n"); 1234 parse(); 1235 log_info("Parsing completed, generating output\n"); 1236 validate(); 1237 output(); 1238 log_info("Success, generated output\n"); 1239 exit(EXIT_SUCCESS); 1240 } 1241