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