| #include <stdio.h> |
| #include <stdarg.h> |
| #include <ctype.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <unistd.h> |
| #include <string.h> |
| #include <errno.h> |
| #include <stdint.h> |
| #include <search.h> |
| #include <stdbool.h> |
| #include <sepol/sepol.h> |
| #include <sepol/policydb/policydb.h> |
| #include <pcre2.h> |
| |
| #define TABLE_SIZE 1024 |
| #define KVP_NUM_OF_RULES (sizeof(rules) / sizeof(key_map)) |
| #define log_set_verbose() do { logging_verbose = 1; log_info("Enabling verbose\n"); } while(0) |
| #define log_error(fmt, ...) log_msg(stderr, "Error: ", fmt, ##__VA_ARGS__) |
| #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__); } |
| |
| #define APP_DATA_REQUIRED_ATTRIB "app_data_file_type" |
| #define COREDOMAIN "coredomain" |
| |
| /** |
| * 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) /*NOLINT*/ |
| |
| |
| typedef struct hash_entry hash_entry; |
| typedef enum key_dir key_dir; |
| typedef enum data_type data_type; |
| typedef enum rule_map_switch rule_map_switch; |
| typedef enum map_match map_match; |
| 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; |
| typedef struct coredomain_violation_entry coredomain_violation_entry; |
| |
| enum map_match { |
| map_no_matches, |
| map_input_matched, |
| map_matched |
| }; |
| |
| const char *map_match_str[] = { |
| "do not match", |
| "match on all inputs", |
| "match on everything" |
| }; |
| |
| /** |
| * Whether or not the "key" from a key vaue pair is considered an |
| * input or an output. |
| */ |
| enum key_dir { |
| dir_in, dir_out |
| }; |
| |
| struct list_element { |
| list_element *next; |
| }; |
| |
| struct list { |
| list_element *head; |
| list_element *tail; |
| void (*freefn)(list_element *e); |
| }; |
| |
| struct key_map_regex { |
| pcre2_code *compiled; |
| pcre2_match_data *match_data; |
| }; |
| |
| /** |
| * The workhorse of the logic. This struct maps key value pairs to |
| * an associated set of meta data maintained in rule_map_new() |
| */ |
| struct key_map { |
| char *name; |
| key_dir dir; |
| char *data; |
| key_map_regex regex; |
| bool (*fn_validate)(char *value, const char *filename, int lineno, char **errmsg); |
| }; |
| |
| /** |
| * Key value pair struct, this represents the raw kvp values coming |
| * from the rules files. |
| */ |
| struct kvp { |
| char *key; |
| char *value; |
| }; |
| |
| /** |
| * Rules are made up of meta data and an associated set of kvp stored in a |
| * 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 */ |
| }; |
| |
| /** |
| * Data associated for a policy file |
| */ |
| struct policy_info { |
| |
| char *policy_file_name; /** policy file path name */ |
| FILE *policy_file; /** file handle to the policy file */ |
| sepol_policydb_t *db; |
| sepol_policy_file_t *pf; |
| sepol_handle_t *handle; |
| sepol_context_t *con; |
| bool vendor; |
| }; |
| |
| struct file_info { |
| FILE *file; /** file itself */ |
| const char *name; /** name of file. do not free, these are not alloc'd */ |
| list_element listify; |
| }; |
| |
| struct coredomain_violation_entry { |
| list_element listify; |
| char *domain; |
| char *filename; |
| int lineno; |
| }; |
| |
| static void coredomain_violation_list_freefn(list_element *e); |
| 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_info out_file; |
| |
| static list input_file_list = list_init(input_file_list_freefn); |
| |
| static list coredomain_violation_list = list_init(coredomain_violation_list_freefn); |
| |
| static policy_info pol = { |
| .policy_file_name = NULL, |
| .policy_file = NULL, |
| .db = NULL, |
| .pf = NULL, |
| .handle = NULL, |
| .con = NULL, |
| .vendor = false |
| }; |
| |
| /** |
| * 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); |
| |
| /* validation call backs */ |
| static bool validate_bool(char *value, const char *filename, int lineno, char **errmsg); |
| static bool validate_levelFrom(char *value, const char *filename, int lineno, char **errmsg); |
| static bool validate_domain(char *value, const char *filename, int lineno, char **errmsg); |
| static bool validate_type(char *value, const char *filename, int lineno, char **errmsg); |
| static bool validate_selinux_level(char *value, const char *filename, int lineno, char **errmsg); |
| static bool validate_uint(char *value, const char *filename, int lineno, char **errmsg); |
| |
| /** |
| * The heart of the mapping process, this must be updated if a new key value pair is added |
| * to a rule. |
| */ |
| key_map rules[] = { |
| /*Inputs*/ |
| { .name = "isSystemServer", .dir = dir_in, .fn_validate = validate_bool }, |
| { .name = "isEphemeralApp", .dir = dir_in, .fn_validate = validate_bool }, |
| { .name = "user", .dir = dir_in, }, |
| { .name = "seinfo", .dir = dir_in, }, |
| { .name = "name", .dir = dir_in, }, |
| { .name = "isPrivApp", .dir = dir_in, .fn_validate = validate_bool }, |
| { .name = "minTargetSdkVersion", .dir = dir_in, .fn_validate = validate_uint }, |
| { .name = "fromRunAs", .dir = dir_in, .fn_validate = validate_bool }, |
| { .name = "isIsolatedComputeApp", .dir = dir_in, .fn_validate = validate_bool }, |
| { .name = "isSdkSandboxAudit", .dir = dir_in, .fn_validate = validate_bool }, |
| { .name = "isSdkSandboxNext", .dir = dir_in, .fn_validate = validate_bool }, |
| /*Outputs*/ |
| { .name = "domain", .dir = dir_out, .fn_validate = validate_domain }, |
| { .name = "type", .dir = dir_out, .fn_validate = validate_type }, |
| { .name = "levelFromUid", .dir = dir_out, .fn_validate = validate_bool }, |
| { .name = "levelFrom", .dir = dir_out, .fn_validate = validate_levelFrom }, |
| { .name = "level", .dir = dir_out, .fn_validate = validate_selinux_level }, |
| }; |
| |
| /** |
| * Appends to the end of the list. |
| * @list The list to append to |
| * @e the element to append |
| */ |
| 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; |
| } |
| |
| /** |
| * Free's all the elements in the specified list. |
| * @list The list to free |
| */ |
| 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); |
| } |
| |
| static void coredomain_violation_list_freefn(list_element *e) { |
| coredomain_violation_entry *c = list_entry(e, typeof(*c), listify); |
| |
| free(c->domain); |
| free(c->filename); |
| free(c); |
| } |
| |
| /** |
| * Send a logging message to a file |
| * @param out |
| * Output file to send message too |
| * @param prefix |
| * A special prefix to write to the file, such as "Error:" |
| * @param fmt |
| * The printf style formatter to use, such as "%d" |
| */ |
| static void __attribute__ ((format(printf, 3, 4))) |
| log_msg(FILE *out, const char *prefix, const char *fmt, ...) { |
| |
| fprintf(out, "%s", prefix); |
| va_list args; |
| va_start(args, fmt); |
| vfprintf(out, fmt, args); |
| va_end(args); |
| } |
| |
| /** |
| * Look up a type in the policy. |
| * @param db |
| * The policy db to search |
| * @param type |
| * The type to search for |
| * @param flavor |
| * The expected flavor of type |
| * @return |
| * Pointer to the type's datum if it exists in the policy with the expected |
| * flavor, NULL otherwise. |
| * @warning |
| * This function should not be called if libsepol is not linked statically |
| * to this executable and LINK_SEPOL_STATIC is not defined. |
| */ |
| static type_datum_t *find_type(sepol_policydb_t *db, char *type, uint32_t flavor) { |
| |
| policydb_t *d = &db->p; |
| hashtab_datum_t dat = hashtab_search(d->p_types.table, type); |
| if (!dat) { |
| return NULL; |
| } |
| type_datum_t *type_dat = (type_datum_t *) dat; |
| if (type_dat->flavor != flavor) { |
| return NULL; |
| } |
| return type_dat; |
| } |
| |
| static bool type_has_attribute(sepol_policydb_t *db, type_datum_t *type_dat, |
| type_datum_t *attrib_dat) { |
| policydb_t *d = &db->p; |
| ebitmap_t *attr_bits = &d->type_attr_map[type_dat->s.value - 1]; |
| return ebitmap_get_bit(attr_bits, attrib_dat->s.value - 1) != 0; |
| } |
| |
| static bool match_regex(key_map *assert, const key_map *check) { |
| |
| char *tomatch = check->data; |
| |
| int ret = pcre2_match(assert->regex.compiled, (PCRE2_SPTR) tomatch, |
| PCRE2_ZERO_TERMINATED, 0, 0, |
| assert->regex.match_data, NULL); |
| |
| /* ret > 0 from pcre2_match means matched */ |
| return ret > 0; |
| } |
| |
| static bool compile_regex(key_map *km, int *errcode, PCRE2_SIZE *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 = pcre2_compile((PCRE2_SPTR) anchored, |
| PCRE2_ZERO_TERMINATED, |
| PCRE2_DOTALL, |
| errcode, erroff, |
| NULL); |
| if (!km->regex.compiled) { |
| return false; |
| } |
| |
| km->regex.match_data = pcre2_match_data_create_from_pattern( |
| km->regex.compiled, NULL); |
| if (!km->regex.match_data) { |
| pcre2_code_free(km->regex.compiled); |
| return false; |
| } |
| return true; |
| } |
| |
| static bool validate_bool( |
| char *value, |
| __attribute__ ((unused)) const char *filename, |
| __attribute__ ((unused)) int lineno, |
| char **errmsg) { |
| if (!strcmp("true", value) || !strcmp("false", value)) { |
| return true; |
| } |
| |
| *errmsg = "Expecting \"true\" or \"false\""; |
| return false; |
| } |
| |
| static bool validate_levelFrom( |
| char *value, |
| __attribute__ ((unused)) const char *filename, |
| __attribute__ ((unused)) int lineno, |
| char **errmsg) { |
| if (strcasecmp(value, "none") && strcasecmp(value, "all") && |
| strcasecmp(value, "app") && strcasecmp(value, "user")) { |
| *errmsg = "Expecting one of: \"none\", \"all\", \"app\" or \"user\""; |
| return false; |
| } |
| return true; |
| } |
| |
| static bool validate_domain(char *value, const char *filename, int lineno, char **errmsg) { |
| |
| #if defined(LINK_SEPOL_STATIC) |
| /* |
| * No policy file present means we cannot check |
| * SE Linux types |
| */ |
| if (!pol.policy_file) { |
| return true; |
| } |
| |
| type_datum_t *type_dat = find_type(pol.db, value, TYPE_TYPE); |
| if (!type_dat) { |
| *errmsg = "Expecting a valid SELinux type"; |
| return false; |
| } |
| |
| if (pol.vendor) { |
| type_datum_t *attrib_dat = find_type(pol.db, COREDOMAIN, TYPE_ATTRIB); |
| if (!attrib_dat) { |
| *errmsg = "The attribute " COREDOMAIN " is not defined in the policy"; |
| return false; |
| } |
| |
| if (type_has_attribute(pol.db, type_dat, attrib_dat)) { |
| coredomain_violation_entry *entry = (coredomain_violation_entry *)malloc(sizeof(*entry)); |
| entry->domain = strdup(value); |
| entry->filename = strdup(filename); |
| entry->lineno = lineno; |
| list_append(&coredomain_violation_list, &entry->listify); |
| } |
| } |
| #endif |
| |
| return true; |
| } |
| |
| static bool validate_type( |
| char *value, |
| __attribute__ ((unused)) const char *filename, |
| __attribute__ ((unused)) int lineno, |
| char **errmsg) { |
| #if defined(LINK_SEPOL_STATIC) |
| /* |
| * No policy file present means we cannot check |
| * SE Linux types |
| */ |
| if (!pol.policy_file) { |
| return true; |
| } |
| |
| type_datum_t *type_dat = find_type(pol.db, value, TYPE_TYPE); |
| if (!type_dat) { |
| *errmsg = "Expecting a valid SELinux type"; |
| return false; |
| } |
| |
| type_datum_t *attrib_dat = find_type(pol.db, APP_DATA_REQUIRED_ATTRIB, |
| TYPE_ATTRIB); |
| if (!attrib_dat) { |
| /* If the policy doesn't contain the attribute, we can't check it */ |
| return true; |
| } |
| |
| if (!type_has_attribute(pol.db, type_dat, attrib_dat)) { |
| *errmsg = "Missing required attribute " APP_DATA_REQUIRED_ATTRIB; |
| return false; |
| } |
| |
| #endif |
| |
| return true; |
| } |
| |
| static bool validate_selinux_level( |
| char *value, |
| __attribute__ ((unused)) const char *filename, |
| __attribute__ ((unused)) int lineno, |
| char **errmsg) { |
| /* |
| * No policy file present means we cannot check |
| * SE Linux MLS |
| */ |
| if (!pol.policy_file) { |
| return true; |
| } |
| |
| int ret = sepol_mls_check(pol.handle, pol.db, value); |
| if (ret < 0) { |
| *errmsg = "Expecting a valid SELinux MLS value"; |
| return false; |
| } |
| |
| return true; |
| } |
| |
| static bool validate_uint( |
| char *value, |
| __attribute__ ((unused)) const char *filename, |
| __attribute__ ((unused)) int lineno, |
| char **errmsg) { |
| char *endptr; |
| long longvalue; |
| longvalue = strtol(value, &endptr, 10); |
| if (('\0' != *endptr) || (longvalue < 0) || (longvalue > INT32_MAX)) { |
| *errmsg = "Expecting a valid unsigned integer"; |
| return false; |
| } |
| |
| return true; |
| } |
| |
| /** |
| * Validates a key_map against a set of enforcement rules, this |
| * function exits the application on a type that cannot be properly |
| * checked |
| * |
| * @param m |
| * The key map to check |
| * @param lineno |
| * The line number in the source file for the corresponding key map |
| * @return |
| * true if valid, false if invalid |
| */ |
| static bool key_map_validate(key_map *m, const char *filename, int lineno, |
| bool is_neverallow) { |
| |
| PCRE2_SIZE erroff; |
| int errcode; |
| bool rc = true; |
| char *key = m->name; |
| char *value = m->data; |
| char *errmsg = NULL; |
| char errstr[256]; |
| |
| log_info("Validating %s=%s\n", key, value); |
| |
| /* |
| * Neverallows are completely skipped from validity checking so you can match |
| * un-unspecified inputs. |
| */ |
| if (is_neverallow) { |
| if (!m->regex.compiled) { |
| rc = compile_regex(m, &errcode, &erroff); |
| if (!rc) { |
| pcre2_get_error_message(errcode, |
| (PCRE2_UCHAR*) errstr, |
| sizeof(errstr)); |
| log_error("Invalid regex on line %d : %s PCRE error: %s at offset %lu", |
| lineno, value, errstr, erroff); |
| } |
| } |
| goto out; |
| } |
| |
| /* If the key has a validation routine, call it */ |
| if (m->fn_validate) { |
| rc = m->fn_validate(value, filename, lineno, &errmsg); |
| |
| if (!rc) { |
| log_error("Could not validate key \"%s\" for value \"%s\" on line: %d in file: \"%s\": %s\n", key, value, |
| lineno, filename, errmsg); |
| } |
| } |
| |
| out: |
| log_info("Key map validate returning: %d\n", rc); |
| return rc; |
| } |
| |
| /** |
| * Prints a rule map back to a file |
| * @param fp |
| * The file handle to print too |
| * @param r |
| * The rule map to print |
| */ |
| static void rule_map_print(FILE *fp, rule_map *r) { |
| |
| size_t i; |
| key_map *m; |
| |
| for (i = 0; i < r->length; i++) { |
| m = &(r->m[i]); |
| if (i < r->length - 1) |
| fprintf(fp, "%s=%s ", m->name, m->data); |
| else |
| fprintf(fp, "%s=%s", m->name, m->data); |
| } |
| } |
| |
| /** |
| * Compare two rule maps for equality |
| * @param rmA |
| * a rule map to check |
| * @param rmB |
| * a rule map to check |
| * @return |
| * a map_match enum indicating the result |
| */ |
| static map_match rule_map_cmp(rule_map *rmA, rule_map *rmB) { |
| |
| size_t i; |
| size_t j; |
| int inputs_found = 0; |
| int num_of_matched_inputs = 0; |
| int input_mode = 0; |
| size_t matches = 0; |
| key_map *mA; |
| key_map *mB; |
| |
| for (i = 0; i < rmA->length; i++) { |
| mA = &(rmA->m[i]); |
| |
| for (j = 0; j < rmB->length; j++) { |
| mB = &(rmB->m[j]); |
| input_mode = 0; |
| |
| if (strcmp(mA->name, mB->name)) |
| continue; |
| |
| if (strcmp(mA->data, mB->data)) |
| continue; |
| |
| if (mB->dir != mA->dir) |
| continue; |
| else if (mB->dir == dir_in) { |
| input_mode = 1; |
| inputs_found++; |
| } |
| |
| if (input_mode) { |
| log_info("Matched input lines: name=%s data=%s\n", mA->name, mA->data); |
| num_of_matched_inputs++; |
| } |
| |
| /* Match found, move on */ |
| log_info("Matched lines: name=%s data=%s", mA->name, mA->data); |
| matches++; |
| break; |
| } |
| } |
| |
| /* If they all matched*/ |
| if (matches == rmA->length) { |
| log_info("Rule map cmp MATCH\n"); |
| return map_matched; |
| } |
| |
| /* They didn't all match but the input's did */ |
| else if (num_of_matched_inputs == inputs_found) { |
| log_info("Rule map cmp INPUT MATCH\n"); |
| return map_input_matched; |
| } |
| |
| /* They didn't all match, and the inputs didn't match, ie it didn't |
| * match */ |
| else { |
| log_info("Rule map cmp NO MATCH\n"); |
| return map_no_matches; |
| } |
| } |
| |
| /** |
| * Frees a rule map |
| * @param rm |
| * rule map to be freed. |
| * @is_in_htable |
| * True if the rule map has been added to the hash table, false |
| * otherwise. |
| */ |
| static void rule_map_free(rule_map *rm, bool is_in_htable) { |
| |
| size_t i; |
| size_t len = rm->length; |
| for (i = 0; i < len; i++) { |
| key_map *m = &(rm->m[i]); |
| free(m->data); |
| |
| if (m->regex.compiled) { |
| pcre2_code_free(m->regex.compiled); |
| } |
| |
| if (m->regex.match_data) { |
| pcre2_match_data_free(m->regex.match_data); |
| } |
| } |
| |
| /* |
| * hdestroy() frees comparsion keys for non glibc |
| * on GLIBC we always free on NON-GLIBC we free if |
| * it is not in the htable. |
| */ |
| if (rm->key) { |
| #ifdef __GLIBC__ |
| /* silence unused warning */ |
| (void)is_in_htable; |
| free(rm->key); |
| #else |
| if (!is_in_htable) { |
| free(rm->key); |
| } |
| #endif |
| } |
| |
| free(rm->filename); |
| free(rm); |
| } |
| |
| static void free_kvp(kvp *k) { |
| free(k->key); |
| free(k->value); |
| } |
| |
| /** |
| * 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. |
| */ |
| static void rule_map_validate(rule_map *rm) { |
| |
| 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++) { |
| rule = &(rm->m[i]); |
| |
| 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 (cnt == assert->length) { |
| list_append(&rm->violations, &assert->listify); |
| } |
| } |
| } |
| |
| /** |
| * Given a set of key value pairs, this will construct a new rule map. |
| * On error this function calls exit. |
| * @param keys |
| * Keys from a rule line to map |
| * @param num_of_keys |
| * The length of the keys array |
| * @param lineno |
| * The line number the keys were extracted from |
| * @return |
| * A rule map pointer. |
| */ |
| 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; |
| rule_map *new_map = NULL; |
| kvp *k = NULL; |
| key_map *r = NULL, *x = NULL; |
| bool seen[KVP_NUM_OF_RULES]; |
| |
| for (i = 0; i < KVP_NUM_OF_RULES; i++) |
| seen[i] = false; |
| |
| new_map = calloc(1, (num_of_keys * sizeof(key_map)) + sizeof(rule_map)); |
| 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++) { |
| k = &(keys[i]); |
| r = &(new_map->m[i]); |
| |
| for (j = 0; j < KVP_NUM_OF_RULES; j++) { |
| x = &(rules[j]); |
| |
| /* Only assign key name to map name */ |
| if (strcasecmp(k->key, x->name)) { |
| if (j == KVP_NUM_OF_RULES - 1) { |
| log_error("No match for key: %s\n", k->key); |
| goto err; |
| } |
| continue; |
| } |
| |
| if (seen[j]) { |
| log_error("Duplicated key: %s\n", k->key); |
| goto err; |
| } |
| seen[j] = true; |
| |
| memcpy(r, x, sizeof(key_map)); |
| |
| /* Assign rule map value to one from file */ |
| r->data = strdup(k->value); |
| if (!r->data) |
| goto oom; |
| |
| /* Enforce type check*/ |
| log_info("Validating keys!\n"); |
| 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 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); |
| int l = (new_map->key) ? strlen(new_map->key) : 0; |
| l = l + key_len + val_len; |
| l += 1; |
| |
| tmp = realloc(new_map->key, l); |
| if (!tmp) |
| goto oom; |
| |
| if (!new_map->key) |
| memset(tmp, 0, l); |
| |
| new_map->key = tmp; |
| |
| strncat(new_map->key, k->key, key_len); |
| strncat(new_map->key, k->value, val_len); |
| } |
| break; |
| } |
| free_kvp(k); |
| } |
| |
| if (new_map->key == NULL) { |
| log_error("Strange, no keys found, input file corrupt perhaps?\n"); |
| goto err; |
| } |
| |
| return new_map; |
| |
| oom: |
| log_error("Out of memory!\n"); |
| err: |
| if (new_map) { |
| rule_map_free(new_map, false); |
| for (; i < num_of_keys; i++) { |
| k = &(keys[i]); |
| free_kvp(k); |
| } |
| } |
| return NULL; |
| } |
| |
| /** |
| * Print the usage of the program |
| */ |
| static void usage() { |
| printf( |
| "checkseapp [options] <input file>\n" |
| "Processes an seapp_contexts file specified by argument <input file> (default stdin) " |
| "and allows later declarations to override previous ones on a match.\n" |
| "Options:\n" |
| "-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 or - for stdout. No argument runs in silent mode and outputs nothing\n"); |
| } |
| |
| static void init() { |
| |
| 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)); |
| } |
| 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); |
| } |
| } |
| } |
| |
| 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) { |
| log_error("Could not open file: %s error: %s\n", |
| pol.policy_file_name, strerror(errno)); |
| exit(EXIT_FAILURE); |
| } |
| |
| pol.handle = sepol_handle_create(); |
| if (!pol.handle) { |
| log_error("Could not create sepolicy handle: %s\n", |
| strerror(errno)); |
| exit(EXIT_FAILURE); |
| } |
| |
| if (sepol_policy_file_create(&pol.pf) < 0) { |
| log_error("Could not create sepolicy file: %s!\n", |
| strerror(errno)); |
| exit(EXIT_FAILURE); |
| } |
| |
| sepol_policy_file_set_fp(pol.pf, pol.policy_file); |
| sepol_policy_file_set_handle(pol.pf, pol.handle); |
| |
| if (sepol_policydb_create(&pol.db) < 0) { |
| log_error("Could not create sepolicy db: %s!\n", |
| strerror(errno)); |
| exit(EXIT_FAILURE); |
| } |
| |
| if (sepol_policydb_read(pol.db, pol.pf) < 0) { |
| log_error("Could not load policy file to db: invalid input file!\n"); |
| exit(EXIT_FAILURE); |
| } |
| } |
| |
| 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!"); |
| #endif |
| |
| } |
| |
| /** |
| * Handle parsing and setting the global flags for the command line |
| * options. This function calls exit on failure. |
| * @param argc |
| * argument count |
| * @param argv |
| * argument list |
| */ |
| static void handle_options(int argc, char *argv[]) { |
| |
| int c; |
| file_info *input_file; |
| |
| while ((c = getopt(argc, argv, "ho:p:vc")) != -1) { |
| switch (c) { |
| case 'h': |
| usage(); |
| exit(EXIT_SUCCESS); |
| case 'o': |
| out_file.name = optarg; |
| break; |
| case 'p': |
| pol.policy_file_name = optarg; |
| break; |
| case 'v': |
| log_set_verbose(); |
| break; |
| case 'c': |
| pol.vendor = true; |
| break; |
| case '?': |
| if (optopt == 'o' || optopt == 'p') |
| log_error("Option -%c requires an argument.\n", optopt); |
| else if (isprint (optopt)) |
| log_error("Unknown option `-%c'.\n", optopt); |
| else { |
| log_error( |
| "Unknown option character `\\x%x'.\n", |
| optopt); |
| } |
| default: |
| exit(EXIT_FAILURE); |
| } |
| } |
| |
| for (c = optind; c < argc; c++) { |
| |
| 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); |
| } |
| } |
| |
| /** |
| * Adds a rule to the hash table and to the ordered list if needed. |
| * @param rm |
| * The rule map to add. |
| */ |
| static void rule_add(rule_map *rm) { |
| |
| map_match cmp; |
| ENTRY e; |
| ENTRY *f; |
| hash_entry *entry; |
| hash_entry *tmp; |
| list *list_to_addto; |
| |
| e.key = rm->key; |
| e.data = NULL; |
| |
| log_info("Searching for key: %s\n", e.key); |
| /* Check to see if it has already been added*/ |
| f = hsearch(e, FIND); |
| |
| /* |
| * Since your only hashing on a partial key, the inputs we need to handle |
| * when you want to override the outputs for a given input set, as well as |
| * checking for duplicate entries. |
| */ |
| if (f) { |
| log_info("Existing entry found!\n"); |
| tmp = (hash_entry *)f->data; |
| cmp = rule_map_cmp(rm, tmp->r); |
| log_error("Duplicate line detected in file: %s\n" |
| "Lines %d and %d %s!\n", |
| rm->filename, tmp->r->lineno, rm->lineno, |
| map_match_str[cmp]); |
| rule_map_free(rm, false); |
| goto err; |
| } |
| /* It wasn't found, just add the rule map to the table */ |
| else { |
| |
| entry = malloc(sizeof(hash_entry)); |
| if (!entry) |
| goto oom; |
| |
| entry->r = rm; |
| e.data = entry; |
| |
| f = hsearch(e, ENTER); |
| if (f == NULL) { |
| goto oom; |
| } |
| |
| /* new entries must be added to the ordered list */ |
| entry->r = rm; |
| list_to_addto = rm->is_never_allow ? &nallow_list : &line_order_list; |
| list_append(list_to_addto, &entry->listify); |
| } |
| |
| return; |
| oom: |
| if (e.key) |
| free(e.key); |
| if (entry) |
| free(entry); |
| if (rm) |
| free(rm); |
| log_error("Out of memory in function: %s\n", __FUNCTION__); |
| err: |
| exit(EXIT_FAILURE); |
| } |
| |
| static void parse_file(file_info *in_file) { |
| |
| char *p; |
| size_t len; |
| 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; |
| |
| char line_buf[BUFSIZ]; |
| kvp keys[KVP_NUM_OF_RULES]; |
| |
| while (fgets(line_buf, sizeof(line_buf) - 1, in_file->file)) { |
| 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; |
| |
| /* 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; |
| |
| token = strtok_r(p, " \t", &saveptr); |
| if (!token) |
| goto err; |
| |
| token_cnt = 0; |
| memset(keys, 0, sizeof(kvp) * KVP_NUM_OF_RULES); |
| while (1) { |
| |
| name = token; |
| value = strchr(name, '='); |
| if (!value) |
| goto err; |
| *value++ = 0; |
| |
| keys[token_cnt].key = strdup(name); |
| if (!keys[token_cnt].key) |
| goto oom; |
| |
| keys[token_cnt].value = strdup(value); |
| if (!keys[token_cnt].value) |
| goto oom; |
| |
| token_cnt++; |
| |
| token = strtok_r(NULL, " \t", &saveptr); |
| if (!token) |
| break; |
| |
| if (token_cnt == KVP_NUM_OF_RULES) |
| goto oob; |
| |
| } /*End token parsing */ |
| |
| rule_map *r = rule_map_new(keys, token_cnt, lineno, in_file->name, is_never_allow); |
| if (!r) |
| goto err; |
| rule_add(r); |
| |
| } /* End file parsing */ |
| return; |
| |
| err: |
| log_error("Reading file: \"%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); |
| oob: |
| log_error("Reading file: \"%s\" line: %zu reason: the size of key pairs exceeds the MAX(%zu)\n", |
| in_file->name, lineno, KVP_NUM_OF_RULES); |
| 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; |
| coredomain_violation_entry *c; |
| 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"); |
| } |
| } |
| |
| bool coredomain_violation = false; |
| list_for_each(&coredomain_violation_list, cursor) { |
| c = list_entry(cursor, typeof(*c), listify); |
| fprintf(stderr, "Forbidden attribute " COREDOMAIN " assigned to domain \"%s\" in " |
| "File \"%s\" on line %d\n", c->domain, c->filename, c->lineno); |
| coredomain_violation = true; |
| } |
| |
| if (coredomain_violation) { |
| fprintf(stderr, "********************************************************************************\n"); |
| fprintf(stderr, "You tried to assign coredomain with vendor seapp_contexts, which is not allowed.\n" |
| "Either move offending entries to system, system_ext, or product seapp_contexts,\n" |
| "or remove 'coredomain' attribute from the domains.\n" |
| "See an example of how to fix this:\n" |
| "https://android-review.googlesource.com/2671075\n"); |
| fprintf(stderr, "********************************************************************************\n"); |
| found_issues = true; |
| } |
| |
| 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" |
| * ordering. |
| */ |
| static void output() { |
| |
| hash_entry *e; |
| list_element *cursor; |
| |
| 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"); |
| } |
| } |
| |
| /** |
| * This function is registered to the at exit handler and should clean up |
| * the programs dynamic resources, such as memory and fd's. |
| */ |
| static void cleanup() { |
| |
| /* Only close this when it was opened by me and not the crt */ |
| 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) { |
| |
| log_info("Closing file: %s\n", pol.policy_file_name); |
| fclose(pol.policy_file); |
| |
| if (pol.db) |
| sepol_policydb_free(pol.db); |
| |
| if (pol.pf) |
| sepol_policy_file_free(pol.pf); |
| |
| if (pol.handle) |
| sepol_handle_destroy(pol.handle); |
| } |
| |
| log_info("Freeing lists\n"); |
| list_free(&input_file_list); |
| list_free(&line_order_list); |
| list_free(&nallow_list); |
| list_free(&coredomain_violation_list); |
| hdestroy(); |
| } |
| |
| int main(int argc, char *argv[]) { |
| if (!hcreate(TABLE_SIZE)) { |
| log_error("Could not create hash table: %s\n", strerror(errno)); |
| exit(EXIT_FAILURE); |
| } |
| atexit(cleanup); |
| handle_options(argc, argv); |
| init(); |
| 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); |
| } |