diff options
Diffstat (limited to '')
-rw-r--r-- | security/selinux/ss/avtab.c | 668 | ||||
-rw-r--r-- | security/selinux/ss/avtab.h | 121 | ||||
-rw-r--r-- | security/selinux/ss/conditional.c | 666 | ||||
-rw-r--r-- | security/selinux/ss/conditional.h | 82 | ||||
-rw-r--r-- | security/selinux/ss/constraint.h | 63 | ||||
-rw-r--r-- | security/selinux/ss/context.h | 164 | ||||
-rw-r--r-- | security/selinux/ss/ebitmap.c | 530 | ||||
-rw-r--r-- | security/selinux/ss/ebitmap.h | 152 | ||||
-rw-r--r-- | security/selinux/ss/hashtab.c | 178 | ||||
-rw-r--r-- | security/selinux/ss/hashtab.h | 88 | ||||
-rw-r--r-- | security/selinux/ss/mls.c | 677 | ||||
-rw-r--r-- | security/selinux/ss/mls.h | 104 | ||||
-rw-r--r-- | security/selinux/ss/mls_types.h | 52 | ||||
-rw-r--r-- | security/selinux/ss/policydb.c | 3562 | ||||
-rw-r--r-- | security/selinux/ss/policydb.h | 381 | ||||
-rw-r--r-- | security/selinux/ss/services.c | 3732 | ||||
-rw-r--r-- | security/selinux/ss/services.h | 42 | ||||
-rw-r--r-- | security/selinux/ss/sidtab.c | 306 | ||||
-rw-r--r-- | security/selinux/ss/sidtab.h | 57 | ||||
-rw-r--r-- | security/selinux/ss/status.c | 127 | ||||
-rw-r--r-- | security/selinux/ss/symtab.c | 44 | ||||
-rw-r--r-- | security/selinux/ss/symtab.h | 24 |
22 files changed, 11820 insertions, 0 deletions
diff --git a/security/selinux/ss/avtab.c b/security/selinux/ss/avtab.c new file mode 100644 index 000000000..c0417cf17 --- /dev/null +++ b/security/selinux/ss/avtab.c @@ -0,0 +1,668 @@ +/* + * Implementation of the access vector table type. + * + * Author : Stephen Smalley, <sds@tycho.nsa.gov> + */ + +/* 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. + * + * Updated: Yuichi Nakamura <ynakam@hitachisoft.jp> + * Tuned number of hash slots for avtab to reduce memory usage + */ + +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/errno.h> +#include "avtab.h" +#include "policydb.h" + +static struct kmem_cache *avtab_node_cachep; +static struct kmem_cache *avtab_xperms_cachep; + +/* Based on MurmurHash3, written by Austin Appleby and placed in the + * public domain. + */ +static inline int avtab_hash(struct avtab_key *keyp, u32 mask) +{ + static const u32 c1 = 0xcc9e2d51; + static const u32 c2 = 0x1b873593; + static const u32 r1 = 15; + static const u32 r2 = 13; + static const u32 m = 5; + static const u32 n = 0xe6546b64; + + u32 hash = 0; + +#define mix(input) { \ + u32 v = input; \ + v *= c1; \ + v = (v << r1) | (v >> (32 - r1)); \ + v *= c2; \ + hash ^= v; \ + hash = (hash << r2) | (hash >> (32 - r2)); \ + hash = hash * m + n; \ +} + + mix(keyp->target_class); + mix(keyp->target_type); + mix(keyp->source_type); + +#undef mix + + hash ^= hash >> 16; + hash *= 0x85ebca6b; + hash ^= hash >> 13; + hash *= 0xc2b2ae35; + hash ^= hash >> 16; + + return hash & mask; +} + +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; + struct avtab_extended_perms *xperms; + newnode = kmem_cache_zalloc(avtab_node_cachep, GFP_KERNEL); + if (newnode == NULL) + return NULL; + newnode->key = *key; + + if (key->specified & AVTAB_XPERMS) { + xperms = kmem_cache_zalloc(avtab_xperms_cachep, GFP_KERNEL); + if (xperms == NULL) { + kmem_cache_free(avtab_node_cachep, newnode); + return NULL; + } + *xperms = *(datum->u.xperms); + newnode->datum.u.xperms = xperms; + } else { + newnode->datum.u.data = datum->u.data; + } + + if (prev) { + newnode->next = prev->next; + prev->next = newnode; + } else { + newnode->next = flex_array_get_ptr(h->htable, hvalue); + if (flex_array_put_ptr(h->htable, hvalue, newnode, + GFP_KERNEL|__GFP_ZERO)) { + kmem_cache_free(avtab_node_cachep, newnode); + return NULL; + } + } + + 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; + u16 specified = key->specified & ~(AVTAB_ENABLED|AVTAB_ENABLED_OLD); + + if (!h || !h->htable) + return -EINVAL; + + hvalue = avtab_hash(key, h->mask); + for (prev = NULL, cur = flex_array_get_ptr(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 && + (specified & cur->key.specified)) { + /* extended perms may not be unique */ + if (specified & AVTAB_XPERMS) + break; + 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; + u16 specified = key->specified & ~(AVTAB_ENABLED|AVTAB_ENABLED_OLD); + + if (!h || !h->htable) + return NULL; + hvalue = avtab_hash(key, h->mask); + for (prev = NULL, cur = flex_array_get_ptr(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 && + (specified & cur->key.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; + } + return avtab_insert_node(h, hvalue, prev, cur, key, datum); +} + +struct avtab_datum *avtab_search(struct avtab *h, struct avtab_key *key) +{ + int hvalue; + struct avtab_node *cur; + u16 specified = key->specified & ~(AVTAB_ENABLED|AVTAB_ENABLED_OLD); + + if (!h || !h->htable) + return NULL; + + hvalue = avtab_hash(key, h->mask); + for (cur = flex_array_get_ptr(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->key.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 hvalue; + struct avtab_node *cur; + u16 specified = key->specified & ~(AVTAB_ENABLED|AVTAB_ENABLED_OLD); + + if (!h || !h->htable) + return NULL; + + hvalue = avtab_hash(key, h->mask); + for (cur = flex_array_get_ptr(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->key.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; + + specified &= ~(AVTAB_ENABLED|AVTAB_ENABLED_OLD); + 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->key.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 < h->nslot; i++) { + cur = flex_array_get_ptr(h->htable, i); + while (cur) { + temp = cur; + cur = cur->next; + if (temp->key.specified & AVTAB_XPERMS) + kmem_cache_free(avtab_xperms_cachep, + temp->datum.u.xperms); + kmem_cache_free(avtab_node_cachep, temp); + } + } + flex_array_free(h->htable); + h->htable = NULL; + h->nslot = 0; + h->mask = 0; +} + +int avtab_init(struct avtab *h) +{ + h->htable = NULL; + h->nel = 0; + return 0; +} + +int avtab_alloc(struct avtab *h, u32 nrules) +{ + u32 mask = 0; + u32 shift = 0; + u32 work = nrules; + u32 nslot = 0; + + if (nrules == 0) + goto avtab_alloc_out; + + while (work) { + work = work >> 1; + shift++; + } + if (shift > 2) + shift = shift - 2; + nslot = 1 << shift; + if (nslot > MAX_AVTAB_HASH_BUCKETS) + nslot = MAX_AVTAB_HASH_BUCKETS; + mask = nslot - 1; + + h->htable = flex_array_alloc(sizeof(struct avtab_node *), nslot, + GFP_KERNEL | __GFP_ZERO); + if (!h->htable) + return -ENOMEM; + + avtab_alloc_out: + h->nel = 0; + h->nslot = nslot; + h->mask = mask; + pr_debug("SELinux: %d avtab hash slots, %d rules.\n", + h->nslot, nrules); + return 0; +} + +void avtab_hash_eval(struct avtab *h, char *tag) +{ + int i, chain_len, slots_used, max_chain_len; + unsigned long long chain2_len_sum; + struct avtab_node *cur; + + slots_used = 0; + max_chain_len = 0; + chain2_len_sum = 0; + for (i = 0; i < h->nslot; i++) { + cur = flex_array_get_ptr(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; + chain2_len_sum += chain_len * chain_len; + } + } + + pr_debug("SELinux: %s: %d entries and %d/%d buckets used, " + "longest chain length %d sum of chain length^2 %llu\n", + tag, h->nel, slots_used, h->nslot, max_chain_len, + chain2_len_sum); +} + +static uint16_t spec_order[] = { + AVTAB_ALLOWED, + AVTAB_AUDITDENY, + AVTAB_AUDITALLOW, + AVTAB_TRANSITION, + AVTAB_CHANGE, + AVTAB_MEMBER, + AVTAB_XPERMS_ALLOWED, + AVTAB_XPERMS_AUDITALLOW, + AVTAB_XPERMS_DONTAUDIT +}; + +int avtab_read_item(struct avtab *a, void *fp, struct policydb *pol, + int (*insertf)(struct avtab *a, struct avtab_key *k, + struct avtab_datum *d, void *p), + void *p) +{ + __le16 buf16[4]; + u16 enabled; + u32 items, items2, val, vers = pol->policyvers; + struct avtab_key key; + struct avtab_datum datum; + struct avtab_extended_perms xperms; + __le32 buf32[ARRAY_SIZE(xperms.perms.p)]; + int i, rc; + unsigned set; + + memset(&key, 0, sizeof(struct avtab_key)); + memset(&datum, 0, sizeof(struct avtab_datum)); + + if (vers < POLICYDB_VERSION_AVTAB) { + rc = next_entry(buf32, fp, sizeof(u32)); + if (rc) { + pr_err("SELinux: avtab: truncated entry\n"); + return rc; + } + items2 = le32_to_cpu(buf32[0]); + if (items2 > ARRAY_SIZE(buf32)) { + pr_err("SELinux: avtab: entry overflow\n"); + return -EINVAL; + + } + rc = next_entry(buf32, fp, sizeof(u32)*items2); + if (rc) { + pr_err("SELinux: avtab: truncated entry\n"); + return rc; + } + items = 0; + + val = le32_to_cpu(buf32[items++]); + key.source_type = (u16)val; + if (key.source_type != val) { + pr_err("SELinux: avtab: truncated source type\n"); + return -EINVAL; + } + val = le32_to_cpu(buf32[items++]); + key.target_type = (u16)val; + if (key.target_type != val) { + pr_err("SELinux: avtab: truncated target type\n"); + return -EINVAL; + } + val = le32_to_cpu(buf32[items++]); + key.target_class = (u16)val; + if (key.target_class != val) { + pr_err("SELinux: avtab: truncated target class\n"); + return -EINVAL; + } + + val = le32_to_cpu(buf32[items++]); + enabled = (val & AVTAB_ENABLED_OLD) ? AVTAB_ENABLED : 0; + + if (!(val & (AVTAB_AV | AVTAB_TYPE))) { + pr_err("SELinux: avtab: null entry\n"); + return -EINVAL; + } + if ((val & AVTAB_AV) && + (val & AVTAB_TYPE)) { + pr_err("SELinux: avtab: entry has both access vectors and types\n"); + return -EINVAL; + } + if (val & AVTAB_XPERMS) { + pr_err("SELinux: avtab: entry has extended permissions\n"); + return -EINVAL; + } + + for (i = 0; i < ARRAY_SIZE(spec_order); i++) { + if (val & spec_order[i]) { + key.specified = spec_order[i] | enabled; + datum.u.data = le32_to_cpu(buf32[items++]); + rc = insertf(a, &key, &datum, p); + if (rc) + return rc; + } + } + + if (items != items2) { + pr_err("SELinux: avtab: entry only had %d items, expected %d\n", + items2, items); + return -EINVAL; + } + return 0; + } + + rc = next_entry(buf16, fp, sizeof(u16)*4); + if (rc) { + pr_err("SELinux: avtab: truncated entry\n"); + return rc; + } + + items = 0; + key.source_type = le16_to_cpu(buf16[items++]); + key.target_type = le16_to_cpu(buf16[items++]); + key.target_class = le16_to_cpu(buf16[items++]); + key.specified = le16_to_cpu(buf16[items++]); + + if (!policydb_type_isvalid(pol, key.source_type) || + !policydb_type_isvalid(pol, key.target_type) || + !policydb_class_isvalid(pol, key.target_class)) { + pr_err("SELinux: avtab: invalid type or class\n"); + return -EINVAL; + } + + set = 0; + for (i = 0; i < ARRAY_SIZE(spec_order); i++) { + if (key.specified & spec_order[i]) + set++; + } + if (!set || set > 1) { + pr_err("SELinux: avtab: more than one specifier\n"); + return -EINVAL; + } + + if ((vers < POLICYDB_VERSION_XPERMS_IOCTL) && + (key.specified & AVTAB_XPERMS)) { + pr_err("SELinux: avtab: policy version %u does not " + "support extended permissions rules and one " + "was specified\n", vers); + return -EINVAL; + } else if (key.specified & AVTAB_XPERMS) { + memset(&xperms, 0, sizeof(struct avtab_extended_perms)); + rc = next_entry(&xperms.specified, fp, sizeof(u8)); + if (rc) { + pr_err("SELinux: avtab: truncated entry\n"); + return rc; + } + rc = next_entry(&xperms.driver, fp, sizeof(u8)); + if (rc) { + pr_err("SELinux: avtab: truncated entry\n"); + return rc; + } + rc = next_entry(buf32, fp, sizeof(u32)*ARRAY_SIZE(xperms.perms.p)); + if (rc) { + pr_err("SELinux: avtab: truncated entry\n"); + return rc; + } + for (i = 0; i < ARRAY_SIZE(xperms.perms.p); i++) + xperms.perms.p[i] = le32_to_cpu(buf32[i]); + datum.u.xperms = &xperms; + } else { + rc = next_entry(buf32, fp, sizeof(u32)); + if (rc) { + pr_err("SELinux: avtab: truncated entry\n"); + return rc; + } + datum.u.data = le32_to_cpu(*buf32); + } + if ((key.specified & AVTAB_TYPE) && + !policydb_type_isvalid(pol, datum.u.data)) { + pr_err("SELinux: avtab: invalid type\n"); + return -EINVAL; + } + return insertf(a, &key, &datum, p); +} + +static int avtab_insertf(struct avtab *a, struct avtab_key *k, + struct avtab_datum *d, void *p) +{ + return avtab_insert(a, k, d); +} + +int avtab_read(struct avtab *a, void *fp, struct policydb *pol) +{ + int rc; + __le32 buf[1]; + u32 nel, i; + + + rc = next_entry(buf, fp, sizeof(u32)); + if (rc < 0) { + pr_err("SELinux: avtab: truncated table\n"); + goto bad; + } + nel = le32_to_cpu(buf[0]); + if (!nel) { + pr_err("SELinux: avtab: table is empty\n"); + rc = -EINVAL; + goto bad; + } + + rc = avtab_alloc(a, nel); + if (rc) + goto bad; + + for (i = 0; i < nel; i++) { + rc = avtab_read_item(a, fp, pol, avtab_insertf, NULL); + if (rc) { + if (rc == -ENOMEM) + pr_err("SELinux: avtab: out of memory\n"); + else if (rc == -EEXIST) + pr_err("SELinux: avtab: duplicate entry\n"); + + goto bad; + } + } + + rc = 0; +out: + return rc; + +bad: + avtab_destroy(a); + goto out; +} + +int avtab_write_item(struct policydb *p, struct avtab_node *cur, void *fp) +{ + __le16 buf16[4]; + __le32 buf32[ARRAY_SIZE(cur->datum.u.xperms->perms.p)]; + int rc; + unsigned int i; + + buf16[0] = cpu_to_le16(cur->key.source_type); + buf16[1] = cpu_to_le16(cur->key.target_type); + buf16[2] = cpu_to_le16(cur->key.target_class); + buf16[3] = cpu_to_le16(cur->key.specified); + rc = put_entry(buf16, sizeof(u16), 4, fp); + if (rc) + return rc; + + if (cur->key.specified & AVTAB_XPERMS) { + rc = put_entry(&cur->datum.u.xperms->specified, sizeof(u8), 1, fp); + if (rc) + return rc; + rc = put_entry(&cur->datum.u.xperms->driver, sizeof(u8), 1, fp); + if (rc) + return rc; + for (i = 0; i < ARRAY_SIZE(cur->datum.u.xperms->perms.p); i++) + buf32[i] = cpu_to_le32(cur->datum.u.xperms->perms.p[i]); + rc = put_entry(buf32, sizeof(u32), + ARRAY_SIZE(cur->datum.u.xperms->perms.p), fp); + } else { + buf32[0] = cpu_to_le32(cur->datum.u.data); + rc = put_entry(buf32, sizeof(u32), 1, fp); + } + if (rc) + return rc; + return 0; +} + +int avtab_write(struct policydb *p, struct avtab *a, void *fp) +{ + unsigned int i; + int rc = 0; + struct avtab_node *cur; + __le32 buf[1]; + + buf[0] = cpu_to_le32(a->nel); + rc = put_entry(buf, sizeof(u32), 1, fp); + if (rc) + return rc; + + for (i = 0; i < a->nslot; i++) { + for (cur = flex_array_get_ptr(a->htable, i); cur; + cur = cur->next) { + rc = avtab_write_item(p, cur, fp); + if (rc) + return rc; + } + } + + return rc; +} + +void __init avtab_cache_init(void) +{ + avtab_node_cachep = kmem_cache_create("avtab_node", + sizeof(struct avtab_node), + 0, SLAB_PANIC, NULL); + avtab_xperms_cachep = kmem_cache_create("avtab_extended_perms", + sizeof(struct avtab_extended_perms), + 0, SLAB_PANIC, NULL); +} diff --git a/security/selinux/ss/avtab.h b/security/selinux/ss/avtab.h new file mode 100644 index 000000000..0d652fad5 --- /dev/null +++ b/security/selinux/ss/avtab.h @@ -0,0 +1,121 @@ +/* + * 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@tycho.nsa.gov> + */ + +/* 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. + * + * Updated: Yuichi Nakamura <ynakam@hitachisoft.jp> + * Tuned number of hash slots for avtab to reduce memory usage + */ +#ifndef _SS_AVTAB_H_ +#define _SS_AVTAB_H_ + +#include "security.h" +#include <linux/flex_array.h> + +struct avtab_key { + u16 source_type; /* source type */ + u16 target_type; /* target type */ + u16 target_class; /* target object class */ +#define AVTAB_ALLOWED 0x0001 +#define AVTAB_AUDITALLOW 0x0002 +#define AVTAB_AUDITDENY 0x0004 +#define AVTAB_AV (AVTAB_ALLOWED | AVTAB_AUDITALLOW | AVTAB_AUDITDENY) +#define AVTAB_TRANSITION 0x0010 +#define AVTAB_MEMBER 0x0020 +#define AVTAB_CHANGE 0x0040 +#define AVTAB_TYPE (AVTAB_TRANSITION | AVTAB_MEMBER | AVTAB_CHANGE) +/* extended permissions */ +#define AVTAB_XPERMS_ALLOWED 0x0100 +#define AVTAB_XPERMS_AUDITALLOW 0x0200 +#define AVTAB_XPERMS_DONTAUDIT 0x0400 +#define AVTAB_XPERMS (AVTAB_XPERMS_ALLOWED | \ + AVTAB_XPERMS_AUDITALLOW | \ + AVTAB_XPERMS_DONTAUDIT) +#define AVTAB_ENABLED_OLD 0x80000000 /* reserved for used in cond_avtab */ +#define AVTAB_ENABLED 0x8000 /* reserved for used in cond_avtab */ + u16 specified; /* what field is specified */ +}; + +/* + * For operations that require more than the 32 permissions provided by the avc + * extended permissions may be used to provide 256 bits of permissions. + */ +struct avtab_extended_perms { +/* These are not flags. All 256 values may be used */ +#define AVTAB_XPERMS_IOCTLFUNCTION 0x01 +#define AVTAB_XPERMS_IOCTLDRIVER 0x02 + /* extension of the avtab_key specified */ + u8 specified; /* ioctl, netfilter, ... */ + /* + * if 256 bits is not adequate as is often the case with ioctls, then + * multiple extended perms may be used and the driver field + * specifies which permissions are included. + */ + u8 driver; + /* 256 bits of permissions */ + struct extended_perms_data perms; +}; + +struct avtab_datum { + union { + u32 data; /* access vector or type value */ + struct avtab_extended_perms *xperms; + } u; +}; + +struct avtab_node { + struct avtab_key key; + struct avtab_datum datum; + struct avtab_node *next; +}; + +struct avtab { + struct flex_array *htable; + u32 nel; /* number of elements */ + u32 nslot; /* number of hash slots */ + u32 mask; /* mask to compute hash func */ + +}; + +int avtab_init(struct avtab *); +int avtab_alloc(struct avtab *, u32); +struct avtab_datum *avtab_search(struct avtab *h, struct avtab_key *k); +void avtab_destroy(struct avtab *h); +void avtab_hash_eval(struct avtab *h, char *tag); + +struct policydb; +int avtab_read_item(struct avtab *a, void *fp, struct policydb *pol, + int (*insert)(struct avtab *a, struct avtab_key *k, + struct avtab_datum *d, void *p), + void *p); + +int avtab_read(struct avtab *a, void *fp, struct policydb *pol); +int avtab_write_item(struct policydb *p, struct avtab_node *cur, void *fp); +int avtab_write(struct policydb *p, struct avtab *a, void *fp); + +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); + +struct avtab_node *avtab_search_node_next(struct avtab_node *node, int specified); + +#define MAX_AVTAB_HASH_BITS 16 +#define MAX_AVTAB_HASH_BUCKETS (1 << MAX_AVTAB_HASH_BITS) + +#endif /* _SS_AVTAB_H_ */ + diff --git a/security/selinux/ss/conditional.c b/security/selinux/ss/conditional.c new file mode 100644 index 000000000..f49e522e9 --- /dev/null +++ b/security/selinux/ss/conditional.c @@ -0,0 +1,666 @@ +/* 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 <linux/slab.h> + +#include "security.h" +#include "conditional.h" +#include "services.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; 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) + pr_err("SELinux: expression result was undefined - disabling all rules.\n"); + /* turn the rules on or off */ + for (cur = node->true_list; cur; cur = cur->next) { + if (new_state <= 0) + cur->node->key.specified &= ~AVTAB_ENABLED; + else + cur->node->key.specified |= AVTAB_ENABLED; + } + + for (cur = node->false_list; cur; cur = cur->next) { + /* -1 or 1 */ + if (new_state) + cur->node->key.specified &= ~AVTAB_ENABLED; + else + cur->node->key.specified |= AVTAB_ENABLED; + } + } + return 0; +} + +int cond_policydb_init(struct policydb *p) +{ + int rc; + + p->bool_val_to_struct = NULL; + p->cond_list = NULL; + + rc = avtab_init(&p->te_cond_avtab); + if (rc) + return rc; + + return 0; +} + +static void cond_av_list_destroy(struct cond_av_list *list) +{ + struct cond_av_list *cur, *next; + for (cur = list; cur; 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; 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; cur = next) { + next = cur->next; + cond_node_destroy(cur); + } +} + +void cond_policydb_destroy(struct policydb *p) +{ + 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) +{ + kfree(p->bool_val_to_struct); + p->bool_val_to_struct = kmalloc_array(p->p_bools.nprim, + sizeof(*p->bool_val_to_struct), + GFP_KERNEL); + if (!p->bool_val_to_struct) + return -ENOMEM; + return 0; +} + +int cond_destroy_bool(void *key, void *datum, void *p) +{ + kfree(key); + kfree(datum); + return 0; +} + +int cond_index_bool(void *key, void *datum, void *datap) +{ + struct policydb *p; + struct cond_bool_datum *booldatum; + struct flex_array *fa; + + booldatum = datum; + p = datap; + + if (!booldatum->value || booldatum->value > p->p_bools.nprim) + return -EINVAL; + + fa = p->sym_val_to_name[SYM_BOOLS]; + if (flex_array_put_ptr(fa, booldatum->value - 1, key, + GFP_KERNEL | __GFP_ZERO)) + BUG(); + 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; + __le32 buf[3]; + u32 len; + int rc; + + booldatum = kzalloc(sizeof(*booldatum), GFP_KERNEL); + if (!booldatum) + return -ENOMEM; + + rc = next_entry(buf, fp, sizeof buf); + if (rc) + goto err; + + booldatum->value = le32_to_cpu(buf[0]); + booldatum->state = le32_to_cpu(buf[1]); + + rc = -EINVAL; + if (!bool_isvalid(booldatum)) + goto err; + + len = le32_to_cpu(buf[2]); + if (((len == 0) || (len == (u32)-1))) + goto err; + + rc = -ENOMEM; + key = kmalloc(len + 1, GFP_KERNEL); + if (!key) + goto err; + rc = next_entry(key, fp, len); + if (rc) + goto err; + key[len] = '\0'; + rc = hashtab_insert(h, key, booldatum); + if (rc) + goto err; + + return 0; +err: + cond_destroy_bool(key, booldatum, NULL); + return rc; +} + +struct cond_insertf_data { + struct policydb *p; + struct cond_av_list *other; + struct cond_av_list *head; + struct cond_av_list *tail; +}; + +static int cond_insertf(struct avtab *a, struct avtab_key *k, struct avtab_datum *d, void *ptr) +{ + struct cond_insertf_data *data = ptr; + struct policydb *p = data->p; + struct cond_av_list *other = data->other, *list, *cur; + struct avtab_node *node_ptr; + u8 found; + int rc = -EINVAL; + + /* + * For type rules we have to make certain there aren't any + * conflicting rules by searching the te_avtab and the + * cond_te_avtab. + */ + if (k->specified & AVTAB_TYPE) { + if (avtab_search(&p->te_avtab, k)) { + pr_err("SELinux: type rule already exists outside of a conditional.\n"); + goto err; + } + /* + * If we are reading the false list other will be a pointer to + * the true list. We can have duplicate entries if there is only + * 1 other entry and it is in our true list. + * + * If we are reading the true list (other == NULL) there shouldn't + * be any other entries. + */ + if (other) { + node_ptr = avtab_search_node(&p->te_cond_avtab, k); + if (node_ptr) { + if (avtab_search_node_next(node_ptr, k->specified)) { + pr_err("SELinux: too many conflicting type rules.\n"); + goto err; + } + found = 0; + for (cur = other; cur; cur = cur->next) { + if (cur->node == node_ptr) { + found = 1; + break; + } + } + if (!found) { + pr_err("SELinux: conflicting type rules.\n"); + goto err; + } + } + } else { + if (avtab_search(&p->te_cond_avtab, k)) { + pr_err("SELinux: conflicting type rules when adding type rule for true.\n"); + goto err; + } + } + } + + node_ptr = avtab_insert_nonunique(&p->te_cond_avtab, k, d); + if (!node_ptr) { + pr_err("SELinux: could not insert rule.\n"); + rc = -ENOMEM; + goto err; + } + + list = kzalloc(sizeof(*list), GFP_KERNEL); + if (!list) { + rc = -ENOMEM; + goto err; + } + + list->node = node_ptr; + if (!data->head) + data->head = list; + else + data->tail->next = list; + data->tail = list; + return 0; + +err: + cond_av_list_destroy(data->head); + data->head = NULL; + return rc; +} + +static int cond_read_av_list(struct policydb *p, void *fp, struct cond_av_list **ret_list, struct cond_av_list *other) +{ + int i, rc; + __le32 buf[1]; + u32 len; + struct cond_insertf_data data; + + *ret_list = NULL; + + rc = next_entry(buf, fp, sizeof(u32)); + if (rc) + return rc; + + len = le32_to_cpu(buf[0]); + if (len == 0) + return 0; + + data.p = p; + data.other = other; + data.head = NULL; + data.tail = NULL; + for (i = 0; i < len; i++) { + rc = avtab_read_item(&p->te_cond_avtab, fp, p, cond_insertf, + &data); + if (rc) + return rc; + } + + *ret_list = data.head; + return 0; +} + +static int expr_isvalid(struct policydb *p, struct cond_expr *expr) +{ + if (expr->expr_type <= 0 || expr->expr_type > COND_LAST) { + pr_err("SELinux: conditional expressions uses unknown operator.\n"); + return 0; + } + + if (expr->bool > p->p_bools.nprim) { + pr_err("SELinux: conditional expressions uses unknown bool.\n"); + return 0; + } + return 1; +} + +static int cond_read_node(struct policydb *p, struct cond_node *node, void *fp) +{ + __le32 buf[2]; + u32 len, i; + int rc; + struct cond_expr *expr = NULL, *last = NULL; + + rc = next_entry(buf, fp, sizeof(u32) * 2); + if (rc) + goto err; + + node->cur_state = le32_to_cpu(buf[0]); + + /* expr */ + len = le32_to_cpu(buf[1]); + + for (i = 0; i < len; i++) { + rc = next_entry(buf, fp, sizeof(u32) * 2); + if (rc) + goto err; + + rc = -ENOMEM; + expr = kzalloc(sizeof(*expr), GFP_KERNEL); + if (!expr) + goto err; + + expr->expr_type = le32_to_cpu(buf[0]); + expr->bool = le32_to_cpu(buf[1]); + + if (!expr_isvalid(p, expr)) { + rc = -EINVAL; + kfree(expr); + goto err; + } + + if (i == 0) + node->expr = expr; + else + last->next = expr; + last = expr; + } + + rc = cond_read_av_list(p, fp, &node->true_list, NULL); + if (rc) + goto err; + rc = cond_read_av_list(p, fp, &node->false_list, node->true_list); + if (rc) + goto err; + return 0; +err: + cond_node_destroy(node); + return rc; +} + +int cond_read_list(struct policydb *p, void *fp) +{ + struct cond_node *node, *last = NULL; + __le32 buf[1]; + u32 i, len; + int rc; + + rc = next_entry(buf, fp, sizeof buf); + if (rc) + return rc; + + len = le32_to_cpu(buf[0]); + + rc = avtab_alloc(&(p->te_cond_avtab), p->te_avtab.nel); + if (rc) + goto err; + + for (i = 0; i < len; i++) { + rc = -ENOMEM; + node = kzalloc(sizeof(*node), GFP_KERNEL); + if (!node) + goto err; + + rc = cond_read_node(p, node, fp); + if (rc) + goto err; + + if (i == 0) + p->cond_list = node; + else + last->next = node; + last = node; + } + return 0; +err: + cond_list_destroy(p->cond_list); + p->cond_list = NULL; + return rc; +} + +int cond_write_bool(void *vkey, void *datum, void *ptr) +{ + char *key = vkey; + struct cond_bool_datum *booldatum = datum; + struct policy_data *pd = ptr; + void *fp = pd->fp; + __le32 buf[3]; + u32 len; + int rc; + + len = strlen(key); + buf[0] = cpu_to_le32(booldatum->value); + buf[1] = cpu_to_le32(booldatum->state); + buf[2] = cpu_to_le32(len); + rc = put_entry(buf, sizeof(u32), 3, fp); + if (rc) + return rc; + rc = put_entry(key, 1, len, fp); + if (rc) + return rc; + return 0; +} + +/* + * cond_write_cond_av_list doesn't write out the av_list nodes. + * Instead it writes out the key/value pairs from the avtab. This + * is necessary because there is no way to uniquely identifying rules + * in the avtab so it is not possible to associate individual rules + * in the avtab with a conditional without saving them as part of + * the conditional. This means that the avtab with the conditional + * rules will not be saved but will be rebuilt on policy load. + */ +static int cond_write_av_list(struct policydb *p, + struct cond_av_list *list, struct policy_file *fp) +{ + __le32 buf[1]; + struct cond_av_list *cur_list; + u32 len; + int rc; + + len = 0; + for (cur_list = list; cur_list != NULL; cur_list = cur_list->next) + len++; + + buf[0] = cpu_to_le32(len); + rc = put_entry(buf, sizeof(u32), 1, fp); + if (rc) + return rc; + + if (len == 0) + return 0; + + for (cur_list = list; cur_list != NULL; cur_list = cur_list->next) { + rc = avtab_write_item(p, cur_list->node, fp); + if (rc) + return rc; + } + + return 0; +} + +static int cond_write_node(struct policydb *p, struct cond_node *node, + struct policy_file *fp) +{ + struct cond_expr *cur_expr; + __le32 buf[2]; + int rc; + u32 len = 0; + + buf[0] = cpu_to_le32(node->cur_state); + rc = put_entry(buf, sizeof(u32), 1, fp); + if (rc) + return rc; + + for (cur_expr = node->expr; cur_expr != NULL; cur_expr = cur_expr->next) + len++; + + buf[0] = cpu_to_le32(len); + rc = put_entry(buf, sizeof(u32), 1, fp); + if (rc) + return rc; + + for (cur_expr = node->expr; cur_expr != NULL; cur_expr = cur_expr->next) { + buf[0] = cpu_to_le32(cur_expr->expr_type); + buf[1] = cpu_to_le32(cur_expr->bool); + rc = put_entry(buf, sizeof(u32), 2, fp); + if (rc) + return rc; + } + + rc = cond_write_av_list(p, node->true_list, fp); + if (rc) + return rc; + rc = cond_write_av_list(p, node->false_list, fp); + if (rc) + return rc; + + return 0; +} + +int cond_write_list(struct policydb *p, struct cond_node *list, void *fp) +{ + struct cond_node *cur; + u32 len; + __le32 buf[1]; + int rc; + + len = 0; + for (cur = list; cur != NULL; cur = cur->next) + len++; + buf[0] = cpu_to_le32(len); + rc = put_entry(buf, sizeof(u32), 1, fp); + if (rc) + return rc; + + for (cur = list; cur != NULL; cur = cur->next) { + rc = cond_write_node(p, cur, fp); + if (rc) + return rc; + } + + return 0; +} + +void cond_compute_xperms(struct avtab *ctab, struct avtab_key *key, + struct extended_perms_decision *xpermd) +{ + struct avtab_node *node; + + if (!ctab || !key || !xpermd) + return; + + for (node = avtab_search_node(ctab, key); node; + node = avtab_search_node_next(node, key->specified)) { + if (node->key.specified & AVTAB_ENABLED) + services_compute_xperms_decision(xpermd, node); + } + return; + +} +/* 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 extended_perms *xperms) +{ + struct avtab_node *node; + + if (!ctab || !key || !avd) + return; + + for (node = avtab_search_node(ctab, key); node; + node = avtab_search_node_next(node, key->specified)) { + if ((u16)(AVTAB_ALLOWED|AVTAB_ENABLED) == + (node->key.specified & (AVTAB_ALLOWED|AVTAB_ENABLED))) + avd->allowed |= node->datum.u.data; + if ((u16)(AVTAB_AUDITDENY|AVTAB_ENABLED) == + (node->key.specified & (AVTAB_AUDITDENY|AVTAB_ENABLED))) + /* Since a '0' in an auditdeny mask represents a + * permission we do NOT want to audit (dontaudit), we use + * the '&' operand to ensure that all '0's in the mask + * are retained (much unlike the allow and auditallow cases). + */ + avd->auditdeny &= node->datum.u.data; + if ((u16)(AVTAB_AUDITALLOW|AVTAB_ENABLED) == + (node->key.specified & (AVTAB_AUDITALLOW|AVTAB_ENABLED))) + avd->auditallow |= node->datum.u.data; + if (xperms && (node->key.specified & AVTAB_ENABLED) && + (node->key.specified & AVTAB_XPERMS)) + services_compute_xperms_drivers(xperms, node); + } +} diff --git a/security/selinux/ss/conditional.h b/security/selinux/ss/conditional.h new file mode 100644 index 000000000..ddb43e7e1 --- /dev/null +++ b/security/selinux/ss/conditional.h @@ -0,0 +1,82 @@ +/* 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" +#include "../include/conditional.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 COND_NEQ + __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); +int cond_write_bool(void *key, void *datum, void *ptr); +int cond_write_list(struct policydb *p, struct cond_node *list, void *fp); + +void cond_compute_av(struct avtab *ctab, struct avtab_key *key, + struct av_decision *avd, struct extended_perms *xperms); +void cond_compute_xperms(struct avtab *ctab, struct avtab_key *key, + struct extended_perms_decision *xpermd); +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 000000000..4e563be9e --- /dev/null +++ b/security/selinux/ss/constraint.h @@ -0,0 +1,63 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * 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@tycho.nsa.gov> + */ +#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 type_set *type_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 000000000..2260c44a5 --- /dev/null +++ b/security/selinux/ss/context.h @@ -0,0 +1,164 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * 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@tycho.nsa.gov> + */ +#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; + u32 len; /* length of string in bytes */ + struct mls_range range; + char *str; /* string representation if context cannot be mapped. */ +}; + +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; + + 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; +} + +/* + * Sets both levels in the MLS range of 'dst' to the low level of 'src'. + */ +static inline int mls_context_cpy_low(struct context *dst, struct context *src) +{ + int rc; + + 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[0].sens; + rc = ebitmap_cpy(&dst->range.level[1].cat, &src->range.level[0].cat); + if (rc) + ebitmap_destroy(&dst->range.level[0].cat); +out: + return rc; +} + +/* + * Sets both levels in the MLS range of 'dst' to the high level of 'src'. + */ +static inline int mls_context_cpy_high(struct context *dst, struct context *src) +{ + int rc; + + dst->range.level[0].sens = src->range.level[1].sens; + rc = ebitmap_cpy(&dst->range.level[0].cat, &src->range.level[1].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) +{ + 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) +{ + 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) +{ + int rc; + + dst->user = src->user; + dst->role = src->role; + dst->type = src->type; + if (src->str) { + dst->str = kstrdup(src->str, GFP_ATOMIC); + if (!dst->str) + return -ENOMEM; + dst->len = src->len; + } else { + dst->str = NULL; + dst->len = 0; + } + rc = mls_context_cpy(dst, src); + if (rc) { + kfree(dst->str); + return rc; + } + return 0; +} + +static inline void context_destroy(struct context *c) +{ + c->user = c->role = c->type = 0; + kfree(c->str); + c->str = NULL; + c->len = 0; + mls_context_destroy(c); +} + +static inline int context_cmp(struct context *c1, struct context *c2) +{ + if (c1->len && c2->len) + return (c1->len == c2->len && !strcmp(c1->str, c2->str)); + if (c1->len || c2->len) + return 0; + 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 000000000..8f624f800 --- /dev/null +++ b/security/selinux/ss/ebitmap.c @@ -0,0 +1,530 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Implementation of the extensible bitmap type. + * + * Author : Stephen Smalley, <sds@tycho.nsa.gov> + */ +/* + * Updated: Hewlett-Packard <paul@paul-moore.com> + * + * Added support to import/export the NetLabel category bitmap + * + * (c) Copyright Hewlett-Packard Development Company, L.P., 2006 + */ +/* + * Updated: KaiGai Kohei <kaigai@ak.jp.nec.com> + * Applied standard bit operations to improve bitmap scanning. + */ + +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/errno.h> +#include <net/netlabel.h> +#include "ebitmap.h" +#include "policydb.h" + +#define BITS_PER_U64 (sizeof(u64) * 8) + +static struct kmem_cache *ebitmap_node_cachep; + +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) && + !memcmp(n1->maps, n2->maps, EBITMAP_SIZE / 8)) { + 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 = kmem_cache_zalloc(ebitmap_node_cachep, GFP_ATOMIC); + if (!new) { + ebitmap_destroy(dst); + return -ENOMEM; + } + new->startbit = n->startbit; + memcpy(new->maps, n->maps, EBITMAP_SIZE / 8); + new->next = NULL; + if (prev) + prev->next = new; + else + dst->node = new; + prev = new; + n = n->next; + } + + dst->highbit = src->highbit; + return 0; +} + +#ifdef CONFIG_NETLABEL +/** + * ebitmap_netlbl_export - Export an ebitmap into a NetLabel category bitmap + * @ebmap: the ebitmap to export + * @catmap: the NetLabel category bitmap + * + * Description: + * Export a SELinux extensibile bitmap into a NetLabel category bitmap. + * Returns zero on success, negative values on error. + * + */ +int ebitmap_netlbl_export(struct ebitmap *ebmap, + struct netlbl_lsm_catmap **catmap) +{ + struct ebitmap_node *e_iter = ebmap->node; + unsigned long e_map; + u32 offset; + unsigned int iter; + int rc; + + if (e_iter == NULL) { + *catmap = NULL; + return 0; + } + + if (*catmap != NULL) + netlbl_catmap_free(*catmap); + *catmap = NULL; + + while (e_iter) { + offset = e_iter->startbit; + for (iter = 0; iter < EBITMAP_UNIT_NUMS; iter++) { + e_map = e_iter->maps[iter]; + if (e_map != 0) { + rc = netlbl_catmap_setlong(catmap, + offset, + e_map, + GFP_ATOMIC); + if (rc != 0) + goto netlbl_export_failure; + } + offset += EBITMAP_UNIT_SIZE; + } + e_iter = e_iter->next; + } + + return 0; + +netlbl_export_failure: + netlbl_catmap_free(*catmap); + return -ENOMEM; +} + +/** + * ebitmap_netlbl_import - Import a NetLabel category bitmap into an ebitmap + * @ebmap: the ebitmap to import + * @catmap: the NetLabel category bitmap + * + * Description: + * Import a NetLabel category bitmap into a SELinux extensibile bitmap. + * Returns zero on success, negative values on error. + * + */ +int ebitmap_netlbl_import(struct ebitmap *ebmap, + struct netlbl_lsm_catmap *catmap) +{ + int rc; + struct ebitmap_node *e_iter = NULL; + struct ebitmap_node *e_prev = NULL; + u32 offset = 0, idx; + unsigned long bitmap; + + for (;;) { + rc = netlbl_catmap_getlong(catmap, &offset, &bitmap); + if (rc < 0) + goto netlbl_import_failure; + if (offset == (u32)-1) + return 0; + + /* don't waste ebitmap space if the netlabel bitmap is empty */ + if (bitmap == 0) { + offset += EBITMAP_UNIT_SIZE; + continue; + } + + if (e_iter == NULL || + offset >= e_iter->startbit + EBITMAP_SIZE) { + e_prev = e_iter; + e_iter = kmem_cache_zalloc(ebitmap_node_cachep, GFP_ATOMIC); + if (e_iter == NULL) + goto netlbl_import_failure; + e_iter->startbit = offset - (offset % EBITMAP_SIZE); + if (e_prev == NULL) + ebmap->node = e_iter; + else + e_prev->next = e_iter; + ebmap->highbit = e_iter->startbit + EBITMAP_SIZE; + } + + /* offset will always be aligned to an unsigned long */ + idx = EBITMAP_NODE_INDEX(e_iter, offset); + e_iter->maps[idx] = bitmap; + + /* next */ + offset += EBITMAP_UNIT_SIZE; + } + + /* NOTE: we should never reach this return */ + return 0; + +netlbl_import_failure: + ebitmap_destroy(ebmap); + return -ENOMEM; +} +#endif /* CONFIG_NETLABEL */ + +/* + * Check to see if all the bits set in e2 are also set in e1. Optionally, + * if last_e2bit is non-zero, the highest set bit in e2 cannot exceed + * last_e2bit. + */ +int ebitmap_contains(struct ebitmap *e1, struct ebitmap *e2, u32 last_e2bit) +{ + struct ebitmap_node *n1, *n2; + int i; + + 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; + } + for (i = EBITMAP_UNIT_NUMS - 1; (i >= 0) && !n2->maps[i]; ) + i--; /* Skip trailing NULL map entries */ + if (last_e2bit && (i >= 0)) { + u32 lastsetbit = n2->startbit + i * EBITMAP_UNIT_SIZE + + __fls(n2->maps[i]); + if (lastsetbit > last_e2bit) + return 0; + } + + while (i >= 0) { + if ((n1->maps[i] & n2->maps[i]) != n2->maps[i]) + return 0; + i--; + } + + 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 + EBITMAP_SIZE) > bit) + return ebitmap_node_get_bit(n, bit); + 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 + EBITMAP_SIZE) > bit) { + if (value) { + ebitmap_node_set_bit(n, bit); + } else { + unsigned int s; + + ebitmap_node_clr_bit(n, bit); + + s = find_first_bit(n->maps, EBITMAP_SIZE); + if (s < EBITMAP_SIZE) + return 0; + + /* drop this node from the bitmap */ + if (!n->next) { + /* + * this was the highest map + * within the bitmap + */ + if (prev) + e->highbit = prev->startbit + + EBITMAP_SIZE; + else + e->highbit = 0; + } + if (prev) + prev->next = n->next; + else + e->node = n->next; + kmem_cache_free(ebitmap_node_cachep, n); + } + return 0; + } + prev = n; + n = n->next; + } + + if (!value) + return 0; + + new = kmem_cache_zalloc(ebitmap_node_cachep, GFP_ATOMIC); + if (!new) + return -ENOMEM; + + new->startbit = bit - (bit % EBITMAP_SIZE); + ebitmap_node_set_bit(new, bit); + + if (!n) + /* this node will be the highest map within the bitmap */ + e->highbit = new->startbit + EBITMAP_SIZE; + + 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; + kmem_cache_free(ebitmap_node_cachep, temp); + } + + e->highbit = 0; + e->node = NULL; + return; +} + +int ebitmap_read(struct ebitmap *e, void *fp) +{ + struct ebitmap_node *n = NULL; + u32 mapunit, count, startbit, index; + u64 map; + __le32 buf[3]; + int rc, i; + + ebitmap_init(e); + + rc = next_entry(buf, fp, sizeof buf); + if (rc < 0) + goto out; + + mapunit = le32_to_cpu(buf[0]); + e->highbit = le32_to_cpu(buf[1]); + count = le32_to_cpu(buf[2]); + + if (mapunit != BITS_PER_U64) { + pr_err("SELinux: ebitmap: map size %u does not " + "match my size %zd (high bit was %d)\n", + mapunit, BITS_PER_U64, e->highbit); + goto bad; + } + + /* round up e->highbit */ + e->highbit += EBITMAP_SIZE - 1; + e->highbit -= (e->highbit % EBITMAP_SIZE); + + if (!e->highbit) { + e->node = NULL; + goto ok; + } + + if (e->highbit && !count) + goto bad; + + for (i = 0; i < count; i++) { + rc = next_entry(&startbit, fp, sizeof(u32)); + if (rc < 0) { + pr_err("SELinux: ebitmap: truncated map\n"); + goto bad; + } + startbit = le32_to_cpu(startbit); + + if (startbit & (mapunit - 1)) { + pr_err("SELinux: ebitmap start bit (%d) is " + "not a multiple of the map unit size (%u)\n", + startbit, mapunit); + goto bad; + } + if (startbit > e->highbit - mapunit) { + pr_err("SELinux: ebitmap start bit (%d) is " + "beyond the end of the bitmap (%u)\n", + startbit, (e->highbit - mapunit)); + goto bad; + } + + if (!n || startbit >= n->startbit + EBITMAP_SIZE) { + struct ebitmap_node *tmp; + tmp = kmem_cache_zalloc(ebitmap_node_cachep, GFP_KERNEL); + if (!tmp) { + pr_err("SELinux: ebitmap: out of memory\n"); + rc = -ENOMEM; + goto bad; + } + /* round down */ + tmp->startbit = startbit - (startbit % EBITMAP_SIZE); + if (n) + n->next = tmp; + else + e->node = tmp; + n = tmp; + } else if (startbit <= n->startbit) { + pr_err("SELinux: ebitmap: start bit %d" + " comes after start bit %d\n", + startbit, n->startbit); + goto bad; + } + + rc = next_entry(&map, fp, sizeof(u64)); + if (rc < 0) { + pr_err("SELinux: ebitmap: truncated map\n"); + goto bad; + } + map = le64_to_cpu(map); + + index = (startbit - n->startbit) / EBITMAP_UNIT_SIZE; + while (map) { + n->maps[index++] = map & (-1UL); + map = EBITMAP_SHIFT_UNIT_SIZE(map); + } + } +ok: + rc = 0; +out: + return rc; +bad: + if (!rc) + rc = -EINVAL; + ebitmap_destroy(e); + goto out; +} + +int ebitmap_write(struct ebitmap *e, void *fp) +{ + struct ebitmap_node *n; + u32 count; + __le32 buf[3]; + u64 map; + int bit, last_bit, last_startbit, rc; + + buf[0] = cpu_to_le32(BITS_PER_U64); + + count = 0; + last_bit = 0; + last_startbit = -1; + ebitmap_for_each_positive_bit(e, n, bit) { + if (rounddown(bit, (int)BITS_PER_U64) > last_startbit) { + count++; + last_startbit = rounddown(bit, BITS_PER_U64); + } + last_bit = roundup(bit + 1, BITS_PER_U64); + } + buf[1] = cpu_to_le32(last_bit); + buf[2] = cpu_to_le32(count); + + rc = put_entry(buf, sizeof(u32), 3, fp); + if (rc) + return rc; + + map = 0; + last_startbit = INT_MIN; + ebitmap_for_each_positive_bit(e, n, bit) { + if (rounddown(bit, (int)BITS_PER_U64) > last_startbit) { + __le64 buf64[1]; + + /* this is the very first bit */ + if (!map) { + last_startbit = rounddown(bit, BITS_PER_U64); + map = (u64)1 << (bit - last_startbit); + continue; + } + + /* write the last node */ + buf[0] = cpu_to_le32(last_startbit); + rc = put_entry(buf, sizeof(u32), 1, fp); + if (rc) + return rc; + + buf64[0] = cpu_to_le64(map); + rc = put_entry(buf64, sizeof(u64), 1, fp); + if (rc) + return rc; + + /* set up for the next node */ + map = 0; + last_startbit = rounddown(bit, BITS_PER_U64); + } + map |= (u64)1 << (bit - last_startbit); + } + /* write the last node */ + if (map) { + __le64 buf64[1]; + + /* write the last node */ + buf[0] = cpu_to_le32(last_startbit); + rc = put_entry(buf, sizeof(u32), 1, fp); + if (rc) + return rc; + + buf64[0] = cpu_to_le64(map); + rc = put_entry(buf64, sizeof(u64), 1, fp); + if (rc) + return rc; + } + return 0; +} + +void __init ebitmap_cache_init(void) +{ + ebitmap_node_cachep = kmem_cache_create("ebitmap_node", + sizeof(struct ebitmap_node), + 0, SLAB_PANIC, NULL); +} diff --git a/security/selinux/ss/ebitmap.h b/security/selinux/ss/ebitmap.h new file mode 100644 index 000000000..6aa7cf6a2 --- /dev/null +++ b/security/selinux/ss/ebitmap.h @@ -0,0 +1,152 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * 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@tycho.nsa.gov> + */ +#ifndef _SS_EBITMAP_H_ +#define _SS_EBITMAP_H_ + +#include <net/netlabel.h> + +#ifdef CONFIG_64BIT +#define EBITMAP_NODE_SIZE 64 +#else +#define EBITMAP_NODE_SIZE 32 +#endif + +#define EBITMAP_UNIT_NUMS ((EBITMAP_NODE_SIZE-sizeof(void *)-sizeof(u32))\ + / sizeof(unsigned long)) +#define EBITMAP_UNIT_SIZE BITS_PER_LONG +#define EBITMAP_SIZE (EBITMAP_UNIT_NUMS * EBITMAP_UNIT_SIZE) +#define EBITMAP_BIT 1ULL +#define EBITMAP_SHIFT_UNIT_SIZE(x) \ + (((x) >> EBITMAP_UNIT_SIZE / 2) >> EBITMAP_UNIT_SIZE / 2) + +struct ebitmap_node { + struct ebitmap_node *next; + unsigned long maps[EBITMAP_UNIT_NUMS]; + u32 startbit; +}; + +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) + +static inline unsigned int ebitmap_start_positive(struct ebitmap *e, + struct ebitmap_node **n) +{ + unsigned int ofs; + + for (*n = e->node; *n; *n = (*n)->next) { + ofs = find_first_bit((*n)->maps, EBITMAP_SIZE); + if (ofs < EBITMAP_SIZE) + return (*n)->startbit + ofs; + } + return ebitmap_length(e); +} + +static inline void ebitmap_init(struct ebitmap *e) +{ + memset(e, 0, sizeof(*e)); +} + +static inline unsigned int ebitmap_next_positive(struct ebitmap *e, + struct ebitmap_node **n, + unsigned int bit) +{ + unsigned int ofs; + + ofs = find_next_bit((*n)->maps, EBITMAP_SIZE, bit - (*n)->startbit + 1); + if (ofs < EBITMAP_SIZE) + return ofs + (*n)->startbit; + + for (*n = (*n)->next; *n; *n = (*n)->next) { + ofs = find_first_bit((*n)->maps, EBITMAP_SIZE); + if (ofs < EBITMAP_SIZE) + return ofs + (*n)->startbit; + } + return ebitmap_length(e); +} + +#define EBITMAP_NODE_INDEX(node, bit) \ + (((bit) - (node)->startbit) / EBITMAP_UNIT_SIZE) +#define EBITMAP_NODE_OFFSET(node, bit) \ + (((bit) - (node)->startbit) % EBITMAP_UNIT_SIZE) + +static inline int ebitmap_node_get_bit(struct ebitmap_node *n, + unsigned int bit) +{ + unsigned int index = EBITMAP_NODE_INDEX(n, bit); + unsigned int ofs = EBITMAP_NODE_OFFSET(n, bit); + + BUG_ON(index >= EBITMAP_UNIT_NUMS); + if ((n->maps[index] & (EBITMAP_BIT << ofs))) + return 1; + return 0; +} + +static inline void ebitmap_node_set_bit(struct ebitmap_node *n, + unsigned int bit) +{ + unsigned int index = EBITMAP_NODE_INDEX(n, bit); + unsigned int ofs = EBITMAP_NODE_OFFSET(n, bit); + + BUG_ON(index >= EBITMAP_UNIT_NUMS); + n->maps[index] |= (EBITMAP_BIT << ofs); +} + +static inline void ebitmap_node_clr_bit(struct ebitmap_node *n, + unsigned int bit) +{ + unsigned int index = EBITMAP_NODE_INDEX(n, bit); + unsigned int ofs = EBITMAP_NODE_OFFSET(n, bit); + + BUG_ON(index >= EBITMAP_UNIT_NUMS); + n->maps[index] &= ~(EBITMAP_BIT << ofs); +} + +#define ebitmap_for_each_positive_bit(e, n, bit) \ + for (bit = ebitmap_start_positive(e, &n); \ + bit < ebitmap_length(e); \ + bit = ebitmap_next_positive(e, &n, bit)) \ + +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, u32 last_e2bit); +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); +int ebitmap_write(struct ebitmap *e, void *fp); + +#ifdef CONFIG_NETLABEL +int ebitmap_netlbl_export(struct ebitmap *ebmap, + struct netlbl_lsm_catmap **catmap); +int ebitmap_netlbl_import(struct ebitmap *ebmap, + struct netlbl_lsm_catmap *catmap); +#else +static inline int ebitmap_netlbl_export(struct ebitmap *ebmap, + struct netlbl_lsm_catmap **catmap) +{ + return -ENOMEM; +} +static inline int ebitmap_netlbl_import(struct ebitmap *ebmap, + struct netlbl_lsm_catmap *catmap) +{ + return -ENOMEM; +} +#endif + +#endif /* _SS_EBITMAP_H_ */ diff --git a/security/selinux/ss/hashtab.c b/security/selinux/ss/hashtab.c new file mode 100644 index 000000000..ebfdaa31e --- /dev/null +++ b/security/selinux/ss/hashtab.c @@ -0,0 +1,178 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Implementation of the hash table type. + * + * Author : Stephen Smalley, <sds@tycho.nsa.gov> + */ +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/errno.h> +#include <linux/sched.h> +#include "hashtab.h" + +static struct kmem_cache *hashtab_node_cachep; + +struct hashtab *hashtab_create(u32 (*hash_value)(struct hashtab *h, const void *key), + int (*keycmp)(struct hashtab *h, const void *key1, const void *key2), + u32 size) +{ + struct hashtab *p; + u32 i; + + p = kzalloc(sizeof(*p), GFP_KERNEL); + if (!p) + return p; + + p->size = size; + p->nel = 0; + p->hash_value = hash_value; + p->keycmp = keycmp; + p->htable = kmalloc_array(size, sizeof(*p->htable), GFP_KERNEL); + if (!p->htable) { + 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; + + cond_resched(); + + 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 = kmem_cache_zalloc(hashtab_node_cachep, GFP_KERNEL); + if (!newnode) + return -ENOMEM; + 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, const void *key) +{ + u32 hvalue; + struct hashtab_node *cur; + + if (!h) + return NULL; + + hvalue = h->hash_value(h, key); + cur = h->htable[hvalue]; + while (cur && h->keycmp(h, key, cur->key) > 0) + cur = cur->next; + + if (!cur || (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) { + temp = cur; + cur = cur->next; + kmem_cache_free(hashtab_node_cachep, 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) { + 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 (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; +} + +void __init hashtab_cache_init(void) +{ + hashtab_node_cachep = kmem_cache_create("hashtab_node", + sizeof(struct hashtab_node), + 0, SLAB_PANIC, NULL); +} diff --git a/security/selinux/ss/hashtab.h b/security/selinux/ss/hashtab.h new file mode 100644 index 000000000..3e3e42bfd --- /dev/null +++ b/security/selinux/ss/hashtab.h @@ -0,0 +1,88 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * 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@tycho.nsa.gov> + */ +#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, const void *key); + /* hash function */ + int (*keycmp)(struct hashtab *h, const void *key1, const 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, const void *key), + int (*keycmp)(struct hashtab *h, const void *key1, const 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, const 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 000000000..39475fb45 --- /dev/null +++ b/security/selinux/ss/mls.c @@ -0,0 +1,677 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Implementation of the multi-level security (MLS) policy. + * + * Author : Stephen Smalley, <sds@tycho.nsa.gov> + */ +/* + * Updated: Trusted Computer Solutions, Inc. <dgoeddel@trustedcs.com> + * + * Support for enhanced MLS infrastructure. + * + * Copyright (C) 2004-2006 Trusted Computer Solutions, Inc. + */ +/* + * Updated: Hewlett-Packard <paul@paul-moore.com> + * + * Added support to import/export the MLS label from NetLabel + * + * (c) Copyright Hewlett-Packard Development Company, L.P., 2006 + */ + +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/string.h> +#include <linux/errno.h> +#include <net/netlabel.h> +#include "sidtab.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 policydb *p, struct context *context) +{ + int i, l, len, head, prev; + char *nm; + struct ebitmap *e; + struct ebitmap_node *node; + + if (!p->mls_enabled) + return 0; + + len = 1; /* for the beginning ":" */ + for (l = 0; l < 2; l++) { + int index_sens = context->range.level[l].sens; + len += strlen(sym_name(p, SYM_LEVELS, index_sens - 1)); + + /* categories */ + head = -2; + prev = -2; + e = &context->range.level[l].cat; + ebitmap_for_each_positive_bit(e, node, i) { + if (i - prev > 1) { + /* one or more negative bits are skipped */ + if (head != prev) { + nm = sym_name(p, SYM_CATS, prev); + len += strlen(nm) + 1; + } + nm = sym_name(p, SYM_CATS, i); + len += strlen(nm) + 1; + head = i; + } + prev = i; + } + if (prev != head) { + nm = sym_name(p, SYM_CATS, prev); + len += strlen(nm) + 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 policydb *p, + struct context *context, + char **scontext) +{ + char *scontextp, *nm; + int i, l, head, prev; + struct ebitmap *e; + struct ebitmap_node *node; + + if (!p->mls_enabled) + return; + + scontextp = *scontext; + + *scontextp = ':'; + scontextp++; + + for (l = 0; l < 2; l++) { + strcpy(scontextp, sym_name(p, SYM_LEVELS, + context->range.level[l].sens - 1)); + scontextp += strlen(scontextp); + + /* categories */ + head = -2; + prev = -2; + e = &context->range.level[l].cat; + ebitmap_for_each_positive_bit(e, node, i) { + if (i - prev > 1) { + /* one or more negative bits are skipped */ + if (prev != head) { + if (prev - head > 1) + *scontextp++ = '.'; + else + *scontextp++ = ','; + nm = sym_name(p, SYM_CATS, prev); + strcpy(scontextp, nm); + scontextp += strlen(nm); + } + if (prev < 0) + *scontextp++ = ':'; + else + *scontextp++ = ','; + nm = sym_name(p, SYM_CATS, i); + strcpy(scontextp, nm); + scontextp += strlen(nm); + head = i; + } + prev = i; + } + + if (prev != head) { + if (prev - head > 1) + *scontextp++ = '.'; + else + *scontextp++ = ','; + nm = sym_name(p, SYM_CATS, prev); + strcpy(scontextp, nm); + scontextp += strlen(nm); + } + + if (l == 0) { + if (mls_level_eq(&context->range.level[0], + &context->range.level[1])) + break; + else + *scontextp++ = '-'; + } + } + + *scontext = scontextp; + return; +} + +int mls_level_isvalid(struct policydb *p, struct mls_level *l) +{ + struct level_datum *levdatum; + + if (!l->sens || l->sens > p->p_levels.nprim) + return 0; + levdatum = hashtab_search(p->p_levels.table, + sym_name(p, SYM_LEVELS, l->sens - 1)); + if (!levdatum) + return 0; + + /* + * Return 1 iff all the bits set in l->cat are also be set in + * levdatum->level->cat and no bit in l->cat is larger than + * p->p_cats.nprim. + */ + return ebitmap_contains(&levdatum->level->cat, &l->cat, + p->p_cats.nprim); +} + +int mls_range_isvalid(struct policydb *p, struct mls_range *r) +{ + return (mls_level_isvalid(p, &r->level[0]) && + mls_level_isvalid(p, &r->level[1]) && + mls_level_dom(&r->level[1], &r->level[0])); +} + +/* + * 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 user_datum *usrdatum; + + if (!p->mls_enabled) + return 1; + + if (!mls_range_isvalid(p, &c->range)) + 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. + * + * If a def_sid is provided and no MLS field is present, + * copy the MLS field of the associated default context. + * Used for upgraded to MLS systems where objects may lack + * MLS fields. + * + * Policy read-lock must be held for sidtab lookup. + * + */ +int mls_context_to_sid(struct policydb *pol, + char oldc, + char **scontext, + struct context *context, + struct sidtab *s, + u32 def_sid) +{ + + char delim; + char *scontextp, *p, *rngptr; + struct level_datum *levdatum; + struct cat_datum *catdatum, *rngdatum; + int l, rc = -EINVAL; + + if (!pol->mls_enabled) { + if (def_sid != SECSID_NULL && oldc) + *scontext += strlen(*scontext) + 1; + return 0; + } + + /* + * No MLS component to the security context, try and map to + * default if provided. + */ + if (!oldc) { + struct context *defcon; + + if (def_sid == SECSID_NULL) + goto out; + + defcon = sidtab_search(s, def_sid); + if (!defcon) + goto out; + + rc = mls_context_cpy(context, defcon); + 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(pol->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 */ + rngptr = strchr(scontextp, '.'); + if (rngptr != NULL) { + /* Remove '.' */ + *rngptr++ = '\0'; + } + + catdatum = hashtab_search(pol->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(pol->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; +} + +/* + * Set the MLS fields in the security context structure + * `context' based on the string representation in + * the string `str'. This function will allocate temporary memory with the + * given constraints of gfp_mask. + */ +int mls_from_string(struct policydb *p, char *str, struct context *context, + gfp_t gfp_mask) +{ + char *tmpstr, *freestr; + int rc; + + if (!p->mls_enabled) + return -EINVAL; + + /* we need freestr because mls_context_to_sid will change + the value of tmpstr */ + tmpstr = freestr = kstrdup(str, gfp_mask); + if (!tmpstr) { + rc = -ENOMEM; + } else { + rc = mls_context_to_sid(p, ':', &tmpstr, context, + NULL, SECSID_NULL); + kfree(freestr); + } + + return rc; +} + +/* + * Copies the MLS range `range' into `context'. + */ +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 policydb *p, + struct context *fromcon, struct user_datum *user, + struct context *usercon) +{ + if (p->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; + struct ebitmap_node *node; + int l, i; + + if (!oldp->mls_enabled || !newp->mls_enabled) + return 0; + + for (l = 0; l < 2; l++) { + levdatum = hashtab_search(newp->p_levels.table, + sym_name(oldp, SYM_LEVELS, + c->range.level[l].sens - 1)); + + if (!levdatum) + return -EINVAL; + c->range.level[l].sens = levdatum->level->sens; + + ebitmap_init(&bitmap); + ebitmap_for_each_positive_bit(&c->range.level[l].cat, node, i) { + int rc; + + catdatum = hashtab_search(newp->p_cats.table, + sym_name(oldp, SYM_CATS, i)); + if (!catdatum) + return -EINVAL; + rc = ebitmap_set_bit(&bitmap, catdatum->value - 1, 1); + if (rc) + return rc; + + cond_resched(); + } + ebitmap_destroy(&c->range.level[l].cat); + c->range.level[l].cat = bitmap; + } + + return 0; +} + +int mls_compute_sid(struct policydb *p, + struct context *scontext, + struct context *tcontext, + u16 tclass, + u32 specified, + struct context *newcontext, + bool sock) +{ + struct range_trans rtr; + struct mls_range *r; + struct class_datum *cladatum; + int default_range = 0; + + if (!p->mls_enabled) + return 0; + + switch (specified) { + case AVTAB_TRANSITION: + /* Look for a range transition rule. */ + rtr.source_type = scontext->type; + rtr.target_type = tcontext->type; + rtr.target_class = tclass; + r = hashtab_search(p->range_tr, &rtr); + if (r) + return mls_range_set(newcontext, r); + + if (tclass && tclass <= p->p_classes.nprim) { + cladatum = p->class_val_to_struct[tclass - 1]; + if (cladatum) + default_range = cladatum->default_range; + } + + switch (default_range) { + case DEFAULT_SOURCE_LOW: + return mls_context_cpy_low(newcontext, scontext); + case DEFAULT_SOURCE_HIGH: + return mls_context_cpy_high(newcontext, scontext); + case DEFAULT_SOURCE_LOW_HIGH: + return mls_context_cpy(newcontext, scontext); + case DEFAULT_TARGET_LOW: + return mls_context_cpy_low(newcontext, tcontext); + case DEFAULT_TARGET_HIGH: + return mls_context_cpy_high(newcontext, tcontext); + case DEFAULT_TARGET_LOW_HIGH: + return mls_context_cpy(newcontext, tcontext); + } + + /* Fallthrough */ + case AVTAB_CHANGE: + if ((tclass == p->process_class) || (sock == true)) + /* Use the process MLS attributes. */ + return mls_context_cpy(newcontext, scontext); + else + /* Use the process effective MLS attributes. */ + return mls_context_cpy_low(newcontext, scontext); + case AVTAB_MEMBER: + /* Use the process effective MLS attributes. */ + return mls_context_cpy_low(newcontext, scontext); + + /* fall through */ + } + return -EINVAL; +} + +#ifdef CONFIG_NETLABEL +/** + * mls_export_netlbl_lvl - Export the MLS sensitivity levels to NetLabel + * @context: the security context + * @secattr: the NetLabel security attributes + * + * Description: + * Given the security context copy the low MLS sensitivity level into the + * NetLabel MLS sensitivity level field. + * + */ +void mls_export_netlbl_lvl(struct policydb *p, + struct context *context, + struct netlbl_lsm_secattr *secattr) +{ + if (!p->mls_enabled) + return; + + secattr->attr.mls.lvl = context->range.level[0].sens - 1; + secattr->flags |= NETLBL_SECATTR_MLS_LVL; +} + +/** + * mls_import_netlbl_lvl - Import the NetLabel MLS sensitivity levels + * @context: the security context + * @secattr: the NetLabel security attributes + * + * Description: + * Given the security context and the NetLabel security attributes, copy the + * NetLabel MLS sensitivity level into the context. + * + */ +void mls_import_netlbl_lvl(struct policydb *p, + struct context *context, + struct netlbl_lsm_secattr *secattr) +{ + if (!p->mls_enabled) + return; + + context->range.level[0].sens = secattr->attr.mls.lvl + 1; + context->range.level[1].sens = context->range.level[0].sens; +} + +/** + * mls_export_netlbl_cat - Export the MLS categories to NetLabel + * @context: the security context + * @secattr: the NetLabel security attributes + * + * Description: + * Given the security context copy the low MLS categories into the NetLabel + * MLS category field. Returns zero on success, negative values on failure. + * + */ +int mls_export_netlbl_cat(struct policydb *p, + struct context *context, + struct netlbl_lsm_secattr *secattr) +{ + int rc; + + if (!p->mls_enabled) + return 0; + + rc = ebitmap_netlbl_export(&context->range.level[0].cat, + &secattr->attr.mls.cat); + if (rc == 0 && secattr->attr.mls.cat != NULL) + secattr->flags |= NETLBL_SECATTR_MLS_CAT; + + return rc; +} + +/** + * mls_import_netlbl_cat - Import the MLS categories from NetLabel + * @context: the security context + * @secattr: the NetLabel security attributes + * + * Description: + * Copy the NetLabel security attributes into the SELinux context; since the + * NetLabel security attribute only contains a single MLS category use it for + * both the low and high categories of the context. Returns zero on success, + * negative values on failure. + * + */ +int mls_import_netlbl_cat(struct policydb *p, + struct context *context, + struct netlbl_lsm_secattr *secattr) +{ + int rc; + + if (!p->mls_enabled) + return 0; + + rc = ebitmap_netlbl_import(&context->range.level[0].cat, + secattr->attr.mls.cat); + if (rc) + goto import_netlbl_cat_failure; + memcpy(&context->range.level[1].cat, &context->range.level[0].cat, + sizeof(context->range.level[0].cat)); + + return 0; + +import_netlbl_cat_failure: + ebitmap_destroy(&context->range.level[0].cat); + return rc; +} +#endif /* CONFIG_NETLABEL */ diff --git a/security/selinux/ss/mls.h b/security/selinux/ss/mls.h new file mode 100644 index 000000000..9a3ff7af7 --- /dev/null +++ b/security/selinux/ss/mls.h @@ -0,0 +1,104 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Multi-level security (MLS) policy operations. + * + * Author : Stephen Smalley, <sds@tycho.nsa.gov> + */ +/* + * Updated: Trusted Computer Solutions, Inc. <dgoeddel@trustedcs.com> + * + * Support for enhanced MLS infrastructure. + * + * Copyright (C) 2004-2006 Trusted Computer Solutions, Inc. + */ +/* + * Updated: Hewlett-Packard <paul@paul-moore.com> + * + * Added support to import/export the MLS label from NetLabel + * + * (c) Copyright Hewlett-Packard Development Company, L.P., 2006 + */ + +#ifndef _SS_MLS_H_ +#define _SS_MLS_H_ + +#include "context.h" +#include "policydb.h" + +int mls_compute_context_len(struct policydb *p, struct context *context); +void mls_sid_to_context(struct policydb *p, struct context *context, + char **scontext); +int mls_context_isvalid(struct policydb *p, struct context *c); +int mls_range_isvalid(struct policydb *p, struct mls_range *r); +int mls_level_isvalid(struct policydb *p, struct mls_level *l); + +int mls_context_to_sid(struct policydb *p, + char oldc, + char **scontext, + struct context *context, + struct sidtab *s, + u32 def_sid); + +int mls_from_string(struct policydb *p, char *str, struct context *context, + gfp_t gfp_mask); + +int mls_range_set(struct context *context, struct mls_range *range); + +int mls_convert_context(struct policydb *oldp, + struct policydb *newp, + struct context *context); + +int mls_compute_sid(struct policydb *p, + struct context *scontext, + struct context *tcontext, + u16 tclass, + u32 specified, + struct context *newcontext, + bool sock); + +int mls_setup_user_range(struct policydb *p, + struct context *fromcon, struct user_datum *user, + struct context *usercon); + +#ifdef CONFIG_NETLABEL +void mls_export_netlbl_lvl(struct policydb *p, + struct context *context, + struct netlbl_lsm_secattr *secattr); +void mls_import_netlbl_lvl(struct policydb *p, + struct context *context, + struct netlbl_lsm_secattr *secattr); +int mls_export_netlbl_cat(struct policydb *p, + struct context *context, + struct netlbl_lsm_secattr *secattr); +int mls_import_netlbl_cat(struct policydb *p, + struct context *context, + struct netlbl_lsm_secattr *secattr); +#else +static inline void mls_export_netlbl_lvl(struct policydb *p, + struct context *context, + struct netlbl_lsm_secattr *secattr) +{ + return; +} +static inline void mls_import_netlbl_lvl(struct policydb *p, + struct context *context, + struct netlbl_lsm_secattr *secattr) +{ + return; +} +static inline int mls_export_netlbl_cat(struct policydb *p, + struct context *context, + struct netlbl_lsm_secattr *secattr) +{ + return -ENOMEM; +} +static inline int mls_import_netlbl_cat(struct policydb *p, + struct context *context, + struct netlbl_lsm_secattr *secattr) +{ + return -ENOMEM; +} +#endif + +#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 000000000..068e0d780 --- /dev/null +++ b/security/selinux/ss/mls_types.h @@ -0,0 +1,52 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Type definitions for the multi-level security (MLS) policy. + * + * Author : Stephen Smalley, <sds@tycho.nsa.gov> + */ +/* + * 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" +#include "ebitmap.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) +{ + 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) +{ + return ((l1->sens >= l2->sens) && + ebitmap_contains(&l1->cat, &l2->cat, 0)); +} + +#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 000000000..91d259c87 --- /dev/null +++ b/security/selinux/ss/policydb.c @@ -0,0 +1,3562 @@ +/* + * Implementation of the policy database. + * + * Author : Stephen Smalley, <sds@tycho.nsa.gov> + */ + +/* + * 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 + * + * Updated: Hewlett-Packard <paul@paul-moore.com> + * + * Added support for the policy capability bitmap + * + * Update: Mellanox Techonologies + * + * Added Infiniband support + * + * Copyright (C) 2016 Mellanox Techonologies + * Copyright (C) 2007 Hewlett-Packard Development Company, L.P. + * 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/sched.h> +#include <linux/slab.h> +#include <linux/string.h> +#include <linux/errno.h> +#include <linux/audit.h> +#include <linux/flex_array.h> +#include "security.h" + +#include "policydb.h" +#include "conditional.h" +#include "mls.h" +#include "services.h" + +#define _DEBUG_HASHES + +#ifdef DEBUG_HASHES +static const char *symtab_name[SYM_NUM] = { + "common prefixes", + "classes", + "roles", + "types", + "users", + "bools", + "levels", + "categories", +}; +#endif + +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 - 3, + }, + { + .version = POLICYDB_VERSION_BOOL, + .sym_num = SYM_NUM - 2, + .ocon_num = OCON_NUM - 3, + }, + { + .version = POLICYDB_VERSION_IPV6, + .sym_num = SYM_NUM - 2, + .ocon_num = OCON_NUM - 2, + }, + { + .version = POLICYDB_VERSION_NLCLASS, + .sym_num = SYM_NUM - 2, + .ocon_num = OCON_NUM - 2, + }, + { + .version = POLICYDB_VERSION_MLS, + .sym_num = SYM_NUM, + .ocon_num = OCON_NUM - 2, + }, + { + .version = POLICYDB_VERSION_AVTAB, + .sym_num = SYM_NUM, + .ocon_num = OCON_NUM - 2, + }, + { + .version = POLICYDB_VERSION_RANGETRANS, + .sym_num = SYM_NUM, + .ocon_num = OCON_NUM - 2, + }, + { + .version = POLICYDB_VERSION_POLCAP, + .sym_num = SYM_NUM, + .ocon_num = OCON_NUM - 2, + }, + { + .version = POLICYDB_VERSION_PERMISSIVE, + .sym_num = SYM_NUM, + .ocon_num = OCON_NUM - 2, + }, + { + .version = POLICYDB_VERSION_BOUNDARY, + .sym_num = SYM_NUM, + .ocon_num = OCON_NUM - 2, + }, + { + .version = POLICYDB_VERSION_FILENAME_TRANS, + .sym_num = SYM_NUM, + .ocon_num = OCON_NUM - 2, + }, + { + .version = POLICYDB_VERSION_ROLETRANS, + .sym_num = SYM_NUM, + .ocon_num = OCON_NUM - 2, + }, + { + .version = POLICYDB_VERSION_NEW_OBJECT_DEFAULTS, + .sym_num = SYM_NUM, + .ocon_num = OCON_NUM - 2, + }, + { + .version = POLICYDB_VERSION_DEFAULT_TYPE, + .sym_num = SYM_NUM, + .ocon_num = OCON_NUM - 2, + }, + { + .version = POLICYDB_VERSION_CONSTRAINT_NAMES, + .sym_num = SYM_NUM, + .ocon_num = OCON_NUM - 2, + }, + { + .version = POLICYDB_VERSION_XPERMS_IOCTL, + .sym_num = SYM_NUM, + .ocon_num = OCON_NUM - 2, + }, + { + .version = POLICYDB_VERSION_INFINIBAND, + .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 < ARRAY_SIZE(policydb_compat); 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 = kzalloc(sizeof(*role), GFP_KERNEL); + if (!role) + return -ENOMEM; + + rc = -EINVAL; + role->value = ++p->p_roles.nprim; + if (role->value != OBJECT_R_VAL) + goto out; + + rc = -ENOMEM; + key = kstrdup(OBJECT_R, GFP_KERNEL); + if (!key) + goto out; + + rc = hashtab_insert(p->p_roles.table, key, role); + if (rc) + goto out; + + return 0; +out: + kfree(key); + kfree(role); + return rc; +} + +static u32 filenametr_hash(struct hashtab *h, const void *k) +{ + const struct filename_trans *ft = k; + unsigned long hash; + unsigned int byte_num; + unsigned char focus; + + hash = ft->stype ^ ft->ttype ^ ft->tclass; + + byte_num = 0; + while ((focus = ft->name[byte_num++])) + hash = partial_name_hash(focus, hash); + return hash & (h->size - 1); +} + +static int filenametr_cmp(struct hashtab *h, const void *k1, const void *k2) +{ + const struct filename_trans *ft1 = k1; + const struct filename_trans *ft2 = k2; + int v; + + v = ft1->stype - ft2->stype; + if (v) + return v; + + v = ft1->ttype - ft2->ttype; + if (v) + return v; + + v = ft1->tclass - ft2->tclass; + if (v) + return v; + + return strcmp(ft1->name, ft2->name); + +} + +static u32 rangetr_hash(struct hashtab *h, const void *k) +{ + const struct range_trans *key = k; + return (key->source_type + (key->target_type << 3) + + (key->target_class << 5)) & (h->size - 1); +} + +static int rangetr_cmp(struct hashtab *h, const void *k1, const void *k2) +{ + const struct range_trans *key1 = k1, *key2 = k2; + int v; + + v = key1->source_type - key2->source_type; + if (v) + return v; + + v = key1->target_type - key2->target_type; + if (v) + return v; + + v = key1->target_class - key2->target_class; + + return v; +} + +static int (*destroy_f[SYM_NUM]) (void *key, void *datum, void *datap); + +/* + * 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; + } + + rc = avtab_init(&p->te_avtab); + if (rc) + goto out; + + rc = roles_init(p); + if (rc) + goto out; + + rc = cond_policydb_init(p); + if (rc) + goto out; + + p->filename_trans = hashtab_create(filenametr_hash, filenametr_cmp, (1 << 10)); + if (!p->filename_trans) { + rc = -ENOMEM; + goto out; + } + + p->range_tr = hashtab_create(rangetr_hash, rangetr_cmp, 256); + if (!p->range_tr) { + rc = -ENOMEM; + goto out; + } + + ebitmap_init(&p->filename_trans_ttypes); + ebitmap_init(&p->policycaps); + ebitmap_init(&p->permissive_map); + + return 0; +out: + hashtab_destroy(p->filename_trans); + hashtab_destroy(p->range_tr); + for (i = 0; i < SYM_NUM; i++) { + hashtab_map(p->symtab[i].table, destroy_f[i], NULL); + hashtab_destroy(p->symtab[i].table); + } + return rc; +} + +/* + * 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; + struct flex_array *fa; + + comdatum = datum; + p = datap; + if (!comdatum->value || comdatum->value > p->p_commons.nprim) + return -EINVAL; + + fa = p->sym_val_to_name[SYM_COMMONS]; + if (flex_array_put_ptr(fa, comdatum->value - 1, key, + GFP_KERNEL | __GFP_ZERO)) + BUG(); + return 0; +} + +static int class_index(void *key, void *datum, void *datap) +{ + struct policydb *p; + struct class_datum *cladatum; + struct flex_array *fa; + + cladatum = datum; + p = datap; + if (!cladatum->value || cladatum->value > p->p_classes.nprim) + return -EINVAL; + fa = p->sym_val_to_name[SYM_CLASSES]; + if (flex_array_put_ptr(fa, cladatum->value - 1, key, + GFP_KERNEL | __GFP_ZERO)) + BUG(); + 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; + struct flex_array *fa; + + role = datum; + p = datap; + if (!role->value + || role->value > p->p_roles.nprim + || role->bounds > p->p_roles.nprim) + return -EINVAL; + + fa = p->sym_val_to_name[SYM_ROLES]; + if (flex_array_put_ptr(fa, role->value - 1, key, + GFP_KERNEL | __GFP_ZERO)) + BUG(); + 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; + struct flex_array *fa; + + typdatum = datum; + p = datap; + + if (typdatum->primary) { + if (!typdatum->value + || typdatum->value > p->p_types.nprim + || typdatum->bounds > p->p_types.nprim) + return -EINVAL; + fa = p->sym_val_to_name[SYM_TYPES]; + if (flex_array_put_ptr(fa, typdatum->value - 1, key, + GFP_KERNEL | __GFP_ZERO)) + BUG(); + + fa = p->type_val_to_struct_array; + if (flex_array_put_ptr(fa, typdatum->value - 1, typdatum, + GFP_KERNEL | __GFP_ZERO)) + BUG(); + } + + return 0; +} + +static int user_index(void *key, void *datum, void *datap) +{ + struct policydb *p; + struct user_datum *usrdatum; + struct flex_array *fa; + + usrdatum = datum; + p = datap; + if (!usrdatum->value + || usrdatum->value > p->p_users.nprim + || usrdatum->bounds > p->p_users.nprim) + return -EINVAL; + + fa = p->sym_val_to_name[SYM_USERS]; + if (flex_array_put_ptr(fa, usrdatum->value - 1, key, + GFP_KERNEL | __GFP_ZERO)) + BUG(); + 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; + struct flex_array *fa; + + levdatum = datum; + p = datap; + + if (!levdatum->isalias) { + if (!levdatum->level->sens || + levdatum->level->sens > p->p_levels.nprim) + return -EINVAL; + fa = p->sym_val_to_name[SYM_LEVELS]; + if (flex_array_put_ptr(fa, levdatum->level->sens - 1, key, + GFP_KERNEL | __GFP_ZERO)) + BUG(); + } + + return 0; +} + +static int cat_index(void *key, void *datum, void *datap) +{ + struct policydb *p; + struct cat_datum *catdatum; + struct flex_array *fa; + + catdatum = datum; + p = datap; + + if (!catdatum->isalias) { + if (!catdatum->value || catdatum->value > p->p_cats.nprim) + return -EINVAL; + fa = p->sym_val_to_name[SYM_CATS]; + if (flex_array_put_ptr(fa, catdatum->value - 1, key, + GFP_KERNEL | __GFP_ZERO)) + BUG(); + } + + 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, +}; + +#ifdef DEBUG_HASHES +static void hash_eval(struct hashtab *h, const char *hash_name) +{ + struct hashtab_info info; + + hashtab_stat(h, &info); + pr_debug("SELinux: %s: %d entries and %d/%d buckets used, " + "longest chain length %d\n", hash_name, h->nel, + info.slots_used, h->size, info.max_chain_len); +} + +static void symtab_hash_eval(struct symtab *s) +{ + int i; + + for (i = 0; i < SYM_NUM; i++) + hash_eval(s[i].table, symtab_name[i]); +} + +#else +static inline void hash_eval(struct hashtab *h, char *hash_name) +{ +} +#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(struct policydb *p) +{ + int i, rc; + + if (p->mls_enabled) + pr_debug("SELinux: %d users, %d roles, %d types, %d bools, %d sens, %d cats\n", + p->p_users.nprim, p->p_roles.nprim, p->p_types.nprim, + p->p_bools.nprim, p->p_levels.nprim, p->p_cats.nprim); + else + pr_debug("SELinux: %d users, %d roles, %d types, %d bools\n", + p->p_users.nprim, p->p_roles.nprim, p->p_types.nprim, + p->p_bools.nprim); + + pr_debug("SELinux: %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->class_val_to_struct = kcalloc(p->p_classes.nprim, + sizeof(*p->class_val_to_struct), + GFP_KERNEL); + if (!p->class_val_to_struct) + return -ENOMEM; + + p->role_val_to_struct = kcalloc(p->p_roles.nprim, + sizeof(*p->role_val_to_struct), + GFP_KERNEL); + if (!p->role_val_to_struct) + return -ENOMEM; + + p->user_val_to_struct = kcalloc(p->p_users.nprim, + sizeof(*p->user_val_to_struct), + GFP_KERNEL); + if (!p->user_val_to_struct) + return -ENOMEM; + + /* Yes, I want the sizeof the pointer, not the structure */ + p->type_val_to_struct_array = flex_array_alloc(sizeof(struct type_datum *), + p->p_types.nprim, + GFP_KERNEL | __GFP_ZERO); + if (!p->type_val_to_struct_array) + return -ENOMEM; + + rc = flex_array_prealloc(p->type_val_to_struct_array, 0, + p->p_types.nprim, GFP_KERNEL | __GFP_ZERO); + if (rc) + goto out; + + rc = cond_init_bool_indexes(p); + if (rc) + goto out; + + for (i = 0; i < SYM_NUM; i++) { + p->sym_val_to_name[i] = flex_array_alloc(sizeof(char *), + p->symtab[i].nprim, + GFP_KERNEL | __GFP_ZERO); + if (!p->sym_val_to_name[i]) + return -ENOMEM; + + rc = flex_array_prealloc(p->sym_val_to_name[i], + 0, p->symtab[i].nprim, + GFP_KERNEL | __GFP_ZERO); + if (rc) + goto out; + + rc = hashtab_map(p->symtab[i].table, index_f[i], p); + if (rc) + goto out; + } + rc = 0; +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); + if (datum) { + comdatum = datum; + hashtab_map(comdatum->permissions.table, perm_destroy, NULL); + hashtab_destroy(comdatum->permissions.table); + } + kfree(datum); + return 0; +} + +static void constraint_expr_destroy(struct constraint_expr *expr) +{ + if (expr) { + ebitmap_destroy(&expr->names); + if (expr->type_names) { + ebitmap_destroy(&expr->type_names->types); + ebitmap_destroy(&expr->type_names->negset); + kfree(expr->type_names); + } + kfree(expr); + } +} + +static int cls_destroy(void *key, void *datum, void *p) +{ + struct class_datum *cladatum; + struct constraint_node *constraint, *ctemp; + struct constraint_expr *e, *etmp; + + kfree(key); + if (datum) { + 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) { + etmp = e; + e = e->next; + constraint_expr_destroy(etmp); + } + ctemp = constraint; + constraint = constraint->next; + kfree(ctemp); + } + + constraint = cladatum->validatetrans; + while (constraint) { + e = constraint->expr; + while (e) { + etmp = e; + e = e->next; + constraint_expr_destroy(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); + if (datum) { + 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); + if (datum) { + 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); + if (datum) { + levdatum = datum; + if (levdatum->level) + 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, + cls_destroy, + role_destroy, + type_destroy, + user_destroy, + cond_destroy_bool, + sens_destroy, + cat_destroy, +}; + +static int filenametr_destroy(void *key, void *datum, void *p) +{ + struct filename_trans *ft = key; + kfree(ft->name); + kfree(key); + kfree(datum); + cond_resched(); + return 0; +} + +static int range_tr_destroy(void *key, void *datum, void *p) +{ + struct mls_range *rt = datum; + kfree(key); + ebitmap_destroy(&rt->level[0].cat); + ebitmap_destroy(&rt->level[1].cat); + kfree(datum); + cond_resched(); + return 0; +} + +static void ocontext_destroy(struct ocontext *c, int i) +{ + if (!c) + return; + + 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; + struct role_allow *ra, *lra = NULL; + struct role_trans *tr, *ltr = NULL; + + for (i = 0; i < SYM_NUM; i++) { + cond_resched(); + 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]) + flex_array_free(p->sym_val_to_name[i]); + } + + kfree(p->class_val_to_struct); + kfree(p->role_val_to_struct); + kfree(p->user_val_to_struct); + if (p->type_val_to_struct_array) + flex_array_free(p->type_val_to_struct_array); + + avtab_destroy(&p->te_avtab); + + for (i = 0; i < OCON_NUM; i++) { + cond_resched(); + c = p->ocontexts[i]; + while (c) { + ctmp = c; + c = c->next; + ocontext_destroy(ctmp, i); + } + p->ocontexts[i] = NULL; + } + + g = p->genfs; + while (g) { + cond_resched(); + 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); + } + p->genfs = NULL; + + cond_policydb_destroy(p); + + for (tr = p->role_tr; tr; tr = tr->next) { + cond_resched(); + kfree(ltr); + ltr = tr; + } + kfree(ltr); + + for (ra = p->role_allow; ra; ra = ra->next) { + cond_resched(); + kfree(lra); + lra = ra; + } + kfree(lra); + + hashtab_map(p->filename_trans, filenametr_destroy, NULL); + hashtab_destroy(p->filename_trans); + + hashtab_map(p->range_tr, range_tr_destroy, NULL); + hashtab_destroy(p->range_tr); + + if (p->type_attr_map_array) { + for (i = 0; i < p->p_types.nprim; i++) { + struct ebitmap *e; + + e = flex_array_get(p->type_attr_map_array, i); + if (!e) + continue; + ebitmap_destroy(e); + } + flex_array_free(p->type_attr_map_array); + } + + ebitmap_destroy(&p->filename_trans_ttypes); + ebitmap_destroy(&p->policycaps); + ebitmap_destroy(&p->permissive_map); +} + +/* + * 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) { + pr_err("SELinux: out of memory on SID table init\n"); + goto out; + } + + head = p->ocontexts[OCON_ISID]; + for (c = head; c; c = c->next) { + rc = -EINVAL; + if (!c->context[0].user) { + pr_err("SELinux: SID %s was never defined.\n", + c->u.name); + goto out; + } + + rc = sidtab_insert(s, c->sid[0], &c->context[0]); + if (rc) { + pr_err("SELinux: unable to load initial SID %s.\n", + c->u.name); + goto out; + } + } + rc = 0; +out: + return rc; +} + +int policydb_class_isvalid(struct policydb *p, unsigned int class) +{ + if (!class || class > p->p_classes.nprim) + return 0; + return 1; +} + +int policydb_role_isvalid(struct policydb *p, unsigned int role) +{ + if (!role || role > p->p_roles.nprim) + return 0; + return 1; +} + +int policydb_type_isvalid(struct policydb *p, unsigned int type) +{ + if (!type || type > p->p_types.nprim) + return 0; + return 1; +} + +/* + * 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 (!role || !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) +{ + __le32 buf[2]; + u32 items; + int rc; + + rc = next_entry(buf, fp, sizeof(u32)); + if (rc) + goto out; + + rc = -EINVAL; + items = le32_to_cpu(buf[0]); + if (items > ARRAY_SIZE(buf)) { + pr_err("SELinux: mls: range overflow\n"); + goto out; + } + + rc = next_entry(buf, fp, sizeof(u32) * items); + if (rc) { + pr_err("SELinux: 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) { + pr_err("SELinux: mls: error reading low categories\n"); + goto out; + } + if (items > 1) { + rc = ebitmap_read(&r->level[1].cat, fp); + if (rc) { + pr_err("SELinux: mls: error reading high categories\n"); + goto bad_high; + } + } else { + rc = ebitmap_cpy(&r->level[1].cat, &r->level[0].cat); + if (rc) { + pr_err("SELinux: mls: out of memory\n"); + goto bad_high; + } + } + + return 0; +bad_high: + ebitmap_destroy(&r->level[0].cat); +out: + return rc; +} + +/* + * 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) +{ + __le32 buf[3]; + int rc; + + rc = next_entry(buf, fp, sizeof buf); + if (rc) { + pr_err("SELinux: 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) { + rc = mls_read_range_helper(&c->range, fp); + if (rc) { + pr_err("SELinux: error reading MLS range of context\n"); + goto out; + } + } + + rc = -EINVAL; + if (!policydb_context_isvalid(p, c)) { + pr_err("SELinux: invalid security context\n"); + context_destroy(c); + goto out; + } + rc = 0; +out: + return rc; +} + +/* + * The following *_read functions are used to + * read the symbol data from a policy database + * binary representation file. + */ + +static int str_read(char **strp, gfp_t flags, void *fp, u32 len) +{ + int rc; + char *str; + + if ((len == 0) || (len == (u32)-1)) + return -EINVAL; + + str = kmalloc(len + 1, flags | __GFP_NOWARN); + if (!str) + return -ENOMEM; + + /* it's expected the caller should free the str */ + *strp = str; + + rc = next_entry(str, fp, len); + if (rc) + return rc; + + str[len] = '\0'; + return 0; +} + +static int perm_read(struct policydb *p, struct hashtab *h, void *fp) +{ + char *key = NULL; + struct perm_datum *perdatum; + int rc; + __le32 buf[2]; + u32 len; + + perdatum = kzalloc(sizeof(*perdatum), GFP_KERNEL); + if (!perdatum) + return -ENOMEM; + + rc = next_entry(buf, fp, sizeof buf); + if (rc) + goto bad; + + len = le32_to_cpu(buf[0]); + perdatum->value = le32_to_cpu(buf[1]); + + rc = str_read(&key, GFP_KERNEL, fp, len); + if (rc) + goto bad; + + rc = hashtab_insert(h, key, perdatum); + if (rc) + goto bad; + + return 0; +bad: + perm_destroy(key, perdatum, NULL); + return rc; +} + +static int common_read(struct policydb *p, struct hashtab *h, void *fp) +{ + char *key = NULL; + struct common_datum *comdatum; + __le32 buf[4]; + u32 len, nel; + int i, rc; + + comdatum = kzalloc(sizeof(*comdatum), GFP_KERNEL); + if (!comdatum) + return -ENOMEM; + + rc = next_entry(buf, fp, sizeof buf); + if (rc) + 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]); + + rc = str_read(&key, GFP_KERNEL, fp, len); + if (rc) + goto bad; + + 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; + return 0; +bad: + common_destroy(key, comdatum, NULL); + return rc; +} + +static void type_set_init(struct type_set *t) +{ + ebitmap_init(&t->types); + ebitmap_init(&t->negset); +} + +static int type_set_read(struct type_set *t, void *fp) +{ + __le32 buf[1]; + int rc; + + if (ebitmap_read(&t->types, fp)) + return -EINVAL; + if (ebitmap_read(&t->negset, fp)) + return -EINVAL; + + rc = next_entry(buf, fp, sizeof(u32)); + if (rc < 0) + return -EINVAL; + t->flags = le32_to_cpu(buf[0]); + + return 0; +} + + +static int read_cons_helper(struct policydb *p, + struct constraint_node **nodep, + int ncons, int allowxtarget, void *fp) +{ + struct constraint_node *c, *lc; + struct constraint_expr *e, *le; + __le32 buf[3]; + u32 nexpr; + int rc, i, j, depth; + + lc = NULL; + for (i = 0; i < ncons; i++) { + c = kzalloc(sizeof(*c), GFP_KERNEL); + if (!c) + return -ENOMEM; + + if (lc) + lc->next = c; + else + *nodep = c; + + rc = next_entry(buf, fp, (sizeof(u32) * 2)); + if (rc) + 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 = kzalloc(sizeof(*e), GFP_KERNEL); + if (!e) + return -ENOMEM; + + if (le) + le->next = e; + else + c->expr = e; + + rc = next_entry(buf, fp, (sizeof(u32) * 3)); + if (rc) + 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++; + rc = ebitmap_read(&e->names, fp); + if (rc) + return rc; + if (p->policyvers >= + POLICYDB_VERSION_CONSTRAINT_NAMES) { + e->type_names = kzalloc(sizeof + (*e->type_names), + GFP_KERNEL); + if (!e->type_names) + return -ENOMEM; + type_set_init(e->type_names); + rc = type_set_read(e->type_names, fp); + if (rc) + return rc; + } + 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; + __le32 buf[6]; + u32 len, len2, ncons, nel; + int i, rc; + + cladatum = kzalloc(sizeof(*cladatum), GFP_KERNEL); + if (!cladatum) + return -ENOMEM; + + rc = next_entry(buf, fp, sizeof(u32)*6); + if (rc) + 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]); + + rc = str_read(&key, GFP_KERNEL, fp, len); + if (rc) + goto bad; + + if (len2) { + rc = str_read(&cladatum->comkey, GFP_KERNEL, fp, len2); + if (rc) + goto bad; + + rc = -EINVAL; + cladatum->comdatum = hashtab_search(p->p_commons.table, cladatum->comkey); + if (!cladatum->comdatum) { + pr_err("SELinux: unknown common %s\n", + cladatum->comkey); + goto bad; + } + } + for (i = 0; i < nel; i++) { + rc = perm_read(p, cladatum->permissions.table, fp); + if (rc) + goto bad; + } + + rc = read_cons_helper(p, &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) + goto bad; + ncons = le32_to_cpu(buf[0]); + rc = read_cons_helper(p, &cladatum->validatetrans, + ncons, 1, fp); + if (rc) + goto bad; + } + + if (p->policyvers >= POLICYDB_VERSION_NEW_OBJECT_DEFAULTS) { + rc = next_entry(buf, fp, sizeof(u32) * 3); + if (rc) + goto bad; + + cladatum->default_user = le32_to_cpu(buf[0]); + cladatum->default_role = le32_to_cpu(buf[1]); + cladatum->default_range = le32_to_cpu(buf[2]); + } + + if (p->policyvers >= POLICYDB_VERSION_DEFAULT_TYPE) { + rc = next_entry(buf, fp, sizeof(u32) * 1); + if (rc) + goto bad; + cladatum->default_type = le32_to_cpu(buf[0]); + } + + rc = hashtab_insert(h, key, cladatum); + if (rc) + goto bad; + + return 0; +bad: + cls_destroy(key, cladatum, NULL); + return rc; +} + +static int role_read(struct policydb *p, struct hashtab *h, void *fp) +{ + char *key = NULL; + struct role_datum *role; + int rc, to_read = 2; + __le32 buf[3]; + u32 len; + + role = kzalloc(sizeof(*role), GFP_KERNEL); + if (!role) + return -ENOMEM; + + if (p->policyvers >= POLICYDB_VERSION_BOUNDARY) + to_read = 3; + + rc = next_entry(buf, fp, sizeof(buf[0]) * to_read); + if (rc) + goto bad; + + len = le32_to_cpu(buf[0]); + role->value = le32_to_cpu(buf[1]); + if (p->policyvers >= POLICYDB_VERSION_BOUNDARY) + role->bounds = le32_to_cpu(buf[2]); + + rc = str_read(&key, GFP_KERNEL, fp, len); + if (rc) + goto bad; + + 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) { + rc = -EINVAL; + if (role->value != OBJECT_R_VAL) { + pr_err("SELinux: Role %s has wrong value %d\n", + OBJECT_R, role->value); + goto bad; + } + rc = 0; + goto bad; + } + + rc = hashtab_insert(h, key, role); + if (rc) + goto bad; + return 0; +bad: + role_destroy(key, role, NULL); + return rc; +} + +static int type_read(struct policydb *p, struct hashtab *h, void *fp) +{ + char *key = NULL; + struct type_datum *typdatum; + int rc, to_read = 3; + __le32 buf[4]; + u32 len; + + typdatum = kzalloc(sizeof(*typdatum), GFP_KERNEL); + if (!typdatum) + return -ENOMEM; + + if (p->policyvers >= POLICYDB_VERSION_BOUNDARY) + to_read = 4; + + rc = next_entry(buf, fp, sizeof(buf[0]) * to_read); + if (rc) + goto bad; + + len = le32_to_cpu(buf[0]); + typdatum->value = le32_to_cpu(buf[1]); + if (p->policyvers >= POLICYDB_VERSION_BOUNDARY) { + u32 prop = le32_to_cpu(buf[2]); + + if (prop & TYPEDATUM_PROPERTY_PRIMARY) + typdatum->primary = 1; + if (prop & TYPEDATUM_PROPERTY_ATTRIBUTE) + typdatum->attribute = 1; + + typdatum->bounds = le32_to_cpu(buf[3]); + } else { + typdatum->primary = le32_to_cpu(buf[2]); + } + + rc = str_read(&key, GFP_KERNEL, fp, len); + if (rc) + goto bad; + + rc = hashtab_insert(h, key, typdatum); + if (rc) + goto bad; + return 0; +bad: + type_destroy(key, typdatum, NULL); + return rc; +} + + +/* + * Read a MLS level structure from a policydb binary + * representation file. + */ +static int mls_read_level(struct mls_level *lp, void *fp) +{ + __le32 buf[1]; + int rc; + + memset(lp, 0, sizeof(*lp)); + + rc = next_entry(buf, fp, sizeof buf); + if (rc) { + pr_err("SELinux: mls: truncated level\n"); + return rc; + } + lp->sens = le32_to_cpu(buf[0]); + + rc = ebitmap_read(&lp->cat, fp); + if (rc) { + pr_err("SELinux: mls: error reading level categories\n"); + return rc; + } + return 0; +} + +static int user_read(struct policydb *p, struct hashtab *h, void *fp) +{ + char *key = NULL; + struct user_datum *usrdatum; + int rc, to_read = 2; + __le32 buf[3]; + u32 len; + + usrdatum = kzalloc(sizeof(*usrdatum), GFP_KERNEL); + if (!usrdatum) + return -ENOMEM; + + if (p->policyvers >= POLICYDB_VERSION_BOUNDARY) + to_read = 3; + + rc = next_entry(buf, fp, sizeof(buf[0]) * to_read); + if (rc) + goto bad; + + len = le32_to_cpu(buf[0]); + usrdatum->value = le32_to_cpu(buf[1]); + if (p->policyvers >= POLICYDB_VERSION_BOUNDARY) + usrdatum->bounds = le32_to_cpu(buf[2]); + + rc = str_read(&key, GFP_KERNEL, fp, len); + if (rc) + goto bad; + + 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; + return 0; +bad: + user_destroy(key, usrdatum, NULL); + return rc; +} + +static int sens_read(struct policydb *p, struct hashtab *h, void *fp) +{ + char *key = NULL; + struct level_datum *levdatum; + int rc; + __le32 buf[2]; + u32 len; + + levdatum = kzalloc(sizeof(*levdatum), GFP_ATOMIC); + if (!levdatum) + return -ENOMEM; + + rc = next_entry(buf, fp, sizeof buf); + if (rc) + goto bad; + + len = le32_to_cpu(buf[0]); + levdatum->isalias = le32_to_cpu(buf[1]); + + rc = str_read(&key, GFP_ATOMIC, fp, len); + if (rc) + goto bad; + + rc = -ENOMEM; + levdatum->level = kmalloc(sizeof(*levdatum->level), GFP_ATOMIC); + if (!levdatum->level) + goto bad; + + rc = mls_read_level(levdatum->level, fp); + if (rc) + goto bad; + + rc = hashtab_insert(h, key, levdatum); + if (rc) + goto bad; + return 0; +bad: + sens_destroy(key, levdatum, NULL); + return rc; +} + +static int cat_read(struct policydb *p, struct hashtab *h, void *fp) +{ + char *key = NULL; + struct cat_datum *catdatum; + int rc; + __le32 buf[3]; + u32 len; + + catdatum = kzalloc(sizeof(*catdatum), GFP_ATOMIC); + if (!catdatum) + return -ENOMEM; + + rc = next_entry(buf, fp, sizeof buf); + if (rc) + goto bad; + + len = le32_to_cpu(buf[0]); + catdatum->value = le32_to_cpu(buf[1]); + catdatum->isalias = le32_to_cpu(buf[2]); + + rc = str_read(&key, GFP_ATOMIC, fp, len); + if (rc) + goto bad; + + rc = hashtab_insert(h, key, catdatum); + if (rc) + goto bad; + return 0; +bad: + cat_destroy(key, catdatum, NULL); + return rc; +} + +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, +}; + +static int user_bounds_sanity_check(void *key, void *datum, void *datap) +{ + struct user_datum *upper, *user; + struct policydb *p = datap; + int depth = 0; + + upper = user = datum; + while (upper->bounds) { + struct ebitmap_node *node; + unsigned long bit; + + if (++depth == POLICYDB_BOUNDS_MAXDEPTH) { + pr_err("SELinux: user %s: " + "too deep or looped boundary", + (char *) key); + return -EINVAL; + } + + upper = p->user_val_to_struct[upper->bounds - 1]; + ebitmap_for_each_positive_bit(&user->roles, node, bit) { + if (ebitmap_get_bit(&upper->roles, bit)) + continue; + + pr_err("SELinux: boundary violated policy: " + "user=%s role=%s bounds=%s\n", + sym_name(p, SYM_USERS, user->value - 1), + sym_name(p, SYM_ROLES, bit), + sym_name(p, SYM_USERS, upper->value - 1)); + + return -EINVAL; + } + } + + return 0; +} + +static int role_bounds_sanity_check(void *key, void *datum, void *datap) +{ + struct role_datum *upper, *role; + struct policydb *p = datap; + int depth = 0; + + upper = role = datum; + while (upper->bounds) { + struct ebitmap_node *node; + unsigned long bit; + + if (++depth == POLICYDB_BOUNDS_MAXDEPTH) { + pr_err("SELinux: role %s: " + "too deep or looped bounds\n", + (char *) key); + return -EINVAL; + } + + upper = p->role_val_to_struct[upper->bounds - 1]; + ebitmap_for_each_positive_bit(&role->types, node, bit) { + if (ebitmap_get_bit(&upper->types, bit)) + continue; + + pr_err("SELinux: boundary violated policy: " + "role=%s type=%s bounds=%s\n", + sym_name(p, SYM_ROLES, role->value - 1), + sym_name(p, SYM_TYPES, bit), + sym_name(p, SYM_ROLES, upper->value - 1)); + + return -EINVAL; + } + } + + return 0; +} + +static int type_bounds_sanity_check(void *key, void *datum, void *datap) +{ + struct type_datum *upper; + struct policydb *p = datap; + int depth = 0; + + upper = datum; + while (upper->bounds) { + if (++depth == POLICYDB_BOUNDS_MAXDEPTH) { + pr_err("SELinux: type %s: " + "too deep or looped boundary\n", + (char *) key); + return -EINVAL; + } + + upper = flex_array_get_ptr(p->type_val_to_struct_array, + upper->bounds - 1); + BUG_ON(!upper); + + if (upper->attribute) { + pr_err("SELinux: type %s: " + "bounded by attribute %s", + (char *) key, + sym_name(p, SYM_TYPES, upper->value - 1)); + return -EINVAL; + } + } + + return 0; +} + +static int policydb_bounds_sanity_check(struct policydb *p) +{ + int rc; + + if (p->policyvers < POLICYDB_VERSION_BOUNDARY) + return 0; + + rc = hashtab_map(p->p_users.table, + user_bounds_sanity_check, p); + if (rc) + return rc; + + rc = hashtab_map(p->p_roles.table, + role_bounds_sanity_check, p); + if (rc) + return rc; + + rc = hashtab_map(p->p_types.table, + type_bounds_sanity_check, p); + if (rc) + return rc; + + return 0; +} + +u16 string_to_security_class(struct policydb *p, const char *name) +{ + struct class_datum *cladatum; + + cladatum = hashtab_search(p->p_classes.table, name); + if (!cladatum) + return 0; + + return cladatum->value; +} + +u32 string_to_av_perm(struct policydb *p, u16 tclass, const char *name) +{ + struct class_datum *cladatum; + struct perm_datum *perdatum = NULL; + struct common_datum *comdatum; + + if (!tclass || tclass > p->p_classes.nprim) + return 0; + + cladatum = p->class_val_to_struct[tclass-1]; + comdatum = cladatum->comdatum; + if (comdatum) + perdatum = hashtab_search(comdatum->permissions.table, + name); + if (!perdatum) + perdatum = hashtab_search(cladatum->permissions.table, + name); + if (!perdatum) + return 0; + + return 1U << (perdatum->value-1); +} + +static int range_read(struct policydb *p, void *fp) +{ + struct range_trans *rt = NULL; + struct mls_range *r = NULL; + int i, rc; + __le32 buf[2]; + u32 nel; + + if (p->policyvers < POLICYDB_VERSION_MLS) + return 0; + + rc = next_entry(buf, fp, sizeof(u32)); + if (rc) + return rc; + + nel = le32_to_cpu(buf[0]); + for (i = 0; i < nel; i++) { + rc = -ENOMEM; + rt = kzalloc(sizeof(*rt), GFP_KERNEL); + if (!rt) + goto out; + + rc = next_entry(buf, fp, (sizeof(u32) * 2)); + if (rc) + goto out; + + rt->source_type = le32_to_cpu(buf[0]); + rt->target_type = le32_to_cpu(buf[1]); + if (p->policyvers >= POLICYDB_VERSION_RANGETRANS) { + rc = next_entry(buf, fp, sizeof(u32)); + if (rc) + goto out; + rt->target_class = le32_to_cpu(buf[0]); + } else + rt->target_class = p->process_class; + + rc = -EINVAL; + if (!policydb_type_isvalid(p, rt->source_type) || + !policydb_type_isvalid(p, rt->target_type) || + !policydb_class_isvalid(p, rt->target_class)) + goto out; + + rc = -ENOMEM; + r = kzalloc(sizeof(*r), GFP_KERNEL); + if (!r) + goto out; + + rc = mls_read_range_helper(r, fp); + if (rc) + goto out; + + rc = -EINVAL; + if (!mls_range_isvalid(p, r)) { + pr_warn("SELinux: rangetrans: invalid range\n"); + goto out; + } + + rc = hashtab_insert(p->range_tr, rt, r); + if (rc) + goto out; + + rt = NULL; + r = NULL; + } + hash_eval(p->range_tr, "rangetr"); + rc = 0; +out: + kfree(rt); + kfree(r); + return rc; +} + +static int filename_trans_read(struct policydb *p, void *fp) +{ + struct filename_trans *ft; + struct filename_trans_datum *otype; + char *name; + u32 nel, len; + __le32 buf[4]; + int rc, i; + + if (p->policyvers < POLICYDB_VERSION_FILENAME_TRANS) + return 0; + + rc = next_entry(buf, fp, sizeof(u32)); + if (rc) + return rc; + nel = le32_to_cpu(buf[0]); + + for (i = 0; i < nel; i++) { + otype = NULL; + name = NULL; + + rc = -ENOMEM; + ft = kzalloc(sizeof(*ft), GFP_KERNEL); + if (!ft) + goto out; + + rc = -ENOMEM; + otype = kmalloc(sizeof(*otype), GFP_KERNEL); + if (!otype) + goto out; + + /* length of the path component string */ + rc = next_entry(buf, fp, sizeof(u32)); + if (rc) + goto out; + len = le32_to_cpu(buf[0]); + + /* path component string */ + rc = str_read(&name, GFP_KERNEL, fp, len); + if (rc) + goto out; + + ft->name = name; + + rc = next_entry(buf, fp, sizeof(u32) * 4); + if (rc) + goto out; + + ft->stype = le32_to_cpu(buf[0]); + ft->ttype = le32_to_cpu(buf[1]); + ft->tclass = le32_to_cpu(buf[2]); + + otype->otype = le32_to_cpu(buf[3]); + + rc = ebitmap_set_bit(&p->filename_trans_ttypes, ft->ttype, 1); + if (rc) + goto out; + + rc = hashtab_insert(p->filename_trans, ft, otype); + if (rc) { + /* + * Do not return -EEXIST to the caller, or the system + * will not boot. + */ + if (rc != -EEXIST) + goto out; + /* But free memory to avoid memory leak. */ + kfree(ft); + kfree(name); + kfree(otype); + } + } + hash_eval(p->filename_trans, "filenametr"); + return 0; +out: + kfree(ft); + kfree(name); + kfree(otype); + + return rc; +} + +static int genfs_read(struct policydb *p, void *fp) +{ + int i, j, rc; + u32 nel, nel2, len, len2; + __le32 buf[1]; + struct ocontext *l, *c; + struct ocontext *newc = NULL; + struct genfs *genfs_p, *genfs; + struct genfs *newgenfs = NULL; + + rc = next_entry(buf, fp, sizeof(u32)); + if (rc) + return rc; + nel = le32_to_cpu(buf[0]); + + for (i = 0; i < nel; i++) { + rc = next_entry(buf, fp, sizeof(u32)); + if (rc) + goto out; + len = le32_to_cpu(buf[0]); + + rc = -ENOMEM; + newgenfs = kzalloc(sizeof(*newgenfs), GFP_KERNEL); + if (!newgenfs) + goto out; + + rc = str_read(&newgenfs->fstype, GFP_KERNEL, fp, len); + if (rc) + goto out; + + for (genfs_p = NULL, genfs = p->genfs; genfs; + genfs_p = genfs, genfs = genfs->next) { + rc = -EINVAL; + if (strcmp(newgenfs->fstype, genfs->fstype) == 0) { + pr_err("SELinux: dup genfs fstype %s\n", + newgenfs->fstype); + goto out; + } + if (strcmp(newgenfs->fstype, genfs->fstype) < 0) + break; + } + newgenfs->next = genfs; + if (genfs_p) + genfs_p->next = newgenfs; + else + p->genfs = newgenfs; + genfs = newgenfs; + newgenfs = NULL; + + rc = next_entry(buf, fp, sizeof(u32)); + if (rc) + goto out; + + nel2 = le32_to_cpu(buf[0]); + for (j = 0; j < nel2; j++) { + rc = next_entry(buf, fp, sizeof(u32)); + if (rc) + goto out; + len = le32_to_cpu(buf[0]); + + rc = -ENOMEM; + newc = kzalloc(sizeof(*newc), GFP_KERNEL); + if (!newc) + goto out; + + rc = str_read(&newc->u.name, GFP_KERNEL, fp, len); + if (rc) + goto out; + + rc = next_entry(buf, fp, sizeof(u32)); + if (rc) + goto out; + + newc->v.sclass = le32_to_cpu(buf[0]); + rc = context_read_and_validate(&newc->context[0], p, fp); + if (rc) + goto out; + + for (l = NULL, c = genfs->head; c; + l = c, c = c->next) { + rc = -EINVAL; + if (!strcmp(newc->u.name, c->u.name) && + (!c->v.sclass || !newc->v.sclass || + newc->v.sclass == c->v.sclass)) { + pr_err("SELinux: dup genfs entry (%s,%s)\n", + genfs->fstype, c->u.name); + goto out; + } + len = strlen(newc->u.name); + len2 = strlen(c->u.name); + if (len > len2) + break; + } + + newc->next = c; + if (l) + l->next = newc; + else + genfs->head = newc; + newc = NULL; + } + } + rc = 0; +out: + if (newgenfs) { + kfree(newgenfs->fstype); + kfree(newgenfs); + } + ocontext_destroy(newc, OCON_FSUSE); + + return rc; +} + +static int ocontext_read(struct policydb *p, struct policydb_compat_info *info, + void *fp) +{ + int i, j, rc; + u32 nel, len; + __be64 prefixbuf[1]; + __le32 buf[3]; + struct ocontext *l, *c; + u32 nodebuf[8]; + + for (i = 0; i < info->ocon_num; i++) { + rc = next_entry(buf, fp, sizeof(u32)); + if (rc) + goto out; + nel = le32_to_cpu(buf[0]); + + l = NULL; + for (j = 0; j < nel; j++) { + rc = -ENOMEM; + c = kzalloc(sizeof(*c), GFP_KERNEL); + if (!c) + goto out; + if (l) + l->next = c; + else + p->ocontexts[i] = c; + l = c; + + switch (i) { + case OCON_ISID: + rc = next_entry(buf, fp, sizeof(u32)); + if (rc) + goto out; + + c->sid[0] = le32_to_cpu(buf[0]); + rc = context_read_and_validate(&c->context[0], p, fp); + if (rc) + goto out; + break; + case OCON_FS: + case OCON_NETIF: + rc = next_entry(buf, fp, sizeof(u32)); + if (rc) + goto out; + len = le32_to_cpu(buf[0]); + + rc = str_read(&c->u.name, GFP_KERNEL, fp, len); + if (rc) + goto out; + + rc = context_read_and_validate(&c->context[0], p, fp); + if (rc) + goto out; + rc = context_read_and_validate(&c->context[1], p, fp); + if (rc) + goto out; + break; + case OCON_PORT: + rc = next_entry(buf, fp, sizeof(u32)*3); + if (rc) + goto out; + 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 out; + break; + case OCON_NODE: + rc = next_entry(nodebuf, fp, sizeof(u32) * 2); + if (rc) + goto out; + c->u.node.addr = nodebuf[0]; /* network order */ + c->u.node.mask = nodebuf[1]; /* network order */ + rc = context_read_and_validate(&c->context[0], p, fp); + if (rc) + goto out; + break; + case OCON_FSUSE: + rc = next_entry(buf, fp, sizeof(u32)*2); + if (rc) + goto out; + + rc = -EINVAL; + c->v.behavior = le32_to_cpu(buf[0]); + /* Determined at runtime, not in policy DB. */ + if (c->v.behavior == SECURITY_FS_USE_MNTPOINT) + goto out; + if (c->v.behavior > SECURITY_FS_USE_MAX) + goto out; + + len = le32_to_cpu(buf[1]); + rc = str_read(&c->u.name, GFP_KERNEL, fp, len); + if (rc) + goto out; + + rc = context_read_and_validate(&c->context[0], p, fp); + if (rc) + goto out; + break; + case OCON_NODE6: { + int k; + + rc = next_entry(nodebuf, fp, sizeof(u32) * 8); + if (rc) + goto out; + for (k = 0; k < 4; k++) + c->u.node6.addr[k] = nodebuf[k]; + for (k = 0; k < 4; k++) + c->u.node6.mask[k] = nodebuf[k+4]; + rc = context_read_and_validate(&c->context[0], p, fp); + if (rc) + goto out; + break; + } + case OCON_IBPKEY: { + u32 pkey_lo, pkey_hi; + + rc = next_entry(prefixbuf, fp, sizeof(u64)); + if (rc) + goto out; + + /* we need to have subnet_prefix in CPU order */ + c->u.ibpkey.subnet_prefix = be64_to_cpu(prefixbuf[0]); + + rc = next_entry(buf, fp, sizeof(u32) * 2); + if (rc) + goto out; + + pkey_lo = le32_to_cpu(buf[0]); + pkey_hi = le32_to_cpu(buf[1]); + + if (pkey_lo > U16_MAX || pkey_hi > U16_MAX) { + rc = -EINVAL; + goto out; + } + + c->u.ibpkey.low_pkey = pkey_lo; + c->u.ibpkey.high_pkey = pkey_hi; + + rc = context_read_and_validate(&c->context[0], + p, + fp); + if (rc) + goto out; + break; + } + case OCON_IBENDPORT: { + u32 port; + + rc = next_entry(buf, fp, sizeof(u32) * 2); + if (rc) + goto out; + len = le32_to_cpu(buf[0]); + + rc = str_read(&c->u.ibendport.dev_name, GFP_KERNEL, fp, len); + if (rc) + goto out; + + port = le32_to_cpu(buf[1]); + if (port > U8_MAX || port == 0) { + rc = -EINVAL; + goto out; + } + + c->u.ibendport.port = port; + + rc = context_read_and_validate(&c->context[0], + p, + fp); + if (rc) + goto out; + break; + } /* end case */ + } /* end switch */ + } + } + rc = 0; +out: + return rc; +} + +/* + * 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; + int i, j, rc; + __le32 buf[4]; + u32 len, nprim, nel; + + char *policydb_str; + struct policydb_compat_info *info; + + rc = policydb_init(p); + if (rc) + return rc; + + /* Read the magic number and string length. */ + rc = next_entry(buf, fp, sizeof(u32) * 2); + if (rc) + goto bad; + + rc = -EINVAL; + if (le32_to_cpu(buf[0]) != POLICYDB_MAGIC) { + pr_err("SELinux: policydb magic number 0x%x does " + "not match expected magic number 0x%x\n", + le32_to_cpu(buf[0]), POLICYDB_MAGIC); + goto bad; + } + + rc = -EINVAL; + len = le32_to_cpu(buf[1]); + if (len != strlen(POLICYDB_STRING)) { + pr_err("SELinux: policydb string length %d does not " + "match expected length %zu\n", + len, strlen(POLICYDB_STRING)); + goto bad; + } + + rc = -ENOMEM; + policydb_str = kmalloc(len + 1, GFP_KERNEL); + if (!policydb_str) { + pr_err("SELinux: unable to allocate memory for policydb " + "string of length %d\n", len); + goto bad; + } + + rc = next_entry(policydb_str, fp, len); + if (rc) { + pr_err("SELinux: truncated policydb string identifier\n"); + kfree(policydb_str); + goto bad; + } + + rc = -EINVAL; + policydb_str[len] = '\0'; + if (strcmp(policydb_str, POLICYDB_STRING)) { + pr_err("SELinux: 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 and table sizes. */ + rc = next_entry(buf, fp, sizeof(u32)*4); + if (rc) + goto bad; + + rc = -EINVAL; + p->policyvers = le32_to_cpu(buf[0]); + if (p->policyvers < POLICYDB_VERSION_MIN || + p->policyvers > POLICYDB_VERSION_MAX) { + pr_err("SELinux: policydb version %d does not match " + "my version range %d-%d\n", + le32_to_cpu(buf[0]), POLICYDB_VERSION_MIN, POLICYDB_VERSION_MAX); + goto bad; + } + + if ((le32_to_cpu(buf[1]) & POLICYDB_CONFIG_MLS)) { + p->mls_enabled = 1; + + rc = -EINVAL; + if (p->policyvers < POLICYDB_VERSION_MLS) { + pr_err("SELinux: security policydb version %d " + "(MLS) not backwards compatible\n", + p->policyvers); + goto bad; + } + } + p->reject_unknown = !!(le32_to_cpu(buf[1]) & REJECT_UNKNOWN); + p->allow_unknown = !!(le32_to_cpu(buf[1]) & ALLOW_UNKNOWN); + + if (p->policyvers >= POLICYDB_VERSION_POLCAP) { + rc = ebitmap_read(&p->policycaps, fp); + if (rc) + goto bad; + } + + if (p->policyvers >= POLICYDB_VERSION_PERMISSIVE) { + rc = ebitmap_read(&p->permissive_map, fp); + if (rc) + goto bad; + } + + rc = -EINVAL; + info = policydb_lookup_compat(p->policyvers); + if (!info) { + pr_err("SELinux: unable to find policy compat info " + "for version %d\n", p->policyvers); + goto bad; + } + + rc = -EINVAL; + if (le32_to_cpu(buf[2]) != info->sym_num || + le32_to_cpu(buf[3]) != info->ocon_num) { + pr_err("SELinux: policydb table sizes (%d,%d) do " + "not match mine (%d,%d)\n", le32_to_cpu(buf[2]), + le32_to_cpu(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) + 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 = -EINVAL; + p->process_class = string_to_security_class(p, "process"); + if (!p->process_class) + goto bad; + + rc = avtab_read(&p->te_avtab, fp, p); + 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) + goto bad; + nel = le32_to_cpu(buf[0]); + ltr = NULL; + for (i = 0; i < nel; i++) { + rc = -ENOMEM; + tr = kzalloc(sizeof(*tr), GFP_KERNEL); + if (!tr) + goto bad; + if (ltr) + ltr->next = tr; + else + p->role_tr = tr; + rc = next_entry(buf, fp, sizeof(u32)*3); + if (rc) + goto bad; + + rc = -EINVAL; + tr->role = le32_to_cpu(buf[0]); + tr->type = le32_to_cpu(buf[1]); + tr->new_role = le32_to_cpu(buf[2]); + if (p->policyvers >= POLICYDB_VERSION_ROLETRANS) { + rc = next_entry(buf, fp, sizeof(u32)); + if (rc) + goto bad; + tr->tclass = le32_to_cpu(buf[0]); + } else + tr->tclass = p->process_class; + + rc = -EINVAL; + if (!policydb_role_isvalid(p, tr->role) || + !policydb_type_isvalid(p, tr->type) || + !policydb_class_isvalid(p, tr->tclass) || + !policydb_role_isvalid(p, tr->new_role)) + goto bad; + ltr = tr; + } + + rc = next_entry(buf, fp, sizeof(u32)); + if (rc) + goto bad; + nel = le32_to_cpu(buf[0]); + lra = NULL; + for (i = 0; i < nel; i++) { + rc = -ENOMEM; + ra = kzalloc(sizeof(*ra), GFP_KERNEL); + if (!ra) + goto bad; + if (lra) + lra->next = ra; + else + p->role_allow = ra; + rc = next_entry(buf, fp, sizeof(u32)*2); + if (rc) + goto bad; + + rc = -EINVAL; + ra->role = le32_to_cpu(buf[0]); + ra->new_role = le32_to_cpu(buf[1]); + if (!policydb_role_isvalid(p, ra->role) || + !policydb_role_isvalid(p, ra->new_role)) + goto bad; + lra = ra; + } + + rc = filename_trans_read(p, fp); + if (rc) + goto bad; + + rc = policydb_index(p); + if (rc) + goto bad; + + rc = -EINVAL; + p->process_trans_perms = string_to_av_perm(p, p->process_class, "transition"); + p->process_trans_perms |= string_to_av_perm(p, p->process_class, "dyntransition"); + if (!p->process_trans_perms) + goto bad; + + rc = ocontext_read(p, info, fp); + if (rc) + goto bad; + + rc = genfs_read(p, fp); + if (rc) + goto bad; + + rc = range_read(p, fp); + if (rc) + goto bad; + + rc = -ENOMEM; + p->type_attr_map_array = flex_array_alloc(sizeof(struct ebitmap), + p->p_types.nprim, + GFP_KERNEL | __GFP_ZERO); + if (!p->type_attr_map_array) + goto bad; + + /* preallocate so we don't have to worry about the put ever failing */ + rc = flex_array_prealloc(p->type_attr_map_array, 0, p->p_types.nprim, + GFP_KERNEL | __GFP_ZERO); + if (rc) + goto bad; + + for (i = 0; i < p->p_types.nprim; i++) { + struct ebitmap *e = flex_array_get(p->type_attr_map_array, i); + + BUG_ON(!e); + ebitmap_init(e); + if (p->policyvers >= POLICYDB_VERSION_AVTAB) { + rc = ebitmap_read(e, fp); + if (rc) + goto bad; + } + /* add the type itself as the degenerate case */ + rc = ebitmap_set_bit(e, i, 1); + if (rc) + goto bad; + } + + rc = policydb_bounds_sanity_check(p); + if (rc) + goto bad; + + rc = 0; +out: + return rc; +bad: + policydb_destroy(p); + goto out; +} + +/* + * Write a MLS level structure to a policydb binary + * representation file. + */ +static int mls_write_level(struct mls_level *l, void *fp) +{ + __le32 buf[1]; + int rc; + + buf[0] = cpu_to_le32(l->sens); + rc = put_entry(buf, sizeof(u32), 1, fp); + if (rc) + return rc; + + rc = ebitmap_write(&l->cat, fp); + if (rc) + return rc; + + return 0; +} + +/* + * Write a MLS range structure to a policydb binary + * representation file. + */ +static int mls_write_range_helper(struct mls_range *r, void *fp) +{ + __le32 buf[3]; + size_t items; + int rc, eq; + + eq = mls_level_eq(&r->level[1], &r->level[0]); + + if (eq) + items = 2; + else + items = 3; + buf[0] = cpu_to_le32(items-1); + buf[1] = cpu_to_le32(r->level[0].sens); + if (!eq) + buf[2] = cpu_to_le32(r->level[1].sens); + + BUG_ON(items > ARRAY_SIZE(buf)); + + rc = put_entry(buf, sizeof(u32), items, fp); + if (rc) + return rc; + + rc = ebitmap_write(&r->level[0].cat, fp); + if (rc) + return rc; + if (!eq) { + rc = ebitmap_write(&r->level[1].cat, fp); + if (rc) + return rc; + } + + return 0; +} + +static int sens_write(void *vkey, void *datum, void *ptr) +{ + char *key = vkey; + struct level_datum *levdatum = datum; + struct policy_data *pd = ptr; + void *fp = pd->fp; + __le32 buf[2]; + size_t len; + int rc; + + len = strlen(key); + buf[0] = cpu_to_le32(len); + buf[1] = cpu_to_le32(levdatum->isalias); + rc = put_entry(buf, sizeof(u32), 2, fp); + if (rc) + return rc; + + rc = put_entry(key, 1, len, fp); + if (rc) + return rc; + + rc = mls_write_level(levdatum->level, fp); + if (rc) + return rc; + + return 0; +} + +static int cat_write(void *vkey, void *datum, void *ptr) +{ + char *key = vkey; + struct cat_datum *catdatum = datum; + struct policy_data *pd = ptr; + void *fp = pd->fp; + __le32 buf[3]; + size_t len; + int rc; + + len = strlen(key); + buf[0] = cpu_to_le32(len); + buf[1] = cpu_to_le32(catdatum->value); + buf[2] = cpu_to_le32(catdatum->isalias); + rc = put_entry(buf, sizeof(u32), 3, fp); + if (rc) + return rc; + + rc = put_entry(key, 1, len, fp); + if (rc) + return rc; + + return 0; +} + +static int role_trans_write(struct policydb *p, void *fp) +{ + struct role_trans *r = p->role_tr; + struct role_trans *tr; + u32 buf[3]; + size_t nel; + int rc; + + nel = 0; + for (tr = r; tr; tr = tr->next) + nel++; + buf[0] = cpu_to_le32(nel); + rc = put_entry(buf, sizeof(u32), 1, fp); + if (rc) + return rc; + for (tr = r; tr; tr = tr->next) { + buf[0] = cpu_to_le32(tr->role); + buf[1] = cpu_to_le32(tr->type); + buf[2] = cpu_to_le32(tr->new_role); + rc = put_entry(buf, sizeof(u32), 3, fp); + if (rc) + return rc; + if (p->policyvers >= POLICYDB_VERSION_ROLETRANS) { + buf[0] = cpu_to_le32(tr->tclass); + rc = put_entry(buf, sizeof(u32), 1, fp); + if (rc) + return rc; + } + } + + return 0; +} + +static int role_allow_write(struct role_allow *r, void *fp) +{ + struct role_allow *ra; + u32 buf[2]; + size_t nel; + int rc; + + nel = 0; + for (ra = r; ra; ra = ra->next) + nel++; + buf[0] = cpu_to_le32(nel); + rc = put_entry(buf, sizeof(u32), 1, fp); + if (rc) + return rc; + for (ra = r; ra; ra = ra->next) { + buf[0] = cpu_to_le32(ra->role); + buf[1] = cpu_to_le32(ra->new_role); + rc = put_entry(buf, sizeof(u32), 2, fp); + if (rc) + return rc; + } + return 0; +} + +/* + * Write a security context structure + * to a policydb binary representation file. + */ +static int context_write(struct policydb *p, struct context *c, + void *fp) +{ + int rc; + __le32 buf[3]; + + buf[0] = cpu_to_le32(c->user); + buf[1] = cpu_to_le32(c->role); + buf[2] = cpu_to_le32(c->type); + + rc = put_entry(buf, sizeof(u32), 3, fp); + if (rc) + return rc; + + rc = mls_write_range_helper(&c->range, fp); + if (rc) + return rc; + + return 0; +} + +/* + * The following *_write functions are used to + * write the symbol data to a policy database + * binary representation file. + */ + +static int perm_write(void *vkey, void *datum, void *fp) +{ + char *key = vkey; + struct perm_datum *perdatum = datum; + __le32 buf[2]; + size_t len; + int rc; + + len = strlen(key); + buf[0] = cpu_to_le32(len); + buf[1] = cpu_to_le32(perdatum->value); + rc = put_entry(buf, sizeof(u32), 2, fp); + if (rc) + return rc; + + rc = put_entry(key, 1, len, fp); + if (rc) + return rc; + + return 0; +} + +static int common_write(void *vkey, void *datum, void *ptr) +{ + char *key = vkey; + struct common_datum *comdatum = datum; + struct policy_data *pd = ptr; + void *fp = pd->fp; + __le32 buf[4]; + size_t len; + int rc; + + len = strlen(key); + buf[0] = cpu_to_le32(len); + buf[1] = cpu_to_le32(comdatum->value); + buf[2] = cpu_to_le32(comdatum->permissions.nprim); + buf[3] = cpu_to_le32(comdatum->permissions.table->nel); + rc = put_entry(buf, sizeof(u32), 4, fp); + if (rc) + return rc; + + rc = put_entry(key, 1, len, fp); + if (rc) + return rc; + + rc = hashtab_map(comdatum->permissions.table, perm_write, fp); + if (rc) + return rc; + + return 0; +} + +static int type_set_write(struct type_set *t, void *fp) +{ + int rc; + __le32 buf[1]; + + if (ebitmap_write(&t->types, fp)) + return -EINVAL; + if (ebitmap_write(&t->negset, fp)) + return -EINVAL; + + buf[0] = cpu_to_le32(t->flags); + rc = put_entry(buf, sizeof(u32), 1, fp); + if (rc) + return -EINVAL; + + return 0; +} + +static int write_cons_helper(struct policydb *p, struct constraint_node *node, + void *fp) +{ + struct constraint_node *c; + struct constraint_expr *e; + __le32 buf[3]; + u32 nel; + int rc; + + for (c = node; c; c = c->next) { + nel = 0; + for (e = c->expr; e; e = e->next) + nel++; + buf[0] = cpu_to_le32(c->permissions); + buf[1] = cpu_to_le32(nel); + rc = put_entry(buf, sizeof(u32), 2, fp); + if (rc) + return rc; + for (e = c->expr; e; e = e->next) { + buf[0] = cpu_to_le32(e->expr_type); + buf[1] = cpu_to_le32(e->attr); + buf[2] = cpu_to_le32(e->op); + rc = put_entry(buf, sizeof(u32), 3, fp); + if (rc) + return rc; + + switch (e->expr_type) { + case CEXPR_NAMES: + rc = ebitmap_write(&e->names, fp); + if (rc) + return rc; + if (p->policyvers >= + POLICYDB_VERSION_CONSTRAINT_NAMES) { + rc = type_set_write(e->type_names, fp); + if (rc) + return rc; + } + break; + default: + break; + } + } + } + + return 0; +} + +static int class_write(void *vkey, void *datum, void *ptr) +{ + char *key = vkey; + struct class_datum *cladatum = datum; + struct policy_data *pd = ptr; + void *fp = pd->fp; + struct policydb *p = pd->p; + struct constraint_node *c; + __le32 buf[6]; + u32 ncons; + size_t len, len2; + int rc; + + len = strlen(key); + if (cladatum->comkey) + len2 = strlen(cladatum->comkey); + else + len2 = 0; + + ncons = 0; + for (c = cladatum->constraints; c; c = c->next) + ncons++; + + buf[0] = cpu_to_le32(len); + buf[1] = cpu_to_le32(len2); + buf[2] = cpu_to_le32(cladatum->value); + buf[3] = cpu_to_le32(cladatum->permissions.nprim); + if (cladatum->permissions.table) + buf[4] = cpu_to_le32(cladatum->permissions.table->nel); + else + buf[4] = 0; + buf[5] = cpu_to_le32(ncons); + rc = put_entry(buf, sizeof(u32), 6, fp); + if (rc) + return rc; + + rc = put_entry(key, 1, len, fp); + if (rc) + return rc; + + if (cladatum->comkey) { + rc = put_entry(cladatum->comkey, 1, len2, fp); + if (rc) + return rc; + } + + rc = hashtab_map(cladatum->permissions.table, perm_write, fp); + if (rc) + return rc; + + rc = write_cons_helper(p, cladatum->constraints, fp); + if (rc) + return rc; + + /* write out the validatetrans rule */ + ncons = 0; + for (c = cladatum->validatetrans; c; c = c->next) + ncons++; + + buf[0] = cpu_to_le32(ncons); + rc = put_entry(buf, sizeof(u32), 1, fp); + if (rc) + return rc; + + rc = write_cons_helper(p, cladatum->validatetrans, fp); + if (rc) + return rc; + + if (p->policyvers >= POLICYDB_VERSION_NEW_OBJECT_DEFAULTS) { + buf[0] = cpu_to_le32(cladatum->default_user); + buf[1] = cpu_to_le32(cladatum->default_role); + buf[2] = cpu_to_le32(cladatum->default_range); + + rc = put_entry(buf, sizeof(uint32_t), 3, fp); + if (rc) + return rc; + } + + if (p->policyvers >= POLICYDB_VERSION_DEFAULT_TYPE) { + buf[0] = cpu_to_le32(cladatum->default_type); + rc = put_entry(buf, sizeof(uint32_t), 1, fp); + if (rc) + return rc; + } + + return 0; +} + +static int role_write(void *vkey, void *datum, void *ptr) +{ + char *key = vkey; + struct role_datum *role = datum; + struct policy_data *pd = ptr; + void *fp = pd->fp; + struct policydb *p = pd->p; + __le32 buf[3]; + size_t items, len; + int rc; + + len = strlen(key); + items = 0; + buf[items++] = cpu_to_le32(len); + buf[items++] = cpu_to_le32(role->value); + if (p->policyvers >= POLICYDB_VERSION_BOUNDARY) + buf[items++] = cpu_to_le32(role->bounds); + + BUG_ON(items > ARRAY_SIZE(buf)); + + rc = put_entry(buf, sizeof(u32), items, fp); + if (rc) + return rc; + + rc = put_entry(key, 1, len, fp); + if (rc) + return rc; + + rc = ebitmap_write(&role->dominates, fp); + if (rc) + return rc; + + rc = ebitmap_write(&role->types, fp); + if (rc) + return rc; + + return 0; +} + +static int type_write(void *vkey, void *datum, void *ptr) +{ + char *key = vkey; + struct type_datum *typdatum = datum; + struct policy_data *pd = ptr; + struct policydb *p = pd->p; + void *fp = pd->fp; + __le32 buf[4]; + int rc; + size_t items, len; + + len = strlen(key); + items = 0; + buf[items++] = cpu_to_le32(len); + buf[items++] = cpu_to_le32(typdatum->value); + if (p->policyvers >= POLICYDB_VERSION_BOUNDARY) { + u32 properties = 0; + + if (typdatum->primary) + properties |= TYPEDATUM_PROPERTY_PRIMARY; + + if (typdatum->attribute) + properties |= TYPEDATUM_PROPERTY_ATTRIBUTE; + + buf[items++] = cpu_to_le32(properties); + buf[items++] = cpu_to_le32(typdatum->bounds); + } else { + buf[items++] = cpu_to_le32(typdatum->primary); + } + BUG_ON(items > ARRAY_SIZE(buf)); + rc = put_entry(buf, sizeof(u32), items, fp); + if (rc) + return rc; + + rc = put_entry(key, 1, len, fp); + if (rc) + return rc; + + return 0; +} + +static int user_write(void *vkey, void *datum, void *ptr) +{ + char *key = vkey; + struct user_datum *usrdatum = datum; + struct policy_data *pd = ptr; + struct policydb *p = pd->p; + void *fp = pd->fp; + __le32 buf[3]; + size_t items, len; + int rc; + + len = strlen(key); + items = 0; + buf[items++] = cpu_to_le32(len); + buf[items++] = cpu_to_le32(usrdatum->value); + if (p->policyvers >= POLICYDB_VERSION_BOUNDARY) + buf[items++] = cpu_to_le32(usrdatum->bounds); + BUG_ON(items > ARRAY_SIZE(buf)); + rc = put_entry(buf, sizeof(u32), items, fp); + if (rc) + return rc; + + rc = put_entry(key, 1, len, fp); + if (rc) + return rc; + + rc = ebitmap_write(&usrdatum->roles, fp); + if (rc) + return rc; + + rc = mls_write_range_helper(&usrdatum->range, fp); + if (rc) + return rc; + + rc = mls_write_level(&usrdatum->dfltlevel, fp); + if (rc) + return rc; + + return 0; +} + +static int (*write_f[SYM_NUM]) (void *key, void *datum, + void *datap) = +{ + common_write, + class_write, + role_write, + type_write, + user_write, + cond_write_bool, + sens_write, + cat_write, +}; + +static int ocontext_write(struct policydb *p, struct policydb_compat_info *info, + void *fp) +{ + unsigned int i, j, rc; + size_t nel, len; + __be64 prefixbuf[1]; + __le32 buf[3]; + u32 nodebuf[8]; + struct ocontext *c; + for (i = 0; i < info->ocon_num; i++) { + nel = 0; + for (c = p->ocontexts[i]; c; c = c->next) + nel++; + buf[0] = cpu_to_le32(nel); + rc = put_entry(buf, sizeof(u32), 1, fp); + if (rc) + return rc; + for (c = p->ocontexts[i]; c; c = c->next) { + switch (i) { + case OCON_ISID: + buf[0] = cpu_to_le32(c->sid[0]); + rc = put_entry(buf, sizeof(u32), 1, fp); + if (rc) + return rc; + rc = context_write(p, &c->context[0], fp); + if (rc) + return rc; + break; + case OCON_FS: + case OCON_NETIF: + len = strlen(c->u.name); + buf[0] = cpu_to_le32(len); + rc = put_entry(buf, sizeof(u32), 1, fp); + if (rc) + return rc; + rc = put_entry(c->u.name, 1, len, fp); + if (rc) + return rc; + rc = context_write(p, &c->context[0], fp); + if (rc) + return rc; + rc = context_write(p, &c->context[1], fp); + if (rc) + return rc; + break; + case OCON_PORT: + buf[0] = cpu_to_le32(c->u.port.protocol); + buf[1] = cpu_to_le32(c->u.port.low_port); + buf[2] = cpu_to_le32(c->u.port.high_port); + rc = put_entry(buf, sizeof(u32), 3, fp); + if (rc) + return rc; + rc = context_write(p, &c->context[0], fp); + if (rc) + return rc; + break; + case OCON_NODE: + nodebuf[0] = c->u.node.addr; /* network order */ + nodebuf[1] = c->u.node.mask; /* network order */ + rc = put_entry(nodebuf, sizeof(u32), 2, fp); + if (rc) + return rc; + rc = context_write(p, &c->context[0], fp); + if (rc) + return rc; + break; + case OCON_FSUSE: + buf[0] = cpu_to_le32(c->v.behavior); + len = strlen(c->u.name); + buf[1] = cpu_to_le32(len); + rc = put_entry(buf, sizeof(u32), 2, fp); + if (rc) + return rc; + rc = put_entry(c->u.name, 1, len, fp); + if (rc) + return rc; + rc = context_write(p, &c->context[0], fp); + if (rc) + return rc; + break; + case OCON_NODE6: + for (j = 0; j < 4; j++) + nodebuf[j] = c->u.node6.addr[j]; /* network order */ + for (j = 0; j < 4; j++) + nodebuf[j + 4] = c->u.node6.mask[j]; /* network order */ + rc = put_entry(nodebuf, sizeof(u32), 8, fp); + if (rc) + return rc; + rc = context_write(p, &c->context[0], fp); + if (rc) + return rc; + break; + case OCON_IBPKEY: + /* subnet_prefix is in CPU order */ + prefixbuf[0] = cpu_to_be64(c->u.ibpkey.subnet_prefix); + + rc = put_entry(prefixbuf, sizeof(u64), 1, fp); + if (rc) + return rc; + + buf[0] = cpu_to_le32(c->u.ibpkey.low_pkey); + buf[1] = cpu_to_le32(c->u.ibpkey.high_pkey); + + rc = put_entry(buf, sizeof(u32), 2, fp); + if (rc) + return rc; + rc = context_write(p, &c->context[0], fp); + if (rc) + return rc; + break; + case OCON_IBENDPORT: + len = strlen(c->u.ibendport.dev_name); + buf[0] = cpu_to_le32(len); + buf[1] = cpu_to_le32(c->u.ibendport.port); + rc = put_entry(buf, sizeof(u32), 2, fp); + if (rc) + return rc; + rc = put_entry(c->u.ibendport.dev_name, 1, len, fp); + if (rc) + return rc; + rc = context_write(p, &c->context[0], fp); + if (rc) + return rc; + break; + } + } + } + return 0; +} + +static int genfs_write(struct policydb *p, void *fp) +{ + struct genfs *genfs; + struct ocontext *c; + size_t len; + __le32 buf[1]; + int rc; + + len = 0; + for (genfs = p->genfs; genfs; genfs = genfs->next) + len++; + buf[0] = cpu_to_le32(len); + rc = put_entry(buf, sizeof(u32), 1, fp); + if (rc) + return rc; + for (genfs = p->genfs; genfs; genfs = genfs->next) { + len = strlen(genfs->fstype); + buf[0] = cpu_to_le32(len); + rc = put_entry(buf, sizeof(u32), 1, fp); + if (rc) + return rc; + rc = put_entry(genfs->fstype, 1, len, fp); + if (rc) + return rc; + len = 0; + for (c = genfs->head; c; c = c->next) + len++; + buf[0] = cpu_to_le32(len); + rc = put_entry(buf, sizeof(u32), 1, fp); + if (rc) + return rc; + for (c = genfs->head; c; c = c->next) { + len = strlen(c->u.name); + buf[0] = cpu_to_le32(len); + rc = put_entry(buf, sizeof(u32), 1, fp); + if (rc) + return rc; + rc = put_entry(c->u.name, 1, len, fp); + if (rc) + return rc; + buf[0] = cpu_to_le32(c->v.sclass); + rc = put_entry(buf, sizeof(u32), 1, fp); + if (rc) + return rc; + rc = context_write(p, &c->context[0], fp); + if (rc) + return rc; + } + } + return 0; +} + +static int hashtab_cnt(void *key, void *data, void *ptr) +{ + int *cnt = ptr; + *cnt = *cnt + 1; + + return 0; +} + +static int range_write_helper(void *key, void *data, void *ptr) +{ + __le32 buf[2]; + struct range_trans *rt = key; + struct mls_range *r = data; + struct policy_data *pd = ptr; + void *fp = pd->fp; + struct policydb *p = pd->p; + int rc; + + buf[0] = cpu_to_le32(rt->source_type); + buf[1] = cpu_to_le32(rt->target_type); + rc = put_entry(buf, sizeof(u32), 2, fp); + if (rc) + return rc; + if (p->policyvers >= POLICYDB_VERSION_RANGETRANS) { + buf[0] = cpu_to_le32(rt->target_class); + rc = put_entry(buf, sizeof(u32), 1, fp); + if (rc) + return rc; + } + rc = mls_write_range_helper(r, fp); + if (rc) + return rc; + + return 0; +} + +static int range_write(struct policydb *p, void *fp) +{ + __le32 buf[1]; + int rc, nel; + struct policy_data pd; + + pd.p = p; + pd.fp = fp; + + /* count the number of entries in the hashtab */ + nel = 0; + rc = hashtab_map(p->range_tr, hashtab_cnt, &nel); + if (rc) + return rc; + + buf[0] = cpu_to_le32(nel); + rc = put_entry(buf, sizeof(u32), 1, fp); + if (rc) + return rc; + + /* actually write all of the entries */ + rc = hashtab_map(p->range_tr, range_write_helper, &pd); + if (rc) + return rc; + + return 0; +} + +static int filename_write_helper(void *key, void *data, void *ptr) +{ + __le32 buf[4]; + struct filename_trans *ft = key; + struct filename_trans_datum *otype = data; + void *fp = ptr; + int rc; + u32 len; + + len = strlen(ft->name); + buf[0] = cpu_to_le32(len); + rc = put_entry(buf, sizeof(u32), 1, fp); + if (rc) + return rc; + + rc = put_entry(ft->name, sizeof(char), len, fp); + if (rc) + return rc; + + buf[0] = cpu_to_le32(ft->stype); + buf[1] = cpu_to_le32(ft->ttype); + buf[2] = cpu_to_le32(ft->tclass); + buf[3] = cpu_to_le32(otype->otype); + + rc = put_entry(buf, sizeof(u32), 4, fp); + if (rc) + return rc; + + return 0; +} + +static int filename_trans_write(struct policydb *p, void *fp) +{ + u32 nel; + __le32 buf[1]; + int rc; + + if (p->policyvers < POLICYDB_VERSION_FILENAME_TRANS) + return 0; + + nel = 0; + rc = hashtab_map(p->filename_trans, hashtab_cnt, &nel); + if (rc) + return rc; + + buf[0] = cpu_to_le32(nel); + rc = put_entry(buf, sizeof(u32), 1, fp); + if (rc) + return rc; + + rc = hashtab_map(p->filename_trans, filename_write_helper, fp); + if (rc) + return rc; + + return 0; +} + +/* + * Write the configuration data in a policy database + * structure to a policy database binary representation + * file. + */ +int policydb_write(struct policydb *p, void *fp) +{ + unsigned int i, num_syms; + int rc; + __le32 buf[4]; + u32 config; + size_t len; + struct policydb_compat_info *info; + + /* + * refuse to write policy older than compressed avtab + * to simplify the writer. There are other tests dropped + * since we assume this throughout the writer code. Be + * careful if you ever try to remove this restriction + */ + if (p->policyvers < POLICYDB_VERSION_AVTAB) { + pr_err("SELinux: refusing to write policy version %d." + " Because it is less than version %d\n", p->policyvers, + POLICYDB_VERSION_AVTAB); + return -EINVAL; + } + + config = 0; + if (p->mls_enabled) + config |= POLICYDB_CONFIG_MLS; + + if (p->reject_unknown) + config |= REJECT_UNKNOWN; + if (p->allow_unknown) + config |= ALLOW_UNKNOWN; + + /* Write the magic number and string identifiers. */ + buf[0] = cpu_to_le32(POLICYDB_MAGIC); + len = strlen(POLICYDB_STRING); + buf[1] = cpu_to_le32(len); + rc = put_entry(buf, sizeof(u32), 2, fp); + if (rc) + return rc; + rc = put_entry(POLICYDB_STRING, 1, len, fp); + if (rc) + return rc; + + /* Write the version, config, and table sizes. */ + info = policydb_lookup_compat(p->policyvers); + if (!info) { + pr_err("SELinux: compatibility lookup failed for policy " + "version %d", p->policyvers); + return -EINVAL; + } + + buf[0] = cpu_to_le32(p->policyvers); + buf[1] = cpu_to_le32(config); + buf[2] = cpu_to_le32(info->sym_num); + buf[3] = cpu_to_le32(info->ocon_num); + + rc = put_entry(buf, sizeof(u32), 4, fp); + if (rc) + return rc; + + if (p->policyvers >= POLICYDB_VERSION_POLCAP) { + rc = ebitmap_write(&p->policycaps, fp); + if (rc) + return rc; + } + + if (p->policyvers >= POLICYDB_VERSION_PERMISSIVE) { + rc = ebitmap_write(&p->permissive_map, fp); + if (rc) + return rc; + } + + num_syms = info->sym_num; + for (i = 0; i < num_syms; i++) { + struct policy_data pd; + + pd.fp = fp; + pd.p = p; + + buf[0] = cpu_to_le32(p->symtab[i].nprim); + buf[1] = cpu_to_le32(p->symtab[i].table->nel); + + rc = put_entry(buf, sizeof(u32), 2, fp); + if (rc) + return rc; + rc = hashtab_map(p->symtab[i].table, write_f[i], &pd); + if (rc) + return rc; + } + + rc = avtab_write(p, &p->te_avtab, fp); + if (rc) + return rc; + + rc = cond_write_list(p, p->cond_list, fp); + if (rc) + return rc; + + rc = role_trans_write(p, fp); + if (rc) + return rc; + + rc = role_allow_write(p->role_allow, fp); + if (rc) + return rc; + + rc = filename_trans_write(p, fp); + if (rc) + return rc; + + rc = ocontext_write(p, info, fp); + if (rc) + return rc; + + rc = genfs_write(p, fp); + if (rc) + return rc; + + rc = range_write(p, fp); + if (rc) + return rc; + + for (i = 0; i < p->p_types.nprim; i++) { + struct ebitmap *e = flex_array_get(p->type_attr_map_array, i); + + BUG_ON(!e); + rc = ebitmap_write(e, fp); + if (rc) + return rc; + } + + return 0; +} diff --git a/security/selinux/ss/policydb.h b/security/selinux/ss/policydb.h new file mode 100644 index 000000000..215f8f30a --- /dev/null +++ b/security/selinux/ss/policydb.h @@ -0,0 +1,381 @@ +/* + * A policy database (policydb) specifies the + * configuration data for the security policy. + * + * Author : Stephen Smalley, <sds@tycho.nsa.gov> + */ + +/* + * 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 <linux/flex_array.h> + +#include "symtab.h" +#include "avtab.h" +#include "sidtab.h" +#include "ebitmap.h" +#include "mls_types.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 */ +/* Options how a new object user, role, and type should be decided */ +#define DEFAULT_SOURCE 1 +#define DEFAULT_TARGET 2 + char default_user; + char default_role; + char default_type; +/* Options how a new object range should be decided */ +#define DEFAULT_SOURCE_LOW 1 +#define DEFAULT_SOURCE_HIGH 2 +#define DEFAULT_SOURCE_LOW_HIGH 3 +#define DEFAULT_TARGET_LOW 4 +#define DEFAULT_TARGET_HIGH 5 +#define DEFAULT_TARGET_LOW_HIGH 6 + char default_range; +}; + +/* Role attributes */ +struct role_datum { + u32 value; /* internal role value */ + u32 bounds; /* boundary of role */ + 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, or new object type */ + u32 tclass; /* process class, or new object class */ + u32 new_role; /* new role */ + struct role_trans *next; +}; + +struct filename_trans { + u32 stype; /* current process */ + u32 ttype; /* parent dir context */ + u16 tclass; /* class of new object */ + const char *name; /* last path component */ +}; + +struct filename_trans_datum { + u32 otype; /* expected of new object */ +}; + +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 */ + u32 bounds; /* boundary of type */ + unsigned char primary; /* primary name? */ + unsigned char attribute;/* attribute ?*/ +}; + +/* User attributes */ +struct user_datum { + u32 value; /* internal user value */ + u32 bounds; /* bounds of user */ + 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 source_type; + u32 target_type; + u32 target_class; +}; + +/* Boolean data type */ +struct cond_bool_datum { + __u32 value; /* internal type value */ + int state; +}; + +struct cond_node; + +/* + * type set preserves data needed to determine constraint info from + * policy source. This is not used by the kernel policy but allows + * utilities such as audit2allow to determine constraint denials. + */ +struct type_set { + struct ebitmap types; + struct ebitmap negset; + u32 flags; +}; + +/* + * 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 */ + struct { + u64 subnet_prefix; + u16 low_pkey; + u16 high_pkey; + } ibpkey; + struct { + char *dev_name; + u8 port; + } ibendport; + } 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_IBPKEY 7 /* Infiniband PKeys */ +#define OCON_IBENDPORT 8 /* Infiniband end ports */ +#define OCON_NUM 9 + +/* The policy database */ +struct policydb { + int mls_enabled; + + /* 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) */ + struct flex_array *sym_val_to_name[SYM_NUM]; + + /* 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; + struct flex_array *type_val_to_struct_array; + + /* type enforcement access vectors and transitions */ + struct avtab te_avtab; + + /* role transitions */ + struct role_trans *role_tr; + + /* file transitions with the last path component */ + /* quickly exclude lookups when parent ttype has no rules */ + struct ebitmap filename_trans_ttypes; + /* actual set of filename_trans rules */ + struct hashtab *filename_trans; + + /* 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 table (range_trans_key -> mls_range) */ + struct hashtab *range_tr; + + /* type -> attribute reverse mapping */ + struct flex_array *type_attr_map_array; + + struct ebitmap policycaps; + + struct ebitmap permissive_map; + + /* length of this policy when it was loaded */ + size_t len; + + unsigned int policyvers; + + unsigned int reject_unknown : 1; + unsigned int allow_unknown : 1; + + u16 process_class; + u32 process_trans_perms; +}; + +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_class_isvalid(struct policydb *p, unsigned int class); +extern int policydb_type_isvalid(struct policydb *p, unsigned int type); +extern int policydb_role_isvalid(struct policydb *p, unsigned int role); +extern int policydb_read(struct policydb *p, void *fp); +extern int policydb_write(struct policydb *p, void *fp); + +#define PERM_SYMTAB_SIZE 32 + +#define POLICYDB_CONFIG_MLS 1 + +/* the config flags related to unknown classes/perms are bits 2 and 3 */ +#define REJECT_UNKNOWN 0x00000002 +#define ALLOW_UNKNOWN 0x00000004 + +#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; +}; + +struct policy_data { + struct policydb *p; + void *fp; +}; + +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; +} + +static inline int put_entry(const void *buf, size_t bytes, int num, struct policy_file *fp) +{ + size_t len = bytes * num; + + memcpy(fp->data, buf, len); + fp->data += len; + fp->len -= len; + + return 0; +} + +static inline char *sym_name(struct policydb *p, unsigned int sym_num, unsigned int element_nr) +{ + struct flex_array *fa = p->sym_val_to_name[sym_num]; + + return flex_array_get_ptr(fa, element_nr); +} + +extern u16 string_to_security_class(struct policydb *p, const char *name); +extern u32 string_to_av_perm(struct policydb *p, u16 tclass, const char *name); + +#endif /* _SS_POLICYDB_H_ */ + diff --git a/security/selinux/ss/services.c b/security/selinux/ss/services.c new file mode 100644 index 000000000..a9f2bc844 --- /dev/null +++ b/security/selinux/ss/services.c @@ -0,0 +1,3732 @@ +/* + * Implementation of the security services. + * + * Authors : Stephen Smalley, <sds@tycho.nsa.gov> + * James Morris <jmorris@redhat.com> + * + * Updated: Trusted Computer Solutions, Inc. <dgoeddel@trustedcs.com> + * + * Support for enhanced MLS infrastructure. + * Support for context based audit filters. + * + * Updated: Frank Mayer <mayerf@tresys.com> and Karl MacMillan <kmacmillan@tresys.com> + * + * Added conditional policy language extensions + * + * Updated: Hewlett-Packard <paul@paul-moore.com> + * + * Added support for NetLabel + * Added support for the policy capability bitmap + * + * Updated: Chad Sellers <csellers@tresys.com> + * + * Added validation of kernel classes and permissions + * + * Updated: KaiGai Kohei <kaigai@ak.jp.nec.com> + * + * Added support for bounds domain and audit messaged on masked permissions + * + * Updated: Guido Trentalancia <guido@trentalancia.com> + * + * Added support for runtime switching of the policy type + * + * Copyright (C) 2008, 2009 NEC Corporation + * Copyright (C) 2006, 2007 Hewlett-Packard Development Company, L.P. + * Copyright (C) 2004-2006 Trusted Computer Solutions, Inc. + * Copyright (C) 2003 - 2004, 2006 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/rcupdate.h> +#include <linux/errno.h> +#include <linux/in.h> +#include <linux/sched.h> +#include <linux/audit.h> +#include <linux/mutex.h> +#include <linux/selinux.h> +#include <linux/flex_array.h> +#include <linux/vmalloc.h> +#include <net/netlabel.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" +#include "objsec.h" +#include "netlabel.h" +#include "xfrm.h" +#include "ebitmap.h" +#include "audit.h" + +/* Policy capability names */ +char *selinux_policycap_names[__POLICYDB_CAPABILITY_MAX] = { + "network_peer_controls", + "open_perms", + "extended_socket_class", + "always_check_network", + "cgroup_seclabel", + "nnp_nosuid_transition" +}; + +static struct selinux_ss selinux_ss; + +void selinux_ss_init(struct selinux_ss **ss) +{ + rwlock_init(&selinux_ss.policy_rwlock); + mutex_init(&selinux_ss.status_lock); + *ss = &selinux_ss; +} + +/* Forward declaration. */ +static int context_struct_to_string(struct policydb *policydb, + struct context *context, + char **scontext, + u32 *scontext_len); + +static void context_struct_compute_av(struct policydb *policydb, + struct context *scontext, + struct context *tcontext, + u16 tclass, + struct av_decision *avd, + struct extended_perms *xperms); + +static int selinux_set_mapping(struct policydb *pol, + struct security_class_mapping *map, + struct selinux_map *out_map) +{ + u16 i, j; + unsigned k; + bool print_unknown_handle = false; + + /* Find number of classes in the input mapping */ + if (!map) + return -EINVAL; + i = 0; + while (map[i].name) + i++; + + /* Allocate space for the class records, plus one for class zero */ + out_map->mapping = kcalloc(++i, sizeof(*out_map->mapping), GFP_ATOMIC); + if (!out_map->mapping) + return -ENOMEM; + + /* Store the raw class and permission values */ + j = 0; + while (map[j].name) { + struct security_class_mapping *p_in = map + (j++); + struct selinux_mapping *p_out = out_map->mapping + j; + + /* An empty class string skips ahead */ + if (!strcmp(p_in->name, "")) { + p_out->num_perms = 0; + continue; + } + + p_out->value = string_to_security_class(pol, p_in->name); + if (!p_out->value) { + pr_info("SELinux: Class %s not defined in policy.\n", + p_in->name); + if (pol->reject_unknown) + goto err; + p_out->num_perms = 0; + print_unknown_handle = true; + continue; + } + + k = 0; + while (p_in->perms[k]) { + /* An empty permission string skips ahead */ + if (!*p_in->perms[k]) { + k++; + continue; + } + p_out->perms[k] = string_to_av_perm(pol, p_out->value, + p_in->perms[k]); + if (!p_out->perms[k]) { + pr_info("SELinux: Permission %s in class %s not defined in policy.\n", + p_in->perms[k], p_in->name); + if (pol->reject_unknown) + goto err; + print_unknown_handle = true; + } + + k++; + } + p_out->num_perms = k; + } + + if (print_unknown_handle) + pr_info("SELinux: the above unknown classes and permissions will be %s\n", + pol->allow_unknown ? "allowed" : "denied"); + + out_map->size = i; + return 0; +err: + kfree(out_map->mapping); + out_map->mapping = NULL; + return -EINVAL; +} + +/* + * Get real, policy values from mapped values + */ + +static u16 unmap_class(struct selinux_map *map, u16 tclass) +{ + if (tclass < map->size) + return map->mapping[tclass].value; + + return tclass; +} + +/* + * Get kernel value for class from its policy value + */ +static u16 map_class(struct selinux_map *map, u16 pol_value) +{ + u16 i; + + for (i = 1; i < map->size; i++) { + if (map->mapping[i].value == pol_value) + return i; + } + + return SECCLASS_NULL; +} + +static void map_decision(struct selinux_map *map, + u16 tclass, struct av_decision *avd, + int allow_unknown) +{ + if (tclass < map->size) { + struct selinux_mapping *mapping = &map->mapping[tclass]; + unsigned int i, n = mapping->num_perms; + u32 result; + + for (i = 0, result = 0; i < n; i++) { + if (avd->allowed & mapping->perms[i]) + result |= 1<<i; + if (allow_unknown && !mapping->perms[i]) + result |= 1<<i; + } + avd->allowed = result; + + for (i = 0, result = 0; i < n; i++) + if (avd->auditallow & mapping->perms[i]) + result |= 1<<i; + avd->auditallow = result; + + for (i = 0, result = 0; i < n; i++) { + if (avd->auditdeny & mapping->perms[i]) + result |= 1<<i; + if (!allow_unknown && !mapping->perms[i]) + result |= 1<<i; + } + /* + * In case the kernel has a bug and requests a permission + * between num_perms and the maximum permission number, we + * should audit that denial + */ + for (; i < (sizeof(u32)*8); i++) + result |= 1<<i; + avd->auditdeny = result; + } +} + +int security_mls_enabled(struct selinux_state *state) +{ + struct policydb *p = &state->ss->policydb; + + return p->mls_enabled; +} + +/* + * 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 policydb *policydb, + 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]; +} + +/* + * security_dump_masked_av - dumps masked permissions during + * security_compute_av due to RBAC, MLS/Constraint and Type bounds. + */ +static int dump_masked_av_helper(void *k, void *d, void *args) +{ + struct perm_datum *pdatum = d; + char **permission_names = args; + + BUG_ON(pdatum->value < 1 || pdatum->value > 32); + + permission_names[pdatum->value - 1] = (char *)k; + + return 0; +} + +static void security_dump_masked_av(struct policydb *policydb, + struct context *scontext, + struct context *tcontext, + u16 tclass, + u32 permissions, + const char *reason) +{ + struct common_datum *common_dat; + struct class_datum *tclass_dat; + struct audit_buffer *ab; + char *tclass_name; + char *scontext_name = NULL; + char *tcontext_name = NULL; + char *permission_names[32]; + int index; + u32 length; + bool need_comma = false; + + if (!permissions) + return; + + tclass_name = sym_name(policydb, SYM_CLASSES, tclass - 1); + tclass_dat = policydb->class_val_to_struct[tclass - 1]; + common_dat = tclass_dat->comdatum; + + /* init permission_names */ + if (common_dat && + hashtab_map(common_dat->permissions.table, + dump_masked_av_helper, permission_names) < 0) + goto out; + + if (hashtab_map(tclass_dat->permissions.table, + dump_masked_av_helper, permission_names) < 0) + goto out; + + /* get scontext/tcontext in text form */ + if (context_struct_to_string(policydb, scontext, + &scontext_name, &length) < 0) + goto out; + + if (context_struct_to_string(policydb, tcontext, + &tcontext_name, &length) < 0) + goto out; + + /* audit a message */ + ab = audit_log_start(audit_context(), + GFP_ATOMIC, AUDIT_SELINUX_ERR); + if (!ab) + goto out; + + audit_log_format(ab, "op=security_compute_av reason=%s " + "scontext=%s tcontext=%s tclass=%s perms=", + reason, scontext_name, tcontext_name, tclass_name); + + for (index = 0; index < 32; index++) { + u32 mask = (1 << index); + + if ((mask & permissions) == 0) + continue; + + audit_log_format(ab, "%s%s", + need_comma ? "," : "", + permission_names[index] + ? permission_names[index] : "????"); + need_comma = true; + } + audit_log_end(ab); +out: + /* release scontext/tcontext */ + kfree(tcontext_name); + kfree(scontext_name); + + return; +} + +/* + * security_boundary_permission - drops violated permissions + * on boundary constraint. + */ +static void type_attribute_bounds_av(struct policydb *policydb, + struct context *scontext, + struct context *tcontext, + u16 tclass, + struct av_decision *avd) +{ + struct context lo_scontext; + struct context lo_tcontext, *tcontextp = tcontext; + struct av_decision lo_avd; + struct type_datum *source; + struct type_datum *target; + u32 masked = 0; + + source = flex_array_get_ptr(policydb->type_val_to_struct_array, + scontext->type - 1); + BUG_ON(!source); + + if (!source->bounds) + return; + + target = flex_array_get_ptr(policydb->type_val_to_struct_array, + tcontext->type - 1); + BUG_ON(!target); + + memset(&lo_avd, 0, sizeof(lo_avd)); + + memcpy(&lo_scontext, scontext, sizeof(lo_scontext)); + lo_scontext.type = source->bounds; + + if (target->bounds) { + memcpy(&lo_tcontext, tcontext, sizeof(lo_tcontext)); + lo_tcontext.type = target->bounds; + tcontextp = &lo_tcontext; + } + + context_struct_compute_av(policydb, &lo_scontext, + tcontextp, + tclass, + &lo_avd, + NULL); + + masked = ~lo_avd.allowed & avd->allowed; + + if (likely(!masked)) + return; /* no masked permission */ + + /* mask violated permissions */ + avd->allowed &= ~masked; + + /* audit masked permissions */ + security_dump_masked_av(policydb, scontext, tcontext, + tclass, masked, "bounds"); +} + +/* + * flag which drivers have permissions + * only looking for ioctl based extended permssions + */ +void services_compute_xperms_drivers( + struct extended_perms *xperms, + struct avtab_node *node) +{ + unsigned int i; + + if (node->datum.u.xperms->specified == AVTAB_XPERMS_IOCTLDRIVER) { + /* if one or more driver has all permissions allowed */ + for (i = 0; i < ARRAY_SIZE(xperms->drivers.p); i++) + xperms->drivers.p[i] |= node->datum.u.xperms->perms.p[i]; + } else if (node->datum.u.xperms->specified == AVTAB_XPERMS_IOCTLFUNCTION) { + /* if allowing permissions within a driver */ + security_xperm_set(xperms->drivers.p, + node->datum.u.xperms->driver); + } + + /* If no ioctl commands are allowed, ignore auditallow and auditdeny */ + if (node->key.specified & AVTAB_XPERMS_ALLOWED) + xperms->len = 1; +} + +/* + * Compute access vectors and extended permissions based on a context + * structure pair for the permissions in a particular class. + */ +static void context_struct_compute_av(struct policydb *policydb, + struct context *scontext, + struct context *tcontext, + u16 tclass, + struct av_decision *avd, + struct extended_perms *xperms) +{ + struct constraint_node *constraint; + struct role_allow *ra; + struct avtab_key avkey; + struct avtab_node *node; + struct class_datum *tclass_datum; + struct ebitmap *sattr, *tattr; + struct ebitmap_node *snode, *tnode; + unsigned int i, j; + + avd->allowed = 0; + avd->auditallow = 0; + avd->auditdeny = 0xffffffff; + if (xperms) { + memset(&xperms->drivers, 0, sizeof(xperms->drivers)); + xperms->len = 0; + } + + if (unlikely(!tclass || tclass > policydb->p_classes.nprim)) { + if (printk_ratelimit()) + pr_warn("SELinux: Invalid class %hu\n", tclass); + return; + } + + tclass_datum = policydb->class_val_to_struct[tclass - 1]; + + /* + * If a specific type enforcement rule was defined for + * this permission check, then use it. + */ + avkey.target_class = tclass; + avkey.specified = AVTAB_AV | AVTAB_XPERMS; + sattr = flex_array_get(policydb->type_attr_map_array, + scontext->type - 1); + BUG_ON(!sattr); + tattr = flex_array_get(policydb->type_attr_map_array, + tcontext->type - 1); + BUG_ON(!tattr); + ebitmap_for_each_positive_bit(sattr, snode, i) { + ebitmap_for_each_positive_bit(tattr, tnode, j) { + avkey.source_type = i + 1; + avkey.target_type = j + 1; + for (node = avtab_search_node(&policydb->te_avtab, + &avkey); + node; + node = avtab_search_node_next(node, avkey.specified)) { + if (node->key.specified == AVTAB_ALLOWED) + avd->allowed |= node->datum.u.data; + else if (node->key.specified == AVTAB_AUDITALLOW) + avd->auditallow |= node->datum.u.data; + else if (node->key.specified == AVTAB_AUDITDENY) + avd->auditdeny &= node->datum.u.data; + else if (xperms && (node->key.specified & AVTAB_XPERMS)) + services_compute_xperms_drivers(xperms, node); + } + + /* Check conditional av table for additional permissions */ + cond_compute_av(&policydb->te_cond_avtab, &avkey, + avd, xperms); + + } + } + + /* + * 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(policydb, scontext, tcontext, NULL, + constraint->expr)) { + 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 == policydb->process_class && + (avd->allowed & policydb->process_trans_perms) && + 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 &= ~policydb->process_trans_perms; + } + + /* + * If the given source and target types have boundary + * constraint, lazy checks have to mask any violated + * permission and notice it to userspace via audit. + */ + type_attribute_bounds_av(policydb, scontext, tcontext, + tclass, avd); +} + +static int security_validtrans_handle_fail(struct selinux_state *state, + struct context *ocontext, + struct context *ncontext, + struct context *tcontext, + u16 tclass) +{ + struct policydb *p = &state->ss->policydb; + char *o = NULL, *n = NULL, *t = NULL; + u32 olen, nlen, tlen; + + if (context_struct_to_string(p, ocontext, &o, &olen)) + goto out; + if (context_struct_to_string(p, ncontext, &n, &nlen)) + goto out; + if (context_struct_to_string(p, tcontext, &t, &tlen)) + goto out; + audit_log(audit_context(), GFP_ATOMIC, AUDIT_SELINUX_ERR, + "op=security_validate_transition seresult=denied" + " oldcontext=%s newcontext=%s taskcontext=%s tclass=%s", + o, n, t, sym_name(p, SYM_CLASSES, tclass-1)); +out: + kfree(o); + kfree(n); + kfree(t); + + if (!enforcing_enabled(state)) + return 0; + return -EPERM; +} + +static int security_compute_validatetrans(struct selinux_state *state, + u32 oldsid, u32 newsid, u32 tasksid, + u16 orig_tclass, bool user) +{ + struct policydb *policydb; + struct sidtab *sidtab; + struct context *ocontext; + struct context *ncontext; + struct context *tcontext; + struct class_datum *tclass_datum; + struct constraint_node *constraint; + u16 tclass; + int rc = 0; + + + if (!state->initialized) + return 0; + + read_lock(&state->ss->policy_rwlock); + + policydb = &state->ss->policydb; + sidtab = &state->ss->sidtab; + + if (!user) + tclass = unmap_class(&state->ss->map, orig_tclass); + else + tclass = orig_tclass; + + if (!tclass || tclass > policydb->p_classes.nprim) { + rc = -EINVAL; + goto out; + } + tclass_datum = policydb->class_val_to_struct[tclass - 1]; + + ocontext = sidtab_search(sidtab, oldsid); + if (!ocontext) { + pr_err("SELinux: %s: unrecognized SID %d\n", + __func__, oldsid); + rc = -EINVAL; + goto out; + } + + ncontext = sidtab_search(sidtab, newsid); + if (!ncontext) { + pr_err("SELinux: %s: unrecognized SID %d\n", + __func__, newsid); + rc = -EINVAL; + goto out; + } + + tcontext = sidtab_search(sidtab, tasksid); + if (!tcontext) { + pr_err("SELinux: %s: unrecognized SID %d\n", + __func__, tasksid); + rc = -EINVAL; + goto out; + } + + constraint = tclass_datum->validatetrans; + while (constraint) { + if (!constraint_expr_eval(policydb, ocontext, ncontext, + tcontext, constraint->expr)) { + if (user) + rc = -EPERM; + else + rc = security_validtrans_handle_fail(state, + ocontext, + ncontext, + tcontext, + tclass); + goto out; + } + constraint = constraint->next; + } + +out: + read_unlock(&state->ss->policy_rwlock); + return rc; +} + +int security_validate_transition_user(struct selinux_state *state, + u32 oldsid, u32 newsid, u32 tasksid, + u16 tclass) +{ + return security_compute_validatetrans(state, oldsid, newsid, tasksid, + tclass, true); +} + +int security_validate_transition(struct selinux_state *state, + u32 oldsid, u32 newsid, u32 tasksid, + u16 orig_tclass) +{ + return security_compute_validatetrans(state, oldsid, newsid, tasksid, + orig_tclass, false); +} + +/* + * security_bounded_transition - check whether the given + * transition is directed to bounded, or not. + * It returns 0, if @newsid is bounded by @oldsid. + * Otherwise, it returns error code. + * + * @oldsid : current security identifier + * @newsid : destinated security identifier + */ +int security_bounded_transition(struct selinux_state *state, + u32 old_sid, u32 new_sid) +{ + struct policydb *policydb; + struct sidtab *sidtab; + struct context *old_context, *new_context; + struct type_datum *type; + int index; + int rc; + + if (!state->initialized) + return 0; + + read_lock(&state->ss->policy_rwlock); + + policydb = &state->ss->policydb; + sidtab = &state->ss->sidtab; + + rc = -EINVAL; + old_context = sidtab_search(sidtab, old_sid); + if (!old_context) { + pr_err("SELinux: %s: unrecognized SID %u\n", + __func__, old_sid); + goto out; + } + + rc = -EINVAL; + new_context = sidtab_search(sidtab, new_sid); + if (!new_context) { + pr_err("SELinux: %s: unrecognized SID %u\n", + __func__, new_sid); + goto out; + } + + rc = 0; + /* type/domain unchanged */ + if (old_context->type == new_context->type) + goto out; + + index = new_context->type; + while (true) { + type = flex_array_get_ptr(policydb->type_val_to_struct_array, + index - 1); + BUG_ON(!type); + + /* not bounded anymore */ + rc = -EPERM; + if (!type->bounds) + break; + + /* @newsid is bounded by @oldsid */ + rc = 0; + if (type->bounds == old_context->type) + break; + + index = type->bounds; + } + + if (rc) { + char *old_name = NULL; + char *new_name = NULL; + u32 length; + + if (!context_struct_to_string(policydb, old_context, + &old_name, &length) && + !context_struct_to_string(policydb, new_context, + &new_name, &length)) { + audit_log(audit_context(), + GFP_ATOMIC, AUDIT_SELINUX_ERR, + "op=security_bounded_transition " + "seresult=denied " + "oldcontext=%s newcontext=%s", + old_name, new_name); + } + kfree(new_name); + kfree(old_name); + } +out: + read_unlock(&state->ss->policy_rwlock); + + return rc; +} + +static void avd_init(struct selinux_state *state, struct av_decision *avd) +{ + avd->allowed = 0; + avd->auditallow = 0; + avd->auditdeny = 0xffffffff; + avd->seqno = state->ss->latest_granting; + avd->flags = 0; +} + +void services_compute_xperms_decision(struct extended_perms_decision *xpermd, + struct avtab_node *node) +{ + unsigned int i; + + if (node->datum.u.xperms->specified == AVTAB_XPERMS_IOCTLFUNCTION) { + if (xpermd->driver != node->datum.u.xperms->driver) + return; + } else if (node->datum.u.xperms->specified == AVTAB_XPERMS_IOCTLDRIVER) { + if (!security_xperm_test(node->datum.u.xperms->perms.p, + xpermd->driver)) + return; + } else { + BUG(); + } + + if (node->key.specified == AVTAB_XPERMS_ALLOWED) { + xpermd->used |= XPERMS_ALLOWED; + if (node->datum.u.xperms->specified == AVTAB_XPERMS_IOCTLDRIVER) { + memset(xpermd->allowed->p, 0xff, + sizeof(xpermd->allowed->p)); + } + if (node->datum.u.xperms->specified == AVTAB_XPERMS_IOCTLFUNCTION) { + for (i = 0; i < ARRAY_SIZE(xpermd->allowed->p); i++) + xpermd->allowed->p[i] |= + node->datum.u.xperms->perms.p[i]; + } + } else if (node->key.specified == AVTAB_XPERMS_AUDITALLOW) { + xpermd->used |= XPERMS_AUDITALLOW; + if (node->datum.u.xperms->specified == AVTAB_XPERMS_IOCTLDRIVER) { + memset(xpermd->auditallow->p, 0xff, + sizeof(xpermd->auditallow->p)); + } + if (node->datum.u.xperms->specified == AVTAB_XPERMS_IOCTLFUNCTION) { + for (i = 0; i < ARRAY_SIZE(xpermd->auditallow->p); i++) + xpermd->auditallow->p[i] |= + node->datum.u.xperms->perms.p[i]; + } + } else if (node->key.specified == AVTAB_XPERMS_DONTAUDIT) { + xpermd->used |= XPERMS_DONTAUDIT; + if (node->datum.u.xperms->specified == AVTAB_XPERMS_IOCTLDRIVER) { + memset(xpermd->dontaudit->p, 0xff, + sizeof(xpermd->dontaudit->p)); + } + if (node->datum.u.xperms->specified == AVTAB_XPERMS_IOCTLFUNCTION) { + for (i = 0; i < ARRAY_SIZE(xpermd->dontaudit->p); i++) + xpermd->dontaudit->p[i] |= + node->datum.u.xperms->perms.p[i]; + } + } else { + BUG(); + } +} + +void security_compute_xperms_decision(struct selinux_state *state, + u32 ssid, + u32 tsid, + u16 orig_tclass, + u8 driver, + struct extended_perms_decision *xpermd) +{ + struct policydb *policydb; + struct sidtab *sidtab; + u16 tclass; + struct context *scontext, *tcontext; + struct avtab_key avkey; + struct avtab_node *node; + struct ebitmap *sattr, *tattr; + struct ebitmap_node *snode, *tnode; + unsigned int i, j; + + xpermd->driver = driver; + xpermd->used = 0; + memset(xpermd->allowed->p, 0, sizeof(xpermd->allowed->p)); + memset(xpermd->auditallow->p, 0, sizeof(xpermd->auditallow->p)); + memset(xpermd->dontaudit->p, 0, sizeof(xpermd->dontaudit->p)); + + read_lock(&state->ss->policy_rwlock); + if (!state->initialized) + goto allow; + + policydb = &state->ss->policydb; + sidtab = &state->ss->sidtab; + + scontext = sidtab_search(sidtab, ssid); + if (!scontext) { + pr_err("SELinux: %s: unrecognized SID %d\n", + __func__, ssid); + goto out; + } + + tcontext = sidtab_search(sidtab, tsid); + if (!tcontext) { + pr_err("SELinux: %s: unrecognized SID %d\n", + __func__, tsid); + goto out; + } + + tclass = unmap_class(&state->ss->map, orig_tclass); + if (unlikely(orig_tclass && !tclass)) { + if (policydb->allow_unknown) + goto allow; + goto out; + } + + + if (unlikely(!tclass || tclass > policydb->p_classes.nprim)) { + pr_warn_ratelimited("SELinux: Invalid class %hu\n", tclass); + goto out; + } + + avkey.target_class = tclass; + avkey.specified = AVTAB_XPERMS; + sattr = flex_array_get(policydb->type_attr_map_array, + scontext->type - 1); + BUG_ON(!sattr); + tattr = flex_array_get(policydb->type_attr_map_array, + tcontext->type - 1); + BUG_ON(!tattr); + ebitmap_for_each_positive_bit(sattr, snode, i) { + ebitmap_for_each_positive_bit(tattr, tnode, j) { + avkey.source_type = i + 1; + avkey.target_type = j + 1; + for (node = avtab_search_node(&policydb->te_avtab, + &avkey); + node; + node = avtab_search_node_next(node, avkey.specified)) + services_compute_xperms_decision(xpermd, node); + + cond_compute_xperms(&policydb->te_cond_avtab, + &avkey, xpermd); + } + } +out: + read_unlock(&state->ss->policy_rwlock); + return; +allow: + memset(xpermd->allowed->p, 0xff, sizeof(xpermd->allowed->p)); + goto out; +} + +/** + * security_compute_av - Compute access vector decisions. + * @ssid: source security identifier + * @tsid: target security identifier + * @tclass: target security class + * @avd: access vector decisions + * @xperms: extended permissions + * + * Compute a set of access vector decisions based on the + * SID pair (@ssid, @tsid) for the permissions in @tclass. + */ +void security_compute_av(struct selinux_state *state, + u32 ssid, + u32 tsid, + u16 orig_tclass, + struct av_decision *avd, + struct extended_perms *xperms) +{ + struct policydb *policydb; + struct sidtab *sidtab; + u16 tclass; + struct context *scontext = NULL, *tcontext = NULL; + + read_lock(&state->ss->policy_rwlock); + avd_init(state, avd); + xperms->len = 0; + if (!state->initialized) + goto allow; + + policydb = &state->ss->policydb; + sidtab = &state->ss->sidtab; + + scontext = sidtab_search(sidtab, ssid); + if (!scontext) { + pr_err("SELinux: %s: unrecognized SID %d\n", + __func__, ssid); + goto out; + } + + /* permissive domain? */ + if (ebitmap_get_bit(&policydb->permissive_map, scontext->type)) + avd->flags |= AVD_FLAGS_PERMISSIVE; + + tcontext = sidtab_search(sidtab, tsid); + if (!tcontext) { + pr_err("SELinux: %s: unrecognized SID %d\n", + __func__, tsid); + goto out; + } + + tclass = unmap_class(&state->ss->map, orig_tclass); + if (unlikely(orig_tclass && !tclass)) { + if (policydb->allow_unknown) + goto allow; + goto out; + } + context_struct_compute_av(policydb, scontext, tcontext, tclass, avd, + xperms); + map_decision(&state->ss->map, orig_tclass, avd, + policydb->allow_unknown); +out: + read_unlock(&state->ss->policy_rwlock); + return; +allow: + avd->allowed = 0xffffffff; + goto out; +} + +void security_compute_av_user(struct selinux_state *state, + u32 ssid, + u32 tsid, + u16 tclass, + struct av_decision *avd) +{ + struct policydb *policydb; + struct sidtab *sidtab; + struct context *scontext = NULL, *tcontext = NULL; + + read_lock(&state->ss->policy_rwlock); + avd_init(state, avd); + if (!state->initialized) + goto allow; + + policydb = &state->ss->policydb; + sidtab = &state->ss->sidtab; + + scontext = sidtab_search(sidtab, ssid); + if (!scontext) { + pr_err("SELinux: %s: unrecognized SID %d\n", + __func__, ssid); + goto out; + } + + /* permissive domain? */ + if (ebitmap_get_bit(&policydb->permissive_map, scontext->type)) + avd->flags |= AVD_FLAGS_PERMISSIVE; + + tcontext = sidtab_search(sidtab, tsid); + if (!tcontext) { + pr_err("SELinux: %s: unrecognized SID %d\n", + __func__, tsid); + goto out; + } + + if (unlikely(!tclass)) { + if (policydb->allow_unknown) + goto allow; + goto out; + } + + context_struct_compute_av(policydb, scontext, tcontext, tclass, avd, + NULL); + out: + read_unlock(&state->ss->policy_rwlock); + return; +allow: + avd->allowed = 0xffffffff; + goto out; +} + +/* + * 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 policydb *p, + struct context *context, + char **scontext, u32 *scontext_len) +{ + char *scontextp; + + if (scontext) + *scontext = NULL; + *scontext_len = 0; + + if (context->len) { + *scontext_len = context->len; + if (scontext) { + *scontext = kstrdup(context->str, GFP_ATOMIC); + if (!(*scontext)) + return -ENOMEM; + } + return 0; + } + + /* Compute the size of the context. */ + *scontext_len += strlen(sym_name(p, SYM_USERS, context->user - 1)) + 1; + *scontext_len += strlen(sym_name(p, SYM_ROLES, context->role - 1)) + 1; + *scontext_len += strlen(sym_name(p, SYM_TYPES, context->type - 1)) + 1; + *scontext_len += mls_compute_context_len(p, context); + + if (!scontext) + return 0; + + /* 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. + */ + scontextp += sprintf(scontextp, "%s:%s:%s", + sym_name(p, SYM_USERS, context->user - 1), + sym_name(p, SYM_ROLES, context->role - 1), + sym_name(p, SYM_TYPES, context->type - 1)); + + mls_sid_to_context(p, context, &scontextp); + + *scontextp = 0; + + return 0; +} + +#include "initial_sid_to_string.h" + +const char *security_get_initial_sid_context(u32 sid) +{ + if (unlikely(sid > SECINITSID_NUM)) + return NULL; + return initial_sid_to_string[sid]; +} + +static int security_sid_to_context_core(struct selinux_state *state, + u32 sid, char **scontext, + u32 *scontext_len, int force) +{ + struct policydb *policydb; + struct sidtab *sidtab; + struct context *context; + int rc = 0; + + if (scontext) + *scontext = NULL; + *scontext_len = 0; + + if (!state->initialized) { + if (sid <= SECINITSID_NUM) { + char *scontextp; + + *scontext_len = strlen(initial_sid_to_string[sid]) + 1; + if (!scontext) + goto out; + scontextp = kmemdup(initial_sid_to_string[sid], + *scontext_len, GFP_ATOMIC); + if (!scontextp) { + rc = -ENOMEM; + goto out; + } + *scontext = scontextp; + goto out; + } + pr_err("SELinux: %s: called before initial " + "load_policy on unknown SID %d\n", __func__, sid); + rc = -EINVAL; + goto out; + } + read_lock(&state->ss->policy_rwlock); + policydb = &state->ss->policydb; + sidtab = &state->ss->sidtab; + if (force) + context = sidtab_search_force(sidtab, sid); + else + context = sidtab_search(sidtab, sid); + if (!context) { + pr_err("SELinux: %s: unrecognized SID %d\n", + __func__, sid); + rc = -EINVAL; + goto out_unlock; + } + rc = context_struct_to_string(policydb, context, scontext, + scontext_len); +out_unlock: + read_unlock(&state->ss->policy_rwlock); +out: + return rc; + +} + +/** + * 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(struct selinux_state *state, + u32 sid, char **scontext, u32 *scontext_len) +{ + return security_sid_to_context_core(state, sid, scontext, + scontext_len, 0); +} + +int security_sid_to_context_force(struct selinux_state *state, u32 sid, + char **scontext, u32 *scontext_len) +{ + return security_sid_to_context_core(state, sid, scontext, + scontext_len, 1); +} + +/* + * Caveat: Mutates scontext. + */ +static int string_to_context_struct(struct policydb *pol, + struct sidtab *sidtabp, + char *scontext, + u32 scontext_len, + struct context *ctx, + u32 def_sid) +{ + struct role_datum *role; + struct type_datum *typdatum; + struct user_datum *usrdatum; + char *scontextp, *p, oldc; + int rc = 0; + + context_init(ctx); + + /* Parse the security context. */ + + rc = -EINVAL; + scontextp = (char *) scontext; + + /* Extract the user. */ + p = scontextp; + while (*p && *p != ':') + p++; + + if (*p == 0) + goto out; + + *p++ = 0; + + usrdatum = hashtab_search(pol->p_users.table, scontextp); + if (!usrdatum) + goto out; + + ctx->user = usrdatum->value; + + /* Extract role. */ + scontextp = p; + while (*p && *p != ':') + p++; + + if (*p == 0) + goto out; + + *p++ = 0; + + role = hashtab_search(pol->p_roles.table, scontextp); + if (!role) + goto out; + ctx->role = role->value; + + /* Extract type. */ + scontextp = p; + while (*p && *p != ':') + p++; + oldc = *p; + *p++ = 0; + + typdatum = hashtab_search(pol->p_types.table, scontextp); + if (!typdatum || typdatum->attribute) + goto out; + + ctx->type = typdatum->value; + + rc = mls_context_to_sid(pol, oldc, &p, ctx, sidtabp, def_sid); + if (rc) + goto out; + + rc = -EINVAL; + if ((p - scontext) < scontext_len) + goto out; + + /* Check the validity of the new context. */ + if (!policydb_context_isvalid(pol, ctx)) + goto out; + rc = 0; +out: + if (rc) + context_destroy(ctx); + return rc; +} + +static int security_context_to_sid_core(struct selinux_state *state, + const char *scontext, u32 scontext_len, + u32 *sid, u32 def_sid, gfp_t gfp_flags, + int force) +{ + struct policydb *policydb; + struct sidtab *sidtab; + char *scontext2, *str = NULL; + struct context context; + int rc = 0; + + /* An empty security context is never valid. */ + if (!scontext_len) + return -EINVAL; + + /* Copy the string to allow changes and ensure a NUL terminator */ + scontext2 = kmemdup_nul(scontext, scontext_len, gfp_flags); + if (!scontext2) + return -ENOMEM; + + if (!state->initialized) { + int i; + + for (i = 1; i < SECINITSID_NUM; i++) { + if (!strcmp(initial_sid_to_string[i], scontext2)) { + *sid = i; + goto out; + } + } + *sid = SECINITSID_KERNEL; + goto out; + } + *sid = SECSID_NULL; + + if (force) { + /* Save another copy for storing in uninterpreted form */ + rc = -ENOMEM; + str = kstrdup(scontext2, gfp_flags); + if (!str) + goto out; + } + read_lock(&state->ss->policy_rwlock); + policydb = &state->ss->policydb; + sidtab = &state->ss->sidtab; + rc = string_to_context_struct(policydb, sidtab, scontext2, + scontext_len, &context, def_sid); + if (rc == -EINVAL && force) { + context.str = str; + context.len = strlen(str) + 1; + str = NULL; + } else if (rc) + goto out_unlock; + rc = sidtab_context_to_sid(sidtab, &context, sid); + context_destroy(&context); +out_unlock: + read_unlock(&state->ss->policy_rwlock); +out: + kfree(scontext2); + kfree(str); + 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 + * @gfp: context for the allocation + * + * 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(struct selinux_state *state, + const char *scontext, u32 scontext_len, u32 *sid, + gfp_t gfp) +{ + return security_context_to_sid_core(state, scontext, scontext_len, + sid, SECSID_NULL, gfp, 0); +} + +int security_context_str_to_sid(struct selinux_state *state, + const char *scontext, u32 *sid, gfp_t gfp) +{ + return security_context_to_sid(state, scontext, strlen(scontext), + sid, gfp); +} + +/** + * security_context_to_sid_default - Obtain a SID for a given security context, + * falling back to specified default if needed. + * + * @scontext: security context + * @scontext_len: length in bytes + * @sid: security identifier, SID + * @def_sid: default SID to assign on error + * + * Obtains a SID associated with the security context that + * has the string representation specified by @scontext. + * The default SID is passed to the MLS layer to be used to allow + * kernel labeling of the MLS field if the MLS field is not present + * (for upgrading to MLS without full relabel). + * Implicitly forces adding of the context even if it cannot be mapped yet. + * Returns -%EINVAL if the context is invalid, -%ENOMEM if insufficient + * memory is available, or 0 on success. + */ +int security_context_to_sid_default(struct selinux_state *state, + const char *scontext, u32 scontext_len, + u32 *sid, u32 def_sid, gfp_t gfp_flags) +{ + return security_context_to_sid_core(state, scontext, scontext_len, + sid, def_sid, gfp_flags, 1); +} + +int security_context_to_sid_force(struct selinux_state *state, + const char *scontext, u32 scontext_len, + u32 *sid) +{ + return security_context_to_sid_core(state, scontext, scontext_len, + sid, SECSID_NULL, GFP_KERNEL, 1); +} + +static int compute_sid_handle_invalid_context( + struct selinux_state *state, + struct context *scontext, + struct context *tcontext, + u16 tclass, + struct context *newcontext) +{ + struct policydb *policydb = &state->ss->policydb; + char *s = NULL, *t = NULL, *n = NULL; + u32 slen, tlen, nlen; + + if (context_struct_to_string(policydb, scontext, &s, &slen)) + goto out; + if (context_struct_to_string(policydb, tcontext, &t, &tlen)) + goto out; + if (context_struct_to_string(policydb, newcontext, &n, &nlen)) + goto out; + audit_log(audit_context(), GFP_ATOMIC, AUDIT_SELINUX_ERR, + "op=security_compute_sid invalid_context=%s" + " scontext=%s" + " tcontext=%s" + " tclass=%s", + n, s, t, sym_name(policydb, SYM_CLASSES, tclass-1)); +out: + kfree(s); + kfree(t); + kfree(n); + if (!enforcing_enabled(state)) + return 0; + return -EACCES; +} + +static void filename_compute_type(struct policydb *policydb, + struct context *newcontext, + u32 stype, u32 ttype, u16 tclass, + const char *objname) +{ + struct filename_trans ft; + struct filename_trans_datum *otype; + + /* + * Most filename trans rules are going to live in specific directories + * like /dev or /var/run. This bitmap will quickly skip rule searches + * if the ttype does not contain any rules. + */ + if (!ebitmap_get_bit(&policydb->filename_trans_ttypes, ttype)) + return; + + ft.stype = stype; + ft.ttype = ttype; + ft.tclass = tclass; + ft.name = objname; + + otype = hashtab_search(policydb->filename_trans, &ft); + if (otype) + newcontext->type = otype->otype; +} + +static int security_compute_sid(struct selinux_state *state, + u32 ssid, + u32 tsid, + u16 orig_tclass, + u32 specified, + const char *objname, + u32 *out_sid, + bool kern) +{ + struct policydb *policydb; + struct sidtab *sidtab; + struct class_datum *cladatum = NULL; + struct context *scontext = NULL, *tcontext = NULL, newcontext; + struct role_trans *roletr = NULL; + struct avtab_key avkey; + struct avtab_datum *avdatum; + struct avtab_node *node; + u16 tclass; + int rc = 0; + bool sock; + + if (!state->initialized) { + switch (orig_tclass) { + case SECCLASS_PROCESS: /* kernel value */ + *out_sid = ssid; + break; + default: + *out_sid = tsid; + break; + } + goto out; + } + + context_init(&newcontext); + + read_lock(&state->ss->policy_rwlock); + + if (kern) { + tclass = unmap_class(&state->ss->map, orig_tclass); + sock = security_is_socket_class(orig_tclass); + } else { + tclass = orig_tclass; + sock = security_is_socket_class(map_class(&state->ss->map, + tclass)); + } + + policydb = &state->ss->policydb; + sidtab = &state->ss->sidtab; + + scontext = sidtab_search(sidtab, ssid); + if (!scontext) { + pr_err("SELinux: %s: unrecognized SID %d\n", + __func__, ssid); + rc = -EINVAL; + goto out_unlock; + } + tcontext = sidtab_search(sidtab, tsid); + if (!tcontext) { + pr_err("SELinux: %s: unrecognized SID %d\n", + __func__, tsid); + rc = -EINVAL; + goto out_unlock; + } + + if (tclass && tclass <= policydb->p_classes.nprim) + cladatum = policydb->class_val_to_struct[tclass - 1]; + + /* Set the user identity. */ + switch (specified) { + case AVTAB_TRANSITION: + case AVTAB_CHANGE: + if (cladatum && cladatum->default_user == DEFAULT_TARGET) { + newcontext.user = tcontext->user; + } else { + /* notice this gets both DEFAULT_SOURCE and unset */ + /* 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 to default values. */ + if (cladatum && cladatum->default_role == DEFAULT_SOURCE) { + newcontext.role = scontext->role; + } else if (cladatum && cladatum->default_role == DEFAULT_TARGET) { + newcontext.role = tcontext->role; + } else { + if ((tclass == policydb->process_class) || (sock == true)) + newcontext.role = scontext->role; + else + newcontext.role = OBJECT_R_VAL; + } + + /* Set the type to default values. */ + if (cladatum && cladatum->default_type == DEFAULT_SOURCE) { + newcontext.type = scontext->type; + } else if (cladatum && cladatum->default_type == DEFAULT_TARGET) { + newcontext.type = tcontext->type; + } else { + if ((tclass == policydb->process_class) || (sock == true)) { + /* Use the type of process. */ + newcontext.type = scontext->type; + } else { + /* 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; + avkey.specified = specified; + avdatum = avtab_search(&policydb->te_avtab, &avkey); + + /* If no permanent rule, also check for enabled conditional rules */ + if (!avdatum) { + node = avtab_search_node(&policydb->te_cond_avtab, &avkey); + for (; node; node = avtab_search_node_next(node, specified)) { + if (node->key.specified & AVTAB_ENABLED) { + avdatum = &node->datum; + break; + } + } + } + + if (avdatum) { + /* Use the type from the type transition/member/change rule. */ + newcontext.type = avdatum->u.data; + } + + /* if we have a objname this is a file trans check so check those rules */ + if (objname) + filename_compute_type(policydb, &newcontext, scontext->type, + tcontext->type, tclass, objname); + + /* Check for class-specific changes. */ + 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) && + (roletr->tclass == tclass)) { + /* Use the role transition rule. */ + newcontext.role = roletr->new_role; + break; + } + } + } + + /* Set the MLS attributes. + This is done last because it may allocate memory. */ + rc = mls_compute_sid(policydb, scontext, tcontext, tclass, specified, + &newcontext, sock); + if (rc) + goto out_unlock; + + /* Check the validity of the context. */ + if (!policydb_context_isvalid(policydb, &newcontext)) { + rc = compute_sid_handle_invalid_context(state, 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: + read_unlock(&state->ss->policy_rwlock); + 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(struct selinux_state *state, + u32 ssid, u32 tsid, u16 tclass, + const struct qstr *qstr, u32 *out_sid) +{ + return security_compute_sid(state, ssid, tsid, tclass, + AVTAB_TRANSITION, + qstr ? qstr->name : NULL, out_sid, true); +} + +int security_transition_sid_user(struct selinux_state *state, + u32 ssid, u32 tsid, u16 tclass, + const char *objname, u32 *out_sid) +{ + return security_compute_sid(state, ssid, tsid, tclass, + AVTAB_TRANSITION, + objname, out_sid, false); +} + +/** + * 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(struct selinux_state *state, + u32 ssid, + u32 tsid, + u16 tclass, + u32 *out_sid) +{ + return security_compute_sid(state, ssid, tsid, tclass, + AVTAB_MEMBER, NULL, + out_sid, false); +} + +/** + * 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(struct selinux_state *state, + u32 ssid, + u32 tsid, + u16 tclass, + u32 *out_sid) +{ + return security_compute_sid(state, + ssid, tsid, tclass, AVTAB_CHANGE, NULL, + out_sid, false); +} + +/* Clone the SID into the new SID table. */ +static int clone_sid(u32 sid, + struct context *context, + void *arg) +{ + struct sidtab *s = arg; + + if (sid > SECINITSID_NUM) + return sidtab_insert(s, sid, context); + else + return 0; +} + +static inline int convert_context_handle_invalid_context( + struct selinux_state *state, + struct context *context) +{ + struct policydb *policydb = &state->ss->policydb; + char *s; + u32 len; + + if (enforcing_enabled(state)) + return -EINVAL; + + if (!context_struct_to_string(policydb, context, &s, &len)) { + pr_warn("SELinux: Context %s would be invalid if enforcing\n", + s); + kfree(s); + } + return 0; +} + +struct convert_context_args { + struct selinux_state *state; + 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 ocontext *oc; + struct mls_range *range; + struct role_datum *role; + struct type_datum *typdatum; + struct user_datum *usrdatum; + char *s; + u32 len; + int rc = 0; + + if (key <= SECINITSID_NUM) + goto out; + + args = p; + + if (c->str) { + struct context ctx; + + rc = -ENOMEM; + s = kstrdup(c->str, GFP_KERNEL); + if (!s) + goto out; + + rc = string_to_context_struct(args->newp, NULL, s, + c->len, &ctx, SECSID_NULL); + kfree(s); + if (!rc) { + pr_info("SELinux: Context %s became valid (mapped).\n", + c->str); + /* Replace string with mapped representation. */ + kfree(c->str); + memcpy(c, &ctx, sizeof(*c)); + goto out; + } else if (rc == -EINVAL) { + /* Retain string representation for later mapping. */ + rc = 0; + goto out; + } else { + /* Other error condition, e.g. ENOMEM. */ + pr_err("SELinux: Unable to map context %s, rc = %d.\n", + c->str, -rc); + goto out; + } + } + + rc = context_cpy(&oldc, c); + if (rc) + goto out; + + /* Convert the user. */ + rc = -EINVAL; + usrdatum = hashtab_search(args->newp->p_users.table, + sym_name(args->oldp, SYM_USERS, c->user - 1)); + if (!usrdatum) + goto bad; + c->user = usrdatum->value; + + /* Convert the role. */ + rc = -EINVAL; + role = hashtab_search(args->newp->p_roles.table, + sym_name(args->oldp, SYM_ROLES, c->role - 1)); + if (!role) + goto bad; + c->role = role->value; + + /* Convert the type. */ + rc = -EINVAL; + typdatum = hashtab_search(args->newp->p_types.table, + sym_name(args->oldp, SYM_TYPES, c->type - 1)); + if (!typdatum) + goto bad; + c->type = typdatum->value; + + /* Convert the MLS fields if dealing with MLS policies */ + if (args->oldp->mls_enabled && args->newp->mls_enabled) { + rc = mls_convert_context(args->oldp, args->newp, c); + if (rc) + goto bad; + } else if (args->oldp->mls_enabled && !args->newp->mls_enabled) { + /* + * Switching between MLS and non-MLS policy: + * free any storage used by the MLS fields in the + * context for all existing entries in the sidtab. + */ + mls_context_destroy(c); + } else if (!args->oldp->mls_enabled && args->newp->mls_enabled) { + /* + * Switching between non-MLS and MLS policy: + * ensure that the MLS fields of the context for all + * existing entries in the sidtab are filled in with a + * suitable default value, likely taken from one of the + * initial SIDs. + */ + oc = args->newp->ocontexts[OCON_ISID]; + while (oc && oc->sid[0] != SECINITSID_UNLABELED) + oc = oc->next; + rc = -EINVAL; + if (!oc) { + pr_err("SELinux: unable to look up" + " the initial SIDs list\n"); + goto bad; + } + range = &oc->context[0].range; + rc = mls_range_set(c, range); + if (rc) + goto bad; + } + + /* Check the validity of the new context. */ + if (!policydb_context_isvalid(args->newp, c)) { + rc = convert_context_handle_invalid_context(args->state, + &oldc); + if (rc) + goto bad; + } + + context_destroy(&oldc); + + rc = 0; +out: + return rc; +bad: + /* Map old representation to string and save it. */ + rc = context_struct_to_string(args->oldp, &oldc, &s, &len); + if (rc) + return rc; + context_destroy(&oldc); + context_destroy(c); + c->str = s; + c->len = len; + pr_info("SELinux: Context %s became invalid (unmapped).\n", + c->str); + rc = 0; + goto out; +} + +static void security_load_policycaps(struct selinux_state *state) +{ + struct policydb *p = &state->ss->policydb; + unsigned int i; + struct ebitmap_node *node; + + for (i = 0; i < ARRAY_SIZE(state->policycap); i++) + state->policycap[i] = ebitmap_get_bit(&p->policycaps, i); + + for (i = 0; i < ARRAY_SIZE(selinux_policycap_names); i++) + pr_info("SELinux: policy capability %s=%d\n", + selinux_policycap_names[i], + ebitmap_get_bit(&p->policycaps, i)); + + ebitmap_for_each_positive_bit(&p->policycaps, node, i) { + if (i >= ARRAY_SIZE(selinux_policycap_names)) + pr_info("SELinux: unknown policy capability %u\n", + i); + } +} + +static int security_preserve_bools(struct selinux_state *state, + struct policydb *newpolicydb); + +/** + * 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(struct selinux_state *state, void *data, size_t len) +{ + struct policydb *policydb; + struct sidtab *sidtab; + struct policydb *oldpolicydb, *newpolicydb; + struct sidtab oldsidtab, newsidtab; + struct selinux_mapping *oldmapping; + struct selinux_map newmap; + struct convert_context_args args; + u32 seqno; + int rc = 0; + struct policy_file file = { data, len }, *fp = &file; + + oldpolicydb = kcalloc(2, sizeof(*oldpolicydb), GFP_KERNEL); + if (!oldpolicydb) { + rc = -ENOMEM; + goto out; + } + newpolicydb = oldpolicydb + 1; + + policydb = &state->ss->policydb; + sidtab = &state->ss->sidtab; + + if (!state->initialized) { + rc = policydb_read(policydb, fp); + if (rc) + goto out; + + policydb->len = len; + rc = selinux_set_mapping(policydb, secclass_map, + &state->ss->map); + if (rc) { + policydb_destroy(policydb); + goto out; + } + + rc = policydb_load_isids(policydb, sidtab); + if (rc) { + policydb_destroy(policydb); + goto out; + } + + security_load_policycaps(state); + state->initialized = 1; + seqno = ++state->ss->latest_granting; + selinux_complete_init(); + avc_ss_reset(state->avc, seqno); + selnl_notify_policyload(seqno); + selinux_status_update_policyload(state, seqno); + selinux_netlbl_cache_invalidate(); + selinux_xfrm_notify_policyload(); + goto out; + } + +#if 0 + sidtab_hash_eval(sidtab, "sids"); +#endif + + rc = policydb_read(newpolicydb, fp); + if (rc) + goto out; + + newpolicydb->len = len; + /* If switching between different policy types, log MLS status */ + if (policydb->mls_enabled && !newpolicydb->mls_enabled) + pr_info("SELinux: Disabling MLS support...\n"); + else if (!policydb->mls_enabled && newpolicydb->mls_enabled) + pr_info("SELinux: Enabling MLS support...\n"); + + rc = policydb_load_isids(newpolicydb, &newsidtab); + if (rc) { + pr_err("SELinux: unable to load the initial SIDs\n"); + policydb_destroy(newpolicydb); + goto out; + } + + rc = selinux_set_mapping(newpolicydb, secclass_map, &newmap); + if (rc) + goto err; + + rc = security_preserve_bools(state, newpolicydb); + if (rc) { + pr_err("SELinux: unable to preserve booleans\n"); + goto err; + } + + /* Clone the SID table. */ + sidtab_shutdown(sidtab); + + rc = sidtab_map(sidtab, clone_sid, &newsidtab); + if (rc) + goto err; + + /* + * Convert the internal representations of contexts + * in the new SID table. + */ + args.state = state; + args.oldp = policydb; + args.newp = newpolicydb; + rc = sidtab_map(&newsidtab, convert_context, &args); + if (rc) { + pr_err("SELinux: unable to convert the internal" + " representation of contexts in the new SID" + " table\n"); + goto err; + } + + /* 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. */ + write_lock_irq(&state->ss->policy_rwlock); + memcpy(policydb, newpolicydb, sizeof(*policydb)); + sidtab_set(sidtab, &newsidtab); + security_load_policycaps(state); + oldmapping = state->ss->map.mapping; + state->ss->map.mapping = newmap.mapping; + state->ss->map.size = newmap.size; + seqno = ++state->ss->latest_granting; + write_unlock_irq(&state->ss->policy_rwlock); + + /* Free the old policydb and SID table. */ + policydb_destroy(oldpolicydb); + sidtab_destroy(&oldsidtab); + kfree(oldmapping); + + avc_ss_reset(state->avc, seqno); + selnl_notify_policyload(seqno); + selinux_status_update_policyload(state, seqno); + selinux_netlbl_cache_invalidate(); + selinux_xfrm_notify_policyload(); + + rc = 0; + goto out; + +err: + kfree(newmap.mapping); + sidtab_destroy(&newsidtab); + policydb_destroy(newpolicydb); + +out: + kfree(oldpolicydb); + return rc; +} + +size_t security_policydb_len(struct selinux_state *state) +{ + struct policydb *p = &state->ss->policydb; + size_t len; + + read_lock(&state->ss->policy_rwlock); + len = p->len; + read_unlock(&state->ss->policy_rwlock); + + return len; +} + +/** + * security_port_sid - Obtain the SID for a port. + * @protocol: protocol number + * @port: port number + * @out_sid: security identifier + */ +int security_port_sid(struct selinux_state *state, + u8 protocol, u16 port, u32 *out_sid) +{ + struct policydb *policydb; + struct sidtab *sidtab; + struct ocontext *c; + int rc = 0; + + read_lock(&state->ss->policy_rwlock); + + policydb = &state->ss->policydb; + sidtab = &state->ss->sidtab; + + 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: + read_unlock(&state->ss->policy_rwlock); + return rc; +} + +/** + * security_pkey_sid - Obtain the SID for a pkey. + * @subnet_prefix: Subnet Prefix + * @pkey_num: pkey number + * @out_sid: security identifier + */ +int security_ib_pkey_sid(struct selinux_state *state, + u64 subnet_prefix, u16 pkey_num, u32 *out_sid) +{ + struct policydb *policydb; + struct sidtab *sidtab; + struct ocontext *c; + int rc = 0; + + read_lock(&state->ss->policy_rwlock); + + policydb = &state->ss->policydb; + sidtab = &state->ss->sidtab; + + c = policydb->ocontexts[OCON_IBPKEY]; + while (c) { + if (c->u.ibpkey.low_pkey <= pkey_num && + c->u.ibpkey.high_pkey >= pkey_num && + c->u.ibpkey.subnet_prefix == subnet_prefix) + 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_UNLABELED; + +out: + read_unlock(&state->ss->policy_rwlock); + return rc; +} + +/** + * security_ib_endport_sid - Obtain the SID for a subnet management interface. + * @dev_name: device name + * @port: port number + * @out_sid: security identifier + */ +int security_ib_endport_sid(struct selinux_state *state, + const char *dev_name, u8 port_num, u32 *out_sid) +{ + struct policydb *policydb; + struct sidtab *sidtab; + struct ocontext *c; + int rc = 0; + + read_lock(&state->ss->policy_rwlock); + + policydb = &state->ss->policydb; + sidtab = &state->ss->sidtab; + + c = policydb->ocontexts[OCON_IBENDPORT]; + while (c) { + if (c->u.ibendport.port == port_num && + !strncmp(c->u.ibendport.dev_name, + dev_name, + IB_DEVICE_NAME_MAX)) + 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_UNLABELED; + +out: + read_unlock(&state->ss->policy_rwlock); + return rc; +} + +/** + * security_netif_sid - Obtain the SID for a network interface. + * @name: interface name + * @if_sid: interface SID + */ +int security_netif_sid(struct selinux_state *state, + char *name, u32 *if_sid) +{ + struct policydb *policydb; + struct sidtab *sidtab; + int rc = 0; + struct ocontext *c; + + read_lock(&state->ss->policy_rwlock); + + policydb = &state->ss->policydb; + sidtab = &state->ss->sidtab; + + 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]; + } else + *if_sid = SECINITSID_NETIF; + +out: + read_unlock(&state->ss->policy_rwlock); + 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(struct selinux_state *state, + u16 domain, + void *addrp, + u32 addrlen, + u32 *out_sid) +{ + struct policydb *policydb; + struct sidtab *sidtab; + int rc; + struct ocontext *c; + + read_lock(&state->ss->policy_rwlock); + + policydb = &state->ss->policydb; + sidtab = &state->ss->sidtab; + + switch (domain) { + case AF_INET: { + u32 addr; + + rc = -EINVAL; + if (addrlen != sizeof(u32)) + 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: + rc = -EINVAL; + if (addrlen != sizeof(u64) * 2) + 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: + rc = 0; + *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; + } + + rc = 0; +out: + read_unlock(&state->ss->policy_rwlock); + 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(struct selinux_state *state, + u32 fromsid, + char *username, + u32 **sids, + u32 *nel) +{ + struct policydb *policydb; + struct sidtab *sidtab; + struct context *fromcon, usercon; + u32 *mysids = NULL, *mysids2, sid; + u32 mynel = 0, maxnel = SIDS_NEL; + struct user_datum *user; + struct role_datum *role; + struct ebitmap_node *rnode, *tnode; + int rc = 0, i, j; + + *sids = NULL; + *nel = 0; + + if (!state->initialized) + goto out; + + read_lock(&state->ss->policy_rwlock); + + policydb = &state->ss->policydb; + sidtab = &state->ss->sidtab; + + context_init(&usercon); + + rc = -EINVAL; + fromcon = sidtab_search(sidtab, fromsid); + if (!fromcon) + goto out_unlock; + + rc = -EINVAL; + user = hashtab_search(policydb->p_users.table, username); + if (!user) + goto out_unlock; + + usercon.user = user->value; + + rc = -ENOMEM; + mysids = kcalloc(maxnel, sizeof(*mysids), GFP_ATOMIC); + if (!mysids) + goto out_unlock; + + ebitmap_for_each_positive_bit(&user->roles, rnode, i) { + role = policydb->role_val_to_struct[i]; + usercon.role = i + 1; + ebitmap_for_each_positive_bit(&role->types, tnode, j) { + usercon.type = j + 1; + + if (mls_setup_user_range(policydb, fromcon, user, + &usercon)) + continue; + + rc = sidtab_context_to_sid(sidtab, &usercon, &sid); + if (rc) + goto out_unlock; + if (mynel < maxnel) { + mysids[mynel++] = sid; + } else { + rc = -ENOMEM; + maxnel += SIDS_NEL; + mysids2 = kcalloc(maxnel, sizeof(*mysids2), GFP_ATOMIC); + if (!mysids2) + goto out_unlock; + memcpy(mysids2, mysids, mynel * sizeof(*mysids2)); + kfree(mysids); + mysids = mysids2; + mysids[mynel++] = sid; + } + } + } + rc = 0; +out_unlock: + read_unlock(&state->ss->policy_rwlock); + if (rc || !mynel) { + kfree(mysids); + goto out; + } + + rc = -ENOMEM; + mysids2 = kcalloc(mynel, sizeof(*mysids2), GFP_KERNEL); + if (!mysids2) { + kfree(mysids); + goto out; + } + for (i = 0, j = 0; i < mynel; i++) { + struct av_decision dummy_avd; + rc = avc_has_perm_noaudit(state, + fromsid, mysids[i], + SECCLASS_PROCESS, /* kernel value */ + PROCESS__TRANSITION, AVC_STRICT, + &dummy_avd); + if (!rc) + mysids2[j++] = mysids[i]; + cond_resched(); + } + rc = 0; + kfree(mysids); + *sids = mysids2; + *nel = j; +out: + return rc; +} + +/** + * __security_genfs_sid - Helper to 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. + * + * The caller must acquire the policy_rwlock before calling this function. + */ +static inline int __security_genfs_sid(struct selinux_state *state, + const char *fstype, + char *path, + u16 orig_sclass, + u32 *sid) +{ + struct policydb *policydb = &state->ss->policydb; + struct sidtab *sidtab = &state->ss->sidtab; + int len; + u16 sclass; + struct genfs *genfs; + struct ocontext *c; + int rc, cmp = 0; + + while (path[0] == '/' && path[1] == '/') + path++; + + sclass = unmap_class(&state->ss->map, orig_sclass); + *sid = SECINITSID_UNLABELED; + + for (genfs = policydb->genfs; genfs; genfs = genfs->next) { + cmp = strcmp(fstype, genfs->fstype); + if (cmp <= 0) + break; + } + + rc = -ENOENT; + if (!genfs || cmp) + 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; + } + + rc = -ENOENT; + if (!c) + 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]; + rc = 0; +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 + * + * Acquire policy_rwlock before calling __security_genfs_sid() and release + * it afterward. + */ +int security_genfs_sid(struct selinux_state *state, + const char *fstype, + char *path, + u16 orig_sclass, + u32 *sid) +{ + int retval; + + read_lock(&state->ss->policy_rwlock); + retval = __security_genfs_sid(state, fstype, path, orig_sclass, sid); + read_unlock(&state->ss->policy_rwlock); + return retval; +} + +/** + * security_fs_use - Determine how to handle labeling for a filesystem. + * @sb: superblock in question + */ +int security_fs_use(struct selinux_state *state, struct super_block *sb) +{ + struct policydb *policydb; + struct sidtab *sidtab; + int rc = 0; + struct ocontext *c; + struct superblock_security_struct *sbsec = sb->s_security; + const char *fstype = sb->s_type->name; + + read_lock(&state->ss->policy_rwlock); + + policydb = &state->ss->policydb; + sidtab = &state->ss->sidtab; + + c = policydb->ocontexts[OCON_FSUSE]; + while (c) { + if (strcmp(fstype, c->u.name) == 0) + break; + c = c->next; + } + + if (c) { + sbsec->behavior = c->v.behavior; + if (!c->sid[0]) { + rc = sidtab_context_to_sid(sidtab, &c->context[0], + &c->sid[0]); + if (rc) + goto out; + } + sbsec->sid = c->sid[0]; + } else { + rc = __security_genfs_sid(state, fstype, "/", SECCLASS_DIR, + &sbsec->sid); + if (rc) { + sbsec->behavior = SECURITY_FS_USE_NONE; + rc = 0; + } else { + sbsec->behavior = SECURITY_FS_USE_GENFS; + } + } + +out: + read_unlock(&state->ss->policy_rwlock); + return rc; +} + +int security_get_bools(struct selinux_state *state, + int *len, char ***names, int **values) +{ + struct policydb *policydb; + int i, rc; + + if (!state->initialized) { + *len = 0; + *names = NULL; + *values = NULL; + return 0; + } + + read_lock(&state->ss->policy_rwlock); + + policydb = &state->ss->policydb; + + *names = NULL; + *values = NULL; + + rc = 0; + *len = policydb->p_bools.nprim; + if (!*len) + goto out; + + rc = -ENOMEM; + *names = kcalloc(*len, sizeof(char *), GFP_ATOMIC); + if (!*names) + goto err; + + rc = -ENOMEM; + *values = kcalloc(*len, sizeof(int), GFP_ATOMIC); + if (!*values) + goto err; + + for (i = 0; i < *len; i++) { + (*values)[i] = policydb->bool_val_to_struct[i]->state; + + rc = -ENOMEM; + (*names)[i] = kstrdup(sym_name(policydb, SYM_BOOLS, i), + GFP_ATOMIC); + if (!(*names)[i]) + goto err; + } + rc = 0; +out: + read_unlock(&state->ss->policy_rwlock); + return rc; +err: + if (*names) { + for (i = 0; i < *len; i++) + kfree((*names)[i]); + kfree(*names); + } + kfree(*values); + *len = 0; + *names = NULL; + *values = NULL; + goto out; +} + + +int security_set_bools(struct selinux_state *state, int len, int *values) +{ + struct policydb *policydb; + int i, rc; + int lenp, seqno = 0; + struct cond_node *cur; + + write_lock_irq(&state->ss->policy_rwlock); + + policydb = &state->ss->policydb; + + rc = -EFAULT; + lenp = policydb->p_bools.nprim; + if (len != lenp) + goto out; + + for (i = 0; i < len; i++) { + if (!!values[i] != policydb->bool_val_to_struct[i]->state) { + audit_log(audit_context(), GFP_ATOMIC, + AUDIT_MAC_CONFIG_CHANGE, + "bool=%s val=%d old_val=%d auid=%u ses=%u", + sym_name(policydb, SYM_BOOLS, i), + !!values[i], + policydb->bool_val_to_struct[i]->state, + from_kuid(&init_user_ns, audit_get_loginuid(current)), + audit_get_sessionid(current)); + } + if (values[i]) + policydb->bool_val_to_struct[i]->state = 1; + else + policydb->bool_val_to_struct[i]->state = 0; + } + + for (cur = policydb->cond_list; cur; cur = cur->next) { + rc = evaluate_cond_node(policydb, cur); + if (rc) + goto out; + } + + seqno = ++state->ss->latest_granting; + rc = 0; +out: + write_unlock_irq(&state->ss->policy_rwlock); + if (!rc) { + avc_ss_reset(state->avc, seqno); + selnl_notify_policyload(seqno); + selinux_status_update_policyload(state, seqno); + selinux_xfrm_notify_policyload(); + } + return rc; +} + +int security_get_bool_value(struct selinux_state *state, + int index) +{ + struct policydb *policydb; + int rc; + int len; + + read_lock(&state->ss->policy_rwlock); + + policydb = &state->ss->policydb; + + rc = -EFAULT; + len = policydb->p_bools.nprim; + if (index >= len) + goto out; + + rc = policydb->bool_val_to_struct[index]->state; +out: + read_unlock(&state->ss->policy_rwlock); + return rc; +} + +static int security_preserve_bools(struct selinux_state *state, + struct policydb *policydb) +{ + int rc, nbools = 0, *bvalues = NULL, i; + char **bnames = NULL; + struct cond_bool_datum *booldatum; + struct cond_node *cur; + + rc = security_get_bools(state, &nbools, &bnames, &bvalues); + if (rc) + goto out; + for (i = 0; i < nbools; i++) { + booldatum = hashtab_search(policydb->p_bools.table, bnames[i]); + if (booldatum) + booldatum->state = bvalues[i]; + } + for (cur = policydb->cond_list; cur; cur = cur->next) { + rc = evaluate_cond_node(policydb, cur); + if (rc) + goto out; + } + +out: + if (bnames) { + for (i = 0; i < nbools; i++) + kfree(bnames[i]); + } + kfree(bnames); + kfree(bvalues); + return rc; +} + +/* + * security_sid_mls_copy() - computes a new sid based on the given + * sid and the mls portion of mls_sid. + */ +int security_sid_mls_copy(struct selinux_state *state, + u32 sid, u32 mls_sid, u32 *new_sid) +{ + struct policydb *policydb = &state->ss->policydb; + struct sidtab *sidtab = &state->ss->sidtab; + struct context *context1; + struct context *context2; + struct context newcon; + char *s; + u32 len; + int rc; + + rc = 0; + if (!state->initialized || !policydb->mls_enabled) { + *new_sid = sid; + goto out; + } + + context_init(&newcon); + + read_lock(&state->ss->policy_rwlock); + + rc = -EINVAL; + context1 = sidtab_search(sidtab, sid); + if (!context1) { + pr_err("SELinux: %s: unrecognized SID %d\n", + __func__, sid); + goto out_unlock; + } + + rc = -EINVAL; + context2 = sidtab_search(sidtab, mls_sid); + if (!context2) { + pr_err("SELinux: %s: unrecognized SID %d\n", + __func__, mls_sid); + goto out_unlock; + } + + newcon.user = context1->user; + newcon.role = context1->role; + newcon.type = context1->type; + rc = mls_context_cpy(&newcon, context2); + if (rc) + goto out_unlock; + + /* Check the validity of the new context. */ + if (!policydb_context_isvalid(policydb, &newcon)) { + rc = convert_context_handle_invalid_context(state, &newcon); + if (rc) { + if (!context_struct_to_string(policydb, &newcon, &s, + &len)) { + audit_log(audit_context(), + GFP_ATOMIC, AUDIT_SELINUX_ERR, + "op=security_sid_mls_copy " + "invalid_context=%s", s); + kfree(s); + } + goto out_unlock; + } + } + + rc = sidtab_context_to_sid(sidtab, &newcon, new_sid); +out_unlock: + read_unlock(&state->ss->policy_rwlock); + context_destroy(&newcon); +out: + return rc; +} + +/** + * security_net_peersid_resolve - Compare and resolve two network peer SIDs + * @nlbl_sid: NetLabel SID + * @nlbl_type: NetLabel labeling protocol type + * @xfrm_sid: XFRM SID + * + * Description: + * Compare the @nlbl_sid and @xfrm_sid values and if the two SIDs can be + * resolved into a single SID it is returned via @peer_sid and the function + * returns zero. Otherwise @peer_sid is set to SECSID_NULL and the function + * returns a negative value. A table summarizing the behavior is below: + * + * | function return | @sid + * ------------------------------+-----------------+----------------- + * no peer labels | 0 | SECSID_NULL + * single peer label | 0 | <peer_label> + * multiple, consistent labels | 0 | <peer_label> + * multiple, inconsistent labels | -<errno> | SECSID_NULL + * + */ +int security_net_peersid_resolve(struct selinux_state *state, + u32 nlbl_sid, u32 nlbl_type, + u32 xfrm_sid, + u32 *peer_sid) +{ + struct policydb *policydb = &state->ss->policydb; + struct sidtab *sidtab = &state->ss->sidtab; + int rc; + struct context *nlbl_ctx; + struct context *xfrm_ctx; + + *peer_sid = SECSID_NULL; + + /* handle the common (which also happens to be the set of easy) cases + * right away, these two if statements catch everything involving a + * single or absent peer SID/label */ + if (xfrm_sid == SECSID_NULL) { + *peer_sid = nlbl_sid; + return 0; + } + /* NOTE: an nlbl_type == NETLBL_NLTYPE_UNLABELED is a "fallback" label + * and is treated as if nlbl_sid == SECSID_NULL when a XFRM SID/label + * is present */ + if (nlbl_sid == SECSID_NULL || nlbl_type == NETLBL_NLTYPE_UNLABELED) { + *peer_sid = xfrm_sid; + return 0; + } + + /* + * We don't need to check initialized here since the only way both + * nlbl_sid and xfrm_sid are not equal to SECSID_NULL would be if the + * security server was initialized and state->initialized was true. + */ + if (!policydb->mls_enabled) + return 0; + + read_lock(&state->ss->policy_rwlock); + + rc = -EINVAL; + nlbl_ctx = sidtab_search(sidtab, nlbl_sid); + if (!nlbl_ctx) { + pr_err("SELinux: %s: unrecognized SID %d\n", + __func__, nlbl_sid); + goto out; + } + rc = -EINVAL; + xfrm_ctx = sidtab_search(sidtab, xfrm_sid); + if (!xfrm_ctx) { + pr_err("SELinux: %s: unrecognized SID %d\n", + __func__, xfrm_sid); + goto out; + } + rc = (mls_context_cmp(nlbl_ctx, xfrm_ctx) ? 0 : -EACCES); + if (rc) + goto out; + + /* at present NetLabel SIDs/labels really only carry MLS + * information so if the MLS portion of the NetLabel SID + * matches the MLS portion of the labeled XFRM SID/label + * then pass along the XFRM SID as it is the most + * expressive */ + *peer_sid = xfrm_sid; +out: + read_unlock(&state->ss->policy_rwlock); + return rc; +} + +static int get_classes_callback(void *k, void *d, void *args) +{ + struct class_datum *datum = d; + char *name = k, **classes = args; + int value = datum->value - 1; + + classes[value] = kstrdup(name, GFP_ATOMIC); + if (!classes[value]) + return -ENOMEM; + + return 0; +} + +int security_get_classes(struct selinux_state *state, + char ***classes, int *nclasses) +{ + struct policydb *policydb = &state->ss->policydb; + int rc; + + if (!state->initialized) { + *nclasses = 0; + *classes = NULL; + return 0; + } + + read_lock(&state->ss->policy_rwlock); + + rc = -ENOMEM; + *nclasses = policydb->p_classes.nprim; + *classes = kcalloc(*nclasses, sizeof(**classes), GFP_ATOMIC); + if (!*classes) + goto out; + + rc = hashtab_map(policydb->p_classes.table, get_classes_callback, + *classes); + if (rc) { + int i; + for (i = 0; i < *nclasses; i++) + kfree((*classes)[i]); + kfree(*classes); + } + +out: + read_unlock(&state->ss->policy_rwlock); + return rc; +} + +static int get_permissions_callback(void *k, void *d, void *args) +{ + struct perm_datum *datum = d; + char *name = k, **perms = args; + int value = datum->value - 1; + + perms[value] = kstrdup(name, GFP_ATOMIC); + if (!perms[value]) + return -ENOMEM; + + return 0; +} + +int security_get_permissions(struct selinux_state *state, + char *class, char ***perms, int *nperms) +{ + struct policydb *policydb = &state->ss->policydb; + int rc, i; + struct class_datum *match; + + read_lock(&state->ss->policy_rwlock); + + rc = -EINVAL; + match = hashtab_search(policydb->p_classes.table, class); + if (!match) { + pr_err("SELinux: %s: unrecognized class %s\n", + __func__, class); + goto out; + } + + rc = -ENOMEM; + *nperms = match->permissions.nprim; + *perms = kcalloc(*nperms, sizeof(**perms), GFP_ATOMIC); + if (!*perms) + goto out; + + if (match->comdatum) { + rc = hashtab_map(match->comdatum->permissions.table, + get_permissions_callback, *perms); + if (rc) + goto err; + } + + rc = hashtab_map(match->permissions.table, get_permissions_callback, + *perms); + if (rc) + goto err; + +out: + read_unlock(&state->ss->policy_rwlock); + return rc; + +err: + read_unlock(&state->ss->policy_rwlock); + for (i = 0; i < *nperms; i++) + kfree((*perms)[i]); + kfree(*perms); + return rc; +} + +int security_get_reject_unknown(struct selinux_state *state) +{ + return state->ss->policydb.reject_unknown; +} + +int security_get_allow_unknown(struct selinux_state *state) +{ + return state->ss->policydb.allow_unknown; +} + +/** + * security_policycap_supported - Check for a specific policy capability + * @req_cap: capability + * + * Description: + * This function queries the currently loaded policy to see if it supports the + * capability specified by @req_cap. Returns true (1) if the capability is + * supported, false (0) if it isn't supported. + * + */ +int security_policycap_supported(struct selinux_state *state, + unsigned int req_cap) +{ + struct policydb *policydb = &state->ss->policydb; + int rc; + + read_lock(&state->ss->policy_rwlock); + rc = ebitmap_get_bit(&policydb->policycaps, req_cap); + read_unlock(&state->ss->policy_rwlock); + + return rc; +} + +struct selinux_audit_rule { + u32 au_seqno; + struct context au_ctxt; +}; + +void selinux_audit_rule_free(void *vrule) +{ + struct selinux_audit_rule *rule = vrule; + + if (rule) { + context_destroy(&rule->au_ctxt); + kfree(rule); + } +} + +int selinux_audit_rule_init(u32 field, u32 op, char *rulestr, void **vrule) +{ + struct selinux_state *state = &selinux_state; + struct policydb *policydb = &state->ss->policydb; + struct selinux_audit_rule *tmprule; + struct role_datum *roledatum; + struct type_datum *typedatum; + struct user_datum *userdatum; + struct selinux_audit_rule **rule = (struct selinux_audit_rule **)vrule; + int rc = 0; + + *rule = NULL; + + if (!state->initialized) + return -EOPNOTSUPP; + + switch (field) { + case AUDIT_SUBJ_USER: + case AUDIT_SUBJ_ROLE: + case AUDIT_SUBJ_TYPE: + case AUDIT_OBJ_USER: + case AUDIT_OBJ_ROLE: + case AUDIT_OBJ_TYPE: + /* only 'equals' and 'not equals' fit user, role, and type */ + if (op != Audit_equal && op != Audit_not_equal) + return -EINVAL; + break; + case AUDIT_SUBJ_SEN: + case AUDIT_SUBJ_CLR: + case AUDIT_OBJ_LEV_LOW: + case AUDIT_OBJ_LEV_HIGH: + /* we do not allow a range, indicated by the presence of '-' */ + if (strchr(rulestr, '-')) + return -EINVAL; + break; + default: + /* only the above fields are valid */ + return -EINVAL; + } + + tmprule = kzalloc(sizeof(struct selinux_audit_rule), GFP_KERNEL); + if (!tmprule) + return -ENOMEM; + + context_init(&tmprule->au_ctxt); + + read_lock(&state->ss->policy_rwlock); + + tmprule->au_seqno = state->ss->latest_granting; + + switch (field) { + case AUDIT_SUBJ_USER: + case AUDIT_OBJ_USER: + rc = -EINVAL; + userdatum = hashtab_search(policydb->p_users.table, rulestr); + if (!userdatum) + goto out; + tmprule->au_ctxt.user = userdatum->value; + break; + case AUDIT_SUBJ_ROLE: + case AUDIT_OBJ_ROLE: + rc = -EINVAL; + roledatum = hashtab_search(policydb->p_roles.table, rulestr); + if (!roledatum) + goto out; + tmprule->au_ctxt.role = roledatum->value; + break; + case AUDIT_SUBJ_TYPE: + case AUDIT_OBJ_TYPE: + rc = -EINVAL; + typedatum = hashtab_search(policydb->p_types.table, rulestr); + if (!typedatum) + goto out; + tmprule->au_ctxt.type = typedatum->value; + break; + case AUDIT_SUBJ_SEN: + case AUDIT_SUBJ_CLR: + case AUDIT_OBJ_LEV_LOW: + case AUDIT_OBJ_LEV_HIGH: + rc = mls_from_string(policydb, rulestr, &tmprule->au_ctxt, + GFP_ATOMIC); + if (rc) + goto out; + break; + } + rc = 0; +out: + read_unlock(&state->ss->policy_rwlock); + + if (rc) { + selinux_audit_rule_free(tmprule); + tmprule = NULL; + } + + *rule = tmprule; + + return rc; +} + +/* Check to see if the rule contains any selinux fields */ +int selinux_audit_rule_known(struct audit_krule *rule) +{ + int i; + + for (i = 0; i < rule->field_count; i++) { + struct audit_field *f = &rule->fields[i]; + switch (f->type) { + case AUDIT_SUBJ_USER: + case AUDIT_SUBJ_ROLE: + case AUDIT_SUBJ_TYPE: + case AUDIT_SUBJ_SEN: + case AUDIT_SUBJ_CLR: + case AUDIT_OBJ_USER: + case AUDIT_OBJ_ROLE: + case AUDIT_OBJ_TYPE: + case AUDIT_OBJ_LEV_LOW: + case AUDIT_OBJ_LEV_HIGH: + return 1; + } + } + + return 0; +} + +int selinux_audit_rule_match(u32 sid, u32 field, u32 op, void *vrule, + struct audit_context *actx) +{ + struct selinux_state *state = &selinux_state; + struct context *ctxt; + struct mls_level *level; + struct selinux_audit_rule *rule = vrule; + int match = 0; + + if (unlikely(!rule)) { + WARN_ONCE(1, "selinux_audit_rule_match: missing rule\n"); + return -ENOENT; + } + + read_lock(&state->ss->policy_rwlock); + + if (rule->au_seqno < state->ss->latest_granting) { + match = -ESTALE; + goto out; + } + + ctxt = sidtab_search(&state->ss->sidtab, sid); + if (unlikely(!ctxt)) { + WARN_ONCE(1, "selinux_audit_rule_match: unrecognized SID %d\n", + sid); + match = -ENOENT; + goto out; + } + + /* a field/op pair that is not caught here will simply fall through + without a match */ + switch (field) { + case AUDIT_SUBJ_USER: + case AUDIT_OBJ_USER: + switch (op) { + case Audit_equal: + match = (ctxt->user == rule->au_ctxt.user); + break; + case Audit_not_equal: + match = (ctxt->user != rule->au_ctxt.user); + break; + } + break; + case AUDIT_SUBJ_ROLE: + case AUDIT_OBJ_ROLE: + switch (op) { + case Audit_equal: + match = (ctxt->role == rule->au_ctxt.role); + break; + case Audit_not_equal: + match = (ctxt->role != rule->au_ctxt.role); + break; + } + break; + case AUDIT_SUBJ_TYPE: + case AUDIT_OBJ_TYPE: + switch (op) { + case Audit_equal: + match = (ctxt->type == rule->au_ctxt.type); + break; + case Audit_not_equal: + match = (ctxt->type != rule->au_ctxt.type); + break; + } + break; + case AUDIT_SUBJ_SEN: + case AUDIT_SUBJ_CLR: + case AUDIT_OBJ_LEV_LOW: + case AUDIT_OBJ_LEV_HIGH: + level = ((field == AUDIT_SUBJ_SEN || + field == AUDIT_OBJ_LEV_LOW) ? + &ctxt->range.level[0] : &ctxt->range.level[1]); + switch (op) { + case Audit_equal: + match = mls_level_eq(&rule->au_ctxt.range.level[0], + level); + break; + case Audit_not_equal: + match = !mls_level_eq(&rule->au_ctxt.range.level[0], + level); + break; + case Audit_lt: + match = (mls_level_dom(&rule->au_ctxt.range.level[0], + level) && + !mls_level_eq(&rule->au_ctxt.range.level[0], + level)); + break; + case Audit_le: + match = mls_level_dom(&rule->au_ctxt.range.level[0], + level); + break; + case Audit_gt: + match = (mls_level_dom(level, + &rule->au_ctxt.range.level[0]) && + !mls_level_eq(level, + &rule->au_ctxt.range.level[0])); + break; + case Audit_ge: + match = mls_level_dom(level, + &rule->au_ctxt.range.level[0]); + break; + } + } + +out: + read_unlock(&state->ss->policy_rwlock); + return match; +} + +static int (*aurule_callback)(void) = audit_update_lsm_rules; + +static int aurule_avc_callback(u32 event) +{ + int err = 0; + + if (event == AVC_CALLBACK_RESET && aurule_callback) + err = aurule_callback(); + return err; +} + +static int __init aurule_init(void) +{ + int err; + + err = avc_add_callback(aurule_avc_callback, AVC_CALLBACK_RESET); + if (err) + panic("avc_add_callback() failed, error %d\n", err); + + return err; +} +__initcall(aurule_init); + +#ifdef CONFIG_NETLABEL +/** + * security_netlbl_cache_add - Add an entry to the NetLabel cache + * @secattr: the NetLabel packet security attributes + * @sid: the SELinux SID + * + * Description: + * Attempt to cache the context in @ctx, which was derived from the packet in + * @skb, in the NetLabel subsystem cache. This function assumes @secattr has + * already been initialized. + * + */ +static void security_netlbl_cache_add(struct netlbl_lsm_secattr *secattr, + u32 sid) +{ + u32 *sid_cache; + + sid_cache = kmalloc(sizeof(*sid_cache), GFP_ATOMIC); + if (sid_cache == NULL) + return; + secattr->cache = netlbl_secattr_cache_alloc(GFP_ATOMIC); + if (secattr->cache == NULL) { + kfree(sid_cache); + return; + } + + *sid_cache = sid; + secattr->cache->free = kfree; + secattr->cache->data = sid_cache; + secattr->flags |= NETLBL_SECATTR_CACHE; +} + +/** + * security_netlbl_secattr_to_sid - Convert a NetLabel secattr to a SELinux SID + * @secattr: the NetLabel packet security attributes + * @sid: the SELinux SID + * + * Description: + * Convert the given NetLabel security attributes in @secattr into a + * SELinux SID. If the @secattr field does not contain a full SELinux + * SID/context then use SECINITSID_NETMSG as the foundation. If possible the + * 'cache' field of @secattr is set and the CACHE flag is set; this is to + * allow the @secattr to be used by NetLabel to cache the secattr to SID + * conversion for future lookups. Returns zero on success, negative values on + * failure. + * + */ +int security_netlbl_secattr_to_sid(struct selinux_state *state, + struct netlbl_lsm_secattr *secattr, + u32 *sid) +{ + struct policydb *policydb = &state->ss->policydb; + struct sidtab *sidtab = &state->ss->sidtab; + int rc; + struct context *ctx; + struct context ctx_new; + + if (!state->initialized) { + *sid = SECSID_NULL; + return 0; + } + + read_lock(&state->ss->policy_rwlock); + + if (secattr->flags & NETLBL_SECATTR_CACHE) + *sid = *(u32 *)secattr->cache->data; + else if (secattr->flags & NETLBL_SECATTR_SECID) + *sid = secattr->attr.secid; + else if (secattr->flags & NETLBL_SECATTR_MLS_LVL) { + rc = -EIDRM; + ctx = sidtab_search(sidtab, SECINITSID_NETMSG); + if (ctx == NULL) + goto out; + + context_init(&ctx_new); + ctx_new.user = ctx->user; + ctx_new.role = ctx->role; + ctx_new.type = ctx->type; + mls_import_netlbl_lvl(policydb, &ctx_new, secattr); + if (secattr->flags & NETLBL_SECATTR_MLS_CAT) { + rc = mls_import_netlbl_cat(policydb, &ctx_new, secattr); + if (rc) + goto out; + } + rc = -EIDRM; + if (!mls_context_isvalid(policydb, &ctx_new)) + goto out_free; + + rc = sidtab_context_to_sid(sidtab, &ctx_new, sid); + if (rc) + goto out_free; + + security_netlbl_cache_add(secattr, *sid); + + ebitmap_destroy(&ctx_new.range.level[0].cat); + } else + *sid = SECSID_NULL; + + read_unlock(&state->ss->policy_rwlock); + return 0; +out_free: + ebitmap_destroy(&ctx_new.range.level[0].cat); +out: + read_unlock(&state->ss->policy_rwlock); + return rc; +} + +/** + * security_netlbl_sid_to_secattr - Convert a SELinux SID to a NetLabel secattr + * @sid: the SELinux SID + * @secattr: the NetLabel packet security attributes + * + * Description: + * Convert the given SELinux SID in @sid into a NetLabel security attribute. + * Returns zero on success, negative values on failure. + * + */ +int security_netlbl_sid_to_secattr(struct selinux_state *state, + u32 sid, struct netlbl_lsm_secattr *secattr) +{ + struct policydb *policydb = &state->ss->policydb; + int rc; + struct context *ctx; + + if (!state->initialized) + return 0; + + read_lock(&state->ss->policy_rwlock); + + rc = -ENOENT; + ctx = sidtab_search(&state->ss->sidtab, sid); + if (ctx == NULL) + goto out; + + rc = -ENOMEM; + secattr->domain = kstrdup(sym_name(policydb, SYM_TYPES, ctx->type - 1), + GFP_ATOMIC); + if (secattr->domain == NULL) + goto out; + + secattr->attr.secid = sid; + secattr->flags |= NETLBL_SECATTR_DOMAIN_CPY | NETLBL_SECATTR_SECID; + mls_export_netlbl_lvl(policydb, ctx, secattr); + rc = mls_export_netlbl_cat(policydb, ctx, secattr); +out: + read_unlock(&state->ss->policy_rwlock); + return rc; +} +#endif /* CONFIG_NETLABEL */ + +/** + * security_read_policy - read the policy. + * @data: binary policy data + * @len: length of data in bytes + * + */ +int security_read_policy(struct selinux_state *state, + void **data, size_t *len) +{ + struct policydb *policydb = &state->ss->policydb; + int rc; + struct policy_file fp; + + if (!state->initialized) + return -EINVAL; + + *len = security_policydb_len(state); + + *data = vmalloc_user(*len); + if (!*data) + return -ENOMEM; + + fp.data = *data; + fp.len = *len; + + read_lock(&state->ss->policy_rwlock); + rc = policydb_write(policydb, &fp); + read_unlock(&state->ss->policy_rwlock); + + if (rc) + return rc; + + *len = (unsigned long)fp.data - (unsigned long)*data; + return 0; + +} diff --git a/security/selinux/ss/services.h b/security/selinux/ss/services.h new file mode 100644 index 000000000..24c7bdcc8 --- /dev/null +++ b/security/selinux/ss/services.h @@ -0,0 +1,42 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Implementation of the security services. + * + * Author : Stephen Smalley, <sds@tycho.nsa.gov> + */ +#ifndef _SS_SERVICES_H_ +#define _SS_SERVICES_H_ + +#include "policydb.h" +#include "sidtab.h" + +/* Mapping for a single class */ +struct selinux_mapping { + u16 value; /* policy value for class */ + unsigned int num_perms; /* number of permissions in class */ + u32 perms[sizeof(u32) * 8]; /* policy values for permissions */ +}; + +/* Map for all of the classes, with array size */ +struct selinux_map { + struct selinux_mapping *mapping; /* indexed by class */ + u16 size; /* array size of mapping */ +}; + +struct selinux_ss { + struct sidtab sidtab; + struct policydb policydb; + rwlock_t policy_rwlock; + u32 latest_granting; + struct selinux_map map; + struct page *status_page; + struct mutex status_lock; +}; + +void services_compute_xperms_drivers(struct extended_perms *xperms, + struct avtab_node *node); + +void services_compute_xperms_decision(struct extended_perms_decision *xpermd, + struct avtab_node *node); + +#endif /* _SS_SERVICES_H_ */ diff --git a/security/selinux/ss/sidtab.c b/security/selinux/ss/sidtab.c new file mode 100644 index 000000000..fd75a12fa --- /dev/null +++ b/security/selinux/ss/sidtab.c @@ -0,0 +1,306 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Implementation of the SID table type. + * + * Author : Stephen Smalley, <sds@tycho.nsa.gov> + */ +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/spinlock.h> +#include <linux/errno.h> +#include "flask.h" +#include "security.h" +#include "sidtab.h" + +#define SIDTAB_HASH(sid) \ +(sid & SIDTAB_HASH_MASK) + +int sidtab_init(struct sidtab *s) +{ + int i; + + s->htable = kmalloc_array(SIDTAB_SIZE, sizeof(*s->htable), 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; + spin_lock_init(&s->lock); + return 0; +} + +int sidtab_insert(struct sidtab *s, u32 sid, struct context *context) +{ + int hvalue; + struct sidtab_node *prev, *cur, *newnode; + + if (!s) + return -ENOMEM; + + hvalue = SIDTAB_HASH(sid); + prev = NULL; + cur = s->htable[hvalue]; + while (cur && sid > cur->sid) { + prev = cur; + cur = cur->next; + } + + if (cur && sid == cur->sid) + return -EEXIST; + + newnode = kmalloc(sizeof(*newnode), GFP_ATOMIC); + if (!newnode) + return -ENOMEM; + + newnode->sid = sid; + if (context_cpy(&newnode->context, context)) { + kfree(newnode); + return -ENOMEM; + } + + 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; + return 0; +} + +static struct context *sidtab_search_core(struct sidtab *s, u32 sid, int force) +{ + int hvalue; + struct sidtab_node *cur; + + if (!s) + return NULL; + + hvalue = SIDTAB_HASH(sid); + cur = s->htable[hvalue]; + while (cur && sid > cur->sid) + cur = cur->next; + + if (force && cur && sid == cur->sid && cur->context.len) + return &cur->context; + + if (!cur || sid != cur->sid || cur->context.len) { + /* Remap invalid SIDs to the unlabeled SID. */ + sid = SECINITSID_UNLABELED; + hvalue = SIDTAB_HASH(sid); + cur = s->htable[hvalue]; + while (cur && sid > cur->sid) + cur = cur->next; + if (!cur || sid != cur->sid) + return NULL; + } + + return &cur->context; +} + +struct context *sidtab_search(struct sidtab *s, u32 sid) +{ + return sidtab_search_core(s, sid, 0); +} + +struct context *sidtab_search_force(struct sidtab *s, u32 sid) +{ + return sidtab_search_core(s, sid, 1); +} + +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) { + rc = apply(cur->sid, &cur->context, args); + if (rc) + goto out; + cur = cur->next; + } + } +out: + return rc; +} + +static void sidtab_update_cache(struct sidtab *s, struct sidtab_node *n, int loc) +{ + BUG_ON(loc >= SIDTAB_CACHE_LEN); + + while (loc > 0) { + s->cache[loc] = s->cache[loc - 1]; + loc--; + } + s->cache[0] = n; +} + +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) { + if (context_cmp(&cur->context, context)) { + sidtab_update_cache(s, cur, SIDTAB_CACHE_LEN - 1); + return cur->sid; + } + cur = cur->next; + } + } + return 0; +} + +static inline u32 sidtab_search_cache(struct sidtab *s, struct context *context) +{ + int i; + struct sidtab_node *node; + + for (i = 0; i < SIDTAB_CACHE_LEN; i++) { + node = s->cache[i]; + if (unlikely(!node)) + return 0; + if (context_cmp(&node->context, context)) { + sidtab_update_cache(s, node, i); + return node->sid; + } + } + 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_cache(s, context); + if (!sid) + sid = sidtab_search_context(s, context); + if (!sid) { + spin_lock_irqsave(&s->lock, 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++; + if (context->len) + pr_info("SELinux: Context %s is not valid (left unmapped).\n", + context->str); + ret = sidtab_insert(s, sid, context); + if (ret) + s->next_sid--; +unlock_out: + spin_unlock_irqrestore(&s->lock, 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; + } + } + + pr_debug("%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) { + 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; + int i; + + spin_lock_irqsave(&src->lock, flags); + dst->htable = src->htable; + dst->nel = src->nel; + dst->next_sid = src->next_sid; + dst->shutdown = 0; + for (i = 0; i < SIDTAB_CACHE_LEN; i++) + dst->cache[i] = NULL; + spin_unlock_irqrestore(&src->lock, flags); +} + +void sidtab_shutdown(struct sidtab *s) +{ + unsigned long flags; + + spin_lock_irqsave(&s->lock, flags); + s->shutdown = 1; + spin_unlock_irqrestore(&s->lock, flags); +} diff --git a/security/selinux/ss/sidtab.h b/security/selinux/ss/sidtab.h new file mode 100644 index 000000000..a1a1d2617 --- /dev/null +++ b/security/selinux/ss/sidtab.h @@ -0,0 +1,57 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * A security identifier table (sidtab) is a hash table + * of security context structures indexed by SID value. + * + * Author : Stephen Smalley, <sds@tycho.nsa.gov> + */ +#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; +#define SIDTAB_CACHE_LEN 3 + struct sidtab_node *cache[SIDTAB_CACHE_LEN]; + 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); +struct context *sidtab_search_force(struct sidtab *s, u32 sid); + +int sidtab_map(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/status.c b/security/selinux/ss/status.c new file mode 100644 index 000000000..a121de45a --- /dev/null +++ b/security/selinux/ss/status.c @@ -0,0 +1,127 @@ +/* + * mmap based event notifications for SELinux + * + * Author: KaiGai Kohei <kaigai@ak.jp.nec.com> + * + * Copyright (C) 2010 NEC corporation + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + */ +#include <linux/kernel.h> +#include <linux/gfp.h> +#include <linux/mm.h> +#include <linux/mutex.h> +#include "avc.h" +#include "services.h" + +/* + * The selinux_status_page shall be exposed to userspace applications + * using mmap interface on /selinux/status. + * It enables to notify applications a few events that will cause reset + * of userspace access vector without context switching. + * + * The selinux_kernel_status structure on the head of status page is + * protected from concurrent accesses using seqlock logic, so userspace + * application should reference the status page according to the seqlock + * logic. + * + * Typically, application checks status->sequence at the head of access + * control routine. If it is odd-number, kernel is updating the status, + * so please wait for a moment. If it is changed from the last sequence + * number, it means something happen, so application will reset userspace + * avc, if needed. + * In most cases, application shall confirm the kernel status is not + * changed without any system call invocations. + */ + +/* + * selinux_kernel_status_page + * + * It returns a reference to selinux_status_page. If the status page is + * not allocated yet, it also tries to allocate it at the first time. + */ +struct page *selinux_kernel_status_page(struct selinux_state *state) +{ + struct selinux_kernel_status *status; + struct page *result = NULL; + + mutex_lock(&state->ss->status_lock); + if (!state->ss->status_page) { + state->ss->status_page = alloc_page(GFP_KERNEL|__GFP_ZERO); + + if (state->ss->status_page) { + status = page_address(state->ss->status_page); + + status->version = SELINUX_KERNEL_STATUS_VERSION; + status->sequence = 0; + status->enforcing = enforcing_enabled(state); + /* + * NOTE: the next policyload event shall set + * a positive value on the status->policyload, + * although it may not be 1, but never zero. + * So, application can know it was updated. + */ + status->policyload = 0; + status->deny_unknown = + !security_get_allow_unknown(state); + } + } + result = state->ss->status_page; + mutex_unlock(&state->ss->status_lock); + + return result; +} + +/* + * selinux_status_update_setenforce + * + * It updates status of the current enforcing/permissive mode. + */ +void selinux_status_update_setenforce(struct selinux_state *state, + int enforcing) +{ + struct selinux_kernel_status *status; + + mutex_lock(&state->ss->status_lock); + if (state->ss->status_page) { + status = page_address(state->ss->status_page); + + status->sequence++; + smp_wmb(); + + status->enforcing = enforcing; + + smp_wmb(); + status->sequence++; + } + mutex_unlock(&state->ss->status_lock); +} + +/* + * selinux_status_update_policyload + * + * It updates status of the times of policy reloaded, and current + * setting of deny_unknown. + */ +void selinux_status_update_policyload(struct selinux_state *state, + int seqno) +{ + struct selinux_kernel_status *status; + + mutex_lock(&state->ss->status_lock); + if (state->ss->status_page) { + status = page_address(state->ss->status_page); + + status->sequence++; + smp_wmb(); + + status->policyload = seqno; + status->deny_unknown = !security_get_allow_unknown(state); + + smp_wmb(); + status->sequence++; + } + mutex_unlock(&state->ss->status_lock); +} diff --git a/security/selinux/ss/symtab.c b/security/selinux/ss/symtab.c new file mode 100644 index 000000000..dc2ce9416 --- /dev/null +++ b/security/selinux/ss/symtab.c @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Implementation of the symbol table type. + * + * Author : Stephen Smalley, <sds@tycho.nsa.gov> + */ +#include <linux/kernel.h> +#include <linux/string.h> +#include <linux/errno.h> +#include "symtab.h" + +static unsigned int symhash(struct hashtab *h, const void *key) +{ + const 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, const void *key1, const void *key2) +{ + const 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 -ENOMEM; + s->nprim = 0; + return 0; +} + diff --git a/security/selinux/ss/symtab.h b/security/selinux/ss/symtab.h new file mode 100644 index 000000000..d75fcafe7 --- /dev/null +++ b/security/selinux/ss/symtab.h @@ -0,0 +1,24 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * 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@tycho.nsa.gov> + */ +#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_ */ + + |