diff --git a/Android.mk b/Android.mk index 8189f88f3d23b01f61d26d759ebea14159de17d9..c47b035500caeafadb918b03c0818ca08aef8718 100644 --- a/Android.mk +++ b/Android.mk @@ -173,18 +173,16 @@ LOCAL_MODULE_PATH := $(TARGET_ROOT_OUT) include $(BUILD_SYSTEM)/base_rules.mk -seapp_contexts.tmp := $(intermediates)/seapp_contexts.tmp -$(seapp_contexts.tmp): $(call build_policy, seapp_contexts) - @mkdir -p $(dir $@) - $(hide) m4 -s $^ > $@ +all_sc_files := $(call build_policy, seapp_contexts) $(LOCAL_BUILT_MODULE): PRIVATE_SEPOLICY := $(built_sepolicy) -$(LOCAL_BUILT_MODULE) : $(seapp_contexts.tmp) $(built_sepolicy) $(HOST_OUT_EXECUTABLES)/checkseapp +$(LOCAL_BUILT_MODULE): PRIVATE_SC_FILES := $(all_sc_files) +$(LOCAL_BUILT_MODULE): $(built_sepolicy) $(all_sc_files) $(HOST_OUT_EXECUTABLES)/checkseapp @mkdir -p $(dir $@) - $(HOST_OUT_EXECUTABLES)/checkseapp -p $(PRIVATE_SEPOLICY) -o $@ $< + $(HOST_OUT_EXECUTABLES)/checkseapp -p $(PRIVATE_SEPOLICY) -o $@ $(PRIVATE_SC_FILES) built_sc := $(LOCAL_BUILT_MODULE) -seapp_contexts.tmp := +all_sc_files := ################################## include $(CLEAR_VARS) @@ -194,18 +192,16 @@ LOCAL_MODULE_TAGS := tests include $(BUILD_SYSTEM)/base_rules.mk -general_seapp_contexts.tmp := $(intermediates)/general_seapp_contexts.tmp -$(general_seapp_contexts.tmp): $(addprefix $(LOCAL_PATH)/, seapp_contexts) - @mkdir -p $(dir $@) - $(hide) m4 -s $^ > $@ +all_sc_files := $(addprefix $(LOCAL_PATH)/, seapp_contexts) $(LOCAL_BUILT_MODULE): PRIVATE_SEPOLICY := $(built_sepolicy) -$(LOCAL_BUILT_MODULE) : $(general_seapp_contexts.tmp) $(built_sepolicy) $(HOST_OUT_EXECUTABLES)/checkseapp +$(LOCAL_BUILT_MODULE): PRIVATE_SC_FILE := $(all_sc_files) +$(LOCAL_BUILT_MODULE): $(built_sepolicy) $(all_sc_files) $(HOST_OUT_EXECUTABLES)/checkseapp @mkdir -p $(dir $@) - $(HOST_OUT_EXECUTABLES)/checkseapp -p $(PRIVATE_SEPOLICY) -o $@ $< + $(HOST_OUT_EXECUTABLES)/checkseapp -p $(PRIVATE_SEPOLICY) -o $@ $(PRIVATE_SC_FILE) GENERAL_SEAPP_CONTEXTS := $(LOCAL_BUILT_MODULE) -general_seapp_contexts.tmp := +all_sc_files := ################################## include $(CLEAR_VARS) diff --git a/seapp_contexts b/seapp_contexts index 8b2b59c230c7e7dfcdb850a56de1ae4726c09534..b0c61cfed7864165042071fb971e783f06fc28fc 100644 --- a/seapp_contexts +++ b/seapp_contexts @@ -38,6 +38,42 @@ # levelFrom=app or levelFrom=all is only supported for _app UIDs. # level may be used to specify a fixed level for any UID. # +# +# Neverallow Assertions +# Additional compile time assertion checks can be added as well. The assertion +# rules are lines beginning with the keyword neverallow. Full support for PCRE +# regular expressions exists on all input and output selectors. Neverallow +# rules are never output to the built seapp_contexts file. Like all keywords, +# neverallows are case-insensitive. A neverallow is asserted when all key value +# inputs are matched on a key value rule line. +# + +# only the system server can be in system_server domain +neverallow isSystemServer=false domain=system_server +neverallow isSystemServer="" domain=system_server + +# system domains should never be assigned outside of system uid +neverallow user=((?!system).)* domain=system_app +neverallow user=((?!system).)* type=system_app_data_file + +# anything with a non-known uid with a specified name should have a specified seinfo +neverallow user=_app name=.* seinfo="" +neverallow user=_app name=.* seinfo=default + +# neverallow shared relro to any other domain +# and neverallow any other uid into shared_relro +neverallow user=shared_relro domain=((?!shared_relro).)* +neverallow user=((?!shared_relro).)* domain=shared_relro + +# neverallow non-isolated uids into isolated_app domain +# and vice versa +neverallow user=_isolated domain=((?!isolated_app).)* +neverallow user=((?!_isolated).)* domain=isolated_app + +# uid shell should always be in shell domain, however non-shell +# uid's can be in shell domain +neverallow user=shell domain=((?!shell).)* + isSystemServer=true domain=system_server user=system seinfo=platform domain=system_app type=system_app_data_file user=bluetooth seinfo=platform domain=bluetooth type=bluetooth_data_file diff --git a/tools/Android.mk b/tools/Android.mk index 2a2e83dd8863a06a6bb9b9391446b4593803f395..98f562ce3a56776eafe342de6d4ca0a66c09eb37 100644 --- a/tools/Android.mk +++ b/tools/Android.mk @@ -4,10 +4,13 @@ include $(CLEAR_VARS) LOCAL_MODULE := checkseapp LOCAL_MODULE_TAGS := optional -LOCAL_C_INCLUDES := external/selinux/libsepol/include/ +LOCAL_C_INCLUDES := \ + external/pcre \ + external/selinux/libsepol/include LOCAL_CFLAGS := -DLINK_SEPOL_STATIC -Wall -Werror LOCAL_SRC_FILES := check_seapp.c LOCAL_STATIC_LIBRARIES := libsepol +LOCAL_WHOLE_STATIC_LIBRARIES := libpcre LOCAL_CXX_STL := none include $(BUILD_HOST_EXECUTABLE) diff --git a/tools/check_seapp.c b/tools/check_seapp.c index 10c97e08b486c240155b967f49988539351bdee4..ae4f7e3e03d9b4c9641e02029c6b1235f33a91f9 100644 --- a/tools/check_seapp.c +++ b/tools/check_seapp.c @@ -11,6 +11,7 @@ #include <stdbool.h> #include <sepol/sepol.h> #include <sepol/policydb/policydb.h> +#include <pcre.h> #define TABLE_SIZE 1024 #define KVP_NUM_OF_RULES (sizeof(rules) / sizeof(key_map)) @@ -19,7 +20,32 @@ #define log_warn(fmt, ...) log_msg(stderr, "Warning: ", fmt, ##__VA_ARGS__) #define log_info(fmt, ...) if (logging_verbose ) { log_msg(stdout, "Info: ", fmt, ##__VA_ARGS__); } -typedef struct line_order_list line_order_list; +/** + * Initializes an empty, static list. + */ +#define list_init(free_fn) { .head = NULL, .tail = NULL, .freefn = free_fn } + +/** + * given an item in the list, finds the offset for the container + * it was stored in. + * + * @element The element from the list + * @type The container type ie what you allocated that has the list_element structure in it. + * @name The name of the field that is the list_element + * + */ +#define list_entry(element, type, name) \ + (type *)(((uint8_t *)element) - (uint8_t *)&(((type *)NULL)->name)) + +/** + * Iterates over the list, do not free elements from the list when using this. + * @list The list head to walk + * @var The variable name for the cursor + */ +#define list_for_each(list, var) \ + for(var = (list)->head; var != NULL; var = var->next) + + typedef struct hash_entry hash_entry; typedef enum key_dir key_dir; typedef enum data_type data_type; @@ -29,6 +55,10 @@ typedef struct key_map key_map; typedef struct kvp kvp; typedef struct rule_map rule_map; typedef struct policy_info policy_info; +typedef struct list_element list_element; +typedef struct list list; +typedef struct key_map_regex key_map_regex; +typedef struct file_info file_info; enum map_match { map_no_matches, @@ -58,16 +88,19 @@ enum data_type { dt_bool, dt_string }; -/** - * This list is used to store a double pointer to each - * hash table / line rule combination. This way a replacement - * in the hash table automatically updates the list. The list - * is also used to keep "first encountered" ordering amongst - * the encountered key value pairs in the rules file. - */ -struct line_order_list { - hash_entry *e; - line_order_list *next; +struct list_element { + list_element *next; +}; + +struct list { + list_element *head; + list_element *tail; + void (*freefn)(list_element *e); +}; + +struct key_map_regex { + pcre *compiled; + pcre_extra *extra; }; /** @@ -79,6 +112,7 @@ struct key_map { key_dir dir; data_type type; char *data; + key_map_regex regex; }; /** @@ -95,13 +129,18 @@ struct kvp { * key_map array. */ struct rule_map { + bool is_never_allow; + list violations; + list_element listify; char *key; /** key value before hashing */ size_t length; /** length of the key map */ int lineno; /** Line number rule was encounter on */ + char *filename; /** File it was found in */ key_map m[]; /** key value mapping */ }; struct hash_entry { + list_element listify; rule_map *r; /** The rule map to store at that location */ }; @@ -118,20 +157,23 @@ struct policy_info { sepol_context_t *con; }; +struct file_info { + FILE *file; /** file itself */ + const char *name; /** name of file. do not free, these are not alloc'd */ + list_element listify; +}; + +static void input_file_list_freefn(list_element *e); +static void line_order_list_freefn(list_element *e); +static void rule_map_free(rule_map *rm, bool is_in_htable); + /** Set to !0 to enable verbose logging */ static int logging_verbose = 0; /** file handle to the output file */ -static FILE *output_file = NULL; - -/** file handle to the input file */ -static FILE *input_file = NULL; +static file_info out_file; -/** output file path name */ -static char *out_file_name = NULL; - -/** input file path name */ -static char *in_file_name = NULL; +static list input_file_list = list_init(input_file_list_freefn); static policy_info pol = { .policy_file_name = NULL, @@ -142,6 +184,19 @@ static policy_info pol = { .con = NULL }; +/** + * Head pointer to a linked list of + * rule map table entries (hash_entry), used for + * preserving the order of entries + * based on "first encounter" + */ +static list line_order_list = list_init(line_order_list_freefn); + +/* + * List of hash_entrys for never allow rules. + */ +static list nallow_list = list_init(line_order_list_freefn); + /** * The heart of the mapping process, this must be updated if a new key value pair is added * to a rule. @@ -163,18 +218,59 @@ key_map rules[] = { }; /** - * Head pointer to a linked list of - * rule map table entries, used for - * preserving the order of entries - * based on "first encounter" + * Appends to the end of the list. + * @list The list to append to + * @e the element to append */ -static line_order_list *list_head = NULL; +void list_append(list *list, list_element *e) { + + memset(e, 0, sizeof(*e)); + + if (list->head == NULL ) { + list->head = list->tail = e; + return; + } + + list->tail->next = e; + list->tail = e; + return; +} /** - * Pointer to the tail of the list for - * quick appends to the end of the list + * Free's all the elements in the specified list. + * @list The list to free */ -static line_order_list *list_tail = NULL; +static void list_free(list *list) { + + list_element *tmp; + list_element *cursor = list->head; + + while (cursor) { + tmp = cursor; + cursor = cursor->next; + if (list->freefn) { + list->freefn(tmp); + } + } +} + +/* + * called when the lists are freed + */ +static void line_order_list_freefn(list_element *e) { + hash_entry *h = list_entry(e, typeof(*h), listify); + rule_map_free(h->r, true); + free(h); +} + +static void input_file_list_freefn(list_element *e) { + file_info *f = list_entry(e, typeof(*f), listify); + + if (f->file) { + fclose(f->file); + } + free(f); +} /** * Send a logging message to a file @@ -220,6 +316,41 @@ int check_type(sepol_policydb_t *db, char *type) { return rc; } +static bool match_regex(key_map *assert, const key_map *check) { + + char *tomatch = check->data; + + int ret = pcre_exec(assert->regex.compiled, assert->regex.extra, tomatch, + strlen(tomatch), 0, 0, NULL, 0); + + /* 0 from pcre_exec means matched */ + return !ret; +} + +static bool compile_regex(key_map *km, const char **errbuf, int *erroff) { + + size_t size; + char *anchored; + + /* + * Explicitly anchor all regex's + * The size is the length of the string to anchor (km->data), the anchor + * characters ^ and $ and the null byte. Hence strlen(km->data) + 3 + */ + size = strlen(km->data) + 3; + anchored = alloca(size); + sprintf(anchored, "^%s$", km->data); + + km->regex.compiled = pcre_compile(anchored, PCRE_DOTALL, errbuf, erroff, + NULL ); + if (!km->regex.compiled) { + return false; + } + + km->regex.extra = pcre_study(km->regex.compiled, 0, errbuf); + return true; +} + /** * Validates a key_map against a set of enforcement rules, this * function exits the application on a type that cannot be properly @@ -230,11 +361,14 @@ int check_type(sepol_policydb_t *db, char *type) { * @param lineno * The line number in the source file for the corresponding key map * @return - * 1 if valid, 0 if invalid + * true if valid, false if invalid */ -static int key_map_validate(key_map *m, int lineno) { +static bool key_map_validate(key_map *m, const char *filename, int lineno, + bool is_neverallow) { - int rc = 1; + int erroff; + const char *errbuf; + bool rc = true; int ret = 1; char *key = m->name; char *value = m->data; @@ -242,14 +376,29 @@ static int key_map_validate(key_map *m, int lineno) { log_info("Validating %s=%s\n", key, value); - /* Booleans can always be checked for sanity */ + /* + * Neverallows are completely skipped from sanity checking so you can match + * un-unspecified inputs. + */ + if (is_neverallow) { + if (!m->regex.compiled) { + rc = compile_regex(m, &errbuf, &erroff); + if (!rc) { + log_error("Invalid regex on line %d : %s PCRE error: %s at offset %d", + lineno, value, errbuf, erroff); + } + } + goto out; + } + + /* Booleans can always be checked for sanity */ if (type == dt_bool && (!strcmp("true", value) || !strcmp("false", value))) { goto out; } else if (type == dt_bool) { log_error("Expected boolean value got: %s=%s on line: %d in file: %s\n", - key, value, lineno, in_file_name); - rc = 0; + key, value, lineno, filename); + rc = false; goto out; } @@ -257,8 +406,8 @@ static int key_map_validate(key_map *m, int lineno) { (strcasecmp(value, "none") && strcasecmp(value, "all") && strcasecmp(value, "app") && strcasecmp(value, "user"))) { log_error("Unknown levelFrom=%s on line: %d in file: %s\n", - value, lineno, in_file_name); - rc = 0; + value, lineno, filename); + rc = false; goto out; } @@ -274,8 +423,8 @@ static int key_map_validate(key_map *m, int lineno) { if(!check_type(pol.db, value)) { log_error("Could not find selinux type \"%s\" on line: %d in file: %s\n", value, - lineno, in_file_name); - rc = 0; + lineno, filename); + rc = false; } goto out; } @@ -284,8 +433,8 @@ static int key_map_validate(key_map *m, int lineno) { ret = sepol_mls_check(pol.handle, pol.db, value); if (ret < 0) { log_error("Could not find selinux level \"%s\", on line: %d in file: %s\n", value, - lineno, in_file_name); - rc = 0; + lineno, filename); + rc = false; goto out; } } @@ -391,11 +540,6 @@ static map_match rule_map_cmp(rule_map *rmA, rule_map *rmB) { } } -/** - * Frees a rule map - * @param rm - * rule map to be freed. - */ /** * Frees a rule map * @param rm @@ -411,6 +555,14 @@ static void rule_map_free(rule_map *rm, bool is_in_htable) { for (i = 0; i < len; i++) { key_map *m = &(rm->m[i]); free(m->data); + + if (m->regex.compiled) { + pcre_free(m->regex.compiled); + } + + if (m->regex.extra) { + pcre_free_study(m->regex.extra); + } } /* @@ -430,6 +582,7 @@ static void rule_map_free(rule_map *rm, bool is_in_htable) { #endif } + free(rm->filename); free(rm); } @@ -440,42 +593,62 @@ static void free_kvp(kvp *k) { /** * Checks a rule_map for any variation of KVP's that shouldn't be allowed. + * It builds an assertion failure list for each rule map. * Note that this function logs all errors. * * Current Checks: * 1. That a specified name entry should have a specified seinfo entry as well. + * 2. That no rule violates a neverallow * @param rm * The rule map to check for validity. - * @return - * true if the rule is valid, false otherwise. */ -static bool rule_map_validate(const rule_map *rm) { +static void rule_map_validate(rule_map *rm) { - size_t i; - bool found_name = false; - bool found_seinfo = false; - char *name = NULL; - const key_map *tmp; + size_t i, j; + const key_map *rule; + key_map *nrule; + hash_entry *e; + rule_map *assert; + list_element *cursor; + + list_for_each(&nallow_list, cursor) { + e = list_entry(cursor, typeof(*e), listify); + assert = e->r; + + size_t cnt = 0; + + for (j = 0; j < assert->length; j++) { + nrule = &(assert->m[j]); + + // mark that nrule->name is for a null check + bool is_null_check = !strcmp(nrule->data, "\"\""); - for(i=0; i < rm->length; i++) { - tmp = &(rm->m[i]); + for (i = 0; i < rm->length; i++) { + rule = &(rm->m[i]); - if(!strcmp(tmp->name, "name") && tmp->data) { - name = tmp->data; - found_name = true; + if (!strcmp(rule->name, nrule->name)) { + + /* the name was found, (data cannot be false) then it was specified */ + is_null_check = false; + + if (match_regex(nrule, rule)) { + cnt++; + } + } + } + + /* + * the nrule was marked in a null check and we never found a match on nrule, thus + * it matched and we update the cnt + */ + if (is_null_check) { + cnt++; + } } - if(!strcmp(tmp->name, "seinfo") && tmp->data && strcmp(tmp->data, "default")) { - found_seinfo = true; + if (cnt == assert->length) { + list_append(&rm->violations, &assert->listify); } } - - if(found_name && !found_seinfo) { - log_error("No specific seinfo value specified with name=\"%s\", on line: %d: insecure configuration!\n", - name, rm->lineno); - return false; - } - - return true; } /** @@ -490,10 +663,10 @@ static bool rule_map_validate(const rule_map *rm) { * @return * A rule map pointer. */ -static rule_map *rule_map_new(kvp keys[], size_t num_of_keys, int lineno) { +static rule_map *rule_map_new(kvp keys[], size_t num_of_keys, int lineno, + const char *filename, bool is_never_allow) { size_t i = 0, j = 0; - bool valid_rule; rule_map *new_map = NULL; kvp *k = NULL; key_map *r = NULL, *x = NULL; @@ -506,8 +679,13 @@ static rule_map *rule_map_new(kvp keys[], size_t num_of_keys, int lineno) { if (!new_map) goto oom; + new_map->is_never_allow = is_never_allow; new_map->length = num_of_keys; new_map->lineno = lineno; + new_map->filename = strdup(filename); + if (!new_map->filename) { + goto oom; + } /* For all the keys in a rule line*/ for (i = 0; i < num_of_keys; i++) { @@ -541,13 +719,16 @@ static rule_map *rule_map_new(kvp keys[], size_t num_of_keys, int lineno) { /* Enforce type check*/ log_info("Validating keys!\n"); - if (!key_map_validate(r, lineno)) { + if (!key_map_validate(r, filename, lineno, new_map->is_never_allow)) { log_error("Could not validate\n"); goto err; } - /* Only build key off of inputs*/ - if (r->dir == dir_in) { + /* + * Only build key off of inputs with the exception of neverallows. + * Neverallows are keyed off of all key value pairs, + */ + if (r->dir == dir_in || new_map->is_never_allow) { char *tmp; int key_len = strlen(k->key); int val_len = strlen(k->value); @@ -577,12 +758,6 @@ static rule_map *rule_map_new(kvp keys[], size_t num_of_keys, int lineno) { goto err; } - valid_rule = rule_map_validate(new_map); - if(!valid_rule) { - /* Error message logged from rule_map_validate() */ - goto err; - } - return new_map; oom: @@ -610,32 +785,59 @@ static void usage() { "-h - print this help message\n" "-v - enable verbose debugging informations\n" "-p policy file - specify policy file for strict checking of output selectors against the policy\n" - "-o output file - specify output file, default is stdout\n"); + "-o output file - specify output file or - for stdout. No argument runs in silent mode and outputs nothing\n"); } static void init() { - /* If not set on stdin already */ - if(!input_file) { - log_info("Opening input file: %s\n", in_file_name); - input_file = fopen(in_file_name, "r"); - if (!input_file) { - log_error("Could not open file: %s error: %s\n", in_file_name, strerror(errno)); + bool has_out_file; + list_element *cursor; + file_info *tmp; + + /* input files if the list is empty, use stdin */ + if (!input_file_list.head) { + log_info("Using stdin for input\n"); + tmp = malloc(sizeof(*tmp)); + if (!tmp) { + log_error("oom"); exit(EXIT_FAILURE); } + tmp->name = "stdin"; + tmp->file = stdin; + list_append(&input_file_list, &(tmp->listify)); } - - /* If not set on std out already */ - if(!output_file) { - output_file = fopen(out_file_name, "w+"); - if (!output_file) { - log_error("Could not open file: %s error: %s\n", out_file_name, strerror(errno)); - exit(EXIT_FAILURE); + else { + list_for_each(&input_file_list, cursor) { + tmp = list_entry(cursor, typeof(*tmp), listify); + + log_info("Opening input file: \"%s\"\n", tmp->name); + tmp->file = fopen(tmp->name, "r"); + if (!tmp->file) { + log_error("Could not open file: %s error: %s\n", tmp->name, + strerror(errno)); + exit(EXIT_FAILURE); + } } } - if (pol.policy_file_name) { + has_out_file = out_file.name != NULL; + + /* If output file is -, then use stdout, else open the path */ + if (has_out_file && !strcmp(out_file.name, "-")) { + out_file.file = stdout; + out_file.name = "stdout"; + } + else if (has_out_file) { + out_file.file = fopen(out_file.name, "w+"); + } + if (has_out_file && !out_file.file) { + log_error("Could not open file: \"%s\" error: \"%s\"\n", out_file.name, + strerror(errno)); + exit(EXIT_FAILURE); + } + + if (pol.policy_file_name) { log_info("Opening policy file: %s\n", pol.policy_file_name); pol.policy_file = fopen(pol.policy_file_name, "rb"); if (!pol.policy_file) { @@ -673,9 +875,14 @@ static void init() { } } - log_info("Policy file set to: %s\n", (pol.policy_file_name == NULL) ? "None" : pol.policy_file_name); - log_info("Input file set to: %s\n", (in_file_name == NULL) ? "stdin" : in_file_name); - log_info("Output file set to: %s\n", (out_file_name == NULL) ? "stdout" : out_file_name); + list_for_each(&input_file_list, cursor) { + tmp = list_entry(cursor, typeof(*tmp), listify); + log_info("Input file set to: \"%s\"\n", tmp->name); + } + + log_info("Policy file set to: \"%s\"\n", + (pol.policy_file_name == NULL) ? "None" : pol.policy_file_name); + log_info("Output file set to: \"%s\"\n", out_file.name); #if !defined(LINK_SEPOL_STATIC) log_warn("LINK_SEPOL_STATIC is not defined\n""Not checking types!"); @@ -694,7 +901,7 @@ static void init() { static void handle_options(int argc, char *argv[]) { int c; - int num_of_args; + file_info *input_file; while ((c = getopt(argc, argv, "ho:p:v")) != -1) { switch (c) { @@ -702,7 +909,7 @@ static void handle_options(int argc, char *argv[]) { usage(); exit(EXIT_SUCCESS); case 'o': - out_file_name = optarg; + out_file.name = optarg; break; case 'p': pol.policy_file_name = optarg; @@ -725,70 +932,15 @@ static void handle_options(int argc, char *argv[]) { } } - num_of_args = argc - optind; - - if (num_of_args > 1) { - log_error("Too many arguments, expected 0 or 1, argument, got %d\n", num_of_args); - usage(); - exit(EXIT_FAILURE); - } else if (num_of_args == 1) { - in_file_name = argv[argc - 1]; - } else { - input_file = stdin; - in_file_name = "stdin"; - } - - if (!out_file_name) { - output_file = stdout; - out_file_name = "stdout"; - } -} - -/** - * Adds a rule_map double pointer, ie the hash table pointer to the list. - * By using a double pointer, the hash table can have a line be overridden - * and the value is updated in the list. This function calls exit on failure. - * @param rm - * the rule_map to add. - */ -static void list_add(hash_entry *e) { - - line_order_list *node = malloc(sizeof(line_order_list)); - if (node == NULL) - goto oom; - - node->next = NULL; - node->e = e; - - if (list_head == NULL) - list_head = list_tail = node; - else { - list_tail->next = node; - list_tail = list_tail->next; - } - return; - -oom: - log_error("Out of memory!\n"); - exit(EXIT_FAILURE); -} - -/** - * Free's the rule map list, which ultimatley contains - * all the malloc'd rule_maps. - */ -static void list_free() { - line_order_list *cursor, *tmp; - hash_entry *e; + for (c = optind; c < argc; c++) { - cursor = list_head; - while (cursor) { - e = cursor->e; - rule_map_free(e->r, true); - tmp = cursor; - cursor = cursor->next; - free(e); - free(tmp); + input_file = calloc(1, sizeof(*input_file)); + if (!input_file) { + log_error("oom"); + exit(EXIT_FAILURE); + } + input_file->name = argv[c]; + list_append(&input_file_list, &input_file->listify); } } @@ -804,6 +956,7 @@ static void rule_add(rule_map *rm) { ENTRY *f; hash_entry *entry; hash_entry *tmp; + list *list_to_addto; e.key = rm->key; @@ -822,7 +975,7 @@ static void rule_add(rule_map *rm) { cmp = rule_map_cmp(rm, tmp->r); log_error("Duplicate line detected in file: %s\n" "Lines %d and %d %s!\n", - in_file_name, tmp->r->lineno, rm->lineno, + rm->filename, tmp->r->lineno, rm->lineno, map_match_str[cmp]); rule_map_free(rm, false); goto err; @@ -844,7 +997,8 @@ static void rule_add(rule_map *rm) { /* new entries must be added to the ordered list */ entry->r = rm; - list_add(entry); + list_to_addto = rm->is_never_allow ? &nallow_list : &line_order_list; + list_append(list_to_addto, &entry->listify); } return; @@ -860,31 +1014,44 @@ err: exit(EXIT_FAILURE); } -/** - * Parses the seapp_contexts file and adds them to the - * hash table and ordered list entries when it encounters them. - * Calls exit on failure. - */ -static void parse() { +static void parse_file(file_info *in_file) { - char line_buf[BUFSIZ]; - char *token; - unsigned lineno = 0; - char *p, *name = NULL, *value = NULL, *saveptr; + char *p; size_t len; - kvp keys[KVP_NUM_OF_RULES]; + char *token; + char *saveptr; + bool is_never_allow; + bool found_whitespace; + + size_t lineno = 0; + char *name = NULL; + char *value = NULL; size_t token_cnt = 0; - while (fgets(line_buf, sizeof line_buf - 1, input_file)) { + char line_buf[BUFSIZ]; + kvp keys[KVP_NUM_OF_RULES]; + while (fgets(line_buf, sizeof(line_buf) - 1, in_file->file)) { lineno++; - log_info("Got line %d\n", lineno); + is_never_allow = false; + found_whitespace = false; + log_info("Got line %zu\n", lineno); len = strlen(line_buf); if (line_buf[len - 1] == '\n') line_buf[len - 1] = '\0'; p = line_buf; - while (isspace(*p)) + + /* neverallow lines must start with neverallow (ie ^neverallow) */ + if (!strncasecmp(p, "neverallow", strlen("neverallow"))) { + p += strlen("neverallow"); + is_never_allow = true; + } + + /* strip trailing whitespace skip comments */ + while (isspace(*p)) { p++; + found_whitespace = true; + } if (*p == '#' || *p == '\0') continue; @@ -918,7 +1085,7 @@ static void parse() { } /*End token parsing */ - rule_map *r = rule_map_new(keys, token_cnt, lineno); + rule_map *r = rule_map_new(keys, token_cnt, lineno, in_file->name, is_never_allow); if (!r) goto err; rule_add(r); @@ -927,14 +1094,63 @@ static void parse() { return; err: - log_error("reading %s, line %u, name %s, value %s\n", - in_file_name, lineno, name, value); + log_error("reading %s, line %zu, name %s, value %s\n", + in_file->name, lineno, name, value); + if(found_whitespace && name && !strcasecmp(name, "neverallow")) { + log_error("perhaps whitespace before neverallow\n"); + } exit(EXIT_FAILURE); oom: log_error("In function %s: Out of memory\n", __FUNCTION__); exit(EXIT_FAILURE); } +/** + * Parses the seapp_contexts file and neverallow file + * and adds them to the hash table and ordered list entries + * when it encounters them. + * Calls exit on failure. + */ +static void parse() { + + file_info *current; + list_element *cursor; + list_for_each(&input_file_list, cursor) { + current = list_entry(cursor, typeof(*current), listify); + parse_file(current); + } +} + +static void validate() { + + list_element *cursor, *v; + bool found_issues = false; + hash_entry *e; + rule_map *r; + list_for_each(&line_order_list, cursor) { + e = list_entry(cursor, typeof(*e), listify); + rule_map_validate(e->r); + } + + list_for_each(&line_order_list, cursor) { + e = list_entry(cursor, typeof(*e), listify); + r = e->r; + list_for_each(&r->violations, v) { + found_issues = true; + log_error("Rule in File \"%s\" on line %d: \"", e->r->filename, e->r->lineno); + rule_map_print(stderr, e->r); + r = list_entry(v, rule_map, listify); + fprintf(stderr, "\" violates neverallow in File \"%s\" on line %d: \"", r->filename, r->lineno); + rule_map_print(stderr, r); + fprintf(stderr, "\"\n"); + } + } + + if (found_issues) { + exit(EXIT_FAILURE); + } +} + /** * Should be called after parsing to cause the printing of the rule_maps * stored in the ordered list, head first, which preserves the "first encountered" @@ -942,15 +1158,18 @@ oom: */ static void output() { - rule_map *r; - line_order_list *cursor; - cursor = list_head; + hash_entry *e; + list_element *cursor; - while (cursor) { - r = cursor->e->r; - rule_map_print(output_file, r); - cursor = cursor->next; - fprintf(output_file, "\n"); + if (!out_file.file) { + log_info("No output file, not outputting.\n"); + return; + } + + list_for_each(&line_order_list, cursor) { + e = list_entry(cursor, hash_entry, listify); + rule_map_print(out_file.file, e->r); + fprintf(out_file.file, "\n"); } } @@ -961,15 +1180,9 @@ static void output() { static void cleanup() { /* Only close this when it was opened by me and not the crt */ - if (out_file_name && output_file) { - log_info("Closing file: %s\n", out_file_name); - fclose(output_file); - } - - /* Only close this when it was opened by me and not the crt */ - if (in_file_name && input_file) { - log_info("Closing file: %s\n", in_file_name); - fclose(input_file); + if (out_file.name && strcmp(out_file.name, "stdout") && out_file.file) { + log_info("Closing file: %s\n", out_file.name); + fclose(out_file.file); } if (pol.policy_file) { @@ -987,8 +1200,10 @@ static void cleanup() { sepol_handle_destroy(pol.handle); } - log_info("Freeing list\n"); - list_free(); + log_info("Freeing lists\n"); + list_free(&input_file_list); + list_free(&line_order_list); + list_free(&nallow_list); hdestroy(); } @@ -1003,6 +1218,7 @@ int main(int argc, char *argv[]) { log_info("Starting to parse\n"); parse(); log_info("Parsing completed, generating output\n"); + validate(); output(); log_info("Success, generated output\n"); exit(EXIT_SUCCESS);