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 = "isOwner", .dir = dir_in, .fn_validate = validate_bool },
208 { .name = "user", .dir = dir_in, },
209 { .name = "seinfo", .dir = dir_in, },
210 { .name = "name", .dir = dir_in, },
211 { .name = "path", .dir = dir_in, },
212 { .name = "isPrivApp", .dir = dir_in, .fn_validate = validate_bool },
213 { .name = "minTargetSdkVersion", .dir = dir_in, .fn_validate = validate_uint },
214 { .name = "fromRunAs", .dir = dir_in, .fn_validate = validate_bool },
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 */
list_append(list * list,list_element * e)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 */
list_free(list * list)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 */
line_order_list_freefn(list_element * e)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
input_file_list_freefn(list_element * e)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)))
log_msg(FILE * out,const char * prefix,const char * fmt,...)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 */
check_type(sepol_policydb_t * db,char * type)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
match_regex(key_map * assert,const key_map * check)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
compile_regex(key_map * km,int * errcode,PCRE2_SIZE * erroff)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
validate_bool(char * value,char ** errmsg)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
validate_levelFrom(char * value,char ** errmsg)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
validate_selinux_type(char * value,char ** errmsg)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
validate_selinux_level(char * value,char ** errmsg)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
validate_uint(char * value,char ** errmsg)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 */
key_map_validate(key_map * m,const char * filename,int lineno,bool is_neverallow)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 validity 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 */
rule_map_print(FILE * fp,rule_map * r)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 */
rule_map_cmp(rule_map * rmA,rule_map * rmB)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 */
rule_map_free(rule_map * rm,bool is_in_htable)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
free_kvp(kvp * k)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 */
rule_map_validate(rule_map * rm)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 */
rule_map_new(kvp keys[],size_t num_of_keys,int lineno,const char * filename,bool is_never_allow)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 (j == KVP_NUM_OF_RULES - 1) {
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 */
usage()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
init()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 */
handle_options(int argc,char * argv[])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 */
rule_add(rule_map * rm)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
parse_file(file_info * in_file)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 if (token_cnt == KVP_NUM_OF_RULES)
1131 goto oob;
1132
1133 } /*End token parsing */
1134
1135 rule_map *r = rule_map_new(keys, token_cnt, lineno, in_file->name, is_never_allow);
1136 if (!r)
1137 goto err;
1138 rule_add(r);
1139
1140 } /* End file parsing */
1141 return;
1142
1143 err:
1144 log_error("Reading file: \"%s\" line: %zu name: \"%s\" value: \"%s\"\n",
1145 in_file->name, lineno, name, value);
1146 if(found_whitespace && name && !strcasecmp(name, "neverallow")) {
1147 log_error("perhaps whitespace before neverallow\n");
1148 }
1149 exit(EXIT_FAILURE);
1150 oom:
1151 log_error("In function %s: Out of memory\n", __FUNCTION__);
1152 exit(EXIT_FAILURE);
1153 oob:
1154 log_error("Reading file: \"%s\" line: %zu reason: the size of key pairs exceeds the MAX(%zu)\n",
1155 in_file->name, lineno, KVP_NUM_OF_RULES);
1156 exit(EXIT_FAILURE);
1157 }
1158
1159 /**
1160 * Parses the seapp_contexts file and neverallow file
1161 * and adds them to the hash table and ordered list entries
1162 * when it encounters them.
1163 * Calls exit on failure.
1164 */
parse()1165 static void parse() {
1166
1167 file_info *current;
1168 list_element *cursor;
1169 list_for_each(&input_file_list, cursor) {
1170 current = list_entry(cursor, typeof(*current), listify);
1171 parse_file(current);
1172 }
1173 }
1174
validate()1175 static void validate() {
1176
1177 list_element *cursor, *v;
1178 bool found_issues = false;
1179 hash_entry *e;
1180 rule_map *r;
1181 list_for_each(&line_order_list, cursor) {
1182 e = list_entry(cursor, typeof(*e), listify);
1183 rule_map_validate(e->r);
1184 }
1185
1186 list_for_each(&line_order_list, cursor) {
1187 e = list_entry(cursor, typeof(*e), listify);
1188 r = e->r;
1189 list_for_each(&r->violations, v) {
1190 found_issues = true;
1191 log_error("Rule in File \"%s\" on line %d: \"", e->r->filename, e->r->lineno);
1192 rule_map_print(stderr, e->r);
1193 r = list_entry(v, rule_map, listify);
1194 fprintf(stderr, "\" violates neverallow in File \"%s\" on line %d: \"", r->filename, r->lineno);
1195 rule_map_print(stderr, r);
1196 fprintf(stderr, "\"\n");
1197 }
1198 }
1199
1200 if (found_issues) {
1201 exit(EXIT_FAILURE);
1202 }
1203 }
1204
1205 /**
1206 * Should be called after parsing to cause the printing of the rule_maps
1207 * stored in the ordered list, head first, which preserves the "first encountered"
1208 * ordering.
1209 */
output()1210 static void output() {
1211
1212 hash_entry *e;
1213 list_element *cursor;
1214
1215 if (!out_file.file) {
1216 log_info("No output file, not outputting.\n");
1217 return;
1218 }
1219
1220 list_for_each(&line_order_list, cursor) {
1221 e = list_entry(cursor, hash_entry, listify);
1222 rule_map_print(out_file.file, e->r);
1223 fprintf(out_file.file, "\n");
1224 }
1225 }
1226
1227 /**
1228 * This function is registered to the at exit handler and should clean up
1229 * the programs dynamic resources, such as memory and fd's.
1230 */
cleanup()1231 static void cleanup() {
1232
1233 /* Only close this when it was opened by me and not the crt */
1234 if (out_file.name && strcmp(out_file.name, "stdout") && out_file.file) {
1235 log_info("Closing file: %s\n", out_file.name);
1236 fclose(out_file.file);
1237 }
1238
1239 if (pol.policy_file) {
1240
1241 log_info("Closing file: %s\n", pol.policy_file_name);
1242 fclose(pol.policy_file);
1243
1244 if (pol.db)
1245 sepol_policydb_free(pol.db);
1246
1247 if (pol.pf)
1248 sepol_policy_file_free(pol.pf);
1249
1250 if (pol.handle)
1251 sepol_handle_destroy(pol.handle);
1252 }
1253
1254 log_info("Freeing lists\n");
1255 list_free(&input_file_list);
1256 list_free(&line_order_list);
1257 list_free(&nallow_list);
1258 hdestroy();
1259 }
1260
main(int argc,char * argv[])1261 int main(int argc, char *argv[]) {
1262 if (!hcreate(TABLE_SIZE)) {
1263 log_error("Could not create hash table: %s\n", strerror(errno));
1264 exit(EXIT_FAILURE);
1265 }
1266 atexit(cleanup);
1267 handle_options(argc, argv);
1268 init();
1269 log_info("Starting to parse\n");
1270 parse();
1271 log_info("Parsing completed, generating output\n");
1272 validate();
1273 output();
1274 log_info("Success, generated output\n");
1275 exit(EXIT_SUCCESS);
1276 }
1277