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