| // 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); | 
 | } |