| /* Authors: Joshua Brindle <[email protected]> |
| * Jason Tang <[email protected]> |
| * |
| * Updates: KaiGai Kohei <[email protected]> |
| * adds checks based on newer boundary facility. |
| * |
| * A set of utility functions that aid policy decision when dealing |
| * with hierarchal namespaces. |
| * |
| * Copyright (C) 2005 Tresys Technology, LLC |
| * |
| * Copyright (c) 2008 NEC Corporation |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Lesser General Public |
| * License as published by the Free Software Foundation; either |
| * version 2.1 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public |
| * License along with this library; if not, write to the Free Software |
| * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA |
| */ |
| |
| #include <string.h> |
| #include <stdlib.h> |
| #include <assert.h> |
| #include <sepol/policydb/policydb.h> |
| #include <sepol/policydb/conditional.h> |
| #include <sepol/policydb/hierarchy.h> |
| #include <sepol/policydb/expand.h> |
| #include <sepol/policydb/util.h> |
| |
| #include "debug.h" |
| |
| typedef struct hierarchy_args { |
| policydb_t *p; |
| avtab_t *expa; /* expanded avtab */ |
| /* This tells check_avtab_hierarchy to check this list in addition to the unconditional avtab */ |
| cond_av_list_t *opt_cond_list; |
| sepol_handle_t *handle; |
| int numerr; |
| } hierarchy_args_t; |
| |
| /* |
| * find_parent_(type|role|user) |
| * |
| * This function returns the parent datum of given XXX_datum_t |
| * object or NULL, if it doesn't exist. |
| * |
| * If the given datum has a valid bounds, this function merely |
| * returns the indicated object. Otherwise, it looks up the |
| * parent based on the based hierarchy. |
| */ |
| #define find_parent_template(prefix) \ |
| int find_parent_##prefix(hierarchy_args_t *a, \ |
| prefix##_datum_t *datum, \ |
| prefix##_datum_t **parent) \ |
| { \ |
| char *parent_name, *datum_name, *tmp; \ |
| \ |
| if (datum->bounds) \ |
| *parent = a->p->prefix##_val_to_struct[datum->bounds - 1]; \ |
| else { \ |
| datum_name = a->p->p_##prefix##_val_to_name[datum->s.value - 1]; \ |
| \ |
| tmp = strrchr(datum_name, '.'); \ |
| /* no '.' means it has no parent */ \ |
| if (!tmp) { \ |
| *parent = NULL; \ |
| return 0; \ |
| } \ |
| \ |
| parent_name = strdup(datum_name); \ |
| if (!parent_name) \ |
| return -1; \ |
| parent_name[tmp - datum_name] = '\0'; \ |
| \ |
| *parent = hashtab_search(a->p->p_##prefix##s.table, parent_name); \ |
| if (!*parent) { \ |
| /* Orphan type/role/user */ \ |
| ERR(a->handle, \ |
| "%s doesn't exist, %s is an orphan", \ |
| parent_name, \ |
| a->p->p_##prefix##_val_to_name[datum->s.value - 1]); \ |
| free(parent_name); \ |
| return -1; \ |
| } \ |
| free(parent_name); \ |
| } \ |
| \ |
| return 0; \ |
| } |
| |
| static find_parent_template(type) |
| static find_parent_template(role) |
| static find_parent_template(user) |
| |
| static void compute_avtab_datum(hierarchy_args_t *args, |
| avtab_key_t *key, |
| avtab_datum_t *result) |
| { |
| avtab_datum_t *avdatp; |
| uint32_t av = 0; |
| |
| avdatp = avtab_search(args->expa, key); |
| if (avdatp) |
| av = avdatp->data; |
| if (args->opt_cond_list) { |
| avdatp = cond_av_list_search(key, args->opt_cond_list); |
| if (avdatp) |
| av |= avdatp->data; |
| } |
| |
| result->data = av; |
| } |
| |
| /* This function verifies that the type passed in either has a parent or is in the |
| * root of the namespace, 0 on success, 1 on orphan and -1 on error |
| */ |
| static int check_type_hierarchy_callback(hashtab_key_t k, hashtab_datum_t d, |
| void *args) |
| { |
| hierarchy_args_t *a; |
| type_datum_t *t, *tp; |
| |
| a = (hierarchy_args_t *) args; |
| t = (type_datum_t *) d; |
| |
| if (t->flavor == TYPE_ATTRIB) { |
| /* It's an attribute, we don't care */ |
| return 0; |
| } |
| if (find_parent_type(a, t, &tp) < 0) |
| return -1; |
| |
| if (tp && tp->flavor == TYPE_ATTRIB) { |
| /* The parent is an attribute but the child isn't, not legal */ |
| ERR(a->handle, "type %s is a child of an attribute %s", |
| (char *) k, a->p->p_type_val_to_name[tp->s.value - 1]); |
| a->numerr++; |
| return -1; |
| } |
| return 0; |
| } |
| |
| /* This function only verifies that the avtab node passed in does not violate any |
| * hiearchy constraint via any relationship with other types in the avtab. |
| * it should be called using avtab_map, returns 0 on success, 1 on violation and |
| * -1 on error. opt_cond_list is an optional argument that tells this to check |
| * a conditional list for the relationship as well as the unconditional avtab |
| */ |
| static int check_avtab_hierarchy_callback(avtab_key_t * k, avtab_datum_t * d, |
| void *args) |
| { |
| avtab_key_t key; |
| hierarchy_args_t *a = (hierarchy_args_t *) args; |
| type_datum_t *s, *t1 = NULL, *t2 = NULL; |
| avtab_datum_t av; |
| |
| if (!(k->specified & AVTAB_ALLOWED)) { |
| /* This is not an allow rule, no checking done */ |
| return 0; |
| } |
| |
| /* search for parent first */ |
| s = a->p->type_val_to_struct[k->source_type - 1]; |
| if (find_parent_type(a, s, &t1) < 0) |
| return -1; |
| if (t1) { |
| /* |
| * search for access allowed between type 1's |
| * parent and type 2. |
| */ |
| key.source_type = t1->s.value; |
| key.target_type = k->target_type; |
| key.target_class = k->target_class; |
| key.specified = AVTAB_ALLOWED; |
| compute_avtab_datum(a, &key, &av); |
| |
| if ((av.data & d->data) == d->data) |
| return 0; |
| } |
| |
| /* next we try type 1 and type 2's parent */ |
| s = a->p->type_val_to_struct[k->target_type - 1]; |
| if (find_parent_type(a, s, &t2) < 0) |
| return -1; |
| if (t2) { |
| /* |
| * search for access allowed between type 1 and |
| * type 2's parent. |
| */ |
| key.source_type = k->source_type; |
| key.target_type = t2->s.value; |
| key.target_class = k->target_class; |
| key.specified = AVTAB_ALLOWED; |
| compute_avtab_datum(a, &key, &av); |
| |
| if ((av.data & d->data) == d->data) |
| return 0; |
| } |
| |
| if (t1 && t2) { |
| /* |
| * search for access allowed between type 1's parent |
| * and type 2's parent. |
| */ |
| key.source_type = t1->s.value; |
| key.target_type = t2->s.value; |
| key.target_class = k->target_class; |
| key.specified = AVTAB_ALLOWED; |
| compute_avtab_datum(a, &key, &av); |
| |
| if ((av.data & d->data) == d->data) |
| return 0; |
| } |
| |
| /* |
| * Neither one of these types have parents and |
| * therefore the hierarchical constraint does not apply |
| */ |
| if (!t1 && !t2) |
| return 0; |
| |
| /* |
| * At this point there is a violation of the hierarchal |
| * constraint, send error condition back |
| */ |
| ERR(a->handle, |
| "hierarchy violation between types %s and %s : %s { %s }", |
| a->p->p_type_val_to_name[k->source_type - 1], |
| a->p->p_type_val_to_name[k->target_type - 1], |
| a->p->p_class_val_to_name[k->target_class - 1], |
| sepol_av_to_string(a->p, k->target_class, d->data & ~av.data)); |
| a->numerr++; |
| return 0; |
| } |
| |
| /* |
| * If same permissions are allowed for same combination of |
| * source and target, we can evaluate them as unconditional |
| * one. |
| * See the following example. A_t type is bounds of B_t type, |
| * so B_t can never have wider permissions then A_t. |
| * A_t has conditional permission on X_t, however, a part of |
| * them (getattr and read) are unconditionaly allowed to A_t. |
| * |
| * Example) |
| * typebounds A_t B_t; |
| * |
| * allow B_t X_t : file { getattr }; |
| * if (foo_bool) { |
| * allow A_t X_t : file { getattr read }; |
| * } else { |
| * allow A_t X_t : file { getattr read write }; |
| * } |
| * |
| * We have to pull up them as unconditional ones in this case, |
| * because it seems to us B_t is violated to bounds constraints |
| * during unconditional policy checking. |
| */ |
| static int pullup_unconditional_perms(cond_list_t * cond_list, |
| hierarchy_args_t * args) |
| { |
| cond_list_t *cur_node; |
| cond_av_list_t *cur_av, *expl_true = NULL, *expl_false = NULL; |
| avtab_t expa_true, expa_false; |
| avtab_datum_t *avdatp; |
| avtab_datum_t avdat; |
| avtab_ptr_t avnode; |
| |
| for (cur_node = cond_list; cur_node; cur_node = cur_node->next) { |
| if (avtab_init(&expa_true)) |
| goto oom0; |
| if (avtab_init(&expa_false)) |
| goto oom1; |
| if (expand_cond_av_list(args->p, cur_node->true_list, |
| &expl_true, &expa_true)) |
| goto oom2; |
| if (expand_cond_av_list(args->p, cur_node->false_list, |
| &expl_false, &expa_false)) |
| goto oom3; |
| for (cur_av = expl_true; cur_av; cur_av = cur_av->next) { |
| avdatp = avtab_search(&expa_false, |
| &cur_av->node->key); |
| if (!avdatp) |
| continue; |
| |
| avdat.data = (cur_av->node->datum.data |
| & avdatp->data); |
| if (!avdat.data) |
| continue; |
| |
| avnode = avtab_search_node(args->expa, |
| &cur_av->node->key); |
| if (avnode) { |
| avnode->datum.data |= avdat.data; |
| } else { |
| if (avtab_insert(args->expa, |
| &cur_av->node->key, |
| &avdat)) |
| goto oom4; |
| } |
| } |
| cond_av_list_destroy(expl_false); |
| cond_av_list_destroy(expl_true); |
| avtab_destroy(&expa_false); |
| avtab_destroy(&expa_true); |
| } |
| return 0; |
| |
| oom4: |
| cond_av_list_destroy(expl_false); |
| oom3: |
| cond_av_list_destroy(expl_true); |
| oom2: |
| avtab_destroy(&expa_false); |
| oom1: |
| avtab_destroy(&expa_true); |
| oom0: |
| ERR(args->handle, "out of memory on conditional av list expansion"); |
| return 1; |
| } |
| |
| static int check_cond_avtab_hierarchy(cond_list_t * cond_list, |
| hierarchy_args_t * args) |
| { |
| int rc; |
| cond_list_t *cur_node; |
| cond_av_list_t *cur_av, *expl = NULL; |
| avtab_t expa; |
| hierarchy_args_t *a = (hierarchy_args_t *) args; |
| avtab_datum_t avdat, *uncond; |
| |
| for (cur_node = cond_list; cur_node; cur_node = cur_node->next) { |
| /* |
| * Check true condition |
| */ |
| if (avtab_init(&expa)) |
| goto oom; |
| if (expand_cond_av_list(args->p, cur_node->true_list, |
| &expl, &expa)) { |
| avtab_destroy(&expa); |
| goto oom; |
| } |
| args->opt_cond_list = expl; |
| for (cur_av = expl; cur_av; cur_av = cur_av->next) { |
| avdat.data = cur_av->node->datum.data; |
| uncond = avtab_search(a->expa, &cur_av->node->key); |
| if (uncond) |
| avdat.data |= uncond->data; |
| rc = check_avtab_hierarchy_callback(&cur_av->node->key, |
| &avdat, args); |
| if (rc) |
| args->numerr++; |
| } |
| cond_av_list_destroy(expl); |
| |
| /* |
| * Check false condition |
| */ |
| if (avtab_init(&expa)) |
| goto oom; |
| if (expand_cond_av_list(args->p, cur_node->false_list, |
| &expl, &expa)) { |
| avtab_destroy(&expa); |
| goto oom; |
| } |
| args->opt_cond_list = expl; |
| for (cur_av = expl; cur_av; cur_av = cur_av->next) { |
| avdat.data = cur_av->node->datum.data; |
| uncond = avtab_search(a->expa, &cur_av->node->key); |
| if (uncond) |
| avdat.data |= uncond->data; |
| |
| rc = check_avtab_hierarchy_callback(&cur_av->node->key, |
| &avdat, args); |
| if (rc) |
| a->numerr++; |
| } |
| cond_av_list_destroy(expl); |
| avtab_destroy(&expa); |
| } |
| |
| return 0; |
| |
| oom: |
| ERR(args->handle, "out of memory on conditional av list expansion"); |
| return 1; |
| } |
| |
| /* The role hierarchy is defined as: a child role cannot have more types than it's parent. |
| * This function should be called with hashtab_map, it will return 0 on success, 1 on |
| * constraint violation and -1 on error |
| */ |
| static int check_role_hierarchy_callback(hashtab_key_t k |
| __attribute__ ((unused)), |
| hashtab_datum_t d, void *args) |
| { |
| hierarchy_args_t *a; |
| role_datum_t *r, *rp; |
| |
| a = (hierarchy_args_t *) args; |
| r = (role_datum_t *) d; |
| |
| if (find_parent_role(a, r, &rp) < 0) |
| return -1; |
| |
| if (rp && !ebitmap_contains(&rp->types.types, &r->types.types)) { |
| /* hierarchical constraint violation, return error */ |
| ERR(a->handle, "Role hierarchy violation, %s exceeds %s", |
| (char *) k, a->p->p_role_val_to_name[rp->s.value - 1]); |
| a->numerr++; |
| } |
| return 0; |
| } |
| |
| /* The user hierarchy is defined as: a child user cannot have a role that |
| * its parent doesn't have. This function should be called with hashtab_map, |
| * it will return 0 on success, 1 on constraint violation and -1 on error. |
| */ |
| static int check_user_hierarchy_callback(hashtab_key_t k |
| __attribute__ ((unused)), |
| hashtab_datum_t d, void *args) |
| { |
| hierarchy_args_t *a; |
| user_datum_t *u, *up; |
| |
| a = (hierarchy_args_t *) args; |
| u = (user_datum_t *) d; |
| |
| if (find_parent_user(a, u, &up) < 0) |
| return -1; |
| |
| if (up && !ebitmap_contains(&up->roles.roles, &u->roles.roles)) { |
| /* hierarchical constraint violation, return error */ |
| ERR(a->handle, "User hierarchy violation, %s exceeds %s", |
| (char *) k, a->p->p_user_val_to_name[up->s.value - 1]); |
| a->numerr++; |
| } |
| return 0; |
| } |
| |
| int hierarchy_check_constraints(sepol_handle_t * handle, policydb_t * p) |
| { |
| hierarchy_args_t args; |
| avtab_t expa; |
| |
| if (avtab_init(&expa)) |
| goto oom; |
| if (expand_avtab(p, &p->te_avtab, &expa)) { |
| avtab_destroy(&expa); |
| goto oom; |
| } |
| |
| args.p = p; |
| args.expa = &expa; |
| args.opt_cond_list = NULL; |
| args.handle = handle; |
| args.numerr = 0; |
| |
| if (hashtab_map(p->p_types.table, check_type_hierarchy_callback, &args)) |
| goto bad; |
| |
| if (pullup_unconditional_perms(p->cond_list, &args)) |
| return -1; |
| |
| if (avtab_map(&expa, check_avtab_hierarchy_callback, &args)) |
| goto bad; |
| |
| if (check_cond_avtab_hierarchy(p->cond_list, &args)) |
| goto bad; |
| |
| if (hashtab_map(p->p_roles.table, check_role_hierarchy_callback, &args)) |
| goto bad; |
| |
| if (hashtab_map(p->p_users.table, check_user_hierarchy_callback, &args)) |
| goto bad; |
| |
| if (args.numerr) { |
| ERR(handle, "%d total errors found during hierarchy check", |
| args.numerr); |
| goto bad; |
| } |
| |
| avtab_destroy(&expa); |
| return 0; |
| |
| bad: |
| avtab_destroy(&expa); |
| return -1; |
| |
| oom: |
| ERR(handle, "Out of memory"); |
| return -1; |
| } |