#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> #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__); } typedef struct line_order_list line_order_list; 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; 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 }; /** * Used as options to rule_map_free() * * This is needed to get around the fact that GNU C's hash_map doesn't copy the key, so * we cannot free a key when overrding rule_map's in the table. */ enum rule_map_switch { rule_map_preserve_key, /** Used to preserve the key in the rule_map, ie don't free it*/ rule_map_destroy_key /** Used when you need a full free of the rule_map structure*/ }; /** * The expected "type" of data the value in the key * value pair should be. */ 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; }; /** * 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; data_type type; char *data; }; /** * 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 { char *key; /** key value before hashing */ size_t length; /** length of the key map */ int lineno; /** Line number rule was encounter on */ key_map m[]; /** key value mapping */ }; struct hash_entry { 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; }; /** 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; /** output file path name */ static char *out_file_name = NULL; /** input file path name */ static char *in_file_name = NULL; static policy_info pol = { .policy_file_name = NULL, .policy_file = NULL, .db = NULL, .pf = NULL, .handle = NULL, .con = NULL }; /** * 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", .type = dt_bool, .dir = dir_in, .data = NULL }, { .name = "isOwner", .type = dt_bool, .dir = dir_in, .data = NULL }, { .name = "user", .type = dt_string, .dir = dir_in, .data = NULL }, { .name = "seinfo", .type = dt_string, .dir = dir_in, .data = NULL }, { .name = "name", .type = dt_string, .dir = dir_in, .data = NULL }, { .name = "path", .type = dt_string, .dir = dir_in, .data = NULL }, /*Outputs*/ { .name = "domain", .type = dt_string, .dir = dir_out, .data = NULL }, { .name = "type", .type = dt_string, .dir = dir_out, .data = NULL }, { .name = "levelFromUid", .type = dt_bool, .dir = dir_out, .data = NULL }, { .name = "levelFrom", .type = dt_string, .dir = dir_out, .data = NULL }, { .name = "level", .type = dt_string, .dir = dir_out, .data = NULL }, }; /** * Head pointer to a linked list of * rule map table entries, used for * preserving the order of entries * based on "first encounter" */ static line_order_list *list_head = NULL; /** * Pointer to the tail of the list for * quick appends to the end of the list */ static line_order_list *list_tail = NULL; /** * 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); } /** * Checks for a type in the policy. * @param db * The policy db to search * @param type * The type to search for * @return * 1 if the type is found, 0 otherwise. * @warning * This function always returns 1 if libsepol is not linked * statically to this executable and LINK_SEPOL_STATIC is not * defined. */ int check_type(sepol_policydb_t *db, char *type) { int rc = 1; #if defined(LINK_SEPOL_STATIC) policydb_t *d = (policydb_t *)db; hashtab_datum_t dat; dat = hashtab_search(d->p_types.table, type); rc = (dat == NULL) ? 0 : 1; #endif return rc; } /** * 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 * 1 if valid, 0 if invalid */ static int key_map_validate(key_map *m, int lineno) { int rc = 1; int ret = 1; char *key = m->name; char *value = m->data; data_type type = m->type; log_info("Validating %s=%s\n", key, value); /* 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, out_file_name); rc = 0; goto out; } if (!strcasecmp(key, "levelFrom") && (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, out_file_name); rc = 0; goto out; } /* * If there is no policy file present, * then it is not going to enforce the types against the policy so just return. * User and name cannot really be checked. */ if (!pol.policy_file) { goto out; } else if (!strcasecmp(key, "type") || !strcasecmp(key, "domain")) { if(!check_type(pol.db, value)) { log_error("Could not find selinux type \"%s\" on line: %d in file: %s\n", value, lineno, out_file_name); rc = 0; } goto out; } else if (!strcasecmp(key, "level")) { 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, out_file_name); rc = 0; goto out; } } 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; if (rmA->length != rmB->length) return map_no_matches; 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 (mA->type != mB->type) continue; 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. */ static void rule_map_free(rule_map *rm, rule_map_switch s __attribute__((unused)) /* only glibc builds, ignored otherwise */) { size_t i; size_t len = rm->length; for (i = 0; i < len; i++) { key_map *m = &(rm->m[i]); free(m->data); } /* hdestroy() frees comparsion keys for non glibc */ #ifdef __GLIBC__ if(s == rule_map_destroy_key && rm->key) free(rm->key); #endif 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. * Note that this function logs all errors. * * Current Checks: * 1. That a specified name entry should have a specified seinfo entry as well. * @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) { size_t i; bool found_name = false; bool found_seinfo = false; char *name = NULL; const key_map *tmp; for(i=0; i < rm->length; i++) { tmp = &(rm->m[i]); if(!strcmp(tmp->name, "name") && tmp->data) { name = tmp->data; found_name = true; } if(!strcmp(tmp->name, "seinfo") && tmp->data && strcmp(tmp->data, "default")) { found_seinfo = true; } } 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; } /** * 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) { size_t i = 0, j = 0; bool valid_rule; 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->length = num_of_keys; new_map->lineno = lineno; /* 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 (i == KVP_NUM_OF_RULES) { 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, lineno)) { log_error("Could not validate\n"); goto err; } /* Only build key off of inputs*/ if (r->dir == dir_in) { 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; } valid_rule = rule_map_validate(new_map); if(!valid_rule) { /* Error message logged from rule_map_validate() */ goto err; } return new_map; oom: log_error("Out of memory!\n"); err: if(new_map) { rule_map_free(new_map, rule_map_destroy_key); 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, default is stdout\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)); exit(EXIT_FAILURE); } } /* 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); } } 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 lod policy file to db: %s!\n", strerror(errno)); exit(EXIT_FAILURE); } } 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); #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; int num_of_args; while ((c = getopt(argc, argv, "ho:p:sv")) != -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 '?': 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); } } 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; cursor = list_head; while (cursor) { e = cursor->e; rule_map_free(e->r, rule_map_destroy_key); tmp = cursor; cursor = cursor->next; free(e); free(tmp); } } /** * 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; e.key = rm->key; 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", out_file_name, tmp->r->lineno, rm->lineno, map_match_str[cmp]); rule_map_free(rm, rule_map_destroy_key); 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_add(entry); } 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); } /** * 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() { char line_buf[BUFSIZ]; char *token; unsigned lineno = 0; char *p, *name = NULL, *value = NULL, *saveptr; size_t len; kvp keys[KVP_NUM_OF_RULES]; size_t token_cnt = 0; while (fgets(line_buf, sizeof line_buf - 1, input_file)) { lineno++; log_info("Got line %d\n", lineno); len = strlen(line_buf); if (line_buf[len - 1] == '\n') line_buf[len - 1] = '\0'; p = line_buf; while (isspace(*p)) p++; 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; } /*End token parsing */ rule_map *r = rule_map_new(keys, token_cnt, lineno); if (!r) goto err; rule_add(r); } /* End file parsing */ return; err: log_error("reading %s, line %u, name %s, value %s\n", in_file_name, lineno, name, value); exit(EXIT_FAILURE); oom: log_error("In function %s: Out of memory\n", __FUNCTION__); 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() { rule_map *r; line_order_list *cursor; cursor = list_head; while (cursor) { r = cursor->e->r; rule_map_print(output_file, r); cursor = cursor->next; fprintf(output_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 && 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 (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 list\n"); list_free(); 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"); output(); log_info("Success, generated output\n"); exit(EXIT_SUCCESS); }