| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * Implementation of the SID table type. |
| * |
| * Original author: Stephen Smalley, <sds@tycho.nsa.gov> |
| * Author: Ondrej Mosnacek, <omosnacek@gmail.com> |
| * |
| * Copyright (C) 2018 Red Hat, Inc. |
| */ |
| #include <linux/errno.h> |
| #include <linux/kernel.h> |
| #include <linux/slab.h> |
| #include <linux/sched.h> |
| #include <linux/spinlock.h> |
| #include <asm/barrier.h> |
| #include "flask.h" |
| #include "security.h" |
| #include "sidtab.h" |
| |
| int sidtab_init(struct sidtab *s) |
| { |
| u32 i; |
| |
| memset(s->roots, 0, sizeof(s->roots)); |
| |
| /* max count is SIDTAB_MAX so valid index is always < SIDTAB_MAX */ |
| for (i = 0; i < SIDTAB_RCACHE_SIZE; i++) |
| s->rcache[i] = SIDTAB_MAX; |
| |
| for (i = 0; i < SECINITSID_NUM; i++) |
| s->isids[i].set = 0; |
| |
| s->count = 0; |
| s->convert = NULL; |
| |
| spin_lock_init(&s->lock); |
| return 0; |
| } |
| |
| int sidtab_set_initial(struct sidtab *s, u32 sid, struct context *context) |
| { |
| struct sidtab_isid_entry *entry; |
| int rc; |
| |
| if (sid == 0 || sid > SECINITSID_NUM) |
| return -EINVAL; |
| |
| entry = &s->isids[sid - 1]; |
| |
| rc = context_cpy(&entry->context, context); |
| if (rc) |
| return rc; |
| |
| entry->set = 1; |
| return 0; |
| } |
| |
| static u32 sidtab_level_from_count(u32 count) |
| { |
| u32 capacity = SIDTAB_LEAF_ENTRIES; |
| u32 level = 0; |
| |
| while (count > capacity) { |
| capacity <<= SIDTAB_INNER_SHIFT; |
| ++level; |
| } |
| return level; |
| } |
| |
| static int sidtab_alloc_roots(struct sidtab *s, u32 level) |
| { |
| u32 l; |
| |
| if (!s->roots[0].ptr_leaf) { |
| s->roots[0].ptr_leaf = kzalloc(SIDTAB_NODE_ALLOC_SIZE, |
| GFP_ATOMIC); |
| if (!s->roots[0].ptr_leaf) |
| return -ENOMEM; |
| } |
| for (l = 1; l <= level; ++l) |
| if (!s->roots[l].ptr_inner) { |
| s->roots[l].ptr_inner = kzalloc(SIDTAB_NODE_ALLOC_SIZE, |
| GFP_ATOMIC); |
| if (!s->roots[l].ptr_inner) |
| return -ENOMEM; |
| s->roots[l].ptr_inner->entries[0] = s->roots[l - 1]; |
| } |
| return 0; |
| } |
| |
| static struct context *sidtab_do_lookup(struct sidtab *s, u32 index, int alloc) |
| { |
| union sidtab_entry_inner *entry; |
| u32 level, capacity_shift, leaf_index = index / SIDTAB_LEAF_ENTRIES; |
| |
| /* find the level of the subtree we need */ |
| level = sidtab_level_from_count(index + 1); |
| capacity_shift = level * SIDTAB_INNER_SHIFT; |
| |
| /* allocate roots if needed */ |
| if (alloc && sidtab_alloc_roots(s, level) != 0) |
| return NULL; |
| |
| /* lookup inside the subtree */ |
| entry = &s->roots[level]; |
| while (level != 0) { |
| capacity_shift -= SIDTAB_INNER_SHIFT; |
| --level; |
| |
| entry = &entry->ptr_inner->entries[leaf_index >> capacity_shift]; |
| leaf_index &= ((u32)1 << capacity_shift) - 1; |
| |
| if (!entry->ptr_inner) { |
| if (alloc) |
| entry->ptr_inner = kzalloc(SIDTAB_NODE_ALLOC_SIZE, |
| GFP_ATOMIC); |
| if (!entry->ptr_inner) |
| return NULL; |
| } |
| } |
| if (!entry->ptr_leaf) { |
| if (alloc) |
| entry->ptr_leaf = kzalloc(SIDTAB_NODE_ALLOC_SIZE, |
| GFP_ATOMIC); |
| if (!entry->ptr_leaf) |
| return NULL; |
| } |
| return &entry->ptr_leaf->entries[index % SIDTAB_LEAF_ENTRIES].context; |
| } |
| |
| static struct context *sidtab_lookup(struct sidtab *s, u32 index) |
| { |
| /* read entries only after reading count */ |
| u32 count = smp_load_acquire(&s->count); |
| |
| if (index >= count) |
| return NULL; |
| |
| return sidtab_do_lookup(s, index, 0); |
| } |
| |
| static struct context *sidtab_lookup_initial(struct sidtab *s, u32 sid) |
| { |
| return s->isids[sid - 1].set ? &s->isids[sid - 1].context : NULL; |
| } |
| |
| static struct context *sidtab_search_core(struct sidtab *s, u32 sid, int force) |
| { |
| struct context *context; |
| |
| if (sid != 0) { |
| if (sid > SECINITSID_NUM) |
| context = sidtab_lookup(s, sid - (SECINITSID_NUM + 1)); |
| else |
| context = sidtab_lookup_initial(s, sid); |
| if (context && (!context->len || force)) |
| return context; |
| } |
| |
| return sidtab_lookup_initial(s, SECINITSID_UNLABELED); |
| } |
| |
| 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); |
| } |
| |
| static int sidtab_find_context(union sidtab_entry_inner entry, |
| u32 *pos, u32 count, u32 level, |
| struct context *context, u32 *index) |
| { |
| int rc; |
| u32 i; |
| |
| if (level != 0) { |
| struct sidtab_node_inner *node = entry.ptr_inner; |
| |
| i = 0; |
| while (i < SIDTAB_INNER_ENTRIES && *pos < count) { |
| rc = sidtab_find_context(node->entries[i], |
| pos, count, level - 1, |
| context, index); |
| if (rc == 0) |
| return 0; |
| i++; |
| } |
| } else { |
| struct sidtab_node_leaf *node = entry.ptr_leaf; |
| |
| i = 0; |
| while (i < SIDTAB_LEAF_ENTRIES && *pos < count) { |
| if (context_cmp(&node->entries[i].context, context)) { |
| *index = *pos; |
| return 0; |
| } |
| (*pos)++; |
| i++; |
| } |
| } |
| return -ENOENT; |
| } |
| |
| static void sidtab_rcache_update(struct sidtab *s, u32 index, u32 pos) |
| { |
| while (pos > 0) { |
| WRITE_ONCE(s->rcache[pos], READ_ONCE(s->rcache[pos - 1])); |
| --pos; |
| } |
| WRITE_ONCE(s->rcache[0], index); |
| } |
| |
| static void sidtab_rcache_push(struct sidtab *s, u32 index) |
| { |
| sidtab_rcache_update(s, index, SIDTAB_RCACHE_SIZE - 1); |
| } |
| |
| static int sidtab_rcache_search(struct sidtab *s, struct context *context, |
| u32 *index) |
| { |
| u32 i; |
| |
| for (i = 0; i < SIDTAB_RCACHE_SIZE; i++) { |
| u32 v = READ_ONCE(s->rcache[i]); |
| |
| if (v >= SIDTAB_MAX) |
| continue; |
| |
| if (context_cmp(sidtab_do_lookup(s, v, 0), context)) { |
| sidtab_rcache_update(s, v, i); |
| *index = v; |
| return 0; |
| } |
| } |
| return -ENOENT; |
| } |
| |
| static int sidtab_reverse_lookup(struct sidtab *s, struct context *context, |
| u32 *index) |
| { |
| unsigned long flags; |
| u32 count, count_locked, level, pos; |
| struct sidtab_convert_params *convert; |
| struct context *dst, *dst_convert; |
| int rc; |
| |
| rc = sidtab_rcache_search(s, context, index); |
| if (rc == 0) |
| return 0; |
| |
| /* read entries only after reading count */ |
| count = smp_load_acquire(&s->count); |
| level = sidtab_level_from_count(count); |
| |
| pos = 0; |
| rc = sidtab_find_context(s->roots[level], &pos, count, level, |
| context, index); |
| if (rc == 0) { |
| sidtab_rcache_push(s, *index); |
| return 0; |
| } |
| |
| /* lock-free search failed: lock, re-search, and insert if not found */ |
| spin_lock_irqsave(&s->lock, flags); |
| |
| convert = s->convert; |
| count_locked = s->count; |
| level = sidtab_level_from_count(count_locked); |
| |
| /* if count has changed before we acquired the lock, then catch up */ |
| while (count < count_locked) { |
| if (context_cmp(sidtab_do_lookup(s, count, 0), context)) { |
| sidtab_rcache_push(s, count); |
| *index = count; |
| rc = 0; |
| goto out_unlock; |
| } |
| ++count; |
| } |
| |
| /* bail out if we already reached max entries */ |
| rc = -EOVERFLOW; |
| if (count >= SIDTAB_MAX) |
| goto out_unlock; |
| |
| /* insert context into new entry */ |
| rc = -ENOMEM; |
| dst = sidtab_do_lookup(s, count, 1); |
| if (!dst) |
| goto out_unlock; |
| |
| rc = context_cpy(dst, context); |
| if (rc) |
| goto out_unlock; |
| |
| /* |
| * if we are building a new sidtab, we need to convert the context |
| * and insert it there as well |
| */ |
| if (convert) { |
| rc = -ENOMEM; |
| dst_convert = sidtab_do_lookup(convert->target, count, 1); |
| if (!dst_convert) { |
| context_destroy(dst); |
| goto out_unlock; |
| } |
| |
| rc = convert->func(context, dst_convert, convert->args); |
| if (rc) { |
| context_destroy(dst); |
| goto out_unlock; |
| } |
| |
| /* at this point we know the insert won't fail */ |
| convert->target->count = count + 1; |
| } |
| |
| if (context->len) |
| pr_info("SELinux: Context %s is not valid (left unmapped).\n", |
| context->str); |
| |
| sidtab_rcache_push(s, count); |
| *index = count; |
| |
| /* write entries before writing new count */ |
| smp_store_release(&s->count, count + 1); |
| |
| rc = 0; |
| out_unlock: |
| spin_unlock_irqrestore(&s->lock, flags); |
| return rc; |
| } |
| |
| int sidtab_context_to_sid(struct sidtab *s, struct context *context, u32 *sid) |
| { |
| int rc; |
| u32 i; |
| |
| for (i = 0; i < SECINITSID_NUM; i++) { |
| struct sidtab_isid_entry *entry = &s->isids[i]; |
| |
| if (entry->set && context_cmp(context, &entry->context)) { |
| *sid = i + 1; |
| return 0; |
| } |
| } |
| |
| rc = sidtab_reverse_lookup(s, context, sid); |
| if (rc) |
| return rc; |
| *sid += SECINITSID_NUM + 1; |
| return 0; |
| } |
| |
| static int sidtab_convert_tree(union sidtab_entry_inner *edst, |
| union sidtab_entry_inner *esrc, |
| u32 *pos, u32 count, u32 level, |
| struct sidtab_convert_params *convert) |
| { |
| int rc; |
| u32 i; |
| |
| if (level != 0) { |
| if (!edst->ptr_inner) { |
| edst->ptr_inner = kzalloc(SIDTAB_NODE_ALLOC_SIZE, |
| GFP_KERNEL); |
| if (!edst->ptr_inner) |
| return -ENOMEM; |
| } |
| i = 0; |
| while (i < SIDTAB_INNER_ENTRIES && *pos < count) { |
| rc = sidtab_convert_tree(&edst->ptr_inner->entries[i], |
| &esrc->ptr_inner->entries[i], |
| pos, count, level - 1, |
| convert); |
| if (rc) |
| return rc; |
| i++; |
| } |
| } else { |
| if (!edst->ptr_leaf) { |
| edst->ptr_leaf = kzalloc(SIDTAB_NODE_ALLOC_SIZE, |
| GFP_KERNEL); |
| if (!edst->ptr_leaf) |
| return -ENOMEM; |
| } |
| i = 0; |
| while (i < SIDTAB_LEAF_ENTRIES && *pos < count) { |
| rc = convert->func(&esrc->ptr_leaf->entries[i].context, |
| &edst->ptr_leaf->entries[i].context, |
| convert->args); |
| if (rc) |
| return rc; |
| (*pos)++; |
| i++; |
| } |
| cond_resched(); |
| } |
| return 0; |
| } |
| |
| int sidtab_convert(struct sidtab *s, struct sidtab_convert_params *params) |
| { |
| unsigned long flags; |
| u32 count, level, pos; |
| int rc; |
| |
| spin_lock_irqsave(&s->lock, flags); |
| |
| /* concurrent policy loads are not allowed */ |
| if (s->convert) { |
| spin_unlock_irqrestore(&s->lock, flags); |
| return -EBUSY; |
| } |
| |
| count = s->count; |
| level = sidtab_level_from_count(count); |
| |
| /* allocate last leaf in the new sidtab (to avoid race with |
| * live convert) |
| */ |
| rc = sidtab_do_lookup(params->target, count - 1, 1) ? 0 : -ENOMEM; |
| if (rc) { |
| spin_unlock_irqrestore(&s->lock, flags); |
| return rc; |
| } |
| |
| /* set count in case no new entries are added during conversion */ |
| params->target->count = count; |
| |
| /* enable live convert of new entries */ |
| s->convert = params; |
| |
| /* we can safely do the rest of the conversion outside the lock */ |
| spin_unlock_irqrestore(&s->lock, flags); |
| |
| pr_info("SELinux: Converting %u SID table entries...\n", count); |
| |
| /* convert all entries not covered by live convert */ |
| pos = 0; |
| rc = sidtab_convert_tree(¶ms->target->roots[level], |
| &s->roots[level], &pos, count, level, params); |
| if (rc) { |
| /* we need to keep the old table - disable live convert */ |
| spin_lock_irqsave(&s->lock, flags); |
| s->convert = NULL; |
| spin_unlock_irqrestore(&s->lock, flags); |
| } |
| return rc; |
| } |
| |
| static void sidtab_destroy_tree(union sidtab_entry_inner entry, u32 level) |
| { |
| u32 i; |
| |
| if (level != 0) { |
| struct sidtab_node_inner *node = entry.ptr_inner; |
| |
| if (!node) |
| return; |
| |
| for (i = 0; i < SIDTAB_INNER_ENTRIES; i++) |
| sidtab_destroy_tree(node->entries[i], level - 1); |
| kfree(node); |
| } else { |
| struct sidtab_node_leaf *node = entry.ptr_leaf; |
| |
| if (!node) |
| return; |
| |
| for (i = 0; i < SIDTAB_LEAF_ENTRIES; i++) |
| context_destroy(&node->entries[i].context); |
| kfree(node); |
| } |
| } |
| |
| void sidtab_destroy(struct sidtab *s) |
| { |
| u32 i, level; |
| |
| for (i = 0; i < SECINITSID_NUM; i++) |
| if (s->isids[i].set) |
| context_destroy(&s->isids[i].context); |
| |
| level = SIDTAB_MAX_LEVEL; |
| while (level && !s->roots[level].ptr_inner) |
| --level; |
| |
| sidtab_destroy_tree(s->roots[level], level); |
| } |