/* Authors: Karl MacMillan <kmacmillan@tresys.com> * Frank Mayer <mayerf@tresys.com> * David Caplan <dac@tresys.com> * * Copyright (C) 2003 - 2005 Tresys Technology, LLC * * 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 <stdlib.h> #include <sepol/policydb/flask_types.h> #include <sepol/policydb/conditional.h> #include "private.h" /* move all type rules to top of t/f lists to help kernel on evaluation */ static void cond_optimize(cond_av_list_t ** l) { cond_av_list_t *top, *p, *cur; top = p = cur = *l; while (cur) { if ((cur->node->key.specified & AVTAB_TYPE) && (top != cur)) { p->next = cur->next; cur->next = top; top = cur; cur = p->next; } else { p = cur; cur = cur->next; } } *l = top; } /* reorder t/f lists for kernel */ void cond_optimize_lists(cond_list_t * cl) { cond_list_t *n; for (n = cl; n != NULL; n = n->next) { cond_optimize(&n->true_list); cond_optimize(&n->false_list); } } static int bool_present(unsigned int target, unsigned int bools[], unsigned int num_bools) { unsigned int i = 0; int ret = 1; if (num_bools > COND_MAX_BOOLS) { return 0; } while (i < num_bools && target != bools[i]) i++; if (i == num_bools) ret = 0; /* got to end w/o match */ return ret; } static int same_bools(cond_node_t * a, cond_node_t * b) { unsigned int i, x; x = a->nbools; /* same number of bools? */ if (x != b->nbools) return 0; /* make sure all the bools in a are also in b */ for (i = 0; i < x; i++) if (!bool_present(a->bool_ids[i], b->bool_ids, x)) return 0; return 1; } /* * Determine if two conditional expressions are equal. */ int cond_expr_equal(cond_node_t * a, cond_node_t * b) { cond_expr_t *cur_a, *cur_b; if (a == NULL || b == NULL) return 0; if (a->nbools != b->nbools) return 0; /* if exprs have <= COND_MAX_BOOLS we can check the precompute values * for the expressions. */ if (a->nbools <= COND_MAX_BOOLS && b->nbools <= COND_MAX_BOOLS) { if (!same_bools(a, b)) return 0; return (a->expr_pre_comp == b->expr_pre_comp); } /* for long expressions we check for exactly the same expression */ cur_a = a->expr; cur_b = b->expr; while (1) { if (cur_a == NULL && cur_b == NULL) return 1; else if (cur_a == NULL || cur_b == NULL) return 0; if (cur_a->expr_type != cur_b->expr_type) return 0; if (cur_a->expr_type == COND_BOOL) { if (cur_a->bool != cur_b->bool) return 0; } cur_a = cur_a->next; cur_b = cur_b->next; } return 1; } /* Create a new conditional node, optionally copying * the conditional expression from an existing node. * If node is NULL then a new node will be created * with no conditional expression. */ cond_node_t *cond_node_create(policydb_t * p, cond_node_t * node) { cond_node_t *new_node; unsigned int i; new_node = (cond_node_t *)malloc(sizeof(cond_node_t)); if (!new_node) { return NULL; } memset(new_node, 0, sizeof(cond_node_t)); if (node) { new_node->expr = cond_copy_expr(node->expr); if (!new_node->expr) { free(new_node); return NULL; } new_node->cur_state = cond_evaluate_expr(p, new_node->expr); new_node->nbools = node->nbools; for (i = 0; i < min(node->nbools, COND_MAX_BOOLS); i++) new_node->bool_ids[i] = node->bool_ids[i]; new_node->expr_pre_comp = node->expr_pre_comp; new_node->flags = node->flags; } return new_node; } /* Find a conditional (the needle) within a list of existing ones (the * haystack) that has a matching expression. If found, return a * pointer to the existing node, setting 'was_created' to 0. * Otherwise create a new one and return it, setting 'was_created' to * 1. */ cond_node_t *cond_node_find(policydb_t * p, cond_node_t * needle, cond_node_t * haystack, int *was_created) { while (haystack) { if (cond_expr_equal(needle, haystack)) { *was_created = 0; return haystack; } haystack = haystack->next; } *was_created = 1; return cond_node_create(p, needle); } /* return either a pre-existing matching node or create a new node */ cond_node_t *cond_node_search(policydb_t * p, cond_node_t * list, cond_node_t * cn) { int was_created; cond_node_t *result = cond_node_find(p, cn, list, &was_created); if (result != NULL && was_created) { /* add conditional node to policy list */ result->next = p->cond_list; p->cond_list = result; } return result; } /* * cond_evaluate_expr evaluates a conditional expr * in reverse polish notation. It returns true (1), false (0), * or undefined (-1). Undefined occurs when the expression * exceeds the stack depth of COND_EXPR_MAXDEPTH. */ int cond_evaluate_expr(policydb_t * p, cond_expr_t * expr) { cond_expr_t *cur; int s[COND_EXPR_MAXDEPTH]; int sp = -1; s[0] = -1; for (cur = expr; cur != NULL; cur = cur->next) { switch (cur->expr_type) { case COND_BOOL: if (sp == (COND_EXPR_MAXDEPTH - 1)) return -1; sp++; s[sp] = p->bool_val_to_struct[cur->bool - 1]->state; break; case COND_NOT: if (sp < 0) return -1; s[sp] = !s[sp]; break; case COND_OR: if (sp < 1) return -1; sp--; s[sp] |= s[sp + 1]; break; case COND_AND: if (sp < 1) return -1; sp--; s[sp] &= s[sp + 1]; break; case COND_XOR: if (sp < 1) return -1; sp--; s[sp] ^= s[sp + 1]; break; case COND_EQ: if (sp < 1) return -1; sp--; s[sp] = (s[sp] == s[sp + 1]); break; case COND_NEQ: if (sp < 1) return -1; sp--; s[sp] = (s[sp] != s[sp + 1]); break; default: return -1; } } return s[0]; } cond_expr_t *cond_copy_expr(cond_expr_t * expr) { cond_expr_t *cur, *head, *tail, *new_expr; tail = head = NULL; cur = expr; while (cur) { new_expr = (cond_expr_t *) malloc(sizeof(cond_expr_t)); if (!new_expr) goto free_head; memset(new_expr, 0, sizeof(cond_expr_t)); new_expr->expr_type = cur->expr_type; new_expr->bool = cur->bool; if (!head) head = new_expr; if (tail) tail->next = new_expr; tail = new_expr; cur = cur->next; } return head; free_head: while (head) { tail = head->next; free(head); head = tail; } return NULL; } /* * evaluate_cond_node evaluates the conditional stored in * a cond_node_t and if the result is different than the * current state of the node it sets the rules in the true/false * list appropriately. If the result of the expression is undefined * all of the rules are disabled for safety. */ static int evaluate_cond_node(policydb_t * p, cond_node_t * node) { int new_state; cond_av_list_t *cur; new_state = cond_evaluate_expr(p, node->expr); if (new_state != node->cur_state) { node->cur_state = new_state; if (new_state == -1) printf ("expression result was undefined - disabling all rules.\n"); /* turn the rules on or off */ for (cur = node->true_list; cur != NULL; cur = cur->next) { if (new_state <= 0) { cur->node->key.specified &= ~AVTAB_ENABLED; } else { cur->node->key.specified |= AVTAB_ENABLED; } } for (cur = node->false_list; cur != NULL; cur = cur->next) { /* -1 or 1 */ if (new_state) { cur->node->key.specified &= ~AVTAB_ENABLED; } else { cur->node->key.specified |= AVTAB_ENABLED; } } } return 0; } /* precompute and simplify an expression if possible. If left with !expression, change * to expression and switch t and f. precompute expression for expressions with limited * number of bools. */ int cond_normalize_expr(policydb_t * p, cond_node_t * cn) { cond_expr_t *ne, *e; cond_av_list_t *tmp; unsigned int i, j, orig_value[COND_MAX_BOOLS]; int k; uint32_t test = 0x0; avrule_t *tmp2; cn->nbools = 0; memset(cn->bool_ids, 0, sizeof(cn->bool_ids)); cn->expr_pre_comp = 0x0; /* take care of !expr case */ ne = NULL; e = cn->expr; /* becuase it's RPN look at last element */ while (e->next != NULL) { ne = e; e = e->next; } if (e->expr_type == COND_NOT) { if (ne) { ne->next = NULL; } else { /* ne should never be NULL */ printf ("Found expr with no bools and only a ! - this should never happen.\n"); return -1; } /* swap the true and false lists */ tmp = cn->true_list; cn->true_list = cn->false_list; cn->false_list = tmp; tmp2 = cn->avtrue_list; cn->avtrue_list = cn->avfalse_list; cn->avfalse_list = tmp2; /* free the "not" node in the list */ free(e); } /* find all the bools in the expression */ for (e = cn->expr; e != NULL; e = e->next) { switch (e->expr_type) { case COND_BOOL: i = 0; /* see if we've already seen this bool */ if (!bool_present(e->bool, cn->bool_ids, cn->nbools)) { /* count em all but only record up to COND_MAX_BOOLS */ if (cn->nbools < COND_MAX_BOOLS) cn->bool_ids[cn->nbools++] = e->bool; else cn->nbools++; } break; default: break; } } /* only precompute for exprs with <= COND_AX_BOOLS */ if (cn->nbools <= COND_MAX_BOOLS) { /* save the default values for the bools so we can play with them */ for (i = 0; i < cn->nbools; i++) { orig_value[i] = p->bool_val_to_struct[cn->bool_ids[i] - 1]->state; } /* loop through all possible combinations of values for bools in expression */ for (test = 0x0; test < (0x1U << cn->nbools); test++) { /* temporarily set the value for all the bools in the * expression using the corr. bit in test */ for (j = 0; j < cn->nbools; j++) { p->bool_val_to_struct[cn->bool_ids[j] - 1]->state = (test & (0x1 << j)) ? 1 : 0; } k = cond_evaluate_expr(p, cn->expr); if (k == -1) { printf ("While testing expression, expression result " "was undefined - this should never happen.\n"); return -1; } /* set the bit if expression evaluates true */ if (k) cn->expr_pre_comp |= 0x1 << test; } /* restore bool default values */ for (i = 0; i < cn->nbools; i++) p->bool_val_to_struct[cn->bool_ids[i] - 1]->state = orig_value[i]; } return 0; } int evaluate_conds(policydb_t * p) { int ret; cond_node_t *cur; for (cur = p->cond_list; cur != NULL; cur = cur->next) { ret = evaluate_cond_node(p, cur); if (ret) return ret; } return 0; } int cond_policydb_init(policydb_t * p) { p->bool_val_to_struct = NULL; p->cond_list = NULL; if (avtab_init(&p->te_cond_avtab)) return -1; return 0; } void cond_av_list_destroy(cond_av_list_t * list) { cond_av_list_t *cur, *next; for (cur = list; cur != NULL; cur = next) { next = cur->next; /* the avtab_ptr_t node is destroy by the avtab */ free(cur); } } void cond_expr_destroy(cond_expr_t * expr) { cond_expr_t *cur_expr, *next_expr; if (!expr) return; for (cur_expr = expr; cur_expr != NULL; cur_expr = next_expr) { next_expr = cur_expr->next; free(cur_expr); } } void cond_node_destroy(cond_node_t * node) { if (!node) return; cond_expr_destroy(node->expr); avrule_list_destroy(node->avtrue_list); avrule_list_destroy(node->avfalse_list); cond_av_list_destroy(node->true_list); cond_av_list_destroy(node->false_list); } void cond_list_destroy(cond_list_t * list) { cond_node_t *next, *cur; if (list == NULL) return; for (cur = list; cur != NULL; cur = next) { next = cur->next; cond_node_destroy(cur); free(cur); } } void cond_policydb_destroy(policydb_t * p) { if (p->bool_val_to_struct != NULL) free(p->bool_val_to_struct); avtab_destroy(&p->te_cond_avtab); cond_list_destroy(p->cond_list); } int cond_init_bool_indexes(policydb_t * p) { if (p->bool_val_to_struct) free(p->bool_val_to_struct); p->bool_val_to_struct = (cond_bool_datum_t **) malloc(p->p_bools.nprim * sizeof(cond_bool_datum_t *)); if (!p->bool_val_to_struct) return -1; return 0; } int cond_destroy_bool(hashtab_key_t key, hashtab_datum_t datum, void *p __attribute__ ((unused))) { if (key) free(key); free(datum); return 0; } int cond_index_bool(hashtab_key_t key, hashtab_datum_t datum, void *datap) { policydb_t *p; cond_bool_datum_t *booldatum; booldatum = datum; p = datap; if (!booldatum->s.value || booldatum->s.value > p->p_bools.nprim) return -EINVAL; p->p_bool_val_to_name[booldatum->s.value - 1] = key; p->bool_val_to_struct[booldatum->s.value - 1] = booldatum; return 0; } static int bool_isvalid(cond_bool_datum_t * b) { if (!(b->state == 0 || b->state == 1)) return 0; return 1; } int cond_read_bool(policydb_t * p, hashtab_t h, struct policy_file *fp) { char *key = 0; cond_bool_datum_t *booldatum; uint32_t buf[3], len; int rc; booldatum = malloc(sizeof(cond_bool_datum_t)); if (!booldatum) return -1; memset(booldatum, 0, sizeof(cond_bool_datum_t)); rc = next_entry(buf, fp, sizeof(uint32_t) * 3); if (rc < 0) goto err; booldatum->s.value = le32_to_cpu(buf[0]); booldatum->state = le32_to_cpu(buf[1]); if (!bool_isvalid(booldatum)) goto err; len = le32_to_cpu(buf[2]); key = malloc(len + 1); if (!key) goto err; rc = next_entry(key, fp, len); if (rc < 0) goto err; key[len] = 0; if (p->policy_type != POLICY_KERN && p->policyvers >= MOD_POLICYDB_VERSION_TUNABLE_SEP) { rc = next_entry(buf, fp, sizeof(uint32_t)); if (rc < 0) goto err; booldatum->flags = le32_to_cpu(buf[0]); } if (hashtab_insert(h, key, booldatum)) goto err; return 0; err: cond_destroy_bool(key, booldatum, 0); return -1; } struct cond_insertf_data { struct policydb *p; cond_av_list_t *other; cond_av_list_t *head; cond_av_list_t *tail; }; static int cond_insertf(avtab_t * a __attribute__ ((unused)), avtab_key_t * k, avtab_datum_t * d, void *ptr) { struct cond_insertf_data *data = ptr; struct policydb *p = data->p; cond_av_list_t *other = data->other, *list, *cur; avtab_ptr_t node_ptr; uint8_t found; /* * For type rules we have to make certain there aren't any * conflicting rules by searching the te_avtab and the * cond_te_avtab. */ if (k->specified & AVTAB_TYPE) { if (avtab_search(&p->te_avtab, k)) { printf ("security: type rule already exists outside of a conditional."); goto err; } /* * If we are reading the false list other will be a pointer to * the true list. We can have duplicate entries if there is only * 1 other entry and it is in our true list. * * If we are reading the true list (other == NULL) there shouldn't * be any other entries. */ if (other) { node_ptr = avtab_search_node(&p->te_cond_avtab, k); if (node_ptr) { if (avtab_search_node_next (node_ptr, k->specified)) { printf ("security: too many conflicting type rules."); goto err; } found = 0; for (cur = other; cur != NULL; cur = cur->next) { if (cur->node == node_ptr) { found = 1; break; } } if (!found) { printf ("security: conflicting type rules.\n"); goto err; } } } else { if (avtab_search(&p->te_cond_avtab, k)) { printf ("security: conflicting type rules when adding type rule for true.\n"); goto err; } } } node_ptr = avtab_insert_nonunique(&p->te_cond_avtab, k, d); if (!node_ptr) { printf("security: could not insert rule."); goto err; } node_ptr->parse_context = (void *)1; list = malloc(sizeof(cond_av_list_t)); if (!list) goto err; memset(list, 0, sizeof(cond_av_list_t)); list->node = node_ptr; if (!data->head) data->head = list; else data->tail->next = list; data->tail = list; return 0; err: cond_av_list_destroy(data->head); data->head = NULL; return -1; } static int cond_read_av_list(policydb_t * p, void *fp, cond_av_list_t ** ret_list, cond_av_list_t * other) { unsigned int i; int rc; uint32_t buf[1], len; struct cond_insertf_data data; *ret_list = NULL; len = 0; rc = next_entry(buf, fp, sizeof(uint32_t)); if (rc < 0) return -1; len = le32_to_cpu(buf[0]); if (len == 0) { return 0; } data.p = p; data.other = other; data.head = NULL; data.tail = NULL; for (i = 0; i < len; i++) { rc = avtab_read_item(fp, p->policyvers, &p->te_cond_avtab, cond_insertf, &data); if (rc) return rc; } *ret_list = data.head; return 0; } static int expr_isvalid(policydb_t * p, cond_expr_t * expr) { if (expr->expr_type <= 0 || expr->expr_type > COND_LAST) { printf ("security: conditional expressions uses unknown operator.\n"); return 0; } if (expr->bool > p->p_bools.nprim) { printf ("security: conditional expressions uses unknown bool.\n"); return 0; } return 1; } static int cond_read_node(policydb_t * p, cond_node_t * node, void *fp) { uint32_t buf[2]; int len, i, rc; cond_expr_t *expr = NULL, *last = NULL; rc = next_entry(buf, fp, sizeof(uint32_t)); if (rc < 0) goto err; node->cur_state = le32_to_cpu(buf[0]); len = 0; rc = next_entry(buf, fp, sizeof(uint32_t)); if (rc < 0) goto err; /* expr */ len = le32_to_cpu(buf[0]); for (i = 0; i < len; i++) { rc = next_entry(buf, fp, sizeof(uint32_t) * 2); if (rc < 0) goto err; expr = malloc(sizeof(cond_expr_t)); if (!expr) { goto err; } memset(expr, 0, sizeof(cond_expr_t)); expr->expr_type = le32_to_cpu(buf[0]); expr->bool = le32_to_cpu(buf[1]); if (!expr_isvalid(p, expr)) { free(expr); goto err; } if (i == 0) { node->expr = expr; } else { last->next = expr; } last = expr; } if (p->policy_type == POLICY_KERN) { if (cond_read_av_list(p, fp, &node->true_list, NULL) != 0) goto err; if (cond_read_av_list(p, fp, &node->false_list, node->true_list) != 0) goto err; } else { if (avrule_read_list(p, &node->avtrue_list, fp)) goto err; if (avrule_read_list(p, &node->avfalse_list, fp)) goto err; } if (p->policy_type != POLICY_KERN && p->policyvers >= MOD_POLICYDB_VERSION_TUNABLE_SEP) { rc = next_entry(buf, fp, sizeof(uint32_t)); if (rc < 0) goto err; node->flags = le32_to_cpu(buf[0]); } return 0; err: cond_node_destroy(node); free(node); return -1; } int cond_read_list(policydb_t * p, cond_list_t ** list, void *fp) { cond_node_t *node, *last = NULL; uint32_t buf[1]; int i, len, rc; rc = next_entry(buf, fp, sizeof(uint32_t)); if (rc < 0) return -1; len = le32_to_cpu(buf[0]); rc = avtab_alloc(&p->te_cond_avtab, p->te_avtab.nel); if (rc) goto err; for (i = 0; i < len; i++) { node = malloc(sizeof(cond_node_t)); if (!node) goto err; memset(node, 0, sizeof(cond_node_t)); if (cond_read_node(p, node, fp) != 0) goto err; if (i == 0) { *list = node; } else { last->next = node; } last = node; } return 0; err: return -1; } /* Determine whether additional permissions are granted by the conditional * av table, and if so, add them to the result */ void cond_compute_av(avtab_t * ctab, avtab_key_t * key, struct sepol_av_decision *avd) { avtab_ptr_t node; if (!ctab || !key || !avd) return; for (node = avtab_search_node(ctab, key); node != NULL; node = avtab_search_node_next(node, key->specified)) { if ((uint16_t) (AVTAB_ALLOWED | AVTAB_ENABLED) == (node->key.specified & (AVTAB_ALLOWED | AVTAB_ENABLED))) avd->allowed |= node->datum.data; if ((uint16_t) (AVTAB_AUDITDENY | AVTAB_ENABLED) == (node->key.specified & (AVTAB_AUDITDENY | AVTAB_ENABLED))) /* Since a '0' in an auditdeny mask represents a * permission we do NOT want to audit (dontaudit), we use * the '&' operand to ensure that all '0's in the mask * are retained (much unlike the allow and auditallow cases). */ avd->auditdeny &= node->datum.data; if ((uint16_t) (AVTAB_AUDITALLOW | AVTAB_ENABLED) == (node->key.specified & (AVTAB_AUDITALLOW | AVTAB_ENABLED))) avd->auditallow |= node->datum.data; } return; } avtab_datum_t *cond_av_list_search(avtab_key_t * key, cond_av_list_t * cond_list) { cond_av_list_t *cur_av; for (cur_av = cond_list; cur_av != NULL; cur_av = cur_av->next) { if (cur_av->node->key.source_type == key->source_type && cur_av->node->key.target_type == key->target_type && cur_av->node->key.target_class == key->target_class) return &cur_av->node->datum; } return NULL; }