From 1d190c459abba61f916c4dceede492c372bdcd75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20G=C3=B6ttsche?= Date: Mon, 10 May 2021 14:50:58 +0200 Subject: [PATCH 1/4] Infer parameter types of interfaces and templates Infer the type of parameters of templates and interfaces. This can help find mis-declarations, see next commit. In the future this can be used for further checks, e.g. find discrepancies between the kind of a parameter and its documentation. --- .gitignore | 1 + src/Makefile.am | 2 +- src/infer.c | 333 ++++++++++++++++++++++ src/infer.h | 64 +++++ src/maps.c | 71 ++++- src/maps.h | 14 +- src/runner.c | 5 + src/tree.c | 142 +++++++++ src/tree.h | 14 + tests/Makefile.am | 19 +- tests/check_infer.c | 254 +++++++++++++++++ tests/sample_policy_files/infer_loop.if | 7 + tests/sample_policy_files/simple_infer.if | 22 ++ 13 files changed, 927 insertions(+), 21 deletions(-) create mode 100644 src/infer.c create mode 100644 src/infer.h create mode 100644 tests/check_infer.c create mode 100644 tests/sample_policy_files/infer_loop.if create mode 100644 tests/sample_policy_files/simple_infer.if diff --git a/.gitignore b/.gitignore index 00c1bdae..89ff732f 100644 --- a/.gitignore +++ b/.gitignore @@ -32,6 +32,7 @@ tests/check_startup tests/check_te_checks tests/check_ordering tests/check_perm_macro +tests/check_infer tests/functional/policies/parse_errors/test3_tmp.if tests/functional/policies/parse_errors/test5_tmp.te tests/functional/policies/parse_errors/test6_tmp.if diff --git a/src/Makefile.am b/src/Makefile.am index df108cd4..92d42ebe 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -13,7 +13,7 @@ # limitations under the License. bin_PROGRAMS = selint -selint_SOURCES = main.c lex.l parse.y tree.c tree.h selint_error.h parse_functions.c parse_functions.h maps.c maps.h runner.c runner.h parse_fc.c parse_fc.h template.c template.h file_list.c file_list.h check_hooks.c check_hooks.h fc_checks.c fc_checks.h util.c util.h if_checks.c if_checks.h selint_config.c selint_config.h string_list.c string_list.h startup.c startup.h te_checks.c te_checks.h ordering.c ordering.h color.c color.h perm_macro.c perm_macro.h +selint_SOURCES = main.c lex.l parse.y tree.c tree.h selint_error.h parse_functions.c parse_functions.h maps.c maps.h runner.c runner.h parse_fc.c parse_fc.h template.c template.h file_list.c file_list.h check_hooks.c check_hooks.h fc_checks.c fc_checks.h util.c util.h if_checks.c if_checks.h selint_config.c selint_config.h string_list.c string_list.h startup.c startup.h te_checks.c te_checks.h ordering.c ordering.h color.c color.h perm_macro.c perm_macro.h infer.c infer.h BUILT_SOURCES = parse.h AM_YFLAGS = -d -Wno-yacc -Werror=conflicts-rr -Werror=conflicts-sr diff --git a/src/infer.c b/src/infer.c new file mode 100644 index 00000000..fcc24882 --- /dev/null +++ b/src/infer.c @@ -0,0 +1,333 @@ +/* +* Copyright 2021 The SELint Contributors +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +#include +#include +#include +#include + +#include "infer.h" +#include "color.h" +#include "maps.h" +#include "util.h" + +/* Uses directly in the testsuite */ +enum selint_error infer_interfaces_shallow(const struct policy_node *node); +enum selint_error infer_interfaces_deep(const struct policy_node *node); + +static enum param_flavor name_to_param_flavor(enum name_flavor flavor) +{ + switch (flavor) { + case NAME_TYPE: + return PARAM_TYPE; + case NAME_TYPEATTRIBUTE: + return PARAM_TYPEATTRIBUTE; + case NAME_TYPE_OR_ATTRIBUTE: + return PARAM_TYPE_OR_ATTRIBUTE; + case NAME_ROLE: + return PARAM_ROLE; + case NAME_ROLEATTRIBUTE: + return PARAM_ROLEATTRIBUTE; + case NAME_ROLE_OR_ATTRIBUTE: + return PARAM_ROLE_OR_ATTRIBUTE; + case NAME_CLASS: + return PARAM_CLASS; + case NAME_OBJECT_NAME: + return PARAM_OBJECT_NAME; + default: + // should never happen + return PARAM_UNKNOWN; + } +} + +enum infer_type { IN_SHALLOW, IN_DEEP }; + +struct infer_data { + struct interface_trait *if_data; + enum infer_type mode; + const struct policy_node *node; +}; + +static const char *trait_type_to_str(enum trait_type t) +{ + switch (t) { + case INTERFACE_TRAIT: + return "interface"; + case TEMPLATE_TRAIT: + return "template"; + case MACRO_TRAIT: + return "macro"; + default: + // should never happen + return "unknown-trait-type"; + } +} + +static void infer_func(const char *name, enum name_flavor flavor, unsigned short id, void *visitor_data) +{ + if (!name) { + return; + } + + struct infer_data *data = visitor_data; + + const char *dollar = strchr(name, '$'); + if (!dollar) { + return; + } + + if (0 == strcmp(name, "$*") && flavor == NAME_IF_PARAM && id == 1) { + const struct interface_trait *call_trait = look_up_in_if_traits_map(data->node->data.ic_data->name); + if (!call_trait) { + print_if_verbose("No call trait for %s\n", data->node->data.ic_data->name); + } else { + for (int i = 0; i < TRAIT_MAX_PARAMETERS; i++) { + data->if_data->parameters[i] = call_trait->parameters[i]; + } + } + return; + } + + char *param_end; + errno = 0; + unsigned long param_no = strtoul(dollar + 1, ¶m_end, 10); + if (param_no == 0 || errno != 0) { + fprintf(stderr, "%sError%s: Failed to parse parameter number from name '%s' in %s %s!\n", + color_error(), color_reset(), + name, + trait_type_to_str(data->if_data->type), + data->if_data->name); + return; + } + param_no--; // start counting at 0 ($0 is invalid) + if (param_no > TRAIT_MAX_PARAMETERS) { + fprintf(stderr, "%sWarning%s: Only up to %u parameters supported, parsed %lu from name '%s' in %s %s!\n", + color_warning(), color_reset(), + TRAIT_MAX_PARAMETERS, + param_no, + name, + trait_type_to_str(data->if_data->type), + data->if_data->name); + return; + } + + // skip dash of exclusions + if (name[0] == '-') { + name = name + 1; + } + + if (dollar == name && *param_end == '\0') { + // name is just a parameter, e.g. '$1' + if (data->if_data->parameters[param_no] < PARAM_FINAL_INFERRED) { + if (data->mode == IN_DEEP && flavor == NAME_IF_PARAM) { + const struct interface_trait *call_trait = look_up_in_if_traits_map(data->node->data.ic_data->name); + if (!call_trait) { + print_if_verbose("No call trait for %s\n", data->node->data.ic_data->name); + } else { + data->if_data->parameters[param_no] = call_trait->parameters[id-1]; + } + } else { + data->if_data->parameters[param_no] = name_to_param_flavor(flavor); + } + } + return; + } + + if (data->if_data->parameters[param_no] < PARAM_FINAL_INFERRED) { + data->if_data->parameters[param_no] = PARAM_TEXT; + } +} + +static enum selint_error infer_interface(struct interface_trait *if_trait, const struct policy_node *node, enum infer_type mode) +{ + struct infer_data data = { if_trait, mode, NULL }; + static unsigned short nesting = 1; + + if (nesting > 40) { + return SELINT_IF_CALL_LOOP; + } + + for (; node && node->flavor != NODE_INTERFACE_DEF && node->flavor != NODE_TEMP_DEF; node = dfs_next(node)) { + if (mode == IN_DEEP && node->flavor == NODE_IF_CALL) { + const char *call_name = node->data.ic_data->name; + struct interface_trait *call_trait = look_up_in_if_traits_map(call_name); + if (!call_trait) { + print_if_verbose("No call trait found for %s\n", call_name); + } else if (!call_trait->is_inferred && call_trait->type != MACRO_TRAIT) { + nesting++; + enum selint_error ret = infer_interface(call_trait, call_trait->node->first_child, mode); + nesting--; + if (ret != SELINT_SUCCESS) { + return ret; + } + } + } + + data.node = node; + visit_names_in_node(node, infer_func, &data); + } + + return SELINT_SUCCESS; +} + +enum selint_error infer_interfaces_shallow(const struct policy_node *node) +{ + for (const struct policy_node *cur_node = node; cur_node; cur_node = cur_node->next) { + // skip non ifs + if (cur_node->flavor != NODE_INTERFACE_DEF && cur_node->flavor != NODE_TEMP_DEF) { + continue; + } + + struct interface_trait *if_trait = malloc(sizeof(struct interface_trait)); + if_trait->name = strdup(cur_node->data.str); + if_trait->type = (cur_node->flavor == NODE_TEMP_DEF) ? TEMPLATE_TRAIT : INTERFACE_TRAIT; + if_trait->is_inferred = false; + memset(if_trait->parameters, 0, sizeof if_trait->parameters); + if_trait->node = cur_node; + + enum selint_error ret = infer_interface(if_trait, cur_node->first_child, IN_SHALLOW); + if (ret != SELINT_SUCCESS) { + return ret; + } + + bool is_inferred = true; + for (int i = 0; i < TRAIT_MAX_PARAMETERS; ++i) { + if (if_trait->parameters[i] == PARAM_UNKNOWN) { + is_inferred = false; + break; + } + } + if_trait->is_inferred = is_inferred; + + insert_into_if_traits_map(cur_node->data.str, if_trait); + } + + return SELINT_SUCCESS; +} + +enum selint_error infer_interfaces_deep(const struct policy_node *node) +{ + for (const struct policy_node *cur_node = node; cur_node; cur_node = cur_node->next) { + // skip non ifs + if (cur_node->flavor != NODE_INTERFACE_DEF && cur_node->flavor != NODE_TEMP_DEF) { + continue; + } + + const char *if_name = cur_node->data.str; + struct interface_trait *if_trait = look_up_in_if_traits_map(if_name); + + if (if_trait->is_inferred) { + continue; + } + + enum selint_error ret = infer_interface(if_trait, cur_node->first_child, IN_DEEP); + if (ret != SELINT_SUCCESS) { + return ret; + } + for (int i = 0; i < TRAIT_MAX_PARAMETERS; ++i) { + if (if_trait->parameters[i] == PARAM_UNKNOWN) { + print_if_verbose("Parameter %d of %s %s not inferred\n", + i + 1, + trait_type_to_str(if_trait->type), + if_trait->name); + } + } + if_trait->is_inferred = true; + } + + return SELINT_SUCCESS; +} + +static void add_refpolicy_macro(const char *name, int param_count, const enum param_flavor flavors[]) +{ + struct interface_trait *if_trait = malloc(sizeof(struct interface_trait)); + if_trait->name = strdup(name); + if_trait->type = MACRO_TRAIT; + if_trait->is_inferred = true; + if_trait->node = NULL; + for (int i = 0; i < TRAIT_MAX_PARAMETERS; i++) { + if (i < param_count) { + if_trait->parameters[i] = flavors[i]; + } else { + if_trait->parameters[i] = PARAM_INITIAL; + } + } + + insert_into_if_traits_map(name, if_trait); +} + +enum selint_error infer_all_interfaces(const struct policy_file_list *files) +{ + // manually insert common refpolicy macros, since macro definitions are not + // part of the internal policy representation + add_refpolicy_macro("can_exec", + 2, + (enum param_flavor[2]){ PARAM_TYPE_OR_ATTRIBUTE, PARAM_TYPE_OR_ATTRIBUTE }); + add_refpolicy_macro("filetrans_pattern", + 5, + (enum param_flavor[5]){ PARAM_TYPE_OR_ATTRIBUTE, PARAM_TYPE_OR_ATTRIBUTE, PARAM_TYPE, PARAM_CLASS, PARAM_OBJECT_NAME }); + add_refpolicy_macro("filetrans_add_pattern", + 5, + (enum param_flavor[5]){ PARAM_TYPE_OR_ATTRIBUTE, PARAM_TYPE_OR_ATTRIBUTE, PARAM_TYPE, PARAM_CLASS, PARAM_OBJECT_NAME }); + add_refpolicy_macro("domtrans_pattern", + 3, + (enum param_flavor[3]){ PARAM_TYPE_OR_ATTRIBUTE, PARAM_TYPE_OR_ATTRIBUTE, PARAM_TYPE }); + add_refpolicy_macro("domain_auto_transition_pattern", + 3, + (enum param_flavor[3]){ PARAM_TYPE_OR_ATTRIBUTE, PARAM_TYPE_OR_ATTRIBUTE, PARAM_TYPE }); + add_refpolicy_macro("admin_pattern", + 2, + (enum param_flavor[2]){ PARAM_TYPE_OR_ATTRIBUTE, PARAM_TYPE_OR_ATTRIBUTE }); + add_refpolicy_macro("stream_connect_pattern", + 4, + (enum param_flavor[4]){ PARAM_TYPE_OR_ATTRIBUTE, PARAM_TYPE_OR_ATTRIBUTE, PARAM_TYPE_OR_ATTRIBUTE, PARAM_TYPE_OR_ATTRIBUTE }); + add_refpolicy_macro("dgram_send_pattern", + 4, + (enum param_flavor[4]){ PARAM_TYPE_OR_ATTRIBUTE, PARAM_TYPE_OR_ATTRIBUTE, PARAM_TYPE_OR_ATTRIBUTE, PARAM_TYPE_OR_ATTRIBUTE }); + + + // first infer only simple ifs; do not infer based on other called sub ifs + print_if_verbose("Start shallow infer step...\n"); + for (const struct policy_file_node *cur_file = files->head; cur_file; cur_file = cur_file->next) { + enum selint_error ret = infer_interfaces_shallow(cur_file->file->ast); + if (ret != SELINT_SUCCESS) { + return ret; + } + } + + // on the second run the policy_nodes are linked to the traits, so we can infer deep + print_if_verbose("Start deep infer step...\n"); + for (const struct policy_file_node *cur_file = files->head; cur_file; cur_file = cur_file->next) { + enum selint_error ret = infer_interfaces_deep(cur_file->file->ast); + if (ret != SELINT_SUCCESS) { + return ret; + } + } + + print_if_verbose("Finished infer steps\n"); + + return SELINT_SUCCESS; +} + +void free_interface_trait(struct interface_trait *to_free) +{ + if (to_free == NULL) { + return; + } + + free(to_free->name); + free(to_free); +} diff --git a/src/infer.h b/src/infer.h new file mode 100644 index 00000000..5a4e9948 --- /dev/null +++ b/src/infer.h @@ -0,0 +1,64 @@ +/* +* Copyright 2021 The SELint Contributors +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +#ifndef INFER_H +#define INFER_H + +#include + +#include "file_list.h" + +enum param_flavor { + /* the first flavors are not final, e.g. they can be replaced by a final one */ + PARAM_INITIAL = 0, + PARAM_UNKNOWN, + PARAM_TEXT, + PARAM_TYPE_OR_ATTRIBUTE, + PARAM_ROLE_OR_ATTRIBUTE, + + /* the following flavors are final */ + PARAM_FINAL_INFERRED, + PARAM_TYPE = PARAM_FINAL_INFERRED, + PARAM_TYPEATTRIBUTE, + PARAM_ROLE, + PARAM_ROLEATTRIBUTE, + PARAM_CLASS, + PARAM_OBJECT_NAME, +}; + +enum trait_type { + INTERFACE_TRAIT, + TEMPLATE_TRAIT, + MACRO_TRAIT, +}; + +#define TRAIT_MAX_PARAMETERS 10 // support max 10 parameters for now +struct interface_trait { + char *name; + enum trait_type type; + bool is_inferred; + //bool is_transform_if; + //bool is_filetrans_if; + //bool is_role_if; + enum param_flavor parameters[TRAIT_MAX_PARAMETERS]; + const struct policy_node *node; +}; + +enum selint_error infer_all_interfaces(const struct policy_file_list *files); + +void free_interface_trait(struct interface_trait *to_free); + +#endif /* INFER_H */ diff --git a/src/maps.c b/src/maps.c index d447dc28..4bbc8910 100644 --- a/src/maps.c +++ b/src/maps.c @@ -27,6 +27,7 @@ static struct hash_elem *perm_map = NULL; static struct hash_elem *mods_map = NULL; static struct hash_elem *mod_layers_map = NULL; static struct hash_elem *ifs_map = NULL; +static struct hash_elem *if_traits_map = NULL; static struct bool_hash_elem *transform_map = NULL; static struct bool_hash_elem *filetrans_map = NULL; static struct bool_hash_elem *role_if_map = NULL; @@ -576,6 +577,29 @@ void insert_into_permmacros_map(const char *name, struct string_list *permission } } +#if defined(__clang__) && defined(__clang_major__) && (__clang_major__ >= 4) +__attribute__((no_sanitize("unsigned-integer-overflow"))) +#if (__clang_major__ >= 12) +__attribute__((no_sanitize("unsigned-shift-base"))) +#endif +#endif +void insert_into_if_traits_map(const char *if_name, struct interface_trait *trait) +{ + struct hash_elem *if_trait; + + HASH_FIND(hh_if_traits, if_traits_map, if_name, strlen(if_name), if_trait); + + if (!if_trait) { + if_trait = malloc(sizeof(struct hash_elem)); + if_trait->key = strdup(if_name); + if_trait->val = trait; + HASH_ADD_KEYPTR(hh_if_traits, if_traits_map, if_trait->key, + strlen(if_trait->key), if_trait); + } else { + free_interface_trait(trait); + } +} + #if defined(__clang__) && defined(__clang_major__) && (__clang_major__ >= 4) __attribute__((no_sanitize("unsigned-integer-overflow"))) #if (__clang_major__ >= 12) @@ -610,10 +634,29 @@ unsigned int permmacros_map_count() return HASH_CNT(hh_permmacros, permmacros_map); } -#define FREE_MAP(mn) HASH_ITER(hh_ ## mn, mn ## _map, cur_decl, tmp_decl) { \ +#if defined(__clang__) && defined(__clang_major__) && (__clang_major__ >= 4) +__attribute__((no_sanitize("unsigned-integer-overflow"))) +#if (__clang_major__ >= 12) +__attribute__((no_sanitize("unsigned-shift-base"))) +#endif +#endif +struct interface_trait *look_up_in_if_traits_map(const char *if_name) +{ + struct hash_elem *if_trait; + + HASH_FIND(hh_if_traits, if_traits_map, if_name, strlen(if_name), if_trait); + + if (if_trait == NULL) { + return NULL; + } else { + return if_trait->val; + } +} + +#define FREE_MAP(mn, free_func) HASH_ITER(hh_ ## mn, mn ## _map, cur_decl, tmp_decl) { \ HASH_DELETE(hh_ ## mn, mn ## _map, cur_decl); \ + free_func(cur_decl->val); \ free(cur_decl->key); \ - free(cur_decl->val); \ free(cur_decl); \ } \ @@ -628,27 +671,29 @@ void free_all_maps() struct hash_elem *cur_decl, *tmp_decl; - FREE_MAP(type); + FREE_MAP(type, free); + + FREE_MAP(role, free); - FREE_MAP(role); + FREE_MAP(user, free); - FREE_MAP(user); + FREE_MAP(attr_type, free); - FREE_MAP(attr_type); + FREE_MAP(attr_role, free); - FREE_MAP(attr_role); + FREE_MAP(bool, free); - FREE_MAP(bool); + FREE_MAP(class, free); - FREE_MAP(class); + FREE_MAP(perm, free); - FREE_MAP(perm); + FREE_MAP(mods, free); - FREE_MAP(mods); + FREE_MAP(mod_layers, free); - FREE_MAP(mod_layers); + FREE_MAP(ifs, free); - FREE_MAP(ifs); + FREE_MAP(if_traits, free_interface_trait); struct bool_hash_elem *cur_bool, *tmp_bool; diff --git a/src/maps.h b/src/maps.h index 8d682d5f..a4182744 100644 --- a/src/maps.h +++ b/src/maps.h @@ -21,13 +21,21 @@ #include "tree.h" #include "selint_error.h" +#include "infer.h" struct hash_elem { + char *key; + void *val; + UT_hash_handle hh_type, hh_role, hh_user, hh_attr_type, hh_attr_role, hh_bool, + hh_class, hh_perm, hh_mods, hh_ifs, hh_mod_layers, hh_if_traits; +}; + +/*struct hash_elem { char *key; char *val; UT_hash_handle hh_type, hh_role, hh_user, hh_attr_type, hh_attr_role, hh_bool, hh_class, hh_perm, hh_mods, hh_ifs, hh_mod_layers; -}; +};*/ struct bool_hash_elem { char *key; @@ -101,6 +109,10 @@ void visit_all_in_permmacros_map(void (*visitor)(const char *name, const struct unsigned int permmacros_map_count(void); +void insert_into_if_traits_map(const char *if_name, struct interface_trait *trait); + +struct interface_trait *look_up_in_if_traits_map(const char *if_name); + unsigned int decl_map_count(enum decl_flavor flavor); void free_all_maps(void); diff --git a/src/runner.c b/src/runner.c index e185a918..13927575 100644 --- a/src/runner.c +++ b/src/runner.c @@ -28,6 +28,7 @@ #include "parse.h" #include "util.h" #include "startup.h" +#include "infer.h" #define CHECK_ENABLED(cid) is_check_enabled(cid, config_enabled_checks, config_disabled_checks, cl_enabled_checks, cl_disabled_checks, only_enabled) @@ -439,6 +440,10 @@ enum selint_error run_analysis(struct checks *ck, all_if_files->tail = context_if_files->tail; mark_transform_interfaces(all_if_files); + res = infer_all_interfaces(all_if_files); + if (res != SELINT_SUCCESS) { + goto out; + } // Restore if (if_files->tail) { diff --git a/src/tree.c b/src/tree.c index 321f9208..6ebef978 100644 --- a/src/tree.c +++ b/src/tree.c @@ -307,6 +307,148 @@ struct string_list *get_names_in_node(const struct policy_node *node) return ret; } +void visit_names_in_node(const struct policy_node *node, node_visior visitor_func, void *visitor_data) +{ + const struct av_rule_data *av_data; + const struct type_transition_data *tt_data; + const struct role_transition_data *rt_data; + const struct declaration_data *d_data; + const struct if_call_data *ifc_data; + const struct role_allow_data *ra_data; + const struct role_types_data *rtyp_data; + const struct attribute_data *at_data; + unsigned short id = 1; + + switch (node->flavor) { + case NODE_AV_RULE: + av_data = node->data.av_data; + for (const struct string_list *sl = av_data->sources; sl; sl = sl->next) { + visitor_func(sl->string, NAME_TYPE_OR_ATTRIBUTE, 0, visitor_data); + } + for (const struct string_list *sl = av_data->targets; sl; sl = sl->next) { + visitor_func(sl->string, NAME_TYPE_OR_ATTRIBUTE, 0, visitor_data); + } + break; + + case NODE_TT_RULE: + tt_data = node->data.tt_data; + for (const struct string_list *sl = tt_data->sources; sl; sl = sl->next) { + visitor_func(sl->string, NAME_TYPE_OR_ATTRIBUTE, 0, visitor_data); + } + for (const struct string_list *sl = tt_data->targets; sl; sl = sl->next) { + visitor_func(sl->string, NAME_TYPE_OR_ATTRIBUTE, 0, visitor_data); + } + for (const struct string_list *sl = tt_data->object_classes; sl; sl = sl->next) { + visitor_func(sl->string, NAME_CLASS, 0, visitor_data); + } + visitor_func(tt_data->default_type, NAME_TYPE, 0, visitor_data); + visitor_func(tt_data->name, NAME_OBJECT_NAME, 0, visitor_data); + break; + + case NODE_RT_RULE: + rt_data = node->data.rt_data; + for (const struct string_list *sl = rt_data->sources; sl; sl = sl->next) { + visitor_func(sl->string, NAME_ROLE_OR_ATTRIBUTE, 0, visitor_data); + } + for (const struct string_list *sl = rt_data->targets; sl; sl = sl->next) { + visitor_func(sl->string, NAME_TYPE_OR_ATTRIBUTE, 0, visitor_data); + } + for (const struct string_list *sl = rt_data->object_classes; sl; sl = sl->next) { + visitor_func(sl->string, NAME_CLASS, 0, visitor_data); + } + visitor_func(rt_data->default_role, NAME_ROLE, 0, visitor_data); + break; + + case NODE_DECL: + d_data = node->data.d_data; + // skip declared name + if (d_data->flavor == DECL_TYPE) { + for (const struct string_list *sl = d_data->attrs; sl; sl = sl->next) { + visitor_func(sl->string, NAME_TYPEATTRIBUTE, 0, visitor_data); + } + } + break; + + case NODE_IF_CALL: + ifc_data = node->data.ic_data; + for (const struct string_list *sl = ifc_data->args; sl; sl = sl->next) { + visitor_func(sl->string, NAME_IF_PARAM, id++, visitor_data); + } + break; + + case NODE_ROLE_ALLOW: + ra_data = node->data.ra_data; + for (const struct string_list *sl = ra_data->from; sl; sl = sl->next) { + visitor_func(sl->string, NAME_ROLE, 0, visitor_data); + } + for (const struct string_list *sl = ra_data->to; sl; sl = sl->next) { + visitor_func(sl->string, NAME_ROLE, 0, visitor_data); + } + break; + + case NODE_ROLE_TYPES: + rtyp_data = node->data.rtyp_data; + visitor_func(rtyp_data->role, NAME_ROLE, 0, visitor_data); + for (const struct string_list *sl = rtyp_data->types; sl; sl = sl->next) { + visitor_func(sl->string, NAME_TYPE_OR_ATTRIBUTE, 0, visitor_data); + } + break; + + case NODE_TYPE_ATTRIBUTE: + at_data = node->data.at_data; + visitor_func(at_data->type, NAME_TYPE, 0, visitor_data); + for (const struct string_list *sl = at_data->attrs; sl; sl = sl->next) { + visitor_func(sl->string, NAME_TYPEATTRIBUTE, 0, visitor_data); + } + break; + + case NODE_ROLE_ATTRIBUTE: + at_data = node->data.at_data; + visitor_func(at_data->type, NAME_ROLE, 0, visitor_data); + for (const struct string_list *sl = at_data->attrs; sl; sl = sl->next) { + visitor_func(sl->string, NAME_ROLEATTRIBUTE, 0, visitor_data); + } + break; + + //TODO: + //case NODE_ALIAS: + //case NODE_TYPE_ALIAS: + // ret = calloc(1, sizeof(struct string_list)); + // ret->string = strdup(node->data.str); + // break; + + case NODE_PERMISSIVE: + visitor_func(node->data.str, NAME_TYPE, 0, visitor_data); + break; + + /* + NODE_TE_FILE, + NODE_IF_FILE, + NODE_FC_FILE, + NODE_HEADER, + NODE_M4_CALL, + NODE_OPTIONAL_POLICY, + NODE_OPTIONAL_ELSE, + NODE_TUNABLE_POLICY, + NODE_IFDEF, + NODE_M4_ARG, + NODE_START_BLOCK, + NODE_INTERFACE_DEF, + NODE_TEMP_DEF, + NODE_REQUIRE, + NODE_GEN_REQ, + NODE_FC_ENTRY, + NODE_COMMENT, + NODE_EMPTY, + NODE_SEMICOLON, + NODE_CLEANUP, + NODE_ERROR + */ + default: + break; + } +} + struct string_list *get_names_required(const struct policy_node *node) { struct string_list *ret = NULL; diff --git a/src/tree.h b/src/tree.h index 51e974f0..e384e8a1 100644 --- a/src/tree.h +++ b/src/tree.h @@ -237,6 +237,20 @@ const char *get_name_if_in_template(const struct policy_node *cur); struct string_list *get_names_in_node(const struct policy_node *node); +enum name_flavor { + NAME_TYPE, + NAME_TYPEATTRIBUTE, + NAME_TYPE_OR_ATTRIBUTE, + NAME_ROLE, + NAME_ROLEATTRIBUTE, + NAME_ROLE_OR_ATTRIBUTE, + NAME_IF_PARAM, + NAME_CLASS, + NAME_OBJECT_NAME, +}; +typedef void (*node_visior)(const char *name, enum name_flavor flavor, unsigned short id, void *visitor_data); +void visit_names_in_node(const struct policy_node *node, node_visior visitor_func, void *visitor_data); + struct string_list *get_names_required(const struct policy_node *node); const char *decl_flavor_to_string(enum decl_flavor flavor); diff --git a/tests/Makefile.am b/tests/Makefile.am index b1c31065..bafd3e40 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -15,7 +15,7 @@ @VALGRIND_CHECK_RULES@ VALGRIND_memcheck_FLAGS=--leak-check=full --show-reachable=yes --show-leak-kinds=all --errors-for-leak-kinds=all -TESTS = check_tree check_parse_functions check_maps check_parsing check_parse_fc check_template check_file_list check_fc_checks check_check_hooks check_selint_config check_if_checks check_string_list check_runner check_startup check_te_checks check_ordering check_perm_macro +TESTS = check_tree check_parse_functions check_maps check_parsing check_parse_fc check_template check_file_list check_fc_checks check_check_hooks check_selint_config check_if_checks check_string_list check_runner check_startup check_te_checks check_ordering check_perm_macro check_infer check_PROGRAMS = ${TESTS} AV_FILE_PERM_FILES=sample_av/file/index \ @@ -106,11 +106,13 @@ SAMPLE_POLICY_FILES=sample_policy_files/access_vectors \ sample_policy_files/empty.te \ sample_policy_files/extended_perms.te \ sample_policy_files/ifdef_block.te \ + sample_policy_files/infer_loop.if \ sample_policy_files/modules.conf \ sample_policy_files/nested_templates.if \ sample_policy_files/none_context.fc \ sample_policy_files/obj_perm_sets.spt \ sample_policy_files/perms.spt \ + sample_policy_files/simple_infer.if \ sample_policy_files/syntax_error.te \ sample_policy_files/uncommon.te \ sample_policy_files/with_m4.fc @@ -263,14 +265,16 @@ COLOR_HEADS=$(top_builddir)/src/color.h COLOR_OBJS=$(top_builddir)/src/color.o PERM_MACRO_HEADS=$(top_builddir)/src/perm_macro.h ${UTIL_HEADS} ${COLOR_HEADS} PERM_MACRO_OBJS=$(top_builddir)/src/perm_macro.o ${UTIL_OBJS} ${COLOR_OBJS} -SELINT_CONFIG_HEADS=$(top_builddir)/src/selint_config.h ${STRING_LIST_HEADS} ${TREE_HEADS} ${MAPS_HEADS} ${ORDERING_HEADS} +INFER_HEADS=$(top_builddir)/src/infer.h ${COLOR_HEADS} ${UTIL_HEADS} +INFER_OBJS=$(top_builddir)/src/infer.o ${COLOR_OBJS} ${UTIL_OBJS} +SELINT_CONFIG_HEADS=$(top_builddir)/src/selint_config.h ${SELINT_ERROR_HEADS} ${STRING_LIST_HEADS} ${TREE_HEADS} ${MAPS_HEADS} ${ORDERING_HEADS} SELINT_CONFIG_OBJS=$(top_builddir)/src/selint_config.o ${STRING_LIST_OBJS} ${TREE_OBJS} ${MAPS_OBJS} ${UTIL_OBJS} -TREE_HEADS=$(top_builddir)/src/tree.h ${STRING_LIST_HEADS} -TREE_OBJS=$(top_builddir)/src/tree.o ${STRING_LIST_OBJS} $(top_builddir)/src/maps.o +TREE_HEADS=$(top_builddir)/src/tree.h ${SELINT_ERROR_HEADS} ${STRING_LIST_HEADS} +TREE_OBJS=$(top_builddir)/src/tree.o ${STRING_LIST_OBJS} $(top_builddir)/src/maps.o ${INFER_OBJS} FILE_LIST_HEADS=$(top_builddir)/src/file_list.h ${TREE_HEADS} FILE_LIST_OBJS=$(top_builddir)/src/file_list.o ${TREE_OBJS} -MAPS_HEADS=$(top_builddir)/src/maps.h ${SELINT_ERROR_HEADS} ${TREE_HEADS} -MAPS_OBJS=$(top_builddir)/src/maps.o ${TREE_OBJS} +MAPS_HEADS=$(top_builddir)/src/maps.h ${SELINT_ERROR_HEADS} ${TREE_HEADS} ${INFER_HEADS} +MAPS_OBJS=$(top_builddir)/src/maps.o ${TREE_OBJS} ${INFER_OBJS} TEMPLATE_HEADS=$(top_builddir)/src/template.h ${SELINT_ERROR_HEADS} ${TREE_HEADS} TEMPLATE_OBJS=$(top_builddir)/src/template.o ${TREE_OBJS} PARSE_FUNCTIONS_HEADS=$(top_builddir)/src/parse_functions.h ${SELINT_ERROR_HEADS} ${TREE_HEADS} ${MAPS_HEADS} ${PERM_MACRO_HEADS} @@ -345,4 +349,7 @@ check_ordering_LDADD = @CHECK_LIBS@ $(sort ${ORDERING_OBJS} ${RUNNER_OBJS} ${MAP check_perm_macro_SOURCES = check_perm_macro.c ${PERM_MACRO_HEADS} ${STARTUP_HEADS} ${SELINT_ERROR_HEADS} ${MAPS_HEADS} check_perm_macro_LDADD = @CHECK_LIBS@ $(sort ${PERM_MACRO_OBJS} ${STARTUP_OBJS} ${MAPS_OBJS}) +check_infer_SOURCES = check_infer.c ${INFER_HEADS} ${MAPS_HEADS} ${PARSE_HEADS} +check_infer_LDADD = @CHECK_LIBS@ $(sort ${INFER_OBJS} ${MAPS_OBJS} ${PARSE_OBJS}) + MOSTLYCLEANFILES = *.gcov *.gcda *.gcno diff --git a/tests/check_infer.c b/tests/check_infer.c new file mode 100644 index 00000000..52467339 --- /dev/null +++ b/tests/check_infer.c @@ -0,0 +1,254 @@ +/* +* Copyright 2021 The SELint Contributors +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +#include +#include + +#include "../src/tree.h" +#include "../src/parse.h" +#include "../src/parse_functions.h" + +#define POLICIES_DIR SAMPLE_POL_DIR +#define SIMPLE_INFER_IF POLICIES_DIR "simple_infer.if" +#define INFER_LOOP_IF POLICIES_DIR "infer_loop.if" + +extern enum selint_error infer_interfaces_deep(const struct policy_node *node); +extern enum selint_error infer_interfaces_shallow(const struct policy_node *node); + +START_TEST (test_infer_simple) { + + // setup + set_current_module_name("simple_infer"); + + FILE *f = fopen(SIMPLE_INFER_IF, "r"); + ck_assert_ptr_nonnull(f); + struct policy_node *ast = yyparse_wrapper(f, SIMPLE_INFER_IF, NODE_IF_FILE); + fclose(f); + ck_assert_ptr_nonnull(ast); + + ck_assert_int_eq(SELINT_SUCCESS, infer_interfaces_shallow(ast)); + + // actual checks + const struct interface_trait *if_trait; + + // sample_infer_typeorattribute1 + if_trait = look_up_in_if_traits_map("sample_infer_typeorattribute1"); + ck_assert_ptr_nonnull(if_trait); + ck_assert_str_eq("sample_infer_typeorattribute1", if_trait->name); + ck_assert_int_eq(INTERFACE_TRAIT, if_trait->type); + ck_assert_int_eq(true, if_trait->is_inferred); + ck_assert_int_eq(PARAM_TYPE_OR_ATTRIBUTE, if_trait->parameters[0]); + for (int i = 1; i < TRAIT_MAX_PARAMETERS; ++i) ck_assert_int_eq(PARAM_INITIAL, if_trait->parameters[i]); + ck_assert_ptr_nonnull(if_trait->node); + + // sample_infer_typeorattribute2 + if_trait = look_up_in_if_traits_map("sample_infer_typeorattribute2"); + ck_assert_ptr_nonnull(if_trait); + ck_assert_str_eq("sample_infer_typeorattribute2", if_trait->name); + ck_assert_int_eq(INTERFACE_TRAIT, if_trait->type); + ck_assert_int_eq(true, if_trait->is_inferred); + ck_assert_int_eq(PARAM_TYPE_OR_ATTRIBUTE, if_trait->parameters[0]); + for (int i = 1; i < TRAIT_MAX_PARAMETERS; ++i) ck_assert_int_eq(PARAM_INITIAL, if_trait->parameters[i]); + ck_assert_ptr_nonnull(if_trait->node); + + // sample_infer_unknown1 + if_trait = look_up_in_if_traits_map("sample_infer_unknown1"); + ck_assert_ptr_nonnull(if_trait); + ck_assert_str_eq("sample_infer_unknown1", if_trait->name); + ck_assert_int_eq(INTERFACE_TRAIT, if_trait->type); + ck_assert_int_eq(false, if_trait->is_inferred); + ck_assert_int_eq(PARAM_UNKNOWN, if_trait->parameters[0]); + for (int i = 1; i < TRAIT_MAX_PARAMETERS; ++i) ck_assert_int_eq(PARAM_INITIAL, if_trait->parameters[i]); + ck_assert_ptr_nonnull(if_trait->node); + + // sample_infer_temp1 + if_trait = look_up_in_if_traits_map("sample_infer_temp1"); + ck_assert_ptr_nonnull(if_trait); + ck_assert_str_eq("sample_infer_temp1", if_trait->name); + ck_assert_int_eq(TEMPLATE_TRAIT, if_trait->type); + ck_assert_int_eq(true, if_trait->is_inferred); + ck_assert_int_eq(PARAM_TEXT, if_trait->parameters[0]); + ck_assert_int_eq(PARAM_ROLE, if_trait->parameters[1]); + ck_assert_int_eq(PARAM_TYPE_OR_ATTRIBUTE, if_trait->parameters[2]); + for (int i = 3; i < TRAIT_MAX_PARAMETERS; ++i) ck_assert_int_eq(PARAM_INITIAL, if_trait->parameters[i]); + ck_assert_ptr_nonnull(if_trait->node); + + // sample_redirect_if + if_trait = look_up_in_if_traits_map("sample_redirect_if"); + ck_assert_ptr_nonnull(if_trait); + ck_assert_str_eq("sample_redirect_if", if_trait->name); + ck_assert_int_eq(INTERFACE_TRAIT, if_trait->type); + ck_assert_int_eq(true, if_trait->is_inferred); + ck_assert_int_eq(PARAM_TYPE_OR_ATTRIBUTE, if_trait->parameters[0]); + for (int i = 1; i < TRAIT_MAX_PARAMETERS; ++i) ck_assert_int_eq(PARAM_INITIAL, if_trait->parameters[i]); + ck_assert_ptr_nonnull(if_trait->node); + + ck_assert_int_eq(SELINT_SUCCESS, infer_interfaces_deep(ast)); + + // sample_infer_typeorattribute1 + if_trait = look_up_in_if_traits_map("sample_infer_typeorattribute1"); + ck_assert_ptr_nonnull(if_trait); + ck_assert_str_eq("sample_infer_typeorattribute1", if_trait->name); + ck_assert_int_eq(INTERFACE_TRAIT, if_trait->type); + ck_assert_int_eq(true, if_trait->is_inferred); + ck_assert_int_eq(PARAM_TYPE_OR_ATTRIBUTE, if_trait->parameters[0]); + for (int i = 1; i < TRAIT_MAX_PARAMETERS; ++i) ck_assert_int_eq(PARAM_INITIAL, if_trait->parameters[i]); + ck_assert_ptr_nonnull(if_trait->node); + + // sample_infer_typeorattribute2 + if_trait = look_up_in_if_traits_map("sample_infer_typeorattribute2"); + ck_assert_ptr_nonnull(if_trait); + ck_assert_str_eq("sample_infer_typeorattribute2", if_trait->name); + ck_assert_int_eq(INTERFACE_TRAIT, if_trait->type); + ck_assert_int_eq(true, if_trait->is_inferred); + ck_assert_int_eq(PARAM_TYPE_OR_ATTRIBUTE, if_trait->parameters[0]); + for (int i = 1; i < TRAIT_MAX_PARAMETERS; ++i) ck_assert_int_eq(PARAM_INITIAL, if_trait->parameters[i]); + ck_assert_ptr_nonnull(if_trait->node); + + // sample_infer_unknown1 + if_trait = look_up_in_if_traits_map("sample_infer_unknown1"); + ck_assert_ptr_nonnull(if_trait); + ck_assert_str_eq("sample_infer_unknown1", if_trait->name); + ck_assert_int_eq(INTERFACE_TRAIT, if_trait->type); + ck_assert_int_eq(true, if_trait->is_inferred); + ck_assert_int_eq(PARAM_TYPE_OR_ATTRIBUTE, if_trait->parameters[0]); + for (int i = 1; i < TRAIT_MAX_PARAMETERS; ++i) ck_assert_int_eq(PARAM_INITIAL, if_trait->parameters[i]); + ck_assert_ptr_nonnull(if_trait->node); + + // sample_infer_temp1 + if_trait = look_up_in_if_traits_map("sample_infer_temp1"); + ck_assert_ptr_nonnull(if_trait); + ck_assert_str_eq("sample_infer_temp1", if_trait->name); + ck_assert_int_eq(TEMPLATE_TRAIT, if_trait->type); + ck_assert_int_eq(true, if_trait->is_inferred); + ck_assert_int_eq(PARAM_TEXT, if_trait->parameters[0]); + ck_assert_int_eq(PARAM_ROLE, if_trait->parameters[1]); + ck_assert_int_eq(PARAM_TYPE_OR_ATTRIBUTE, if_trait->parameters[2]); + for (int i = 3; i < TRAIT_MAX_PARAMETERS; ++i) ck_assert_int_eq(PARAM_INITIAL, if_trait->parameters[i]); + ck_assert_ptr_nonnull(if_trait->node); + + // sample_redirect_if + if_trait = look_up_in_if_traits_map("sample_redirect_if"); + ck_assert_ptr_nonnull(if_trait); + ck_assert_str_eq("sample_redirect_if", if_trait->name); + ck_assert_int_eq(INTERFACE_TRAIT, if_trait->type); + ck_assert_int_eq(true, if_trait->is_inferred); + ck_assert_int_eq(PARAM_TYPE_OR_ATTRIBUTE, if_trait->parameters[0]); + for (int i = 1; i < TRAIT_MAX_PARAMETERS; ++i) ck_assert_int_eq(PARAM_INITIAL, if_trait->parameters[i]); + ck_assert_ptr_nonnull(if_trait->node); + + // cleanup + free_policy_node(ast); + + cleanup_parsing(); + +} +END_TEST + +START_TEST (test_infer_loop) { + + // setup + set_current_module_name("infer_loop"); + + FILE *f = fopen(INFER_LOOP_IF, "r"); + ck_assert_ptr_nonnull(f); + struct policy_node *ast = yyparse_wrapper(f, INFER_LOOP_IF, NODE_IF_FILE); + fclose(f); + ck_assert_ptr_nonnull(ast); + + ck_assert_int_eq(SELINT_SUCCESS, infer_interfaces_shallow(ast)); + + const struct interface_trait *if_trait; + + // sample_infer_loop1 + if_trait = look_up_in_if_traits_map("sample_infer_loop1"); + ck_assert_ptr_nonnull(if_trait); + ck_assert_str_eq("sample_infer_loop1", if_trait->name); + ck_assert_int_eq(INTERFACE_TRAIT, if_trait->type); + ck_assert_int_eq(false, if_trait->is_inferred); + ck_assert_int_eq(PARAM_UNKNOWN, if_trait->parameters[0]); + for (int i = 1; i < TRAIT_MAX_PARAMETERS; ++i) ck_assert_int_eq(PARAM_INITIAL, if_trait->parameters[i]); + ck_assert_ptr_nonnull(if_trait->node); + + // sample_infer_loop2 + if_trait = look_up_in_if_traits_map("sample_infer_loop2"); + ck_assert_ptr_nonnull(if_trait); + ck_assert_str_eq("sample_infer_loop2", if_trait->name); + ck_assert_int_eq(INTERFACE_TRAIT, if_trait->type); + ck_assert_int_eq(false, if_trait->is_inferred); + ck_assert_int_eq(PARAM_UNKNOWN, if_trait->parameters[0]); + for (int i = 1; i < TRAIT_MAX_PARAMETERS; ++i) ck_assert_int_eq(PARAM_INITIAL, if_trait->parameters[i]); + ck_assert_ptr_nonnull(if_trait->node); + + ck_assert_int_eq(SELINT_IF_CALL_LOOP, infer_interfaces_deep(ast)); + + // sample_infer_loop1 + if_trait = look_up_in_if_traits_map("sample_infer_loop1"); + ck_assert_ptr_nonnull(if_trait); + ck_assert_str_eq("sample_infer_loop1", if_trait->name); + ck_assert_int_eq(INTERFACE_TRAIT, if_trait->type); + ck_assert_int_eq(false, if_trait->is_inferred); + ck_assert_int_eq(PARAM_UNKNOWN, if_trait->parameters[0]); + for (int i = 1; i < TRAIT_MAX_PARAMETERS; ++i) ck_assert_int_eq(PARAM_INITIAL, if_trait->parameters[i]); + ck_assert_ptr_nonnull(if_trait->node); + + // sample_infer_loop2 + if_trait = look_up_in_if_traits_map("sample_infer_loop2"); + ck_assert_ptr_nonnull(if_trait); + ck_assert_str_eq("sample_infer_loop2", if_trait->name); + ck_assert_int_eq(INTERFACE_TRAIT, if_trait->type); + ck_assert_int_eq(false, if_trait->is_inferred); + ck_assert_int_eq(PARAM_UNKNOWN, if_trait->parameters[0]); + for (int i = 1; i < TRAIT_MAX_PARAMETERS; ++i) ck_assert_int_eq(PARAM_INITIAL, if_trait->parameters[i]); + ck_assert_ptr_nonnull(if_trait->node); + + // cleanup + free_policy_node(ast); + + cleanup_parsing(); + +} +END_TEST + +static Suite *parsing_suite(void) { + Suite *s; + TCase *tc_core; + + s = suite_create("Infer"); + + tc_core = tcase_create("Core"); + + tcase_add_test(tc_core, test_infer_simple); + tcase_add_test(tc_core, test_infer_loop); + suite_add_tcase(s, tc_core); + + return s; +} + +int main(void) { + + int number_failed; + Suite *s; + SRunner *sr; + + s = parsing_suite(); + sr = srunner_create(s); + srunner_run_all(sr, CK_NORMAL); + number_failed = srunner_ntests_failed(sr); + srunner_free(sr); + + return (number_failed == 0) ? 0 : -1; +} diff --git a/tests/sample_policy_files/infer_loop.if b/tests/sample_policy_files/infer_loop.if new file mode 100644 index 00000000..9688e93c --- /dev/null +++ b/tests/sample_policy_files/infer_loop.if @@ -0,0 +1,7 @@ +interface(`sample_infer_loop1',` + sample_infer_loop2($1) +') + +interface(`sample_infer_loop2',` + sample_infer_loop1($1) +') diff --git a/tests/sample_policy_files/simple_infer.if b/tests/sample_policy_files/simple_infer.if new file mode 100644 index 00000000..bb1fb770 --- /dev/null +++ b/tests/sample_policy_files/simple_infer.if @@ -0,0 +1,22 @@ +interface(`sample_infer_typeorattribute1',` + allow $1 target:cls perm; +') + +interface(`sample_infer_typeorattribute2',` + type $1_advanced_t; + allow $1 target:cls perm; +') + +interface(`sample_infer_unknown1',` + sample_infer_typeorattribute2($1) +') + +template(`sample_infer_temp1',` + type $1_t; + role $2 types $1_t; + allow $1_t $3:fd use; +') + +interface(`sample_redirect_if',` + sample_infer_typeorattribute1($*) +') From 9bebc769951ea40b5a53a593e104c031f4bcb6a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20G=C3=B6ttsche?= Date: Mon, 10 May 2021 14:13:13 +0200 Subject: [PATCH 2/4] Add checks for template/interface mis-declarations Interfaces in the refpolicy should not: - declare anything (no side effects) - use prefix parameters Add one check to find interfaces that should be declared as a template and one check to find templates that can be declared as an interface. Refpolicy findings: qemu.if: 112: (S): Template qemu_role might be declared as an interface (S-012) wm.if: 142: (S): Interface wm_dbus_chat should be a template, due to parameter 0 (S-011) wm.if: 250: (S): Interface wm_write_pipes should be a template, due to parameter 0 (S-011) gnome.if: 673: (S): Interface gnome_dbus_chat_gkeyringd should be a template, due to parameter 0 (S-011) gnome.if: 741: (S): Interface gnome_stream_connect_gkeyringd should be a template, due to parameter 0 (S-011) userdomain.if: 1431: (S): Template userdom_security_admin_template might be declared as an interface (S-012) kismet.if: 18: (S): Template kismet_role might be declared as an interface (S-012) dbus.if: 193: (S): Interface dbus_connect_spec_session_bus should be a template, due to parameter 0 (S-011) dbus.if: 245: (S): Interface dbus_spec_session_bus_client should be a template, due to parameter 0 (S-011) dbus.if: 298: (S): Interface dbus_send_spec_session_bus should be a template, due to parameter 0 (S-011) dbus.if: 436: (S): Interface dbus_spec_session_domain should be a template, due to parameter 0 (S-011) rlogin.if: 32: (S): Template rlogin_read_home_content might be declared as an interface (S-012) git.if: 18: (S): Template git_role might be declared as an interface (S-012) Found the following issue counts: S-011: 8 S-012: 5 Closes: #205 --- README | 2 + src/check_hooks.h | 2 + src/if_checks.c | 74 +++++++++++++++++++ src/if_checks.h | 24 ++++++ src/runner.c | 8 ++ tests/Makefile.am | 2 + tests/functional/end-to-end.bats | 8 ++ .../functional/policies/check_triggers/s11.if | 20 +++++ .../functional/policies/check_triggers/s12.if | 20 +++++ 9 files changed, 160 insertions(+) create mode 100644 tests/functional/policies/check_triggers/s11.if create mode 100644 tests/functional/policies/check_triggers/s12.if diff --git a/README b/README index fa5e3409..1b60e280 100644 --- a/README +++ b/README @@ -168,6 +168,8 @@ CHECK IDS S-008: Unquoted gen_require block S-009: Permission macro suffix does not match class name S-010: Permission macro usage suggested + S-011: Interface should be decalred as template + S-012: Template can be declared as interface W-001: Type or attribute referenced without explicit declaration W-002: Type, attribute or role used but not listed in require block in interface diff --git a/src/check_hooks.h b/src/check_hooks.h index c5db22c7..86b600a8 100644 --- a/src/check_hooks.h +++ b/src/check_hooks.h @@ -41,6 +41,8 @@ enum style_ids { S_ID_UNQUOTE_GENREQ = 8, S_ID_PERM_SUFFIX = 9, S_ID_PERMMACRO = 10, + S_ID_TEXT_IF_PARAM = 11, + S_ID_VOID_TEMP_DECL = 12, S_END }; diff --git a/src/if_checks.c b/src/if_checks.c index d22c9f35..f4273113 100644 --- a/src/if_checks.c +++ b/src/if_checks.c @@ -232,6 +232,80 @@ struct check_result *check_unquoted_gen_require_block(__attribute__((unused)) co return NULL; } +struct check_result *check_text_param_in_interface(__attribute__((unused)) const struct + check_data *data, + const struct + policy_node *node) +{ + const char *if_name = node->data.str; + const struct interface_trait *trait = look_up_in_if_traits_map(if_name); + if (!trait || !trait->is_inferred) { + return NULL; + } + + for (int i = 0; i < TRAIT_MAX_PARAMETERS; i++) { + if (trait->parameters[i] == PARAM_TEXT) { + return make_check_result('S', S_ID_TEXT_IF_PARAM, + "Interface %s should be a template, due to parameter %d", + if_name, + i + 1); + } + + if (trait->parameters[i] == PARAM_INITIAL) { + break; + } + } + + return NULL; +} + +struct check_result *check_unnecessary_template_definition(__attribute__((unused)) const struct + check_data *data, + const struct + policy_node *node) +{ + const char *temp_name = node->data.str; + const struct interface_trait *trait = look_up_in_if_traits_map(temp_name); + if (!trait || !trait->is_inferred) { + return NULL; + } + + for (int i = 0; i < TRAIT_MAX_PARAMETERS; i++) { + if (trait->parameters[i] == PARAM_TEXT) { + return NULL; + } + + if (trait->parameters[i] == PARAM_INITIAL) { + break; + } + } + + for (const struct policy_node *cur = node->first_child; cur; cur = dfs_next(cur)) { + if (cur == node || cur == node->next) { + break; + } + + if (cur->flavor == NODE_GEN_REQ || cur->flavor == NODE_REQUIRE) { + cur = cur->next; + } + + if (cur->flavor == NODE_DECL) { + return NULL; + } + + if (cur->flavor == NODE_IF_CALL) { + const struct if_call_data *ic_data = cur->data.ic_data; + if (look_up_in_template_map(ic_data->name)) { + return NULL; + } + } + } + + return make_check_result('S', S_ID_VOID_TEMP_DECL, + "Template %s might be declared as an interface", + temp_name); +} + struct check_result *check_name_used_but_not_required_in_if(const struct check_data *data, const struct diff --git a/src/if_checks.h b/src/if_checks.h index e93a2c19..bb01b53d 100644 --- a/src/if_checks.h +++ b/src/if_checks.h @@ -81,6 +81,30 @@ struct check_result *check_unquoted_gen_require_block(const struct const struct policy_node *node); +/********************************************* +* Check that an interface has no text parameter +* Called on NODE_INTERFACE_DEF nodes +* data - metadata about the file +* node - the node to check +* returns NULL if passed or check_result for issue S-011 +*********************************************/ +struct check_result *check_text_param_in_interface(const struct + check_data *data, + const struct + policy_node *node); + +/********************************************* +* Check for templates that can be declared as interface +* Called on NODE_TEMP_DEF nodes +* data - metadata about the file +* node - the node to check +* returns NULL if passed or check_result for issue S-012 +*********************************************/ +struct check_result *check_unnecessary_template_definition(const struct + check_data *data, + const struct + policy_node *node); + /********************************************* * Check that all names referenced in interface are listed in its require block * (or declared in that template) diff --git a/src/runner.c b/src/runner.c index 13927575..0fb4935c 100644 --- a/src/runner.c +++ b/src/runner.c @@ -189,6 +189,14 @@ struct checks *register_checks(char level, add_check(NODE_AV_RULE, ck, "S-010", check_perm_macro_available); } + if (CHECK_ENABLED("S-011")) { + add_check(NODE_INTERFACE_DEF, ck, "S-011", + check_text_param_in_interface); + } + if (CHECK_ENABLED("S-012")) { + add_check(NODE_TEMP_DEF, ck, "S-012", + check_unnecessary_template_definition); + } // FALLTHRU case 'W': if (CHECK_ENABLED("W-001")) { diff --git a/tests/Makefile.am b/tests/Makefile.am index bafd3e40..381b3448 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -157,6 +157,8 @@ FUNCTIONAL_TEST_FILES=functional/end-to-end.bats \ functional/policies/check_triggers/s08.if \ functional/policies/check_triggers/s09.pass.te \ functional/policies/check_triggers/s09.warn.te \ + functional/policies/check_triggers/s11.if \ + functional/policies/check_triggers/s12.if \ functional/policies/check_triggers/w01_other.te \ functional/policies/check_triggers/w01.te \ functional/policies/check_triggers/w02.if \ diff --git a/tests/functional/end-to-end.bats b/tests/functional/end-to-end.bats index b66cf5ae..e97b3abb 100644 --- a/tests/functional/end-to-end.bats +++ b/tests/functional/end-to-end.bats @@ -188,6 +188,14 @@ test_parse_error_impl() { test_one_check_expect "S-009" "s09.warn.te" 6 } +@test "S-011" { + test_one_check "S-011" "s11.if" +} + +@test "S-012" { + test_one_check "S-012" "s12.if" +} + @test "W-001" { test_one_check_expect "W-001" "w01*" 5 } diff --git a/tests/functional/policies/check_triggers/s11.if b/tests/functional/policies/check_triggers/s11.if new file mode 100644 index 00000000..d86815cb --- /dev/null +++ b/tests/functional/policies/check_triggers/s11.if @@ -0,0 +1,20 @@ +interface(`foo1', ` + gen_require(` + type foo_t; + ') + + allow $1 foo_t:file read; +') + +interface(`foo2', ` + gen_require(` + type foo_t; + ') + + allow $1_t foo_t:file read; +') + +interface(`foo3', ` + type foo_t; + allow $1 foo_t:file read; +') diff --git a/tests/functional/policies/check_triggers/s12.if b/tests/functional/policies/check_triggers/s12.if new file mode 100644 index 00000000..3148cf16 --- /dev/null +++ b/tests/functional/policies/check_triggers/s12.if @@ -0,0 +1,20 @@ +template(`foo1', ` + gen_require(` + type foo_t; + ') + + allow $1 foo_t:file read; +') + +template(`foo2', ` + gen_require(` + type foo_t; + ') + + allow $1_t foo_t:file read; +') + +template(`foo3', ` + type foo_t; + allow $1 foo_t:file read; +') From 2120a5e869720e4ac0ed8118ddead03621e06cdc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20G=C3=B6ttsche?= Date: Sun, 30 May 2021 12:56:07 +0200 Subject: [PATCH 3/4] typos --- README | 2 +- tests/sample_policy_files/declaring_template.te | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README b/README index 1b60e280..fc001ad6 100644 --- a/README +++ b/README @@ -168,7 +168,7 @@ CHECK IDS S-008: Unquoted gen_require block S-009: Permission macro suffix does not match class name S-010: Permission macro usage suggested - S-011: Interface should be decalred as template + S-011: Interface should be declared as template S-012: Template can be declared as interface W-001: Type or attribute referenced without explicit declaration diff --git a/tests/sample_policy_files/declaring_template.te b/tests/sample_policy_files/declaring_template.te index 91e84dc3..19d3f279 100644 --- a/tests/sample_policy_files/declaring_template.te +++ b/tests/sample_policy_files/declaring_template.te @@ -1,4 +1,4 @@ -policy_module(delcaring_template, 1.0.0) +policy_module(declaring_template, 1.0.0) declaring_template(foo, bar) From 3910c5c1b3c8c800a3586ac161f56cee7f26755a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20G=C3=B6ttsche?= Date: Mon, 5 Jul 2021 20:35:11 +0200 Subject: [PATCH 4/4] Test infer conflicting usage [skip ci] --- tests/Makefile.am | 3 +- tests/check_infer.c | 51 ++++++++++++++++++- tests/sample_policy_files/infer_conflict.if | 4 ++ .../{simple_infer.if => infer_simple.if} | 0 4 files changed, 55 insertions(+), 3 deletions(-) create mode 100644 tests/sample_policy_files/infer_conflict.if rename tests/sample_policy_files/{simple_infer.if => infer_simple.if} (100%) diff --git a/tests/Makefile.am b/tests/Makefile.am index 381b3448..2391a5f6 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -106,13 +106,14 @@ SAMPLE_POLICY_FILES=sample_policy_files/access_vectors \ sample_policy_files/empty.te \ sample_policy_files/extended_perms.te \ sample_policy_files/ifdef_block.te \ + sample_policy_files/infer_conflict.if \ sample_policy_files/infer_loop.if \ + sample_policy_files/infer_simple.if \ sample_policy_files/modules.conf \ sample_policy_files/nested_templates.if \ sample_policy_files/none_context.fc \ sample_policy_files/obj_perm_sets.spt \ sample_policy_files/perms.spt \ - sample_policy_files/simple_infer.if \ sample_policy_files/syntax_error.te \ sample_policy_files/uncommon.te \ sample_policy_files/with_m4.fc diff --git a/tests/check_infer.c b/tests/check_infer.c index 52467339..59fdf319 100644 --- a/tests/check_infer.c +++ b/tests/check_infer.c @@ -22,7 +22,8 @@ #include "../src/parse_functions.h" #define POLICIES_DIR SAMPLE_POL_DIR -#define SIMPLE_INFER_IF POLICIES_DIR "simple_infer.if" +#define CONFLICT_INFER_IF POLICIES_DIR "infer_conflict.if" +#define SIMPLE_INFER_IF POLICIES_DIR "infer_simple.if" #define INFER_LOOP_IF POLICIES_DIR "infer_loop.if" extern enum selint_error infer_interfaces_deep(const struct policy_node *node); @@ -31,7 +32,7 @@ extern enum selint_error infer_interfaces_shallow(const struct policy_node *node START_TEST (test_infer_simple) { // setup - set_current_module_name("simple_infer"); + set_current_module_name("infer_simple"); FILE *f = fopen(SIMPLE_INFER_IF, "r"); ck_assert_ptr_nonnull(f); @@ -223,6 +224,51 @@ START_TEST (test_infer_loop) { } END_TEST +START_TEST (test_infer_conflict) { + + // setup + set_current_module_name("infer_conflict"); + + FILE *f = fopen(CONFLICT_INFER_IF, "r"); + ck_assert_ptr_nonnull(f); + struct policy_node *ast = yyparse_wrapper(f, CONFLICT_INFER_IF, NODE_IF_FILE); + fclose(f); + ck_assert_ptr_nonnull(ast); + + ck_assert_int_eq(SELINT_SUCCESS, infer_interfaces_shallow(ast)); + + const struct interface_trait *if_trait; + + // conflict_infer + if_trait = look_up_in_if_traits_map("conflict_infer"); + ck_assert_ptr_nonnull(if_trait); + ck_assert_str_eq("conflict_infer", if_trait->name); + ck_assert_int_eq(INTERFACE_TRAIT, if_trait->type); + ck_assert_int_eq(true, if_trait->is_inferred); + ck_assert_int_eq(PARAM_ROLE, if_trait->parameters[0]); // TODO: report conflict, not PARAM_ROLE + for (int i = 1; i < TRAIT_MAX_PARAMETERS; ++i) ck_assert_int_eq(PARAM_INITIAL, if_trait->parameters[i]); + ck_assert_ptr_nonnull(if_trait->node); + + ck_assert_int_eq(SELINT_SUCCESS, infer_interfaces_deep(ast)); + + // conflict_infer + if_trait = look_up_in_if_traits_map("conflict_infer"); + ck_assert_ptr_nonnull(if_trait); + ck_assert_str_eq("conflict_infer", if_trait->name); + ck_assert_int_eq(INTERFACE_TRAIT, if_trait->type); + ck_assert_int_eq(true, if_trait->is_inferred); + ck_assert_int_eq(PARAM_ROLE, if_trait->parameters[0]); + for (int i = 1; i < TRAIT_MAX_PARAMETERS; ++i) ck_assert_int_eq(PARAM_INITIAL, if_trait->parameters[i]); + ck_assert_ptr_nonnull(if_trait->node); + + // cleanup + free_policy_node(ast); + + cleanup_parsing(); + +} +END_TEST + static Suite *parsing_suite(void) { Suite *s; TCase *tc_core; @@ -233,6 +279,7 @@ static Suite *parsing_suite(void) { tcase_add_test(tc_core, test_infer_simple); tcase_add_test(tc_core, test_infer_loop); + tcase_add_test(tc_core, test_infer_conflict); suite_add_tcase(s, tc_core); return s; diff --git a/tests/sample_policy_files/infer_conflict.if b/tests/sample_policy_files/infer_conflict.if new file mode 100644 index 00000000..dcc42c99 --- /dev/null +++ b/tests/sample_policy_files/infer_conflict.if @@ -0,0 +1,4 @@ +interface(`conflict_infer',` + allow $1 target:cls perm; + role $1 types target; +') diff --git a/tests/sample_policy_files/simple_infer.if b/tests/sample_policy_files/infer_simple.if similarity index 100% rename from tests/sample_policy_files/simple_infer.if rename to tests/sample_policy_files/infer_simple.if