From ad3cb39e54040e5a03328d8006f428579d1654e0 Mon Sep 17 00:00:00 2001 From: William Roberts Date: Thu, 24 Sep 2015 18:10:54 -0700 Subject: [PATCH] checkfc: add attribute test Enable checkfc to check *_contexts against a set of valid attributes which must be associated with all types in the contexts file that is being checked. Since it's imperative that checkfc knows which file its checking to choose the proper attribute set, the -s option is introduced to indicate the service_contexts file. The property_contexts file continues to use the existing -p and file_contexts requires no specification, aka it's the default. Failure examples: file_contexts: Error: type "init" is not of set: "fs_type, dev_type, file_type" service_contexts: Error: type "init_exec" is not of set: "service_manager_type" property_contexts: Error: type "bluetooth_service" is not of set: "property_type" Change-Id: I62077e4d0760858a9459e753e14dfd209868080f Signed-off-by: William Roberts --- Android.mk | 4 +- attributes | 10 ++ tools/checkfc.c | 366 +++++++++++++++++++++++++++++++++++++++--------- 3 files changed, 311 insertions(+), 69 deletions(-) diff --git a/Android.mk b/Android.mk index a41fc037a..9b39a5993 100644 --- a/Android.mk +++ b/Android.mk @@ -351,7 +351,7 @@ $(service_contexts.tmp): $(all_svc_files) $(all_svcfiles_with_nl) $(built_nl) $(LOCAL_BUILT_MODULE): PRIVATE_SEPOLICY := $(built_sepolicy) $(LOCAL_BUILT_MODULE): $(service_contexts.tmp) $(built_sepolicy) $(HOST_OUT_EXECUTABLES)/checkfc $(ACP) @mkdir -p $(dir $@) - $(hide) $(HOST_OUT_EXECUTABLES)/checkfc -p $(PRIVATE_SEPOLICY) $< + $(hide) $(HOST_OUT_EXECUTABLES)/checkfc -s $(PRIVATE_SEPOLICY) $< $(hide) $(ACP) $< $@ built_svc := $(LOCAL_BUILT_MODULE) @@ -375,7 +375,7 @@ $(general_service_contexts.tmp): $(addprefix $(LOCAL_PATH)/, service_contexts) $(LOCAL_BUILT_MODULE): PRIVATE_SEPOLICY := $(built_general_sepolicy) $(LOCAL_BUILT_MODULE): $(general_service_contexts.tmp) $(built_general_sepolicy) $(HOST_OUT_EXECUTABLES)/checkfc $(ACP) @mkdir -p $(dir $@) - $(hide) $(HOST_OUT_EXECUTABLES)/checkfc -p $(PRIVATE_SEPOLICY) $< + $(hide) $(HOST_OUT_EXECUTABLES)/checkfc -s $(PRIVATE_SEPOLICY) $< $(hide) $(ACP) $< $@ general_service_contexts.tmp := diff --git a/attributes b/attributes index 3f4d5ef15..485b3e9a9 100644 --- a/attributes +++ b/attributes @@ -3,6 +3,8 @@ # # All types used for devices. +# On change, update CHECK_FC_ASSERT_ATTRS +# in tools/checkfc.c attribute dev_type; # All types used for processes. @@ -19,6 +21,8 @@ attribute domain; attribute domain_deprecated; # All types used for filesystems. +# On change, update CHECK_FC_ASSERT_ATTRS +# definition in tools/checkfc.c. attribute fs_type; # All types used for context= mounts. @@ -26,6 +30,8 @@ attribute contextmount_type; # All types used for files that can exist on a labeled fs. # Do not use for pseudo file types. +# On change, update CHECK_FC_ASSERT_ATTRS +# definition in tools/checkfc.c. attribute file_type; # All types used for domain entry points. @@ -53,6 +59,8 @@ attribute netif_type; attribute port_type; # All types used for property service +# On change, update CHECK_PC_ASSERT_ATTRS +# definition in tools/checkfc.c. attribute property_type; # All properties defined in core SELinux policy. Should not be @@ -69,6 +77,8 @@ attribute app_api_service; attribute system_api_service; # All types used for services managed by service_manager. +# On change, update CHECK_SC_ASSERT_ATTRS +# definition in tools/checkfc.c. attribute service_manager_type; # All domains that can override MLS restrictions. diff --git a/tools/checkfc.c b/tools/checkfc.c index 3b9a21698..602a05f76 100644 --- a/tools/checkfc.c +++ b/tools/checkfc.c @@ -1,35 +1,303 @@ #include +#include #include #include -#include +#include +#include +#include #include #include #include -static int nerr; +static const char * const CHECK_FC_ASSERT_ATTRS[] = { "fs_type", "dev_type", "file_type", NULL }; +static const char * const CHECK_PC_ASSERT_ATTRS[] = { "property_type", NULL }; +static const char * const CHECK_SC_ASSERT_ATTRS[] = { "service_manager_type", NULL }; + +typedef enum filemode filemode; +enum filemode { + filemode_file_contexts = 0, + filemode_property_contexts, + filemode_service_contexts +}; + +static struct { + /* policy */ + struct { + union { + /* Union these so we don't have to cast */ + sepol_policydb_t *sdb; + policydb_t *pdb; + }; + sepol_policy_file_t *pf; + sepol_handle_t *handle; + FILE *file; +#define SEHANDLE_CNT 2 + struct selabel_handle *sehnd[SEHANDLE_CNT]; + } sepolicy; + + /* assertions */ + struct { + const char * const *attrs; /* for the original set to print on error */ + ebitmap_t set; /* the ebitmap representation of the attrs */ + } assert; + +} global_state; + +static const char * const *filemode_to_assert_attrs(filemode mode) +{ + switch (mode) { + case filemode_file_contexts: + return CHECK_FC_ASSERT_ATTRS; + case filemode_property_contexts: + return CHECK_PC_ASSERT_ATTRS; + case filemode_service_contexts: + return CHECK_SC_ASSERT_ATTRS; + } + /* die on invalid parameters */ + fprintf(stderr, "Error: Invalid mode of operation: %d\n", mode); + exit(1); +} + +static int get_attr_bit(policydb_t *policydb, const char *attr_name) +{ + struct type_datum *attr = hashtab_search(policydb->p_types.table, (char *)attr_name); + if (!attr) { + fprintf(stderr, "Error: \"%s\" is not defined in this policy.\n", attr_name); + return -1; + } + + if (attr->flavor != TYPE_ATTRIB) { + fprintf(stderr, "Error: \"%s\" is not an attribute in this policy.\n", attr_name); + return -1; + } + + return attr->s.value - 1; +} + +static bool ebitmap_attribute_assertion_init(ebitmap_t *assertions, const char * const attributes[]) +{ + + while (*attributes) { + + int bit_pos = get_attr_bit(global_state.sepolicy.pdb, *attributes); + if (bit_pos < 0) { + /* get_attr_bit() logs error */ + return false; + } + + int err = ebitmap_set_bit(assertions, bit_pos, 1); + if (err) { + fprintf(stderr, "Error: setting bit on assertion ebitmap!\n"); + return false; + } + attributes++; + } + return true; +} + +static bool is_type_of_attribute_set(policydb_t *policydb, const char *type_name, + ebitmap_t *attr_set) +{ + struct type_datum *type = hashtab_search(policydb->p_types.table, (char *)type_name); + if (!type) { + fprintf(stderr, "Error: \"%s\" is not defined in this policy.\n", type_name); + return false; + } + + if (type->flavor != TYPE_TYPE) { + fprintf(stderr, "Error: \"%s\" is not a type in this policy.\n", type_name); + return false; + } + + ebitmap_t dst; + ebitmap_init(&dst); + + /* Take the intersection, if the set is empty, then its a failure */ + int rc = ebitmap_and(&dst, attr_set, &policydb->type_attr_map[type->s.value - 1]); + if (rc) { + fprintf(stderr, "Error: Could not perform ebitmap_and: %d\n", rc); + exit(1); + } + + bool res = (bool)ebitmap_length(&dst); + + ebitmap_destroy(&dst); + return res; +} + +static void dump_char_array(FILE *stream, const char * const *strings) +{ + + const char * const *p = strings; + + fprintf(stream, "\""); + + while (*p) { + const char *s = *p++; + const char *fmt = *p ? "%s, " : "%s\""; + fprintf(stream, fmt, s); + } +} static int validate(char **contextp) { - char *context = *contextp; - if (sepol_check_context(context) < 0) { - nerr++; - return -1; - } - return 0; + bool res; + char *context = *contextp; + + sepol_context_t *ctx; + int rc = sepol_context_from_string(global_state.sepolicy.handle, context, + &ctx); + if (rc < 0) { + fprintf(stderr, "Error: Could not allocate context from string"); + exit(1); + } + + rc = sepol_context_check(global_state.sepolicy.handle, + global_state.sepolicy.sdb, ctx); + if (rc < 0) { + goto out; + } + + const char *type_name = sepol_context_get_type(ctx); + + uint32_t len = ebitmap_length(&global_state.assert.set); + if (len > 0) { + res = !is_type_of_attribute_set(global_state.sepolicy.pdb, type_name, + &global_state.assert.set); + if (res) { + fprintf(stderr, "Error: type \"%s\" is not of set: ", type_name); + dump_char_array(stderr, global_state.assert.attrs); + fprintf(stderr, "\n"); + /* The calls above did not affect rc, so set error before going to out */ + rc = -1; + goto out; + } + } + /* Success: Although it should be 0, we explicitly set rc to 0 for clarity */ + rc = 0; + + out: + sepol_context_free(ctx); + return rc; } static void usage(char *name) { - fprintf(stderr, "usage1: %s [-p] sepolicy context_file\n\n", name); - fprintf(stderr, "Parses a context file and checks for syntax errors.\n"); - fprintf(stderr, "The context_file is assumed to be a file_contexts file\n"); - fprintf(stderr, "unless the -p option is used to indicate the property backend.\n\n"); + fprintf(stderr, "usage1: %s [-p|-s] sepolicy context_file\n\n" + "Parses a context file and checks for syntax errors.\n" + "The context_file is assumed to be a file_contexts file\n" + "unless the -p or -s option is used to indicate the property or service backend respectively.\n\n" - fprintf(stderr, "usage2: %s -c file_contexts1 file_contexts2\n\n", name); - fprintf(stderr, "Compares two file contexts files and reports one of subset, equal, superset, or incomparable.\n"); - fprintf(stderr, "\n"); + "usage2: %s -c file_contexts1 file_contexts2\n\n" + "Compares two file contexts files and reports one of subset, equal, superset, or incomparable.\n\n", + name, name); exit(1); } +static void cleanup(void) { + + if (global_state.sepolicy.file) { + fclose(global_state.sepolicy.file); + } + + if (global_state.sepolicy.sdb) { + sepol_policydb_free(global_state.sepolicy.sdb); + } + + if (global_state.sepolicy.pf) { + sepol_policy_file_free(global_state.sepolicy.pf); + } + + if (global_state.sepolicy.handle) { + sepol_handle_destroy(global_state.sepolicy.handle); + } + + ebitmap_destroy(&global_state.assert.set); + + int i; + for (i = 0; i < SEHANDLE_CNT; i++) { + struct selabel_handle *sehnd = global_state.sepolicy.sehnd[i]; + if (sehnd) { + selabel_close(sehnd); + } + } +} + +static void do_compare_and_die_on_error(struct selinux_opt opts[], unsigned int backend, char *paths[]) +{ + enum selabel_cmp_result result; + char *result_str[] = { "subset", "equal", "superset", "incomparable" }; + int i; + + opts[0].value = NULL; /* not validating against a policy when comparing */ + + for (i = 0; i < SEHANDLE_CNT; i++) { + opts[1].value = paths[i]; + global_state.sepolicy.sehnd[i] = selabel_open(backend, opts, 2); + if (!global_state.sepolicy.sehnd[i]) { + fprintf(stderr, "Error: could not load context file from %s\n", paths[i]); + exit(1); + } + } + + result = selabel_cmp(global_state.sepolicy.sehnd[0], global_state.sepolicy.sehnd[1]); + printf("%s\n", result_str[result]); +} + +static void do_fc_check_and_die_on_error(struct selinux_opt opts[], unsigned int backend, filemode mode, + const char *sepolicy_file, const char *context_file) +{ + global_state.sepolicy.file = fopen(sepolicy_file, "r"); + if (!global_state.sepolicy.file) { + perror("Error: could not open policy file"); + exit(1); + } + + global_state.sepolicy.handle = sepol_handle_create(); + if (!global_state.sepolicy.handle) { + fprintf(stderr, "Error: could not create policy handle: %s\n", strerror(errno)); + exit(1); + } + + if (sepol_policy_file_create(&global_state.sepolicy.pf) < 0) { + perror("Error: could not create policy handle"); + exit(1); + } + + sepol_policy_file_set_fp(global_state.sepolicy.pf, global_state.sepolicy.file); + sepol_policy_file_set_handle(global_state.sepolicy.pf, global_state.sepolicy.handle); + + int rc = sepol_policydb_create(&global_state.sepolicy.sdb); + if (rc < 0) { + perror("Error: could not create policy db"); + exit(1); + } + + rc = sepol_policydb_read(global_state.sepolicy.sdb, global_state.sepolicy.pf); + if (rc < 0) { + perror("Error: could not read file into policy db"); + exit(1); + } + + global_state.assert.attrs = filemode_to_assert_attrs(mode); + + bool ret = ebitmap_attribute_assertion_init(&global_state.assert.set, global_state.assert.attrs); + if (!ret) { + /* error messages logged by ebitmap_attribute_assertion_init() */ + exit(1); + } + + selinux_set_callback(SELINUX_CB_VALIDATE, + (union selinux_callback)&validate); + + opts[1].value = context_file; + + global_state.sepolicy.sehnd[0] = selabel_open(backend, opts, 2); + if (!global_state.sepolicy.sehnd[0]) { + fprintf(stderr, "Error: could not load context file from %s\n", context_file); + exit(1); + } +} + int main(int argc, char **argv) { struct selinux_opt opts[] = { @@ -40,17 +308,22 @@ int main(int argc, char **argv) // Default backend unless changed by input argument. unsigned int backend = SELABEL_CTX_FILE; - FILE *fp; bool compare = false; - struct selabel_handle *sehnd[2]; char c; - while ((c = getopt(argc, argv, "cph")) != -1) { + filemode mode = filemode_file_contexts; + + while ((c = getopt(argc, argv, "cps")) != -1) { switch (c) { case 'c': compare = true; break; case 'p': + mode = filemode_property_contexts; + backend = SELABEL_CTX_ANDROID_PROP; + break; + case 's': + mode = filemode_service_contexts; backend = SELABEL_CTX_ANDROID_PROP; break; case 'h': @@ -69,57 +342,16 @@ int main(int argc, char **argv) usage(argv[0]); } + atexit(cleanup); + if (compare) { - enum selabel_cmp_result result; - char *result_str[] = { "subset", "equal", "superset", "incomparable" }; - int i; + do_compare_and_die_on_error(opts, backend, &(argv[index])); + } else { + /* remaining args are sepolicy file and context file */ + char *sepolicy_file = argv[index]; + char *context_file = argv[index + 1]; - opts[0].value = NULL; /* not validating against a policy when comparing */ - - for (i = 0; i < 2; i++) { - opts[1].value = argv[index+i]; - sehnd[i] = selabel_open(backend, opts, 2); - if (!sehnd[i]) { - fprintf(stderr, "Error loading context file from %s\n", argv[index+i]); - exit(1); - } - } - - result = selabel_cmp(sehnd[0], sehnd[1]); - for (i = 0; i < 2; i++) - selabel_close(sehnd[i]); - printf("%s\n", result_str[result]); - exit(0); + do_fc_check_and_die_on_error(opts, backend, mode, sepolicy_file, context_file); } - - // remaining args are sepolicy file and context file - char *sepolicyFile = argv[index]; - char *contextFile = argv[index + 1]; - - fp = fopen(sepolicyFile, "r"); - if (!fp) { - perror(sepolicyFile); - exit(1); - } - if (sepol_set_policydb_from_file(fp) < 0) { - fprintf(stderr, "Error loading policy from %s\n", sepolicyFile); - exit(1); - } - - selinux_set_callback(SELINUX_CB_VALIDATE, - (union selinux_callback)&validate); - - opts[1].value = contextFile; - - sehnd[0] = selabel_open(backend, opts, 2); - if (!sehnd[0]) { - fprintf(stderr, "Error loading context file from %s\n", contextFile); - exit(1); - } - if (nerr) { - fprintf(stderr, "Invalid context file found in %s\n", contextFile); - exit(1); - } - exit(0); }