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