| /* 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; |
| } |