diff options
author | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 15:20:36 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 15:20:36 -0700 |
commit | 1da177e4c3f41524e886b7f1b8a0c1fc7321cac2 (patch) | |
tree | 0bba044c4ce775e45a88a51686b5d9f90697ea9d /security/selinux/ss |
Linux-2.6.12-rc2v2.6.12-rc2
Initial git repository build. I'm not bothering with the full history,
even though we have it. We can create a separate "historical" git
archive of that later if we want to, and in the meantime it's about
3.2GB when imported into git - space that would just make the early
git days unnecessarily complicated, when we don't have a lot of good
infrastructure for it.
Let it rip!
Diffstat (limited to 'security/selinux/ss')
-rw-r--r-- | security/selinux/ss/Makefile | 9 | ||||
-rw-r--r-- | security/selinux/ss/avtab.c | 399 | ||||
-rw-r--r-- | security/selinux/ss/avtab.h | 85 | ||||
-rw-r--r-- | security/selinux/ss/conditional.c | 489 | ||||
-rw-r--r-- | security/selinux/ss/conditional.h | 77 | ||||
-rw-r--r-- | security/selinux/ss/constraint.h | 61 | ||||
-rw-r--r-- | security/selinux/ss/context.h | 107 | ||||
-rw-r--r-- | security/selinux/ss/ebitmap.c | 293 | ||||
-rw-r--r-- | security/selinux/ss/ebitmap.h | 48 | ||||
-rw-r--r-- | security/selinux/ss/hashtab.c | 167 | ||||
-rw-r--r-- | security/selinux/ss/hashtab.h | 87 | ||||
-rw-r--r-- | security/selinux/ss/mls.c | 527 | ||||
-rw-r--r-- | security/selinux/ss/mls.h | 42 | ||||
-rw-r--r-- | security/selinux/ss/mls_types.h | 56 | ||||
-rw-r--r-- | security/selinux/ss/policydb.c | 1843 | ||||
-rw-r--r-- | security/selinux/ss/policydb.h | 275 | ||||
-rw-r--r-- | security/selinux/ss/services.c | 1777 | ||||
-rw-r--r-- | security/selinux/ss/services.h | 15 | ||||
-rw-r--r-- | security/selinux/ss/sidtab.c | 305 | ||||
-rw-r--r-- | security/selinux/ss/sidtab.h | 59 | ||||
-rw-r--r-- | security/selinux/ss/symtab.c | 44 | ||||
-rw-r--r-- | security/selinux/ss/symtab.h | 23 |
22 files changed, 6788 insertions, 0 deletions
diff --git a/security/selinux/ss/Makefile b/security/selinux/ss/Makefile new file mode 100644 index 00000000000..bad78779b9b --- /dev/null +++ b/security/selinux/ss/Makefile @@ -0,0 +1,9 @@ +# +# Makefile for building the SELinux security server as part of the kernel tree. +# + +EXTRA_CFLAGS += -Isecurity/selinux/include +obj-y := ss.o + +ss-y := ebitmap.o hashtab.o symtab.o sidtab.o avtab.o policydb.o services.o conditional.o mls.o + diff --git a/security/selinux/ss/avtab.c b/security/selinux/ss/avtab.c new file mode 100644 index 00000000000..f238c034c44 --- /dev/null +++ b/security/selinux/ss/avtab.c @@ -0,0 +1,399 @@ +/* + * Implementation of the access vector table type. + * + * Author : Stephen Smalley, <sds@epoch.ncsc.mil> + */ + +/* Updated: Frank Mayer <mayerf@tresys.com> and Karl MacMillan <kmacmillan@tresys.com> + * + * Added conditional policy language extensions + * + * Copyright (C) 2003 Tresys Technology, LLC + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 2. + */ + +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/vmalloc.h> +#include <linux/errno.h> + +#include "avtab.h" +#include "policydb.h" + +#define AVTAB_HASH(keyp) \ +((keyp->target_class + \ + (keyp->target_type << 2) + \ + (keyp->source_type << 9)) & \ + AVTAB_HASH_MASK) + +static kmem_cache_t *avtab_node_cachep; + +static struct avtab_node* +avtab_insert_node(struct avtab *h, int hvalue, + struct avtab_node * prev, struct avtab_node * cur, + struct avtab_key *key, struct avtab_datum *datum) +{ + struct avtab_node * newnode; + newnode = kmem_cache_alloc(avtab_node_cachep, SLAB_KERNEL); + if (newnode == NULL) + return NULL; + memset(newnode, 0, sizeof(struct avtab_node)); + newnode->key = *key; + newnode->datum = *datum; + if (prev) { + newnode->next = prev->next; + prev->next = newnode; + } else { + newnode->next = h->htable[hvalue]; + h->htable[hvalue] = newnode; + } + + h->nel++; + return newnode; +} + +static int avtab_insert(struct avtab *h, struct avtab_key *key, struct avtab_datum *datum) +{ + int hvalue; + struct avtab_node *prev, *cur, *newnode; + + if (!h) + return -EINVAL; + + hvalue = AVTAB_HASH(key); + for (prev = NULL, cur = h->htable[hvalue]; + cur; + prev = cur, cur = cur->next) { + if (key->source_type == cur->key.source_type && + key->target_type == cur->key.target_type && + key->target_class == cur->key.target_class && + (datum->specified & cur->datum.specified)) + return -EEXIST; + if (key->source_type < cur->key.source_type) + break; + if (key->source_type == cur->key.source_type && + key->target_type < cur->key.target_type) + break; + if (key->source_type == cur->key.source_type && + key->target_type == cur->key.target_type && + key->target_class < cur->key.target_class) + break; + } + + newnode = avtab_insert_node(h, hvalue, prev, cur, key, datum); + if(!newnode) + return -ENOMEM; + + return 0; +} + +/* Unlike avtab_insert(), this function allow multiple insertions of the same + * key/specified mask into the table, as needed by the conditional avtab. + * It also returns a pointer to the node inserted. + */ +struct avtab_node * +avtab_insert_nonunique(struct avtab * h, struct avtab_key * key, struct avtab_datum * datum) +{ + int hvalue; + struct avtab_node *prev, *cur, *newnode; + + if (!h) + return NULL; + hvalue = AVTAB_HASH(key); + for (prev = NULL, cur = h->htable[hvalue]; + cur; + prev = cur, cur = cur->next) { + if (key->source_type == cur->key.source_type && + key->target_type == cur->key.target_type && + key->target_class == cur->key.target_class && + (datum->specified & cur->datum.specified)) + break; + if (key->source_type < cur->key.source_type) + break; + if (key->source_type == cur->key.source_type && + key->target_type < cur->key.target_type) + break; + if (key->source_type == cur->key.source_type && + key->target_type == cur->key.target_type && + key->target_class < cur->key.target_class) + break; + } + newnode = avtab_insert_node(h, hvalue, prev, cur, key, datum); + + return newnode; +} + +struct avtab_datum *avtab_search(struct avtab *h, struct avtab_key *key, int specified) +{ + int hvalue; + struct avtab_node *cur; + + if (!h) + return NULL; + + hvalue = AVTAB_HASH(key); + for (cur = h->htable[hvalue]; cur; cur = cur->next) { + if (key->source_type == cur->key.source_type && + key->target_type == cur->key.target_type && + key->target_class == cur->key.target_class && + (specified & cur->datum.specified)) + return &cur->datum; + + if (key->source_type < cur->key.source_type) + break; + if (key->source_type == cur->key.source_type && + key->target_type < cur->key.target_type) + break; + if (key->source_type == cur->key.source_type && + key->target_type == cur->key.target_type && + key->target_class < cur->key.target_class) + break; + } + + return NULL; +} + +/* This search function returns a node pointer, and can be used in + * conjunction with avtab_search_next_node() + */ +struct avtab_node* +avtab_search_node(struct avtab *h, struct avtab_key *key, int specified) +{ + int hvalue; + struct avtab_node *cur; + + if (!h) + return NULL; + + hvalue = AVTAB_HASH(key); + for (cur = h->htable[hvalue]; cur; cur = cur->next) { + if (key->source_type == cur->key.source_type && + key->target_type == cur->key.target_type && + key->target_class == cur->key.target_class && + (specified & cur->datum.specified)) + return cur; + + if (key->source_type < cur->key.source_type) + break; + if (key->source_type == cur->key.source_type && + key->target_type < cur->key.target_type) + break; + if (key->source_type == cur->key.source_type && + key->target_type == cur->key.target_type && + key->target_class < cur->key.target_class) + break; + } + return NULL; +} + +struct avtab_node* +avtab_search_node_next(struct avtab_node *node, int specified) +{ + struct avtab_node *cur; + + if (!node) + return NULL; + + for (cur = node->next; cur; cur = cur->next) { + if (node->key.source_type == cur->key.source_type && + node->key.target_type == cur->key.target_type && + node->key.target_class == cur->key.target_class && + (specified & cur->datum.specified)) + return cur; + + if (node->key.source_type < cur->key.source_type) + break; + if (node->key.source_type == cur->key.source_type && + node->key.target_type < cur->key.target_type) + break; + if (node->key.source_type == cur->key.source_type && + node->key.target_type == cur->key.target_type && + node->key.target_class < cur->key.target_class) + break; + } + return NULL; +} + +void avtab_destroy(struct avtab *h) +{ + int i; + struct avtab_node *cur, *temp; + + if (!h || !h->htable) + return; + + for (i = 0; i < AVTAB_SIZE; i++) { + cur = h->htable[i]; + while (cur != NULL) { + temp = cur; + cur = cur->next; + kmem_cache_free(avtab_node_cachep, temp); + } + h->htable[i] = NULL; + } + vfree(h->htable); + h->htable = NULL; +} + + +int avtab_init(struct avtab *h) +{ + int i; + + h->htable = vmalloc(sizeof(*(h->htable)) * AVTAB_SIZE); + if (!h->htable) + return -ENOMEM; + for (i = 0; i < AVTAB_SIZE; i++) + h->htable[i] = NULL; + h->nel = 0; + return 0; +} + +void avtab_hash_eval(struct avtab *h, char *tag) +{ + int i, chain_len, slots_used, max_chain_len; + struct avtab_node *cur; + + slots_used = 0; + max_chain_len = 0; + for (i = 0; i < AVTAB_SIZE; i++) { + cur = h->htable[i]; + if (cur) { + slots_used++; + chain_len = 0; + while (cur) { + chain_len++; + cur = cur->next; + } + + if (chain_len > max_chain_len) + max_chain_len = chain_len; + } + } + + printk(KERN_INFO "%s: %d entries and %d/%d buckets used, longest " + "chain length %d\n", tag, h->nel, slots_used, AVTAB_SIZE, + max_chain_len); +} + +int avtab_read_item(void *fp, struct avtab_datum *avdatum, struct avtab_key *avkey) +{ + u32 buf[7]; + u32 items, items2; + int rc; + + memset(avkey, 0, sizeof(struct avtab_key)); + memset(avdatum, 0, sizeof(struct avtab_datum)); + + rc = next_entry(buf, fp, sizeof(u32)); + if (rc < 0) { + printk(KERN_ERR "security: avtab: truncated entry\n"); + goto bad; + } + items2 = le32_to_cpu(buf[0]); + if (items2 > ARRAY_SIZE(buf)) { + printk(KERN_ERR "security: avtab: entry overflow\n"); + goto bad; + } + rc = next_entry(buf, fp, sizeof(u32)*items2); + if (rc < 0) { + printk(KERN_ERR "security: avtab: truncated entry\n"); + goto bad; + } + items = 0; + avkey->source_type = le32_to_cpu(buf[items++]); + avkey->target_type = le32_to_cpu(buf[items++]); + avkey->target_class = le32_to_cpu(buf[items++]); + avdatum->specified = le32_to_cpu(buf[items++]); + if (!(avdatum->specified & (AVTAB_AV | AVTAB_TYPE))) { + printk(KERN_ERR "security: avtab: null entry\n"); + goto bad; + } + if ((avdatum->specified & AVTAB_AV) && + (avdatum->specified & AVTAB_TYPE)) { + printk(KERN_ERR "security: avtab: entry has both access vectors and types\n"); + goto bad; + } + if (avdatum->specified & AVTAB_AV) { + if (avdatum->specified & AVTAB_ALLOWED) + avtab_allowed(avdatum) = le32_to_cpu(buf[items++]); + if (avdatum->specified & AVTAB_AUDITDENY) + avtab_auditdeny(avdatum) = le32_to_cpu(buf[items++]); + if (avdatum->specified & AVTAB_AUDITALLOW) + avtab_auditallow(avdatum) = le32_to_cpu(buf[items++]); + } else { + if (avdatum->specified & AVTAB_TRANSITION) + avtab_transition(avdatum) = le32_to_cpu(buf[items++]); + if (avdatum->specified & AVTAB_CHANGE) + avtab_change(avdatum) = le32_to_cpu(buf[items++]); + if (avdatum->specified & AVTAB_MEMBER) + avtab_member(avdatum) = le32_to_cpu(buf[items++]); + } + if (items != items2) { + printk(KERN_ERR "security: avtab: entry only had %d items, expected %d\n", + items2, items); + goto bad; + } + + return 0; +bad: + return -1; +} + +int avtab_read(struct avtab *a, void *fp, u32 config) +{ + int rc; + struct avtab_key avkey; + struct avtab_datum avdatum; + u32 buf[1]; + u32 nel, i; + + + rc = next_entry(buf, fp, sizeof(u32)); + if (rc < 0) { + printk(KERN_ERR "security: avtab: truncated table\n"); + goto bad; + } + nel = le32_to_cpu(buf[0]); + if (!nel) { + printk(KERN_ERR "security: avtab: table is empty\n"); + rc = -EINVAL; + goto bad; + } + for (i = 0; i < nel; i++) { + if (avtab_read_item(fp, &avdatum, &avkey)) { + rc = -EINVAL; + goto bad; + } + rc = avtab_insert(a, &avkey, &avdatum); + if (rc) { + if (rc == -ENOMEM) + printk(KERN_ERR "security: avtab: out of memory\n"); + if (rc == -EEXIST) + printk(KERN_ERR "security: avtab: duplicate entry\n"); + goto bad; + } + } + + rc = 0; +out: + return rc; + +bad: + avtab_destroy(a); + goto out; +} + +void avtab_cache_init(void) +{ + avtab_node_cachep = kmem_cache_create("avtab_node", + sizeof(struct avtab_node), + 0, SLAB_PANIC, NULL, NULL); +} + +void avtab_cache_destroy(void) +{ + kmem_cache_destroy (avtab_node_cachep); +} diff --git a/security/selinux/ss/avtab.h b/security/selinux/ss/avtab.h new file mode 100644 index 00000000000..519d4f6dc65 --- /dev/null +++ b/security/selinux/ss/avtab.h @@ -0,0 +1,85 @@ +/* + * An access vector table (avtab) is a hash table + * of access vectors and transition types indexed + * by a type pair and a class. An access vector + * table is used to represent the type enforcement + * tables. + * + * Author : Stephen Smalley, <sds@epoch.ncsc.mil> + */ + +/* Updated: Frank Mayer <mayerf@tresys.com> and Karl MacMillan <kmacmillan@tresys.com> + * + * Added conditional policy language extensions + * + * Copyright (C) 2003 Tresys Technology, LLC + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 2. + */ +#ifndef _SS_AVTAB_H_ +#define _SS_AVTAB_H_ + +struct avtab_key { + u32 source_type; /* source type */ + u32 target_type; /* target type */ + u32 target_class; /* target object class */ +}; + +struct avtab_datum { +#define AVTAB_ALLOWED 1 +#define AVTAB_AUDITALLOW 2 +#define AVTAB_AUDITDENY 4 +#define AVTAB_AV (AVTAB_ALLOWED | AVTAB_AUDITALLOW | AVTAB_AUDITDENY) +#define AVTAB_TRANSITION 16 +#define AVTAB_MEMBER 32 +#define AVTAB_CHANGE 64 +#define AVTAB_TYPE (AVTAB_TRANSITION | AVTAB_MEMBER | AVTAB_CHANGE) +#define AVTAB_ENABLED 0x80000000 /* reserved for used in cond_avtab */ + u32 specified; /* what fields are specified */ + u32 data[3]; /* access vectors or types */ +#define avtab_allowed(x) (x)->data[0] +#define avtab_auditdeny(x) (x)->data[1] +#define avtab_auditallow(x) (x)->data[2] +#define avtab_transition(x) (x)->data[0] +#define avtab_change(x) (x)->data[1] +#define avtab_member(x) (x)->data[2] +}; + +struct avtab_node { + struct avtab_key key; + struct avtab_datum datum; + struct avtab_node *next; +}; + +struct avtab { + struct avtab_node **htable; + u32 nel; /* number of elements */ +}; + +int avtab_init(struct avtab *); +struct avtab_datum *avtab_search(struct avtab *h, struct avtab_key *k, int specified); +void avtab_destroy(struct avtab *h); +void avtab_hash_eval(struct avtab *h, char *tag); + +int avtab_read_item(void *fp, struct avtab_datum *avdatum, struct avtab_key *avkey); +int avtab_read(struct avtab *a, void *fp, u32 config); + +struct avtab_node *avtab_insert_nonunique(struct avtab *h, struct avtab_key *key, + struct avtab_datum *datum); + +struct avtab_node *avtab_search_node(struct avtab *h, struct avtab_key *key, int specified); + +struct avtab_node *avtab_search_node_next(struct avtab_node *node, int specified); + +void avtab_cache_init(void); +void avtab_cache_destroy(void); + +#define AVTAB_HASH_BITS 15 +#define AVTAB_HASH_BUCKETS (1 << AVTAB_HASH_BITS) +#define AVTAB_HASH_MASK (AVTAB_HASH_BUCKETS-1) + +#define AVTAB_SIZE AVTAB_HASH_BUCKETS + +#endif /* _SS_AVTAB_H_ */ + diff --git a/security/selinux/ss/conditional.c b/security/selinux/ss/conditional.c new file mode 100644 index 00000000000..b53441184ac --- /dev/null +++ b/security/selinux/ss/conditional.c @@ -0,0 +1,489 @@ +/* Authors: Karl MacMillan <kmacmillan@tresys.com> + * Frank Mayer <mayerf@tresys.com> + * + * Copyright (C) 2003 - 2004 Tresys Technology, LLC + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 2. + */ + +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/spinlock.h> +#include <asm/semaphore.h> +#include <linux/slab.h> + +#include "security.h" +#include "conditional.h" + +/* + * 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. + */ +static int cond_evaluate_expr(struct policydb *p, struct cond_expr *expr) +{ + + struct cond_expr *cur; + int s[COND_EXPR_MAXDEPTH]; + int sp = -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]; +} + +/* + * evaluate_cond_node evaluates the conditional stored in + * a struct cond_node 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. + */ +int evaluate_cond_node(struct policydb *p, struct cond_node *node) +{ + int new_state; + struct cond_av_list* cur; + + new_state = cond_evaluate_expr(p, node->expr); + if (new_state != node->cur_state) { + node->cur_state = new_state; + if (new_state == -1) + printk(KERN_ERR "security: 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->datum.specified &= ~AVTAB_ENABLED; + } else { + cur->node->datum.specified |= AVTAB_ENABLED; + } + } + + for (cur = node->false_list; cur != NULL; cur = cur->next) { + /* -1 or 1 */ + if (new_state) { + cur->node->datum.specified &= ~AVTAB_ENABLED; + } else { + cur->node->datum.specified |= AVTAB_ENABLED; + } + } + } + return 0; +} + +int cond_policydb_init(struct policydb *p) +{ + p->bool_val_to_struct = NULL; + p->cond_list = NULL; + if (avtab_init(&p->te_cond_avtab)) + return -1; + + return 0; +} + +static void cond_av_list_destroy(struct cond_av_list *list) +{ + struct cond_av_list *cur, *next; + for (cur = list; cur != NULL; cur = next) { + next = cur->next; + /* the avtab_ptr_t node is destroy by the avtab */ + kfree(cur); + } +} + +static void cond_node_destroy(struct cond_node *node) +{ + struct cond_expr *cur_expr, *next_expr; + + for (cur_expr = node->expr; cur_expr != NULL; cur_expr = next_expr) { + next_expr = cur_expr->next; + kfree(cur_expr); + } + cond_av_list_destroy(node->true_list); + cond_av_list_destroy(node->false_list); + kfree(node); +} + +static void cond_list_destroy(struct cond_node *list) +{ + struct cond_node *next, *cur; + + if (list == NULL) + return; + + for (cur = list; cur != NULL; cur = next) { + next = cur->next; + cond_node_destroy(cur); + } +} + +void cond_policydb_destroy(struct policydb *p) +{ + if (p->bool_val_to_struct != NULL) + kfree(p->bool_val_to_struct); + avtab_destroy(&p->te_cond_avtab); + cond_list_destroy(p->cond_list); +} + +int cond_init_bool_indexes(struct policydb *p) +{ + if (p->bool_val_to_struct) + kfree(p->bool_val_to_struct); + p->bool_val_to_struct = (struct cond_bool_datum**) + kmalloc(p->p_bools.nprim * sizeof(struct cond_bool_datum*), GFP_KERNEL); + if (!p->bool_val_to_struct) + return -1; + return 0; +} + +int cond_destroy_bool(void *key, void *datum, void *p) +{ + if (key) + kfree(key); + kfree(datum); + return 0; +} + +int cond_index_bool(void *key, void *datum, void *datap) +{ + struct policydb *p; + struct cond_bool_datum *booldatum; + + booldatum = datum; + p = datap; + + if (!booldatum->value || booldatum->value > p->p_bools.nprim) + return -EINVAL; + + p->p_bool_val_to_name[booldatum->value - 1] = key; + p->bool_val_to_struct[booldatum->value -1] = booldatum; + + return 0; +} + +static int bool_isvalid(struct cond_bool_datum *b) +{ + if (!(b->state == 0 || b->state == 1)) + return 0; + return 1; +} + +int cond_read_bool(struct policydb *p, struct hashtab *h, void *fp) +{ + char *key = NULL; + struct cond_bool_datum *booldatum; + u32 buf[3], len; + int rc; + + booldatum = kmalloc(sizeof(struct cond_bool_datum), GFP_KERNEL); + if (!booldatum) + return -1; + memset(booldatum, 0, sizeof(struct cond_bool_datum)); + + rc = next_entry(buf, fp, sizeof buf); + if (rc < 0) + goto err; + + booldatum->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 = kmalloc(len + 1, GFP_KERNEL); + if (!key) + goto err; + rc = next_entry(key, fp, len); + if (rc < 0) + goto err; + key[len] = 0; + if (hashtab_insert(h, key, booldatum)) + goto err; + + return 0; +err: + cond_destroy_bool(key, booldatum, NULL); + return -1; +} + +static int cond_read_av_list(struct policydb *p, void *fp, struct cond_av_list **ret_list, + struct cond_av_list *other) +{ + struct cond_av_list *list, *last = NULL, *cur; + struct avtab_key key; + struct avtab_datum datum; + struct avtab_node *node_ptr; + int rc; + u32 buf[1], i, len; + u8 found; + + *ret_list = NULL; + + len = 0; + rc = next_entry(buf, fp, sizeof buf); + if (rc < 0) + return -1; + + len = le32_to_cpu(buf[0]); + if (len == 0) { + return 0; + } + + for (i = 0; i < len; i++) { + if (avtab_read_item(fp, &datum, &key)) + goto err; + + /* + * 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 (datum.specified & AVTAB_TYPE) { + if (avtab_search(&p->te_avtab, &key, AVTAB_TYPE)) { + printk("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, &key, AVTAB_TYPE); + if (node_ptr) { + if (avtab_search_node_next(node_ptr, AVTAB_TYPE)) { + printk("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) { + printk("security: conflicting type rules."); + goto err; + } + } + } else { + if (avtab_search(&p->te_cond_avtab, &key, AVTAB_TYPE)) { + printk("security: conflicting type rules when adding type rule for true."); + goto err; + } + } + } + node_ptr = avtab_insert_nonunique(&p->te_cond_avtab, &key, &datum); + if (!node_ptr) { + printk("security: could not insert rule."); + goto err; + } + + list = kmalloc(sizeof(struct cond_av_list), GFP_KERNEL); + if (!list) + goto err; + memset(list, 0, sizeof(struct cond_av_list)); + + list->node = node_ptr; + if (i == 0) + *ret_list = list; + else + last->next = list; + last = list; + + } + + return 0; +err: + cond_av_list_destroy(*ret_list); + *ret_list = NULL; + return -1; +} + +static int expr_isvalid(struct policydb *p, struct cond_expr *expr) +{ + if (expr->expr_type <= 0 || expr->expr_type > COND_LAST) { + printk("security: conditional expressions uses unknown operator.\n"); + return 0; + } + + if (expr->bool > p->p_bools.nprim) { + printk("security: conditional expressions uses unknown bool.\n"); + return 0; + } + return 1; +} + +static int cond_read_node(struct policydb *p, struct cond_node *node, void *fp) +{ + u32 buf[2], len, i; + int rc; + struct cond_expr *expr = NULL, *last = NULL; + + rc = next_entry(buf, fp, sizeof(u32)); + if (rc < 0) + return -1; + + node->cur_state = le32_to_cpu(buf[0]); + + len = 0; + rc = next_entry(buf, fp, sizeof(u32)); + if (rc < 0) + return -1; + + /* expr */ + len = le32_to_cpu(buf[0]); + + for (i = 0; i < len; i++ ) { + rc = next_entry(buf, fp, sizeof(u32) * 2); + if (rc < 0) + goto err; + + expr = kmalloc(sizeof(struct cond_expr), GFP_KERNEL); + if (!expr) { + goto err; + } + memset(expr, 0, sizeof(struct cond_expr)); + + expr->expr_type = le32_to_cpu(buf[0]); + expr->bool = le32_to_cpu(buf[1]); + + if (!expr_isvalid(p, expr)) { + kfree(expr); + goto err; + } + + if (i == 0) { + node->expr = expr; + } else { + last->next = expr; + } + last = expr; + } + + 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; + return 0; +err: + cond_node_destroy(node); + return -1; +} + +int cond_read_list(struct policydb *p, void *fp) +{ + struct cond_node *node, *last = NULL; + u32 buf[1], i, len; + int rc; + + rc = next_entry(buf, fp, sizeof buf); + if (rc < 0) + return -1; + + len = le32_to_cpu(buf[0]); + + for (i = 0; i < len; i++) { + node = kmalloc(sizeof(struct cond_node), GFP_KERNEL); + if (!node) + goto err; + memset(node, 0, sizeof(struct cond_node)); + + if (cond_read_node(p, node, fp) != 0) + goto err; + + if (i == 0) { + p->cond_list = node; + } else { + last->next = node; + } + last = node; + } + return 0; +err: + cond_list_destroy(p->cond_list); + 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(struct avtab *ctab, struct avtab_key *key, struct av_decision *avd) +{ + struct avtab_node *node; + + if(!ctab || !key || !avd) + return; + + for(node = avtab_search_node(ctab, key, AVTAB_AV); node != NULL; + node = avtab_search_node_next(node, AVTAB_AV)) { + if ( (__u32) (AVTAB_ALLOWED|AVTAB_ENABLED) == + (node->datum.specified & (AVTAB_ALLOWED|AVTAB_ENABLED))) + avd->allowed |= avtab_allowed(&node->datum); + if ( (__u32) (AVTAB_AUDITDENY|AVTAB_ENABLED) == + (node->datum.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 &= avtab_auditdeny(&node->datum); + if ( (__u32) (AVTAB_AUDITALLOW|AVTAB_ENABLED) == + (node->datum.specified & (AVTAB_AUDITALLOW|AVTAB_ENABLED))) + avd->auditallow |= avtab_auditallow(&node->datum); + } + return; +} diff --git a/security/selinux/ss/conditional.h b/security/selinux/ss/conditional.h new file mode 100644 index 00000000000..f3a1fc6e5d6 --- /dev/null +++ b/security/selinux/ss/conditional.h @@ -0,0 +1,77 @@ +/* Authors: Karl MacMillan <kmacmillan@tresys.com> + * Frank Mayer <mayerf@tresys.com> + * + * Copyright (C) 2003 - 2004 Tresys Technology, LLC + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 2. + */ + +#ifndef _CONDITIONAL_H_ +#define _CONDITIONAL_H_ + +#include "avtab.h" +#include "symtab.h" +#include "policydb.h" + +#define COND_EXPR_MAXDEPTH 10 + +/* + * A conditional expression is a list of operators and operands + * in reverse polish notation. + */ +struct cond_expr { +#define COND_BOOL 1 /* plain bool */ +#define COND_NOT 2 /* !bool */ +#define COND_OR 3 /* bool || bool */ +#define COND_AND 4 /* bool && bool */ +#define COND_XOR 5 /* bool ^ bool */ +#define COND_EQ 6 /* bool == bool */ +#define COND_NEQ 7 /* bool != bool */ +#define COND_LAST 8 + __u32 expr_type; + __u32 bool; + struct cond_expr *next; +}; + +/* + * Each cond_node contains a list of rules to be enabled/disabled + * depending on the current value of the conditional expression. This + * struct is for that list. + */ +struct cond_av_list { + struct avtab_node *node; + struct cond_av_list *next; +}; + +/* + * A cond node represents a conditional block in a policy. It + * contains a conditional expression, the current state of the expression, + * two lists of rules to enable/disable depending on the value of the + * expression (the true list corresponds to if and the false list corresponds + * to else).. + */ +struct cond_node { + int cur_state; + struct cond_expr *expr; + struct cond_av_list *true_list; + struct cond_av_list *false_list; + struct cond_node *next; +}; + +int cond_policydb_init(struct policydb* p); +void cond_policydb_destroy(struct policydb* p); + +int cond_init_bool_indexes(struct policydb* p); +int cond_destroy_bool(void *key, void *datum, void *p); + +int cond_index_bool(void *key, void *datum, void *datap); + +int cond_read_bool(struct policydb *p, struct hashtab *h, void *fp); +int cond_read_list(struct policydb *p, void *fp); + +void cond_compute_av(struct avtab *ctab, struct avtab_key *key, struct av_decision *avd); + +int evaluate_cond_node(struct policydb *p, struct cond_node *node); + +#endif /* _CONDITIONAL_H_ */ diff --git a/security/selinux/ss/constraint.h b/security/selinux/ss/constraint.h new file mode 100644 index 00000000000..149dda731fd --- /dev/null +++ b/security/selinux/ss/constraint.h @@ -0,0 +1,61 @@ +/* + * A constraint is a condition that must be satisfied in + * order for one or more permissions to be granted. + * Constraints are used to impose additional restrictions + * beyond the type-based rules in `te' or the role-based + * transition rules in `rbac'. Constraints are typically + * used to prevent a process from transitioning to a new user + * identity or role unless it is in a privileged type. + * Constraints are likewise typically used to prevent a + * process from labeling an object with a different user + * identity. + * + * Author : Stephen Smalley, <sds@epoch.ncsc.mil> + */ +#ifndef _SS_CONSTRAINT_H_ +#define _SS_CONSTRAINT_H_ + +#include "ebitmap.h" + +#define CEXPR_MAXDEPTH 5 + +struct constraint_expr { +#define CEXPR_NOT 1 /* not expr */ +#define CEXPR_AND 2 /* expr and expr */ +#define CEXPR_OR 3 /* expr or expr */ +#define CEXPR_ATTR 4 /* attr op attr */ +#define CEXPR_NAMES 5 /* attr op names */ + u32 expr_type; /* expression type */ + +#define CEXPR_USER 1 /* user */ +#define CEXPR_ROLE 2 /* role */ +#define CEXPR_TYPE 4 /* type */ +#define CEXPR_TARGET 8 /* target if set, source otherwise */ +#define CEXPR_XTARGET 16 /* special 3rd target for validatetrans rule */ +#define CEXPR_L1L2 32 /* low level 1 vs. low level 2 */ +#define CEXPR_L1H2 64 /* low level 1 vs. high level 2 */ +#define CEXPR_H1L2 128 /* high level 1 vs. low level 2 */ +#define CEXPR_H1H2 256 /* high level 1 vs. high level 2 */ +#define CEXPR_L1H1 512 /* low level 1 vs. high level 1 */ +#define CEXPR_L2H2 1024 /* low level 2 vs. high level 2 */ + u32 attr; /* attribute */ + +#define CEXPR_EQ 1 /* == or eq */ +#define CEXPR_NEQ 2 /* != */ +#define CEXPR_DOM 3 /* dom */ +#define CEXPR_DOMBY 4 /* domby */ +#define CEXPR_INCOMP 5 /* incomp */ + u32 op; /* operator */ + + struct ebitmap names; /* names */ + + struct constraint_expr *next; /* next expression */ +}; + +struct constraint_node { + u32 permissions; /* constrained permissions */ + struct constraint_expr *expr; /* constraint on permissions */ + struct constraint_node *next; /* next constraint */ +}; + +#endif /* _SS_CONSTRAINT_H_ */ diff --git a/security/selinux/ss/context.h b/security/selinux/ss/context.h new file mode 100644 index 00000000000..0562bacb7b9 --- /dev/null +++ b/security/selinux/ss/context.h @@ -0,0 +1,107 @@ +/* + * A security context is a set of security attributes + * associated with each subject and object controlled + * by the security policy. Security contexts are + * externally represented as variable-length strings + * that can be interpreted by a user or application + * with an understanding of the security policy. + * Internally, the security server uses a simple + * structure. This structure is private to the + * security server and can be changed without affecting + * clients of the security server. + * + * Author : Stephen Smalley, <sds@epoch.ncsc.mil> + */ +#ifndef _SS_CONTEXT_H_ +#define _SS_CONTEXT_H_ + +#include "ebitmap.h" +#include "mls_types.h" +#include "security.h" + +/* + * A security context consists of an authenticated user + * identity, a role, a type and a MLS range. + */ +struct context { + u32 user; + u32 role; + u32 type; + struct mls_range range; +}; + +static inline void mls_context_init(struct context *c) +{ + memset(&c->range, 0, sizeof(c->range)); +} + +static inline int mls_context_cpy(struct context *dst, struct context *src) +{ + int rc; + + if (!selinux_mls_enabled) + return 0; + + dst->range.level[0].sens = src->range.level[0].sens; + rc = ebitmap_cpy(&dst->range.level[0].cat, &src->range.level[0].cat); + if (rc) + goto out; + + dst->range.level[1].sens = src->range.level[1].sens; + rc = ebitmap_cpy(&dst->range.level[1].cat, &src->range.level[1].cat); + if (rc) + ebitmap_destroy(&dst->range.level[0].cat); +out: + return rc; +} + +static inline int mls_context_cmp(struct context *c1, struct context *c2) +{ + if (!selinux_mls_enabled) + return 1; + + return ((c1->range.level[0].sens == c2->range.level[0].sens) && + ebitmap_cmp(&c1->range.level[0].cat,&c2->range.level[0].cat) && + (c1->range.level[1].sens == c2->range.level[1].sens) && + ebitmap_cmp(&c1->range.level[1].cat,&c2->range.level[1].cat)); +} + +static inline void mls_context_destroy(struct context *c) +{ + if (!selinux_mls_enabled) + return; + + ebitmap_destroy(&c->range.level[0].cat); + ebitmap_destroy(&c->range.level[1].cat); + mls_context_init(c); +} + +static inline void context_init(struct context *c) +{ + memset(c, 0, sizeof(*c)); +} + +static inline int context_cpy(struct context *dst, struct context *src) +{ + dst->user = src->user; + dst->role = src->role; + dst->type = src->type; + return mls_context_cpy(dst, src); +} + +static inline void context_destroy(struct context *c) +{ + c->user = c->role = c->type = 0; + mls_context_destroy(c); +} + +static inline int context_cmp(struct context *c1, struct context *c2) +{ + return ((c1->user == c2->user) && + (c1->role == c2->role) && + (c1->type == c2->type) && + mls_context_cmp(c1, c2)); +} + +#endif /* _SS_CONTEXT_H_ */ + diff --git a/security/selinux/ss/ebitmap.c b/security/selinux/ss/ebitmap.c new file mode 100644 index 00000000000..d8ce9cc0b9f --- /dev/null +++ b/security/selinux/ss/ebitmap.c @@ -0,0 +1,293 @@ +/* + * Implementation of the extensible bitmap type. + * + * Author : Stephen Smalley, <sds@epoch.ncsc.mil> + */ +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/errno.h> +#include "ebitmap.h" +#include "policydb.h" + +int ebitmap_cmp(struct ebitmap *e1, struct ebitmap *e2) +{ + struct ebitmap_node *n1, *n2; + + if (e1->highbit != e2->highbit) + return 0; + + n1 = e1->node; + n2 = e2->node; + while (n1 && n2 && + (n1->startbit == n2->startbit) && + (n1->map == n2->map)) { + n1 = n1->next; + n2 = n2->next; + } + + if (n1 || n2) + return 0; + + return 1; +} + +int ebitmap_cpy(struct ebitmap *dst, struct ebitmap *src) +{ + struct ebitmap_node *n, *new, *prev; + + ebitmap_init(dst); + n = src->node; + prev = NULL; + while (n) { + new = kmalloc(sizeof(*new), GFP_ATOMIC); + if (!new) { + ebitmap_destroy(dst); + return -ENOMEM; + } + memset(new, 0, sizeof(*new)); + new->startbit = n->startbit; + new->map = n->map; + new->next = NULL; + if (prev) + prev->next = new; + else + dst->node = new; + prev = new; + n = n->next; + } + + dst->highbit = src->highbit; + return 0; +} + +int ebitmap_contains(struct ebitmap *e1, struct ebitmap *e2) +{ + struct ebitmap_node *n1, *n2; + + if (e1->highbit < e2->highbit) + return 0; + + n1 = e1->node; + n2 = e2->node; + while (n1 && n2 && (n1->startbit <= n2->startbit)) { + if (n1->startbit < n2->startbit) { + n1 = n1->next; + continue; + } + if ((n1->map & n2->map) != n2->map) + return 0; + + n1 = n1->next; + n2 = n2->next; + } + + if (n2) + return 0; + + return 1; +} + +int ebitmap_get_bit(struct ebitmap *e, unsigned long bit) +{ + struct ebitmap_node *n; + + if (e->highbit < bit) + return 0; + + n = e->node; + while (n && (n->startbit <= bit)) { + if ((n->startbit + MAPSIZE) > bit) { + if (n->map & (MAPBIT << (bit - n->startbit))) + return 1; + else + return 0; + } + n = n->next; + } + + return 0; +} + +int ebitmap_set_bit(struct ebitmap *e, unsigned long bit, int value) +{ + struct ebitmap_node *n, *prev, *new; + + prev = NULL; + n = e->node; + while (n && n->startbit <= bit) { + if ((n->startbit + MAPSIZE) > bit) { + if (value) { + n->map |= (MAPBIT << (bit - n->startbit)); + } else { + n->map &= ~(MAPBIT << (bit - n->startbit)); + if (!n->map) { + /* drop this node from the bitmap */ + + if (!n->next) { + /* + * this was the highest map + * within the bitmap + */ + if (prev) + e->highbit = prev->startbit + MAPSIZE; + else + e->highbit = 0; + } + if (prev) + prev->next = n->next; + else + e->node = n->next; + + kfree(n); + } + } + return 0; + } + prev = n; + n = n->next; + } + + if (!value) + return 0; + + new = kmalloc(sizeof(*new), GFP_ATOMIC); + if (!new) + return -ENOMEM; + memset(new, 0, sizeof(*new)); + + new->startbit = bit & ~(MAPSIZE - 1); + new->map = (MAPBIT << (bit - new->startbit)); + + if (!n) + /* this node will be the highest map within the bitmap */ + e->highbit = new->startbit + MAPSIZE; + + if (prev) { + new->next = prev->next; + prev->next = new; + } else { + new->next = e->node; + e->node = new; + } + + return 0; +} + +void ebitmap_destroy(struct ebitmap *e) +{ + struct ebitmap_node *n, *temp; + + if (!e) + return; + + n = e->node; + while (n) { + temp = n; + n = n->next; + kfree(temp); + } + + e->highbit = 0; + e->node = NULL; + return; +} + +int ebitmap_read(struct ebitmap *e, void *fp) +{ + int rc; + struct ebitmap_node *n, *l; + u32 buf[3], mapsize, count, i; + u64 map; + + ebitmap_init(e); + + rc = next_entry(buf, fp, sizeof buf); + if (rc < 0) + goto out; + + mapsize = le32_to_cpu(buf[0]); + e->highbit = le32_to_cpu(buf[1]); + count = le32_to_cpu(buf[2]); + + if (mapsize != MAPSIZE) { + printk(KERN_ERR "security: ebitmap: map size %u does not " + "match my size %Zd (high bit was %d)\n", mapsize, + MAPSIZE, e->highbit); + goto bad; + } + if (!e->highbit) { + e->node = NULL; + goto ok; + } + if (e->highbit & (MAPSIZE - 1)) { + printk(KERN_ERR "security: ebitmap: high bit (%d) is not a " + "multiple of the map size (%Zd)\n", e->highbit, MAPSIZE); + goto bad; + } + l = NULL; + for (i = 0; i < count; i++) { + rc = next_entry(buf, fp, sizeof(u32)); + if (rc < 0) { + printk(KERN_ERR "security: ebitmap: truncated map\n"); + goto bad; + } + n = kmalloc(sizeof(*n), GFP_KERNEL); + if (!n) { + printk(KERN_ERR "security: ebitmap: out of memory\n"); + rc = -ENOMEM; + goto bad; + } + memset(n, 0, sizeof(*n)); + + n->startbit = le32_to_cpu(buf[0]); + + if (n->startbit & (MAPSIZE - 1)) { + printk(KERN_ERR "security: ebitmap start bit (%d) is " + "not a multiple of the map size (%Zd)\n", + n->startbit, MAPSIZE); + goto bad_free; + } + if (n->startbit > (e->highbit - MAPSIZE)) { + printk(KERN_ERR "security: ebitmap start bit (%d) is " + "beyond the end of the bitmap (%Zd)\n", + n->startbit, (e->highbit - MAPSIZE)); + goto bad_free; + } + rc = next_entry(&map, fp, sizeof(u64)); + if (rc < 0) { + printk(KERN_ERR "security: ebitmap: truncated map\n"); + goto bad_free; + } + n->map = le64_to_cpu(map); + + if (!n->map) { + printk(KERN_ERR "security: ebitmap: null map in " + "ebitmap (startbit %d)\n", n->startbit); + goto bad_free; + } + if (l) { + if (n->startbit <= l->startbit) { + printk(KERN_ERR "security: ebitmap: start " + "bit %d comes after start bit %d\n", + n->startbit, l->startbit); + goto bad_free; + } + l->next = n; + } else + e->node = n; + + l = n; + } + +ok: + rc = 0; +out: + return rc; +bad_free: + kfree(n); +bad: + if (!rc) + rc = -EINVAL; + ebitmap_destroy(e); + goto out; +} diff --git a/security/selinux/ss/ebitmap.h b/security/selinux/ss/ebitmap.h new file mode 100644 index 00000000000..471370233fd --- /dev/null +++ b/security/selinux/ss/ebitmap.h @@ -0,0 +1,48 @@ +/* + * An extensible bitmap is a bitmap that supports an + * arbitrary number of bits. Extensible bitmaps are + * used to represent sets of values, such as types, + * roles, categories, and classes. + * + * Each extensible bitmap is implemented as a linked + * list of bitmap nodes, where each bitmap node has + * an explicitly specified starting bit position within + * the total bitmap. + * + * Author : Stephen Smalley, <sds@epoch.ncsc.mil> + */ +#ifndef _SS_EBITMAP_H_ +#define _SS_EBITMAP_H_ + +#define MAPTYPE u64 /* portion of bitmap in each node */ +#define MAPSIZE (sizeof(MAPTYPE) * 8) /* number of bits in node bitmap */ +#define MAPBIT 1ULL /* a bit in the node bitmap */ + +struct ebitmap_node { + u32 startbit; /* starting position in the total bitmap */ + MAPTYPE map; /* this node's portion of the bitmap */ + struct ebitmap_node *next; +}; + +struct ebitmap { + struct ebitmap_node *node; /* first node in the bitmap */ + u32 highbit; /* highest position in the total bitmap */ +}; + +#define ebitmap_length(e) ((e)->highbit) +#define ebitmap_startbit(e) ((e)->node ? (e)->node->startbit : 0) + +static inline void ebitmap_init(struct ebitmap *e) +{ + memset(e, 0, sizeof(*e)); +} + +int ebitmap_cmp(struct ebitmap *e1, struct ebitmap *e2); +int ebitmap_cpy(struct ebitmap *dst, struct ebitmap *src); +int ebitmap_contains(struct ebitmap *e1, struct ebitmap *e2); +int ebitmap_get_bit(struct ebitmap *e, unsigned long bit); +int ebitmap_set_bit(struct ebitmap *e, unsigned long bit, int value); +void ebitmap_destroy(struct ebitmap *e); +int ebitmap_read(struct ebitmap *e, void *fp); + +#endif /* _SS_EBITMAP_H_ */ diff --git a/security/selinux/ss/hashtab.c b/security/selinux/ss/hashtab.c new file mode 100644 index 00000000000..26661fcc00c --- /dev/null +++ b/security/selinux/ss/hashtab.c @@ -0,0 +1,167 @@ +/* + * Implementation of the hash table type. + * + * Author : Stephen Smalley, <sds@epoch.ncsc.mil> + */ +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/errno.h> +#include "hashtab.h" + +struct hashtab *hashtab_create(u32 (*hash_value)(struct hashtab *h, void *key), + int (*keycmp)(struct hashtab *h, void *key1, void *key2), + u32 size) +{ + struct hashtab *p; + u32 i; + + p = kmalloc(sizeof(*p), GFP_KERNEL); + if (p == NULL) + return p; + + memset(p, 0, sizeof(*p)); + p->size = size; + p->nel = 0; + p->hash_value = hash_value; + p->keycmp = keycmp; + p->htable = kmalloc(sizeof(*(p->htable)) * size, GFP_KERNEL); + if (p->htable == NULL) { + kfree(p); + return NULL; + } + + for (i = 0; i < size; i++) + p->htable[i] = NULL; + + return p; +} + +int hashtab_insert(struct hashtab *h, void *key, void *datum) +{ + u32 hvalue; + struct hashtab_node *prev, *cur, *newnode; + + if (!h || h->nel == HASHTAB_MAX_NODES) + return -EINVAL; + + hvalue = h->hash_value(h, key); + prev = NULL; + cur = h->htable[hvalue]; + while (cur && h->keycmp(h, key, cur->key) > 0) { + prev = cur; + cur = cur->next; + } + + if (cur && (h->keycmp(h, key, cur->key) == 0)) + return -EEXIST; + + newnode = kmalloc(sizeof(*newnode), GFP_KERNEL); + if (newnode == NULL) + return -ENOMEM; + memset(newnode, 0, sizeof(*newnode)); + newnode->key = key; + newnode->datum = datum; + if (prev) { + newnode->next = prev->next; + prev->next = newnode; + } else { + newnode->next = h->htable[hvalue]; + h->htable[hvalue] = newnode; + } + + h->nel++; + return 0; +} + +void *hashtab_search(struct hashtab *h, void *key) +{ + u32 hvalue; + struct hashtab_node *cur; + + if (!h) + return NULL; + + hvalue = h->hash_value(h, key); + cur = h->htable[hvalue]; + while (cur != NULL && h->keycmp(h, key, cur->key) > 0) + cur = cur->next; + + if (cur == NULL || (h->keycmp(h, key, cur->key) != 0)) + return NULL; + + return cur->datum; +} + +void hashtab_destroy(struct hashtab *h) +{ + u32 i; + struct hashtab_node *cur, *temp; + + if (!h) + return; + + for (i = 0; i < h->size; i++) { + cur = h->htable[i]; + while (cur != NULL) { + temp = cur; + cur = cur->next; + kfree(temp); + } + h->htable[i] = NULL; + } + + kfree(h->htable); + h->htable = NULL; + + kfree(h); +} + +int hashtab_map(struct hashtab *h, + int (*apply)(void *k, void *d, void *args), + void *args) +{ + u32 i; + int ret; + struct hashtab_node *cur; + + if (!h) + return 0; + + for (i = 0; i < h->size; i++) { + cur = h->htable[i]; + while (cur != NULL) { + ret = apply(cur->key, cur->datum, args); + if (ret) + return ret; + cur = cur->next; + } + } + return 0; +} + + +void hashtab_stat(struct hashtab *h, struct hashtab_info *info) +{ + u32 i, chain_len, slots_used, max_chain_len; + struct hashtab_node *cur; + + slots_used = 0; + max_chain_len = 0; + for (slots_used = max_chain_len = i = 0; i < h->size; i++) { + cur = h->htable[i]; + if (cur) { + slots_used++; + chain_len = 0; + while (cur) { + chain_len++; + cur = cur->next; + } + + if (chain_len > max_chain_len) + max_chain_len = chain_len; + } + } + + info->slots_used = slots_used; + info->max_chain_len = max_chain_len; +} diff --git a/security/selinux/ss/hashtab.h b/security/selinux/ss/hashtab.h new file mode 100644 index 00000000000..4cc85816a71 --- /dev/null +++ b/security/selinux/ss/hashtab.h @@ -0,0 +1,87 @@ +/* + * A hash table (hashtab) maintains associations between + * key values and datum values. The type of the key values + * and the type of the datum values is arbitrary. The + * functions for hash computation and key comparison are + * provided by the creator of the table. + * + * Author : Stephen Smalley, <sds@epoch.ncsc.mil> + */ +#ifndef _SS_HASHTAB_H_ +#define _SS_HASHTAB_H_ + +#define HASHTAB_MAX_NODES 0xffffffff + +struct hashtab_node { + void *key; + void *datum; + struct hashtab_node *next; +}; + +struct hashtab { + struct hashtab_node **htable; /* hash table */ + u32 size; /* number of slots in hash table */ + u32 nel; /* number of elements in hash table */ + u32 (*hash_value)(struct hashtab *h, void *key); + /* hash function */ + int (*keycmp)(struct hashtab *h, void *key1, void *key2); + /* key comparison function */ +}; + +struct hashtab_info { + u32 slots_used; + u32 max_chain_len; +}; + +/* + * Creates a new hash table with the specified characteristics. + * + * Returns NULL if insufficent space is available or + * the new hash table otherwise. + */ +struct hashtab *hashtab_create(u32 (*hash_value)(struct hashtab *h, void *key), + int (*keycmp)(struct hashtab *h, void *key1, void *key2), + u32 size); + +/* + * Inserts the specified (key, datum) pair into the specified hash table. + * + * Returns -ENOMEM on memory allocation error, + * -EEXIST if there is already an entry with the same key, + * -EINVAL for general errors or + * 0 otherwise. + */ +int hashtab_insert(struct hashtab *h, void *k, void *d); + +/* + * Searches for the entry with the specified key in the hash table. + * + * Returns NULL if no entry has the specified key or + * the datum of the entry otherwise. + */ +void *hashtab_search(struct hashtab *h, void *k); + +/* + * Destroys the specified hash table. + */ +void hashtab_destroy(struct hashtab *h); + +/* + * Applies the specified apply function to (key,datum,args) + * for each entry in the specified hash table. + * + * The order in which the function is applied to the entries + * is dependent upon the internal structure of the hash table. + * + * If apply returns a non-zero status, then hashtab_map will cease + * iterating through the hash table and will propagate the error + * return to its caller. + */ +int hashtab_map(struct hashtab *h, + int (*apply)(void *k, void *d, void *args), + void *args); + +/* Fill info with some hash table statistics */ +void hashtab_stat(struct hashtab *h, struct hashtab_info *info); + +#endif /* _SS_HASHTAB_H */ diff --git a/security/selinux/ss/mls.c b/security/selinux/ss/mls.c new file mode 100644 index 00000000000..756036bcc24 --- /dev/null +++ b/security/selinux/ss/mls.c @@ -0,0 +1,527 @@ +/* + * Implementation of the multi-level security (MLS) policy. + * + * Author : Stephen Smalley, <sds@epoch.ncsc.mil> + */ +/* + * Updated: Trusted Computer Solutions, Inc. <dgoeddel@trustedcs.com> + * + * Support for enhanced MLS infrastructure. + * + * Copyright (C) 2004-2005 Trusted Computer Solutions, Inc. + */ + +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/string.h> +#include <linux/errno.h> +#include "mls.h" +#include "policydb.h" +#include "services.h" + +/* + * Return the length in bytes for the MLS fields of the + * security context string representation of `context'. + */ +int mls_compute_context_len(struct context * context) +{ + int i, l, len, range; + + if (!selinux_mls_enabled) + return 0; + + len = 1; /* for the beginning ":" */ + for (l = 0; l < 2; l++) { + range = 0; + len += strlen(policydb.p_sens_val_to_name[context->range.level[l].sens - 1]); + + for (i = 1; i <= ebitmap_length(&context->range.level[l].cat); i++) { + if (ebitmap_get_bit(&context->range.level[l].cat, i - 1)) { + if (range) { + range++; + continue; + } + + len += strlen(policydb.p_cat_val_to_name[i - 1]) + 1; + range++; + } else { + if (range > 1) + len += strlen(policydb.p_cat_val_to_name[i - 2]) + 1; + range = 0; + } + } + /* Handle case where last category is the end of range */ + if (range > 1) + len += strlen(policydb.p_cat_val_to_name[i - 2]) + 1; + + if (l == 0) { + if (mls_level_eq(&context->range.level[0], + &context->range.level[1])) + break; + else + len++; + } + } + + return len; +} + +/* + * Write the security context string representation of + * the MLS fields of `context' into the string `*scontext'. + * Update `*scontext' to point to the end of the MLS fields. + */ +void mls_sid_to_context(struct context *context, + char **scontext) +{ + char *scontextp; + int i, l, range, wrote_sep; + + if (!selinux_mls_enabled) + return; + + scontextp = *scontext; + + *scontextp = ':'; + scontextp++; + + for (l = 0; l < 2; l++) { + range = 0; + wrote_sep = 0; + strcpy(scontextp, + policydb.p_sens_val_to_name[context->range.level[l].sens - 1]); + scontextp += strlen(policydb.p_sens_val_to_name[context->range.level[l].sens - 1]); + + /* categories */ + for (i = 1; i <= ebitmap_length(&context->range.level[l].cat); i++) { + if (ebitmap_get_bit(&context->range.level[l].cat, i - 1)) { + if (range) { + range++; + continue; + } + + if (!wrote_sep) { + *scontextp++ = ':'; + wrote_sep = 1; + } else + *scontextp++ = ','; + strcpy(scontextp, policydb.p_cat_val_to_name[i - 1]); + scontextp += strlen(policydb.p_cat_val_to_name[i - 1]); + range++; + } else { + if (range > 1) { + if (range > 2) + *scontextp++ = '.'; + else + *scontextp++ = ','; + + strcpy(scontextp, policydb.p_cat_val_to_name[i - 2]); + scontextp += strlen(policydb.p_cat_val_to_name[i - 2]); + } + range = 0; + } + } + + /* Handle case where last category is the end of range */ + if (range > 1) { + if (range > 2) + *scontextp++ = '.'; + else + *scontextp++ = ','; + + strcpy(scontextp, policydb.p_cat_val_to_name[i - 2]); + scontextp += strlen(policydb.p_cat_val_to_name[i - 2]); + } + + if (l == 0) { + if (mls_level_eq(&context->range.level[0], + &context->range.level[1])) + break; + else { + *scontextp = '-'; + scontextp++; + } + } + } + + *scontext = scontextp; + return; +} + +/* + * Return 1 if the MLS fields in the security context + * structure `c' are valid. Return 0 otherwise. + */ +int mls_context_isvalid(struct policydb *p, struct context *c) +{ + struct level_datum *levdatum; + struct user_datum *usrdatum; + int i, l; + + if (!selinux_mls_enabled) + return 1; + + /* + * MLS range validity checks: high must dominate low, low level must + * be valid (category set <-> sensitivity check), and high level must + * be valid (category set <-> sensitivity check) + */ + if (!mls_level_dom(&c->range.level[1], &c->range.level[0])) + /* High does not dominate low. */ + return 0; + + for (l = 0; l < 2; l++) { + if (!c->range.level[l].sens || c->range.level[l].sens > p->p_levels.nprim) + return 0; + levdatum = hashtab_search(p->p_levels.table, + p->p_sens_val_to_name[c->range.level[l].sens - 1]); + if (!levdatum) + return 0; + + for (i = 1; i <= ebitmap_length(&c->range.level[l].cat); i++) { + if (ebitmap_get_bit(&c->range.level[l].cat, i - 1)) { + if (i > p->p_cats.nprim) + return 0; + if (!ebitmap_get_bit(&levdatum->level->cat, i - 1)) + /* + * Category may not be associated with + * sensitivity in low level. + */ + return 0; + } + } + } + + if (c->role == OBJECT_R_VAL) + return 1; + + /* + * User must be authorized for the MLS range. + */ + if (!c->user || c->user > p->p_users.nprim) + return 0; + usrdatum = p->user_val_to_struct[c->user - 1]; + if (!mls_range_contains(usrdatum->range, c->range)) + return 0; /* user may not be associated with range */ + + return 1; +} + +/* + * Set the MLS fields in the security context structure + * `context' based on the string representation in + * the string `*scontext'. Update `*scontext' to + * point to the end of the string representation of + * the MLS fields. + * + * This function modifies the string in place, inserting + * NULL characters to terminate the MLS fields. + */ +int mls_context_to_sid(char oldc, + char **scontext, + struct context *context) +{ + + char delim; + char *scontextp, *p, *rngptr; + struct level_datum *levdatum; + struct cat_datum *catdatum, *rngdatum; + int l, rc = -EINVAL; + + if (!selinux_mls_enabled) + return 0; + + /* No MLS component to the security context. */ + if (!oldc) + goto out; + + /* Extract low sensitivity. */ + scontextp = p = *scontext; + while (*p && *p != ':' && *p != '-') + p++; + + delim = *p; + if (delim != 0) + *p++ = 0; + + for (l = 0; l < 2; l++) { + levdatum = hashtab_search(policydb.p_levels.table, scontextp); + if (!levdatum) { + rc = -EINVAL; + goto out; + } + + context->range.level[l].sens = levdatum->level->sens; + + if (delim == ':') { + /* Extract category set. */ + while (1) { + scontextp = p; + while (*p && *p != ',' && *p != '-') + p++; + delim = *p; + if (delim != 0) + *p++ = 0; + + /* Separate into range if exists */ + if ((rngptr = strchr(scontextp, '.')) != NULL) { + /* Remove '.' */ + *rngptr++ = 0; + } + + catdatum = hashtab_search(policydb.p_cats.table, + scontextp); + if (!catdatum) { + rc = -EINVAL; + goto out; + } + + rc = ebitmap_set_bit(&context->range.level[l].cat, + catdatum->value - 1, 1); + if (rc) + goto out; + + /* If range, set all categories in range */ + if (rngptr) { + int i; + + rngdatum = hashtab_search(policydb.p_cats.table, rngptr); + if (!rngdatum) { + rc = -EINVAL; + goto out; + } + + if (catdatum->value >= rngdatum->value) { + rc = -EINVAL; + goto out; + } + + for (i = catdatum->value; i < rngdatum->value; i++) { + rc = ebitmap_set_bit(&context->range.level[l].cat, i, 1); + if (rc) + goto out; + } + } + + if (delim != ',') + break; + } + } + if (delim == '-') { + /* Extract high sensitivity. */ + scontextp = p; + while (*p && *p != ':') + p++; + + delim = *p; + if (delim != 0) + *p++ = 0; + } else + break; + } + + if (l == 0) { + context->range.level[1].sens = context->range.level[0].sens; + rc = ebitmap_cpy(&context->range.level[1].cat, + &context->range.level[0].cat); + if (rc) + goto out; + } + *scontext = ++p; + rc = 0; +out: + return rc; +} + +/* + * Copies the MLS range from `src' into `dst'. + */ +static inline int mls_copy_context(struct context *dst, + struct context *src) +{ + int l, rc = 0; + + /* Copy the MLS range from the source context */ + for (l = 0; l < 2; l++) { + dst->range.level[l].sens = src->range.level[l].sens; + rc = ebitmap_cpy(&dst->range.level[l].cat, + &src->range.level[l].cat); + if (rc) + break; + } + + return rc; +} + +/* + * Copies the effective MLS range from `src' into `dst'. + */ +static inline int mls_scopy_context(struct context *dst, + struct context *src) +{ + int l, rc = 0; + + /* Copy the MLS range from the source context */ + for (l = 0; l < 2; l++) { + dst->range.level[l].sens = src->range.level[0].sens; + rc = ebitmap_cpy(&dst->range.level[l].cat, + &src->range.level[0].cat); + if (rc) + break; + } + + return rc; +} + +/* + * Copies the MLS range `range' into `context'. + */ +static inline int mls_range_set(struct context *context, + struct mls_range *range) +{ + int l, rc = 0; + + /* Copy the MLS range into the context */ + for (l = 0; l < 2; l++) { + context->range.level[l].sens = range->level[l].sens; + rc = ebitmap_cpy(&context->range.level[l].cat, + &range->level[l].cat); + if (rc) + break; + } + + return rc; +} + +int mls_setup_user_range(struct context *fromcon, struct user_datum *user, + struct context *usercon) +{ + if (selinux_mls_enabled) { + struct mls_level *fromcon_sen = &(fromcon->range.level[0]); + struct mls_level *fromcon_clr = &(fromcon->range.level[1]); + struct mls_level *user_low = &(user->range.level[0]); + struct mls_level *user_clr = &(user->range.level[1]); + struct mls_level *user_def = &(user->dfltlevel); + struct mls_level *usercon_sen = &(usercon->range.level[0]); + struct mls_level *usercon_clr = &(usercon->range.level[1]); + + /* Honor the user's default level if we can */ + if (mls_level_between(user_def, fromcon_sen, fromcon_clr)) { + *usercon_sen = *user_def; + } else if (mls_level_between(fromcon_sen, user_def, user_clr)) { + *usercon_sen = *fromcon_sen; + } else if (mls_level_between(fromcon_clr, user_low, user_def)) { + *usercon_sen = *user_low; + } else + return -EINVAL; + + /* Lower the clearance of available contexts + if the clearance of "fromcon" is lower than + that of the user's default clearance (but + only if the "fromcon" clearance dominates + the user's computed sensitivity level) */ + if (mls_level_dom(user_clr, fromcon_clr)) { + *usercon_clr = *fromcon_clr; + } else if (mls_level_dom(fromcon_clr, user_clr)) { + *usercon_clr = *user_clr; + } else + return -EINVAL; + } + + return 0; +} + +/* + * Convert the MLS fields in the security context + * structure `c' from the values specified in the + * policy `oldp' to the values specified in the policy `newp'. + */ +int mls_convert_context(struct policydb *oldp, + struct policydb *newp, + struct context *c) +{ + struct level_datum *levdatum; + struct cat_datum *catdatum; + struct ebitmap bitmap; + int l, i; + + if (!selinux_mls_enabled) + return 0; + + for (l = 0; l < 2; l++) { + levdatum = hashtab_search(newp->p_levels.table, + oldp->p_sens_val_to_name[c->range.level[l].sens - 1]); + + if (!levdatum) + return -EINVAL; + c->range.level[l].sens = levdatum->level->sens; + + ebitmap_init(&bitmap); + for (i = 1; i <= ebitmap_length(&c->range.level[l].cat); i++) { + if (ebitmap_get_bit(&c->range.level[l].cat, i - 1)) { + int rc; + + catdatum = hashtab_search(newp->p_cats.table, + oldp->p_cat_val_to_name[i - 1]); + if (!catdatum) + return -EINVAL; + rc = ebitmap_set_bit(&bitmap, catdatum->value - 1, 1); + if (rc) + return rc; + } + } + ebitmap_destroy(&c->range.level[l].cat); + c->range.level[l].cat = bitmap; + } + + return 0; +} + +int mls_compute_sid(struct context *scontext, + struct context *tcontext, + u16 tclass, + u32 specified, + struct context *newcontext) +{ + if (!selinux_mls_enabled) + return 0; + + switch (specified) { + case AVTAB_TRANSITION: + if (tclass == SECCLASS_PROCESS) { + struct range_trans *rangetr; + /* Look for a range transition rule. */ + for (rangetr = policydb.range_tr; rangetr; + rangetr = rangetr->next) { + if (rangetr->dom == scontext->type && + rangetr->type == tcontext->type) { + /* Set the range from the rule */ + return mls_range_set(newcontext, + &rangetr->range); + } + } + } + /* Fallthrough */ + case AVTAB_CHANGE: + if (tclass == SECCLASS_PROCESS) + /* Use the process MLS attributes. */ + return mls_copy_context(newcontext, scontext); + else + /* Use the process effective MLS attributes. */ + return mls_scopy_context(newcontext, scontext); + case AVTAB_MEMBER: + /* Only polyinstantiate the MLS attributes if + the type is being polyinstantiated */ + if (newcontext->type != tcontext->type) { + /* Use the process effective MLS attributes. */ + return mls_scopy_context(newcontext, scontext); + } else { + /* Use the related object MLS attributes. */ + return mls_copy_context(newcontext, tcontext); + } + default: + return -EINVAL; + } + return -EINVAL; +} + diff --git a/security/selinux/ss/mls.h b/security/selinux/ss/mls.h new file mode 100644 index 00000000000..0d37beaa85e --- /dev/null +++ b/security/selinux/ss/mls.h @@ -0,0 +1,42 @@ +/* + * Multi-level security (MLS) policy operations. + * + * Author : Stephen Smalley, <sds@epoch.ncsc.mil> + */ +/* + * Updated: Trusted Computer Solutions, Inc. <dgoeddel@trustedcs.com> + * + * Support for enhanced MLS infrastructure. + * + * Copyright (C) 2004-2005 Trusted Computer Solutions, Inc. + */ + +#ifndef _SS_MLS_H_ +#define _SS_MLS_H_ + +#include "context.h" +#include "policydb.h" + +int mls_compute_context_len(struct context *context); +void mls_sid_to_context(struct context *context, char **scontext); +int mls_context_isvalid(struct policydb *p, struct context *c); + +int mls_context_to_sid(char oldc, + char **scontext, + struct context *context); + +int mls_convert_context(struct policydb *oldp, + struct policydb *newp, + struct context *context); + +int mls_compute_sid(struct context *scontext, + struct context *tcontext, + u16 tclass, + u32 specified, + struct context *newcontext); + +int mls_setup_user_range(struct context *fromcon, struct user_datum *user, + struct context *usercon); + +#endif /* _SS_MLS_H */ + diff --git a/security/selinux/ss/mls_types.h b/security/selinux/ss/mls_types.h new file mode 100644 index 00000000000..0c692d58d48 --- /dev/null +++ b/security/selinux/ss/mls_types.h @@ -0,0 +1,56 @@ +/* + * Type definitions for the multi-level security (MLS) policy. + * + * Author : Stephen Smalley, <sds@epoch.ncsc.mil> + */ +/* + * Updated: Trusted Computer Solutions, Inc. <dgoeddel@trustedcs.com> + * + * Support for enhanced MLS infrastructure. + * + * Copyright (C) 2004-2005 Trusted Computer Solutions, Inc. + */ + +#ifndef _SS_MLS_TYPES_H_ +#define _SS_MLS_TYPES_H_ + +#include "security.h" + +struct mls_level { + u32 sens; /* sensitivity */ + struct ebitmap cat; /* category set */ +}; + +struct mls_range { + struct mls_level level[2]; /* low == level[0], high == level[1] */ +}; + +static inline int mls_level_eq(struct mls_level *l1, struct mls_level *l2) +{ + if (!selinux_mls_enabled) + return 1; + + return ((l1->sens == l2->sens) && + ebitmap_cmp(&l1->cat, &l2->cat)); +} + +static inline int mls_level_dom(struct mls_level *l1, struct mls_level *l2) +{ + if (!selinux_mls_enabled) + return 1; + + return ((l1->sens >= l2->sens) && + ebitmap_contains(&l1->cat, &l2->cat)); +} + +#define mls_level_incomp(l1, l2) \ +(!mls_level_dom((l1), (l2)) && !mls_level_dom((l2), (l1))) + +#define mls_level_between(l1, l2, l3) \ +(mls_level_dom((l1), (l2)) && mls_level_dom((l3), (l1))) + +#define mls_range_contains(r1, r2) \ +(mls_level_dom(&(r2).level[0], &(r1).level[0]) && \ + mls_level_dom(&(r1).level[1], &(r2).level[1])) + +#endif /* _SS_MLS_TYPES_H_ */ diff --git a/security/selinux/ss/policydb.c b/security/selinux/ss/policydb.c new file mode 100644 index 00000000000..14190efbf33 --- /dev/null +++ b/security/selinux/ss/policydb.c @@ -0,0 +1,1843 @@ +/* + * Implementation of the policy database. + * + * Author : Stephen Smalley, <sds@epoch.ncsc.mil> + */ + +/* + * Updated: Trusted Computer Solutions, Inc. <dgoeddel@trustedcs.com> + * + * Support for enhanced MLS infrastructure. + * + * Updated: Frank Mayer <mayerf@tresys.com> and Karl MacMillan <kmacmillan@tresys.com> + * + * Added conditional policy language extensions + * + * Copyright (C) 2004-2005 Trusted Computer Solutions, Inc. + * Copyright (C) 2003 - 2004 Tresys Technology, LLC + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 2. + */ + +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/string.h> +#include <linux/errno.h> +#include "security.h" + +#include "policydb.h" +#include "conditional.h" +#include "mls.h" + +#define _DEBUG_HASHES + +#ifdef DEBUG_HASHES +static char *symtab_name[SYM_NUM] = { + "common prefixes", + "classes", + "roles", + "types", + "users", + "bools", + "levels", + "categories", +}; +#endif + +int selinux_mls_enabled = 0; + +static unsigned int symtab_sizes[SYM_NUM] = { + 2, + 32, + 16, + 512, + 128, + 16, + 16, + 16, +}; + +struct policydb_compat_info { + int version; + int sym_num; + int ocon_num; +}; + +/* These need to be updated if SYM_NUM or OCON_NUM changes */ +static struct policydb_compat_info policydb_compat[] = { + { + .version = POLICYDB_VERSION_BASE, + .sym_num = SYM_NUM - 3, + .ocon_num = OCON_NUM - 1, + }, + { + .version = POLICYDB_VERSION_BOOL, + .sym_num = SYM_NUM - 2, + .ocon_num = OCON_NUM - 1, + }, + { + .version = POLICYDB_VERSION_IPV6, + .sym_num = SYM_NUM - 2, + .ocon_num = OCON_NUM, + }, + { + .version = POLICYDB_VERSION_NLCLASS, + .sym_num = SYM_NUM - 2, + .ocon_num = OCON_NUM, + }, + { + .version = POLICYDB_VERSION_MLS, + .sym_num = SYM_NUM, + .ocon_num = OCON_NUM, + }, +}; + +static struct policydb_compat_info *policydb_lookup_compat(int version) +{ + int i; + struct policydb_compat_info *info = NULL; + + for (i = 0; i < sizeof(policydb_compat)/sizeof(*info); i++) { + if (policydb_compat[i].version == version) { + info = &policydb_compat[i]; + break; + } + } + return info; +} + +/* + * Initialize the role table. + */ +static int roles_init(struct policydb *p) +{ + char *key = NULL; + int rc; + struct role_datum *role; + + role = kmalloc(sizeof(*role), GFP_KERNEL); + if (!role) { + rc = -ENOMEM; + goto out; + } + memset(role, 0, sizeof(*role)); + role->value = ++p->p_roles.nprim; + if (role->value != OBJECT_R_VAL) { + rc = -EINVAL; + goto out_free_role; + } + key = kmalloc(strlen(OBJECT_R)+1,GFP_KERNEL); + if (!key) { + rc = -ENOMEM; + goto out_free_role; + } + strcpy(key, OBJECT_R); + rc = hashtab_insert(p->p_roles.table, key, role); + if (rc) + goto out_free_key; +out: + return rc; + +out_free_key: + kfree(key); +out_free_role: + kfree(role); + goto out; +} + +/* + * Initialize a policy database structure. + */ +static int policydb_init(struct policydb *p) +{ + int i, rc; + + memset(p, 0, sizeof(*p)); + + for (i = 0; i < SYM_NUM; i++) { + rc = symtab_init(&p->symtab[i], symtab_sizes[i]); + if (rc) + goto out_free_symtab; + } + + rc = avtab_init(&p->te_avtab); + if (rc) + goto out_free_symtab; + + rc = roles_init(p); + if (rc) + goto out_free_avtab; + + rc = cond_policydb_init(p); + if (rc) + goto out_free_avtab; + +out: + return rc; + +out_free_avtab: + avtab_destroy(&p->te_avtab); + +out_free_symtab: + for (i = 0; i < SYM_NUM; i++) + hashtab_destroy(p->symtab[i].table); + goto out; +} + +/* + * The following *_index functions are used to + * define the val_to_name and val_to_struct arrays + * in a policy database structure. The val_to_name + * arrays are used when converting security context + * structures into string representations. The + * val_to_struct arrays are used when the attributes + * of a class, role, or user are needed. + */ + +static int common_index(void *key, void *datum, void *datap) +{ + struct policydb *p; + struct common_datum *comdatum; + + comdatum = datum; + p = datap; + if (!comdatum->value || comdatum->value > p->p_commons.nprim) + return -EINVAL; + p->p_common_val_to_name[comdatum->value - 1] = key; + return 0; +} + +static int class_index(void *key, void *datum, void *datap) +{ + struct policydb *p; + struct class_datum *cladatum; + + cladatum = datum; + p = datap; + if (!cladatum->value || cladatum->value > p->p_classes.nprim) + return -EINVAL; + p->p_class_val_to_name[cladatum->value - 1] = key; + p->class_val_to_struct[cladatum->value - 1] = cladatum; + return 0; +} + +static int role_index(void *key, void *datum, void *datap) +{ + struct policydb *p; + struct role_datum *role; + + role = datum; + p = datap; + if (!role->value || role->value > p->p_roles.nprim) + return -EINVAL; + p->p_role_val_to_name[role->value - 1] = key; + p->role_val_to_struct[role->value - 1] = role; + return 0; +} + +static int type_index(void *key, void *datum, void *datap) +{ + struct policydb *p; + struct type_datum *typdatum; + + typdatum = datum; + p = datap; + + if (typdatum->primary) { + if (!typdatum->value || typdatum->value > p->p_types.nprim) + return -EINVAL; + p->p_type_val_to_name[typdatum->value - 1] = key; + } + + return 0; +} + +static int user_index(void *key, void *datum, void *datap) +{ + struct policydb *p; + struct user_datum *usrdatum; + + usrdatum = datum; + p = datap; + if (!usrdatum->value || usrdatum->value > p->p_users.nprim) + return -EINVAL; + p->p_user_val_to_name[usrdatum->value - 1] = key; + p->user_val_to_struct[usrdatum->value - 1] = usrdatum; + return 0; +} + +static int sens_index(void *key, void *datum, void *datap) +{ + struct policydb *p; + struct level_datum *levdatum; + + levdatum = datum; + p = datap; + + if (!levdatum->isalias) { + if (!levdatum->level->sens || + levdatum->level->sens > p->p_levels.nprim) + return -EINVAL; + p->p_sens_val_to_name[levdatum->level->sens - 1] = key; + } + + return 0; +} + +static int cat_index(void *key, void *datum, void *datap) +{ + struct policydb *p; + struct cat_datum *catdatum; + + catdatum = datum; + p = datap; + + if (!catdatum->isalias) { + if (!catdatum->value || catdatum->value > p->p_cats.nprim) + return -EINVAL; + p->p_cat_val_to_name[catdatum->value - 1] = key; + } + + return 0; +} + +static int (*index_f[SYM_NUM]) (void *key, void *datum, void *datap) = +{ + common_index, + class_index, + role_index, + type_index, + user_index, + cond_index_bool, + sens_index, + cat_index, +}; + +/* + * Define the common val_to_name array and the class + * val_to_name and val_to_struct arrays in a policy + * database structure. + * + * Caller must clean up upon failure. + */ +static int policydb_index_classes(struct policydb *p) +{ + int rc; + + p->p_common_val_to_name = + kmalloc(p->p_commons.nprim * sizeof(char *), GFP_KERNEL); + if (!p->p_common_val_to_name) { + rc = -ENOMEM; + goto out; + } + + rc = hashtab_map(p->p_commons.table, common_index, p); + if (rc) + goto out; + + p->class_val_to_struct = + kmalloc(p->p_classes.nprim * sizeof(*(p->class_val_to_struct)), GFP_KERNEL); + if (!p->class_val_to_struct) { + rc = -ENOMEM; + goto out; + } + + p->p_class_val_to_name = + kmalloc(p->p_classes.nprim * sizeof(char *), GFP_KERNEL); + if (!p->p_class_val_to_name) { + rc = -ENOMEM; + goto out; + } + + rc = hashtab_map(p->p_classes.table, class_index, p); +out: + return rc; +} + +#ifdef DEBUG_HASHES +static void symtab_hash_eval(struct symtab *s) +{ + int i; + + for (i = 0; i < SYM_NUM; i++) { + struct hashtab *h = s[i].table; + struct hashtab_info info; + + hashtab_stat(h, &info); + printk(KERN_INFO "%s: %d entries and %d/%d buckets used, " + "longest chain length %d\n", symtab_name[i], h->nel, + info.slots_used, h->size, info.max_chain_len); + } +} +#endif + +/* + * Define the other val_to_name and val_to_struct arrays + * in a policy database structure. + * + * Caller must clean up on failure. + */ +static int policydb_index_others(struct policydb *p) +{ + int i, rc = 0; + + printk(KERN_INFO "security: %d users, %d roles, %d types, %d bools", + p->p_users.nprim, p->p_roles.nprim, p->p_types.nprim, p->p_bools.nprim); + if (selinux_mls_enabled) + printk(", %d sens, %d cats", p->p_levels.nprim, + p->p_cats.nprim); + printk("\n"); + + printk(KERN_INFO "security: %d classes, %d rules\n", + p->p_classes.nprim, p->te_avtab.nel); + +#ifdef DEBUG_HASHES + avtab_hash_eval(&p->te_avtab, "rules"); + symtab_hash_eval(p->symtab); +#endif + + p->role_val_to_struct = + kmalloc(p->p_roles.nprim * sizeof(*(p->role_val_to_struct)), + GFP_KERNEL); + if (!p->role_val_to_struct) { + rc = -ENOMEM; + goto out; + } + + p->user_val_to_struct = + kmalloc(p->p_users.nprim * sizeof(*(p->user_val_to_struct)), + GFP_KERNEL); + if (!p->user_val_to_struct) { + rc = -ENOMEM; + goto out; + } + + if (cond_init_bool_indexes(p)) { + rc = -ENOMEM; + goto out; + } + + for (i = SYM_ROLES; i < SYM_NUM; i++) { + p->sym_val_to_name[i] = + kmalloc(p->symtab[i].nprim * sizeof(char *), GFP_KERNEL); + if (!p->sym_val_to_name[i]) { + rc = -ENOMEM; + goto out; + } + rc = hashtab_map(p->symtab[i].table, index_f[i], p); + if (rc) + goto out; + } + +out: + return rc; +} + +/* + * The following *_destroy functions are used to + * free any memory allocated for each kind of + * symbol data in the policy database. + */ + +static int perm_destroy(void *key, void *datum, void *p) +{ + kfree(key); + kfree(datum); + return 0; +} + +static int common_destroy(void *key, void *datum, void *p) +{ + struct common_datum *comdatum; + + kfree(key); + comdatum = datum; + hashtab_map(comdatum->permissions.table, perm_destroy, NULL); + hashtab_destroy(comdatum->permissions.table); + kfree(datum); + return 0; +} + +static int class_destroy(void *key, void *datum, void *p) +{ + struct class_datum *cladatum; + struct constraint_node *constraint, *ctemp; + struct constraint_expr *e, *etmp; + + kfree(key); + cladatum = datum; + hashtab_map(cladatum->permissions.table, perm_destroy, NULL); + hashtab_destroy(cladatum->permissions.table); + constraint = cladatum->constraints; + while (constraint) { + e = constraint->expr; + while (e) { + ebitmap_destroy(&e->names); + etmp = e; + e = e->next; + kfree(etmp); + } + ctemp = constraint; + constraint = constraint->next; + kfree(ctemp); + } + + constraint = cladatum->validatetrans; + while (constraint) { + e = constraint->expr; + while (e) { + ebitmap_destroy(&e->names); + etmp = e; + e = e->next; + kfree(etmp); + } + ctemp = constraint; + constraint = constraint->next; + kfree(ctemp); + } + + kfree(cladatum->comkey); + kfree(datum); + return 0; +} + +static int role_destroy(void *key, void *datum, void *p) +{ + struct role_datum *role; + + kfree(key); + role = datum; + ebitmap_destroy(&role->dominates); + ebitmap_destroy(&role->types); + kfree(datum); + return 0; +} + +static int type_destroy(void *key, void *datum, void *p) +{ + kfree(key); + kfree(datum); + return 0; +} + +static int user_destroy(void *key, void *datum, void *p) +{ + struct user_datum *usrdatum; + + kfree(key); + usrdatum = datum; + ebitmap_destroy(&usrdatum->roles); + ebitmap_destroy(&usrdatum->range.level[0].cat); + ebitmap_destroy(&usrdatum->range.level[1].cat); + ebitmap_destroy(&usrdatum->dfltlevel.cat); + kfree(datum); + return 0; +} + +static int sens_destroy(void *key, void *datum, void *p) +{ + struct level_datum *levdatum; + + kfree(key); + levdatum = datum; + ebitmap_destroy(&levdatum->level->cat); + kfree(levdatum->level); + kfree(datum); + return 0; +} + +static int cat_destroy(void *key, void *datum, void *p) +{ + kfree(key); + kfree(datum); + return 0; +} + +static int (*destroy_f[SYM_NUM]) (void *key, void *datum, void *datap) = +{ + common_destroy, + class_destroy, + role_destroy, + type_destroy, + user_destroy, + cond_destroy_bool, + sens_destroy, + cat_destroy, +}; + +static void ocontext_destroy(struct ocontext *c, int i) +{ + context_destroy(&c->context[0]); + context_destroy(&c->context[1]); + if (i == OCON_ISID || i == OCON_FS || + i == OCON_NETIF || i == OCON_FSUSE) + kfree(c->u.name); + kfree(c); +} + +/* + * Free any memory allocated by a policy database structure. + */ +void policydb_destroy(struct policydb *p) +{ + struct ocontext *c, *ctmp; + struct genfs *g, *gtmp; + int i; + + for (i = 0; i < SYM_NUM; i++) { + hashtab_map(p->symtab[i].table, destroy_f[i], NULL); + hashtab_destroy(p->symtab[i].table); + } + + for (i = 0; i < SYM_NUM; i++) { + if (p->sym_val_to_name[i]) + kfree(p->sym_val_to_name[i]); + } + + if (p->class_val_to_struct) + kfree(p->class_val_to_struct); + if (p->role_val_to_struct) + kfree(p->role_val_to_struct); + if (p->user_val_to_struct) + kfree(p->user_val_to_struct); + + avtab_destroy(&p->te_avtab); + + for (i = 0; i < OCON_NUM; i++) { + c = p->ocontexts[i]; + while (c) { + ctmp = c; + c = c->next; + ocontext_destroy(ctmp,i); + } + } + + g = p->genfs; + while (g) { + kfree(g->fstype); + c = g->head; + while (c) { + ctmp = c; + c = c->next; + ocontext_destroy(ctmp,OCON_FSUSE); + } + gtmp = g; + g = g->next; + kfree(gtmp); + } + + cond_policydb_destroy(p); + + return; +} + +/* + * Load the initial SIDs specified in a policy database + * structure into a SID table. + */ +int policydb_load_isids(struct policydb *p, struct sidtab *s) +{ + struct ocontext *head, *c; + int rc; + + rc = sidtab_init(s); + if (rc) { + printk(KERN_ERR "security: out of memory on SID table init\n"); + goto out; + } + + head = p->ocontexts[OCON_ISID]; + for (c = head; c; c = c->next) { + if (!c->context[0].user) { + printk(KERN_ERR "security: SID %s was never " + "defined.\n", c->u.name); + rc = -EINVAL; + goto out; + } + if (sidtab_insert(s, c->sid[0], &c->context[0])) { + printk(KERN_ERR "security: unable to load initial " + "SID %s.\n", c->u.name); + rc = -EINVAL; + goto out; + } + } +out: + return rc; +} + +/* + * Return 1 if the fields in the security context + * structure `c' are valid. Return 0 otherwise. + */ +int policydb_context_isvalid(struct policydb *p, struct context *c) +{ + struct role_datum *role; + struct user_datum *usrdatum; + + if (!c->role || c->role > p->p_roles.nprim) + return 0; + + if (!c->user || c->user > p->p_users.nprim) + return 0; + + if (!c->type || c->type > p->p_types.nprim) + return 0; + + if (c->role != OBJECT_R_VAL) { + /* + * Role must be authorized for the type. + */ + role = p->role_val_to_struct[c->role - 1]; + if (!ebitmap_get_bit(&role->types, + c->type - 1)) + /* role may not be associated with type */ + return 0; + + /* + * User must be authorized for the role. + */ + usrdatum = p->user_val_to_struct[c->user - 1]; + if (!usrdatum) + return 0; + + if (!ebitmap_get_bit(&usrdatum->roles, + c->role - 1)) + /* user may not be associated with role */ + return 0; + } + + if (!mls_context_isvalid(p, c)) + return 0; + + return 1; +} + +/* + * Read a MLS range structure from a policydb binary + * representation file. + */ +static int mls_read_range_helper(struct mls_range *r, void *fp) +{ + u32 buf[2], items; + int rc; + + rc = next_entry(buf, fp, sizeof(u32)); + if (rc < 0) + goto out; + + items = le32_to_cpu(buf[0]); + if (items > ARRAY_SIZE(buf)) { + printk(KERN_ERR "security: mls: range overflow\n"); + rc = -EINVAL; + goto out; + } + rc = next_entry(buf, fp, sizeof(u32) * items); + if (rc < 0) { + printk(KERN_ERR "security: mls: truncated range\n"); + goto out; + } + r->level[0].sens = le32_to_cpu(buf[0]); + if (items > 1) + r->level[1].sens = le32_to_cpu(buf[1]); + else + r->level[1].sens = r->level[0].sens; + + rc = ebitmap_read(&r->level[0].cat, fp); + if (rc) { + printk(KERN_ERR "security: mls: error reading low " + "categories\n"); + goto out; + } + if (items > 1) { + rc = ebitmap_read(&r->level[1].cat, fp); + if (rc) { + printk(KERN_ERR "security: mls: error reading high " + "categories\n"); + goto bad_high; + } + } else { + rc = ebitmap_cpy(&r->level[1].cat, &r->level[0].cat); + if (rc) { + printk(KERN_ERR "security: mls: out of memory\n"); + goto bad_high; + } + } + + rc = 0; +out: + return rc; +bad_high: + ebitmap_destroy(&r->level[0].cat); + goto out; +} + +/* + * Read and validate a security context structure + * from a policydb binary representation file. + */ +static int context_read_and_validate(struct context *c, + struct policydb *p, + void *fp) +{ + u32 buf[3]; + int rc; + + rc = next_entry(buf, fp, sizeof buf); + if (rc < 0) { + printk(KERN_ERR "security: context truncated\n"); + goto out; + } + c->user = le32_to_cpu(buf[0]); + c->role = le32_to_cpu(buf[1]); + c->type = le32_to_cpu(buf[2]); + if (p->policyvers >= POLICYDB_VERSION_MLS) { + if (mls_read_range_helper(&c->range, fp)) { + printk(KERN_ERR "security: error reading MLS range of " + "context\n"); + rc = -EINVAL; + goto out; + } + } + + if (!policydb_context_isvalid(p, c)) { + printk(KERN_ERR "security: invalid security context\n"); + context_destroy(c); + rc = -EINVAL; + } +out: + return rc; +} + +/* + * The following *_read functions are used to + * read the symbol data from a policy database + * binary representation file. + */ + +static int perm_read(struct policydb *p, struct hashtab *h, void *fp) +{ + char *key = NULL; + struct perm_datum *perdatum; + int rc; + u32 buf[2], len; + + perdatum = kmalloc(sizeof(*perdatum), GFP_KERNEL); + if (!perdatum) { + rc = -ENOMEM; + goto out; + } + memset(perdatum, 0, sizeof(*perdatum)); + + rc = next_entry(buf, fp, sizeof buf); + if (rc < 0) + goto bad; + + len = le32_to_cpu(buf[0]); + perdatum->value = le32_to_cpu(buf[1]); + + key = kmalloc(len + 1,GFP_KERNEL); + if (!key) { + rc = -ENOMEM; + goto bad; + } + rc = next_entry(key, fp, len); + if (rc < 0) + goto bad; + key[len] = 0; + + rc = hashtab_insert(h, key, perdatum); + if (rc) + goto bad; +out: + return rc; +bad: + perm_destroy(key, perdatum, NULL); + goto out; +} + +static int common_read(struct policydb *p, struct hashtab *h, void *fp) +{ + char *key = NULL; + struct common_datum *comdatum; + u32 buf[4], len, nel; + int i, rc; + + comdatum = kmalloc(sizeof(*comdatum), GFP_KERNEL); + if (!comdatum) { + rc = -ENOMEM; + goto out; + } + memset(comdatum, 0, sizeof(*comdatum)); + + rc = next_entry(buf, fp, sizeof buf); + if (rc < 0) + goto bad; + + len = le32_to_cpu(buf[0]); + comdatum->value = le32_to_cpu(buf[1]); + + rc = symtab_init(&comdatum->permissions, PERM_SYMTAB_SIZE); + if (rc) + goto bad; + comdatum->permissions.nprim = le32_to_cpu(buf[2]); + nel = le32_to_cpu(buf[3]); + + key = kmalloc(len + 1,GFP_KERNEL); + if (!key) { + rc = -ENOMEM; + goto bad; + } + rc = next_entry(key, fp, len); + if (rc < 0) + goto bad; + key[len] = 0; + + for (i = 0; i < nel; i++) { + rc = perm_read(p, comdatum->permissions.table, fp); + if (rc) + goto bad; + } + + rc = hashtab_insert(h, key, comdatum); + if (rc) + goto bad; +out: + return rc; +bad: + common_destroy(key, comdatum, NULL); + goto out; +} + +static int read_cons_helper(struct constraint_node **nodep, int ncons, + int allowxtarget, void *fp) +{ + struct constraint_node *c, *lc; + struct constraint_expr *e, *le; + u32 buf[3], nexpr; + int rc, i, j, depth; + + lc = NULL; + for (i = 0; i < ncons; i++) { + c = kmalloc(sizeof(*c), GFP_KERNEL); + if (!c) + return -ENOMEM; + memset(c, 0, sizeof(*c)); + + if (lc) { + lc->next = c; + } else { + *nodep = c; + } + + rc = next_entry(buf, fp, (sizeof(u32) * 2)); + if (rc < 0) + return rc; + c->permissions = le32_to_cpu(buf[0]); + nexpr = le32_to_cpu(buf[1]); + le = NULL; + depth = -1; + for (j = 0; j < nexpr; j++) { + e = kmalloc(sizeof(*e), GFP_KERNEL); + if (!e) + return -ENOMEM; + memset(e, 0, sizeof(*e)); + + if (le) { + le->next = e; + } else { + c->expr = e; + } + + rc = next_entry(buf, fp, (sizeof(u32) * 3)); + if (rc < 0) + return rc; + e->expr_type = le32_to_cpu(buf[0]); + e->attr = le32_to_cpu(buf[1]); + e->op = le32_to_cpu(buf[2]); + + switch (e->expr_type) { + case CEXPR_NOT: + if (depth < 0) + return -EINVAL; + break; + case CEXPR_AND: + case CEXPR_OR: + if (depth < 1) + return -EINVAL; + depth--; + break; + case CEXPR_ATTR: + if (depth == (CEXPR_MAXDEPTH - 1)) + return -EINVAL; + depth++; + break; + case CEXPR_NAMES: + if (!allowxtarget && (e->attr & CEXPR_XTARGET)) + return -EINVAL; + if (depth == (CEXPR_MAXDEPTH - 1)) + return -EINVAL; + depth++; + if (ebitmap_read(&e->names, fp)) + return -EINVAL; + break; + default: + return -EINVAL; + } + le = e; + } + if (depth != 0) + return -EINVAL; + lc = c; + } + + return 0; +} + +static int class_read(struct policydb *p, struct hashtab *h, void *fp) +{ + char *key = NULL; + struct class_datum *cladatum; + u32 buf[6], len, len2, ncons, nel; + int i, rc; + + cladatum = kmalloc(sizeof(*cladatum), GFP_KERNEL); + if (!cladatum) { + rc = -ENOMEM; + goto out; + } + memset(cladatum, 0, sizeof(*cladatum)); + + rc = next_entry(buf, fp, sizeof(u32)*6); + if (rc < 0) + goto bad; + + len = le32_to_cpu(buf[0]); + len2 = le32_to_cpu(buf[1]); + cladatum->value = le32_to_cpu(buf[2]); + + rc = symtab_init(&cladatum->permissions, PERM_SYMTAB_SIZE); + if (rc) + goto bad; + cladatum->permissions.nprim = le32_to_cpu(buf[3]); + nel = le32_to_cpu(buf[4]); + + ncons = le32_to_cpu(buf[5]); + + key = kmalloc(len + 1,GFP_KERNEL); + if (!key) { + rc = -ENOMEM; + goto bad; + } + rc = next_entry(key, fp, len); + if (rc < 0) + goto bad; + key[len] = 0; + + if (len2) { + cladatum->comkey = kmalloc(len2 + 1,GFP_KERNEL); + if (!cladatum->comkey) { + rc = -ENOMEM; + goto bad; + } + rc = next_entry(cladatum->comkey, fp, len2); + if (rc < 0) + goto bad; + cladatum->comkey[len2] = 0; + + cladatum->comdatum = hashtab_search(p->p_commons.table, + cladatum->comkey); + if (!cladatum->comdatum) { + printk(KERN_ERR "security: unknown common %s\n", + cladatum->comkey); + rc = -EINVAL; + goto bad; + } + } + for (i = 0; i < nel; i++) { + rc = perm_read(p, cladatum->permissions.table, fp); + if (rc) + goto bad; + } + + rc = read_cons_helper(&cladatum->constraints, ncons, 0, fp); + if (rc) + goto bad; + + if (p->policyvers >= POLICYDB_VERSION_VALIDATETRANS) { + /* grab the validatetrans rules */ + rc = next_entry(buf, fp, sizeof(u32)); + if (rc < 0) + goto bad; + ncons = le32_to_cpu(buf[0]); + rc = read_cons_helper(&cladatum->validatetrans, ncons, 1, fp); + if (rc) + goto bad; + } + + rc = hashtab_insert(h, key, cladatum); + if (rc) + goto bad; + + rc = 0; +out: + return rc; +bad: + class_destroy(key, cladatum, NULL); + goto out; +} + +static int role_read(struct policydb *p, struct hashtab *h, void *fp) +{ + char *key = NULL; + struct role_datum *role; + int rc; + u32 buf[2], len; + + role = kmalloc(sizeof(*role), GFP_KERNEL); + if (!role) { + rc = -ENOMEM; + goto out; + } + memset(role, 0, sizeof(*role)); + + rc = next_entry(buf, fp, sizeof buf); + if (rc < 0) + goto bad; + + len = le32_to_cpu(buf[0]); + role->value = le32_to_cpu(buf[1]); + + key = kmalloc(len + 1,GFP_KERNEL); + if (!key) { + rc = -ENOMEM; + goto bad; + } + rc = next_entry(key, fp, len); + if (rc < 0) + goto bad; + key[len] = 0; + + rc = ebitmap_read(&role->dominates, fp); + if (rc) + goto bad; + + rc = ebitmap_read(&role->types, fp); + if (rc) + goto bad; + + if (strcmp(key, OBJECT_R) == 0) { + if (role->value != OBJECT_R_VAL) { + printk(KERN_ERR "Role %s has wrong value %d\n", + OBJECT_R, role->value); + rc = -EINVAL; + goto bad; + } + rc = 0; + goto bad; + } + + rc = hashtab_insert(h, key, role); + if (rc) + goto bad; +out: + return rc; +bad: + role_destroy(key, role, NULL); + goto out; +} + +static int type_read(struct policydb *p, struct hashtab *h, void *fp) +{ + char *key = NULL; + struct type_datum *typdatum; + int rc; + u32 buf[3], len; + + typdatum = kmalloc(sizeof(*typdatum),GFP_KERNEL); + if (!typdatum) { + rc = -ENOMEM; + return rc; + } + memset(typdatum, 0, sizeof(*typdatum)); + + rc = next_entry(buf, fp, sizeof buf); + if (rc < 0) + goto bad; + + len = le32_to_cpu(buf[0]); + typdatum->value = le32_to_cpu(buf[1]); + typdatum->primary = le32_to_cpu(buf[2]); + + key = kmalloc(len + 1,GFP_KERNEL); + if (!key) { + rc = -ENOMEM; + goto bad; + } + rc = next_entry(key, fp, len); + if (rc < 0) + goto bad; + key[len] = 0; + + rc = hashtab_insert(h, key, typdatum); + if (rc) + goto bad; +out: + return rc; +bad: + type_destroy(key, typdatum, NULL); + goto out; +} + + +/* + * Read a MLS level structure from a policydb binary + * representation file. + */ +static int mls_read_level(struct mls_level *lp, void *fp) +{ + u32 buf[1]; + int rc; + + memset(lp, 0, sizeof(*lp)); + + rc = next_entry(buf, fp, sizeof buf); + if (rc < 0) { + printk(KERN_ERR "security: mls: truncated level\n"); + goto bad; + } + lp->sens = le32_to_cpu(buf[0]); + + if (ebitmap_read(&lp->cat, fp)) { + printk(KERN_ERR "security: mls: error reading level " + "categories\n"); + goto bad; + } + return 0; + +bad: + return -EINVAL; +} + +static int user_read(struct policydb *p, struct hashtab *h, void *fp) +{ + char *key = NULL; + struct user_datum *usrdatum; + int rc; + u32 buf[2], len; + + usrdatum = kmalloc(sizeof(*usrdatum), GFP_KERNEL); + if (!usrdatum) { + rc = -ENOMEM; + goto out; + } + memset(usrdatum, 0, sizeof(*usrdatum)); + + rc = next_entry(buf, fp, sizeof buf); + if (rc < 0) + goto bad; + + len = le32_to_cpu(buf[0]); + usrdatum->value = le32_to_cpu(buf[1]); + + key = kmalloc(len + 1,GFP_KERNEL); + if (!key) { + rc = -ENOMEM; + goto bad; + } + rc = next_entry(key, fp, len); + if (rc < 0) + goto bad; + key[len] = 0; + + rc = ebitmap_read(&usrdatum->roles, fp); + if (rc) + goto bad; + + if (p->policyvers >= POLICYDB_VERSION_MLS) { + rc = mls_read_range_helper(&usrdatum->range, fp); + if (rc) + goto bad; + rc = mls_read_level(&usrdatum->dfltlevel, fp); + if (rc) + goto bad; + } + + rc = hashtab_insert(h, key, usrdatum); + if (rc) + goto bad; +out: + return rc; +bad: + user_destroy(key, usrdatum, NULL); + goto out; +} + +static int sens_read(struct policydb *p, struct hashtab *h, void *fp) +{ + char *key = NULL; + struct level_datum *levdatum; + int rc; + u32 buf[2], len; + + levdatum = kmalloc(sizeof(*levdatum), GFP_ATOMIC); + if (!levdatum) { + rc = -ENOMEM; + goto out; + } + memset(levdatum, 0, sizeof(*levdatum)); + + rc = next_entry(buf, fp, sizeof buf); + if (rc < 0) + goto bad; + + len = le32_to_cpu(buf[0]); + levdatum->isalias = le32_to_cpu(buf[1]); + + key = kmalloc(len + 1,GFP_ATOMIC); + if (!key) { + rc = -ENOMEM; + goto bad; + } + rc = next_entry(key, fp, len); + if (rc < 0) + goto bad; + key[len] = 0; + + levdatum->level = kmalloc(sizeof(struct mls_level), GFP_ATOMIC); + if (!levdatum->level) { + rc = -ENOMEM; + goto bad; + } + if (mls_read_level(levdatum->level, fp)) { + rc = -EINVAL; + goto bad; + } + + rc = hashtab_insert(h, key, levdatum); + if (rc) + goto bad; +out: + return rc; +bad: + sens_destroy(key, levdatum, NULL); + goto out; +} + +static int cat_read(struct policydb *p, struct hashtab *h, void *fp) +{ + char *key = NULL; + struct cat_datum *catdatum; + int rc; + u32 buf[3], len; + + catdatum = kmalloc(sizeof(*catdatum), GFP_ATOMIC); + if (!catdatum) { + rc = -ENOMEM; + goto out; + } + memset(catdatum, 0, sizeof(*catdatum)); + + rc = next_entry(buf, fp, sizeof buf); + if (rc < 0) + goto bad; + + len = le32_to_cpu(buf[0]); + catdatum->value = le32_to_cpu(buf[1]); + catdatum->isalias = le32_to_cpu(buf[2]); + + key = kmalloc(len + 1,GFP_ATOMIC); + if (!key) { + rc = -ENOMEM; + goto bad; + } + rc = next_entry(key, fp, len); + if (rc < 0) + goto bad; + key[len] = 0; + + rc = hashtab_insert(h, key, catdatum); + if (rc) + goto bad; +out: + return rc; + +bad: + cat_destroy(key, catdatum, NULL); + goto out; +} + +static int (*read_f[SYM_NUM]) (struct policydb *p, struct hashtab *h, void *fp) = +{ + common_read, + class_read, + role_read, + type_read, + user_read, + cond_read_bool, + sens_read, + cat_read, +}; + +extern int ss_initialized; + +/* + * Read the configuration data from a policy database binary + * representation file into a policy database structure. + */ +int policydb_read(struct policydb *p, void *fp) +{ + struct role_allow *ra, *lra; + struct role_trans *tr, *ltr; + struct ocontext *l, *c, *newc; + struct genfs *genfs_p, *genfs, *newgenfs; + int i, j, rc; + u32 buf[8], len, len2, config, nprim, nel, nel2; + char *policydb_str; + struct policydb_compat_info *info; + struct range_trans *rt, *lrt; + + config = 0; + + rc = policydb_init(p); + if (rc) + goto out; + + /* Read the magic number and string length. */ + rc = next_entry(buf, fp, sizeof(u32)* 2); + if (rc < 0) + goto bad; + + for (i = 0; i < 2; i++) + buf[i] = le32_to_cpu(buf[i]); + + if (buf[0] != POLICYDB_MAGIC) { + printk(KERN_ERR "security: policydb magic number 0x%x does " + "not match expected magic number 0x%x\n", + buf[0], POLICYDB_MAGIC); + goto bad; + } + + len = buf[1]; + if (len != strlen(POLICYDB_STRING)) { + printk(KERN_ERR "security: policydb string length %d does not " + "match expected length %Zu\n", + len, strlen(POLICYDB_STRING)); + goto bad; + } + policydb_str = kmalloc(len + 1,GFP_KERNEL); + if (!policydb_str) { + printk(KERN_ERR "security: unable to allocate memory for policydb " + "string of length %d\n", len); + rc = -ENOMEM; + goto bad; + } + rc = next_entry(policydb_str, fp, len); + if (rc < 0) { + printk(KERN_ERR "security: truncated policydb string identifier\n"); + kfree(policydb_str); + goto bad; + } + policydb_str[len] = 0; + if (strcmp(policydb_str, POLICYDB_STRING)) { + printk(KERN_ERR "security: policydb string %s does not match " + "my string %s\n", policydb_str, POLICYDB_STRING); + kfree(policydb_str); + goto bad; + } + /* Done with policydb_str. */ + kfree(policydb_str); + policydb_str = NULL; + + /* Read the version, config, and table sizes. */ + rc = next_entry(buf, fp, sizeof(u32)*4); + if (rc < 0) + goto bad; + for (i = 0; i < 4; i++) + buf[i] = le32_to_cpu(buf[i]); + + p->policyvers = buf[0]; + if (p->policyvers < POLICYDB_VERSION_MIN || + p->policyvers > POLICYDB_VERSION_MAX) { + printk(KERN_ERR "security: policydb version %d does not match " + "my version range %d-%d\n", + buf[0], POLICYDB_VERSION_MIN, POLICYDB_VERSION_MAX); + goto bad; + } + + if ((buf[1] & POLICYDB_CONFIG_MLS)) { + if (ss_initialized && !selinux_mls_enabled) { + printk(KERN_ERR "Cannot switch between non-MLS and MLS " + "policies\n"); + goto bad; + } + selinux_mls_enabled = 1; + config |= POLICYDB_CONFIG_MLS; + + if (p->policyvers < POLICYDB_VERSION_MLS) { + printk(KERN_ERR "security policydb version %d (MLS) " + "not backwards compatible\n", p->policyvers); + goto bad; + } + } else { + if (ss_initialized && selinux_mls_enabled) { + printk(KERN_ERR "Cannot switch between MLS and non-MLS " + "policies\n"); + goto bad; + } + } + + info = policydb_lookup_compat(p->policyvers); + if (!info) { + printk(KERN_ERR "security: unable to find policy compat info " + "for version %d\n", p->policyvers); + goto bad; + } + + if (buf[2] != info->sym_num || buf[3] != info->ocon_num) { + printk(KERN_ERR "security: policydb table sizes (%d,%d) do " + "not match mine (%d,%d)\n", buf[2], buf[3], + info->sym_num, info->ocon_num); + goto bad; + } + + for (i = 0; i < info->sym_num; i++) { + rc = next_entry(buf, fp, sizeof(u32)*2); + if (rc < 0) + goto bad; + nprim = le32_to_cpu(buf[0]); + nel = le32_to_cpu(buf[1]); + for (j = 0; j < nel; j++) { + rc = read_f[i](p, p->symtab[i].table, fp); + if (rc) + goto bad; + } + + p->symtab[i].nprim = nprim; + } + + rc = avtab_read(&p->te_avtab, fp, config); + if (rc) + goto bad; + + if (p->policyvers >= POLICYDB_VERSION_BOOL) { + rc = cond_read_list(p, fp); + if (rc) + goto bad; + } + + rc = next_entry(buf, fp, sizeof(u32)); + if (rc < 0) + goto bad; + nel = le32_to_cpu(buf[0]); + ltr = NULL; + for (i = 0; i < nel; i++) { + tr = kmalloc(sizeof(*tr), GFP_KERNEL); + if (!tr) { + rc = -ENOMEM; + goto bad; + } + memset(tr, 0, sizeof(*tr)); + if (ltr) { + ltr->next = tr; + } else { + p->role_tr = tr; + } + rc = next_entry(buf, fp, sizeof(u32)*3); + if (rc < 0) + goto bad; + tr->role = le32_to_cpu(buf[0]); + tr->type = le32_to_cpu(buf[1]); + tr->new_role = le32_to_cpu(buf[2]); + ltr = tr; + } + + rc = next_entry(buf, fp, sizeof(u32)); + if (rc < 0) + goto bad; + nel = le32_to_cpu(buf[0]); + lra = NULL; + for (i = 0; i < nel; i++) { + ra = kmalloc(sizeof(*ra), GFP_KERNEL); + if (!ra) { + rc = -ENOMEM; + goto bad; + } + memset(ra, 0, sizeof(*ra)); + if (lra) { + lra->next = ra; + } else { + p->role_allow = ra; + } + rc = next_entry(buf, fp, sizeof(u32)*2); + if (rc < 0) + goto bad; + ra->role = le32_to_cpu(buf[0]); + ra->new_role = le32_to_cpu(buf[1]); + lra = ra; + } + + rc = policydb_index_classes(p); + if (rc) + goto bad; + + rc = policydb_index_others(p); + if (rc) + goto bad; + + for (i = 0; i < info->ocon_num; i++) { + rc = next_entry(buf, fp, sizeof(u32)); + if (rc < 0) + goto bad; + nel = le32_to_cpu(buf[0]); + l = NULL; + for (j = 0; j < nel; j++) { + c = kmalloc(sizeof(*c), GFP_KERNEL); + if (!c) { + rc = -ENOMEM; + goto bad; + } + memset(c, 0, sizeof(*c)); + if (l) { + l->next = c; + } else { + p->ocontexts[i] = c; + } + l = c; + rc = -EINVAL; + switch (i) { + case OCON_ISID: + rc = next_entry(buf, fp, sizeof(u32)); + if (rc < 0) + goto bad; + c->sid[0] = le32_to_cpu(buf[0]); + rc = context_read_and_validate(&c->context[0], p, fp); + if (rc) + goto bad; + break; + case OCON_FS: + case OCON_NETIF: + rc = next_entry(buf, fp, sizeof(u32)); + if (rc < 0) + goto bad; + len = le32_to_cpu(buf[0]); + c->u.name = kmalloc(len + 1,GFP_KERNEL); + if (!c->u.name) { + rc = -ENOMEM; + goto bad; + } + rc = next_entry(c->u.name, fp, len); + if (rc < 0) + goto bad; + c->u.name[len] = 0; + rc = context_read_and_validate(&c->context[0], p, fp); + if (rc) + goto bad; + rc = context_read_and_validate(&c->context[1], p, fp); + if (rc) + goto bad; + break; + case OCON_PORT: + rc = next_entry(buf, fp, sizeof(u32)*3); + if (rc < 0) + goto bad; + c->u.port.protocol = le32_to_cpu(buf[0]); + c->u.port.low_port = le32_to_cpu(buf[1]); + c->u.port.high_port = le32_to_cpu(buf[2]); + rc = context_read_and_validate(&c->context[0], p, fp); + if (rc) + goto bad; + break; + case OCON_NODE: + rc = next_entry(buf, fp, sizeof(u32)* 2); + if (rc < 0) + goto bad; + c->u.node.addr = le32_to_cpu(buf[0]); + c->u.node.mask = le32_to_cpu(buf[1]); + rc = context_read_and_validate(&c->context[0], p, fp); + if (rc) + goto bad; + break; + case OCON_FSUSE: + rc = next_entry(buf, fp, sizeof(u32)*2); + if (rc < 0) + goto bad; + c->v.behavior = le32_to_cpu(buf[0]); + if (c->v.behavior > SECURITY_FS_USE_NONE) + goto bad; + len = le32_to_cpu(buf[1]); + c->u.name = kmalloc(len + 1,GFP_KERNEL); + if (!c->u.name) { + rc = -ENOMEM; + goto bad; + } + rc = next_entry(c->u.name, fp, len); + if (rc < 0) + goto bad; + c->u.name[len] = 0; + rc = context_read_and_validate(&c->context[0], p, fp); + if (rc) + goto bad; + break; + case OCON_NODE6: { + int k; + + rc = next_entry(buf, fp, sizeof(u32) * 8); + if (rc < 0) + goto bad; + for (k = 0; k < 4; k++) + c->u.node6.addr[k] = le32_to_cpu(buf[k]); + for (k = 0; k < 4; k++) + c->u.node6.mask[k] = le32_to_cpu(buf[k+4]); + if (context_read_and_validate(&c->context[0], p, fp)) + goto bad; + break; + } + } + } + } + + rc = next_entry(buf, fp, sizeof(u32)); + if (rc < 0) + goto bad; + nel = le32_to_cpu(buf[0]); + genfs_p = NULL; + rc = -EINVAL; + for (i = 0; i < nel; i++) { + rc = next_entry(buf, fp, sizeof(u32)); + if (rc < 0) + goto bad; + len = le32_to_cpu(buf[0]); + newgenfs = kmalloc(sizeof(*newgenfs), GFP_KERNEL); + if (!newgenfs) { + rc = -ENOMEM; + goto bad; + } + memset(newgenfs, 0, sizeof(*newgenfs)); + + newgenfs->fstype = kmalloc(len + 1,GFP_KERNEL); + if (!newgenfs->fstype) { + rc = -ENOMEM; + kfree(newgenfs); + goto bad; + } + rc = next_entry(newgenfs->fstype, fp, len); + if (rc < 0) { + kfree(newgenfs->fstype); + kfree(newgenfs); + goto bad; + } + newgenfs->fstype[len] = 0; + for (genfs_p = NULL, genfs = p->genfs; genfs; + genfs_p = genfs, genfs = genfs->next) { + if (strcmp(newgenfs->fstype, genfs->fstype) == 0) { + printk(KERN_ERR "security: dup genfs " + "fstype %s\n", newgenfs->fstype); + kfree(newgenfs->fstype); + kfree(newgenfs); + goto bad; + } + if (strcmp(newgenfs->fstype, genfs->fstype) < 0) + break; + } + newgenfs->next = genfs; + if (genfs_p) + genfs_p->next = newgenfs; + else + p->genfs = newgenfs; + rc = next_entry(buf, fp, sizeof(u32)); + if (rc < 0) + goto bad; + nel2 = le32_to_cpu(buf[0]); + for (j = 0; j < nel2; j++) { + rc = next_entry(buf, fp, sizeof(u32)); + if (rc < 0) + goto bad; + len = le32_to_cpu(buf[0]); + + newc = kmalloc(sizeof(*newc), GFP_KERNEL); + if (!newc) { + rc = -ENOMEM; + goto bad; + } + memset(newc, 0, sizeof(*newc)); + + newc->u.name = kmalloc(len + 1,GFP_KERNEL); + if (!newc->u.name) { + rc = -ENOMEM; + goto bad_newc; + } + rc = next_entry(newc->u.name, fp, len); + if (rc < 0) + goto bad_newc; + newc->u.name[len] = 0; + rc = next_entry(buf, fp, sizeof(u32)); + if (rc < 0) + goto bad_newc; + newc->v.sclass = le32_to_cpu(buf[0]); + if (context_read_and_validate(&newc->context[0], p, fp)) + goto bad_newc; + for (l = NULL, c = newgenfs->head; c; + l = c, c = c->next) { + if (!strcmp(newc->u.name, c->u.name) && + (!c->v.sclass || !newc->v.sclass || + newc->v.sclass == c->v.sclass)) { + printk(KERN_ERR "security: dup genfs " + "entry (%s,%s)\n", + newgenfs->fstype, c->u.name); + goto bad_newc; + } + len = strlen(newc->u.name); + len2 = strlen(c->u.name); + if (len > len2) + break; + } + + newc->next = c; + if (l) + l->next = newc; + else + newgenfs->head = newc; + } + } + + if (p->policyvers >= POLICYDB_VERSION_MLS) { + rc = next_entry(buf, fp, sizeof(u32)); + if (rc < 0) + goto bad; + nel = le32_to_cpu(buf[0]); + lrt = NULL; + for (i = 0; i < nel; i++) { + rt = kmalloc(sizeof(*rt), GFP_KERNEL); + if (!rt) { + rc = -ENOMEM; + goto bad; + } + memset(rt, 0, sizeof(*rt)); + if (lrt) + lrt->next = rt; + else + p->range_tr = rt; + rc = next_entry(buf, fp, (sizeof(u32) * 2)); + if (rc < 0) + goto bad; + rt->dom = le32_to_cpu(buf[0]); + rt->type = le32_to_cpu(buf[1]); + rc = mls_read_range_helper(&rt->range, fp); + if (rc) + goto bad; + lrt = rt; + } + } + + rc = 0; +out: + return rc; +bad_newc: + ocontext_destroy(newc,OCON_FSUSE); +bad: + if (!rc) + rc = -EINVAL; + policydb_destroy(p); + goto out; +} diff --git a/security/selinux/ss/policydb.h b/security/selinux/ss/policydb.h new file mode 100644 index 00000000000..2470e2a1a1c --- /dev/null +++ b/security/selinux/ss/policydb.h @@ -0,0 +1,275 @@ +/* + * A policy database (policydb) specifies the + * configuration data for the security policy. + * + * Author : Stephen Smalley, <sds@epoch.ncsc.mil> + */ + +/* + * Updated: Trusted Computer Solutions, Inc. <dgoeddel@trustedcs.com> + * + * Support for enhanced MLS infrastructure. + * + * Updated: Frank Mayer <mayerf@tresys.com> and Karl MacMillan <kmacmillan@tresys.com> + * + * Added conditional policy language extensions + * + * Copyright (C) 2004-2005 Trusted Computer Solutions, Inc. + * Copyright (C) 2003 - 2004 Tresys Technology, LLC + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 2. + */ + +#ifndef _SS_POLICYDB_H_ +#define _SS_POLICYDB_H_ + +#include "symtab.h" +#include "avtab.h" +#include "sidtab.h" +#include "context.h" +#include "constraint.h" + +/* + * A datum type is defined for each kind of symbol + * in the configuration data: individual permissions, + * common prefixes for access vectors, classes, + * users, roles, types, sensitivities, categories, etc. + */ + +/* Permission attributes */ +struct perm_datum { + u32 value; /* permission bit + 1 */ +}; + +/* Attributes of a common prefix for access vectors */ +struct common_datum { + u32 value; /* internal common value */ + struct symtab permissions; /* common permissions */ +}; + +/* Class attributes */ +struct class_datum { + u32 value; /* class value */ + char *comkey; /* common name */ + struct common_datum *comdatum; /* common datum */ + struct symtab permissions; /* class-specific permission symbol table */ + struct constraint_node *constraints; /* constraints on class permissions */ + struct constraint_node *validatetrans; /* special transition rules */ +}; + +/* Role attributes */ +struct role_datum { + u32 value; /* internal role value */ + struct ebitmap dominates; /* set of roles dominated by this role */ + struct ebitmap types; /* set of authorized types for role */ +}; + +struct role_trans { + u32 role; /* current role */ + u32 type; /* program executable type */ + u32 new_role; /* new role */ + struct role_trans *next; +}; + +struct role_allow { + u32 role; /* current role */ + u32 new_role; /* new role */ + struct role_allow *next; +}; + +/* Type attributes */ +struct type_datum { + u32 value; /* internal type value */ + unsigned char primary; /* primary name? */ +}; + +/* User attributes */ +struct user_datum { + u32 value; /* internal user value */ + struct ebitmap roles; /* set of authorized roles for user */ + struct mls_range range; /* MLS range (min - max) for user */ + struct mls_level dfltlevel; /* default login MLS level for user */ +}; + + +/* Sensitivity attributes */ +struct level_datum { + struct mls_level *level; /* sensitivity and associated categories */ + unsigned char isalias; /* is this sensitivity an alias for another? */ +}; + +/* Category attributes */ +struct cat_datum { + u32 value; /* internal category bit + 1 */ + unsigned char isalias; /* is this category an alias for another? */ +}; + +struct range_trans { + u32 dom; /* current process domain */ + u32 type; /* program executable type */ + struct mls_range range; /* new range */ + struct range_trans *next; +}; + +/* Boolean data type */ +struct cond_bool_datum { + __u32 value; /* internal type value */ + int state; +}; + +struct cond_node; + +/* + * The configuration data includes security contexts for + * initial SIDs, unlabeled file systems, TCP and UDP port numbers, + * network interfaces, and nodes. This structure stores the + * relevant data for one such entry. Entries of the same kind + * (e.g. all initial SIDs) are linked together into a list. + */ +struct ocontext { + union { + char *name; /* name of initial SID, fs, netif, fstype, path */ + struct { + u8 protocol; + u16 low_port; + u16 high_port; + } port; /* TCP or UDP port information */ + struct { + u32 addr; + u32 mask; + } node; /* node information */ + struct { + u32 addr[4]; + u32 mask[4]; + } node6; /* IPv6 node information */ + } u; + union { + u32 sclass; /* security class for genfs */ + u32 behavior; /* labeling behavior for fs_use */ + } v; + struct context context[2]; /* security context(s) */ + u32 sid[2]; /* SID(s) */ + struct ocontext *next; +}; + +struct genfs { + char *fstype; + struct ocontext *head; + struct genfs *next; +}; + +/* symbol table array indices */ +#define SYM_COMMONS 0 +#define SYM_CLASSES 1 +#define SYM_ROLES 2 +#define SYM_TYPES 3 +#define SYM_USERS 4 +#define SYM_BOOLS 5 +#define SYM_LEVELS 6 +#define SYM_CATS 7 +#define SYM_NUM 8 + +/* object context array indices */ +#define OCON_ISID 0 /* initial SIDs */ +#define OCON_FS 1 /* unlabeled file systems */ +#define OCON_PORT 2 /* TCP and UDP port numbers */ +#define OCON_NETIF 3 /* network interfaces */ +#define OCON_NODE 4 /* nodes */ +#define OCON_FSUSE 5 /* fs_use */ +#define OCON_NODE6 6 /* IPv6 nodes */ +#define OCON_NUM 7 + +/* The policy database */ +struct policydb { + /* symbol tables */ + struct symtab symtab[SYM_NUM]; +#define p_commons symtab[SYM_COMMONS] +#define p_classes symtab[SYM_CLASSES] +#define p_roles symtab[SYM_ROLES] +#define p_types symtab[SYM_TYPES] +#define p_users symtab[SYM_USERS] +#define p_bools symtab[SYM_BOOLS] +#define p_levels symtab[SYM_LEVELS] +#define p_cats symtab[SYM_CATS] + + /* symbol names indexed by (value - 1) */ + char **sym_val_to_name[SYM_NUM]; +#define p_common_val_to_name sym_val_to_name[SYM_COMMONS] +#define p_class_val_to_name sym_val_to_name[SYM_CLASSES] +#define p_role_val_to_name sym_val_to_name[SYM_ROLES] +#define p_type_val_to_name sym_val_to_name[SYM_TYPES] +#define p_user_val_to_name sym_val_to_name[SYM_USERS] +#define p_bool_val_to_name sym_val_to_name[SYM_BOOLS] +#define p_sens_val_to_name sym_val_to_name[SYM_LEVELS] +#define p_cat_val_to_name sym_val_to_name[SYM_CATS] + + /* class, role, and user attributes indexed by (value - 1) */ + struct class_datum **class_val_to_struct; + struct role_datum **role_val_to_struct; + struct user_datum **user_val_to_struct; + + /* type enforcement access vectors and transitions */ + struct avtab te_avtab; + + /* role transitions */ + struct role_trans *role_tr; + + /* bools indexed by (value - 1) */ + struct cond_bool_datum **bool_val_to_struct; + /* type enforcement conditional access vectors and transitions */ + struct avtab te_cond_avtab; + /* linked list indexing te_cond_avtab by conditional */ + struct cond_node* cond_list; + + /* role allows */ + struct role_allow *role_allow; + + /* security contexts of initial SIDs, unlabeled file systems, + TCP or UDP port numbers, network interfaces and nodes */ + struct ocontext *ocontexts[OCON_NUM]; + + /* security contexts for files in filesystems that cannot support + a persistent label mapping or use another + fixed labeling behavior. */ + struct genfs *genfs; + + /* range transitions */ + struct range_trans *range_tr; + + unsigned int policyvers; +}; + +extern void policydb_destroy(struct policydb *p); +extern int policydb_load_isids(struct policydb *p, struct sidtab *s); +extern int policydb_context_isvalid(struct policydb *p, struct context *c); +extern int policydb_read(struct policydb *p, void *fp); + +#define PERM_SYMTAB_SIZE 32 + +#define POLICYDB_CONFIG_MLS 1 + +#define OBJECT_R "object_r" +#define OBJECT_R_VAL 1 + +#define POLICYDB_MAGIC SELINUX_MAGIC +#define POLICYDB_STRING "SE Linux" + +struct policy_file { + char *data; + size_t len; +}; + +static inline int next_entry(void *buf, struct policy_file *fp, size_t bytes) +{ + if (bytes > fp->len) + return -EINVAL; + + memcpy(buf, fp->data, bytes); + fp->data += bytes; + fp->len -= bytes; + return 0; +} + +#endif /* _SS_POLICYDB_H_ */ + diff --git a/security/selinux/ss/services.c b/security/selinux/ss/services.c new file mode 100644 index 00000000000..5a820cf88c9 --- /dev/null +++ b/security/selinux/ss/services.c @@ -0,0 +1,1777 @@ +/* + * Implementation of the security services. + * + * Authors : Stephen Smalley, <sds@epoch.ncsc.mil> + * James Morris <jmorris@redhat.com> + * + * Updated: Trusted Computer Solutions, Inc. <dgoeddel@trustedcs.com> + * + * Support for enhanced MLS infrastructure. + * + * Updated: Frank Mayer <mayerf@tresys.com> and Karl MacMillan <kmacmillan@tresys.com> + * + * Added conditional policy language extensions + * + * Copyright (C) 2004-2005 Trusted Computer Solutions, Inc. + * Copyright (C) 2003 - 2004 Tresys Technology, LLC + * Copyright (C) 2003 Red Hat, Inc., James Morris <jmorris@redhat.com> + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 2. + */ +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/string.h> +#include <linux/spinlock.h> +#include <linux/errno.h> +#include <linux/in.h> +#include <linux/sched.h> +#include <linux/audit.h> +#include <asm/semaphore.h> +#include "flask.h" +#include "avc.h" +#include "avc_ss.h" +#include "security.h" +#include "context.h" +#include "policydb.h" +#include "sidtab.h" +#include "services.h" +#include "conditional.h" +#include "mls.h" + +extern void selnl_notify_policyload(u32 seqno); +unsigned int policydb_loaded_version; + +static DEFINE_RWLOCK(policy_rwlock); +#define POLICY_RDLOCK read_lock(&policy_rwlock) +#define POLICY_WRLOCK write_lock_irq(&policy_rwlock) +#define POLICY_RDUNLOCK read_unlock(&policy_rwlock) +#define POLICY_WRUNLOCK write_unlock_irq(&policy_rwlock) + +static DECLARE_MUTEX(load_sem); +#define LOAD_LOCK down(&load_sem) +#define LOAD_UNLOCK up(&load_sem) + +static struct sidtab sidtab; +struct policydb policydb; +int ss_initialized = 0; + +/* + * The largest sequence number that has been used when + * providing an access decision to the access vector cache. + * The sequence number only changes when a policy change + * occurs. + */ +static u32 latest_granting = 0; + +/* Forward declaration. */ +static int context_struct_to_string(struct context *context, char **scontext, + u32 *scontext_len); + +/* + * Return the boolean value of a constraint expression + * when it is applied to the specified source and target + * security contexts. + * + * xcontext is a special beast... It is used by the validatetrans rules + * only. For these rules, scontext is the context before the transition, + * tcontext is the context after the transition, and xcontext is the context + * of the process performing the transition. All other callers of + * constraint_expr_eval should pass in NULL for xcontext. + */ +static int constraint_expr_eval(struct context *scontext, + struct context *tcontext, + struct context *xcontext, + struct constraint_expr *cexpr) +{ + u32 val1, val2; + struct context *c; + struct role_datum *r1, *r2; + struct mls_level *l1, *l2; + struct constraint_expr *e; + int s[CEXPR_MAXDEPTH]; + int sp = -1; + + for (e = cexpr; e; e = e->next) { + switch (e->expr_type) { + case CEXPR_NOT: + BUG_ON(sp < 0); + s[sp] = !s[sp]; + break; + case CEXPR_AND: + BUG_ON(sp < 1); + sp--; + s[sp] &= s[sp+1]; + break; + case CEXPR_OR: + BUG_ON(sp < 1); + sp--; + s[sp] |= s[sp+1]; + break; + case CEXPR_ATTR: + if (sp == (CEXPR_MAXDEPTH-1)) + return 0; + switch (e->attr) { + case CEXPR_USER: + val1 = scontext->user; + val2 = tcontext->user; + break; + case CEXPR_TYPE: + val1 = scontext->type; + val2 = tcontext->type; + break; + case CEXPR_ROLE: + val1 = scontext->role; + val2 = tcontext->role; + r1 = policydb.role_val_to_struct[val1 - 1]; + r2 = policydb.role_val_to_struct[val2 - 1]; + switch (e->op) { + case CEXPR_DOM: + s[++sp] = ebitmap_get_bit(&r1->dominates, + val2 - 1); + continue; + case CEXPR_DOMBY: + s[++sp] = ebitmap_get_bit(&r2->dominates, + val1 - 1); + continue; + case CEXPR_INCOMP: + s[++sp] = ( !ebitmap_get_bit(&r1->dominates, + val2 - 1) && + !ebitmap_get_bit(&r2->dominates, + val1 - 1) ); + continue; + default: + break; + } + break; + case CEXPR_L1L2: + l1 = &(scontext->range.level[0]); + l2 = &(tcontext->range.level[0]); + goto mls_ops; + case CEXPR_L1H2: + l1 = &(scontext->range.level[0]); + l2 = &(tcontext->range.level[1]); + goto mls_ops; + case CEXPR_H1L2: + l1 = &(scontext->range.level[1]); + l2 = &(tcontext->range.level[0]); + goto mls_ops; + case CEXPR_H1H2: + l1 = &(scontext->range.level[1]); + l2 = &(tcontext->range.level[1]); + goto mls_ops; + case CEXPR_L1H1: + l1 = &(scontext->range.level[0]); + l2 = &(scontext->range.level[1]); + goto mls_ops; + case CEXPR_L2H2: + l1 = &(tcontext->range.level[0]); + l2 = &(tcontext->range.level[1]); + goto mls_ops; +mls_ops: + switch (e->op) { + case CEXPR_EQ: + s[++sp] = mls_level_eq(l1, l2); + continue; + case CEXPR_NEQ: + s[++sp] = !mls_level_eq(l1, l2); + continue; + case CEXPR_DOM: + s[++sp] = mls_level_dom(l1, l2); + continue; + case CEXPR_DOMBY: + s[++sp] = mls_level_dom(l2, l1); + continue; + case CEXPR_INCOMP: + s[++sp] = mls_level_incomp(l2, l1); + continue; + default: + BUG(); + return 0; + } + break; + default: + BUG(); + return 0; + } + + switch (e->op) { + case CEXPR_EQ: + s[++sp] = (val1 == val2); + break; + case CEXPR_NEQ: + s[++sp] = (val1 != val2); + break; + default: + BUG(); + return 0; + } + break; + case CEXPR_NAMES: + if (sp == (CEXPR_MAXDEPTH-1)) + return 0; + c = scontext; + if (e->attr & CEXPR_TARGET) + c = tcontext; + else if (e->attr & CEXPR_XTARGET) { + c = xcontext; + if (!c) { + BUG(); + return 0; + } + } + if (e->attr & CEXPR_USER) + val1 = c->user; + else if (e->attr & CEXPR_ROLE) + val1 = c->role; + else if (e->attr & CEXPR_TYPE) + val1 = c->type; + else { + BUG(); + return 0; + } + + switch (e->op) { + case CEXPR_EQ: + s[++sp] = ebitmap_get_bit(&e->names, val1 - 1); + break; + case CEXPR_NEQ: + s[++sp] = !ebitmap_get_bit(&e->names, val1 - 1); + break; + default: + BUG(); + return 0; + } + break; + default: + BUG(); + return 0; + } + } + + BUG_ON(sp != 0); + return s[0]; +} + +/* + * Compute access vectors based on a context structure pair for + * the permissions in a particular class. + */ +static int context_struct_compute_av(struct context *scontext, + struct context *tcontext, + u16 tclass, + u32 requested, + struct av_decision *avd) +{ + struct constraint_node *constraint; + struct role_allow *ra; + struct avtab_key avkey; + struct avtab_datum *avdatum; + struct class_datum *tclass_datum; + + /* + * Remap extended Netlink classes for old policy versions. + * Do this here rather than socket_type_to_security_class() + * in case a newer policy version is loaded, allowing sockets + * to remain in the correct class. + */ + if (policydb_loaded_version < POLICYDB_VERSION_NLCLASS) + if (tclass >= SECCLASS_NETLINK_ROUTE_SOCKET && + tclass <= SECCLASS_NETLINK_DNRT_SOCKET) + tclass = SECCLASS_NETLINK_SOCKET; + + if (!tclass || tclass > policydb.p_classes.nprim) { + printk(KERN_ERR "security_compute_av: unrecognized class %d\n", + tclass); + return -EINVAL; + } + tclass_datum = policydb.class_val_to_struct[tclass - 1]; + + /* + * Initialize the access vectors to the default values. + */ + avd->allowed = 0; + avd->decided = 0xffffffff; + avd->auditallow = 0; + avd->auditdeny = 0xffffffff; + avd->seqno = latest_granting; + + /* + * If a specific type enforcement rule was defined for + * this permission check, then use it. + */ + avkey.source_type = scontext->type; + avkey.target_type = tcontext->type; + avkey.target_class = tclass; + avdatum = avtab_search(&policydb.te_avtab, &avkey, AVTAB_AV); + if (avdatum) { + if (avdatum->specified & AVTAB_ALLOWED) + avd->allowed = avtab_allowed(avdatum); + if (avdatum->specified & AVTAB_AUDITDENY) + avd->auditdeny = avtab_auditdeny(avdatum); + if (avdatum->specified & AVTAB_AUDITALLOW) + avd->auditallow = avtab_auditallow(avdatum); + } + + /* Check conditional av table for additional permissions */ + cond_compute_av(&policydb.te_cond_avtab, &avkey, avd); + + /* + * Remove any permissions prohibited by a constraint (this includes + * the MLS policy). + */ + constraint = tclass_datum->constraints; + while (constraint) { + if ((constraint->permissions & (avd->allowed)) && + !constraint_expr_eval(scontext, tcontext, NULL, + constraint->expr)) { + avd->allowed = (avd->allowed) & ~(constraint->permissions); + } + constraint = constraint->next; + } + + /* + * If checking process transition permission and the + * role is changing, then check the (current_role, new_role) + * pair. + */ + if (tclass == SECCLASS_PROCESS && + (avd->allowed & (PROCESS__TRANSITION | PROCESS__DYNTRANSITION)) && + scontext->role != tcontext->role) { + for (ra = policydb.role_allow; ra; ra = ra->next) { + if (scontext->role == ra->role && + tcontext->role == ra->new_role) + break; + } + if (!ra) + avd->allowed = (avd->allowed) & ~(PROCESS__TRANSITION | + PROCESS__DYNTRANSITION); + } + + return 0; +} + +static int security_validtrans_handle_fail(struct context *ocontext, + struct context *ncontext, + struct context *tcontext, + u16 tclass) +{ + char *o = NULL, *n = NULL, *t = NULL; + u32 olen, nlen, tlen; + + if (context_struct_to_string(ocontext, &o, &olen) < 0) + goto out; + if (context_struct_to_string(ncontext, &n, &nlen) < 0) + goto out; + if (context_struct_to_string(tcontext, &t, &tlen) < 0) + goto out; + audit_log(current->audit_context, + "security_validate_transition: denied for" + " oldcontext=%s newcontext=%s taskcontext=%s tclass=%s", + o, n, t, policydb.p_class_val_to_name[tclass-1]); +out: + kfree(o); + kfree(n); + kfree(t); + + if (!selinux_enforcing) + return 0; + return -EPERM; +} + +int security_validate_transition(u32 oldsid, u32 newsid, u32 tasksid, + u16 tclass) +{ + struct context *ocontext; + struct context *ncontext; + struct context *tcontext; + struct class_datum *tclass_datum; + struct constraint_node *constraint; + int rc = 0; + + if (!ss_initialized) + return 0; + + POLICY_RDLOCK; + + /* + * Remap extended Netlink classes for old policy versions. + * Do this here rather than socket_type_to_security_class() + * in case a newer policy version is loaded, allowing sockets + * to remain in the correct class. + */ + if (policydb_loaded_version < POLICYDB_VERSION_NLCLASS) + if (tclass >= SECCLASS_NETLINK_ROUTE_SOCKET && + tclass <= SECCLASS_NETLINK_DNRT_SOCKET) + tclass = SECCLASS_NETLINK_SOCKET; + + if (!tclass || tclass > policydb.p_classes.nprim) { + printk(KERN_ERR "security_validate_transition: " + "unrecognized class %d\n", tclass); + rc = -EINVAL; + goto out; + } + tclass_datum = policydb.class_val_to_struct[tclass - 1]; + + ocontext = sidtab_search(&sidtab, oldsid); + if (!ocontext) { + printk(KERN_ERR "security_validate_transition: " + " unrecognized SID %d\n", oldsid); + rc = -EINVAL; + goto out; + } + + ncontext = sidtab_search(&sidtab, newsid); + if (!ncontext) { + printk(KERN_ERR "security_validate_transition: " + " unrecognized SID %d\n", newsid); + rc = -EINVAL; + goto out; + } + + tcontext = sidtab_search(&sidtab, tasksid); + if (!tcontext) { + printk(KERN_ERR "security_validate_transition: " + " unrecognized SID %d\n", tasksid); + rc = -EINVAL; + goto out; + } + + constraint = tclass_datum->validatetrans; + while (constraint) { + if (!constraint_expr_eval(ocontext, ncontext, tcontext, + constraint->expr)) { + rc = security_validtrans_handle_fail(ocontext, ncontext, + tcontext, tclass); + goto out; + } + constraint = constraint->next; + } + +out: + POLICY_RDUNLOCK; + return rc; +} + +/** + * security_compute_av - Compute access vector decisions. + * @ssid: source security identifier + * @tsid: target security identifier + * @tclass: target security class + * @requested: requested permissions + * @avd: access vector decisions + * + * Compute a set of access vector decisions based on the + * SID pair (@ssid, @tsid) for the permissions in @tclass. + * Return -%EINVAL if any of the parameters are invalid or %0 + * if the access vector decisions were computed successfully. + */ +int security_compute_av(u32 ssid, + u32 tsid, + u16 tclass, + u32 requested, + struct av_decision *avd) +{ + struct context *scontext = NULL, *tcontext = NULL; + int rc = 0; + + if (!ss_initialized) { + avd->allowed = requested; + avd->decided = requested; + avd->auditallow = 0; + avd->auditdeny = 0xffffffff; + avd->seqno = latest_granting; + return 0; + } + + POLICY_RDLOCK; + + scontext = sidtab_search(&sidtab, ssid); + if (!scontext) { + printk(KERN_ERR "security_compute_av: unrecognized SID %d\n", + ssid); + rc = -EINVAL; + goto out; + } + tcontext = sidtab_search(&sidtab, tsid); + if (!tcontext) { + printk(KERN_ERR "security_compute_av: unrecognized SID %d\n", + tsid); + rc = -EINVAL; + goto out; + } + + rc = context_struct_compute_av(scontext, tcontext, tclass, + requested, avd); +out: + POLICY_RDUNLOCK; + return rc; +} + +/* + * Write the security context string representation of + * the context structure `context' into a dynamically + * allocated string of the correct size. Set `*scontext' + * to point to this string and set `*scontext_len' to + * the length of the string. + */ +static int context_struct_to_string(struct context *context, char **scontext, u32 *scontext_len) +{ + char *scontextp; + + *scontext = NULL; + *scontext_len = 0; + + /* Compute the size of the context. */ + *scontext_len += strlen(policydb.p_user_val_to_name[context->user - 1]) + 1; + *scontext_len += strlen(policydb.p_role_val_to_name[context->role - 1]) + 1; + *scontext_len += strlen(policydb.p_type_val_to_name[context->type - 1]) + 1; + *scontext_len += mls_compute_context_len(context); + + /* Allocate space for the context; caller must free this space. */ + scontextp = kmalloc(*scontext_len, GFP_ATOMIC); + if (!scontextp) { + return -ENOMEM; + } + *scontext = scontextp; + + /* + * Copy the user name, role name and type name into the context. + */ + sprintf(scontextp, "%s:%s:%s", + policydb.p_user_val_to_name[context->user - 1], + policydb.p_role_val_to_name[context->role - 1], + policydb.p_type_val_to_name[context->type - 1]); + scontextp += strlen(policydb.p_user_val_to_name[context->user - 1]) + + 1 + strlen(policydb.p_role_val_to_name[context->role - 1]) + + 1 + strlen(policydb.p_type_val_to_name[context->type - 1]); + + mls_sid_to_context(context, &scontextp); + + *scontextp = 0; + + return 0; +} + +#include "initial_sid_to_string.h" + +/** + * security_sid_to_context - Obtain a context for a given SID. + * @sid: security identifier, SID + * @scontext: security context + * @scontext_len: length in bytes + * + * Write the string representation of the context associated with @sid + * into a dynamically allocated string of the correct size. Set @scontext + * to point to this string and set @scontext_len to the length of the string. + */ +int security_sid_to_context(u32 sid, char **scontext, u32 *scontext_len) +{ + struct context *context; + int rc = 0; + + if (!ss_initialized) { + if (sid <= SECINITSID_NUM) { + char *scontextp; + + *scontext_len = strlen(initial_sid_to_string[sid]) + 1; + scontextp = kmalloc(*scontext_len,GFP_ATOMIC); + strcpy(scontextp, initial_sid_to_string[sid]); + *scontext = scontextp; + goto out; + } + printk(KERN_ERR "security_sid_to_context: called before initial " + "load_policy on unknown SID %d\n", sid); + rc = -EINVAL; + goto out; + } + POLICY_RDLOCK; + context = sidtab_search(&sidtab, sid); + if (!context) { + printk(KERN_ERR "security_sid_to_context: unrecognized SID " + "%d\n", sid); + rc = -EINVAL; + goto out_unlock; + } + rc = context_struct_to_string(context, scontext, scontext_len); +out_unlock: + POLICY_RDUNLOCK; +out: + return rc; + +} + +/** + * security_context_to_sid - Obtain a SID for a given security context. + * @scontext: security context + * @scontext_len: length in bytes + * @sid: security identifier, SID + * + * Obtains a SID associated with the security context that + * has the string representation specified by @scontext. + * Returns -%EINVAL if the context is invalid, -%ENOMEM if insufficient + * memory is available, or 0 on success. + */ +int security_context_to_sid(char *scontext, u32 scontext_len, u32 *sid) +{ + char *scontext2; + struct context context; + struct role_datum *role; + struct type_datum *typdatum; + struct user_datum *usrdatum; + char *scontextp, *p, oldc; + int rc = 0; + + if (!ss_initialized) { + int i; + + for (i = 1; i < SECINITSID_NUM; i++) { + if (!strcmp(initial_sid_to_string[i], scontext)) { + *sid = i; + goto out; + } + } + *sid = SECINITSID_KERNEL; + goto out; + } + *sid = SECSID_NULL; + + /* Copy the string so that we can modify the copy as we parse it. + The string should already by null terminated, but we append a + null suffix to the copy to avoid problems with the existing + attr package, which doesn't view the null terminator as part + of the attribute value. */ + scontext2 = kmalloc(scontext_len+1,GFP_KERNEL); + if (!scontext2) { + rc = -ENOMEM; + goto out; + } + memcpy(scontext2, scontext, scontext_len); + scontext2[scontext_len] = 0; + + context_init(&context); + *sid = SECSID_NULL; + + POLICY_RDLOCK; + + /* Parse the security context. */ + + rc = -EINVAL; + scontextp = (char *) scontext2; + + /* Extract the user. */ + p = scontextp; + while (*p && *p != ':') + p++; + + if (*p == 0) + goto out_unlock; + + *p++ = 0; + + usrdatum = hashtab_search(policydb.p_users.table, scontextp); + if (!usrdatum) + goto out_unlock; + + context.user = usrdatum->value; + + /* Extract role. */ + scontextp = p; + while (*p && *p != ':') + p++; + + if (*p == 0) + goto out_unlock; + + *p++ = 0; + + role = hashtab_search(policydb.p_roles.table, scontextp); + if (!role) + goto out_unlock; + context.role = role->value; + + /* Extract type. */ + scontextp = p; + while (*p && *p != ':') + p++; + oldc = *p; + *p++ = 0; + + typdatum = hashtab_search(policydb.p_types.table, scontextp); + if (!typdatum) + goto out_unlock; + + context.type = typdatum->value; + + rc = mls_context_to_sid(oldc, &p, &context); + if (rc) + goto out_unlock; + + if ((p - scontext2) < scontext_len) { + rc = -EINVAL; + goto out_unlock; + } + + /* Check the validity of the new context. */ + if (!policydb_context_isvalid(&policydb, &context)) { + rc = -EINVAL; + goto out_unlock; + } + /* Obtain the new sid. */ + rc = sidtab_context_to_sid(&sidtab, &context, sid); +out_unlock: + POLICY_RDUNLOCK; + context_destroy(&context); + kfree(scontext2); +out: + return rc; +} + +static int compute_sid_handle_invalid_context( + struct context *scontext, + struct context *tcontext, + u16 tclass, + struct context *newcontext) +{ + char *s = NULL, *t = NULL, *n = NULL; + u32 slen, tlen, nlen; + + if (context_struct_to_string(scontext, &s, &slen) < 0) + goto out; + if (context_struct_to_string(tcontext, &t, &tlen) < 0) + goto out; + if (context_struct_to_string(newcontext, &n, &nlen) < 0) + goto out; + audit_log(current->audit_context, + "security_compute_sid: invalid context %s" + " for scontext=%s" + " tcontext=%s" + " tclass=%s", + n, s, t, policydb.p_class_val_to_name[tclass-1]); +out: + kfree(s); + kfree(t); + kfree(n); + if (!selinux_enforcing) + return 0; + return -EACCES; +} + +static int security_compute_sid(u32 ssid, + u32 tsid, + u16 tclass, + u32 specified, + u32 *out_sid) +{ + struct context *scontext = NULL, *tcontext = NULL, newcontext; + struct role_trans *roletr = NULL; + struct avtab_key avkey; + struct avtab_datum *avdatum; + struct avtab_node *node; + unsigned int type_change = 0; + int rc = 0; + + if (!ss_initialized) { + switch (tclass) { + case SECCLASS_PROCESS: + *out_sid = ssid; + break; + default: + *out_sid = tsid; + break; + } + goto out; + } + + POLICY_RDLOCK; + + scontext = sidtab_search(&sidtab, ssid); + if (!scontext) { + printk(KERN_ERR "security_compute_sid: unrecognized SID %d\n", + ssid); + rc = -EINVAL; + goto out_unlock; + } + tcontext = sidtab_search(&sidtab, tsid); + if (!tcontext) { + printk(KERN_ERR "security_compute_sid: unrecognized SID %d\n", + tsid); + rc = -EINVAL; + goto out_unlock; + } + + context_init(&newcontext); + + /* Set the user identity. */ + switch (specified) { + case AVTAB_TRANSITION: + case AVTAB_CHANGE: + /* Use the process user identity. */ + newcontext.user = scontext->user; + break; + case AVTAB_MEMBER: + /* Use the related object owner. */ + newcontext.user = tcontext->user; + break; + } + + /* Set the role and type to default values. */ + switch (tclass) { + case SECCLASS_PROCESS: + /* Use the current role and type of process. */ + newcontext.role = scontext->role; + newcontext.type = scontext->type; + break; + default: + /* Use the well-defined object role. */ + newcontext.role = OBJECT_R_VAL; + /* Use the type of the related object. */ + newcontext.type = tcontext->type; + } + + /* Look for a type transition/member/change rule. */ + avkey.source_type = scontext->type; + avkey.target_type = tcontext->type; + avkey.target_class = tclass; + avdatum = avtab_search(&policydb.te_avtab, &avkey, AVTAB_TYPE); + + /* If no permanent rule, also check for enabled conditional rules */ + if(!avdatum) { + node = avtab_search_node(&policydb.te_cond_avtab, &avkey, specified); + for (; node != NULL; node = avtab_search_node_next(node, specified)) { + if (node->datum.specified & AVTAB_ENABLED) { + avdatum = &node->datum; + break; + } + } + } + + type_change = (avdatum && (avdatum->specified & specified)); + if (type_change) { + /* Use the type from the type transition/member/change rule. */ + switch (specified) { + case AVTAB_TRANSITION: + newcontext.type = avtab_transition(avdatum); + break; + case AVTAB_MEMBER: + newcontext.type = avtab_member(avdatum); + break; + case AVTAB_CHANGE: + newcontext.type = avtab_change(avdatum); + break; + } + } + + /* Check for class-specific changes. */ + switch (tclass) { + case SECCLASS_PROCESS: + if (specified & AVTAB_TRANSITION) { + /* Look for a role transition rule. */ + for (roletr = policydb.role_tr; roletr; + roletr = roletr->next) { + if (roletr->role == scontext->role && + roletr->type == tcontext->type) { + /* Use the role transition rule. */ + newcontext.role = roletr->new_role; + break; + } + } + } + break; + default: + break; + } + + /* Set the MLS attributes. + This is done last because it may allocate memory. */ + rc = mls_compute_sid(scontext, tcontext, tclass, specified, &newcontext); + if (rc) + goto out_unlock; + + /* Check the validity of the context. */ + if (!policydb_context_isvalid(&policydb, &newcontext)) { + rc = compute_sid_handle_invalid_context(scontext, + tcontext, + tclass, + &newcontext); + if (rc) + goto out_unlock; + } + /* Obtain the sid for the context. */ + rc = sidtab_context_to_sid(&sidtab, &newcontext, out_sid); +out_unlock: + POLICY_RDUNLOCK; + context_destroy(&newcontext); +out: + return rc; +} + +/** + * security_transition_sid - Compute the SID for a new subject/object. + * @ssid: source security identifier + * @tsid: target security identifier + * @tclass: target security class + * @out_sid: security identifier for new subject/object + * + * Compute a SID to use for labeling a new subject or object in the + * class @tclass based on a SID pair (@ssid, @tsid). + * Return -%EINVAL if any of the parameters are invalid, -%ENOMEM + * if insufficient memory is available, or %0 if the new SID was + * computed successfully. + */ +int security_transition_sid(u32 ssid, + u32 tsid, + u16 tclass, + u32 *out_sid) +{ + return security_compute_sid(ssid, tsid, tclass, AVTAB_TRANSITION, out_sid); +} + +/** + * security_member_sid - Compute the SID for member selection. + * @ssid: source security identifier + * @tsid: target security identifier + * @tclass: target security class + * @out_sid: security identifier for selected member + * + * Compute a SID to use when selecting a member of a polyinstantiated + * object of class @tclass based on a SID pair (@ssid, @tsid). + * Return -%EINVAL if any of the parameters are invalid, -%ENOMEM + * if insufficient memory is available, or %0 if the SID was + * computed successfully. + */ +int security_member_sid(u32 ssid, + u32 tsid, + u16 tclass, + u32 *out_sid) +{ + return security_compute_sid(ssid, tsid, tclass, AVTAB_MEMBER, out_sid); +} + +/** + * security_change_sid - Compute the SID for object relabeling. + * @ssid: source security identifier + * @tsid: target security identifier + * @tclass: target security class + * @out_sid: security identifier for selected member + * + * Compute a SID to use for relabeling an object of class @tclass + * based on a SID pair (@ssid, @tsid). + * Return -%EINVAL if any of the parameters are invalid, -%ENOMEM + * if insufficient memory is available, or %0 if the SID was + * computed successfully. + */ +int security_change_sid(u32 ssid, + u32 tsid, + u16 tclass, + u32 *out_sid) +{ + return security_compute_sid(ssid, tsid, tclass, AVTAB_CHANGE, out_sid); +} + +/* + * Verify that each permission that is defined under the + * existing policy is still defined with the same value + * in the new policy. + */ +static int validate_perm(void *key, void *datum, void *p) +{ + struct hashtab *h; + struct perm_datum *perdatum, *perdatum2; + int rc = 0; + + + h = p; + perdatum = datum; + + perdatum2 = hashtab_search(h, key); + if (!perdatum2) { + printk(KERN_ERR "security: permission %s disappeared", + (char *)key); + rc = -ENOENT; + goto out; + } + if (perdatum->value != perdatum2->value) { + printk(KERN_ERR "security: the value of permission %s changed", + (char *)key); + rc = -EINVAL; + } +out: + return rc; +} + +/* + * Verify that each class that is defined under the + * existing policy is still defined with the same + * attributes in the new policy. + */ +static int validate_class(void *key, void *datum, void *p) +{ + struct policydb *newp; + struct class_datum *cladatum, *cladatum2; + int rc; + + newp = p; + cladatum = datum; + + cladatum2 = hashtab_search(newp->p_classes.table, key); + if (!cladatum2) { + printk(KERN_ERR "security: class %s disappeared\n", + (char *)key); + rc = -ENOENT; + goto out; + } + if (cladatum->value != cladatum2->value) { + printk(KERN_ERR "security: the value of class %s changed\n", + (char *)key); + rc = -EINVAL; + goto out; + } + if ((cladatum->comdatum && !cladatum2->comdatum) || + (!cladatum->comdatum && cladatum2->comdatum)) { + printk(KERN_ERR "security: the inherits clause for the access " + "vector definition for class %s changed\n", (char *)key); + rc = -EINVAL; + goto out; + } + if (cladatum->comdatum) { + rc = hashtab_map(cladatum->comdatum->permissions.table, validate_perm, + cladatum2->comdatum->permissions.table); + if (rc) { + printk(" in the access vector definition for class " + "%s\n", (char *)key); + goto out; + } + } + rc = hashtab_map(cladatum->permissions.table, validate_perm, + cladatum2->permissions.table); + if (rc) + printk(" in access vector definition for class %s\n", + (char *)key); +out: + return rc; +} + +/* Clone the SID into the new SID table. */ +static int clone_sid(u32 sid, + struct context *context, + void *arg) +{ + struct sidtab *s = arg; + + return sidtab_insert(s, sid, context); +} + +static inline int convert_context_handle_invalid_context(struct context *context) +{ + int rc = 0; + + if (selinux_enforcing) { + rc = -EINVAL; + } else { + char *s; + u32 len; + + context_struct_to_string(context, &s, &len); + printk(KERN_ERR "security: context %s is invalid\n", s); + kfree(s); + } + return rc; +} + +struct convert_context_args { + struct policydb *oldp; + struct policydb *newp; +}; + +/* + * Convert the values in the security context + * structure `c' from the values specified + * in the policy `p->oldp' to the values specified + * in the policy `p->newp'. Verify that the + * context is valid under the new policy. + */ +static int convert_context(u32 key, + struct context *c, + void *p) +{ + struct convert_context_args *args; + struct context oldc; + struct role_datum *role; + struct type_datum *typdatum; + struct user_datum *usrdatum; + char *s; + u32 len; + int rc; + + args = p; + + rc = context_cpy(&oldc, c); + if (rc) + goto out; + + rc = -EINVAL; + + /* Convert the user. */ + usrdatum = hashtab_search(args->newp->p_users.table, + args->oldp->p_user_val_to_name[c->user - 1]); + if (!usrdatum) { + goto bad; + } + c->user = usrdatum->value; + + /* Convert the role. */ + role = hashtab_search(args->newp->p_roles.table, + args->oldp->p_role_val_to_name[c->role - 1]); + if (!role) { + goto bad; + } + c->role = role->value; + + /* Convert the type. */ + typdatum = hashtab_search(args->newp->p_types.table, + args->oldp->p_type_val_to_name[c->type - 1]); + if (!typdatum) { + goto bad; + } + c->type = typdatum->value; + + rc = mls_convert_context(args->oldp, args->newp, c); + if (rc) + goto bad; + + /* Check the validity of the new context. */ + if (!policydb_context_isvalid(args->newp, c)) { + rc = convert_context_handle_invalid_context(&oldc); + if (rc) + goto bad; + } + + context_destroy(&oldc); +out: + return rc; +bad: + context_struct_to_string(&oldc, &s, &len); + context_destroy(&oldc); + printk(KERN_ERR "security: invalidating context %s\n", s); + kfree(s); + goto out; +} + +extern void selinux_complete_init(void); + +/** + * security_load_policy - Load a security policy configuration. + * @data: binary policy data + * @len: length of data in bytes + * + * Load a new set of security policy configuration data, + * validate it and convert the SID table as necessary. + * This function will flush the access vector cache after + * loading the new policy. + */ +int security_load_policy(void *data, size_t len) +{ + struct policydb oldpolicydb, newpolicydb; + struct sidtab oldsidtab, newsidtab; + struct convert_context_args args; + u32 seqno; + int rc = 0; + struct policy_file file = { data, len }, *fp = &file; + + LOAD_LOCK; + + if (!ss_initialized) { + avtab_cache_init(); + if (policydb_read(&policydb, fp)) { + LOAD_UNLOCK; + avtab_cache_destroy(); + return -EINVAL; + } + if (policydb_load_isids(&policydb, &sidtab)) { + LOAD_UNLOCK; + policydb_destroy(&policydb); + avtab_cache_destroy(); + return -EINVAL; + } + policydb_loaded_version = policydb.policyvers; + ss_initialized = 1; + + LOAD_UNLOCK; + selinux_complete_init(); + return 0; + } + +#if 0 + sidtab_hash_eval(&sidtab, "sids"); +#endif + + if (policydb_read(&newpolicydb, fp)) { + LOAD_UNLOCK; + return -EINVAL; + } + + sidtab_init(&newsidtab); + + /* Verify that the existing classes did not change. */ + if (hashtab_map(policydb.p_classes.table, validate_class, &newpolicydb)) { + printk(KERN_ERR "security: the definition of an existing " + "class changed\n"); + rc = -EINVAL; + goto err; + } + + /* Clone the SID table. */ + sidtab_shutdown(&sidtab); + if (sidtab_map(&sidtab, clone_sid, &newsidtab)) { + rc = -ENOMEM; + goto err; + } + + /* Convert the internal representations of contexts + in the new SID table and remove invalid SIDs. */ + args.oldp = &policydb; + args.newp = &newpolicydb; + sidtab_map_remove_on_error(&newsidtab, convert_context, &args); + + /* Save the old policydb and SID table to free later. */ + memcpy(&oldpolicydb, &policydb, sizeof policydb); + sidtab_set(&oldsidtab, &sidtab); + + /* Install the new policydb and SID table. */ + POLICY_WRLOCK; + memcpy(&policydb, &newpolicydb, sizeof policydb); + sidtab_set(&sidtab, &newsidtab); + seqno = ++latest_granting; + policydb_loaded_version = policydb.policyvers; + POLICY_WRUNLOCK; + LOAD_UNLOCK; + + /* Free the old policydb and SID table. */ + policydb_destroy(&oldpolicydb); + sidtab_destroy(&oldsidtab); + + avc_ss_reset(seqno); + selnl_notify_policyload(seqno); + + return 0; + +err: + LOAD_UNLOCK; + sidtab_destroy(&newsidtab); + policydb_destroy(&newpolicydb); + return rc; + +} + +/** + * security_port_sid - Obtain the SID for a port. + * @domain: communication domain aka address family + * @type: socket type + * @protocol: protocol number + * @port: port number + * @out_sid: security identifier + */ +int security_port_sid(u16 domain, + u16 type, + u8 protocol, + u16 port, + u32 *out_sid) +{ + struct ocontext *c; + int rc = 0; + + POLICY_RDLOCK; + + c = policydb.ocontexts[OCON_PORT]; + while (c) { + if (c->u.port.protocol == protocol && + c->u.port.low_port <= port && + c->u.port.high_port >= port) + break; + c = c->next; + } + + if (c) { + if (!c->sid[0]) { + rc = sidtab_context_to_sid(&sidtab, + &c->context[0], + &c->sid[0]); + if (rc) + goto out; + } + *out_sid = c->sid[0]; + } else { + *out_sid = SECINITSID_PORT; + } + +out: + POLICY_RDUNLOCK; + return rc; +} + +/** + * security_netif_sid - Obtain the SID for a network interface. + * @name: interface name + * @if_sid: interface SID + * @msg_sid: default SID for received packets + */ +int security_netif_sid(char *name, + u32 *if_sid, + u32 *msg_sid) +{ + int rc = 0; + struct ocontext *c; + + POLICY_RDLOCK; + + c = policydb.ocontexts[OCON_NETIF]; + while (c) { + if (strcmp(name, c->u.name) == 0) + break; + c = c->next; + } + + if (c) { + if (!c->sid[0] || !c->sid[1]) { + rc = sidtab_context_to_sid(&sidtab, + &c->context[0], + &c->sid[0]); + if (rc) + goto out; + rc = sidtab_context_to_sid(&sidtab, + &c->context[1], + &c->sid[1]); + if (rc) + goto out; + } + *if_sid = c->sid[0]; + *msg_sid = c->sid[1]; + } else { + *if_sid = SECINITSID_NETIF; + *msg_sid = SECINITSID_NETMSG; + } + +out: + POLICY_RDUNLOCK; + return rc; +} + +static int match_ipv6_addrmask(u32 *input, u32 *addr, u32 *mask) +{ + int i, fail = 0; + + for(i = 0; i < 4; i++) + if(addr[i] != (input[i] & mask[i])) { + fail = 1; + break; + } + + return !fail; +} + +/** + * security_node_sid - Obtain the SID for a node (host). + * @domain: communication domain aka address family + * @addrp: address + * @addrlen: address length in bytes + * @out_sid: security identifier + */ +int security_node_sid(u16 domain, + void *addrp, + u32 addrlen, + u32 *out_sid) +{ + int rc = 0; + struct ocontext *c; + + POLICY_RDLOCK; + + switch (domain) { + case AF_INET: { + u32 addr; + + if (addrlen != sizeof(u32)) { + rc = -EINVAL; + goto out; + } + + addr = *((u32 *)addrp); + + c = policydb.ocontexts[OCON_NODE]; + while (c) { + if (c->u.node.addr == (addr & c->u.node.mask)) + break; + c = c->next; + } + break; + } + + case AF_INET6: + if (addrlen != sizeof(u64) * 2) { + rc = -EINVAL; + goto out; + } + c = policydb.ocontexts[OCON_NODE6]; + while (c) { + if (match_ipv6_addrmask(addrp, c->u.node6.addr, + c->u.node6.mask)) + break; + c = c->next; + } + break; + + default: + *out_sid = SECINITSID_NODE; + goto out; + } + + if (c) { + if (!c->sid[0]) { + rc = sidtab_context_to_sid(&sidtab, + &c->context[0], + &c->sid[0]); + if (rc) + goto out; + } + *out_sid = c->sid[0]; + } else { + *out_sid = SECINITSID_NODE; + } + +out: + POLICY_RDUNLOCK; + return rc; +} + +#define SIDS_NEL 25 + +/** + * security_get_user_sids - Obtain reachable SIDs for a user. + * @fromsid: starting SID + * @username: username + * @sids: array of reachable SIDs for user + * @nel: number of elements in @sids + * + * Generate the set of SIDs for legal security contexts + * for a given user that can be reached by @fromsid. + * Set *@sids to point to a dynamically allocated + * array containing the set of SIDs. Set *@nel to the + * number of elements in the array. + */ + +int security_get_user_sids(u32 fromsid, + char *username, + u32 **sids, + u32 *nel) +{ + struct context *fromcon, usercon; + u32 *mysids, *mysids2, sid; + u32 mynel = 0, maxnel = SIDS_NEL; + struct user_datum *user; + struct role_datum *role; + struct av_decision avd; + int rc = 0, i, j; + + if (!ss_initialized) { + *sids = NULL; + *nel = 0; + goto out; + } + + POLICY_RDLOCK; + + fromcon = sidtab_search(&sidtab, fromsid); + if (!fromcon) { + rc = -EINVAL; + goto out_unlock; + } + + user = hashtab_search(policydb.p_users.table, username); + if (!user) { + rc = -EINVAL; + goto out_unlock; + } + usercon.user = user->value; + + mysids = kmalloc(maxnel*sizeof(*mysids), GFP_ATOMIC); + if (!mysids) { + rc = -ENOMEM; + goto out_unlock; + } + memset(mysids, 0, maxnel*sizeof(*mysids)); + + for (i = ebitmap_startbit(&user->roles); i < ebitmap_length(&user->roles); i++) { + if (!ebitmap_get_bit(&user->roles, i)) + continue; + role = policydb.role_val_to_struct[i]; + usercon.role = i+1; + for (j = ebitmap_startbit(&role->types); j < ebitmap_length(&role->types); j++) { + if (!ebitmap_get_bit(&role->types, j)) + continue; + usercon.type = j+1; + + if (mls_setup_user_range(fromcon, user, &usercon)) + continue; + + rc = context_struct_compute_av(fromcon, &usercon, + SECCLASS_PROCESS, + PROCESS__TRANSITION, + &avd); + if (rc || !(avd.allowed & PROCESS__TRANSITION)) + continue; + rc = sidtab_context_to_sid(&sidtab, &usercon, &sid); + if (rc) { + kfree(mysids); + goto out_unlock; + } + if (mynel < maxnel) { + mysids[mynel++] = sid; + } else { + maxnel += SIDS_NEL; + mysids2 = kmalloc(maxnel*sizeof(*mysids2), GFP_ATOMIC); + if (!mysids2) { + rc = -ENOMEM; + kfree(mysids); + goto out_unlock; + } + memset(mysids2, 0, maxnel*sizeof(*mysids2)); + memcpy(mysids2, mysids, mynel * sizeof(*mysids2)); + kfree(mysids); + mysids = mysids2; + mysids[mynel++] = sid; + } + } + } + + *sids = mysids; + *nel = mynel; + +out_unlock: + POLICY_RDUNLOCK; +out: + return rc; +} + +/** + * security_genfs_sid - Obtain a SID for a file in a filesystem + * @fstype: filesystem type + * @path: path from root of mount + * @sclass: file security class + * @sid: SID for path + * + * Obtain a SID to use for a file in a filesystem that + * cannot support xattr or use a fixed labeling behavior like + * transition SIDs or task SIDs. + */ +int security_genfs_sid(const char *fstype, + char *path, + u16 sclass, + u32 *sid) +{ + int len; + struct genfs *genfs; + struct ocontext *c; + int rc = 0, cmp = 0; + + POLICY_RDLOCK; + + for (genfs = policydb.genfs; genfs; genfs = genfs->next) { + cmp = strcmp(fstype, genfs->fstype); + if (cmp <= 0) + break; + } + + if (!genfs || cmp) { + *sid = SECINITSID_UNLABELED; + rc = -ENOENT; + goto out; + } + + for (c = genfs->head; c; c = c->next) { + len = strlen(c->u.name); + if ((!c->v.sclass || sclass == c->v.sclass) && + (strncmp(c->u.name, path, len) == 0)) + break; + } + + if (!c) { + *sid = SECINITSID_UNLABELED; + rc = -ENOENT; + goto out; + } + + if (!c->sid[0]) { + rc = sidtab_context_to_sid(&sidtab, + &c->context[0], + &c->sid[0]); + if (rc) + goto out; + } + + *sid = c->sid[0]; +out: + POLICY_RDUNLOCK; + return rc; +} + +/** + * security_fs_use - Determine how to handle labeling for a filesystem. + * @fstype: filesystem type + * @behavior: labeling behavior + * @sid: SID for filesystem (superblock) + */ +int security_fs_use( + const char *fstype, + unsigned int *behavior, + u32 *sid) +{ + int rc = 0; + struct ocontext *c; + + POLICY_RDLOCK; + + c = policydb.ocontexts[OCON_FSUSE]; + while (c) { + if (strcmp(fstype, c->u.name) == 0) + break; + c = c->next; + } + + if (c) { + *behavior = c->v.behavior; + if (!c->sid[0]) { + rc = sidtab_context_to_sid(&sidtab, + &c->context[0], + &c->sid[0]); + if (rc) + goto out; + } + *sid = c->sid[0]; + } else { + rc = security_genfs_sid(fstype, "/", SECCLASS_DIR, sid); + if (rc) { + *behavior = SECURITY_FS_USE_NONE; + rc = 0; + } else { + *behavior = SECURITY_FS_USE_GENFS; + } + } + +out: + POLICY_RDUNLOCK; + return rc; +} + +int security_get_bools(int *len, char ***names, int **values) +{ + int i, rc = -ENOMEM; + + POLICY_RDLOCK; + *names = NULL; + *values = NULL; + + *len = policydb.p_bools.nprim; + if (!*len) { + rc = 0; + goto out; + } + + *names = (char**)kmalloc(sizeof(char*) * *len, GFP_ATOMIC); + if (!*names) + goto err; + memset(*names, 0, sizeof(char*) * *len); + + *values = (int*)kmalloc(sizeof(int) * *len, GFP_ATOMIC); + if (!*values) + goto err; + + for (i = 0; i < *len; i++) { + size_t name_len; + (*values)[i] = policydb.bool_val_to_struct[i]->state; + name_len = strlen(policydb.p_bool_val_to_name[i]) + 1; + (*names)[i] = (char*)kmalloc(sizeof(char) * name_len, GFP_ATOMIC); + if (!(*names)[i]) + goto err; + strncpy((*names)[i], policydb.p_bool_val_to_name[i], name_len); + (*names)[i][name_len - 1] = 0; + } + rc = 0; +out: + POLICY_RDUNLOCK; + return rc; +err: + if (*names) { + for (i = 0; i < *len; i++) + if ((*names)[i]) + kfree((*names)[i]); + } + if (*values) + kfree(*values); + goto out; +} + + +int security_set_bools(int len, int *values) +{ + int i, rc = 0; + int lenp, seqno = 0; + struct cond_node *cur; + + POLICY_WRLOCK; + + lenp = policydb.p_bools.nprim; + if (len != lenp) { + rc = -EFAULT; + goto out; + } + + printk(KERN_INFO "security: committed booleans { "); + for (i = 0; i < len; i++) { + if (values[i]) { + policydb.bool_val_to_struct[i]->state = 1; + } else { + policydb.bool_val_to_struct[i]->state = 0; + } + if (i != 0) + printk(", "); + printk("%s:%d", policydb.p_bool_val_to_name[i], + policydb.bool_val_to_struct[i]->state); + } + printk(" }\n"); + + for (cur = policydb.cond_list; cur != NULL; cur = cur->next) { + rc = evaluate_cond_node(&policydb, cur); + if (rc) + goto out; + } + + seqno = ++latest_granting; + +out: + POLICY_WRUNLOCK; + if (!rc) { + avc_ss_reset(seqno); + selnl_notify_policyload(seqno); + } + return rc; +} + +int security_get_bool_value(int bool) +{ + int rc = 0; + int len; + + POLICY_RDLOCK; + + len = policydb.p_bools.nprim; + if (bool >= len) { + rc = -EFAULT; + goto out; + } + + rc = policydb.bool_val_to_struct[bool]->state; +out: + POLICY_RDUNLOCK; + return rc; +} diff --git a/security/selinux/ss/services.h b/security/selinux/ss/services.h new file mode 100644 index 00000000000..e8d907e903c --- /dev/null +++ b/security/selinux/ss/services.h @@ -0,0 +1,15 @@ +/* + * Implementation of the security services. + * + * Author : Stephen Smalley, <sds@epoch.ncsc.mil> + */ +#ifndef _SS_SERVICES_H_ +#define _SS_SERVICES_H_ + +#include "policydb.h" +#include "sidtab.h" + +extern struct policydb policydb; + +#endif /* _SS_SERVICES_H_ */ + diff --git a/security/selinux/ss/sidtab.c b/security/selinux/ss/sidtab.c new file mode 100644 index 00000000000..871c33bd074 --- /dev/null +++ b/security/selinux/ss/sidtab.c @@ -0,0 +1,305 @@ +/* + * Implementation of the SID table type. + * + * Author : Stephen Smalley, <sds@epoch.ncsc.mil> + */ +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/spinlock.h> +#include <linux/errno.h> +#include <linux/sched.h> +#include "flask.h" +#include "security.h" +#include "sidtab.h" + +#define SIDTAB_HASH(sid) \ +(sid & SIDTAB_HASH_MASK) + +#define INIT_SIDTAB_LOCK(s) spin_lock_init(&s->lock) +#define SIDTAB_LOCK(s, x) spin_lock_irqsave(&s->lock, x) +#define SIDTAB_UNLOCK(s, x) spin_unlock_irqrestore(&s->lock, x) + +int sidtab_init(struct sidtab *s) +{ + int i; + + s->htable = kmalloc(sizeof(*(s->htable)) * SIDTAB_SIZE, GFP_ATOMIC); + if (!s->htable) + return -ENOMEM; + for (i = 0; i < SIDTAB_SIZE; i++) + s->htable[i] = NULL; + s->nel = 0; + s->next_sid = 1; + s->shutdown = 0; + INIT_SIDTAB_LOCK(s); + return 0; +} + +int sidtab_insert(struct sidtab *s, u32 sid, struct context *context) +{ + int hvalue, rc = 0; + struct sidtab_node *prev, *cur, *newnode; + + if (!s) { + rc = -ENOMEM; + goto out; + } + + hvalue = SIDTAB_HASH(sid); + prev = NULL; + cur = s->htable[hvalue]; + while (cur != NULL && sid > cur->sid) { + prev = cur; + cur = cur->next; + } + + if (cur && sid == cur->sid) { + rc = -EEXIST; + goto out; + } + + newnode = kmalloc(sizeof(*newnode), GFP_ATOMIC); + if (newnode == NULL) { + rc = -ENOMEM; + goto out; + } + newnode->sid = sid; + if (context_cpy(&newnode->context, context)) { + kfree(newnode); + rc = -ENOMEM; + goto out; + } + + if (prev) { + newnode->next = prev->next; + wmb(); + prev->next = newnode; + } else { + newnode->next = s->htable[hvalue]; + wmb(); + s->htable[hvalue] = newnode; + } + + s->nel++; + if (sid >= s->next_sid) + s->next_sid = sid + 1; +out: + return rc; +} + +struct context *sidtab_search(struct sidtab *s, u32 sid) +{ + int hvalue; + struct sidtab_node *cur; + + if (!s) + return NULL; + + hvalue = SIDTAB_HASH(sid); + cur = s->htable[hvalue]; + while (cur != NULL && sid > cur->sid) + cur = cur->next; + + if (cur == NULL || sid != cur->sid) { + /* Remap invalid SIDs to the unlabeled SID. */ + sid = SECINITSID_UNLABELED; + hvalue = SIDTAB_HASH(sid); + cur = s->htable[hvalue]; + while (cur != NULL && sid > cur->sid) + cur = cur->next; + if (!cur || sid != cur->sid) + return NULL; + } + + return &cur->context; +} + +int sidtab_map(struct sidtab *s, + int (*apply) (u32 sid, + struct context *context, + void *args), + void *args) +{ + int i, rc = 0; + struct sidtab_node *cur; + + if (!s) + goto out; + + for (i = 0; i < SIDTAB_SIZE; i++) { + cur = s->htable[i]; + while (cur != NULL) { + rc = apply(cur->sid, &cur->context, args); + if (rc) + goto out; + cur = cur->next; + } + } +out: + return rc; +} + +void sidtab_map_remove_on_error(struct sidtab *s, + int (*apply) (u32 sid, + struct context *context, + void *args), + void *args) +{ + int i, ret; + struct sidtab_node *last, *cur, *temp; + + if (!s) + return; + + for (i = 0; i < SIDTAB_SIZE; i++) { + last = NULL; + cur = s->htable[i]; + while (cur != NULL) { + ret = apply(cur->sid, &cur->context, args); + if (ret) { + if (last) { + last->next = cur->next; + } else { + s->htable[i] = cur->next; + } + + temp = cur; + cur = cur->next; + context_destroy(&temp->context); + kfree(temp); + s->nel--; + } else { + last = cur; + cur = cur->next; + } + } + } + + return; +} + +static inline u32 sidtab_search_context(struct sidtab *s, + struct context *context) +{ + int i; + struct sidtab_node *cur; + + for (i = 0; i < SIDTAB_SIZE; i++) { + cur = s->htable[i]; + while (cur != NULL) { + if (context_cmp(&cur->context, context)) + return cur->sid; + cur = cur->next; + } + } + return 0; +} + +int sidtab_context_to_sid(struct sidtab *s, + struct context *context, + u32 *out_sid) +{ + u32 sid; + int ret = 0; + unsigned long flags; + + *out_sid = SECSID_NULL; + + sid = sidtab_search_context(s, context); + if (!sid) { + SIDTAB_LOCK(s, flags); + /* Rescan now that we hold the lock. */ + sid = sidtab_search_context(s, context); + if (sid) + goto unlock_out; + /* No SID exists for the context. Allocate a new one. */ + if (s->next_sid == UINT_MAX || s->shutdown) { + ret = -ENOMEM; + goto unlock_out; + } + sid = s->next_sid++; + ret = sidtab_insert(s, sid, context); + if (ret) + s->next_sid--; +unlock_out: + SIDTAB_UNLOCK(s, flags); + } + + if (ret) + return ret; + + *out_sid = sid; + return 0; +} + +void sidtab_hash_eval(struct sidtab *h, char *tag) +{ + int i, chain_len, slots_used, max_chain_len; + struct sidtab_node *cur; + + slots_used = 0; + max_chain_len = 0; + for (i = 0; i < SIDTAB_SIZE; i++) { + cur = h->htable[i]; + if (cur) { + slots_used++; + chain_len = 0; + while (cur) { + chain_len++; + cur = cur->next; + } + + if (chain_len > max_chain_len) + max_chain_len = chain_len; + } + } + + printk(KERN_INFO "%s: %d entries and %d/%d buckets used, longest " + "chain length %d\n", tag, h->nel, slots_used, SIDTAB_SIZE, + max_chain_len); +} + +void sidtab_destroy(struct sidtab *s) +{ + int i; + struct sidtab_node *cur, *temp; + + if (!s) + return; + + for (i = 0; i < SIDTAB_SIZE; i++) { + cur = s->htable[i]; + while (cur != NULL) { + temp = cur; + cur = cur->next; + context_destroy(&temp->context); + kfree(temp); + } + s->htable[i] = NULL; + } + kfree(s->htable); + s->htable = NULL; + s->nel = 0; + s->next_sid = 1; +} + +void sidtab_set(struct sidtab *dst, struct sidtab *src) +{ + unsigned long flags; + + SIDTAB_LOCK(src, flags); + dst->htable = src->htable; + dst->nel = src->nel; + dst->next_sid = src->next_sid; + dst->shutdown = 0; + SIDTAB_UNLOCK(src, flags); +} + +void sidtab_shutdown(struct sidtab *s) +{ + unsigned long flags; + + SIDTAB_LOCK(s, flags); + s->shutdown = 1; + SIDTAB_UNLOCK(s, flags); +} diff --git a/security/selinux/ss/sidtab.h b/security/selinux/ss/sidtab.h new file mode 100644 index 00000000000..2fe9dfa3eb3 --- /dev/null +++ b/security/selinux/ss/sidtab.h @@ -0,0 +1,59 @@ +/* + * A security identifier table (sidtab) is a hash table + * of security context structures indexed by SID value. + * + * Author : Stephen Smalley, <sds@epoch.ncsc.mil> + */ +#ifndef _SS_SIDTAB_H_ +#define _SS_SIDTAB_H_ + +#include "context.h" + +struct sidtab_node { + u32 sid; /* security identifier */ + struct context context; /* security context structure */ + struct sidtab_node *next; +}; + +#define SIDTAB_HASH_BITS 7 +#define SIDTAB_HASH_BUCKETS (1 << SIDTAB_HASH_BITS) +#define SIDTAB_HASH_MASK (SIDTAB_HASH_BUCKETS-1) + +#define SIDTAB_SIZE SIDTAB_HASH_BUCKETS + +struct sidtab { + struct sidtab_node **htable; + unsigned int nel; /* number of elements */ + unsigned int next_sid; /* next SID to allocate */ + unsigned char shutdown; + spinlock_t lock; +}; + +int sidtab_init(struct sidtab *s); +int sidtab_insert(struct sidtab *s, u32 sid, struct context *context); +struct context *sidtab_search(struct sidtab *s, u32 sid); + +int sidtab_map(struct sidtab *s, + int (*apply) (u32 sid, + struct context *context, + void *args), + void *args); + +void sidtab_map_remove_on_error(struct sidtab *s, + int (*apply) (u32 sid, + struct context *context, + void *args), + void *args); + +int sidtab_context_to_sid(struct sidtab *s, + struct context *context, + u32 *sid); + +void sidtab_hash_eval(struct sidtab *h, char *tag); +void sidtab_destroy(struct sidtab *s); +void sidtab_set(struct sidtab *dst, struct sidtab *src); +void sidtab_shutdown(struct sidtab *s); + +#endif /* _SS_SIDTAB_H_ */ + + diff --git a/security/selinux/ss/symtab.c b/security/selinux/ss/symtab.c new file mode 100644 index 00000000000..24a10d36d3b --- /dev/null +++ b/security/selinux/ss/symtab.c @@ -0,0 +1,44 @@ +/* + * Implementation of the symbol table type. + * + * Author : Stephen Smalley, <sds@epoch.ncsc.mil> + */ +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/string.h> +#include <linux/errno.h> +#include "symtab.h" + +static unsigned int symhash(struct hashtab *h, void *key) +{ + char *p, *keyp; + unsigned int size; + unsigned int val; + + val = 0; + keyp = key; + size = strlen(keyp); + for (p = keyp; (p - keyp) < size; p++) + val = (val << 4 | (val >> (8*sizeof(unsigned int)-4))) ^ (*p); + return val & (h->size - 1); +} + +static int symcmp(struct hashtab *h, void *key1, void *key2) +{ + char *keyp1, *keyp2; + + keyp1 = key1; + keyp2 = key2; + return strcmp(keyp1, keyp2); +} + + +int symtab_init(struct symtab *s, unsigned int size) +{ + s->table = hashtab_create(symhash, symcmp, size); + if (!s->table) + return -1; + s->nprim = 0; + return 0; +} + diff --git a/security/selinux/ss/symtab.h b/security/selinux/ss/symtab.h new file mode 100644 index 00000000000..ca422b42fbc --- /dev/null +++ b/security/selinux/ss/symtab.h @@ -0,0 +1,23 @@ +/* + * A symbol table (symtab) maintains associations between symbol + * strings and datum values. The type of the datum values + * is arbitrary. The symbol table type is implemented + * using the hash table type (hashtab). + * + * Author : Stephen Smalley, <sds@epoch.ncsc.mil> + */ +#ifndef _SS_SYMTAB_H_ +#define _SS_SYMTAB_H_ + +#include "hashtab.h" + +struct symtab { + struct hashtab *table; /* hash table (keyed on a string) */ + u32 nprim; /* number of primary names in table */ +}; + +int symtab_init(struct symtab *s, unsigned int size); + +#endif /* _SS_SYMTAB_H_ */ + + |