• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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