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