/* Authors: Joshua Brindle <jbrindle@tresys.com> * Jason Tang <jtang@tresys.com> * * Updates: KaiGai Kohei <kaigai@ak.jp.nec.com> * 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); avtab_destroy(&expa); /* * 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; }