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