summaryrefslogtreecommitdiffstats
path: root/security/selinux/ss
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2026-08-30 09:22:00 -0700
committerLinus Torvalds <torvalds@linux-foundation.org>2026-08-30 09:22:00 -0700
commit034dd340b08be1f2f0477ad16131d609f9dbd53c (patch)
tree2536f4b2d7893ccd916b5abdf3c454ad6edc03c2 /security/selinux/ss
downloadlinux-stable-034dd340b08be1f2f0477ad16131d609f9dbd53c.tar.gz
linux-stable-034dd340b08be1f2f0477ad16131d609f9dbd53c.zip
Merge tag 'trace-v7.3-2' of git://git.kernel.org/pub/scm/linux/kernel/git/trace/linux-tracegrafted
Pull tracing fixes from Steven Rostedt: - Fix error output of boot instance creation failure Currently if a boot instance creation fails, instead of printing out the name of the instance that failed, it prints "(null)". That is because it prints "cur_str" that had already been processed by strsep(). Print the saved name instead. While at it, print the error code of the failure. - Fix use-after-free for same named historgrams Histograms can be named so that they can be used in multiple events. But if the named histogram has a variable attached, the second event that uses the named histogram which duplicates it and needs to free the original after duplication leaves the old variable in place and still visible. If another histogram uses than variable, it will use the stale one which will try to reference the freed duplicate histogram and crash the kernel. Free the duplicate variables along with the duplicated histogram data. - Check return value of kthread_run() in event self test The events self tests uses a kthread for testing but does not check if it succeeded in creating a kthread. If the kthread creation were to fail, the code will still try to call kthread_stop() on the error returned. - Fix race between reading trace_pipe and updating subbuffer size If a user is reading the trace_pipe file at the same time they update the ring buffer sub-buffer size, can cause the trace_pipe read to read stale data. Add trace_access_lock() around updating the ring buffer sub-buffer size. - Fix eventfs_inode on failure path in creation of the events directory In the creation of the "events" directory, if after allocating the eventfs_inode a failure is detected, it calls cleanup_ei() which calls free_ei(). The free_ei() will test if eventfs_inode being freed has no children. It is a bug if it does. But on the failure case of the creation of the "events" directory, the children lists have not yet been initialized and the free will trigger a warning because list_empty() on an uninitialized list returns false. Move the initialization into init_ei() where it makes more sense and makes sure that a created eventfs_inode has its lists initialized upon creation. - Check return value of kthread_run() in ftrace direct sample code The sample code that shows how to use the ftrace direct calls does not test the return of kthread_run() to see if it succeeds. Return a failure if the kthread_run() doesn't succeed. - Clear user events state on fork in case of alloc failure On fork, the child gets a pointer to the parent's user events state. It makes a copy of it then updates the child's pointer to it. But if the allocation fails, the duplication function leaves the child with a pointer to its parent's descriptor. When the child cleans up its data, it will free the parent's descriptor while the parent is still using it. In the duplication function, set the child's user_event_mm to NULL before testing if the allocation succeeded, and when it exits it will not free the parent's descriptor. - Fix retry exhaustion in simple ring buffer reader swap simple_ring_buffer_swap_reader_page() starts with retry set to 8 and post-decrements it only after a failed link replacement. On the final attempt, a successful replacement leaves retry at zero, while a failed replacement leaves it at -1. But the check for success expects the retry value to be non-zero and exits with an error on zero. This is the opposite result. Fix it. - Fail nicely when the remote swap_reader_page() returns an error Currently, if the swap_reader_page() of a remote buffer fails, it triggers a WARN_ON_ONCE() and continues normally. Instead, have it exit with an error and a pr_warn() print instead of a full WARNING. * tag 'trace-v7.3-2' of git://git.kernel.org/pub/scm/linux/kernel/git/trace/linux-trace: ring-buffer: Stop remote reader update when page swap fails tracing: Fix retry exhaustion in simple ring buffer reader swap tracing/user_events: Clear copied tracing state before fork duplication samples/ftrace: Fix kthread_stop() on ERR_PTR in ftrace-direct-multi-modify samples/ftrace: Fix kthread_stop() on ERR_PTR in ftrace-direct-modify eventfs: Initialize ei->children and ei->list in init_ei() tracing: Fix use-after-free in trace_pipe read on sub-buffer order change tracing: Fix crash passing ERR_PTR to kthread_stop() tracing: Fix use-after-free with same-name named triggers tracing: Fix logged instance name on creation failure
Diffstat (limited to 'security/selinux/ss')
-rw-r--r--security/selinux/ss/avtab.c618
-rw-r--r--security/selinux/ss/avtab.h141
-rw-r--r--security/selinux/ss/conditional.c765
-rw-r--r--security/selinux/ss/conditional.h84
-rw-r--r--security/selinux/ss/constraint.h65
-rw-r--r--security/selinux/ss/context.c32
-rw-r--r--security/selinux/ss/context.h204
-rw-r--r--security/selinux/ss/ebitmap.c603
-rw-r--r--security/selinux/ss/ebitmap.h156
-rw-r--r--security/selinux/ss/hashtab.c199
-rw-r--r--security/selinux/ss/hashtab.h154
-rw-r--r--security/selinux/ss/mls.c653
-rw-r--r--security/selinux/ss/mls.h96
-rw-r--r--security/selinux/ss/mls_types.h52
-rw-r--r--security/selinux/ss/policydb.c4155
-rw-r--r--security/selinux/ss/policydb.h428
-rw-r--r--security/selinux/ss/services.c3962
-rw-r--r--security/selinux/ss/services.h47
-rw-r--r--security/selinux/ss/sidtab.c638
-rw-r--r--security/selinux/ss/sidtab.h160
-rw-r--r--security/selinux/ss/symtab.c56
-rw-r--r--security/selinux/ss/symtab.h26
22 files changed, 13294 insertions, 0 deletions
diff --git a/security/selinux/ss/avtab.c b/security/selinux/ss/avtab.c
new file mode 100644
index 000000000..0f94edd01
--- /dev/null
+++ b/security/selinux/ss/avtab.c
@@ -0,0 +1,618 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Implementation of the access vector table type.
+ *
+ * Author : Stephen Smalley, <stephen.smalley.work@gmail.com>
+ */
+
+/* Updated: Frank Mayer <mayerf@tresys.com> and
+ * Karl MacMillan <kmacmillan@tresys.com>
+ * Added conditional policy language extensions
+ * Copyright (C) 2003 Tresys Technology, LLC
+ *
+ * Updated: Yuichi Nakamura <ynakam@hitachisoft.jp>
+ * Tuned number of hash slots for avtab to reduce memory usage
+ */
+
+#include <linux/bitops.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/errno.h>
+#include "avtab.h"
+#include "policydb.h"
+#include "hash.h"
+
+static struct kmem_cache *avtab_node_cachep __ro_after_init;
+static struct kmem_cache *avtab_xperms_cachep __ro_after_init;
+
+static inline u32 avtab_hash(const struct avtab_key *keyp, u32 mask)
+{
+ return av_hash((u32)keyp->target_class, (u32)keyp->target_type,
+ (u32)keyp->source_type, mask);
+}
+
+static struct avtab_node *avtab_insert_node(struct avtab *h,
+ struct avtab_node **dst,
+ const struct avtab_key *key,
+ const 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;
+ }
+
+ newnode->next = *dst;
+ *dst = newnode;
+
+ h->nel++;
+ return newnode;
+}
+
+static int avtab_node_cmp(const struct avtab_key *key1,
+ const struct avtab_key *key2)
+{
+ u16 specified = key1->specified & ~(AVTAB_ENABLED | AVTAB_ENABLED_OLD);
+
+ if (key1->source_type == key2->source_type &&
+ key1->target_type == key2->target_type &&
+ key1->target_class == key2->target_class &&
+ (specified & key2->specified))
+ return 0;
+ if (key1->source_type < key2->source_type)
+ return -1;
+ if (key1->source_type == key2->source_type &&
+ key1->target_type < key2->target_type)
+ return -1;
+ if (key1->source_type == key2->source_type &&
+ key1->target_type == key2->target_type &&
+ key1->target_class < key2->target_class)
+ return -1;
+ return 1;
+}
+
+static int avtab_insert(struct avtab *h, const struct avtab_key *key,
+ const struct avtab_datum *datum)
+{
+ u32 hvalue;
+ struct avtab_node *prev, *cur, *newnode;
+ int cmp;
+
+ if (!h || !h->nslot || h->nel == U32_MAX)
+ return -EINVAL;
+
+ hvalue = avtab_hash(key, h->mask);
+ for (prev = NULL, cur = h->htable[hvalue]; cur;
+ prev = cur, cur = cur->next) {
+ cmp = avtab_node_cmp(key, &cur->key);
+ /* extended perms may not be unique */
+ if (cmp == 0 && !(key->specified & AVTAB_XPERMS))
+ return -EEXIST;
+ if (cmp <= 0)
+ break;
+ }
+
+ newnode = avtab_insert_node(h, prev ? &prev->next : &h->htable[hvalue],
+ 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,
+ const struct avtab_key *key,
+ const struct avtab_datum *datum)
+{
+ u32 hvalue;
+ struct avtab_node *prev, *cur;
+ int cmp;
+
+ if (!h || !h->nslot || h->nel == U32_MAX)
+ return NULL;
+ hvalue = avtab_hash(key, h->mask);
+ for (prev = NULL, cur = h->htable[hvalue]; cur;
+ prev = cur, cur = cur->next) {
+ cmp = avtab_node_cmp(key, &cur->key);
+ if (cmp <= 0)
+ break;
+ }
+ return avtab_insert_node(h, prev ? &prev->next : &h->htable[hvalue],
+ key, datum);
+}
+
+/* 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,
+ const struct avtab_key *key)
+{
+ u32 hvalue;
+ struct avtab_node *cur;
+ int cmp;
+
+ if (!h || !h->nslot)
+ return NULL;
+
+ hvalue = avtab_hash(key, h->mask);
+ for (cur = h->htable[hvalue]; cur; cur = cur->next) {
+ cmp = avtab_node_cmp(key, &cur->key);
+ if (cmp == 0)
+ return cur;
+ if (cmp < 0)
+ break;
+ }
+ return NULL;
+}
+
+struct avtab_node *avtab_search_node_next(struct avtab_node *node,
+ u16 specified)
+{
+ struct avtab_key tmp_key;
+ struct avtab_node *cur;
+ int cmp;
+
+ if (!node)
+ return NULL;
+ tmp_key = node->key;
+ tmp_key.specified = specified;
+ for (cur = node->next; cur; cur = cur->next) {
+ cmp = avtab_node_cmp(&tmp_key, &cur->key);
+ if (cmp == 0)
+ return cur;
+ if (cmp < 0)
+ break;
+ }
+ return NULL;
+}
+
+void avtab_destroy(struct avtab *h)
+{
+ u32 i;
+ struct avtab_node *cur, *temp;
+
+ if (!h)
+ return;
+
+ for (i = 0; i < h->nslot; i++) {
+ cur = 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);
+ }
+ }
+ kvfree(h->htable);
+ h->htable = NULL;
+ h->nel = 0;
+ h->nslot = 0;
+ h->mask = 0;
+}
+
+void avtab_init(struct avtab *h)
+{
+ h->htable = NULL;
+ h->nel = 0;
+ h->nslot = 0;
+ h->mask = 0;
+}
+
+static int avtab_alloc_common(struct avtab *h, u32 nslot)
+{
+ if (!nslot)
+ return 0;
+
+ h->htable = kvcalloc(nslot, sizeof(void *), GFP_KERNEL);
+ if (!h->htable)
+ return -ENOMEM;
+
+ h->nslot = nslot;
+ h->mask = nslot - 1;
+ return 0;
+}
+
+int avtab_alloc(struct avtab *h, u32 nrules)
+{
+ int rc;
+ u32 nslot = 0;
+
+ if (nrules != 0) {
+ nslot = nrules > 3 ? rounddown_pow_of_two(nrules / 2) : 2;
+ if (nslot > MAX_AVTAB_HASH_BUCKETS)
+ nslot = MAX_AVTAB_HASH_BUCKETS;
+
+ rc = avtab_alloc_common(h, nslot);
+ if (rc)
+ return rc;
+ }
+
+ pr_debug("SELinux: %d avtab hash slots, %d rules.\n", nslot, nrules);
+ return 0;
+}
+
+int avtab_alloc_dup(struct avtab *new, const struct avtab *orig)
+{
+ return avtab_alloc_common(new, orig->nslot);
+}
+
+#ifdef CONFIG_SECURITY_SELINUX_DEBUG
+void avtab_hash_eval(struct avtab *h, const char *tag)
+{
+ u32 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 = 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 +=
+ (unsigned long long)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);
+}
+#endif /* CONFIG_SECURITY_SELINUX_DEBUG */
+
+/* clang-format off */
+static const 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
+};
+/* clang-format on */
+
+int avtab_read_item(struct avtab *a, struct policy_file *fp, struct policydb *pol,
+ int (*insertf)(struct avtab *a, const struct avtab_key *k,
+ const struct avtab_datum *d, void *p),
+ void *p, bool conditional)
+{
+ __le16 buf16[4];
+ u16 enabled;
+ u32 items, items2, val, i;
+ struct avtab_key key;
+ struct avtab_datum datum;
+ struct avtab_extended_perms xperms;
+ __le32 buf32[ARRAY_SIZE(xperms.perms.p)];
+ int rc;
+ unsigned int vers = pol->policyvers;
+
+ 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;
+ }
+ /* Read five or more items: source type, target type,
+ * target class, AV type, and at least one datum.
+ */
+ items2 = le32_to_cpu(buf32[0]);
+ if (items2 < 5 || items2 > ARRAY_SIZE(buf32)) {
+ pr_err("SELinux: avtab: invalid item count\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;
+ }
+
+ 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;
+ }
+
+ 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]) {
+ if (items >= items2) {
+ pr_err("SELinux: avtab: entry has too many items (%d/%d)\n",
+ items + 1, items2);
+ return -EINVAL;
+ }
+ key.specified = spec_order[i] | enabled;
+ datum.u.data = le32_to_cpu(buf32[items++]);
+
+ if ((key.specified & AVTAB_TYPE) &&
+ !policydb_simpletype_isvalid(pol, datum.u.data)) {
+ pr_err("SELinux: avtab: invalid type\n");
+ return -EINVAL;
+ }
+
+ 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;
+ }
+
+ if (hweight16(key.specified & ~AVTAB_ENABLED) != 1) {
+ pr_err("SELinux: avtab: not exactly one specifier\n");
+ return -EINVAL;
+ }
+
+ if (key.specified & ~AVTAB_SPECIFIER_MASK) {
+ pr_err("SELinux: avtab: invalid 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 ((vers < POLICYDB_VERSION_COND_XPERMS) &&
+ (key.specified & AVTAB_XPERMS) && conditional) {
+ pr_err("SELinux: avtab: policy version %u does not "
+ "support extended permissions rules in conditional "
+ "policies 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;
+ }
+ if (!avtab_is_valid_xperm_specified(xperms.specified))
+ pr_warn_once_policyload(pol,
+ "SELinux: avtab: unsupported xperm specifier %#x\n",
+ xperms.specified);
+ 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_simpletype_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, const struct avtab_key *k,
+ const struct avtab_datum *d, void *p)
+{
+ return avtab_insert(a, k, d);
+}
+
+int avtab_read(struct avtab *a, struct policy_file *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;
+ }
+
+ /* avtab_read_item() reads at least 96 bytes for any valid entry */
+ rc = size_check(3 * sizeof(u32), nel, fp);
+ if (rc)
+ 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, false);
+ 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, const struct avtab_node *cur, struct policy_file *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, struct policy_file *fp)
+{
+ u32 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 = 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(avtab_node, SLAB_PANIC);
+ avtab_xperms_cachep = KMEM_CACHE(avtab_extended_perms, SLAB_PANIC);
+}
diff --git a/security/selinux/ss/avtab.h b/security/selinux/ss/avtab.h
new file mode 100644
index 000000000..1de4cce28
--- /dev/null
+++ b/security/selinux/ss/avtab.h
@@ -0,0 +1,141 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * 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, <stephen.smalley.work@gmail.com>
+ */
+
+/* Updated: Frank Mayer <mayerf@tresys.com> and
+ * Karl MacMillan <kmacmillan@tresys.com>
+ * Added conditional policy language extensions
+ * Copyright (C) 2003 Tresys Technology, LLC
+ *
+ * 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"
+
+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 */
+#define AVTAB_SPECIFIER_MASK (AVTAB_AV | AVTAB_TYPE | AVTAB_XPERMS | AVTAB_ENABLED)
+ 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
+#define AVTAB_XPERMS_NLMSG 0x03
+ /* 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;
+};
+
+static inline bool avtab_is_valid_xperm_specified(u8 specified)
+{
+ switch (specified) {
+ case AVTAB_XPERMS_IOCTLFUNCTION:
+ case AVTAB_XPERMS_IOCTLDRIVER:
+ case AVTAB_XPERMS_NLMSG:
+ return true;
+ default:
+ return false;
+ }
+}
+
+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 avtab_node **htable;
+ u32 nel; /* number of elements */
+ u32 nslot; /* number of hash slots */
+ u32 mask; /* mask to compute hash func */
+};
+
+void avtab_init(struct avtab *h);
+int avtab_alloc(struct avtab *h, u32 nrules);
+int avtab_alloc_dup(struct avtab *new, const struct avtab *orig);
+void avtab_destroy(struct avtab *h);
+
+#define MAX_AVTAB_HASH_BITS 16
+#define MAX_AVTAB_HASH_BUCKETS (1 << MAX_AVTAB_HASH_BITS)
+
+#ifdef CONFIG_SECURITY_SELINUX_DEBUG
+void avtab_hash_eval(struct avtab *h, const char *tag);
+#else
+static inline void avtab_hash_eval(struct avtab *h, const char *tag)
+{
+}
+#endif
+
+struct policydb;
+struct policy_file;
+int avtab_read_item(struct avtab *a, struct policy_file *fp, struct policydb *pol,
+ int (*insert)(struct avtab *a, const struct avtab_key *k,
+ const struct avtab_datum *d, void *p),
+ void *p, bool conditional);
+
+int avtab_read(struct avtab *a, struct policy_file *fp, struct policydb *pol);
+int avtab_write_item(struct policydb *p, const struct avtab_node *cur,
+ struct policy_file *fp);
+int avtab_write(struct policydb *p, struct avtab *a, struct policy_file *fp);
+
+struct avtab_node *avtab_insert_nonunique(struct avtab *h,
+ const struct avtab_key *key,
+ const struct avtab_datum *datum);
+
+struct avtab_node *avtab_search_node(struct avtab *h,
+ const struct avtab_key *key);
+struct avtab_node *avtab_search_node_next(struct avtab_node *node,
+ u16 specified);
+
+#endif /* _SS_AVTAB_H_ */
diff --git a/security/selinux/ss/conditional.c b/security/selinux/ss/conditional.c
new file mode 100644
index 000000000..2956dd7ed
--- /dev/null
+++ b/security/selinux/ss/conditional.c
@@ -0,0 +1,765 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/* Authors: Karl MacMillan <kmacmillan@tresys.com>
+ * Frank Mayer <mayerf@tresys.com>
+ * Copyright (C) 2003 - 2004 Tresys Technology, LLC
+ */
+
+#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 "policydb.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)
+{
+ u32 i;
+ int s[COND_EXPR_MAXDEPTH];
+ int sp = -1;
+
+ if (expr->len == 0)
+ return -1;
+
+ for (i = 0; i < expr->len; i++) {
+ struct cond_expr_node *node = &expr->nodes[i];
+
+ switch (node->expr_type) {
+ case COND_BOOL:
+ if (sp == (COND_EXPR_MAXDEPTH - 1))
+ return -1;
+ sp++;
+ s[sp] = p->bool_val_to_struct[node->boolean - 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.
+ */
+static void evaluate_cond_node(struct policydb *p, struct cond_node *node)
+{
+ struct avtab_node *avnode;
+ int new_state;
+ u32 i;
+
+ 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 (i = 0; i < node->true_list.len; i++) {
+ avnode = node->true_list.nodes[i];
+ if (new_state <= 0)
+ avnode->key.specified &= ~AVTAB_ENABLED;
+ else
+ avnode->key.specified |= AVTAB_ENABLED;
+ }
+
+ for (i = 0; i < node->false_list.len; i++) {
+ avnode = node->false_list.nodes[i];
+ /* -1 or 1 */
+ if (new_state)
+ avnode->key.specified &= ~AVTAB_ENABLED;
+ else
+ avnode->key.specified |= AVTAB_ENABLED;
+ }
+ }
+}
+
+void evaluate_cond_nodes(struct policydb *p)
+{
+ u32 i;
+
+ for (i = 0; i < p->cond_list_len; i++)
+ evaluate_cond_node(p, &p->cond_list[i]);
+}
+
+void cond_policydb_init(struct policydb *p)
+{
+ p->bool_val_to_struct = NULL;
+ p->cond_list = NULL;
+ p->cond_list_len = 0;
+
+ avtab_init(&p->te_cond_avtab);
+}
+
+static void cond_node_destroy(struct cond_node *node)
+{
+ kfree(node->expr.nodes);
+ /* the avtab_ptr_t nodes are destroyed by the avtab */
+ kfree(node->true_list.nodes);
+ kfree(node->false_list.nodes);
+}
+
+static void cond_list_destroy(struct policydb *p)
+{
+ u32 i;
+
+ for (i = 0; i < p->cond_list_len; i++)
+ cond_node_destroy(&p->cond_list[i]);
+ kfree(p->cond_list);
+ p->cond_list = NULL;
+ p->cond_list_len = 0;
+}
+
+void cond_policydb_destroy(struct policydb *p)
+{
+ kfree(p->bool_val_to_struct);
+ avtab_destroy(&p->te_cond_avtab);
+ cond_list_destroy(p);
+}
+
+int cond_init_bool_indexes(struct policydb *p)
+{
+ kfree(p->bool_val_to_struct);
+ p->bool_val_to_struct = kzalloc_objs(*p->bool_val_to_struct,
+ p->p_bools.nprim);
+ if (!p->bool_val_to_struct)
+ return -ENOMEM;
+
+ avtab_hash_eval(&p->te_cond_avtab, "conditional_rules");
+
+ 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;
+
+ booldatum = datum;
+ p = datap;
+
+ if (!booldatum->value || booldatum->value > p->p_bools.nprim)
+ return -EINVAL;
+
+ p->sym_val_to_name[SYM_BOOLS][booldatum->value - 1] = key;
+ p->bool_val_to_struct[booldatum->value - 1] = booldatum;
+
+ return 0;
+}
+
+int cond_read_bool(struct policydb *p, struct symtab *s, struct policy_file *fp)
+{
+ char *key = NULL;
+ struct cond_bool_datum *booldatum;
+ __le32 buf[3];
+ u32 len, val;
+ int rc;
+
+ booldatum = kzalloc_obj(*booldatum);
+ if (!booldatum)
+ return -ENOMEM;
+
+ rc = next_entry(buf, fp, sizeof(buf));
+ if (rc)
+ goto err;
+
+ booldatum->value = le32_to_cpu(buf[0]);
+ val = le32_to_cpu(buf[1]);
+
+ rc = -EINVAL;
+ if (!val_is_boolean(val))
+ goto err;
+ booldatum->state = (int)val;
+
+ len = le32_to_cpu(buf[2]);
+
+ rc = str_read(&key, GFP_KERNEL, fp, len);
+ if (rc)
+ goto err;
+
+ rc = symtab_insert(s, key, booldatum);
+ if (rc)
+ goto err;
+
+ return 0;
+err:
+ pr_err("SELinux: conditional: failed to read boolean\n");
+ cond_destroy_bool(key, booldatum, NULL);
+ return rc;
+}
+
+struct cond_insertf_data {
+ struct policydb *p;
+ struct avtab_node **dst;
+ struct cond_av_list *other;
+};
+
+static int cond_insertf(struct avtab *a, const struct avtab_key *k,
+ const struct avtab_datum *d, void *ptr)
+{
+ struct cond_insertf_data *data = ptr;
+ struct policydb *p = data->p;
+ struct cond_av_list *other = data->other;
+ struct avtab_node *node_ptr;
+ u32 i;
+ bool found;
+
+ /*
+ * 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_node(&p->te_avtab, k)) {
+ pr_err("SELinux: type rule already exists outside of a conditional.\n");
+ return -EINVAL;
+ }
+ /*
+ * 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");
+ return -EINVAL;
+ }
+ found = false;
+ for (i = 0; i < other->len; i++) {
+ if (other->nodes[i] == node_ptr) {
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ pr_err("SELinux: conflicting type rules.\n");
+ return -EINVAL;
+ }
+ }
+ } else {
+ if (avtab_search_node(&p->te_cond_avtab, k)) {
+ pr_err("SELinux: conflicting type rules when adding type rule for true.\n");
+ return -EINVAL;
+ }
+ }
+ }
+
+ node_ptr = avtab_insert_nonunique(&p->te_cond_avtab, k, d);
+ if (!node_ptr) {
+ pr_err("SELinux: could not insert rule.\n");
+ return -ENOMEM;
+ }
+
+ *data->dst = node_ptr;
+ return 0;
+}
+
+static int cond_read_av_list(struct policydb *p, struct policy_file *fp,
+ struct cond_av_list *list,
+ struct cond_av_list *other)
+{
+ int rc;
+ __le32 buf[1];
+ u32 i, len;
+ struct cond_insertf_data data;
+
+ rc = next_entry(buf, fp, sizeof(u32));
+ if (rc)
+ return rc;
+
+ len = le32_to_cpu(buf[0]);
+ if (len == 0)
+ return 0;
+
+ /* avtab_read_item() reads at least 96 bytes for any valid entry */
+ rc = size_check(3 * sizeof(u32), len, fp);
+ if (rc)
+ return rc;
+
+ list->nodes = kzalloc_objs(*list->nodes, len);
+ if (!list->nodes)
+ return -ENOMEM;
+
+ data.p = p;
+ data.other = other;
+ for (i = 0; i < len; i++) {
+ data.dst = &list->nodes[i];
+ rc = avtab_read_item(&p->te_cond_avtab, fp, p, cond_insertf,
+ &data, true);
+ if (rc) {
+ kfree(list->nodes);
+ list->nodes = NULL;
+ return rc;
+ }
+ }
+
+ list->len = len;
+ return 0;
+}
+
+static int expr_node_isvalid(struct policydb *p, struct cond_expr_node *expr)
+{
+ if (expr->expr_type <= 0 || expr->expr_type > COND_LAST) {
+ pr_err("SELinux: conditional expressions uses unknown operator.\n");
+ return 0;
+ }
+
+ if (expr->expr_type == COND_BOOL &&
+ (expr->boolean == 0 || expr->boolean > 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, struct policy_file *fp)
+{
+ __le32 buf[2];
+ u32 i, len;
+ int rc;
+
+ rc = next_entry(buf, fp, sizeof(u32) * 2);
+ if (rc)
+ return rc;
+
+ node->cur_state = le32_to_cpu(buf[0]);
+
+ /* expr */
+ len = le32_to_cpu(buf[1]);
+
+ /* we will read 64 bytes per node */
+ rc = size_check(2 * sizeof(u32), len, fp);
+ if (rc)
+ return rc;
+
+ node->expr.nodes = kzalloc_objs(*node->expr.nodes, len);
+ if (!node->expr.nodes)
+ return -ENOMEM;
+
+ node->expr.len = len;
+
+ for (i = 0; i < len; i++) {
+ struct cond_expr_node *expr = &node->expr.nodes[i];
+
+ rc = next_entry(buf, fp, sizeof(u32) * 2);
+ if (rc)
+ return rc;
+
+ expr->expr_type = le32_to_cpu(buf[0]);
+ expr->boolean = le32_to_cpu(buf[1]);
+
+ if (!expr_node_isvalid(p, expr))
+ return -EINVAL;
+ }
+
+ rc = cond_read_av_list(p, fp, &node->true_list, NULL);
+ if (rc)
+ return rc;
+ return cond_read_av_list(p, fp, &node->false_list, &node->true_list);
+}
+
+int cond_read_list(struct policydb *p, struct policy_file *fp)
+{
+ __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]);
+
+ /* cond_read_node() reads at least 128 bytes for any valid node */
+ rc = size_check(4 * sizeof(u32), len, fp);
+ if (rc)
+ return rc;
+
+ p->cond_list = kzalloc_objs(*p->cond_list, len);
+ if (!p->cond_list)
+ return -ENOMEM;
+
+ rc = avtab_alloc(&(p->te_cond_avtab), p->te_avtab.nel);
+ if (rc)
+ goto err;
+
+ p->cond_list_len = len;
+
+ for (i = 0; i < len; i++) {
+ rc = cond_read_node(p, &p->cond_list[i], fp);
+ if (rc)
+ goto err;
+ }
+ return 0;
+err:
+ cond_list_destroy(p);
+ 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;
+ struct policy_file *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];
+ u32 i;
+ int rc;
+
+ buf[0] = cpu_to_le32(list->len);
+ rc = put_entry(buf, sizeof(u32), 1, fp);
+ if (rc)
+ return rc;
+
+ for (i = 0; i < list->len; i++) {
+ rc = avtab_write_item(p, list->nodes[i], fp);
+ if (rc)
+ return rc;
+ }
+
+ return 0;
+}
+
+static int cond_write_node(struct policydb *p, struct cond_node *node,
+ struct policy_file *fp)
+{
+ __le32 buf[2];
+ int rc;
+ u32 i;
+
+ buf[0] = cpu_to_le32(node->cur_state);
+ rc = put_entry(buf, sizeof(u32), 1, fp);
+ if (rc)
+ return rc;
+
+ buf[0] = cpu_to_le32(node->expr.len);
+ rc = put_entry(buf, sizeof(u32), 1, fp);
+ if (rc)
+ return rc;
+
+ for (i = 0; i < node->expr.len; i++) {
+ buf[0] = cpu_to_le32(node->expr.nodes[i].expr_type);
+ buf[1] = cpu_to_le32(node->expr.nodes[i].boolean);
+ 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 policy_file *fp)
+{
+ u32 i;
+ __le32 buf[1];
+ int rc;
+
+ buf[0] = cpu_to_le32(p->cond_list_len);
+ rc = put_entry(buf, sizeof(u32), 1, fp);
+ if (rc)
+ return rc;
+
+ for (i = 0; i < p->cond_list_len; i++) {
+ rc = cond_write_node(p, &p->cond_list[i], 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);
+ }
+}
+/* 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);
+ }
+}
+
+static int cond_dup_av_list(struct cond_av_list *new,
+ const struct cond_av_list *orig,
+ struct avtab *avtab)
+{
+ u32 i;
+
+ memset(new, 0, sizeof(*new));
+
+ new->nodes = kzalloc_objs(*new->nodes, orig->len);
+ if (!new->nodes)
+ return -ENOMEM;
+
+ for (i = 0; i < orig->len; i++) {
+ new->nodes[i] = avtab_insert_nonunique(
+ avtab, &orig->nodes[i]->key, &orig->nodes[i]->datum);
+ if (!new->nodes[i])
+ return -ENOMEM;
+ new->len++;
+ }
+
+ return 0;
+}
+
+static int duplicate_policydb_cond_list(struct policydb *newp,
+ const struct policydb *origp)
+{
+ int rc;
+ u32 i;
+
+ rc = avtab_alloc_dup(&newp->te_cond_avtab, &origp->te_cond_avtab);
+ if (rc)
+ return rc;
+
+ newp->cond_list_len = 0;
+ newp->cond_list = kzalloc_objs(*newp->cond_list, origp->cond_list_len);
+ if (!newp->cond_list)
+ goto error;
+
+ for (i = 0; i < origp->cond_list_len; i++) {
+ struct cond_node *newn = &newp->cond_list[i];
+ const struct cond_node *orign = &origp->cond_list[i];
+
+ newp->cond_list_len++;
+
+ newn->cur_state = orign->cur_state;
+ newn->expr.nodes =
+ kmemdup(orign->expr.nodes,
+ orign->expr.len * sizeof(*orign->expr.nodes),
+ GFP_KERNEL);
+ if (!newn->expr.nodes)
+ goto error;
+
+ newn->expr.len = orign->expr.len;
+
+ rc = cond_dup_av_list(&newn->true_list, &orign->true_list,
+ &newp->te_cond_avtab);
+ if (rc)
+ goto error;
+
+ rc = cond_dup_av_list(&newn->false_list, &orign->false_list,
+ &newp->te_cond_avtab);
+ if (rc)
+ goto error;
+ }
+
+ return 0;
+
+error:
+ avtab_destroy(&newp->te_cond_avtab);
+ cond_list_destroy(newp);
+ return -ENOMEM;
+}
+
+static int cond_bools_destroy(void *key, void *datum, void *args)
+{
+ /* key was not copied so no need to free here */
+ kfree(datum);
+ return 0;
+}
+
+static int cond_bools_copy(struct hashtab_node *new,
+ const struct hashtab_node *orig, void *args)
+{
+ struct cond_bool_datum *datum;
+
+ datum = kmemdup(orig->datum, sizeof(struct cond_bool_datum),
+ GFP_KERNEL);
+ if (!datum)
+ return -ENOMEM;
+
+ new->key = orig->key; /* No need to copy, never modified */
+ new->datum = datum;
+ return 0;
+}
+
+static int cond_bools_index(void *key, void *datum, void *args)
+{
+ struct cond_bool_datum *booldatum, **cond_bool_array;
+
+ booldatum = datum;
+ cond_bool_array = args;
+ cond_bool_array[booldatum->value - 1] = booldatum;
+
+ return 0;
+}
+
+static int duplicate_policydb_bools(struct policydb *newdb,
+ const struct policydb *orig)
+{
+ struct cond_bool_datum **cond_bool_array;
+ int rc;
+
+ cond_bool_array = kzalloc_objs(*orig->bool_val_to_struct,
+ orig->p_bools.nprim);
+ if (!cond_bool_array)
+ return -ENOMEM;
+
+ rc = hashtab_duplicate(&newdb->p_bools.table, &orig->p_bools.table,
+ cond_bools_copy, cond_bools_destroy, NULL);
+ if (rc) {
+ kfree(cond_bool_array);
+ return -ENOMEM;
+ }
+
+ hashtab_map(&newdb->p_bools.table, cond_bools_index, cond_bool_array);
+ newdb->bool_val_to_struct = cond_bool_array;
+
+ newdb->p_bools.nprim = orig->p_bools.nprim;
+
+ return 0;
+}
+
+void cond_policydb_destroy_dup(struct policydb *p)
+{
+ hashtab_map(&p->p_bools.table, cond_bools_destroy, NULL);
+ hashtab_destroy(&p->p_bools.table);
+ cond_policydb_destroy(p);
+}
+
+int cond_policydb_dup(struct policydb *new, const struct policydb *orig)
+{
+ cond_policydb_init(new);
+
+ if (duplicate_policydb_bools(new, orig))
+ return -ENOMEM;
+
+ if (duplicate_policydb_cond_list(new, orig)) {
+ cond_policydb_destroy_dup(new);
+ return -ENOMEM;
+ }
+
+ return 0;
+}
diff --git a/security/selinux/ss/conditional.h b/security/selinux/ss/conditional.h
new file mode 100644
index 000000000..468e98ad3
--- /dev/null
+++ b/security/selinux/ss/conditional.h
@@ -0,0 +1,84 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/* Authors: Karl MacMillan <kmacmillan@tresys.com>
+ * Frank Mayer <mayerf@tresys.com>
+ * Copyright (C) 2003 - 2004 Tresys Technology, LLC
+ */
+
+#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_node {
+#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 boolean;
+};
+
+struct cond_expr {
+ struct cond_expr_node *nodes;
+ u32 len;
+};
+
+/*
+ * 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 **nodes;
+ u32 len;
+};
+
+/*
+ * 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;
+};
+
+void 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 symtab *s, struct policy_file *fp);
+int cond_read_list(struct policydb *p, struct policy_file *fp);
+int cond_write_bool(void *key, void *datum, void *ptr);
+int cond_write_list(struct policydb *p, struct policy_file *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);
+void evaluate_cond_nodes(struct policydb *p);
+void cond_policydb_destroy_dup(struct policydb *p);
+int cond_policydb_dup(struct policydb *new, const struct policydb *orig);
+
+#endif /* _CONDITIONAL_H_ */
diff --git a/security/selinux/ss/constraint.h b/security/selinux/ss/constraint.h
new file mode 100644
index 000000000..1d75a8a04
--- /dev/null
+++ b/security/selinux/ss/constraint.h
@@ -0,0 +1,65 @@
+/* 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, <stephen.smalley.work@gmail.com>
+ */
+
+#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 */
+ /* internally unused, only forwarded via policydb_write() */
+ 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.c b/security/selinux/ss/context.c
new file mode 100644
index 000000000..a528b7f76
--- /dev/null
+++ b/security/selinux/ss/context.c
@@ -0,0 +1,32 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Implementations of the security context functions.
+ *
+ * Author: Ondrej Mosnacek <omosnacek@gmail.com>
+ * Copyright (C) 2020 Red Hat, Inc.
+ */
+
+#include <linux/jhash.h>
+
+#include "context.h"
+#include "mls.h"
+
+u32 context_compute_hash(const struct context *c)
+{
+ u32 hash = 0;
+
+ /*
+ * If a context is invalid, it will always be represented by a
+ * context struct with only the len & str set (and vice versa)
+ * under a given policy. Since context structs from different
+ * policies should never meet, it is safe to hash valid and
+ * invalid contexts differently. The context_equal() function
+ * already operates under the same assumption.
+ */
+ if (c->len)
+ return full_name_hash(NULL, c->str, c->len);
+
+ hash = jhash_3words(c->user, c->role, c->type, hash);
+ hash = mls_range_hash(&c->range, hash);
+ return hash;
+}
diff --git a/security/selinux/ss/context.h b/security/selinux/ss/context.h
new file mode 100644
index 000000000..dd3b9b5b5
--- /dev/null
+++ b/security/selinux/ss/context.h
@@ -0,0 +1,204 @@
+/* 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, <stephen.smalley.work@gmail.com>
+ */
+
+#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,
+ const 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,
+ const 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,
+ const 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_glblub(struct context *dst,
+ const struct context *c1,
+ const struct context *c2)
+{
+ struct mls_range *dr = &dst->range;
+ const struct mls_range *r1 = &c1->range, *r2 = &c2->range;
+ int rc = 0;
+
+ if (r1->level[1].sens < r2->level[0].sens ||
+ r2->level[1].sens < r1->level[0].sens)
+ /* These ranges have no common sensitivities */
+ return -EINVAL;
+
+ /* Take the greatest of the low */
+ dr->level[0].sens = max(r1->level[0].sens, r2->level[0].sens);
+
+ /* Take the least of the high */
+ dr->level[1].sens = min(r1->level[1].sens, r2->level[1].sens);
+
+ rc = ebitmap_and(&dr->level[0].cat, &r1->level[0].cat,
+ &r2->level[0].cat);
+ if (rc)
+ goto out;
+
+ rc = ebitmap_and(&dr->level[1].cat, &r1->level[1].cat,
+ &r2->level[1].cat);
+ if (rc)
+ goto out;
+
+out:
+ return rc;
+}
+
+static inline bool mls_context_equal(const struct context *c1,
+ const struct context *c2)
+{
+ return ((c1->range.level[0].sens == c2->range.level[0].sens) &&
+ ebitmap_equal(&c1->range.level[0].cat, &c2->range.level[0].cat) &&
+ (c1->range.level[1].sens == c2->range.level[1].sens) &&
+ ebitmap_equal(&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, const 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);
+ dst->str = NULL;
+ dst->len = 0;
+ 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 bool context_equal(const struct context *c1,
+ const 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_equal(c1, c2));
+}
+
+u32 context_compute_hash(const struct context *c);
+
+#endif /* _SS_CONTEXT_H_ */
diff --git a/security/selinux/ss/ebitmap.c b/security/selinux/ss/ebitmap.c
new file mode 100644
index 000000000..ea894361f
--- /dev/null
+++ b/security/selinux/ss/ebitmap.c
@@ -0,0 +1,603 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Implementation of the extensible bitmap type.
+ *
+ * Author : Stephen Smalley, <stephen.smalley.work@gmail.com>
+ */
+/*
+ * 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 <linux/jhash.h>
+#include <net/netlabel.h>
+#include "ebitmap.h"
+#include "policydb.h"
+
+#define BITS_PER_U64 ((u32)(sizeof(u64) * 8))
+
+static struct kmem_cache *ebitmap_node_cachep __ro_after_init;
+
+bool ebitmap_equal(const struct ebitmap *e1, const struct ebitmap *e2)
+{
+ const struct ebitmap_node *n1, *n2;
+
+ if (e1->highbit != e2->highbit)
+ return false;
+
+ 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 false;
+
+ return true;
+}
+
+int ebitmap_cpy(struct ebitmap *dst, const struct ebitmap *src)
+{
+ struct ebitmap_node *new, *prev;
+ const struct ebitmap_node *n;
+
+ 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;
+}
+
+int ebitmap_and(struct ebitmap *dst, const struct ebitmap *e1,
+ const struct ebitmap *e2)
+{
+ struct ebitmap_node *n;
+ u32 bit;
+ int rc;
+
+ ebitmap_init(dst);
+
+ ebitmap_for_each_positive_bit(e1, n, bit)
+ {
+ if (ebitmap_get_bit(e2, bit)) {
+ rc = ebitmap_set_bit(dst, bit, 1);
+ if (rc < 0)
+ return rc;
+ }
+ }
+ 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(const struct ebitmap *e1, const struct ebitmap *e2,
+ u32 last_e2bit)
+{
+ const 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;
+}
+
+u32 ebitmap_get_highest_set_bit(const struct ebitmap *e)
+{
+ const struct ebitmap_node *n;
+ unsigned long unit;
+ u32 pos = 0;
+
+ n = e->node;
+ if (!n)
+ return 0;
+
+ while (n->next)
+ n = n->next;
+
+ for (unsigned int i = EBITMAP_UNIT_NUMS; i > 0; i--) {
+ unit = n->maps[i - 1];
+ if (unit == 0)
+ continue;
+
+ pos = (i - 1) * EBITMAP_UNIT_SIZE;
+ while (unit >>= 1)
+ pos++;
+ break;
+ }
+
+ return n->startbit + pos;
+}
+
+int ebitmap_get_bit(const struct ebitmap *e, u32 bit)
+{
+ const 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, u32 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 {
+ u32 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;
+}
+
+int ebitmap_read(struct ebitmap *e, struct policy_file *fp)
+{
+ struct ebitmap_node *n = NULL;
+ u32 mapunit, count, startbit, index, i;
+ __le32 ebitmap_start;
+ u64 map;
+ __le64 mapbits;
+ __le32 buf[3];
+ int rc;
+
+ 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 %u (high bit was %u)\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(&ebitmap_start, fp, sizeof(u32));
+ if (rc < 0) {
+ pr_err("SELinux: ebitmap: truncated map\n");
+ goto bad;
+ }
+ startbit = le32_to_cpu(ebitmap_start);
+
+ if (startbit & (mapunit - 1)) {
+ pr_err("SELinux: ebitmap start bit (%u) 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 (%u) 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 %u"
+ " comes after start bit %u\n",
+ startbit, n->startbit);
+ goto bad;
+ }
+
+ rc = next_entry(&mapbits, fp, sizeof(u64));
+ if (rc < 0) {
+ pr_err("SELinux: ebitmap: truncated map\n");
+ goto bad;
+ }
+ map = le64_to_cpu(mapbits);
+ if (!map) {
+ pr_err("SELinux: ebitmap: empty map\n");
+ goto bad;
+ }
+
+ index = (startbit - n->startbit) / EBITMAP_UNIT_SIZE;
+ while (map) {
+ n->maps[index++] = map & (-1UL);
+ map = EBITMAP_SHIFT_UNIT_SIZE(map);
+ }
+ }
+
+ if (n && n->startbit + EBITMAP_SIZE != e->highbit) {
+ pr_err("SELinux: ebitmap: high bit %u is not equal to the expected value %zu\n",
+ e->highbit, n->startbit + EBITMAP_SIZE);
+ goto bad;
+ }
+
+ok:
+ rc = 0;
+out:
+ return rc;
+bad:
+ if (!rc)
+ rc = -EINVAL;
+ ebitmap_destroy(e);
+ goto out;
+}
+
+int ebitmap_write(const struct ebitmap *e, struct policy_file *fp)
+{
+ struct ebitmap_node *n;
+ u32 bit, count, last_bit, last_startbit;
+ __le32 buf[3];
+ u64 map;
+ int rc;
+
+ buf[0] = cpu_to_le32(BITS_PER_U64);
+
+ count = 0;
+ last_bit = 0;
+ last_startbit = U32_MAX;
+ ebitmap_for_each_positive_bit(e, n, bit)
+ {
+ if (last_startbit == U32_MAX ||
+ rounddown(bit, 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 = U32_MAX;
+ ebitmap_for_each_positive_bit(e, n, bit)
+ {
+ if (last_startbit == U32_MAX ||
+ rounddown(bit, 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;
+}
+
+u32 ebitmap_hash(const struct ebitmap *e, u32 hash)
+{
+ struct ebitmap_node *node;
+
+ /* need to change hash even if ebitmap is empty */
+ hash = jhash_1word(e->highbit, hash);
+ for (node = e->node; node; node = node->next) {
+ hash = jhash_1word(node->startbit, hash);
+ hash = jhash(node->maps, sizeof(node->maps), hash);
+ }
+ return hash;
+}
+
+void __init ebitmap_cache_init(void)
+{
+ ebitmap_node_cachep = KMEM_CACHE(ebitmap_node, SLAB_PANIC);
+}
diff --git a/security/selinux/ss/ebitmap.h b/security/selinux/ss/ebitmap.h
new file mode 100644
index 000000000..ae05cc8e4
--- /dev/null
+++ b/security/selinux/ss/ebitmap.h
@@ -0,0 +1,156 @@
+/* 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, <stephen.smalley.work@gmail.com>
+ */
+
+#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 1UL
+#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 u32 ebitmap_start_positive(const struct ebitmap *e,
+ struct ebitmap_node **n)
+{
+ u32 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 u32 ebitmap_next_positive(const struct ebitmap *e,
+ struct ebitmap_node **n, u32 bit)
+{
+ u32 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(const struct ebitmap_node *n, u32 bit)
+{
+ u32 index = EBITMAP_NODE_INDEX(n, bit);
+ u32 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, u32 bit)
+{
+ u32 index = EBITMAP_NODE_INDEX(n, bit);
+ u32 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, u32 bit)
+{
+ u32 index = EBITMAP_NODE_INDEX(n, bit);
+ u32 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))
+
+bool ebitmap_equal(const struct ebitmap *e1, const struct ebitmap *e2);
+int ebitmap_cpy(struct ebitmap *dst, const struct ebitmap *src);
+int ebitmap_and(struct ebitmap *dst, const struct ebitmap *e1,
+ const struct ebitmap *e2);
+int ebitmap_contains(const struct ebitmap *e1, const struct ebitmap *e2,
+ u32 last_e2bit);
+u32 ebitmap_get_highest_set_bit(const struct ebitmap *e);
+int ebitmap_get_bit(const struct ebitmap *e, u32 bit);
+int ebitmap_set_bit(struct ebitmap *e, u32 bit, int value);
+void ebitmap_destroy(struct ebitmap *e);
+struct policy_file;
+int ebitmap_read(struct ebitmap *e, struct policy_file *fp);
+int ebitmap_write(const struct ebitmap *e, struct policy_file *fp);
+u32 ebitmap_hash(const struct ebitmap *e, u32 hash);
+
+#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..9ffc8a371
--- /dev/null
+++ b/security/selinux/ss/hashtab.c
@@ -0,0 +1,199 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Implementation of the hash table type.
+ *
+ * Author : Stephen Smalley, <stephen.smalley.work@gmail.com>
+ */
+
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/errno.h>
+#include "hashtab.h"
+#include "security.h"
+
+static struct kmem_cache *hashtab_node_cachep __ro_after_init;
+
+/*
+ * Here we simply round the number of elements up to the nearest power of two.
+ * I tried also other options like rounding down or rounding to the closest
+ * power of two (up or down based on which is closer), but I was unable to
+ * find any significant difference in lookup/insert performance that would
+ * justify switching to a different (less intuitive) formula. It could be that
+ * a different formula is actually more optimal, but any future changes here
+ * should be supported with performance/memory usage data.
+ *
+ * The total memory used by the htable arrays (only) with Fedora policy loaded
+ * is approximately 163 KB at the time of writing.
+ */
+static u32 hashtab_compute_size(u32 nel)
+{
+ return nel == 0 ? 0 : roundup_pow_of_two(nel);
+}
+
+int hashtab_init(struct hashtab *h, u32 nel_hint)
+{
+ u32 size = hashtab_compute_size(nel_hint);
+
+ /* should already be zeroed, but better be safe */
+ h->nel = 0;
+ h->size = 0;
+ h->htable = NULL;
+
+ if (size) {
+ h->htable = kzalloc_objs(*h->htable, size,
+ GFP_KERNEL | __GFP_NOWARN);
+ if (!h->htable)
+ return -ENOMEM;
+ h->size = size;
+ }
+ return 0;
+}
+
+int __hashtab_insert(struct hashtab *h, struct hashtab_node **dst, void *key,
+ void *datum)
+{
+ struct hashtab_node *newnode;
+
+ newnode = kmem_cache_zalloc(hashtab_node_cachep, GFP_KERNEL);
+ if (!newnode)
+ return -ENOMEM;
+ newnode->key = key;
+ newnode->datum = datum;
+ newnode->next = *dst;
+ *dst = newnode;
+
+ h->nel++;
+ return 0;
+}
+
+void hashtab_destroy(struct hashtab *h)
+{
+ u32 i;
+ struct hashtab_node *cur, *temp;
+
+ 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;
+}
+
+int hashtab_map(struct hashtab *h, int (*apply)(void *k, void *d, void *args),
+ void *args)
+{
+ u32 i;
+ int ret;
+ struct hashtab_node *cur;
+
+ 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;
+}
+
+#ifdef CONFIG_SECURITY_SELINUX_DEBUG
+void hashtab_stat(struct hashtab *h, struct hashtab_info *info)
+{
+ u32 i, chain_len, slots_used, max_chain_len;
+ u64 chain2_len_sum;
+ struct hashtab_node *cur;
+
+ slots_used = 0;
+ max_chain_len = 0;
+ chain2_len_sum = 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;
+
+ chain2_len_sum += (u64)chain_len * chain_len;
+ }
+ }
+
+ info->slots_used = slots_used;
+ info->max_chain_len = max_chain_len;
+ info->chain2_len_sum = chain2_len_sum;
+}
+#endif /* CONFIG_SECURITY_SELINUX_DEBUG */
+
+int hashtab_duplicate(struct hashtab *new, const struct hashtab *orig,
+ int (*copy)(struct hashtab_node *new,
+ const struct hashtab_node *orig, void *args),
+ int (*destroy)(void *k, void *d, void *args), void *args)
+{
+ const struct hashtab_node *orig_cur;
+ struct hashtab_node *cur, *tmp, *tail;
+ u32 i;
+ int rc;
+
+ memset(new, 0, sizeof(*new));
+
+ new->htable = kzalloc_objs(*new->htable, orig->size);
+ if (!new->htable)
+ return -ENOMEM;
+
+ new->size = orig->size;
+
+ for (i = 0; i < orig->size; i++) {
+ tail = NULL;
+ for (orig_cur = orig->htable[i]; orig_cur;
+ orig_cur = orig_cur->next) {
+ tmp = kmem_cache_zalloc(hashtab_node_cachep,
+ GFP_KERNEL);
+ if (!tmp)
+ goto error;
+ rc = copy(tmp, orig_cur, args);
+ if (rc) {
+ kmem_cache_free(hashtab_node_cachep, tmp);
+ goto error;
+ }
+ tmp->next = NULL;
+ if (!tail)
+ new->htable[i] = tmp;
+ else
+ tail->next = tmp;
+ tail = tmp;
+ new->nel++;
+ }
+ }
+
+ return 0;
+
+error:
+ for (i = 0; i < new->size; i++) {
+ for (cur = new->htable[i]; cur; cur = tmp) {
+ tmp = cur->next;
+ destroy(cur->key, cur->datum, args);
+ kmem_cache_free(hashtab_node_cachep, cur);
+ }
+ }
+ kfree(new->htable);
+ memset(new, 0, sizeof(*new));
+ return -ENOMEM;
+}
+
+void __init hashtab_cache_init(void)
+{
+ hashtab_node_cachep = KMEM_CACHE(hashtab_node, SLAB_PANIC);
+}
diff --git a/security/selinux/ss/hashtab.h b/security/selinux/ss/hashtab.h
new file mode 100644
index 000000000..c641fb129
--- /dev/null
+++ b/security/selinux/ss/hashtab.h
@@ -0,0 +1,154 @@
+/* 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, <stephen.smalley.work@gmail.com>
+ */
+
+#ifndef _SS_HASHTAB_H_
+#define _SS_HASHTAB_H_
+
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/sched.h>
+
+#define HASHTAB_MAX_NODES U32_MAX
+
+struct hashtab_key_params {
+ u32 (*hash)(const void *key); /* hash func */
+ int (*cmp)(const void *key1, const void *key2); /* comparison func */
+};
+
+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 */
+};
+
+struct hashtab_info {
+ u32 slots_used;
+ u32 max_chain_len;
+ u64 chain2_len_sum;
+};
+
+/*
+ * Initializes a new hash table with the specified characteristics.
+ *
+ * Returns -ENOMEM if insufficient space is available or 0 otherwise.
+ */
+int hashtab_init(struct hashtab *h, u32 nel_hint);
+
+int __hashtab_insert(struct hashtab *h, struct hashtab_node **dst, void *key,
+ void *datum);
+
+/*
+ * 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.
+ */
+static inline int hashtab_insert(struct hashtab *h, void *key, void *datum,
+ struct hashtab_key_params key_params)
+{
+ u32 hvalue;
+ struct hashtab_node *prev, *cur;
+
+ cond_resched();
+
+ if (!h->size || h->nel == HASHTAB_MAX_NODES)
+ return -EINVAL;
+
+ hvalue = key_params.hash(key) & (h->size - 1);
+ prev = NULL;
+ cur = h->htable[hvalue];
+ while (cur) {
+ int cmp = key_params.cmp(key, cur->key);
+
+ if (cmp == 0)
+ return -EEXIST;
+ if (cmp < 0)
+ break;
+ prev = cur;
+ cur = cur->next;
+ }
+
+ return __hashtab_insert(h, prev ? &prev->next : &h->htable[hvalue], key,
+ datum);
+}
+
+/*
+ * 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.
+ */
+static inline void *hashtab_search(const struct hashtab *h, const void *key,
+ struct hashtab_key_params key_params)
+{
+ u32 hvalue;
+ const struct hashtab_node *cur;
+
+ if (!h->size)
+ return NULL;
+
+ hvalue = key_params.hash(key) & (h->size - 1);
+ cur = h->htable[hvalue];
+ while (cur) {
+ int cmp = key_params.cmp(key, cur->key);
+
+ if (cmp == 0)
+ return cur->datum;
+ if (cmp < 0)
+ break;
+ cur = cur->next;
+ }
+ return NULL;
+}
+
+/*
+ * 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);
+
+int hashtab_duplicate(struct hashtab *new, const struct hashtab *orig,
+ int (*copy)(struct hashtab_node *new,
+ const struct hashtab_node *orig, void *args),
+ int (*destroy)(void *k, void *d, void *args), void *args);
+
+#ifdef CONFIG_SECURITY_SELINUX_DEBUG
+/* Fill info with some hash table statistics */
+void hashtab_stat(struct hashtab *h, struct hashtab_info *info);
+#else
+static inline void hashtab_stat(struct hashtab *h, struct hashtab_info *info)
+{
+ return;
+}
+#endif
+
+#endif /* _SS_HASHTAB_H */
diff --git a/security/selinux/ss/mls.c b/security/selinux/ss/mls.c
new file mode 100644
index 000000000..40c626006
--- /dev/null
+++ b/security/selinux/ss/mls.c
@@ -0,0 +1,653 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Implementation of the multi-level security (MLS) policy.
+ *
+ * Author : Stephen Smalley, <stephen.smalley.work@gmail.com>
+ */
+
+/*
+ * 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
+ * Copyright (C) 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;
+ const 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++) {
+ u32 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)
+{
+ const char *nm;
+ char *scontextp;
+ 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;
+}
+
+bool mls_level_isvalid(const struct policydb *p, const struct mls_level *l)
+{
+ const char *name;
+ const struct level_datum *levdatum;
+
+ if (!l->sens || l->sens > p->p_levels.nprim)
+ return false;
+
+ name = sym_name(p, SYM_LEVELS, l->sens - 1);
+ if (!name)
+ return false;
+
+ levdatum = symtab_search(&p->p_levels, name);
+ if (!levdatum)
+ return false;
+
+ /*
+ * l is valid iff every bit in l->cat is set in levdatum->level.cat
+ * and no bit in l->cat is larger than p->p_cats.nprim.
+ * policydb_index() has already verified that every bit set in
+ * levdatum->level.cat names a defined category, so containment is
+ * sufficient here.
+ */
+ return ebitmap_contains(&levdatum->level.cat, &l->cat,
+ p->p_cats.nprim);
+}
+
+bool mls_range_isvalid(const struct policydb *p, const 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 true if the MLS fields in the security context
+ * structure `c' are valid. Return 0 otherwise.
+ */
+bool mls_context_isvalid(const struct policydb *p, const struct context *c)
+{
+ const struct user_datum *usrdatum;
+
+ if (!p->mls_enabled)
+ return true;
+
+ if (!mls_range_isvalid(p, &c->range))
+ return false;
+
+ if (c->role == OBJECT_R_VAL)
+ return true;
+
+ /*
+ * User must be authorized for the MLS range.
+ */
+ if (!c->user || c->user > p->p_users.nprim)
+ return false;
+ usrdatum = p->user_val_to_struct[c->user - 1];
+ if (!usrdatum || !mls_range_contains(usrdatum->range, c->range))
+ return false; /* user may not be associated with range */
+
+ return true;
+}
+
+/*
+ * Set the MLS fields in the security context structure
+ * `context' based on the string representation in
+ * the string `scontext'.
+ *
+ * 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 *sensitivity, *cur_cat, *next_cat, *rngptr;
+ struct level_datum *levdatum;
+ struct cat_datum *catdatum, *rngdatum;
+ u32 i;
+ int l, rc;
+ char *rangep[2];
+
+ if (!pol->mls_enabled) {
+ /*
+ * With no MLS, only return -EINVAL if there is a MLS field
+ * and it did not come from an xattr.
+ */
+ if (oldc && def_sid == SECSID_NULL)
+ return -EINVAL;
+ 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)
+ return -EINVAL;
+
+ defcon = sidtab_search(s, def_sid);
+ if (!defcon)
+ return -EINVAL;
+
+ return mls_context_cpy(context, defcon);
+ }
+
+ /*
+ * If we're dealing with a range, figure out where the two parts
+ * of the range begin.
+ */
+ rangep[0] = scontext;
+ rangep[1] = strchr(scontext, '-');
+ if (rangep[1]) {
+ rangep[1][0] = '\0';
+ rangep[1]++;
+ }
+
+ /* For each part of the range: */
+ for (l = 0; l < 2; l++) {
+ /* Split sensitivity and category set. */
+ sensitivity = rangep[l];
+ if (sensitivity == NULL)
+ break;
+ next_cat = strchr(sensitivity, ':');
+ if (next_cat)
+ *(next_cat++) = '\0';
+
+ /* Parse sensitivity. */
+ levdatum = symtab_search(&pol->p_levels, sensitivity);
+ if (!levdatum)
+ return -EINVAL;
+ context->range.level[l].sens = levdatum->level.sens;
+
+ /* Extract category set. */
+ while (next_cat != NULL) {
+ cur_cat = next_cat;
+ next_cat = strchr(next_cat, ',');
+ if (next_cat != NULL)
+ *(next_cat++) = '\0';
+
+ /* Separate into range if exists */
+ rngptr = strchr(cur_cat, '.');
+ if (rngptr != NULL) {
+ /* Remove '.' */
+ *rngptr++ = '\0';
+ }
+
+ catdatum = symtab_search(&pol->p_cats, cur_cat);
+ if (!catdatum)
+ return -EINVAL;
+
+ rc = ebitmap_set_bit(&context->range.level[l].cat,
+ catdatum->value - 1, 1);
+ if (rc)
+ return rc;
+
+ /* If range, set all categories in range */
+ if (rngptr == NULL)
+ continue;
+
+ rngdatum = symtab_search(&pol->p_cats, rngptr);
+ if (!rngdatum)
+ return -EINVAL;
+
+ if (catdatum->value >= rngdatum->value)
+ return -EINVAL;
+
+ for (i = catdatum->value; i < rngdatum->value; i++) {
+ rc = ebitmap_set_bit(
+ &context->range.level[l].cat, i, 1);
+ if (rc)
+ return rc;
+ }
+ }
+ }
+
+ /* If we didn't see a '-', the range start is also the range end. */
+ if (rangep[1] == NULL) {
+ 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)
+ return rc;
+ }
+
+ return 0;
+}
+
+/*
+ * 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;
+ int rc;
+
+ if (!p->mls_enabled)
+ return -EINVAL;
+
+ tmpstr = kstrdup(str, gfp_mask);
+ if (!tmpstr) {
+ rc = -ENOMEM;
+ } else {
+ rc = mls_context_to_sid(p, ':', tmpstr, context, NULL,
+ SECSID_NULL);
+ kfree(tmpstr);
+ }
+
+ 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 `oldc' from the values specified in the
+ * policy `oldp' to the values specified in the policy `newp',
+ * storing the resulting context in `newc'.
+ */
+int mls_convert_context(struct policydb *oldp, struct policydb *newp,
+ struct context *oldc, struct context *newc)
+{
+ struct level_datum *levdatum;
+ struct cat_datum *catdatum;
+ struct ebitmap_node *node;
+ u32 i;
+ int l;
+
+ if (!oldp->mls_enabled || !newp->mls_enabled)
+ return 0;
+
+ for (l = 0; l < 2; l++) {
+ const char *name = sym_name(oldp, SYM_LEVELS,
+ oldc->range.level[l].sens - 1);
+
+ levdatum = symtab_search(&newp->p_levels, name);
+
+ if (!levdatum)
+ return -EINVAL;
+ newc->range.level[l].sens = levdatum->level.sens;
+
+ ebitmap_for_each_positive_bit(&oldc->range.level[l].cat, node,
+ i)
+ {
+ int rc;
+
+ catdatum = symtab_search(&newp->p_cats,
+ sym_name(oldp, SYM_CATS, i));
+ if (!catdatum)
+ return -EINVAL;
+ rc = ebitmap_set_bit(&newc->range.level[l].cat,
+ catdatum->value - 1, 1);
+ if (rc)
+ return rc;
+ }
+ }
+
+ 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;
+ char 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 = policydb_rangetr_search(p, &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);
+ case DEFAULT_GLBLUB:
+ return mls_context_glblub(newcontext, scontext,
+ tcontext);
+ }
+
+ fallthrough;
+ case AVTAB_CHANGE:
+ if ((tclass == p->process_class) || sock)
+ /* 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);
+ }
+ return -EINVAL;
+}
+
+#ifdef CONFIG_NETLABEL
+/**
+ * mls_export_netlbl_lvl - Export the MLS sensitivity levels to NetLabel
+ * @p: the policy
+ * @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
+ * @p: the policy
+ * @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
+ * @p: the policy
+ * @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
+ * @p: the policy
+ * @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..93cde1b22
--- /dev/null
+++ b/security/selinux/ss/mls.h
@@ -0,0 +1,96 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Multi-level security (MLS) policy operations.
+ *
+ * Author : Stephen Smalley, <stephen.smalley.work@gmail.com>
+ */
+
+/*
+ * 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
+ * Copyright (X) Hewlett-Packard Development Company, L.P., 2006
+ */
+
+#ifndef _SS_MLS_H_
+#define _SS_MLS_H_
+
+#include <linux/jhash.h>
+
+#include "context.h"
+#include "ebitmap.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);
+bool mls_context_isvalid(const struct policydb *p, const struct context *c);
+bool mls_range_isvalid(const struct policydb *p, const struct mls_range *r);
+bool mls_level_isvalid(const struct policydb *p, const 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 *oldc, struct context *newc);
+
+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
+
+static inline u32 mls_range_hash(const struct mls_range *r, u32 hash)
+{
+ hash = jhash_2words(r->level[0].sens, r->level[1].sens, hash);
+ hash = ebitmap_hash(&r->level[0].cat, hash);
+ hash = ebitmap_hash(&r->level[1].cat, hash);
+ return hash;
+}
+
+#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..51df2ebd1
--- /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, <stephen.smalley.work@gmail.com>
+ */
+
+/*
+ * 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(const struct mls_level *l1,
+ const struct mls_level *l2)
+{
+ return ((l1->sens == l2->sens) && ebitmap_equal(&l1->cat, &l2->cat));
+}
+
+static inline int mls_level_dom(const struct mls_level *l1,
+ const 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..b75c977c6
--- /dev/null
+++ b/security/selinux/ss/policydb.c
@@ -0,0 +1,4155 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Implementation of the policy database.
+ *
+ * Author : Stephen Smalley, <stephen.smalley.work@gmail.com>
+ */
+
+/*
+ * Updated: Trusted Computer Solutions, Inc. <dgoeddel@trustedcs.com>
+ * Support for enhanced MLS infrastructure.
+ * Copyright (C) 2004-2005 Trusted Computer Solutions, Inc.
+ *
+ * Updated: Frank Mayer <mayerf@tresys.com> and
+ * Karl MacMillan <kmacmillan@tresys.com>
+ * Added conditional policy language extensions
+ * Copyright (C) 2003-2004 Tresys Technology, LLC
+ *
+ * Updated: Hewlett-Packard <paul@paul-moore.com>
+ * Added support for the policy capability bitmap
+ * Copyright (C) 2007 Hewlett-Packard Development Company, L.P.
+ *
+ * Update: Mellanox Techonologies
+ * Added Infiniband support
+ * Copyright (C) 2016 Mellanox Techonologies
+ */
+
+#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/sort.h>
+#include "security.h"
+
+#include "policydb.h"
+#include "conditional.h"
+#include "mls.h"
+#include "services.h"
+
+#ifdef CONFIG_SECURITY_SELINUX_DEBUG
+/* clang-format off */
+static const char *const symtab_name[SYM_NUM] = {
+ "common prefixes",
+ "classes",
+ "roles",
+ "types",
+ "users",
+ "bools",
+ "levels",
+ "categories",
+};
+/* clang-format off */
+#endif
+
+struct policydb_compat_info {
+ unsigned int version;
+ unsigned int sym_num;
+ unsigned int ocon_num;
+};
+
+/* These need to be updated if SYM_NUM or OCON_NUM changes */
+static const 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,
+ },
+ {
+ .version = POLICYDB_VERSION_GLBLUB,
+ .sym_num = SYM_NUM,
+ .ocon_num = OCON_NUM,
+ },
+ {
+ .version = POLICYDB_VERSION_COMP_FTRANS,
+ .sym_num = SYM_NUM,
+ .ocon_num = OCON_NUM,
+ },
+ {
+ .version = POLICYDB_VERSION_COND_XPERMS,
+ .sym_num = SYM_NUM,
+ .ocon_num = OCON_NUM,
+ },
+ {
+ .version = POLICYDB_VERSION_NEVERAUDIT,
+ .sym_num = SYM_NUM,
+ .ocon_num = OCON_NUM,
+ },
+};
+
+static const struct policydb_compat_info *
+policydb_lookup_compat(unsigned int version)
+{
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(policydb_compat); i++) {
+ if (policydb_compat[i].version == version)
+ return &policydb_compat[i];
+ }
+
+ return NULL;
+}
+
+/*
+ * 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;
+ ebitmap_destroy(&levdatum->level.cat);
+ }
+ kfree(datum);
+ return 0;
+}
+
+static int cat_destroy(void *key, void *datum, void *p)
+{
+ kfree(key);
+ kfree(datum);
+ return 0;
+}
+
+/* clang-format off */
+static int (*const 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,
+};
+/* clang-format on */
+
+static int filenametr_destroy(void *key, void *datum, void *p)
+{
+ struct filename_trans_key *ft = key;
+ struct filename_trans_datum *next, *d = datum;
+
+ kfree(ft->name);
+ kfree(key);
+ do {
+ ebitmap_destroy(&d->stypes);
+ next = d->next;
+ kfree(d);
+ d = next;
+ } while (unlikely(d));
+ 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 int role_tr_destroy(void *key, void *datum, void *p)
+{
+ kfree(key);
+ kfree(datum);
+ return 0;
+}
+
+static void ocontext_destroy(struct ocontext *c, unsigned 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);
+}
+
+/*
+ * Initialize the role table.
+ */
+static int roles_init(struct policydb *p)
+{
+ char *key = NULL;
+ int rc;
+ struct role_datum *role;
+
+ role = kzalloc_obj(*role);
+ 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 = symtab_insert(&p->p_roles, key, role);
+ if (rc)
+ goto out;
+
+ return 0;
+out:
+ kfree(key);
+ kfree(role);
+ return rc;
+}
+
+static u32 filenametr_hash(const void *k)
+{
+ const struct filename_trans_key *ft = k;
+ unsigned long salt = ft->ttype ^ ft->tclass;
+
+ return full_name_hash((void *)salt, ft->name, strlen(ft->name));
+}
+
+static int filenametr_cmp(const void *k1, const void *k2)
+{
+ const struct filename_trans_key *ft1 = k1;
+ const struct filename_trans_key *ft2 = k2;
+ int v;
+
+ v = cmp_int(ft1->ttype, ft2->ttype);
+ if (v)
+ return v;
+
+ v = cmp_int(ft1->tclass, ft2->tclass);
+ if (v)
+ return v;
+
+ return strcmp(ft1->name, ft2->name);
+}
+
+static const struct hashtab_key_params filenametr_key_params = {
+ .hash = filenametr_hash,
+ .cmp = filenametr_cmp,
+};
+
+struct filename_trans_datum *
+policydb_filenametr_search(struct policydb *p, struct filename_trans_key *key)
+{
+ return hashtab_search(&p->filename_trans, key, filenametr_key_params);
+}
+
+static u32 rangetr_hash(const void *k)
+{
+ const struct range_trans *key = k;
+
+ return key->source_type + (key->target_type << 3) +
+ (key->target_class << 5);
+}
+
+static int rangetr_cmp(const void *k1, const void *k2)
+{
+ const struct range_trans *key1 = k1, *key2 = k2;
+ int v;
+
+ v = cmp_int(key1->source_type, key2->source_type);
+ if (v)
+ return v;
+
+ v = cmp_int(key1->target_type, key2->target_type);
+ if (v)
+ return v;
+
+ v = cmp_int(key1->target_class, key2->target_class);
+
+ return v;
+}
+
+static const struct hashtab_key_params rangetr_key_params = {
+ .hash = rangetr_hash,
+ .cmp = rangetr_cmp,
+};
+
+struct mls_range *policydb_rangetr_search(struct policydb *p,
+ struct range_trans *key)
+{
+ return hashtab_search(&p->range_tr, key, rangetr_key_params);
+}
+
+static u32 role_trans_hash(const void *k)
+{
+ const struct role_trans_key *key = k;
+
+ return jhash_3words(key->role, key->type,
+ (u32)key->tclass << 16 | key->tclass, 0);
+}
+
+static int role_trans_cmp(const void *k1, const void *k2)
+{
+ const struct role_trans_key *key1 = k1, *key2 = k2;
+ int v;
+
+ v = cmp_int(key1->role, key2->role);
+ if (v)
+ return v;
+
+ v = cmp_int(key1->type, key2->type);
+ if (v)
+ return v;
+
+ return cmp_int(key1->tclass, key2->tclass);
+}
+
+static const struct hashtab_key_params roletr_key_params = {
+ .hash = role_trans_hash,
+ .cmp = role_trans_cmp,
+};
+
+struct role_trans_datum *policydb_roletr_search(struct policydb *p,
+ struct role_trans_key *key)
+{
+ return hashtab_search(&p->role_tr, key, roletr_key_params);
+}
+
+/*
+ * Initialize a policy database structure.
+ */
+static void policydb_init(struct policydb *p)
+{
+ memset(p, 0, sizeof(*p));
+
+ avtab_init(&p->te_avtab);
+ cond_policydb_init(p);
+
+ ebitmap_init(&p->filename_trans_ttypes);
+ ebitmap_init(&p->policycaps);
+ ebitmap_init(&p->permissive_map);
+ ebitmap_init(&p->neveraudit_map);
+}
+
+/*
+ * The following *_index functions are used to
+ * define the val_to_name and val_to_struct arrays
+ * in a policy database structure. The val_to_name
+ * arrays are used when converting security context
+ * structures into string representations. The
+ * val_to_struct arrays are used when the attributes
+ * of a class, role, or user are needed.
+ */
+
+static int common_index(void *key, void *datum, void *datap)
+{
+ struct policydb *p;
+ struct common_datum *comdatum;
+
+ comdatum = datum;
+ p = datap;
+ if (!comdatum->value || comdatum->value > p->p_commons.nprim)
+ return -EINVAL;
+
+ p->sym_val_to_name[SYM_COMMONS][comdatum->value - 1] = key;
+
+ return 0;
+}
+
+static int class_index(void *key, void *datum, void *datap)
+{
+ struct policydb *p;
+ struct class_datum *cladatum;
+
+ cladatum = datum;
+ p = datap;
+ if (!cladatum->value || cladatum->value > p->p_classes.nprim)
+ return -EINVAL;
+
+ p->sym_val_to_name[SYM_CLASSES][cladatum->value - 1] = key;
+ p->class_val_to_struct[cladatum->value - 1] = cladatum;
+ return 0;
+}
+
+static int role_index(void *key, void *datum, void *datap)
+{
+ struct policydb *p;
+ struct role_datum *role;
+
+ role = datum;
+ p = datap;
+ if (!role->value || role->value > p->p_roles.nprim ||
+ role->bounds > p->p_roles.nprim)
+ return -EINVAL;
+
+ p->sym_val_to_name[SYM_ROLES][role->value - 1] = key;
+ p->role_val_to_struct[role->value - 1] = role;
+ return 0;
+}
+
+static int type_index(void *key, void *datum, void *datap)
+{
+ struct policydb *p;
+ struct type_datum *typdatum;
+
+ typdatum = datum;
+ p = datap;
+
+ if (!typdatum->value || typdatum->value > p->p_types.nprim ||
+ typdatum->bounds > p->p_types.nprim) {
+ pr_err("SELinux: type %s had value %u bounds %u nprim %u\n",
+ (char *)key, typdatum->value, typdatum->bounds,
+ p->p_types.nprim);
+ return -EINVAL;
+ }
+
+ if (typdatum->primary) {
+ p->sym_val_to_name[SYM_TYPES][typdatum->value - 1] = key;
+ p->type_val_to_struct[typdatum->value - 1] = typdatum;
+ }
+
+ return 0;
+}
+
+static int user_index(void *key, void *datum, void *datap)
+{
+ struct policydb *p;
+ struct user_datum *usrdatum;
+
+ usrdatum = datum;
+ p = datap;
+ if (!usrdatum->value || usrdatum->value > p->p_users.nprim ||
+ usrdatum->bounds > p->p_users.nprim)
+ return -EINVAL;
+
+ p->sym_val_to_name[SYM_USERS][usrdatum->value - 1] = key;
+ p->user_val_to_struct[usrdatum->value - 1] = usrdatum;
+ return 0;
+}
+
+static int sens_index(void *key, void *datum, void *datap)
+{
+ struct policydb *p;
+ struct level_datum *levdatum;
+
+ levdatum = datum;
+ p = datap;
+
+ if (!levdatum->level.sens || levdatum->level.sens > p->p_levels.nprim)
+ return -EINVAL;
+
+ if (!levdatum->isalias)
+ p->sym_val_to_name[SYM_LEVELS][levdatum->level.sens - 1] = key;
+
+ return 0;
+}
+
+static int cat_index(void *key, void *datum, void *datap)
+{
+ struct policydb *p;
+ struct cat_datum *catdatum;
+
+ catdatum = datum;
+ p = datap;
+
+ if (!catdatum->value || catdatum->value > p->p_cats.nprim)
+ return -EINVAL;
+
+ if (!catdatum->isalias)
+ p->sym_val_to_name[SYM_CATS][catdatum->value - 1] = key;
+
+ return 0;
+}
+
+static int sens_cat_index_check(void *key, void *datum, void *datap)
+{
+ struct policydb *p = datap;
+ struct level_datum *levdatum = datum;
+ struct ebitmap_node *node;
+ u32 bit;
+
+ ebitmap_for_each_positive_bit(&levdatum->level.cat, node, bit) {
+ if (bit >= p->p_cats.nprim || !sym_name(p, SYM_CATS, bit)) {
+ pr_err("SELinux: sensitivity %s allows undefined category %u\n",
+ (const char *)key, bit + 1);
+ return -EINVAL;
+ }
+ }
+ return 0;
+}
+
+/* clang-format off */
+static int (*const 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,
+};
+/* clang-format on */
+
+#ifdef CONFIG_SECURITY_SELINUX_DEBUG
+static void hash_eval(struct hashtab *h, const char *hash_name,
+ const char *hash_details)
+{
+ struct hashtab_info info;
+
+ hashtab_stat(h, &info);
+ pr_debug(
+ "SELinux: %s%s%s: %d entries and %d/%d buckets used, longest chain length %d, sum of chain length^2 %llu\n",
+ hash_name, hash_details ? "@" : "", hash_details ?: "", h->nel,
+ info.slots_used, h->size, info.max_chain_len,
+ info.chain2_len_sum);
+}
+
+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], NULL);
+}
+
+#else
+static inline void hash_eval(struct hashtab *h, const char *hash_name,
+ const char *hash_details)
+{
+}
+static inline void symtab_hash_eval(struct symtab *s)
+{
+}
+#endif /* CONFIG_SECURITY_SELINUX_DEBUG */
+
+/*
+ * 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;
+ u32 v;
+
+ 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);
+
+ symtab_hash_eval(p->symtab);
+
+ p->class_val_to_struct = kzalloc_objs(*p->class_val_to_struct,
+ p->p_classes.nprim);
+ if (!p->class_val_to_struct)
+ return -ENOMEM;
+
+ p->role_val_to_struct = kzalloc_objs(*p->role_val_to_struct,
+ p->p_roles.nprim);
+ if (!p->role_val_to_struct)
+ return -ENOMEM;
+
+ p->user_val_to_struct = kzalloc_objs(*p->user_val_to_struct,
+ p->p_users.nprim);
+ if (!p->user_val_to_struct)
+ return -ENOMEM;
+
+ p->type_val_to_struct = kvzalloc_objs(*p->type_val_to_struct,
+ p->p_types.nprim);
+ if (!p->type_val_to_struct)
+ return -ENOMEM;
+
+ rc = cond_init_bool_indexes(p);
+ if (rc)
+ goto out;
+
+ for (i = 0; i < SYM_NUM; i++) {
+ p->sym_val_to_name[i] = kvcalloc(p->symtab[i].nprim,
+ sizeof(char *), GFP_KERNEL);
+ if (!p->sym_val_to_name[i])
+ return -ENOMEM;
+
+ rc = hashtab_map(&p->symtab[i].table, index_f[i], p);
+ if (rc)
+ goto out;
+ }
+
+ /*
+ * A sparse class value is absorbed by policydb_class_isvalid() and
+ * its siblings, but no such predicate exists for booleans: every
+ * user of bool_val_to_struct[] walks it by index and dereferences
+ * each entry -- cond_evaluate_expr(), the two getters and
+ * security_set_bools() -- so an unclaimed one has no consumer that
+ * can tolerate it.
+ */
+ for (v = 0; v < p->p_bools.nprim; v++) {
+ if (!p->bool_val_to_struct[v]) {
+ pr_err("SELinux: boolean %u is declared but not defined\n",
+ v + 1);
+ rc = -EINVAL;
+ goto out;
+ }
+ }
+
+ if (p->mls_enabled) {
+ rc = hashtab_map(&p->p_levels.table, sens_cat_index_check, p);
+ if (rc)
+ goto out;
+ }
+
+ rc = 0;
+out:
+ return rc;
+}
+
+/*
+ * Free any memory allocated by a policy database structure.
+ */
+void policydb_destroy(struct policydb *p)
+{
+ struct ocontext *c, *ctmp;
+ struct genfs *g, *gtmp;
+ u32 i;
+ struct role_allow *ra, *lra = 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++)
+ kvfree(p->sym_val_to_name[i]);
+
+ kfree(p->class_val_to_struct);
+ kfree(p->role_val_to_struct);
+ kfree(p->user_val_to_struct);
+ kvfree(p->type_val_to_struct);
+
+ 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);
+
+ hashtab_map(&p->role_tr, role_tr_destroy, NULL);
+ hashtab_destroy(&p->role_tr);
+
+ 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++)
+ ebitmap_destroy(&p->type_attr_map_array[i]);
+ kvfree(p->type_attr_map_array);
+ }
+
+ ebitmap_destroy(&p->filename_trans_ttypes);
+ ebitmap_destroy(&p->policycaps);
+ ebitmap_destroy(&p->permissive_map);
+ ebitmap_destroy(&p->neveraudit_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;
+ bool isid_init;
+ int rc;
+
+ rc = sidtab_init(s);
+ if (rc) {
+ pr_err("SELinux: out of memory on SID table init\n");
+ return rc;
+ }
+
+ isid_init = ebitmap_get_bit(&p->policycaps,
+ POLICYDB_CAP_USERSPACE_INITIAL_CONTEXT);
+
+ head = p->ocontexts[OCON_ISID];
+ for (c = head; c; c = c->next) {
+ u32 sid = c->sid[0];
+ const char *name = security_get_initial_sid_context(sid);
+
+ if (sid == SECSID_NULL) {
+ pr_err("SELinux: SID 0 was assigned a context.\n");
+ sidtab_destroy(s);
+ return -EINVAL;
+ }
+
+ /* Ignore initial SIDs unused by this kernel. */
+ if (!name)
+ continue;
+
+ /*
+ * Also ignore SECINITSID_INIT if the policy doesn't declare
+ * support for it
+ */
+ if (sid == SECINITSID_INIT && !isid_init)
+ continue;
+
+ rc = sidtab_set_initial(s, sid, &c->context[0]);
+ if (rc) {
+ pr_err("SELinux: unable to load initial SID %s.\n",
+ name);
+ sidtab_destroy(s);
+ return rc;
+ }
+
+ /*
+ * If the policy doesn't support the "userspace_initial_context"
+ * capability, set SECINITSID_INIT to the same context as
+ * SECINITSID_KERNEL. This ensures the same behavior as before
+ * the reintroduction of SECINITSID_INIT, where all tasks
+ * started before policy load would initially get the context
+ * corresponding to SECINITSID_KERNEL.
+ */
+ if (sid == SECINITSID_KERNEL && !isid_init) {
+ rc = sidtab_set_initial(s, SECINITSID_INIT,
+ &c->context[0]);
+ if (rc) {
+ pr_err("SELinux: unable to load initial SID %s.\n",
+ name);
+ sidtab_destroy(s);
+ return rc;
+ }
+ }
+ }
+ return 0;
+}
+
+bool policydb_class_isvalid(const struct policydb *p, u16 class)
+{
+ if (!class || class > p->p_classes.nprim)
+ return false;
+ if (!p->sym_val_to_name[SYM_CLASSES][class - 1])
+ return false;
+ return true;
+}
+
+bool policydb_user_isvalid(const struct policydb *p, u32 user)
+{
+ if (!user || user > p->p_users.nprim)
+ return false;
+ if (!p->sym_val_to_name[SYM_USERS][user - 1])
+ return false;
+ return true;
+}
+
+bool policydb_role_isvalid(const struct policydb *p, u32 role)
+{
+ if (!role || role > p->p_roles.nprim)
+ return false;
+ if (!p->sym_val_to_name[SYM_ROLES][role - 1])
+ return false;
+ return true;
+}
+
+bool policydb_type_isvalid(const struct policydb *p, u32 type)
+{
+ if (!type || type > p->p_types.nprim)
+ return false;
+ if (!p->sym_val_to_name[SYM_TYPES][type - 1])
+ return false;
+ return true;
+}
+
+bool policydb_simpletype_isvalid(const struct policydb *p, u32 type)
+{
+ const struct type_datum *datum;
+
+ if (!type || type > p->p_types.nprim)
+ return false;
+
+ datum = p->type_val_to_struct[type - 1];
+ if (!datum)
+ return false;
+
+ if (datum->attribute)
+ return false;
+
+ return true;
+}
+
+/*
+ * Return true if the fields in the security context
+ * structure `c' are valid. Return 0 otherwise.
+ */
+bool policydb_context_isvalid(const struct policydb *p, const struct context *c)
+{
+ const struct role_datum *role;
+ const struct user_datum *usrdatum;
+
+ if (!c->role || c->role > p->p_roles.nprim)
+ return false;
+
+ if (!c->user || c->user > p->p_users.nprim)
+ return false;
+
+ if (!c->type || c->type > p->p_types.nprim)
+ return false;
+
+ 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 false;
+
+ /*
+ * User must be authorized for the role.
+ */
+ usrdatum = p->user_val_to_struct[c->user - 1];
+ if (!usrdatum)
+ return false;
+
+ if (!ebitmap_get_bit(&usrdatum->roles, c->role - 1))
+ /* user may not be associated with role */
+ return false;
+ }
+
+ if (!mls_context_isvalid(p, c))
+ return false;
+
+ return true;
+}
+
+/*
+ * Read a MLS range structure from a policydb binary
+ * representation file.
+ */
+static int mls_read_range_helper(struct mls_range *r, struct policy_file *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,
+ struct policy_file *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.
+ */
+
+int str_read(char **strp, gfp_t flags, struct policy_file *fp, u32 len)
+{
+ int rc;
+ char *str;
+
+ if ((len == 0) || (len == (u32)-1))
+ return -EINVAL;
+
+ if (size_check(sizeof(char), len, fp))
+ return -EINVAL;
+
+ str = kmalloc(len + 1, flags | __GFP_NOWARN);
+ if (!str)
+ return -ENOMEM;
+
+ rc = next_entry(str, fp, len);
+ if (rc) {
+ kfree(str);
+ return rc;
+ }
+
+ str[len] = '\0';
+ *strp = str;
+ return 0;
+}
+
+/*
+ * Bitmap of the permission values a symtab has claimed. Values are 1-based
+ * and bounded by SEL_VEC_MAX, the width of an access vector, so the whole set
+ * fits in a u32 and the callers reject an nprim past that width.
+ */
+static u32 perm_claimed_mask(u32 nprim)
+{
+ return nprim ? U32_MAX >> (SEL_VEC_MAX - nprim) : 0;
+}
+
+static int perm_read(struct policydb *p, struct symtab *s,
+ struct policy_file *fp, u32 *claimed)
+{
+ char *key = NULL;
+ struct perm_datum *perdatum;
+ int rc;
+ __le32 buf[2];
+ u32 len;
+
+ perdatum = kzalloc_obj(*perdatum);
+ 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 = -EINVAL;
+ if (perdatum->value < 1 || perdatum->value > SEL_VEC_MAX)
+ goto bad;
+ /* indexes an nprim-sized array in security_get_permissions() */
+ if (perdatum->value > s->nprim)
+ goto bad;
+ /* two permissions cannot share one slot of that array */
+ if (*claimed & (1U << (perdatum->value - 1)))
+ goto bad;
+ *claimed |= 1U << (perdatum->value - 1);
+
+ rc = str_read(&key, GFP_KERNEL, fp, len);
+ if (rc)
+ goto bad;
+
+ rc = symtab_insert(s, key, perdatum);
+ if (rc)
+ goto bad;
+
+ return 0;
+bad:
+ perm_destroy(key, perdatum, NULL);
+ return rc;
+}
+
+static int common_read(struct policydb *p, struct symtab *s, struct policy_file *fp)
+{
+ char *key = NULL;
+ struct common_datum *comdatum;
+ __le32 buf[4];
+ u32 i, len, nel, claimed = 0;
+ int rc;
+
+ comdatum = kzalloc_obj(*comdatum);
+ 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]);
+ nel = le32_to_cpu(buf[3]);
+ rc = -EINVAL;
+ if (nel > SEL_VEC_MAX)
+ goto bad;
+
+ /* perm_read() reads at least 64 bytes for any valid permission */
+ rc = size_check(2 * sizeof(u32), nel, fp);
+ if (rc)
+ goto bad;
+
+ rc = symtab_init(&comdatum->permissions, nel);
+ if (rc)
+ goto bad;
+ comdatum->permissions.nprim = le32_to_cpu(buf[2]);
+ /* no permission value can reach a slot past SEL_VEC_MAX */
+ rc = -EINVAL;
+ if (comdatum->permissions.nprim > SEL_VEC_MAX)
+ goto bad;
+
+ rc = str_read(&key, GFP_KERNEL, fp, len);
+ if (rc)
+ goto bad;
+
+ for (i = 0; i < nel; i++) {
+ rc = perm_read(p, &comdatum->permissions, fp, &claimed);
+ if (rc)
+ goto bad;
+ }
+
+ rc = -EINVAL;
+ if (claimed != perm_claimed_mask(comdatum->permissions.nprim)) {
+ pr_err("SELinux: common %s does not define every permission it declares\n",
+ key);
+ goto bad;
+ }
+
+ hash_eval(&comdatum->permissions.table, "common_permissions", key);
+
+ rc = symtab_insert(s, 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, struct policy_file *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,
+ u32 ncons, int allowxtarget, struct policy_file *fp)
+{
+ struct constraint_node *c, *lc;
+ struct constraint_expr *e, *le;
+ __le32 buf[3];
+ u32 i, j, nexpr;
+ int rc, depth;
+
+ lc = NULL;
+ for (i = 0; i < ncons; i++) {
+ c = kzalloc_obj(*c);
+ 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_obj(*e);
+ 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++;
+ switch (e->attr) {
+ case CEXPR_USER:
+ case CEXPR_TYPE:
+ if (e->op != CEXPR_EQ &&
+ e->op != CEXPR_NEQ)
+ return -EINVAL;
+ break;
+ case CEXPR_ROLE:
+ case CEXPR_L1L2:
+ case CEXPR_L1H2:
+ case CEXPR_H1L2:
+ case CEXPR_H1H2:
+ case CEXPR_L1H1:
+ case CEXPR_L2H2:
+ if (e->op < CEXPR_EQ ||
+ e->op > CEXPR_INCOMP)
+ return -EINVAL;
+ break;
+ default:
+ return -EINVAL;
+ }
+ break;
+ case CEXPR_NAMES:
+ if (!allowxtarget && (e->attr & CEXPR_XTARGET))
+ return -EINVAL;
+ if (depth == (CEXPR_MAXDEPTH - 1))
+ return -EINVAL;
+ depth++;
+ switch (e->attr &
+ ~(CEXPR_TARGET|CEXPR_XTARGET)) {
+ case CEXPR_USER:
+ case CEXPR_ROLE:
+ case CEXPR_TYPE:
+ break;
+ default:
+ return -EINVAL;
+ }
+ if ((e->attr & (CEXPR_TARGET|CEXPR_XTARGET)) ==
+ (CEXPR_TARGET|CEXPR_XTARGET))
+ return -EINVAL;
+ if (e->op != CEXPR_EQ && e->op != CEXPR_NEQ)
+ return -EINVAL;
+ rc = ebitmap_read(&e->names, fp);
+ if (rc)
+ return rc;
+ if (p->policyvers >=
+ POLICYDB_VERSION_CONSTRAINT_NAMES) {
+ e->type_names = kzalloc_obj(*e->type_names);
+ 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 symtab *s, struct policy_file *fp)
+{
+ char *key = NULL;
+ struct class_datum *cladatum;
+ __le32 buf[6];
+ u32 i, len, len2, ncons, nel, val, claimed = 0, inherited = 0;
+ int rc;
+
+ cladatum = kzalloc_obj(*cladatum);
+ 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]);
+ nel = le32_to_cpu(buf[4]);
+ rc = -EINVAL;
+ if (nel > SEL_VEC_MAX)
+ goto bad;
+
+ val = le32_to_cpu(buf[2]);
+ rc = -EINVAL;
+ if (val > U16_MAX)
+ goto bad;
+ cladatum->value = val;
+
+ /* perm_read() reads at least 64 bytes for any valid permission */
+ rc = size_check(2 * sizeof(u32), nel, fp);
+ if (rc)
+ goto bad;
+
+ rc = symtab_init(&cladatum->permissions, nel);
+ if (rc)
+ goto bad;
+ cladatum->permissions.nprim = le32_to_cpu(buf[3]);
+ /* no permission value can reach a slot past SEL_VEC_MAX */
+ rc = -EINVAL;
+ if (cladatum->permissions.nprim > SEL_VEC_MAX)
+ goto bad;
+
+ 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 =
+ symtab_search(&p->p_commons, cladatum->comkey);
+ if (!cladatum->comdatum) {
+ pr_err("SELinux: unknown common %s\n",
+ cladatum->comkey);
+ goto bad;
+ }
+
+ /*
+ * security_get_permissions() maps the common's permissions
+ * into an array sized by this class's nprim, so a class must
+ * declare at least as many as the common it inherits.
+ */
+ if (cladatum->permissions.nprim <
+ cladatum->comdatum->permissions.nprim) {
+ pr_err("SELinux: class %s has fewer permissions than common %s\n",
+ key, cladatum->comkey);
+ goto bad;
+ }
+ }
+ for (i = 0; i < nel; i++) {
+ rc = perm_read(p, &cladatum->permissions, fp, &claimed);
+ if (rc)
+ goto bad;
+ }
+
+ /* the class's own permissions must claim the slots the common leaves */
+ if (cladatum->comdatum)
+ inherited = cladatum->comdatum->permissions.nprim;
+ rc = -EINVAL;
+ if (claimed != (perm_claimed_mask(cladatum->permissions.nprim) &
+ ~perm_claimed_mask(inherited))) {
+ pr_err("SELinux: class %s does not define every permission it declares\n",
+ key);
+ goto bad;
+ }
+
+ hash_eval(&cladatum->permissions.table, "class_permissions", key);
+
+ 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;
+
+ rc = -EINVAL;
+ val = le32_to_cpu(buf[0]);
+ switch (val) {
+ case 0:
+ case DEFAULT_SOURCE:
+ case DEFAULT_TARGET:
+ cladatum->default_user = val;
+ break;
+ default:
+ goto bad;
+ }
+ val = le32_to_cpu(buf[1]);
+ switch (val) {
+ case 0:
+ case DEFAULT_SOURCE:
+ case DEFAULT_TARGET:
+ cladatum->default_role = val;
+ break;
+ default:
+ goto bad;
+ }
+ val = le32_to_cpu(buf[2]);
+ switch (val) {
+ case 0:
+ case DEFAULT_SOURCE_LOW:
+ case DEFAULT_SOURCE_HIGH:
+ case DEFAULT_SOURCE_LOW_HIGH:
+ case DEFAULT_TARGET_LOW:
+ case DEFAULT_TARGET_HIGH:
+ case DEFAULT_TARGET_LOW_HIGH:
+ case DEFAULT_GLBLUB:
+ cladatum->default_range = val;
+ break;
+ default:
+ goto bad;
+ }
+ }
+
+ if (p->policyvers >= POLICYDB_VERSION_DEFAULT_TYPE) {
+ rc = next_entry(buf, fp, sizeof(u32) * 1);
+ if (rc)
+ goto bad;
+ rc = -EINVAL;
+ val = le32_to_cpu(buf[0]);
+ switch (val) {
+ case 0:
+ case DEFAULT_TARGET:
+ case DEFAULT_SOURCE:
+ cladatum->default_type = val;
+ break;
+ default:
+ goto bad;
+ }
+ }
+
+ rc = symtab_insert(s, key, cladatum);
+ if (rc)
+ goto bad;
+
+ return 0;
+bad:
+ cls_destroy(key, cladatum, NULL);
+ if (rc)
+ pr_err("SELinux: invalid class\n");
+ return rc;
+}
+
+static int role_read(struct policydb *p, struct symtab *s, struct policy_file *fp)
+{
+ char *key = NULL;
+ struct role_datum *role;
+ int rc;
+ unsigned int to_read = 2;
+ __le32 buf[3];
+ u32 len;
+
+ role = kzalloc_obj(*role);
+ 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 = symtab_insert(s, key, role);
+ if (rc)
+ goto bad;
+ return 0;
+bad:
+ role_destroy(key, role, NULL);
+ return rc;
+}
+
+static int type_read(struct policydb *p, struct symtab *s, struct policy_file *fp)
+{
+ char *key = NULL;
+ struct type_datum *typdatum;
+ int rc;
+ unsigned int to_read = 3;
+ __le32 buf[4];
+ u32 len;
+
+ typdatum = kzalloc_obj(*typdatum);
+ 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 = symtab_insert(s, 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, struct policy_file *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 symtab *s, struct policy_file *fp)
+{
+ char *key = NULL;
+ struct user_datum *usrdatum;
+ int rc;
+ unsigned int to_read = 2;
+ __le32 buf[3];
+ u32 len;
+
+ usrdatum = kzalloc_obj(*usrdatum);
+ 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 = symtab_insert(s, key, usrdatum);
+ if (rc)
+ goto bad;
+ return 0;
+bad:
+ user_destroy(key, usrdatum, NULL);
+ return rc;
+}
+
+static int sens_read(struct policydb *p, struct symtab *s, struct policy_file *fp)
+{
+ char *key = NULL;
+ struct level_datum *levdatum;
+ int rc;
+ __le32 buf[2];
+ u32 len, val;
+
+ levdatum = kzalloc_obj(*levdatum);
+ if (!levdatum)
+ return -ENOMEM;
+
+ rc = next_entry(buf, fp, sizeof buf);
+ if (rc)
+ goto bad;
+
+ len = le32_to_cpu(buf[0]);
+ val = le32_to_cpu(buf[1]);
+ rc = -EINVAL;
+ if (!val_is_boolean(val))
+ goto bad;
+ levdatum->isalias = val;
+
+ rc = str_read(&key, GFP_KERNEL, fp, len);
+ if (rc)
+ goto bad;
+
+ rc = mls_read_level(&levdatum->level, fp);
+ if (rc)
+ goto bad;
+
+ rc = symtab_insert(s, key, levdatum);
+ if (rc)
+ goto bad;
+ return 0;
+bad:
+ sens_destroy(key, levdatum, NULL);
+ if (rc)
+ pr_err("SELinux: invalid sensitivity\n");
+ return rc;
+}
+
+static int cat_read(struct policydb *p, struct symtab *s, struct policy_file *fp)
+{
+ char *key = NULL;
+ struct cat_datum *catdatum;
+ int rc;
+ __le32 buf[3];
+ u32 len, val;
+
+ catdatum = kzalloc_obj(*catdatum);
+ 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]);
+ val = le32_to_cpu(buf[2]);
+ rc = -EINVAL;
+ if (!val_is_boolean(val))
+ goto bad;
+ catdatum->isalias = val;
+
+ rc = str_read(&key, GFP_KERNEL, fp, len);
+ if (rc)
+ goto bad;
+
+ rc = symtab_insert(s, key, catdatum);
+ if (rc)
+ goto bad;
+ return 0;
+bad:
+ cat_destroy(key, catdatum, NULL);
+ if (rc)
+ pr_err("SELinux: invalid category\n");
+ return rc;
+}
+
+/* clang-format off */
+static int (*const read_f[SYM_NUM])(struct policydb *p, struct symtab *s,
+ struct policy_file *fp) = {
+ common_read,
+ class_read,
+ role_read,
+ type_read,
+ user_read,
+ cond_read_bool,
+ sens_read,
+ cat_read,
+};
+/* clang-format on */
+
+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;
+ u32 bit;
+
+ if (++depth == POLICYDB_BOUNDS_MAXDEPTH) {
+ pr_err("SELinux: user %s: "
+ "too deep or looped boundary\n",
+ (char *)key);
+ return -EINVAL;
+ }
+
+ if (!policydb_user_isvalid(p, upper->bounds)) {
+ pr_err("SELinux: user %s: invalid boundary id %d\n",
+ (char *) key, upper->bounds);
+ 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;
+ u32 bit;
+
+ if (++depth == POLICYDB_BOUNDS_MAXDEPTH) {
+ pr_err("SELinux: role %s: "
+ "too deep or looped bounds\n",
+ (char *)key);
+ return -EINVAL;
+ }
+
+ if (!policydb_role_isvalid(p, upper->bounds)) {
+ pr_err("SELinux: role %s: invalid boundary id %d\n",
+ (char *) key, upper->bounds);
+ 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;
+ }
+
+ if (!policydb_type_isvalid(p, upper->bounds)) {
+ pr_err("SELinux: type %s: invalid boundary id %d\n",
+ (char *) key, upper->bounds);
+ return -EINVAL;
+ }
+
+ upper = p->type_val_to_struct[upper->bounds - 1];
+ if (upper->attribute) {
+ pr_err("SELinux: type %s: "
+ "bounded by attribute %s\n",
+ (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 = symtab_search(&p->p_classes, 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 = symtab_search(&comdatum->permissions, name);
+ if (!perdatum)
+ perdatum = symtab_search(&cladatum->permissions, name);
+ if (!perdatum)
+ return 0;
+
+ return 1U << (perdatum->value - 1);
+}
+
+static int range_read(struct policydb *p, struct policy_file *fp)
+{
+ struct range_trans *rt = NULL;
+ struct mls_range *r = NULL;
+ int rc;
+ __le32 buf[2];
+ u32 i, nel, val;
+
+ 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]);
+
+ /* we read at least 64 bytes and mls_read_range_helper() 32 bytes
+ * for any valid range-transition
+ */
+ rc = size_check(3 * sizeof(u32), nel, fp);
+ if (rc)
+ return rc;
+
+ rc = hashtab_init(&p->range_tr, nel);
+ if (rc)
+ return rc;
+
+ for (i = 0; i < nel; i++) {
+ rc = -ENOMEM;
+ rt = kzalloc_obj(*rt);
+ 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;
+ rc = -EINVAL;
+ val = le32_to_cpu(buf[0]);
+ if (val > U16_MAX)
+ goto out;
+ rt->target_class = val;
+ } 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_obj(*r);
+ 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, rangetr_key_params);
+ if (rc)
+ goto out;
+
+ rt = NULL;
+ r = NULL;
+ }
+ hash_eval(&p->range_tr, "rangetr", NULL);
+ rc = 0;
+out:
+ kfree(rt);
+ kfree(r);
+ if (rc)
+ pr_err("SELinux: invalid range\n");
+ return rc;
+}
+
+static int filename_trans_read_helper_compat(struct policydb *p, struct policy_file *fp)
+{
+ struct filename_trans_key key, *ft = NULL;
+ struct filename_trans_datum *last, *datum = NULL;
+ char *name = NULL;
+ u32 len, stype, otype, val;
+ __le32 buf[4];
+ int rc;
+
+ /* length of the path component string */
+ rc = next_entry(buf, fp, sizeof(u32));
+ if (rc)
+ return rc;
+ len = le32_to_cpu(buf[0]);
+
+ /* path component string */
+ rc = str_read(&name, GFP_KERNEL, fp, len);
+ if (rc)
+ return rc;
+
+ rc = next_entry(buf, fp, sizeof(u32) * 4);
+ if (rc)
+ goto out;
+
+ rc = -EINVAL;
+ stype = le32_to_cpu(buf[0]);
+ if (!policydb_type_isvalid(p, stype))
+ goto out;
+ key.ttype = le32_to_cpu(buf[1]);
+ if (!policydb_type_isvalid(p, key.ttype))
+ goto out;
+ val = le32_to_cpu(buf[2]);
+ if (val > U16_MAX || !policydb_class_isvalid(p, val))
+ goto out;
+ key.tclass = val;
+ key.name = name;
+
+ otype = le32_to_cpu(buf[3]);
+ if (!policydb_simpletype_isvalid(p, otype))
+ goto out;
+
+ last = NULL;
+ datum = policydb_filenametr_search(p, &key);
+ while (datum) {
+ if (unlikely(ebitmap_get_bit(&datum->stypes, stype - 1))) {
+ /* conflicting/duplicate rules are ignored */
+ datum = NULL;
+ rc = 0;
+ goto out;
+ }
+ if (likely(datum->otype == otype))
+ break;
+ last = datum;
+ datum = datum->next;
+ }
+ if (!datum) {
+ rc = -ENOMEM;
+ datum = kmalloc_obj(*datum);
+ if (!datum)
+ goto out;
+
+ ebitmap_init(&datum->stypes);
+ datum->otype = otype;
+ datum->next = NULL;
+
+ if (unlikely(last)) {
+ last->next = datum;
+ } else {
+ rc = -ENOMEM;
+ ft = kmemdup(&key, sizeof(key), GFP_KERNEL);
+ if (!ft)
+ goto out;
+
+ rc = hashtab_insert(&p->filename_trans, ft, datum,
+ filenametr_key_params);
+ if (rc)
+ goto out;
+ name = NULL;
+
+ rc = ebitmap_set_bit(&p->filename_trans_ttypes,
+ key.ttype, 1);
+ if (rc)
+ return rc;
+ }
+ }
+ kfree(name);
+ return ebitmap_set_bit(&datum->stypes, stype - 1, 1);
+
+out:
+ kfree(ft);
+ kfree(name);
+ kfree(datum);
+
+ if (rc)
+ pr_err("SELinux: invalid compat filename transition\n");
+ return rc;
+}
+
+static int filename_trans_read_helper(struct policydb *p, struct policy_file *fp)
+{
+ struct filename_trans_key *ft = NULL;
+ struct filename_trans_datum **dst, *datum, *first = NULL;
+ char *name = NULL;
+ u32 len, ttype, ndatum, i, val;
+ u16 tclass;
+ __le32 buf[3];
+ int rc;
+
+ /* length of the path component string */
+ rc = next_entry(buf, fp, sizeof(u32));
+ if (rc)
+ return rc;
+ len = le32_to_cpu(buf[0]);
+
+ /* path component string */
+ rc = str_read(&name, GFP_KERNEL, fp, len);
+ if (rc)
+ return rc;
+
+ rc = next_entry(buf, fp, sizeof(u32) * 3);
+ if (rc)
+ goto out;
+
+ rc = -EINVAL;
+ ttype = le32_to_cpu(buf[0]);
+ if (!policydb_type_isvalid(p, ttype))
+ goto out;
+ val = le32_to_cpu(buf[1]);
+ rc = -EINVAL;
+ if (val > U16_MAX || !policydb_class_isvalid(p, val))
+ goto out;
+ tclass = val;
+
+ ndatum = le32_to_cpu(buf[2]);
+ if (ndatum == 0) {
+ pr_err("SELinux: Filename transition key with no datum\n");
+ rc = -ENOENT;
+ goto out;
+ }
+
+ dst = &first;
+ for (i = 0; i < ndatum; i++) {
+ rc = -ENOMEM;
+ datum = kmalloc_obj(*datum);
+ if (!datum)
+ goto out;
+
+ datum->next = NULL;
+ *dst = datum;
+
+ /* ebitmap_read() will at least init the bitmap */
+ rc = ebitmap_read(&datum->stypes, fp);
+ if (rc)
+ goto out;
+
+ rc = next_entry(buf, fp, sizeof(u32));
+ if (rc)
+ goto out;
+
+ datum->otype = le32_to_cpu(buf[0]);
+
+ rc = -EINVAL;
+ if (!policydb_simpletype_isvalid(p, datum->otype))
+ goto out;
+
+ dst = &datum->next;
+ }
+
+ rc = -ENOMEM;
+ ft = kmalloc_obj(*ft);
+ if (!ft)
+ goto out;
+
+ ft->ttype = ttype;
+ ft->tclass = tclass;
+ ft->name = name;
+
+ rc = hashtab_insert(&p->filename_trans, ft, first,
+ filenametr_key_params);
+ if (rc == -EEXIST)
+ pr_err("SELinux: Duplicate filename transition key\n");
+ if (rc)
+ goto out;
+
+ return ebitmap_set_bit(&p->filename_trans_ttypes, ttype, 1);
+
+out:
+ kfree(ft);
+ kfree(name);
+ while (first) {
+ datum = first;
+ first = first->next;
+
+ ebitmap_destroy(&datum->stypes);
+ kfree(datum);
+ }
+
+ if (rc)
+ pr_err("SELinux: invalid filename transition\n");
+ return rc;
+}
+
+static int filename_trans_read(struct policydb *p, struct policy_file *fp)
+{
+ u32 nel, i;
+ __le32 buf[1];
+ int rc;
+
+ 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]);
+
+ if (p->policyvers < POLICYDB_VERSION_COMP_FTRANS) {
+ p->compat_filename_trans_count = nel;
+
+ rc = hashtab_init(&p->filename_trans, (1 << 11));
+ if (rc)
+ return rc;
+
+ for (i = 0; i < nel; i++) {
+ rc = filename_trans_read_helper_compat(p, fp);
+ if (rc)
+ return rc;
+ }
+ } else {
+ rc = hashtab_init(&p->filename_trans, nel);
+ if (rc)
+ return rc;
+
+ for (i = 0; i < nel; i++) {
+ rc = filename_trans_read_helper(p, fp);
+ if (rc)
+ return rc;
+ }
+ }
+ hash_eval(&p->filename_trans, "filenametr", NULL);
+ return 0;
+}
+
+static int genfs_read(struct policydb *p, struct policy_file *fp)
+{
+ int rc;
+ u32 i, j, nel, nel2, len, len2, val;
+ __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_obj(*newgenfs);
+ 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_obj(*newc);
+ 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;
+
+ rc = -EINVAL;
+ val = le32_to_cpu(buf[0]);
+ if (val > U16_MAX || (val != 0 && !policydb_class_isvalid(p, val)))
+ goto out;
+ newc->v.sclass = val;
+ 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);
+
+ if (rc)
+ pr_err("SELinux: invalid genfs\n");
+
+ return rc;
+}
+
+static int ocontext_read(struct policydb *p,
+ const struct policydb_compat_info *info, struct policy_file *fp)
+{
+ int rc;
+ unsigned int i;
+ u32 j, nel, len, val;
+ __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_obj(*c);
+ 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;
+
+ if (i == OCON_FS)
+ pr_warn("SELinux: void and deprecated fs ocon %s\n",
+ c->u.name);
+
+ 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;
+
+ rc = -EINVAL;
+ val = le32_to_cpu(buf[0]);
+ if (val > U8_MAX)
+ goto out;
+ c->u.port.protocol = val;
+ val = le32_to_cpu(buf[1]);
+ if (val > U16_MAX)
+ goto out;
+ c->u.port.low_port = val;
+ val = le32_to_cpu(buf[2]);
+ if (val > U16_MAX)
+ goto out;
+ c->u.port.high_port = val;
+ if (c->u.port.low_port == 0 ||
+ c->u.port.low_port > c->u.port.high_port)
+ goto out;
+
+ 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:
+ if (rc)
+ pr_err("SELinux: invalid ocon\n");
+ return rc;
+}
+
+/*
+ * Read the configuration data from a policy database binary
+ * representation file into a policy database structure.
+ */
+int policydb_read(struct policydb *p, struct policy_file *fp)
+{
+ struct role_allow *ra, *lra;
+ struct role_trans_key *rtk = NULL;
+ struct role_trans_datum *rtd = NULL;
+ int rc;
+ __le32 buf[4];
+ u32 i, j, len, nprim, nel, perm, val;
+
+ char *policydb_str;
+ const struct policydb_compat_info *info;
+
+ policydb_init(p);
+
+ /* 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 = str_read(&policydb_str, GFP_KERNEL, fp, len);
+ if (rc) {
+ if (rc == -ENOMEM) {
+ pr_err("SELinux: unable to allocate memory for policydb string of length %d\n",
+ len);
+ } else {
+ pr_err("SELinux: truncated policydb string identifier\n");
+ }
+ goto bad;
+ }
+
+ rc = -EINVAL;
+ 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;
+ }
+
+ if (p->policyvers >= POLICYDB_VERSION_NEVERAUDIT) {
+ rc = ebitmap_read(&p->neveraudit_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]);
+
+ /* every read_f() implementation reads at least 128 bytes
+ * for any valid entry
+ */
+ rc = size_check(4 * sizeof(u32), nel, fp);
+ if (rc)
+ goto out;
+
+ rc = symtab_init(&p->symtab[i], nel);
+ if (rc)
+ goto out;
+
+ if (i == SYM_ROLES) {
+ rc = roles_init(p);
+ if (rc)
+ goto out;
+ }
+
+ for (j = 0; j < nel; j++) {
+ rc = read_f[i](p, &p->symtab[i], fp);
+ if (rc)
+ goto bad;
+ }
+
+ p->symtab[i].nprim = nprim;
+ }
+
+ rc = policydb_index(p);
+ if (rc)
+ goto bad;
+
+ rc = -EINVAL;
+ p->process_class = string_to_security_class(p, "process");
+ if (!p->process_class) {
+ pr_err("SELinux: process class is required, not defined in policy\n");
+ goto bad;
+ }
+
+ rc = avtab_read(&p->te_avtab, fp, p);
+ if (rc)
+ goto bad;
+
+ avtab_hash_eval(&p->te_avtab, "rules");
+
+ 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]);
+
+ /* we read at least 96 bytes for any valid role-transition */
+ rc = size_check(3 * sizeof(u32), nel, fp);
+ if (rc)
+ goto bad;
+
+ rc = hashtab_init(&p->role_tr, nel);
+ if (rc)
+ goto bad;
+ for (i = 0; i < nel; i++) {
+ rc = -ENOMEM;
+ rtk = kmalloc_obj(*rtk);
+ if (!rtk)
+ goto bad;
+
+ rc = -ENOMEM;
+ rtd = kmalloc_obj(*rtd);
+ if (!rtd)
+ goto bad;
+
+ rc = next_entry(buf, fp, sizeof(u32) * 3);
+ if (rc)
+ goto bad;
+
+ rtk->role = le32_to_cpu(buf[0]);
+ rtk->type = le32_to_cpu(buf[1]);
+ rtd->new_role = le32_to_cpu(buf[2]);
+ if (p->policyvers >= POLICYDB_VERSION_ROLETRANS) {
+ rc = next_entry(buf, fp, sizeof(u32));
+ if (rc)
+ goto bad;
+ rc = -EINVAL;
+ val = le32_to_cpu(buf[0]);
+ if (val > U16_MAX)
+ goto bad;
+ rtk->tclass = val;
+ } else
+ rtk->tclass = p->process_class;
+
+ rc = -EINVAL;
+ if (!policydb_role_isvalid(p, rtk->role) ||
+ !policydb_type_isvalid(p, rtk->type) ||
+ !policydb_class_isvalid(p, rtk->tclass) ||
+ !policydb_role_isvalid(p, rtd->new_role))
+ goto bad;
+
+ rc = hashtab_insert(&p->role_tr, rtk, rtd, roletr_key_params);
+ if (rc)
+ goto bad;
+
+ rtk = NULL;
+ rtd = NULL;
+ }
+
+ hash_eval(&p->role_tr, "roletr", NULL);
+
+ 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_obj(*ra);
+ 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 = -EINVAL;
+ perm = string_to_av_perm(p, p->process_class, "transition");
+ if (!perm) {
+ pr_err("SELinux: process transition permission is required, not defined in policy\n");
+ goto bad;
+ }
+ p->process_trans_perms = perm;
+ perm = string_to_av_perm(p, p->process_class, "dyntransition");
+ if (!perm) {
+ pr_err("SELinux: process dyntransition permission is required, not defined in policy\n");
+ goto bad;
+ }
+ p->process_trans_perms |= perm;
+
+ 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 = kvzalloc_objs(*p->type_attr_map_array,
+ p->p_types.nprim);
+ if (!p->type_attr_map_array)
+ goto bad;
+
+ /* just in case ebitmap_init() becomes more than just a memset(0): */
+ for (i = 0; i < p->p_types.nprim; i++)
+ ebitmap_init(&p->type_attr_map_array[i]);
+
+ for (i = 0; i < p->p_types.nprim; i++) {
+ struct ebitmap *e = &p->type_attr_map_array[i];
+
+ if (p->policyvers >= POLICYDB_VERSION_AVTAB) {
+ rc = ebitmap_read(e, fp);
+ if (rc)
+ goto bad;
+ }
+
+ rc = -EINVAL;
+ if (ebitmap_get_highest_set_bit(e) >= p->p_types.nprim)
+ 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:
+ kfree(rtk);
+ kfree(rtd);
+ 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, struct policy_file *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, struct policy_file *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;
+ struct policy_file *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;
+ struct policy_file *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_one(void *key, void *datum, void *ptr)
+{
+ struct role_trans_key *rtk = key;
+ struct role_trans_datum *rtd = datum;
+ struct policy_data *pd = ptr;
+ struct policy_file *fp = pd->fp;
+ struct policydb *p = pd->p;
+ __le32 buf[3];
+ int rc;
+
+ buf[0] = cpu_to_le32(rtk->role);
+ buf[1] = cpu_to_le32(rtk->type);
+ buf[2] = cpu_to_le32(rtd->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(rtk->tclass);
+ rc = put_entry(buf, sizeof(u32), 1, fp);
+ if (rc)
+ return rc;
+ }
+ return 0;
+}
+
+static int role_trans_write(struct policydb *p, struct policy_file *fp)
+{
+ struct policy_data pd = { .p = p, .fp = fp };
+ __le32 buf[1];
+ int rc;
+
+ buf[0] = cpu_to_le32(p->role_tr.nel);
+ rc = put_entry(buf, sizeof(u32), 1, fp);
+ if (rc)
+ return rc;
+
+ return hashtab_map(&p->role_tr, role_trans_write_one, &pd);
+}
+
+static int role_allow_write(struct role_allow *r, struct policy_file *fp)
+{
+ struct role_allow *ra;
+ __le32 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, struct policy_file *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;
+ struct policy_file *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, struct policy_file *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,
+ struct policy_file *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;
+ struct policy_file *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);
+ buf[4] = cpu_to_le32(cladatum->permissions.table.nel);
+ 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;
+ struct policy_file *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;
+ struct policy_file *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;
+ struct policy_file *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;
+}
+
+/* clang-format off */
+static int (*const 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,
+};
+/* clang-format on */
+
+static int ocontext_write(struct policydb *p,
+ const struct policydb_compat_info *info,
+ struct policy_file *fp)
+{
+ unsigned int i, j;
+ int 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, struct policy_file *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 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;
+ struct policy_file *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, struct policy_file *fp)
+{
+ __le32 buf[1];
+ int rc;
+ struct policy_data pd;
+
+ pd.p = p;
+ pd.fp = fp;
+
+ buf[0] = cpu_to_le32(p->range_tr.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_compat(void *key, void *data, void *ptr)
+{
+ struct filename_trans_key *ft = key;
+ struct filename_trans_datum *datum = data;
+ struct ebitmap_node *node;
+ struct policy_file *fp = ptr;
+ __le32 buf[4];
+ int rc;
+ u32 bit, len = strlen(ft->name);
+
+ do {
+ ebitmap_for_each_positive_bit(&datum->stypes, node, bit)
+ {
+ 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(bit + 1);
+ buf[1] = cpu_to_le32(ft->ttype);
+ buf[2] = cpu_to_le32(ft->tclass);
+ buf[3] = cpu_to_le32(datum->otype);
+
+ rc = put_entry(buf, sizeof(u32), 4, fp);
+ if (rc)
+ return rc;
+ }
+
+ datum = datum->next;
+ } while (unlikely(datum));
+
+ return 0;
+}
+
+static int filename_write_helper(void *key, void *data, void *ptr)
+{
+ struct filename_trans_key *ft = key;
+ struct filename_trans_datum *datum;
+ struct policy_file *fp = ptr;
+ __le32 buf[3];
+ int rc;
+ u32 ndatum, 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;
+
+ ndatum = 0;
+ datum = data;
+ do {
+ ndatum++;
+ datum = datum->next;
+ } while (unlikely(datum));
+
+ buf[0] = cpu_to_le32(ft->ttype);
+ buf[1] = cpu_to_le32(ft->tclass);
+ buf[2] = cpu_to_le32(ndatum);
+ rc = put_entry(buf, sizeof(u32), 3, fp);
+ if (rc)
+ return rc;
+
+ datum = data;
+ do {
+ rc = ebitmap_write(&datum->stypes, fp);
+ if (rc)
+ return rc;
+
+ buf[0] = cpu_to_le32(datum->otype);
+ rc = put_entry(buf, sizeof(u32), 1, fp);
+ if (rc)
+ return rc;
+
+ datum = datum->next;
+ } while (unlikely(datum));
+
+ return 0;
+}
+
+static int filename_trans_write(struct policydb *p, struct policy_file *fp)
+{
+ __le32 buf[1];
+ int rc;
+
+ if (p->policyvers < POLICYDB_VERSION_FILENAME_TRANS)
+ return 0;
+
+ if (p->policyvers < POLICYDB_VERSION_COMP_FTRANS) {
+ buf[0] = cpu_to_le32(p->compat_filename_trans_count);
+ rc = put_entry(buf, sizeof(u32), 1, fp);
+ if (rc)
+ return rc;
+
+ rc = hashtab_map(&p->filename_trans,
+ filename_write_helper_compat, fp);
+ } else {
+ buf[0] = cpu_to_le32(p->filename_trans.nel);
+ rc = put_entry(buf, sizeof(u32), 1, fp);
+ if (rc)
+ return rc;
+
+ rc = hashtab_map(&p->filename_trans, filename_write_helper, fp);
+ }
+ return rc;
+}
+
+/*
+ * Write the configuration data in a policy database
+ * structure to a policy database binary representation
+ * file.
+ */
+int policydb_write(struct policydb *p, struct policy_file *fp)
+{
+ unsigned int num_syms;
+ int rc;
+ __le32 buf[4];
+ u32 config, i;
+ size_t len;
+ const 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\n",
+ 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;
+ }
+
+ if (p->policyvers >= POLICYDB_VERSION_NEVERAUDIT) {
+ rc = ebitmap_write(&p->neveraudit_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, 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 = &p->type_attr_map_array[i];
+
+ 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..974180b2e
--- /dev/null
+++ b/security/selinux/ss/policydb.h
@@ -0,0 +1,428 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * A policy database (policydb) specifies the
+ * configuration data for the security policy.
+ *
+ * Author : Stephen Smalley, <stephen.smalley.work@gmail.com>
+ */
+
+/*
+ * Updated: Trusted Computer Solutions, Inc. <dgoeddel@trustedcs.com>
+ * Support for enhanced MLS infrastructure.
+ * Copyright (C) 2004-2005 Trusted Computer Solutions, Inc.
+ *
+ * Updated: Frank Mayer <mayerf@tresys.com> and
+ * Karl MacMillan <kmacmillan@tresys.com>
+ * Added conditional policy language extensions
+ * Copyright (C) 2003-2004 Tresys Technology, LLC
+ */
+
+#ifndef _SS_POLICYDB_H_
+#define _SS_POLICYDB_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 {
+ u16 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 perms */
+ 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
+#define DEFAULT_GLBLUB 7
+ char default_range;
+};
+
+/* Role attributes */
+struct role_datum {
+ u32 value; /* internal role value */
+ u32 bounds; /* boundary of role, 0 for none */
+ struct ebitmap dominates; /* set of roles dominated by this role */
+ struct ebitmap types; /* set of authorized types for role */
+};
+
+struct role_trans_key {
+ u32 role; /* current role */
+ u32 type; /* program executable type, or new object type */
+ u16 tclass; /* process class, or new object class */
+};
+
+struct role_trans_datum {
+ u32 new_role; /* new role */
+};
+
+struct filename_trans_key {
+ u32 ttype; /* parent dir context */
+ u16 tclass; /* class of new object */
+ const char *name; /* last path component */
+};
+
+struct filename_trans_datum {
+ struct ebitmap stypes; /* bitmap of source types for this otype */
+ u32 otype; /* resulting type of new object */
+ struct filename_trans_datum *next; /* record for next otype*/
+};
+
+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, 0 for none */
+ /* internally unused, only forwarded via policydb_write() */
+ unsigned char primary; /* primary name? */
+ unsigned char attribute; /* attribute ?*/
+};
+
+/* User attributes */
+struct user_datum {
+ u32 value; /* internal user value */
+ u32 bounds; /* bounds of user, 0 for none */
+ 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;
+ u16 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 {
+ u16 sclass; /* security class for genfs (can be 0 for wildcard) */
+ 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 (deprecated) */
+#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) */
+ char **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 type_datum **type_val_to_struct;
+
+ /* type enforcement access vectors and transitions */
+ struct avtab te_avtab;
+
+ /* role transitions */
+ struct hashtab 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;
+ /* only used if policyvers < POLICYDB_VERSION_COMP_FTRANS */
+ u32 compat_filename_trans_count;
+
+ /* 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;
+ /* array indexing te_cond_avtab by conditional */
+ struct cond_node *cond_list;
+ u32 cond_list_len;
+
+ /* 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 ebitmap *type_attr_map_array;
+
+ struct ebitmap policycaps;
+
+ struct ebitmap permissive_map;
+
+ struct ebitmap neveraudit_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;
+} __randomize_layout;
+
+struct policy_file {
+ char *data;
+ size_t len;
+};
+
+extern void policydb_destroy(struct policydb *p);
+extern int policydb_load_isids(struct policydb *p, struct sidtab *s);
+extern bool policydb_context_isvalid(const struct policydb *p,
+ const struct context *c);
+extern bool policydb_class_isvalid(const struct policydb *p, u16 class);
+extern bool policydb_type_isvalid(const struct policydb *p, u32 type);
+extern bool policydb_simpletype_isvalid(const struct policydb *p, u32 type);
+extern bool policydb_role_isvalid(const struct policydb *p, u32 role);
+extern bool policydb_user_isvalid(const struct policydb *p, u32 user);
+extern int policydb_read(struct policydb *p, struct policy_file *fp);
+extern int policydb_write(struct policydb *p, struct policy_file *fp);
+
+extern struct filename_trans_datum *
+policydb_filenametr_search(struct policydb *p, struct filename_trans_key *key);
+
+extern struct mls_range *policydb_rangetr_search(struct policydb *p,
+ struct range_trans *key);
+
+extern struct role_trans_datum *
+policydb_roletr_search(struct policydb *p, struct role_trans_key *key);
+
+#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_data {
+ struct policydb *p;
+ struct policy_file *fp;
+};
+
+static inline int size_check(size_t bytes, size_t num,
+ const struct policy_file *fp)
+{
+ size_t len;
+
+ if (unlikely(check_mul_overflow(bytes, num, &len)))
+ return -EINVAL;
+
+ if (unlikely(len > fp->len))
+ return -EINVAL;
+
+ return 0;
+}
+
+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, size_t num,
+ struct policy_file *fp)
+{
+ size_t len;
+
+ if (unlikely(check_mul_overflow(bytes, num, &len)))
+ return -EINVAL;
+
+ if (len > fp->len)
+ return -EINVAL;
+ memcpy(fp->data, buf, len);
+ fp->data += len;
+ fp->len -= len;
+
+ return 0;
+}
+
+static inline const char *sym_name(const struct policydb *p, unsigned int sym_num,
+ unsigned int element_nr)
+{
+ return p->sym_val_to_name[sym_num][element_nr];
+}
+
+static inline bool val_is_boolean(u32 value)
+{
+ return value == 0 || value == 1;
+}
+
+extern int str_read(char **strp, gfp_t flags, struct policy_file *fp, u32 len);
+
+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);
+
+#define pr_warn_once_policyload(policy, fmt, ...) \
+ do { \
+ static const void *prev_policy__; \
+ if (prev_policy__ != policy) { \
+ pr_warn(fmt, ##__VA_ARGS__); \
+ prev_policy__ = policy; \
+ } \
+ } while (0)
+
+#endif /* _SS_POLICYDB_H_ */
diff --git a/security/selinux/ss/services.c b/security/selinux/ss/services.c
new file mode 100644
index 000000000..da77ad769
--- /dev/null
+++ b/security/selinux/ss/services.c
@@ -0,0 +1,3962 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Implementation of the security services.
+ *
+ * Authors : Stephen Smalley, <stephen.smalley.work@gmail.com>
+ * 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>
+ */
+#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/parser.h>
+#include <linux/vmalloc.h>
+#include <linux/lsm_hooks.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"
+#include "policycap_names.h"
+#include "ima.h"
+
+struct selinux_policy_convert_data {
+ struct convert_context_args args;
+ struct sidtab_convert_params sidtab_params;
+};
+
+/* Forward declaration. */
+static int context_struct_to_string(struct policydb *policydb,
+ struct context *context,
+ char **scontext,
+ u32 *scontext_len);
+
+static int sidtab_entry_to_string(struct policydb *policydb,
+ struct sidtab *sidtab,
+ struct sidtab_entry *entry,
+ 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,
+ const struct security_class_mapping *map,
+ struct selinux_map *out_map)
+{
+ u16 i, j;
+ 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 = kzalloc_objs(*out_map->mapping, ++i, GFP_ATOMIC);
+ if (!out_map->mapping)
+ return -ENOMEM;
+
+ /* Store the raw class and permission values */
+ j = 0;
+ while (map[j].name) {
+ const struct security_class_mapping *p_in = map + (j++);
+ struct selinux_mapping *p_out = out_map->mapping + j;
+ u16 k;
+
+ /* 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 |= (u32)1<<i;
+ if (allow_unknown && !mapping->perms[i])
+ result |= (u32)1<<i;
+ }
+ avd->allowed = result;
+
+ for (i = 0, result = 0; i < n; i++)
+ if (avd->auditallow & mapping->perms[i])
+ result |= (u32)1<<i;
+ avd->auditallow = result;
+
+ for (i = 0, result = 0; i < n; i++) {
+ if (avd->auditdeny & mapping->perms[i])
+ result |= (u32)1<<i;
+ if (!allow_unknown && !mapping->perms[i])
+ result |= (u32)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 |= (u32)1<<i;
+ avd->auditdeny = result;
+ }
+}
+
+int security_mls_enabled(void)
+{
+ int mls_enabled;
+ struct selinux_policy *policy;
+
+ if (!selinux_initialized())
+ return 0;
+
+ rcu_read_lock();
+ policy = rcu_dereference(selinux_state.policy);
+ mls_enabled = policy->policydb.mls_enabled;
+ rcu_read_unlock();
+ return 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;
+
+ 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;
+ const char *tclass_name;
+ char *scontext_name = NULL;
+ char *tcontext_name = NULL;
+ char *permission_names[SEL_VEC_MAX];
+ 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 < SEL_VEC_MAX; 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);
+}
+
+/*
+ * 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 = policydb->type_val_to_struct[scontext->type - 1];
+ BUG_ON(!source);
+
+ if (!source->bounds)
+ return;
+
+ target = policydb->type_val_to_struct[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 and which base permissions are covered.
+ */
+void services_compute_xperms_drivers(
+ struct extended_perms *xperms,
+ struct avtab_node *node)
+{
+ unsigned int i;
+
+ switch (node->datum.u.xperms->specified) {
+ case AVTAB_XPERMS_IOCTLDRIVER:
+ xperms->base_perms |= AVC_EXT_IOCTL;
+ /* 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];
+ break;
+ case AVTAB_XPERMS_IOCTLFUNCTION:
+ xperms->base_perms |= AVC_EXT_IOCTL;
+ /* if allowing permissions within a driver */
+ security_xperm_set(xperms->drivers.p,
+ node->datum.u.xperms->driver);
+ break;
+ case AVTAB_XPERMS_NLMSG:
+ xperms->base_perms |= AVC_EXT_NLMSG;
+ /* if allowing permissions within a driver */
+ security_xperm_set(xperms->drivers.p,
+ node->datum.u.xperms->driver);
+ break;
+ }
+
+ 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, 0, sizeof(*xperms));
+ }
+
+ if (unlikely(!tclass || tclass > policydb->p_classes.nprim)) {
+ pr_warn_ratelimited("SELinux: Invalid class %u\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 = &policydb->type_attr_map_array[scontext->type - 1];
+ tattr = &policydb->type_attr_map_array[tcontext->type - 1];
+ 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.
+ *
+ * Infinite recursion is avoided via a depth pre-check in
+ * type_bounds_sanity_check().
+ */
+ type_attribute_bounds_av(policydb, scontext, tcontext,
+ tclass, avd);
+}
+
+static int security_validtrans_handle_fail(struct selinux_policy *policy,
+ struct sidtab_entry *oentry,
+ struct sidtab_entry *nentry,
+ struct sidtab_entry *tentry,
+ u16 tclass)
+{
+ struct policydb *p = &policy->policydb;
+ struct sidtab *sidtab = policy->sidtab;
+ char *o = NULL, *n = NULL, *t = NULL;
+ u32 olen, nlen, tlen;
+
+ if (sidtab_entry_to_string(p, sidtab, oentry, &o, &olen))
+ goto out;
+ if (sidtab_entry_to_string(p, sidtab, nentry, &n, &nlen))
+ goto out;
+ if (sidtab_entry_to_string(p, sidtab, tentry, &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())
+ return 0;
+ return -EPERM;
+}
+
+static int security_compute_validatetrans(u32 oldsid, u32 newsid, u32 tasksid,
+ u16 orig_tclass, bool user)
+{
+ struct selinux_policy *policy;
+ struct policydb *policydb;
+ struct sidtab *sidtab;
+ struct sidtab_entry *oentry;
+ struct sidtab_entry *nentry;
+ struct sidtab_entry *tentry;
+ struct class_datum *tclass_datum;
+ struct constraint_node *constraint;
+ u16 tclass;
+ int rc = 0;
+
+
+ if (!selinux_initialized())
+ return 0;
+
+ rcu_read_lock();
+
+ policy = rcu_dereference(selinux_state.policy);
+ policydb = &policy->policydb;
+ sidtab = policy->sidtab;
+
+ if (!user)
+ tclass = unmap_class(&policy->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];
+
+ oentry = sidtab_search_entry(sidtab, oldsid);
+ if (!oentry) {
+ pr_err("SELinux: %s: unrecognized SID %d\n",
+ __func__, oldsid);
+ rc = -EINVAL;
+ goto out;
+ }
+
+ nentry = sidtab_search_entry(sidtab, newsid);
+ if (!nentry) {
+ pr_err("SELinux: %s: unrecognized SID %d\n",
+ __func__, newsid);
+ rc = -EINVAL;
+ goto out;
+ }
+
+ tentry = sidtab_search_entry(sidtab, tasksid);
+ if (!tentry) {
+ 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, &oentry->context,
+ &nentry->context, &tentry->context,
+ constraint->expr)) {
+ if (user)
+ rc = -EPERM;
+ else
+ rc = security_validtrans_handle_fail(policy,
+ oentry,
+ nentry,
+ tentry,
+ tclass);
+ goto out;
+ }
+ constraint = constraint->next;
+ }
+
+out:
+ rcu_read_unlock();
+ return rc;
+}
+
+int security_validate_transition_user(u32 oldsid, u32 newsid, u32 tasksid,
+ u16 tclass)
+{
+ return security_compute_validatetrans(oldsid, newsid, tasksid,
+ tclass, true);
+}
+
+int security_validate_transition(u32 oldsid, u32 newsid, u32 tasksid,
+ u16 orig_tclass)
+{
+ return security_compute_validatetrans(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(u32 old_sid, u32 new_sid)
+{
+ struct selinux_policy *policy;
+ struct policydb *policydb;
+ struct sidtab *sidtab;
+ struct sidtab_entry *old_entry, *new_entry;
+ struct type_datum *type;
+ u32 index;
+ int rc;
+
+ if (!selinux_initialized())
+ return 0;
+
+ rcu_read_lock();
+ policy = rcu_dereference(selinux_state.policy);
+ policydb = &policy->policydb;
+ sidtab = policy->sidtab;
+
+ rc = -EINVAL;
+ old_entry = sidtab_search_entry(sidtab, old_sid);
+ if (!old_entry) {
+ pr_err("SELinux: %s: unrecognized SID %u\n",
+ __func__, old_sid);
+ goto out;
+ }
+
+ rc = -EINVAL;
+ new_entry = sidtab_search_entry(sidtab, new_sid);
+ if (!new_entry) {
+ pr_err("SELinux: %s: unrecognized SID %u\n",
+ __func__, new_sid);
+ goto out;
+ }
+
+ rc = 0;
+ /* type/domain unchanged */
+ if (old_entry->context.type == new_entry->context.type)
+ goto out;
+
+ index = new_entry->context.type;
+ while (true) {
+ type = policydb->type_val_to_struct[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_entry->context.type)
+ break;
+
+ index = type->bounds;
+ }
+
+ if (rc) {
+ char *old_name = NULL;
+ char *new_name = NULL;
+ u32 length;
+
+ if (!sidtab_entry_to_string(policydb, sidtab, old_entry,
+ &old_name, &length) &&
+ !sidtab_entry_to_string(policydb, sidtab, new_entry,
+ &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:
+ rcu_read_unlock();
+
+ return rc;
+}
+
+static void avd_init(struct selinux_policy *policy, struct av_decision *avd)
+{
+ avd->allowed = 0;
+ avd->auditallow = 0;
+ avd->auditdeny = 0xffffffff;
+ if (policy)
+ avd->seqno = policy->latest_granting;
+ else
+ avd->seqno = 0;
+ avd->flags = 0;
+}
+
+static void update_xperms_extended_data(u8 specified,
+ const struct extended_perms_data *from,
+ struct extended_perms_data *xp_data)
+{
+ unsigned int i;
+
+ switch (specified) {
+ case AVTAB_XPERMS_IOCTLDRIVER:
+ memset(xp_data->p, 0xff, sizeof(xp_data->p));
+ break;
+ case AVTAB_XPERMS_IOCTLFUNCTION:
+ case AVTAB_XPERMS_NLMSG:
+ for (i = 0; i < ARRAY_SIZE(xp_data->p); i++)
+ xp_data->p[i] |= from->p[i];
+ break;
+ }
+
+}
+
+void services_compute_xperms_decision(struct extended_perms_decision *xpermd,
+ struct avtab_node *node)
+{
+ u16 specified;
+
+ switch (node->datum.u.xperms->specified) {
+ case AVTAB_XPERMS_IOCTLFUNCTION:
+ if (xpermd->base_perm != AVC_EXT_IOCTL ||
+ xpermd->driver != node->datum.u.xperms->driver)
+ return;
+ break;
+ case AVTAB_XPERMS_IOCTLDRIVER:
+ if (xpermd->base_perm != AVC_EXT_IOCTL ||
+ !security_xperm_test(node->datum.u.xperms->perms.p,
+ xpermd->driver))
+ return;
+ break;
+ case AVTAB_XPERMS_NLMSG:
+ if (xpermd->base_perm != AVC_EXT_NLMSG ||
+ xpermd->driver != node->datum.u.xperms->driver)
+ return;
+ break;
+ default:
+ pr_warn_once(
+ "SELinux: unknown extended permission (%u) will be ignored\n",
+ node->datum.u.xperms->specified);
+ return;
+ }
+
+ specified = node->key.specified & ~(AVTAB_ENABLED | AVTAB_ENABLED_OLD);
+
+ if (specified == AVTAB_XPERMS_ALLOWED) {
+ xpermd->used |= XPERMS_ALLOWED;
+ update_xperms_extended_data(node->datum.u.xperms->specified,
+ &node->datum.u.xperms->perms,
+ xpermd->allowed);
+ } else if (specified == AVTAB_XPERMS_AUDITALLOW) {
+ xpermd->used |= XPERMS_AUDITALLOW;
+ update_xperms_extended_data(node->datum.u.xperms->specified,
+ &node->datum.u.xperms->perms,
+ xpermd->auditallow);
+ } else if (specified == AVTAB_XPERMS_DONTAUDIT) {
+ xpermd->used |= XPERMS_DONTAUDIT;
+ update_xperms_extended_data(node->datum.u.xperms->specified,
+ &node->datum.u.xperms->perms,
+ xpermd->dontaudit);
+ } else {
+ pr_warn_once("SELinux: unknown specified key (%u)\n",
+ node->key.specified);
+ }
+}
+
+void security_compute_xperms_decision(u32 ssid,
+ u32 tsid,
+ u16 orig_tclass,
+ u8 driver,
+ u8 base_perm,
+ struct extended_perms_decision *xpermd)
+{
+ struct selinux_policy *policy;
+ 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->base_perm = base_perm;
+ 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));
+
+ rcu_read_lock();
+ if (!selinux_initialized())
+ goto allow;
+
+ policy = rcu_dereference(selinux_state.policy);
+ policydb = &policy->policydb;
+ sidtab = policy->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(&policy->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 = &policydb->type_attr_map_array[scontext->type - 1];
+ tattr = &policydb->type_attr_map_array[tcontext->type - 1];
+ 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:
+ rcu_read_unlock();
+ 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
+ * @orig_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(u32 ssid,
+ u32 tsid,
+ u16 orig_tclass,
+ struct av_decision *avd,
+ struct extended_perms *xperms)
+{
+ struct selinux_policy *policy;
+ struct policydb *policydb;
+ struct sidtab *sidtab;
+ u16 tclass;
+ struct context *scontext = NULL, *tcontext = NULL;
+
+ rcu_read_lock();
+ policy = rcu_dereference(selinux_state.policy);
+ avd_init(policy, avd);
+ xperms->len = 0;
+ if (!selinux_initialized())
+ goto allow;
+
+ policydb = &policy->policydb;
+ sidtab = policy->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;
+
+ /* neveraudit domain? */
+ if (ebitmap_get_bit(&policydb->neveraudit_map, scontext->type))
+ avd->flags |= AVD_FLAGS_NEVERAUDIT;
+
+ /* both permissive and neveraudit => allow */
+ if (avd->flags == (AVD_FLAGS_PERMISSIVE|AVD_FLAGS_NEVERAUDIT))
+ goto allow;
+
+ tcontext = sidtab_search(sidtab, tsid);
+ if (!tcontext) {
+ pr_err("SELinux: %s: unrecognized SID %d\n",
+ __func__, tsid);
+ goto out;
+ }
+
+ tclass = unmap_class(&policy->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(&policy->map, orig_tclass, avd,
+ policydb->allow_unknown);
+out:
+ rcu_read_unlock();
+ if (avd->flags & AVD_FLAGS_NEVERAUDIT)
+ avd->auditallow = avd->auditdeny = 0;
+ return;
+allow:
+ avd->allowed = 0xffffffff;
+ goto out;
+}
+
+void security_compute_av_user(u32 ssid,
+ u32 tsid,
+ u16 tclass,
+ struct av_decision *avd)
+{
+ struct selinux_policy *policy;
+ struct policydb *policydb;
+ struct sidtab *sidtab;
+ struct context *scontext = NULL, *tcontext = NULL;
+
+ rcu_read_lock();
+ policy = rcu_dereference(selinux_state.policy);
+ avd_init(policy, avd);
+ if (!selinux_initialized())
+ goto allow;
+
+ policydb = &policy->policydb;
+ sidtab = policy->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;
+
+ /* neveraudit domain? */
+ if (ebitmap_get_bit(&policydb->neveraudit_map, scontext->type))
+ avd->flags |= AVD_FLAGS_NEVERAUDIT;
+
+ /* both permissive and neveraudit => allow */
+ if (avd->flags == (AVD_FLAGS_PERMISSIVE|AVD_FLAGS_NEVERAUDIT))
+ goto allow;
+
+ 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:
+ rcu_read_unlock();
+ if (avd->flags & AVD_FLAGS_NEVERAUDIT)
+ avd->auditallow = avd->auditdeny = 0;
+ 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;
+}
+
+static int sidtab_entry_to_string(struct policydb *p,
+ struct sidtab *sidtab,
+ struct sidtab_entry *entry,
+ char **scontext, u32 *scontext_len)
+{
+ int rc = sidtab_sid2str_get(sidtab, entry, scontext, scontext_len);
+
+ if (rc != -ENOENT)
+ return rc;
+
+ rc = context_struct_to_string(p, &entry->context, scontext,
+ scontext_len);
+ if (!rc && scontext)
+ sidtab_sid2str_put(sidtab, entry, *scontext, *scontext_len);
+ return rc;
+}
+
+#include "initial_sid_to_string.h"
+
+int security_sidtab_hash_stats(char *page)
+{
+ struct selinux_policy *policy;
+ int rc;
+
+ if (!selinux_initialized()) {
+ pr_err("SELinux: %s: called before initial load_policy\n",
+ __func__);
+ return -EINVAL;
+ }
+
+ rcu_read_lock();
+ policy = rcu_dereference(selinux_state.policy);
+ rc = sidtab_hash_stats(policy->sidtab, page);
+ rcu_read_unlock();
+
+ return rc;
+}
+
+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(u32 sid, char **scontext,
+ u32 *scontext_len, bool force,
+ bool only_invalid)
+{
+ struct selinux_policy *policy;
+ struct policydb *policydb;
+ struct sidtab *sidtab;
+ struct sidtab_entry *entry;
+ int rc = 0;
+
+ if (scontext)
+ *scontext = NULL;
+ *scontext_len = 0;
+
+ if (!selinux_initialized()) {
+ if (sid <= SECINITSID_NUM) {
+ char *scontextp;
+ const char *s;
+
+ /*
+ * Before the policy is loaded, translate
+ * SECINITSID_INIT to "kernel", because systemd and
+ * libselinux < 2.6 take a getcon_raw() result that is
+ * both non-null and not "kernel" to mean that a policy
+ * is already loaded.
+ */
+ if (sid == SECINITSID_INIT)
+ sid = SECINITSID_KERNEL;
+
+ s = initial_sid_to_string[sid];
+ if (!s)
+ return -EINVAL;
+ *scontext_len = strlen(s) + 1;
+ if (!scontext)
+ return 0;
+ scontextp = kmemdup(s, *scontext_len, GFP_ATOMIC);
+ if (!scontextp)
+ return -ENOMEM;
+ *scontext = scontextp;
+ return 0;
+ }
+ pr_err("SELinux: %s: called before initial "
+ "load_policy on unknown SID %d\n", __func__, sid);
+ return -EINVAL;
+ }
+ rcu_read_lock();
+ policy = rcu_dereference(selinux_state.policy);
+ policydb = &policy->policydb;
+ sidtab = policy->sidtab;
+
+ if (force)
+ entry = sidtab_search_entry_force(sidtab, sid);
+ else
+ entry = sidtab_search_entry(sidtab, sid);
+ if (!entry) {
+ pr_err("SELinux: %s: unrecognized SID %d\n",
+ __func__, sid);
+ rc = -EINVAL;
+ goto out_unlock;
+ }
+ if (only_invalid && !entry->context.len)
+ goto out_unlock;
+
+ rc = sidtab_entry_to_string(policydb, sidtab, entry, scontext,
+ scontext_len);
+
+out_unlock:
+ rcu_read_unlock();
+ 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(u32 sid, char **scontext, u32 *scontext_len)
+{
+ return security_sid_to_context_core(sid, scontext,
+ scontext_len, false, false);
+}
+
+int security_sid_to_context_force(u32 sid,
+ char **scontext, u32 *scontext_len)
+{
+ return security_sid_to_context_core(sid, scontext,
+ scontext_len, true, false);
+}
+
+/**
+ * security_sid_to_context_inval - Obtain a context for a given SID if it
+ * is invalid.
+ * @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, but only if the
+ * context is invalid in the current policy. Set @scontext to point to
+ * this string (or NULL if the context is valid) and set @scontext_len to
+ * the length of the string (or 0 if the context is valid).
+ */
+int security_sid_to_context_inval(u32 sid,
+ char **scontext, u32 *scontext_len)
+{
+ return security_sid_to_context_core(sid, scontext,
+ scontext_len, true, true);
+}
+
+/*
+ * Caveat: Mutates scontext.
+ */
+static int string_to_context_struct(struct policydb *pol,
+ struct sidtab *sidtabp,
+ char *scontext,
+ 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 = scontext;
+
+ /* Extract the user. */
+ p = scontextp;
+ while (*p && *p != ':')
+ p++;
+
+ if (*p == 0)
+ goto out;
+
+ *p++ = 0;
+
+ usrdatum = symtab_search(&pol->p_users, 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 = symtab_search(&pol->p_roles, scontextp);
+ if (!role)
+ goto out;
+ ctx->role = role->value;
+
+ /* Extract type. */
+ scontextp = p;
+ while (*p && *p != ':')
+ p++;
+ oldc = *p;
+ *p++ = 0;
+
+ typdatum = symtab_search(&pol->p_types, 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;
+
+ /* Check the validity of the new context. */
+ rc = -EINVAL;
+ 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(const char *scontext, u32 scontext_len,
+ u32 *sid, u32 def_sid, gfp_t gfp_flags,
+ bool force)
+{
+ struct selinux_policy *policy;
+ 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 (!selinux_initialized()) {
+ u32 i;
+
+ for (i = 1; i < SECINITSID_NUM; i++) {
+ const char *s = initial_sid_to_string[i];
+
+ if (s && !strcmp(s, 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;
+ }
+retry:
+ rcu_read_lock();
+ policy = rcu_dereference(selinux_state.policy);
+ policydb = &policy->policydb;
+ sidtab = policy->sidtab;
+ rc = string_to_context_struct(policydb, sidtab, scontext2,
+ &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);
+ if (rc == -ESTALE) {
+ rcu_read_unlock();
+ if (context.str) {
+ str = context.str;
+ context.str = NULL;
+ }
+ context_destroy(&context);
+ goto retry;
+ }
+ context_destroy(&context);
+out_unlock:
+ rcu_read_unlock();
+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(const char *scontext, u32 scontext_len, u32 *sid,
+ gfp_t gfp)
+{
+ return security_context_to_sid_core(scontext, scontext_len,
+ sid, SECSID_NULL, gfp, false);
+}
+
+int security_context_str_to_sid(const char *scontext, u32 *sid, gfp_t gfp)
+{
+ return security_context_to_sid(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
+ * @gfp_flags: the allocator get-free-page (GFP) flags
+ *
+ * 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(const char *scontext, u32 scontext_len,
+ u32 *sid, u32 def_sid, gfp_t gfp_flags)
+{
+ return security_context_to_sid_core(scontext, scontext_len,
+ sid, def_sid, gfp_flags, true);
+}
+
+int security_context_to_sid_force(const char *scontext, u32 scontext_len,
+ u32 *sid)
+{
+ return security_context_to_sid_core(scontext, scontext_len,
+ sid, SECSID_NULL, GFP_KERNEL, true);
+}
+
+static int compute_sid_handle_invalid_context(
+ struct selinux_policy *policy,
+ struct sidtab_entry *sentry,
+ struct sidtab_entry *tentry,
+ u16 tclass,
+ struct context *newcontext)
+{
+ struct policydb *policydb = &policy->policydb;
+ struct sidtab *sidtab = policy->sidtab;
+ char *s = NULL, *t = NULL, *n = NULL;
+ u32 slen, tlen, nlen;
+ struct audit_buffer *ab;
+
+ if (sidtab_entry_to_string(policydb, sidtab, sentry, &s, &slen))
+ goto out;
+ if (sidtab_entry_to_string(policydb, sidtab, tentry, &t, &tlen))
+ goto out;
+ if (context_struct_to_string(policydb, newcontext, &n, &nlen))
+ goto out;
+ ab = audit_log_start(audit_context(), GFP_ATOMIC, AUDIT_SELINUX_ERR);
+ if (!ab)
+ goto out;
+ audit_log_format(ab,
+ "op=security_compute_sid invalid_context=");
+ /* no need to record the NUL with untrusted strings */
+ audit_log_n_untrustedstring(ab, n, nlen - 1);
+ audit_log_format(ab, " scontext=%s tcontext=%s tclass=%s",
+ s, t, sym_name(policydb, SYM_CLASSES, tclass-1));
+ audit_log_end(ab);
+out:
+ kfree(s);
+ kfree(t);
+ kfree(n);
+ if (!enforcing_enabled())
+ 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_key ft;
+ struct filename_trans_datum *datum;
+
+ /*
+ * 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.ttype = ttype;
+ ft.tclass = tclass;
+ ft.name = objname;
+
+ datum = policydb_filenametr_search(policydb, &ft);
+ while (datum) {
+ if (ebitmap_get_bit(&datum->stypes, stype - 1)) {
+ newcontext->type = datum->otype;
+ return;
+ }
+ datum = datum->next;
+ }
+}
+
+static int security_compute_sid(u32 ssid,
+ u32 tsid,
+ u16 orig_tclass,
+ u16 specified,
+ const char *objname,
+ u32 *out_sid,
+ bool kern)
+{
+ struct selinux_policy *policy;
+ struct policydb *policydb;
+ struct sidtab *sidtab;
+ struct class_datum *cladatum;
+ struct context *scontext, *tcontext, newcontext;
+ struct sidtab_entry *sentry, *tentry;
+ struct avtab_key avkey;
+ struct avtab_node *avnode, *node;
+ u16 tclass;
+ int rc = 0;
+ bool sock;
+
+ if (!selinux_initialized()) {
+ switch (orig_tclass) {
+ case SECCLASS_PROCESS: /* kernel value */
+ *out_sid = ssid;
+ break;
+ default:
+ *out_sid = tsid;
+ break;
+ }
+ goto out;
+ }
+
+retry:
+ cladatum = NULL;
+ context_init(&newcontext);
+
+ rcu_read_lock();
+
+ policy = rcu_dereference(selinux_state.policy);
+
+ if (kern) {
+ tclass = unmap_class(&policy->map, orig_tclass);
+ sock = security_is_socket_class(orig_tclass);
+ } else {
+ tclass = orig_tclass;
+ sock = security_is_socket_class(map_class(&policy->map,
+ tclass));
+ }
+
+ policydb = &policy->policydb;
+ sidtab = policy->sidtab;
+
+ sentry = sidtab_search_entry(sidtab, ssid);
+ if (!sentry) {
+ pr_err("SELinux: %s: unrecognized SID %d\n",
+ __func__, ssid);
+ rc = -EINVAL;
+ goto out_unlock;
+ }
+ tentry = sidtab_search_entry(sidtab, tsid);
+ if (!tentry) {
+ pr_err("SELinux: %s: unrecognized SID %d\n",
+ __func__, tsid);
+ rc = -EINVAL;
+ goto out_unlock;
+ }
+
+ scontext = &sentry->context;
+ tcontext = &tentry->context;
+
+ 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)
+ newcontext.role = scontext->role;
+ else
+ newcontext.role = OBJECT_R_VAL;
+ }
+
+ /* Set the 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;
+ avnode = avtab_search_node(&policydb->te_avtab, &avkey);
+
+ /* If no permanent rule, also check for enabled conditional rules */
+ if (!avnode) {
+ node = avtab_search_node(&policydb->te_cond_avtab, &avkey);
+ for (; node; node = avtab_search_node_next(node, specified)) {
+ if (node->key.specified & AVTAB_ENABLED) {
+ avnode = node;
+ break;
+ }
+ }
+ }
+
+ /* If a permanent rule is found, use the type from
+ * the type transition/member/change rule. Otherwise,
+ * set the type to its default values.
+ */
+ if (avnode) {
+ newcontext.type = avnode->datum.u.data;
+ } else 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) {
+ /* Use the type of process. */
+ newcontext.type = scontext->type;
+ } else {
+ /* Use the type of the related object. */
+ newcontext.type = tcontext->type;
+ }
+ }
+
+ /* 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. */
+ struct role_trans_datum *rtd;
+ struct role_trans_key rtk = {
+ .role = scontext->role,
+ .type = tcontext->type,
+ .tclass = tclass,
+ };
+
+ rtd = policydb_roletr_search(policydb, &rtk);
+ if (rtd)
+ newcontext.role = rtd->new_role;
+ }
+
+ /* 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(policy, sentry,
+ tentry, tclass,
+ &newcontext);
+ if (rc)
+ goto out_unlock;
+ }
+ /* Obtain the sid for the context. */
+ if (context_equal(scontext, &newcontext))
+ *out_sid = ssid;
+ else if (context_equal(tcontext, &newcontext))
+ *out_sid = tsid;
+ else {
+ rc = sidtab_context_to_sid(sidtab, &newcontext, out_sid);
+ if (rc == -ESTALE) {
+ rcu_read_unlock();
+ context_destroy(&newcontext);
+ goto retry;
+ }
+ }
+out_unlock:
+ rcu_read_unlock();
+ 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
+ * @qstr: object name
+ * @out_sid: security identifier for new subject/object
+ *
+ * Compute a SID to use for labeling a new subject or object in the
+ * class @tclass based on a SID pair (@ssid, @tsid).
+ * Return -%EINVAL if any of the parameters are invalid, -%ENOMEM
+ * if insufficient memory is available, or %0 if the new SID was
+ * computed successfully.
+ */
+int security_transition_sid(u32 ssid, u32 tsid, u16 tclass,
+ const struct qstr *qstr, u32 *out_sid)
+{
+ return security_compute_sid(ssid, tsid, tclass,
+ AVTAB_TRANSITION,
+ qstr ? qstr->name : NULL, out_sid, true);
+}
+
+int security_transition_sid_user(u32 ssid, u32 tsid, u16 tclass,
+ const char *objname, u32 *out_sid)
+{
+ return security_compute_sid(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(u32 ssid,
+ u32 tsid,
+ u16 tclass,
+ u32 *out_sid)
+{
+ return security_compute_sid(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(u32 ssid,
+ u32 tsid,
+ u16 tclass,
+ u32 *out_sid)
+{
+ return security_compute_sid(ssid, tsid, tclass, AVTAB_CHANGE, NULL,
+ out_sid, false);
+}
+
+static inline int convert_context_handle_invalid_context(
+ struct policydb *policydb,
+ struct context *context)
+{
+ char *s;
+ u32 len;
+
+ if (enforcing_enabled())
+ 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;
+}
+
+/**
+ * services_convert_context - Convert a security context across policies.
+ * @args: populated convert_context_args struct
+ * @oldc: original context
+ * @newc: converted context
+ * @gfp_flags: allocation flags
+ *
+ * Convert the values in the security context structure @oldc from the values
+ * specified in the policy @args->oldp to the values specified in the policy
+ * @args->newp, storing the new context in @newc, and verifying that the
+ * context is valid under the new policy.
+ */
+int services_convert_context(struct convert_context_args *args,
+ struct context *oldc, struct context *newc,
+ gfp_t gfp_flags)
+{
+ struct ocontext *oc;
+ struct role_datum *role;
+ struct type_datum *typdatum;
+ struct user_datum *usrdatum;
+ char *s;
+ u32 len;
+ int rc;
+
+ if (oldc->str) {
+ s = kstrdup(oldc->str, gfp_flags);
+ if (!s)
+ return -ENOMEM;
+
+ rc = string_to_context_struct(args->newp, NULL, s, newc, SECSID_NULL);
+ if (rc == -EINVAL) {
+ /*
+ * Retain string representation for later mapping.
+ *
+ * IMPORTANT: We need to copy the contents of oldc->str
+ * back into s again because string_to_context_struct()
+ * may have garbled it.
+ */
+ memcpy(s, oldc->str, oldc->len);
+ context_init(newc);
+ newc->str = s;
+ newc->len = oldc->len;
+ return 0;
+ }
+ kfree(s);
+ if (rc) {
+ /* Other error condition, e.g. ENOMEM. */
+ pr_err("SELinux: Unable to map context %s, rc = %d.\n",
+ oldc->str, -rc);
+ return rc;
+ }
+ pr_info("SELinux: Context %s became valid (mapped).\n",
+ oldc->str);
+ return 0;
+ }
+
+ context_init(newc);
+
+ /* Convert the user. */
+ usrdatum = symtab_search(&args->newp->p_users,
+ sym_name(args->oldp, SYM_USERS, oldc->user - 1));
+ if (!usrdatum)
+ goto bad;
+ newc->user = usrdatum->value;
+
+ /* Convert the role. */
+ role = symtab_search(&args->newp->p_roles,
+ sym_name(args->oldp, SYM_ROLES, oldc->role - 1));
+ if (!role)
+ goto bad;
+ newc->role = role->value;
+
+ /* Convert the type. */
+ typdatum = symtab_search(&args->newp->p_types,
+ sym_name(args->oldp, SYM_TYPES, oldc->type - 1));
+ if (!typdatum)
+ goto bad;
+ newc->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, oldc, newc);
+ if (rc)
+ goto bad;
+ } 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;
+ if (!oc) {
+ pr_err("SELinux: unable to look up"
+ " the initial SIDs list\n");
+ goto bad;
+ }
+ rc = mls_range_set(newc, &oc->context[0].range);
+ if (rc)
+ goto bad;
+ }
+
+ /* Check the validity of the new context. */
+ if (!policydb_context_isvalid(args->newp, newc)) {
+ rc = convert_context_handle_invalid_context(args->oldp, oldc);
+ if (rc)
+ goto bad;
+ }
+
+ return 0;
+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(newc);
+ newc->str = s;
+ newc->len = len;
+ pr_info("SELinux: Context %s became invalid (unmapped).\n",
+ newc->str);
+ return 0;
+}
+
+static void security_load_policycaps(struct selinux_policy *policy)
+{
+ struct policydb *p;
+ unsigned int i;
+ struct ebitmap_node *node;
+
+ p = &policy->policydb;
+
+ for (i = 0; i < ARRAY_SIZE(selinux_state.policycap); i++)
+ WRITE_ONCE(selinux_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_policy *oldpolicy,
+ struct selinux_policy *newpolicy);
+
+static void selinux_policy_free(struct selinux_policy *policy)
+{
+ if (!policy)
+ return;
+
+ sidtab_destroy(policy->sidtab);
+ kfree(policy->map.mapping);
+ policydb_destroy(&policy->policydb);
+ kfree(policy->sidtab);
+ kfree(policy);
+}
+
+static void selinux_policy_cond_free(struct selinux_policy *policy)
+{
+ cond_policydb_destroy_dup(&policy->policydb);
+ kfree(policy);
+}
+
+void selinux_policy_cancel(struct selinux_load_state *load_state)
+{
+ struct selinux_state *state = &selinux_state;
+ struct selinux_policy *oldpolicy;
+
+ oldpolicy = rcu_dereference_protected(state->policy,
+ lockdep_is_held(&state->policy_mutex));
+
+ /* a first load has no outgoing policy and converted nothing */
+ if (oldpolicy)
+ sidtab_cancel_convert(oldpolicy->sidtab);
+ selinux_policy_free(load_state->policy);
+ kfree(load_state->convert_data);
+}
+
+static void selinux_notify_policy_change(u32 seqno)
+{
+ /* Flush external caches and notify userspace of policy load */
+ avc_ss_reset(seqno);
+ selnl_notify_policyload(seqno);
+ selinux_status_update_policyload(seqno);
+ selinux_netlbl_cache_invalidate();
+ selinux_xfrm_notify_policyload();
+ selinux_ima_measure_state_locked();
+}
+
+void selinux_policy_commit(struct selinux_load_state *load_state)
+{
+ struct selinux_state *state = &selinux_state;
+ struct selinux_policy *oldpolicy, *newpolicy = load_state->policy;
+ unsigned long flags;
+ u32 seqno;
+
+ oldpolicy = rcu_dereference_protected(state->policy,
+ lockdep_is_held(&state->policy_mutex));
+
+ /* If switching between different policy types, log MLS status */
+ if (oldpolicy) {
+ if (oldpolicy->policydb.mls_enabled && !newpolicy->policydb.mls_enabled)
+ pr_info("SELinux: Disabling MLS support...\n");
+ else if (!oldpolicy->policydb.mls_enabled && newpolicy->policydb.mls_enabled)
+ pr_info("SELinux: Enabling MLS support...\n");
+ }
+
+ /* Set latest granting seqno for new policy. */
+ if (oldpolicy)
+ newpolicy->latest_granting = oldpolicy->latest_granting + 1;
+ else
+ newpolicy->latest_granting = 1;
+ seqno = newpolicy->latest_granting;
+
+ /* Install the new policy. */
+ if (oldpolicy) {
+ sidtab_freeze_begin(oldpolicy->sidtab, &flags);
+ rcu_assign_pointer(state->policy, newpolicy);
+ sidtab_freeze_end(oldpolicy->sidtab, &flags);
+ } else {
+ rcu_assign_pointer(state->policy, newpolicy);
+ }
+
+ /* Load the policycaps from the new policy */
+ security_load_policycaps(newpolicy);
+
+ if (!selinux_initialized()) {
+ /*
+ * After first policy load, the security server is
+ * marked as initialized and ready to handle requests and
+ * any objects created prior to policy load are then labeled.
+ */
+ selinux_mark_initialized();
+ selinux_complete_init();
+ }
+
+ /* Free the old policy */
+ synchronize_rcu();
+ selinux_policy_free(oldpolicy);
+ kfree(load_state->convert_data);
+
+ /* Notify others of the policy change */
+ selinux_notify_policy_change(seqno);
+}
+
+/**
+ * security_load_policy - Load a security policy configuration.
+ * @data: binary policy data
+ * @len: length of data in bytes
+ * @load_state: policy load state
+ *
+ * Load a new set of security policy configuration data,
+ * validate it and convert the SID table as necessary.
+ * This function will flush the access vector cache after
+ * loading the new policy.
+ */
+int security_load_policy(void *data, size_t len,
+ struct selinux_load_state *load_state)
+{
+ struct selinux_state *state = &selinux_state;
+ struct selinux_policy *newpolicy, *oldpolicy;
+ struct selinux_policy_convert_data *convert_data;
+ int rc = 0;
+ struct policy_file file = { data, len }, *fp = &file;
+
+ newpolicy = kzalloc_obj(*newpolicy);
+ if (!newpolicy)
+ return -ENOMEM;
+
+ newpolicy->sidtab = kzalloc_obj(*newpolicy->sidtab);
+ if (!newpolicy->sidtab) {
+ rc = -ENOMEM;
+ goto err_policy;
+ }
+
+ rc = policydb_read(&newpolicy->policydb, fp);
+ if (rc)
+ goto err_sidtab;
+
+ newpolicy->policydb.len = len;
+ rc = selinux_set_mapping(&newpolicy->policydb, secclass_map,
+ &newpolicy->map);
+ if (rc)
+ goto err_policydb;
+
+ rc = policydb_load_isids(&newpolicy->policydb, newpolicy->sidtab);
+ if (rc) {
+ pr_err("SELinux: unable to load the initial SIDs\n");
+ goto err_mapping;
+ }
+
+ if (!selinux_initialized()) {
+ /* First policy load, so no need to preserve state from old policy */
+ load_state->policy = newpolicy;
+ load_state->convert_data = NULL;
+ return 0;
+ }
+
+ oldpolicy = rcu_dereference_protected(state->policy,
+ lockdep_is_held(&state->policy_mutex));
+
+ /* Preserve active boolean values from the old policy */
+ rc = security_preserve_bools(oldpolicy, newpolicy);
+ if (rc) {
+ pr_err("SELinux: unable to preserve booleans\n");
+ goto err_free_isids;
+ }
+
+ /*
+ * Convert the internal representations of contexts
+ * in the new SID table.
+ */
+
+ convert_data = kmalloc_obj(*convert_data);
+ if (!convert_data) {
+ rc = -ENOMEM;
+ goto err_free_isids;
+ }
+
+ convert_data->args.oldp = &oldpolicy->policydb;
+ convert_data->args.newp = &newpolicy->policydb;
+
+ convert_data->sidtab_params.args = &convert_data->args;
+ convert_data->sidtab_params.target = newpolicy->sidtab;
+
+ rc = sidtab_convert(oldpolicy->sidtab, &convert_data->sidtab_params);
+ if (rc) {
+ pr_err("SELinux: unable to convert the internal"
+ " representation of contexts in the new SID"
+ " table\n");
+ goto err_free_convert_data;
+ }
+
+ load_state->policy = newpolicy;
+ load_state->convert_data = convert_data;
+ return 0;
+
+err_free_convert_data:
+ kfree(convert_data);
+err_free_isids:
+ sidtab_destroy(newpolicy->sidtab);
+err_mapping:
+ kfree(newpolicy->map.mapping);
+err_policydb:
+ policydb_destroy(&newpolicy->policydb);
+err_sidtab:
+ kfree(newpolicy->sidtab);
+err_policy:
+ kfree(newpolicy);
+
+ return rc;
+}
+
+/**
+ * ocontext_to_sid - Helper to safely get sid for an ocontext
+ * @sidtab: SID table
+ * @c: ocontext structure
+ * @index: index of the context entry (0 or 1)
+ * @out_sid: pointer to the resulting SID value
+ *
+ * For all ocontexts except OCON_ISID the SID fields are populated
+ * on-demand when needed. Since updating the SID value is an SMP-sensitive
+ * operation, this helper must be used to do that safely.
+ *
+ * WARNING: This function may return -ESTALE, indicating that the caller
+ * must retry the operation after re-acquiring the policy pointer!
+ */
+static int ocontext_to_sid(struct sidtab *sidtab, struct ocontext *c,
+ size_t index, u32 *out_sid)
+{
+ int rc;
+ u32 sid;
+
+ /* Ensure the associated sidtab entry is visible to this thread. */
+ sid = smp_load_acquire(&c->sid[index]);
+ if (!sid) {
+ rc = sidtab_context_to_sid(sidtab, &c->context[index], &sid);
+ if (rc)
+ return rc;
+
+ /*
+ * Ensure the new sidtab entry is visible to other threads
+ * when they see the SID.
+ */
+ smp_store_release(&c->sid[index], sid);
+ }
+ *out_sid = sid;
+ return 0;
+}
+
+/**
+ * security_port_sid - Obtain the SID for a port.
+ * @protocol: protocol number
+ * @port: port number
+ * @out_sid: security identifier
+ */
+int security_port_sid(u8 protocol, u16 port, u32 *out_sid)
+{
+ struct selinux_policy *policy;
+ struct policydb *policydb;
+ struct sidtab *sidtab;
+ struct ocontext *c;
+ int rc;
+
+ if (!selinux_initialized()) {
+ *out_sid = SECINITSID_PORT;
+ return 0;
+ }
+
+retry:
+ rc = 0;
+ rcu_read_lock();
+ policy = rcu_dereference(selinux_state.policy);
+ policydb = &policy->policydb;
+ sidtab = policy->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) {
+ rc = ocontext_to_sid(sidtab, c, 0, out_sid);
+ if (rc == -ESTALE) {
+ rcu_read_unlock();
+ goto retry;
+ }
+ if (rc)
+ goto out;
+ } else {
+ *out_sid = SECINITSID_PORT;
+ }
+
+out:
+ rcu_read_unlock();
+ return rc;
+}
+
+/**
+ * security_ib_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(u64 subnet_prefix, u16 pkey_num, u32 *out_sid)
+{
+ struct selinux_policy *policy;
+ struct policydb *policydb;
+ struct sidtab *sidtab;
+ struct ocontext *c;
+ int rc;
+
+ if (!selinux_initialized()) {
+ *out_sid = SECINITSID_UNLABELED;
+ return 0;
+ }
+
+retry:
+ rc = 0;
+ rcu_read_lock();
+ policy = rcu_dereference(selinux_state.policy);
+ policydb = &policy->policydb;
+ sidtab = policy->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) {
+ rc = ocontext_to_sid(sidtab, c, 0, out_sid);
+ if (rc == -ESTALE) {
+ rcu_read_unlock();
+ goto retry;
+ }
+ if (rc)
+ goto out;
+ } else
+ *out_sid = SECINITSID_UNLABELED;
+
+out:
+ rcu_read_unlock();
+ return rc;
+}
+
+/**
+ * security_ib_endport_sid - Obtain the SID for a subnet management interface.
+ * @dev_name: device name
+ * @port_num: port number
+ * @out_sid: security identifier
+ */
+int security_ib_endport_sid(const char *dev_name, u8 port_num, u32 *out_sid)
+{
+ struct selinux_policy *policy;
+ struct policydb *policydb;
+ struct sidtab *sidtab;
+ struct ocontext *c;
+ int rc;
+
+ if (!selinux_initialized()) {
+ *out_sid = SECINITSID_UNLABELED;
+ return 0;
+ }
+
+retry:
+ rc = 0;
+ rcu_read_lock();
+ policy = rcu_dereference(selinux_state.policy);
+ policydb = &policy->policydb;
+ sidtab = policy->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) {
+ rc = ocontext_to_sid(sidtab, c, 0, out_sid);
+ if (rc == -ESTALE) {
+ rcu_read_unlock();
+ goto retry;
+ }
+ if (rc)
+ goto out;
+ } else
+ *out_sid = SECINITSID_UNLABELED;
+
+out:
+ rcu_read_unlock();
+ return rc;
+}
+
+/**
+ * security_netif_sid - Obtain the SID for a network interface.
+ * @name: interface name
+ * @if_sid: interface SID
+ */
+int security_netif_sid(const char *name, u32 *if_sid)
+{
+ struct selinux_policy *policy;
+ struct policydb *policydb;
+ struct sidtab *sidtab;
+ int rc;
+ struct ocontext *c;
+ bool wildcard_support;
+
+ if (!selinux_initialized()) {
+ *if_sid = SECINITSID_NETIF;
+ return 0;
+ }
+
+retry:
+ rc = 0;
+ rcu_read_lock();
+ policy = rcu_dereference(selinux_state.policy);
+ policydb = &policy->policydb;
+ sidtab = policy->sidtab;
+ wildcard_support = ebitmap_get_bit(&policydb->policycaps, POLICYDB_CAP_NETIF_WILDCARD);
+
+ c = policydb->ocontexts[OCON_NETIF];
+ while (c) {
+ if (wildcard_support) {
+ if (match_wildcard(c->u.name, name))
+ break;
+ } else {
+ if (strcmp(c->u.name, name) == 0)
+ break;
+ }
+
+ c = c->next;
+ }
+
+ if (c) {
+ rc = ocontext_to_sid(sidtab, c, 0, if_sid);
+ if (rc == -ESTALE) {
+ rcu_read_unlock();
+ goto retry;
+ }
+ if (rc)
+ goto out;
+ } else
+ *if_sid = SECINITSID_NETIF;
+
+out:
+ rcu_read_unlock();
+ return rc;
+}
+
+static bool match_ipv6_addrmask(const u32 input[4], const u32 addr[4], const u32 mask[4])
+{
+ int i;
+
+ for (i = 0; i < 4; i++)
+ if (addr[i] != (input[i] & mask[i]))
+ return false;
+
+ return true;
+}
+
+/**
+ * security_node_sid - Obtain the SID for a node (host).
+ * @domain: communication domain aka address family
+ * @addrp: address
+ * @addrlen: address length in bytes
+ * @out_sid: security identifier
+ */
+int security_node_sid(u16 domain,
+ const void *addrp,
+ u32 addrlen,
+ u32 *out_sid)
+{
+ struct selinux_policy *policy;
+ struct policydb *policydb;
+ struct sidtab *sidtab;
+ int rc;
+ struct ocontext *c;
+
+ if (!selinux_initialized()) {
+ *out_sid = SECINITSID_NODE;
+ return 0;
+ }
+
+retry:
+ rcu_read_lock();
+ policy = rcu_dereference(selinux_state.policy);
+ policydb = &policy->policydb;
+ sidtab = policy->sidtab;
+
+ switch (domain) {
+ case AF_INET: {
+ u32 addr;
+
+ rc = -EINVAL;
+ if (addrlen != sizeof(u32))
+ goto out;
+
+ addr = *((const 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) {
+ rc = ocontext_to_sid(sidtab, c, 0, out_sid);
+ if (rc == -ESTALE) {
+ rcu_read_unlock();
+ goto retry;
+ }
+ if (rc)
+ goto out;
+ } else {
+ *out_sid = SECINITSID_NODE;
+ }
+
+ rc = 0;
+out:
+ rcu_read_unlock();
+ return rc;
+}
+
+/**
+ * __security_genfs_sid - Helper to obtain a SID for a file in a filesystem
+ * @policy: policy
+ * @fstype: filesystem type
+ * @path: path from root of mount
+ * @orig_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.
+ *
+ * WARNING: This function may return -ESTALE, indicating that the caller
+ * must retry the operation after re-acquiring the policy pointer!
+ */
+static inline int __security_genfs_sid(struct selinux_policy *policy,
+ const char *fstype,
+ const char *path,
+ u16 orig_sclass,
+ u32 *sid)
+{
+ struct policydb *policydb = &policy->policydb;
+ struct sidtab *sidtab = policy->sidtab;
+ u16 sclass;
+ struct genfs *genfs;
+ struct ocontext *c;
+ int cmp = 0;
+ bool wildcard;
+
+ while (path[0] == '/' && path[1] == '/')
+ path++;
+
+ sclass = unmap_class(&policy->map, orig_sclass);
+ *sid = SECINITSID_UNLABELED;
+
+ for (genfs = policydb->genfs; genfs; genfs = genfs->next) {
+ cmp = strcmp(fstype, genfs->fstype);
+ if (cmp <= 0)
+ break;
+ }
+
+ if (!genfs || cmp)
+ return -ENOENT;
+
+ wildcard = ebitmap_get_bit(&policy->policydb.policycaps,
+ POLICYDB_CAP_GENFS_SECLABEL_WILDCARD);
+ for (c = genfs->head; c; c = c->next) {
+ if (!c->v.sclass || sclass == c->v.sclass) {
+ if (wildcard) {
+ if (match_wildcard(c->u.name, path))
+ break;
+ } else {
+ size_t len = strlen(c->u.name);
+
+ if ((strncmp(c->u.name, path, len)) == 0)
+ break;
+ }
+ }
+ }
+
+ if (!c)
+ return -ENOENT;
+
+ return ocontext_to_sid(sidtab, c, 0, sid);
+}
+
+/**
+ * security_genfs_sid - Obtain a SID for a file in a filesystem
+ * @fstype: filesystem type
+ * @path: path from root of mount
+ * @orig_sclass: file security class
+ * @sid: SID for path
+ *
+ * Acquire policy_rwlock before calling __security_genfs_sid() and release
+ * it afterward.
+ */
+int security_genfs_sid(const char *fstype,
+ const char *path,
+ u16 orig_sclass,
+ u32 *sid)
+{
+ struct selinux_policy *policy;
+ int retval;
+
+ if (!selinux_initialized()) {
+ *sid = SECINITSID_UNLABELED;
+ return 0;
+ }
+
+ do {
+ rcu_read_lock();
+ policy = rcu_dereference(selinux_state.policy);
+ retval = __security_genfs_sid(policy, fstype, path,
+ orig_sclass, sid);
+ rcu_read_unlock();
+ } while (retval == -ESTALE);
+ return retval;
+}
+
+int selinux_policy_genfs_sid(struct selinux_policy *policy,
+ const char *fstype,
+ const char *path,
+ u16 orig_sclass,
+ u32 *sid)
+{
+ /* no lock required, policy is not yet accessible by other threads */
+ return __security_genfs_sid(policy, fstype, path, orig_sclass, sid);
+}
+
+/**
+ * security_fs_use - Determine how to handle labeling for a filesystem.
+ * @sb: superblock in question
+ */
+int security_fs_use(struct super_block *sb)
+{
+ struct selinux_policy *policy;
+ struct policydb *policydb;
+ struct sidtab *sidtab;
+ int rc;
+ struct ocontext *c;
+ struct superblock_security_struct *sbsec = selinux_superblock(sb);
+ const char *fstype = sb->s_type->name;
+
+ if (!selinux_initialized()) {
+ sbsec->behavior = SECURITY_FS_USE_NONE;
+ sbsec->sid = SECINITSID_UNLABELED;
+ return 0;
+ }
+
+retry:
+ rcu_read_lock();
+ policy = rcu_dereference(selinux_state.policy);
+ policydb = &policy->policydb;
+ sidtab = policy->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;
+ rc = ocontext_to_sid(sidtab, c, 0, &sbsec->sid);
+ if (rc == -ESTALE) {
+ rcu_read_unlock();
+ goto retry;
+ }
+ if (rc)
+ goto out;
+ } else {
+ rc = __security_genfs_sid(policy, fstype, "/",
+ SECCLASS_DIR, &sbsec->sid);
+ if (rc == -ESTALE) {
+ rcu_read_unlock();
+ goto retry;
+ }
+ if (rc) {
+ sbsec->behavior = SECURITY_FS_USE_NONE;
+ rc = 0;
+ } else {
+ sbsec->behavior = SECURITY_FS_USE_GENFS;
+ }
+ }
+
+out:
+ rcu_read_unlock();
+ return rc;
+}
+
+int security_get_bools(struct selinux_policy *policy,
+ u32 *len, char ***names, int **values)
+{
+ struct policydb *policydb;
+ u32 i;
+ int rc;
+
+ policydb = &policy->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 = kzalloc_objs(int, *len, 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:
+ 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(u32 len, const int *values)
+{
+ struct selinux_state *state = &selinux_state;
+ struct selinux_policy *newpolicy, *oldpolicy;
+ int rc;
+ u32 i, seqno = 0;
+
+ if (!selinux_initialized())
+ return -EINVAL;
+
+ oldpolicy = rcu_dereference_protected(state->policy,
+ lockdep_is_held(&state->policy_mutex));
+
+ /* Consistency check on number of booleans, should never fail */
+ if (WARN_ON(len != oldpolicy->policydb.p_bools.nprim))
+ return -EINVAL;
+
+ newpolicy = kmemdup(oldpolicy, sizeof(*newpolicy), GFP_KERNEL);
+ if (!newpolicy)
+ return -ENOMEM;
+
+ /*
+ * Deep copy only the parts of the policydb that might be
+ * modified as a result of changing booleans.
+ */
+ rc = cond_policydb_dup(&newpolicy->policydb, &oldpolicy->policydb);
+ if (rc) {
+ kfree(newpolicy);
+ return -ENOMEM;
+ }
+
+ /* Update the boolean states in the copy */
+ for (i = 0; i < len; i++) {
+ int new_state = !!values[i];
+ int old_state = newpolicy->policydb.bool_val_to_struct[i]->state;
+
+ if (new_state != old_state) {
+ audit_log(audit_context(), GFP_ATOMIC,
+ AUDIT_MAC_CONFIG_CHANGE,
+ "bool=%s val=%d old_val=%d auid=%u ses=%u",
+ sym_name(&newpolicy->policydb, SYM_BOOLS, i),
+ new_state,
+ old_state,
+ from_kuid(&init_user_ns, audit_get_loginuid(current)),
+ audit_get_sessionid(current));
+ newpolicy->policydb.bool_val_to_struct[i]->state = new_state;
+ }
+ }
+
+ /* Re-evaluate the conditional rules in the copy */
+ evaluate_cond_nodes(&newpolicy->policydb);
+
+ /* Set latest granting seqno for new policy */
+ newpolicy->latest_granting = oldpolicy->latest_granting + 1;
+ seqno = newpolicy->latest_granting;
+
+ /* Install the new policy */
+ rcu_assign_pointer(state->policy, newpolicy);
+
+ /*
+ * Free the conditional portions of the old policydb
+ * that were copied for the new policy, and the oldpolicy
+ * structure itself but not what it references.
+ */
+ synchronize_rcu();
+ selinux_policy_cond_free(oldpolicy);
+
+ /* Notify others of the policy change */
+ selinux_notify_policy_change(seqno);
+ return 0;
+}
+
+int security_get_bool_value(u32 index)
+{
+ struct selinux_policy *policy;
+ struct policydb *policydb;
+ int rc;
+ u32 len;
+
+ if (!selinux_initialized())
+ return 0;
+
+ rcu_read_lock();
+ policy = rcu_dereference(selinux_state.policy);
+ policydb = &policy->policydb;
+
+ rc = -EFAULT;
+ len = policydb->p_bools.nprim;
+ if (index >= len)
+ goto out;
+
+ rc = policydb->bool_val_to_struct[index]->state;
+out:
+ rcu_read_unlock();
+ return rc;
+}
+
+static int security_preserve_bools(struct selinux_policy *oldpolicy,
+ struct selinux_policy *newpolicy)
+{
+ int rc, *bvalues = NULL;
+ char **bnames = NULL;
+ struct cond_bool_datum *booldatum;
+ u32 i, nbools = 0;
+
+ rc = security_get_bools(oldpolicy, &nbools, &bnames, &bvalues);
+ if (rc)
+ goto out;
+ for (i = 0; i < nbools; i++) {
+ booldatum = symtab_search(&newpolicy->policydb.p_bools,
+ bnames[i]);
+ if (booldatum)
+ booldatum->state = bvalues[i];
+ }
+ evaluate_cond_nodes(&newpolicy->policydb);
+
+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(u32 sid, u32 mls_sid, u32 *new_sid)
+{
+ struct selinux_policy *policy;
+ struct policydb *policydb;
+ struct sidtab *sidtab;
+ struct context *context1;
+ struct context *context2;
+ struct context newcon;
+ char *s;
+ u32 len;
+ int rc;
+
+ if (!selinux_initialized()) {
+ *new_sid = sid;
+ return 0;
+ }
+
+retry:
+ rc = 0;
+ context_init(&newcon);
+
+ rcu_read_lock();
+ policy = rcu_dereference(selinux_state.policy);
+ policydb = &policy->policydb;
+ sidtab = policy->sidtab;
+
+ if (!policydb->mls_enabled) {
+ *new_sid = sid;
+ goto out_unlock;
+ }
+
+ 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(policydb,
+ &newcon);
+ if (rc) {
+ if (!context_struct_to_string(policydb, &newcon, &s,
+ &len)) {
+ struct audit_buffer *ab;
+
+ ab = audit_log_start(audit_context(),
+ GFP_ATOMIC,
+ AUDIT_SELINUX_ERR);
+ audit_log_format(ab,
+ "op=security_sid_mls_copy invalid_context=");
+ /* don't record NUL with untrusted strings */
+ audit_log_n_untrustedstring(ab, s, len - 1);
+ audit_log_end(ab);
+ kfree(s);
+ }
+ goto out_unlock;
+ }
+ }
+ rc = sidtab_context_to_sid(sidtab, &newcon, new_sid);
+ if (rc == -ESTALE) {
+ rcu_read_unlock();
+ context_destroy(&newcon);
+ goto retry;
+ }
+out_unlock:
+ rcu_read_unlock();
+ context_destroy(&newcon);
+ 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
+ * @peer_sid: network peer 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(u32 nlbl_sid, u32 nlbl_type,
+ u32 xfrm_sid,
+ u32 *peer_sid)
+{
+ struct selinux_policy *policy;
+ struct policydb *policydb;
+ struct sidtab *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;
+ }
+
+ if (!selinux_initialized())
+ return 0;
+
+ rcu_read_lock();
+ policy = rcu_dereference(selinux_state.policy);
+ policydb = &policy->policydb;
+ sidtab = policy->sidtab;
+
+ /*
+ * 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) {
+ rc = 0;
+ goto out;
+ }
+
+ 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_equal(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:
+ rcu_read_unlock();
+ return rc;
+}
+
+static int get_classes_callback(void *k, void *d, void *args)
+{
+ struct class_datum *datum = d;
+ char *name = k, **classes = args;
+ u16 value = datum->value - 1;
+
+ classes[value] = kstrdup(name, GFP_ATOMIC);
+ if (!classes[value])
+ return -ENOMEM;
+
+ return 0;
+}
+
+int security_get_classes(struct selinux_policy *policy,
+ char ***classes, u32 *nclasses)
+{
+ struct policydb *policydb;
+ u32 i;
+ int rc;
+
+ policydb = &policy->policydb;
+
+ 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)
+ goto err;
+
+ /*
+ * The class symtab may be sparse, which policydb_class_isvalid() exists
+ * to absorb; the callback fills this array by value, so an unclaimed
+ * one leaves a NULL that sel_make_classes() hands to sel_make_dir().
+ */
+ for (i = 0; i < *nclasses; i++) {
+ if (!(*classes)[i]) {
+ rc = -EINVAL;
+ goto err;
+ }
+ }
+
+out:
+ return rc;
+
+err:
+ for (i = 0; i < *nclasses; i++)
+ kfree((*classes)[i]);
+ kfree(*classes);
+ return rc;
+}
+
+static int get_permissions_callback(void *k, void *d, void *args)
+{
+ struct perm_datum *datum = d;
+ char *name = k, **perms = args;
+ u32 value = datum->value - 1;
+
+ perms[value] = kstrdup(name, GFP_ATOMIC);
+ if (!perms[value])
+ return -ENOMEM;
+
+ return 0;
+}
+
+int security_get_permissions(struct selinux_policy *policy,
+ const char *class, char ***perms, u32 *nperms)
+{
+ struct policydb *policydb;
+ u32 i;
+ int rc;
+ struct class_datum *match;
+
+ policydb = &policy->policydb;
+
+ rc = -EINVAL;
+ match = symtab_search(&policydb->p_classes, 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:
+ return rc;
+
+err:
+ for (i = 0; i < *nperms; i++)
+ kfree((*perms)[i]);
+ kfree(*perms);
+ return rc;
+}
+
+int security_get_reject_unknown(void)
+{
+ struct selinux_policy *policy;
+ int value;
+
+ if (!selinux_initialized())
+ return 0;
+
+ rcu_read_lock();
+ policy = rcu_dereference(selinux_state.policy);
+ value = policy->policydb.reject_unknown;
+ rcu_read_unlock();
+ return value;
+}
+
+int security_get_allow_unknown(void)
+{
+ struct selinux_policy *policy;
+ int value;
+
+ if (!selinux_initialized())
+ return 0;
+
+ rcu_read_lock();
+ policy = rcu_dereference(selinux_state.policy);
+ value = policy->policydb.allow_unknown;
+ rcu_read_unlock();
+ return value;
+}
+
+/**
+ * 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(unsigned int req_cap)
+{
+ struct selinux_policy *policy;
+ int rc;
+
+ if (!selinux_initialized())
+ return 0;
+
+ rcu_read_lock();
+ policy = rcu_dereference(selinux_state.policy);
+ rc = ebitmap_get_bit(&policy->policydb.policycaps, req_cap);
+ rcu_read_unlock();
+
+ return rc;
+}
+
+struct selinux_audit_rule {
+ u32 au_seqno;
+ struct context au_ctxt;
+};
+
+int selinux_audit_rule_avc_callback(u32 event)
+{
+ if (event == AVC_CALLBACK_RESET)
+ return audit_update_lsm_rules();
+ return 0;
+}
+
+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,
+ gfp_t gfp)
+{
+ struct selinux_state *state = &selinux_state;
+ struct selinux_policy *policy;
+ struct policydb *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 (!selinux_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_obj(struct selinux_audit_rule, gfp);
+ if (!tmprule)
+ return -ENOMEM;
+ context_init(&tmprule->au_ctxt);
+
+ rcu_read_lock();
+ policy = rcu_dereference(state->policy);
+ policydb = &policy->policydb;
+ tmprule->au_seqno = policy->latest_granting;
+ switch (field) {
+ case AUDIT_SUBJ_USER:
+ case AUDIT_OBJ_USER:
+ userdatum = symtab_search(&policydb->p_users, rulestr);
+ if (!userdatum) {
+ rc = -EINVAL;
+ goto err;
+ }
+ tmprule->au_ctxt.user = userdatum->value;
+ break;
+ case AUDIT_SUBJ_ROLE:
+ case AUDIT_OBJ_ROLE:
+ roledatum = symtab_search(&policydb->p_roles, rulestr);
+ if (!roledatum) {
+ rc = -EINVAL;
+ goto err;
+ }
+ tmprule->au_ctxt.role = roledatum->value;
+ break;
+ case AUDIT_SUBJ_TYPE:
+ case AUDIT_OBJ_TYPE:
+ typedatum = symtab_search(&policydb->p_types, rulestr);
+ if (!typedatum) {
+ rc = -EINVAL;
+ goto err;
+ }
+ 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 err;
+ break;
+ }
+ rcu_read_unlock();
+
+ *rule = tmprule;
+ return 0;
+
+err:
+ rcu_read_unlock();
+ selinux_audit_rule_free(tmprule);
+ *rule = NULL;
+ return rc;
+}
+
+/* Check to see if the rule contains any selinux fields */
+int selinux_audit_rule_known(struct audit_krule *rule)
+{
+ u32 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(struct lsm_prop *prop, u32 field, u32 op, void *vrule)
+{
+ struct selinux_state *state = &selinux_state;
+ struct selinux_policy *policy;
+ 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;
+ }
+
+ if (!selinux_initialized())
+ return 0;
+
+ rcu_read_lock();
+
+ policy = rcu_dereference(state->policy);
+
+ if (rule->au_seqno < policy->latest_granting) {
+ match = -ESTALE;
+ goto out;
+ }
+
+ ctxt = sidtab_search(policy->sidtab, prop->selinux.secid);
+ if (unlikely(!ctxt)) {
+ WARN_ONCE(1, "selinux_audit_rule_match: unrecognized SID %d\n",
+ prop->selinux.secid);
+ 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:
+ rcu_read_unlock();
+ return match;
+}
+
+#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_obj(*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 netlbl_lsm_secattr *secattr,
+ u32 *sid)
+{
+ struct selinux_policy *policy;
+ struct policydb *policydb;
+ struct sidtab *sidtab;
+ int rc;
+ struct context *ctx;
+ struct context ctx_new;
+
+ if (!selinux_initialized()) {
+ *sid = SECSID_NULL;
+ return 0;
+ }
+
+retry:
+ rc = 0;
+ rcu_read_lock();
+ policy = rcu_dereference(selinux_state.policy);
+ policydb = &policy->policydb;
+ sidtab = policy->sidtab;
+
+ 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)) {
+ ebitmap_destroy(&ctx_new.range.level[0].cat);
+ goto out;
+ }
+
+ rc = sidtab_context_to_sid(sidtab, &ctx_new, sid);
+ ebitmap_destroy(&ctx_new.range.level[0].cat);
+ if (rc == -ESTALE) {
+ rcu_read_unlock();
+ goto retry;
+ }
+ if (rc)
+ goto out;
+
+ security_netlbl_cache_add(secattr, *sid);
+ } else
+ *sid = SECSID_NULL;
+
+out:
+ rcu_read_unlock();
+ 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(u32 sid, struct netlbl_lsm_secattr *secattr)
+{
+ struct selinux_policy *policy;
+ struct policydb *policydb;
+ int rc;
+ struct context *ctx;
+
+ if (!selinux_initialized())
+ return 0;
+
+ rcu_read_lock();
+ policy = rcu_dereference(selinux_state.policy);
+ policydb = &policy->policydb;
+
+ rc = -ENOENT;
+ ctx = sidtab_search(policy->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:
+ rcu_read_unlock();
+ return rc;
+}
+#endif /* CONFIG_NETLABEL */
+
+/**
+ * __security_read_policy - read the policy.
+ * @policy: SELinux policy
+ * @data: binary policy data
+ * @len: length of data in bytes
+ *
+ */
+static int __security_read_policy(struct selinux_policy *policy,
+ void *data, size_t *len)
+{
+ int rc;
+ struct policy_file fp;
+
+ fp.data = data;
+ fp.len = *len;
+
+ rc = policydb_write(&policy->policydb, &fp);
+ if (rc)
+ return rc;
+
+ *len = (unsigned long)fp.data - (unsigned long)data;
+ return 0;
+}
+
+/**
+ * security_read_policy - read the policy.
+ * @data: binary policy data
+ * @len: length of data in bytes
+ *
+ */
+int security_read_policy(void **data, size_t *len)
+{
+ struct selinux_state *state = &selinux_state;
+ struct selinux_policy *policy;
+
+ policy = rcu_dereference_protected(
+ state->policy, lockdep_is_held(&state->policy_mutex));
+ if (!policy)
+ return -EINVAL;
+
+ *len = policy->policydb.len;
+ *data = vmalloc_user(*len);
+ if (!*data)
+ return -ENOMEM;
+
+ return __security_read_policy(policy, *data, len);
+}
+
+/**
+ * security_read_state_kernel - read the policy.
+ * @data: binary policy data
+ * @len: length of data in bytes
+ *
+ * Allocates kernel memory for reading SELinux policy.
+ * This function is for internal use only and should not
+ * be used for returning data to user space.
+ *
+ * This function must be called with policy_mutex held.
+ */
+int security_read_state_kernel(void **data, size_t *len)
+{
+ int err;
+ struct selinux_state *state = &selinux_state;
+ struct selinux_policy *policy;
+
+ policy = rcu_dereference_protected(
+ state->policy, lockdep_is_held(&state->policy_mutex));
+ if (!policy)
+ return -EINVAL;
+
+ *len = policy->policydb.len;
+ *data = vmalloc(*len);
+ if (!*data)
+ return -ENOMEM;
+
+ err = __security_read_policy(policy, *data, len);
+ if (err) {
+ vfree(*data);
+ *data = NULL;
+ *len = 0;
+ }
+ return err;
+}
diff --git a/security/selinux/ss/services.h b/security/selinux/ss/services.h
new file mode 100644
index 000000000..93358e7a6
--- /dev/null
+++ b/security/selinux/ss/services.h
@@ -0,0 +1,47 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Implementation of the security services.
+ *
+ * Author : Stephen Smalley, <stephen.smalley.work@gmail.com>
+ */
+
+#ifndef _SS_SERVICES_H_
+#define _SS_SERVICES_H_
+
+#include "policydb.h"
+
+/* Mapping for a single class */
+struct selinux_mapping {
+ u16 value; /* policy value for class */
+ u16 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_policy {
+ struct sidtab *sidtab;
+ struct policydb policydb;
+ struct selinux_map map;
+ u32 latest_granting;
+} __randomize_layout;
+
+struct convert_context_args {
+ struct policydb *oldp;
+ struct policydb *newp;
+};
+
+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);
+
+int services_convert_context(struct convert_context_args *args,
+ struct context *oldc, struct context *newc,
+ gfp_t gfp_flags);
+
+#endif /* _SS_SERVICES_H_ */
diff --git a/security/selinux/ss/sidtab.c b/security/selinux/ss/sidtab.c
new file mode 100644
index 000000000..118af0aa2
--- /dev/null
+++ b/security/selinux/ss/sidtab.c
@@ -0,0 +1,638 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Implementation of the SID table type.
+ *
+ * Original author: Stephen Smalley, <stephen.smalley.work@gmail.com>
+ * Author: Ondrej Mosnacek, <omosnacek@gmail.com>
+ *
+ * Copyright (C) 2018 Red Hat, Inc.
+ */
+
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/rcupdate.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"
+#include "services.h"
+
+struct sidtab_str_cache {
+ struct rcu_head rcu_member;
+ struct list_head lru_member;
+ struct sidtab_entry *parent;
+ u32 len;
+ char str[] __counted_by(len);
+};
+
+#define index_to_sid(index) ((index) + SECINITSID_NUM + 1)
+#define sid_to_index(sid) ((sid) - (SECINITSID_NUM + 1))
+
+int sidtab_init(struct sidtab *s)
+{
+ u32 i;
+
+ memset(s->roots, 0, sizeof(s->roots));
+
+ for (i = 0; i < SECINITSID_NUM; i++)
+ s->isids[i].set = 0;
+
+ s->frozen = false;
+ s->count = 0;
+ s->convert = NULL;
+ hash_init(s->context_to_sid);
+
+ spin_lock_init(&s->lock);
+
+#if CONFIG_SECURITY_SELINUX_SID2STR_CACHE_SIZE > 0
+ s->cache_free_slots = CONFIG_SECURITY_SELINUX_SID2STR_CACHE_SIZE;
+ INIT_LIST_HEAD(&s->cache_lru_list);
+ spin_lock_init(&s->cache_lock);
+#endif
+
+ return 0;
+}
+
+static u32 context_to_sid(struct sidtab *s, struct context *context, u32 hash)
+{
+ struct sidtab_entry *entry;
+ u32 sid = 0;
+
+ rcu_read_lock();
+ hash_for_each_possible_rcu(s->context_to_sid, entry, list, hash) {
+ if (entry->hash != hash)
+ continue;
+ if (context_equal(&entry->context, context)) {
+ sid = entry->sid;
+ break;
+ }
+ }
+ rcu_read_unlock();
+ return sid;
+}
+
+int sidtab_set_initial(struct sidtab *s, u32 sid, struct context *context)
+{
+ struct sidtab_isid_entry *isid;
+ u32 hash;
+ int rc;
+
+ if (sid == 0 || sid > SECINITSID_NUM)
+ return -EINVAL;
+
+ isid = &s->isids[sid - 1];
+
+ rc = context_cpy(&isid->entry.context, context);
+ if (rc)
+ return rc;
+
+#if CONFIG_SECURITY_SELINUX_SID2STR_CACHE_SIZE > 0
+ isid->entry.cache = NULL;
+#endif
+ isid->set = 1;
+
+ hash = context_compute_hash(context);
+
+ /*
+ * Multiple initial sids may map to the same context. Check that this
+ * context is not already represented in the context_to_sid hashtable
+ * to avoid duplicate entries and long linked lists upon hash
+ * collision.
+ */
+ if (!context_to_sid(s, context, hash)) {
+ isid->entry.sid = sid;
+ isid->entry.hash = hash;
+ hash_add(s->context_to_sid, &isid->entry.list, hash);
+ }
+
+ return 0;
+}
+
+int sidtab_hash_stats(struct sidtab *sidtab, char *page)
+{
+ unsigned int i;
+ int chain_len = 0;
+ int slots_used = 0;
+ int entries = 0;
+ int max_chain_len = 0;
+ unsigned int cur_bucket = 0;
+ struct sidtab_entry *entry;
+
+ rcu_read_lock();
+ hash_for_each_rcu(sidtab->context_to_sid, i, entry, list) {
+ entries++;
+ if (i == cur_bucket) {
+ chain_len++;
+ if (chain_len == 1)
+ slots_used++;
+ } else {
+ cur_bucket = i;
+ if (chain_len > max_chain_len)
+ max_chain_len = chain_len;
+ chain_len = 0;
+ }
+ }
+ rcu_read_unlock();
+
+ if (chain_len > max_chain_len)
+ max_chain_len = chain_len;
+
+ return scnprintf(page, PAGE_SIZE,
+ "entries: %d\nbuckets used: %d/%d\n"
+ "longest chain: %d\n",
+ entries, slots_used, SIDTAB_HASH_BUCKETS,
+ max_chain_len);
+}
+
+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 sidtab_entry *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];
+}
+
+static struct sidtab_entry *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 sidtab_entry *sidtab_lookup_initial(struct sidtab *s, u32 sid)
+{
+ return s->isids[sid - 1].set ? &s->isids[sid - 1].entry : NULL;
+}
+
+static struct sidtab_entry *sidtab_search_core(struct sidtab *s, u32 sid,
+ int force)
+{
+ if (sid != 0) {
+ struct sidtab_entry *entry;
+
+ if (sid > SECINITSID_NUM)
+ entry = sidtab_lookup(s, sid_to_index(sid));
+ else
+ entry = sidtab_lookup_initial(s, sid);
+ if (entry && (!entry->context.len || force))
+ return entry;
+ }
+
+ return sidtab_lookup_initial(s, SECINITSID_UNLABELED);
+}
+
+struct sidtab_entry *sidtab_search_entry(struct sidtab *s, u32 sid)
+{
+ return sidtab_search_core(s, sid, 0);
+}
+
+struct sidtab_entry *sidtab_search_entry_force(struct sidtab *s, u32 sid)
+{
+ return sidtab_search_core(s, sid, 1);
+}
+
+int sidtab_context_to_sid(struct sidtab *s, struct context *context, u32 *sid)
+{
+ unsigned long flags;
+ u32 count, hash = context_compute_hash(context);
+ struct sidtab_convert_params *convert;
+ struct sidtab_entry *dst, *dst_convert;
+ int rc;
+
+ *sid = context_to_sid(s, context, hash);
+ if (*sid)
+ return 0;
+
+ /* lock-free search failed: lock, re-search, and insert if not found */
+ spin_lock_irqsave(&s->lock, flags);
+
+ rc = 0;
+ *sid = context_to_sid(s, context, hash);
+ if (*sid)
+ goto out_unlock;
+
+ if (unlikely(s->frozen)) {
+ /*
+ * This sidtab is now frozen - tell the caller to abort and
+ * get the new one.
+ */
+ rc = -ESTALE;
+ goto out_unlock;
+ }
+
+ count = s->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;
+
+ dst->sid = index_to_sid(count);
+ dst->hash = hash;
+
+ rc = context_cpy(&dst->context, 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
+ */
+ convert = s->convert;
+ if (convert) {
+ struct sidtab *target = convert->target;
+
+ rc = -ENOMEM;
+ dst_convert = sidtab_do_lookup(target, count, 1);
+ if (!dst_convert) {
+ context_destroy(&dst->context);
+ goto out_unlock;
+ }
+
+ rc = services_convert_context(convert->args, context,
+ &dst_convert->context,
+ GFP_ATOMIC);
+ if (rc) {
+ context_destroy(&dst->context);
+ goto out_unlock;
+ }
+ dst_convert->sid = index_to_sid(count);
+ dst_convert->hash = context_compute_hash(&dst_convert->context);
+ target->count = count + 1;
+
+ hash_add_rcu(target->context_to_sid, &dst_convert->list,
+ dst_convert->hash);
+ }
+
+ if (context->len)
+ pr_info("SELinux: Context %s is not valid (left unmapped).\n",
+ context->str);
+
+ *sid = index_to_sid(count);
+
+ /* write entries before updating count */
+ smp_store_release(&s->count, count + 1);
+ hash_add_rcu(s->context_to_sid, &dst->list, dst->hash);
+
+ rc = 0;
+out_unlock:
+ spin_unlock_irqrestore(&s->lock, flags);
+ return rc;
+}
+
+static void sidtab_convert_hashtable(struct sidtab *s, u32 count)
+{
+ struct sidtab_entry *entry;
+ u32 i;
+
+ for (i = 0; i < count; i++) {
+ entry = sidtab_do_lookup(s, i, 0);
+ entry->sid = index_to_sid(i);
+ entry->hash = context_compute_hash(&entry->context);
+
+ hash_add_rcu(s->context_to_sid, &entry->list, entry->hash);
+ }
+}
+
+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 = services_convert_context(
+ convert->args,
+ &esrc->ptr_leaf->entries[i].context,
+ &edst->ptr_leaf->entries[i].context,
+ GFP_KERNEL);
+ 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 convert the tree 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(&params->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;
+ }
+ /*
+ * The hashtable can also be modified in sidtab_context_to_sid()
+ * so we must re-acquire the lock here.
+ */
+ spin_lock_irqsave(&s->lock, flags);
+ sidtab_convert_hashtable(params->target, count);
+ spin_unlock_irqrestore(&s->lock, flags);
+
+ return 0;
+}
+
+void sidtab_cancel_convert(struct sidtab *s)
+{
+ unsigned long flags;
+
+ /* cancelling policy load - disable live convert of sidtab */
+ spin_lock_irqsave(&s->lock, flags);
+ s->convert = NULL;
+ spin_unlock_irqrestore(&s->lock, flags);
+}
+
+void sidtab_freeze_begin(struct sidtab *s, unsigned long *flags)
+ __acquires(&s->lock)
+{
+ spin_lock_irqsave(&s->lock, *flags);
+ s->frozen = true;
+ s->convert = NULL;
+}
+void sidtab_freeze_end(struct sidtab *s, unsigned long *flags)
+ __releases(&s->lock)
+{
+ spin_unlock_irqrestore(&s->lock, *flags);
+}
+
+static void sidtab_destroy_entry(struct sidtab_entry *entry)
+{
+ context_destroy(&entry->context);
+#if CONFIG_SECURITY_SELINUX_SID2STR_CACHE_SIZE > 0
+ kfree(rcu_dereference_raw(entry->cache));
+#endif
+}
+
+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++)
+ sidtab_destroy_entry(&node->entries[i]);
+ kfree(node);
+ }
+}
+
+void sidtab_destroy(struct sidtab *s)
+{
+ u32 i, level;
+
+ for (i = 0; i < SECINITSID_NUM; i++)
+ if (s->isids[i].set)
+ sidtab_destroy_entry(&s->isids[i].entry);
+
+ level = SIDTAB_MAX_LEVEL;
+ while (level && !s->roots[level].ptr_inner)
+ --level;
+
+ sidtab_destroy_tree(s->roots[level], level);
+ /*
+ * The context_to_sid hashtable's objects are all shared
+ * with the isids array and context tree, and so don't need
+ * to be cleaned up here.
+ */
+}
+
+#if CONFIG_SECURITY_SELINUX_SID2STR_CACHE_SIZE > 0
+
+void sidtab_sid2str_put(struct sidtab *s, struct sidtab_entry *entry,
+ const char *str, u32 str_len)
+{
+ struct sidtab_str_cache *cache, *victim = NULL;
+ unsigned long flags;
+
+ /* do not cache invalid contexts */
+ if (entry->context.len)
+ return;
+
+ spin_lock_irqsave(&s->cache_lock, flags);
+
+ cache = rcu_dereference_protected(entry->cache,
+ lockdep_is_held(&s->cache_lock));
+ if (cache) {
+ /* entry in cache - just bump to the head of LRU list */
+ list_move(&cache->lru_member, &s->cache_lru_list);
+ goto out_unlock;
+ }
+
+ cache = kmalloc_flex(*cache, str, str_len, GFP_ATOMIC);
+ if (!cache)
+ goto out_unlock;
+
+ if (s->cache_free_slots == 0) {
+ /* pop a cache entry from the tail and free it */
+ victim = container_of(s->cache_lru_list.prev,
+ struct sidtab_str_cache, lru_member);
+ list_del(&victim->lru_member);
+ rcu_assign_pointer(victim->parent->cache, NULL);
+ } else {
+ s->cache_free_slots--;
+ }
+ cache->parent = entry;
+ cache->len = str_len;
+ memcpy(cache->str, str, str_len);
+ list_add(&cache->lru_member, &s->cache_lru_list);
+
+ rcu_assign_pointer(entry->cache, cache);
+
+out_unlock:
+ spin_unlock_irqrestore(&s->cache_lock, flags);
+ kfree_rcu(victim, rcu_member);
+}
+
+int sidtab_sid2str_get(struct sidtab *s, struct sidtab_entry *entry, char **out,
+ u32 *out_len)
+{
+ struct sidtab_str_cache *cache;
+ int rc = 0;
+
+ if (entry->context.len)
+ return -ENOENT; /* do not cache invalid contexts */
+
+ rcu_read_lock();
+
+ cache = rcu_dereference(entry->cache);
+ if (!cache) {
+ rc = -ENOENT;
+ } else {
+ *out_len = cache->len;
+ if (out) {
+ *out = kmemdup(cache->str, cache->len, GFP_ATOMIC);
+ if (!*out)
+ rc = -ENOMEM;
+ }
+ }
+
+ rcu_read_unlock();
+
+ if (!rc && out)
+ sidtab_sid2str_put(s, entry, *out, *out_len);
+ return rc;
+}
+
+#endif /* CONFIG_SECURITY_SELINUX_SID2STR_CACHE_SIZE > 0 */
diff --git a/security/selinux/ss/sidtab.h b/security/selinux/ss/sidtab.h
new file mode 100644
index 000000000..832c85c70
--- /dev/null
+++ b/security/selinux/ss/sidtab.h
@@ -0,0 +1,160 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * A security identifier table (sidtab) is a lookup table
+ * of security context structures indexed by SID value.
+ *
+ * Original author: Stephen Smalley, <stephen.smalley.work@gmail.com>
+ * Author: Ondrej Mosnacek, <omosnacek@gmail.com>
+ *
+ * Copyright (C) 2018 Red Hat, Inc.
+ */
+
+#ifndef _SS_SIDTAB_H_
+#define _SS_SIDTAB_H_
+
+#include <linux/spinlock_types.h>
+#include <linux/log2.h>
+#include <linux/hashtable.h>
+
+#include "context.h"
+
+struct sidtab_entry {
+ u32 sid;
+ u32 hash;
+ struct context context;
+#if CONFIG_SECURITY_SELINUX_SID2STR_CACHE_SIZE > 0
+ struct sidtab_str_cache __rcu *cache;
+#endif
+ struct hlist_node list;
+};
+
+union sidtab_entry_inner {
+ struct sidtab_node_inner *ptr_inner;
+ struct sidtab_node_leaf *ptr_leaf;
+};
+
+/* align node size to page boundary */
+#define SIDTAB_NODE_ALLOC_SHIFT PAGE_SHIFT
+#define SIDTAB_NODE_ALLOC_SIZE PAGE_SIZE
+
+#define size_to_shift(size) ((size) == 1 ? 1 : (const_ilog2((size)-1) + 1))
+
+#define SIDTAB_INNER_SHIFT \
+ (SIDTAB_NODE_ALLOC_SHIFT - \
+ size_to_shift(sizeof(union sidtab_entry_inner)))
+#define SIDTAB_INNER_ENTRIES ((size_t)1 << SIDTAB_INNER_SHIFT)
+#define SIDTAB_LEAF_ENTRIES \
+ (SIDTAB_NODE_ALLOC_SIZE / sizeof(struct sidtab_entry))
+
+#define SIDTAB_MAX_BITS 32
+#define SIDTAB_MAX U32_MAX
+/* ensure enough tree levels for SIDTAB_MAX entries */
+#define SIDTAB_MAX_LEVEL \
+ DIV_ROUND_UP(SIDTAB_MAX_BITS - size_to_shift(SIDTAB_LEAF_ENTRIES), \
+ SIDTAB_INNER_SHIFT)
+
+struct sidtab_node_leaf {
+ struct sidtab_entry entries[SIDTAB_LEAF_ENTRIES];
+};
+
+struct sidtab_node_inner {
+ union sidtab_entry_inner entries[SIDTAB_INNER_ENTRIES];
+};
+
+struct sidtab_isid_entry {
+ int set;
+ struct sidtab_entry entry;
+};
+
+struct sidtab_convert_params {
+ struct convert_context_args *args;
+ struct sidtab *target;
+};
+
+#define SIDTAB_HASH_BITS CONFIG_SECURITY_SELINUX_SIDTAB_HASH_BITS
+#define SIDTAB_HASH_BUCKETS (1 << SIDTAB_HASH_BITS)
+
+struct sidtab {
+ /*
+ * lock-free read access only for as many items as a prior read of
+ * 'count'
+ */
+ union sidtab_entry_inner roots[SIDTAB_MAX_LEVEL + 1];
+ /*
+ * access atomically via {READ|WRITE}_ONCE(); only increment under
+ * spinlock
+ */
+ u32 count;
+ /* access only under spinlock */
+ struct sidtab_convert_params *convert;
+ bool frozen;
+ spinlock_t lock;
+
+#if CONFIG_SECURITY_SELINUX_SID2STR_CACHE_SIZE > 0
+ /* SID -> context string cache */
+ u32 cache_free_slots;
+ struct list_head cache_lru_list;
+ spinlock_t cache_lock;
+#endif
+
+ /* index == SID - 1 (no entry for SECSID_NULL) */
+ struct sidtab_isid_entry isids[SECINITSID_NUM];
+
+ /* Hash table for fast reverse context-to-sid lookups. */
+ DECLARE_HASHTABLE(context_to_sid, SIDTAB_HASH_BITS);
+};
+
+int sidtab_init(struct sidtab *s);
+int sidtab_set_initial(struct sidtab *s, u32 sid, struct context *context);
+struct sidtab_entry *sidtab_search_entry(struct sidtab *s, u32 sid);
+struct sidtab_entry *sidtab_search_entry_force(struct sidtab *s, u32 sid);
+
+static inline struct context *sidtab_search(struct sidtab *s, u32 sid)
+{
+ struct sidtab_entry *entry = sidtab_search_entry(s, sid);
+
+ return entry ? &entry->context : NULL;
+}
+
+static inline struct context *sidtab_search_force(struct sidtab *s, u32 sid)
+{
+ struct sidtab_entry *entry = sidtab_search_entry_force(s, sid);
+
+ return entry ? &entry->context : NULL;
+}
+
+int sidtab_convert(struct sidtab *s, struct sidtab_convert_params *params);
+
+void sidtab_cancel_convert(struct sidtab *s);
+
+void sidtab_freeze_begin(struct sidtab *s, unsigned long *flags)
+ __acquires(&s->lock);
+void sidtab_freeze_end(struct sidtab *s, unsigned long *flags)
+ __releases(&s->lock);
+
+int sidtab_context_to_sid(struct sidtab *s, struct context *context, u32 *sid);
+
+void sidtab_destroy(struct sidtab *s);
+
+int sidtab_hash_stats(struct sidtab *sidtab, char *page);
+
+#if CONFIG_SECURITY_SELINUX_SID2STR_CACHE_SIZE > 0
+void sidtab_sid2str_put(struct sidtab *s, struct sidtab_entry *entry,
+ const char *str, u32 str_len);
+int sidtab_sid2str_get(struct sidtab *s, struct sidtab_entry *entry, char **out,
+ u32 *out_len);
+#else
+static inline void sidtab_sid2str_put(struct sidtab *s,
+ struct sidtab_entry *entry,
+ const char *str, u32 str_len)
+{
+}
+static inline int sidtab_sid2str_get(struct sidtab *s,
+ struct sidtab_entry *entry, char **out,
+ u32 *out_len)
+{
+ return -ENOENT;
+}
+#endif /* CONFIG_SECURITY_SELINUX_SID2STR_CACHE_SIZE > 0 */
+
+#endif /* _SS_SIDTAB_H_ */
diff --git a/security/selinux/ss/symtab.c b/security/selinux/ss/symtab.c
new file mode 100644
index 000000000..a756554e7
--- /dev/null
+++ b/security/selinux/ss/symtab.c
@@ -0,0 +1,56 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Implementation of the symbol table type.
+ *
+ * Author : Stephen Smalley, <stephen.smalley.work@gmail.com>
+ */
+
+#include <linux/kernel.h>
+#include <linux/string.h>
+#include <linux/errno.h>
+#include "symtab.h"
+
+static unsigned int symhash(const void *key)
+{
+ /*
+ * djb2a
+ * Public domain from cdb v0.75
+ */
+ unsigned int hash = 5381;
+ unsigned char c;
+
+ while ((c = *(const unsigned char *)key++))
+ hash = ((hash << 5) + hash) ^ c;
+
+ return hash;
+}
+
+static int symcmp(const void *key1, const void *key2)
+{
+ const char *keyp1, *keyp2;
+
+ keyp1 = key1;
+ keyp2 = key2;
+ return strcmp(keyp1, keyp2);
+}
+
+static const struct hashtab_key_params symtab_key_params = {
+ .hash = symhash,
+ .cmp = symcmp,
+};
+
+int symtab_init(struct symtab *s, u32 size)
+{
+ s->nprim = 0;
+ return hashtab_init(&s->table, size);
+}
+
+int symtab_insert(struct symtab *s, char *name, void *datum)
+{
+ return hashtab_insert(&s->table, name, datum, symtab_key_params);
+}
+
+void *symtab_search(const struct symtab *s, const char *name)
+{
+ return hashtab_search(&s->table, name, symtab_key_params);
+}
diff --git a/security/selinux/ss/symtab.h b/security/selinux/ss/symtab.h
new file mode 100644
index 000000000..7cfa3b449
--- /dev/null
+++ b/security/selinux/ss/symtab.h
@@ -0,0 +1,26 @@
+/* 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, <stephen.smalley.work@gmail.com>
+ */
+
+#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, u32 size);
+
+int symtab_insert(struct symtab *s, char *name, void *datum);
+void *symtab_search(const struct symtab *s, const char *name);
+
+#endif /* _SS_SYMTAB_H_ */