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