/* 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" #define BOUNDS_AVTAB_SIZE 1024 static int bounds_insert_helper(sepol_handle_t *handle, avtab_t *avtab, avtab_key_t *avtab_key, avtab_datum_t *datum) { int rc = avtab_insert(avtab, avtab_key, datum); if (rc) { if (rc == SEPOL_ENOMEM) ERR(handle, "Insufficient memory"); else ERR(handle, "Unexpected error (%d)", rc); } return rc; } static int bounds_insert_rule(sepol_handle_t *handle, avtab_t *avtab, avtab_t *global, avtab_t *other, avtab_key_t *avtab_key, avtab_datum_t *datum) { int rc = 0; avtab_datum_t *dup = avtab_search(avtab, avtab_key); if (!dup) { rc = bounds_insert_helper(handle, avtab, avtab_key, datum); if (rc) goto exit; } else { dup->data |= datum->data; } if (other) { /* Search the other conditional avtab for the key and * add any common permissions to the global avtab */ uint32_t data = 0; dup = avtab_search(other, avtab_key); if (dup) { data = dup->data & datum->data; if (data) { dup = avtab_search(global, avtab_key); if (!dup) { avtab_datum_t d; d.data = data; rc = bounds_insert_helper(handle, global, avtab_key, &d); if (rc) goto exit; } else { dup->data |= data; } } } } exit: return rc; } static int bounds_expand_rule(sepol_handle_t *handle, policydb_t *p, avtab_t *avtab, avtab_t *global, avtab_t *other, uint32_t parent, uint32_t src, uint32_t tgt, uint32_t class, uint32_t data) { int rc = 0; avtab_key_t avtab_key; avtab_datum_t datum; ebitmap_node_t *tnode; unsigned int i; avtab_key.specified = AVTAB_ALLOWED; avtab_key.target_class = class; datum.data = data; if (ebitmap_get_bit(&p->attr_type_map[src - 1], parent - 1)) { avtab_key.source_type = parent; ebitmap_for_each_bit(&p->attr_type_map[tgt - 1], tnode, i) { if (!ebitmap_node_get_bit(tnode, i)) continue; avtab_key.target_type = i + 1; rc = bounds_insert_rule(handle, avtab, global, other, &avtab_key, &datum); if (rc) goto exit; } } exit: return rc; } static int bounds_expand_cond_rules(sepol_handle_t *handle, policydb_t *p, cond_av_list_t *cur, avtab_t *avtab, avtab_t *global, avtab_t *other, uint32_t parent) { int rc = 0; for (; cur; cur = cur->next) { avtab_ptr_t n = cur->node; rc = bounds_expand_rule(handle, p, avtab, global, other, parent, n->key.source_type, n->key.target_type, n->key.target_class, n->datum.data); if (rc) goto exit; } exit: return rc; } struct bounds_expand_args { sepol_handle_t *handle; policydb_t *p; avtab_t *avtab; uint32_t parent; }; static int bounds_expand_rule_callback(avtab_key_t *k, avtab_datum_t *d, void *args) { struct bounds_expand_args *a = (struct bounds_expand_args *)args; if (!(k->specified & AVTAB_ALLOWED)) return 0; return bounds_expand_rule(a->handle, a->p, a->avtab, NULL, NULL, a->parent, k->source_type, k->target_type, k->target_class, d->data); } struct bounds_cond_info { avtab_t true_avtab; avtab_t false_avtab; cond_list_t *cond_list; struct bounds_cond_info *next; }; static void bounds_destroy_cond_info(struct bounds_cond_info *cur) { struct bounds_cond_info *next; for (; cur; cur = next) { next = cur->next; avtab_destroy(&cur->true_avtab); avtab_destroy(&cur->false_avtab); cur->next = NULL; free(cur); } } static int bounds_expand_parent_rules(sepol_handle_t *handle, policydb_t *p, avtab_t *global_avtab, struct bounds_cond_info **cond_info, uint32_t parent) { int rc = 0; struct bounds_expand_args args; cond_list_t *cur; avtab_init(global_avtab); rc = avtab_alloc(global_avtab, BOUNDS_AVTAB_SIZE); if (rc) goto oom; args.handle = handle; args.p = p; args.avtab = global_avtab; args.parent = parent; rc = avtab_map(&p->te_avtab, bounds_expand_rule_callback, &args); if (rc) goto exit; *cond_info = NULL; for (cur = p->cond_list; cur; cur = cur->next) { struct bounds_cond_info *ci; ci = malloc(sizeof(struct bounds_cond_info)); if (!ci) goto oom; avtab_init(&ci->true_avtab); avtab_init(&ci->false_avtab); ci->cond_list = cur; ci->next = *cond_info; *cond_info = ci; if (cur->true_list) { rc = avtab_alloc(&ci->true_avtab, BOUNDS_AVTAB_SIZE); if (rc) goto oom; rc = bounds_expand_cond_rules(handle, p, cur->true_list, &ci->true_avtab, NULL, NULL, parent); if (rc) goto exit; } if (cur->false_list) { rc = avtab_alloc(&ci->false_avtab, BOUNDS_AVTAB_SIZE); if (rc) goto oom; rc = bounds_expand_cond_rules(handle, p, cur->false_list, &ci->false_avtab, global_avtab, &ci->true_avtab, parent); if (rc) goto exit; } } return 0; oom: ERR(handle, "Insufficient memory"); exit: ERR(handle,"Failed to expand parent rules\n"); avtab_destroy(global_avtab); bounds_destroy_cond_info(*cond_info); *cond_info = NULL; return rc; } static int bounds_not_covered(avtab_t *global_avtab, avtab_t *cur_avtab, avtab_key_t *avtab_key, uint32_t data) { avtab_datum_t *datum = avtab_search(cur_avtab, avtab_key); if (datum) data &= ~datum->data; if (global_avtab && data) { datum = avtab_search(global_avtab, avtab_key); if (datum) data &= ~datum->data; } return data; } static int bounds_add_bad(sepol_handle_t *handle, uint32_t src, uint32_t tgt, uint32_t class, uint32_t data, avtab_ptr_t *bad) { struct avtab_node *new = malloc(sizeof(struct avtab_node)); if (new == NULL) { ERR(handle, "Insufficient memory"); return SEPOL_ENOMEM; } memset(new, 0, sizeof(struct avtab_node)); new->key.source_type = src; new->key.target_type = tgt; new->key.target_class = class; new->datum.data = data; new->next = *bad; *bad = new; return 0; } static int bounds_check_rule(sepol_handle_t *handle, policydb_t *p, avtab_t *global_avtab, avtab_t *cur_avtab, uint32_t child, uint32_t parent, uint32_t src, uint32_t tgt, uint32_t class, uint32_t data, avtab_ptr_t *bad, int *numbad) { int rc = 0; avtab_key_t avtab_key; type_datum_t *td; ebitmap_node_t *tnode; unsigned int i; uint32_t d; avtab_key.specified = AVTAB_ALLOWED; avtab_key.target_class = class; if (ebitmap_get_bit(&p->attr_type_map[src - 1], child - 1)) { avtab_key.source_type = parent; ebitmap_for_each_bit(&p->attr_type_map[tgt - 1], tnode, i) { if (!ebitmap_node_get_bit(tnode, i)) continue; td = p->type_val_to_struct[i]; if (td && td->bounds) { avtab_key.target_type = td->bounds; d = bounds_not_covered(global_avtab, cur_avtab, &avtab_key, data); } else { avtab_key.target_type = i + 1; d = bounds_not_covered(global_avtab, cur_avtab, &avtab_key, data); } if (d) { (*numbad)++; rc = bounds_add_bad(handle, child, i+1, class, d, bad); if (rc) goto exit; } } } exit: return rc; } static int bounds_check_cond_rules(sepol_handle_t *handle, policydb_t *p, avtab_t *global_avtab, avtab_t *cond_avtab, cond_av_list_t *rules, uint32_t child, uint32_t parent, avtab_ptr_t *bad, int *numbad) { int rc = 0; cond_av_list_t *cur; for (cur = rules; cur; cur = cur->next) { avtab_ptr_t ap = cur->node; avtab_key_t *key = &ap->key; avtab_datum_t *datum = &ap->datum; if (!(key->specified & AVTAB_ALLOWED)) continue; rc = bounds_check_rule(handle, p, global_avtab, cond_avtab, child, parent, key->source_type, key->target_type, key->target_class, datum->data, bad, numbad); if (rc) goto exit; } exit: return rc; } struct bounds_check_args { sepol_handle_t *handle; policydb_t *p; avtab_t *cur_avtab; uint32_t child; uint32_t parent; avtab_ptr_t bad; int numbad; }; static int bounds_check_rule_callback(avtab_key_t *k, avtab_datum_t *d, void *args) { struct bounds_check_args *a = (struct bounds_check_args *)args; if (!(k->specified & AVTAB_ALLOWED)) return 0; return bounds_check_rule(a->handle, a->p, NULL, a->cur_avtab, a->child, a->parent, k->source_type, k->target_type, k->target_class, d->data, &a->bad, &a->numbad); } static int bounds_check_child_rules(sepol_handle_t *handle, policydb_t *p, avtab_t *global_avtab, struct bounds_cond_info *cond_info, uint32_t child, uint32_t parent, avtab_ptr_t *bad, int *numbad) { int rc; struct bounds_check_args args; struct bounds_cond_info *cur; args.handle = handle; args.p = p; args.cur_avtab = global_avtab; args.child = child; args.parent = parent; args.bad = NULL; args.numbad = 0; rc = avtab_map(&p->te_avtab, bounds_check_rule_callback, &args); if (rc) goto exit; for (cur = cond_info; cur; cur = cur->next) { cond_list_t *node = cur->cond_list; rc = bounds_check_cond_rules(handle, p, global_avtab, &cur->true_avtab, node->true_list, child, parent, &args.bad, &args.numbad); if (rc) goto exit; rc = bounds_check_cond_rules(handle, p, global_avtab, &cur->false_avtab, node->false_list, child, parent, &args.bad, &args.numbad); if (rc) goto exit; } *numbad += args.numbad; *bad = args.bad; exit: return rc; } int bounds_check_type(sepol_handle_t *handle, policydb_t *p, uint32_t child, uint32_t parent, avtab_ptr_t *bad, int *numbad) { int rc = 0; avtab_t global_avtab; struct bounds_cond_info *cond_info = NULL; rc = bounds_expand_parent_rules(handle, p, &global_avtab, &cond_info, parent); if (rc) goto exit; rc = bounds_check_child_rules(handle, p, &global_avtab, cond_info, child, parent, bad, numbad); bounds_destroy_cond_info(cond_info); avtab_destroy(&global_avtab); exit: return rc; } struct bounds_args { sepol_handle_t *handle; policydb_t *p; int numbad; }; static void bounds_report(sepol_handle_t *handle, policydb_t *p, uint32_t child, uint32_t parent, avtab_ptr_t cur) { ERR(handle, "Child type %s exceeds bounds of parent %s in the following rules:", p->p_type_val_to_name[child - 1], p->p_type_val_to_name[parent - 1]); for (; cur; cur = cur->next) { ERR(handle, " %s %s : %s { %s }", p->p_type_val_to_name[cur->key.source_type - 1], p->p_type_val_to_name[cur->key.target_type - 1], p->p_class_val_to_name[cur->key.target_class - 1], sepol_av_to_string(p, cur->key.target_class, cur->datum.data)); } } void bounds_destroy_bad(avtab_ptr_t cur) { avtab_ptr_t next; for (; cur; cur = next) { next = cur->next; cur->next = NULL; free(cur); } } static int bounds_check_type_callback(hashtab_key_t k __attribute__ ((unused)), hashtab_datum_t d, void *args) { int rc = 0; struct bounds_args *a = (struct bounds_args *)args; type_datum_t *t = (type_datum_t *)d; avtab_ptr_t bad = NULL; if (t->bounds) { rc = bounds_check_type(a->handle, a->p, t->s.value, t->bounds, &bad, &a->numbad); if (bad) { bounds_report(a->handle, a->p, t->s.value, t->bounds, bad); bounds_destroy_bad(bad); } } return rc; } int bounds_check_types(sepol_handle_t *handle, policydb_t *p) { int rc; struct bounds_args args; args.handle = handle; args.p = p; args.numbad = 0; rc = hashtab_map(p->p_types.table, bounds_check_type_callback, &args); if (rc) goto exit; if (args.numbad > 0) { ERR(handle, "%d errors found during type bounds check", args.numbad); rc = SEPOL_ERR; } exit: return rc; } /* The role bounds is defined as: a child role cannot have a type that * its parent doesn't have. */ static int bounds_check_role_callback(hashtab_key_t k, hashtab_datum_t d, void *args) { struct bounds_args *a = (struct bounds_args *)args; role_datum_t *r = (role_datum_t *) d; role_datum_t *rp = NULL; if (!r->bounds) return 0; rp = a->p->role_val_to_struct[r->bounds - 1]; if (rp && !ebitmap_contains(&rp->types.types, &r->types.types)) { ERR(a->handle, "Role bounds violation, %s exceeds %s", (char *)k, a->p->p_role_val_to_name[rp->s.value - 1]); a->numbad++; } return 0; } int bounds_check_roles(sepol_handle_t *handle, policydb_t *p) { struct bounds_args args; args.handle = handle; args.p = p; args.numbad = 0; hashtab_map(p->p_roles.table, bounds_check_role_callback, &args); if (args.numbad > 0) { ERR(handle, "%d errors found during role bounds check", args.numbad); return SEPOL_ERR; } return 0; } /* The user bounds is defined as: a child user cannot have a role that * its parent doesn't have. */ static int bounds_check_user_callback(hashtab_key_t k, hashtab_datum_t d, void *args) { struct bounds_args *a = (struct bounds_args *)args; user_datum_t *u = (user_datum_t *) d; user_datum_t *up = NULL; if (!u->bounds) return 0; up = a->p->user_val_to_struct[u->bounds - 1]; if (up && !ebitmap_contains(&up->roles.roles, &u->roles.roles)) { ERR(a->handle, "User bounds violation, %s exceeds %s", (char *) k, a->p->p_user_val_to_name[up->s.value - 1]); a->numbad++; } return 0; } int bounds_check_users(sepol_handle_t *handle, policydb_t *p) { struct bounds_args args; args.handle = handle; args.p = p; args.numbad = 0; hashtab_map(p->p_users.table, bounds_check_user_callback, &args); if (args.numbad > 0) { ERR(handle, "%d errors found during user bounds check", args.numbad); return SEPOL_ERR; } return 0; } #define add_hierarchy_callback_template(prefix) \ int hierarchy_add_##prefix##_callback(hashtab_key_t k __attribute__ ((unused)), \ hashtab_datum_t d, void *args) \ { \ struct bounds_args *a = (struct bounds_args *)args; \ sepol_handle_t *handle = a->handle; \ policydb_t *p = a->p; \ prefix##_datum_t *datum = (prefix##_datum_t *)d; \ prefix##_datum_t *parent; \ char *parent_name, *datum_name, *tmp; \ \ if (!datum->bounds) { \ datum_name = p->p_##prefix##_val_to_name[datum->s.value - 1]; \ \ tmp = strrchr(datum_name, '.'); \ /* no '.' means it has no parent */ \ if (!tmp) return 0; \ \ parent_name = strdup(datum_name); \ if (!parent_name) { \ ERR(handle, "Insufficient memory"); \ return SEPOL_ENOMEM; \ } \ parent_name[tmp - datum_name] = '\0'; \ \ parent = hashtab_search(p->p_##prefix##s.table, parent_name); \ if (!parent) { \ /* Orphan type/role/user */ \ ERR(handle, "%s doesn't exist, %s is an orphan",\ parent_name, \ p->p_##prefix##_val_to_name[datum->s.value - 1]); \ free(parent_name); \ a->numbad++; \ return 0; \ } \ datum->bounds = parent->s.value; \ free(parent_name); \ } \ \ return 0; \ } \ static add_hierarchy_callback_template(type) static add_hierarchy_callback_template(role) static add_hierarchy_callback_template(user) int hierarchy_add_bounds(sepol_handle_t *handle, policydb_t *p) { int rc = 0; struct bounds_args args; args.handle = handle; args.p = p; args.numbad = 0; rc = hashtab_map(p->p_users.table, hierarchy_add_user_callback, &args); if (rc) goto exit; rc = hashtab_map(p->p_roles.table, hierarchy_add_role_callback, &args); if (rc) goto exit; rc = hashtab_map(p->p_types.table, hierarchy_add_type_callback, &args); if (rc) goto exit; if (args.numbad > 0) { ERR(handle, "%d errors found while adding hierarchies", args.numbad); rc = SEPOL_ERR; } exit: return rc; } int hierarchy_check_constraints(sepol_handle_t * handle, policydb_t * p) { int rc = 0; int violation = 0; rc = hierarchy_add_bounds(handle, p); if (rc) goto exit; rc = bounds_check_users(handle, p); if (rc) violation = 1; rc = bounds_check_roles(handle, p); if (rc) violation = 1; rc = bounds_check_types(handle, p); if (rc) { if (rc == SEPOL_ERR) violation = 1; else goto exit; } if (violation) rc = SEPOL_ERR; exit: return rc; }