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