diff options
Diffstat (limited to 'security/selinux/ss/sidtab.c')
-rw-r--r-- | security/selinux/ss/sidtab.c | 306 |
1 files changed, 306 insertions, 0 deletions
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); +} |