xref: /aosp_15_r20/system/sepolicy/tools/check_seapp.c (revision e4a36f4174b17bbab9dc043f4a65dc8d87377290)
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