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