diff options
Diffstat (limited to 'security/selinux')
53 files changed, 28190 insertions, 0 deletions
diff --git a/security/selinux/.gitignore b/security/selinux/.gitignore new file mode 100644 index 000000000..168fae13c --- /dev/null +++ b/security/selinux/.gitignore @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-2.0-only +av_permissions.h +flask.h diff --git a/security/selinux/Kconfig b/security/selinux/Kconfig new file mode 100644 index 000000000..9e921fc72 --- /dev/null +++ b/security/selinux/Kconfig @@ -0,0 +1,117 @@ +# SPDX-License-Identifier: GPL-2.0-only +config SECURITY_SELINUX + bool "NSA SELinux Support" + depends on SECURITY_NETWORK && AUDIT && NET && INET + select NETWORK_SECMARK + default n + help + This selects NSA Security-Enhanced Linux (SELinux). + You will also need a policy configuration and a labeled filesystem. + If you are unsure how to answer this question, answer N. + +config SECURITY_SELINUX_BOOTPARAM + bool "NSA SELinux boot parameter" + depends on SECURITY_SELINUX + default n + help + This option adds a kernel parameter 'selinux', which allows SELinux + to be disabled at boot. If this option is selected, SELinux + functionality can be disabled with selinux=0 on the kernel + command line. The purpose of this option is to allow a single + kernel image to be distributed with SELinux built in, but not + necessarily enabled. + + If you are unsure how to answer this question, answer N. + +config SECURITY_SELINUX_DISABLE + bool "NSA SELinux runtime disable" + depends on SECURITY_SELINUX + select SECURITY_WRITABLE_HOOKS + default n + help + This option enables writing to a selinuxfs node 'disable', which + allows SELinux to be disabled at runtime prior to the policy load. + SELinux will then remain disabled until the next boot. + This option is similar to the selinux=0 boot parameter, but is to + support runtime disabling of SELinux, e.g. from /sbin/init, for + portability across platforms where boot parameters are difficult + to employ. + + NOTE: selecting this option will disable the '__ro_after_init' + kernel hardening feature for security hooks. Please consider + using the selinux=0 boot parameter instead of enabling this + option. + + WARNING: this option is deprecated and will be removed in a future + kernel release. + + If you are unsure how to answer this question, answer N. + +config SECURITY_SELINUX_DEVELOP + bool "NSA SELinux Development Support" + depends on SECURITY_SELINUX + default y + help + This enables the development support option of NSA SELinux, + which is useful for experimenting with SELinux and developing + policies. If unsure, say Y. With this option enabled, the + kernel will start in permissive mode (log everything, deny nothing) + unless you specify enforcing=1 on the kernel command line. You + can interactively toggle the kernel between enforcing mode and + permissive mode (if permitted by the policy) via + /sys/fs/selinux/enforce. + +config SECURITY_SELINUX_AVC_STATS + bool "NSA SELinux AVC Statistics" + depends on SECURITY_SELINUX + default y + help + This option collects access vector cache statistics to + /sys/fs/selinux/avc/cache_stats, which may be monitored via + tools such as avcstat. + +config SECURITY_SELINUX_CHECKREQPROT_VALUE + int "NSA SELinux checkreqprot default value" + depends on SECURITY_SELINUX + range 0 1 + default 0 + help + This option sets the default value for the 'checkreqprot' flag + that determines whether SELinux checks the protection requested + by the application or the protection that will be applied by the + kernel (including any implied execute for read-implies-exec) for + mmap and mprotect calls. If this option is set to 0 (zero), + SELinux will default to checking the protection that will be applied + by the kernel. If this option is set to 1 (one), SELinux will + default to checking the protection requested by the application. + The checkreqprot flag may be changed from the default via the + 'checkreqprot=' boot parameter. It may also be changed at runtime + via /sys/fs/selinux/checkreqprot if authorized by policy. + + WARNING: this option is deprecated and will be removed in a future + kernel release. + + If you are unsure how to answer this question, answer 0. + +config SECURITY_SELINUX_SIDTAB_HASH_BITS + int "NSA SELinux sidtab hashtable size" + depends on SECURITY_SELINUX + range 8 13 + default 9 + help + This option sets the number of buckets used in the sidtab hashtable + to 2^SECURITY_SELINUX_SIDTAB_HASH_BITS buckets. The number of hash + collisions may be viewed at /sys/fs/selinux/ss/sidtab_hash_stats. If + chain lengths are high (e.g. > 20) then selecting a higher value here + will ensure that lookups times are short and stable. + +config SECURITY_SELINUX_SID2STR_CACHE_SIZE + int "NSA SELinux SID to context string translation cache size" + depends on SECURITY_SELINUX + default 256 + help + This option defines the size of the internal SID -> context string + cache, which improves the performance of context to string + conversion. Setting this option to 0 disables the cache completely. + + If unsure, keep the default value. diff --git a/security/selinux/Makefile b/security/selinux/Makefile new file mode 100644 index 000000000..332a1c752 --- /dev/null +++ b/security/selinux/Makefile @@ -0,0 +1,32 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for building the SELinux module as part of the kernel tree. +# + +obj-$(CONFIG_SECURITY_SELINUX) := selinux.o + +selinux-y := avc.o hooks.o selinuxfs.o netlink.o nlmsgtab.o netif.o \ + netnode.o netport.o status.o \ + ss/ebitmap.o ss/hashtab.o ss/symtab.o ss/sidtab.o ss/avtab.o \ + ss/policydb.o ss/services.o ss/conditional.o ss/mls.o ss/context.o + +selinux-$(CONFIG_SECURITY_NETWORK_XFRM) += xfrm.o + +selinux-$(CONFIG_NETLABEL) += netlabel.o + +selinux-$(CONFIG_SECURITY_INFINIBAND) += ibpkey.o + +ccflags-y := -I$(srctree)/security/selinux -I$(srctree)/security/selinux/include + +$(addprefix $(obj)/,$(selinux-y)): $(obj)/flask.h + +quiet_cmd_flask = GEN $(obj)/flask.h $(obj)/av_permissions.h + cmd_flask = $< $(obj)/flask.h $(obj)/av_permissions.h + +targets += flask.h av_permissions.h +# once make >= 4.3 is required, we can use grouped targets in the rule below, +# which basically involves adding both headers and a '&' before the colon, see +# the example below: +# $(obj)/flask.h $(obj)/av_permissions.h &: scripts/selinux/... +$(obj)/flask.h: scripts/selinux/genheaders/genheaders FORCE + $(call if_changed,flask) diff --git a/security/selinux/avc.c b/security/selinux/avc.c new file mode 100644 index 000000000..884a014ce --- /dev/null +++ b/security/selinux/avc.c @@ -0,0 +1,1241 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Implementation of the kernel access vector cache (AVC). + * + * Authors: Stephen Smalley, <sds@tycho.nsa.gov> + * James Morris <jmorris@redhat.com> + * + * Update: KaiGai, Kohei <kaigai@ak.jp.nec.com> + * Replaced the avc_lock spinlock by RCU. + * + * Copyright (C) 2003 Red Hat, Inc., James Morris <jmorris@redhat.com> + */ +#include <linux/types.h> +#include <linux/stddef.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/fs.h> +#include <linux/dcache.h> +#include <linux/init.h> +#include <linux/skbuff.h> +#include <linux/percpu.h> +#include <linux/list.h> +#include <net/sock.h> +#include <linux/un.h> +#include <net/af_unix.h> +#include <linux/ip.h> +#include <linux/audit.h> +#include <linux/ipv6.h> +#include <net/ipv6.h> +#include "avc.h" +#include "avc_ss.h" +#include "classmap.h" + +#define CREATE_TRACE_POINTS +#include <trace/events/avc.h> + +#define AVC_CACHE_SLOTS 512 +#define AVC_DEF_CACHE_THRESHOLD 512 +#define AVC_CACHE_RECLAIM 16 + +#ifdef CONFIG_SECURITY_SELINUX_AVC_STATS +#define avc_cache_stats_incr(field) this_cpu_inc(avc_cache_stats.field) +#else +#define avc_cache_stats_incr(field) do {} while (0) +#endif + +struct avc_entry { + u32 ssid; + u32 tsid; + u16 tclass; + struct av_decision avd; + struct avc_xperms_node *xp_node; +}; + +struct avc_node { + struct avc_entry ae; + struct hlist_node list; /* anchored in avc_cache->slots[i] */ + struct rcu_head rhead; +}; + +struct avc_xperms_decision_node { + struct extended_perms_decision xpd; + struct list_head xpd_list; /* list of extended_perms_decision */ +}; + +struct avc_xperms_node { + struct extended_perms xp; + struct list_head xpd_head; /* list head of extended_perms_decision */ +}; + +struct avc_cache { + struct hlist_head slots[AVC_CACHE_SLOTS]; /* head for avc_node->list */ + spinlock_t slots_lock[AVC_CACHE_SLOTS]; /* lock for writes */ + atomic_t lru_hint; /* LRU hint for reclaim scan */ + atomic_t active_nodes; + u32 latest_notif; /* latest revocation notification */ +}; + +struct avc_callback_node { + int (*callback) (u32 event); + u32 events; + struct avc_callback_node *next; +}; + +#ifdef CONFIG_SECURITY_SELINUX_AVC_STATS +DEFINE_PER_CPU(struct avc_cache_stats, avc_cache_stats) = { 0 }; +#endif + +struct selinux_avc { + unsigned int avc_cache_threshold; + struct avc_cache avc_cache; +}; + +static struct selinux_avc selinux_avc; + +void selinux_avc_init(struct selinux_avc **avc) +{ + int i; + + selinux_avc.avc_cache_threshold = AVC_DEF_CACHE_THRESHOLD; + for (i = 0; i < AVC_CACHE_SLOTS; i++) { + INIT_HLIST_HEAD(&selinux_avc.avc_cache.slots[i]); + spin_lock_init(&selinux_avc.avc_cache.slots_lock[i]); + } + atomic_set(&selinux_avc.avc_cache.active_nodes, 0); + atomic_set(&selinux_avc.avc_cache.lru_hint, 0); + *avc = &selinux_avc; +} + +unsigned int avc_get_cache_threshold(struct selinux_avc *avc) +{ + return avc->avc_cache_threshold; +} + +void avc_set_cache_threshold(struct selinux_avc *avc, + unsigned int cache_threshold) +{ + avc->avc_cache_threshold = cache_threshold; +} + +static struct avc_callback_node *avc_callbacks; +static struct kmem_cache *avc_node_cachep; +static struct kmem_cache *avc_xperms_data_cachep; +static struct kmem_cache *avc_xperms_decision_cachep; +static struct kmem_cache *avc_xperms_cachep; + +static inline int avc_hash(u32 ssid, u32 tsid, u16 tclass) +{ + return (ssid ^ (tsid<<2) ^ (tclass<<4)) & (AVC_CACHE_SLOTS - 1); +} + +/** + * avc_init - Initialize the AVC. + * + * Initialize the access vector cache. + */ +void __init avc_init(void) +{ + avc_node_cachep = kmem_cache_create("avc_node", sizeof(struct avc_node), + 0, SLAB_PANIC, NULL); + avc_xperms_cachep = kmem_cache_create("avc_xperms_node", + sizeof(struct avc_xperms_node), + 0, SLAB_PANIC, NULL); + avc_xperms_decision_cachep = kmem_cache_create( + "avc_xperms_decision_node", + sizeof(struct avc_xperms_decision_node), + 0, SLAB_PANIC, NULL); + avc_xperms_data_cachep = kmem_cache_create("avc_xperms_data", + sizeof(struct extended_perms_data), + 0, SLAB_PANIC, NULL); +} + +int avc_get_hash_stats(struct selinux_avc *avc, char *page) +{ + int i, chain_len, max_chain_len, slots_used; + struct avc_node *node; + struct hlist_head *head; + + rcu_read_lock(); + + slots_used = 0; + max_chain_len = 0; + for (i = 0; i < AVC_CACHE_SLOTS; i++) { + head = &avc->avc_cache.slots[i]; + if (!hlist_empty(head)) { + slots_used++; + chain_len = 0; + hlist_for_each_entry_rcu(node, head, list) + chain_len++; + if (chain_len > max_chain_len) + max_chain_len = chain_len; + } + } + + rcu_read_unlock(); + + return scnprintf(page, PAGE_SIZE, "entries: %d\nbuckets used: %d/%d\n" + "longest chain: %d\n", + atomic_read(&avc->avc_cache.active_nodes), + slots_used, AVC_CACHE_SLOTS, max_chain_len); +} + +/* + * using a linked list for extended_perms_decision lookup because the list is + * always small. i.e. less than 5, typically 1 + */ +static struct extended_perms_decision *avc_xperms_decision_lookup(u8 driver, + struct avc_xperms_node *xp_node) +{ + struct avc_xperms_decision_node *xpd_node; + + list_for_each_entry(xpd_node, &xp_node->xpd_head, xpd_list) { + if (xpd_node->xpd.driver == driver) + return &xpd_node->xpd; + } + return NULL; +} + +static inline unsigned int +avc_xperms_has_perm(struct extended_perms_decision *xpd, + u8 perm, u8 which) +{ + unsigned int rc = 0; + + if ((which == XPERMS_ALLOWED) && + (xpd->used & XPERMS_ALLOWED)) + rc = security_xperm_test(xpd->allowed->p, perm); + else if ((which == XPERMS_AUDITALLOW) && + (xpd->used & XPERMS_AUDITALLOW)) + rc = security_xperm_test(xpd->auditallow->p, perm); + else if ((which == XPERMS_DONTAUDIT) && + (xpd->used & XPERMS_DONTAUDIT)) + rc = security_xperm_test(xpd->dontaudit->p, perm); + return rc; +} + +static void avc_xperms_allow_perm(struct avc_xperms_node *xp_node, + u8 driver, u8 perm) +{ + struct extended_perms_decision *xpd; + security_xperm_set(xp_node->xp.drivers.p, driver); + xpd = avc_xperms_decision_lookup(driver, xp_node); + if (xpd && xpd->allowed) + security_xperm_set(xpd->allowed->p, perm); +} + +static void avc_xperms_decision_free(struct avc_xperms_decision_node *xpd_node) +{ + struct extended_perms_decision *xpd; + + xpd = &xpd_node->xpd; + if (xpd->allowed) + kmem_cache_free(avc_xperms_data_cachep, xpd->allowed); + if (xpd->auditallow) + kmem_cache_free(avc_xperms_data_cachep, xpd->auditallow); + if (xpd->dontaudit) + kmem_cache_free(avc_xperms_data_cachep, xpd->dontaudit); + kmem_cache_free(avc_xperms_decision_cachep, xpd_node); +} + +static void avc_xperms_free(struct avc_xperms_node *xp_node) +{ + struct avc_xperms_decision_node *xpd_node, *tmp; + + if (!xp_node) + return; + + list_for_each_entry_safe(xpd_node, tmp, &xp_node->xpd_head, xpd_list) { + list_del(&xpd_node->xpd_list); + avc_xperms_decision_free(xpd_node); + } + kmem_cache_free(avc_xperms_cachep, xp_node); +} + +static void avc_copy_xperms_decision(struct extended_perms_decision *dest, + struct extended_perms_decision *src) +{ + dest->driver = src->driver; + dest->used = src->used; + if (dest->used & XPERMS_ALLOWED) + memcpy(dest->allowed->p, src->allowed->p, + sizeof(src->allowed->p)); + if (dest->used & XPERMS_AUDITALLOW) + memcpy(dest->auditallow->p, src->auditallow->p, + sizeof(src->auditallow->p)); + if (dest->used & XPERMS_DONTAUDIT) + memcpy(dest->dontaudit->p, src->dontaudit->p, + sizeof(src->dontaudit->p)); +} + +/* + * similar to avc_copy_xperms_decision, but only copy decision + * information relevant to this perm + */ +static inline void avc_quick_copy_xperms_decision(u8 perm, + struct extended_perms_decision *dest, + struct extended_perms_decision *src) +{ + /* + * compute index of the u32 of the 256 bits (8 u32s) that contain this + * command permission + */ + u8 i = perm >> 5; + + dest->used = src->used; + if (dest->used & XPERMS_ALLOWED) + dest->allowed->p[i] = src->allowed->p[i]; + if (dest->used & XPERMS_AUDITALLOW) + dest->auditallow->p[i] = src->auditallow->p[i]; + if (dest->used & XPERMS_DONTAUDIT) + dest->dontaudit->p[i] = src->dontaudit->p[i]; +} + +static struct avc_xperms_decision_node + *avc_xperms_decision_alloc(u8 which) +{ + struct avc_xperms_decision_node *xpd_node; + struct extended_perms_decision *xpd; + + xpd_node = kmem_cache_zalloc(avc_xperms_decision_cachep, + GFP_NOWAIT | __GFP_NOWARN); + if (!xpd_node) + return NULL; + + xpd = &xpd_node->xpd; + if (which & XPERMS_ALLOWED) { + xpd->allowed = kmem_cache_zalloc(avc_xperms_data_cachep, + GFP_NOWAIT | __GFP_NOWARN); + if (!xpd->allowed) + goto error; + } + if (which & XPERMS_AUDITALLOW) { + xpd->auditallow = kmem_cache_zalloc(avc_xperms_data_cachep, + GFP_NOWAIT | __GFP_NOWARN); + if (!xpd->auditallow) + goto error; + } + if (which & XPERMS_DONTAUDIT) { + xpd->dontaudit = kmem_cache_zalloc(avc_xperms_data_cachep, + GFP_NOWAIT | __GFP_NOWARN); + if (!xpd->dontaudit) + goto error; + } + return xpd_node; +error: + avc_xperms_decision_free(xpd_node); + return NULL; +} + +static int avc_add_xperms_decision(struct avc_node *node, + struct extended_perms_decision *src) +{ + struct avc_xperms_decision_node *dest_xpd; + + node->ae.xp_node->xp.len++; + dest_xpd = avc_xperms_decision_alloc(src->used); + if (!dest_xpd) + return -ENOMEM; + avc_copy_xperms_decision(&dest_xpd->xpd, src); + list_add(&dest_xpd->xpd_list, &node->ae.xp_node->xpd_head); + return 0; +} + +static struct avc_xperms_node *avc_xperms_alloc(void) +{ + struct avc_xperms_node *xp_node; + + xp_node = kmem_cache_zalloc(avc_xperms_cachep, GFP_NOWAIT | __GFP_NOWARN); + if (!xp_node) + return xp_node; + INIT_LIST_HEAD(&xp_node->xpd_head); + return xp_node; +} + +static int avc_xperms_populate(struct avc_node *node, + struct avc_xperms_node *src) +{ + struct avc_xperms_node *dest; + struct avc_xperms_decision_node *dest_xpd; + struct avc_xperms_decision_node *src_xpd; + + if (src->xp.len == 0) + return 0; + dest = avc_xperms_alloc(); + if (!dest) + return -ENOMEM; + + memcpy(dest->xp.drivers.p, src->xp.drivers.p, sizeof(dest->xp.drivers.p)); + dest->xp.len = src->xp.len; + + /* for each source xpd allocate a destination xpd and copy */ + list_for_each_entry(src_xpd, &src->xpd_head, xpd_list) { + dest_xpd = avc_xperms_decision_alloc(src_xpd->xpd.used); + if (!dest_xpd) + goto error; + avc_copy_xperms_decision(&dest_xpd->xpd, &src_xpd->xpd); + list_add(&dest_xpd->xpd_list, &dest->xpd_head); + } + node->ae.xp_node = dest; + return 0; +error: + avc_xperms_free(dest); + return -ENOMEM; + +} + +static inline u32 avc_xperms_audit_required(u32 requested, + struct av_decision *avd, + struct extended_perms_decision *xpd, + u8 perm, + int result, + u32 *deniedp) +{ + u32 denied, audited; + + denied = requested & ~avd->allowed; + if (unlikely(denied)) { + audited = denied & avd->auditdeny; + if (audited && xpd) { + if (avc_xperms_has_perm(xpd, perm, XPERMS_DONTAUDIT)) + audited &= ~requested; + } + } else if (result) { + audited = denied = requested; + } else { + audited = requested & avd->auditallow; + if (audited && xpd) { + if (!avc_xperms_has_perm(xpd, perm, XPERMS_AUDITALLOW)) + audited &= ~requested; + } + } + + *deniedp = denied; + return audited; +} + +static inline int avc_xperms_audit(struct selinux_state *state, + u32 ssid, u32 tsid, u16 tclass, + u32 requested, struct av_decision *avd, + struct extended_perms_decision *xpd, + u8 perm, int result, + struct common_audit_data *ad) +{ + u32 audited, denied; + + audited = avc_xperms_audit_required( + requested, avd, xpd, perm, result, &denied); + if (likely(!audited)) + return 0; + return slow_avc_audit(state, ssid, tsid, tclass, requested, + audited, denied, result, ad); +} + +static void avc_node_free(struct rcu_head *rhead) +{ + struct avc_node *node = container_of(rhead, struct avc_node, rhead); + avc_xperms_free(node->ae.xp_node); + kmem_cache_free(avc_node_cachep, node); + avc_cache_stats_incr(frees); +} + +static void avc_node_delete(struct selinux_avc *avc, struct avc_node *node) +{ + hlist_del_rcu(&node->list); + call_rcu(&node->rhead, avc_node_free); + atomic_dec(&avc->avc_cache.active_nodes); +} + +static void avc_node_kill(struct selinux_avc *avc, struct avc_node *node) +{ + avc_xperms_free(node->ae.xp_node); + kmem_cache_free(avc_node_cachep, node); + avc_cache_stats_incr(frees); + atomic_dec(&avc->avc_cache.active_nodes); +} + +static void avc_node_replace(struct selinux_avc *avc, + struct avc_node *new, struct avc_node *old) +{ + hlist_replace_rcu(&old->list, &new->list); + call_rcu(&old->rhead, avc_node_free); + atomic_dec(&avc->avc_cache.active_nodes); +} + +static inline int avc_reclaim_node(struct selinux_avc *avc) +{ + struct avc_node *node; + int hvalue, try, ecx; + unsigned long flags; + struct hlist_head *head; + spinlock_t *lock; + + for (try = 0, ecx = 0; try < AVC_CACHE_SLOTS; try++) { + hvalue = atomic_inc_return(&avc->avc_cache.lru_hint) & + (AVC_CACHE_SLOTS - 1); + head = &avc->avc_cache.slots[hvalue]; + lock = &avc->avc_cache.slots_lock[hvalue]; + + if (!spin_trylock_irqsave(lock, flags)) + continue; + + rcu_read_lock(); + hlist_for_each_entry(node, head, list) { + avc_node_delete(avc, node); + avc_cache_stats_incr(reclaims); + ecx++; + if (ecx >= AVC_CACHE_RECLAIM) { + rcu_read_unlock(); + spin_unlock_irqrestore(lock, flags); + goto out; + } + } + rcu_read_unlock(); + spin_unlock_irqrestore(lock, flags); + } +out: + return ecx; +} + +static struct avc_node *avc_alloc_node(struct selinux_avc *avc) +{ + struct avc_node *node; + + node = kmem_cache_zalloc(avc_node_cachep, GFP_NOWAIT | __GFP_NOWARN); + if (!node) + goto out; + + INIT_HLIST_NODE(&node->list); + avc_cache_stats_incr(allocations); + + if (atomic_inc_return(&avc->avc_cache.active_nodes) > + avc->avc_cache_threshold) + avc_reclaim_node(avc); + +out: + return node; +} + +static void avc_node_populate(struct avc_node *node, u32 ssid, u32 tsid, u16 tclass, struct av_decision *avd) +{ + node->ae.ssid = ssid; + node->ae.tsid = tsid; + node->ae.tclass = tclass; + memcpy(&node->ae.avd, avd, sizeof(node->ae.avd)); +} + +static inline struct avc_node *avc_search_node(struct selinux_avc *avc, + u32 ssid, u32 tsid, u16 tclass) +{ + struct avc_node *node, *ret = NULL; + int hvalue; + struct hlist_head *head; + + hvalue = avc_hash(ssid, tsid, tclass); + head = &avc->avc_cache.slots[hvalue]; + hlist_for_each_entry_rcu(node, head, list) { + if (ssid == node->ae.ssid && + tclass == node->ae.tclass && + tsid == node->ae.tsid) { + ret = node; + break; + } + } + + return ret; +} + +/** + * avc_lookup - Look up an AVC entry. + * @ssid: source security identifier + * @tsid: target security identifier + * @tclass: target security class + * + * Look up an AVC entry that is valid for the + * (@ssid, @tsid), interpreting the permissions + * based on @tclass. If a valid AVC entry exists, + * then this function returns the avc_node. + * Otherwise, this function returns NULL. + */ +static struct avc_node *avc_lookup(struct selinux_avc *avc, + u32 ssid, u32 tsid, u16 tclass) +{ + struct avc_node *node; + + avc_cache_stats_incr(lookups); + node = avc_search_node(avc, ssid, tsid, tclass); + + if (node) + return node; + + avc_cache_stats_incr(misses); + return NULL; +} + +static int avc_latest_notif_update(struct selinux_avc *avc, + int seqno, int is_insert) +{ + int ret = 0; + static DEFINE_SPINLOCK(notif_lock); + unsigned long flag; + + spin_lock_irqsave(¬if_lock, flag); + if (is_insert) { + if (seqno < avc->avc_cache.latest_notif) { + pr_warn("SELinux: avc: seqno %d < latest_notif %d\n", + seqno, avc->avc_cache.latest_notif); + ret = -EAGAIN; + } + } else { + if (seqno > avc->avc_cache.latest_notif) + avc->avc_cache.latest_notif = seqno; + } + spin_unlock_irqrestore(¬if_lock, flag); + + return ret; +} + +/** + * avc_insert - Insert an AVC entry. + * @ssid: source security identifier + * @tsid: target security identifier + * @tclass: target security class + * @avd: resulting av decision + * @xp_node: resulting extended permissions + * + * Insert an AVC entry for the SID pair + * (@ssid, @tsid) and class @tclass. + * The access vectors and the sequence number are + * normally provided by the security server in + * response to a security_compute_av() call. If the + * sequence number @avd->seqno is not less than the latest + * revocation notification, then the function copies + * the access vectors into a cache entry, returns + * avc_node inserted. Otherwise, this function returns NULL. + */ +static struct avc_node *avc_insert(struct selinux_avc *avc, + u32 ssid, u32 tsid, u16 tclass, + struct av_decision *avd, + struct avc_xperms_node *xp_node) +{ + struct avc_node *pos, *node = NULL; + int hvalue; + unsigned long flag; + spinlock_t *lock; + struct hlist_head *head; + + if (avc_latest_notif_update(avc, avd->seqno, 1)) + return NULL; + + node = avc_alloc_node(avc); + if (!node) + return NULL; + + avc_node_populate(node, ssid, tsid, tclass, avd); + if (avc_xperms_populate(node, xp_node)) { + avc_node_kill(avc, node); + return NULL; + } + + hvalue = avc_hash(ssid, tsid, tclass); + head = &avc->avc_cache.slots[hvalue]; + lock = &avc->avc_cache.slots_lock[hvalue]; + spin_lock_irqsave(lock, flag); + hlist_for_each_entry(pos, head, list) { + if (pos->ae.ssid == ssid && + pos->ae.tsid == tsid && + pos->ae.tclass == tclass) { + avc_node_replace(avc, node, pos); + goto found; + } + } + hlist_add_head_rcu(&node->list, head); +found: + spin_unlock_irqrestore(lock, flag); + return node; +} + +/** + * avc_audit_pre_callback - SELinux specific information + * will be called by generic audit code + * @ab: the audit buffer + * @a: audit_data + */ +static void avc_audit_pre_callback(struct audit_buffer *ab, void *a) +{ + struct common_audit_data *ad = a; + struct selinux_audit_data *sad = ad->selinux_audit_data; + u32 av = sad->audited; + const char **perms; + int i, perm; + + audit_log_format(ab, "avc: %s ", sad->denied ? "denied" : "granted"); + + if (av == 0) { + audit_log_format(ab, " null"); + return; + } + + perms = secclass_map[sad->tclass-1].perms; + + audit_log_format(ab, " {"); + i = 0; + perm = 1; + while (i < (sizeof(av) * 8)) { + if ((perm & av) && perms[i]) { + audit_log_format(ab, " %s", perms[i]); + av &= ~perm; + } + i++; + perm <<= 1; + } + + if (av) + audit_log_format(ab, " 0x%x", av); + + audit_log_format(ab, " } for "); +} + +/** + * avc_audit_post_callback - SELinux specific information + * will be called by generic audit code + * @ab: the audit buffer + * @a: audit_data + */ +static void avc_audit_post_callback(struct audit_buffer *ab, void *a) +{ + struct common_audit_data *ad = a; + struct selinux_audit_data *sad = ad->selinux_audit_data; + char *scontext = NULL; + char *tcontext = NULL; + const char *tclass = NULL; + u32 scontext_len; + u32 tcontext_len; + int rc; + + rc = security_sid_to_context(sad->state, sad->ssid, &scontext, + &scontext_len); + if (rc) + audit_log_format(ab, " ssid=%d", sad->ssid); + else + audit_log_format(ab, " scontext=%s", scontext); + + rc = security_sid_to_context(sad->state, sad->tsid, &tcontext, + &tcontext_len); + if (rc) + audit_log_format(ab, " tsid=%d", sad->tsid); + else + audit_log_format(ab, " tcontext=%s", tcontext); + + tclass = secclass_map[sad->tclass-1].name; + audit_log_format(ab, " tclass=%s", tclass); + + if (sad->denied) + audit_log_format(ab, " permissive=%u", sad->result ? 0 : 1); + + trace_selinux_audited(sad, scontext, tcontext, tclass); + kfree(tcontext); + kfree(scontext); + + /* in case of invalid context report also the actual context string */ + rc = security_sid_to_context_inval(sad->state, sad->ssid, &scontext, + &scontext_len); + if (!rc && scontext) { + if (scontext_len && scontext[scontext_len - 1] == '\0') + scontext_len--; + audit_log_format(ab, " srawcon="); + audit_log_n_untrustedstring(ab, scontext, scontext_len); + kfree(scontext); + } + + rc = security_sid_to_context_inval(sad->state, sad->tsid, &scontext, + &scontext_len); + if (!rc && scontext) { + if (scontext_len && scontext[scontext_len - 1] == '\0') + scontext_len--; + audit_log_format(ab, " trawcon="); + audit_log_n_untrustedstring(ab, scontext, scontext_len); + kfree(scontext); + } +} + +/* This is the slow part of avc audit with big stack footprint */ +noinline int slow_avc_audit(struct selinux_state *state, + u32 ssid, u32 tsid, u16 tclass, + u32 requested, u32 audited, u32 denied, int result, + struct common_audit_data *a) +{ + struct common_audit_data stack_data; + struct selinux_audit_data sad; + + if (WARN_ON(!tclass || tclass >= ARRAY_SIZE(secclass_map))) + return -EINVAL; + + if (!a) { + a = &stack_data; + a->type = LSM_AUDIT_DATA_NONE; + } + + sad.tclass = tclass; + sad.requested = requested; + sad.ssid = ssid; + sad.tsid = tsid; + sad.audited = audited; + sad.denied = denied; + sad.result = result; + sad.state = state; + + a->selinux_audit_data = &sad; + + common_lsm_audit(a, avc_audit_pre_callback, avc_audit_post_callback); + return 0; +} + +/** + * avc_add_callback - Register a callback for security events. + * @callback: callback function + * @events: security events + * + * Register a callback function for events in the set @events. + * Returns %0 on success or -%ENOMEM if insufficient memory + * exists to add the callback. + */ +int __init avc_add_callback(int (*callback)(u32 event), u32 events) +{ + struct avc_callback_node *c; + int rc = 0; + + c = kmalloc(sizeof(*c), GFP_KERNEL); + if (!c) { + rc = -ENOMEM; + goto out; + } + + c->callback = callback; + c->events = events; + c->next = avc_callbacks; + avc_callbacks = c; +out: + return rc; +} + +/** + * avc_update_node Update an AVC entry + * @event : Updating event + * @perms : Permission mask bits + * @ssid,@tsid,@tclass : identifier of an AVC entry + * @seqno : sequence number when decision was made + * @xpd: extended_perms_decision to be added to the node + * @flags: the AVC_* flags, e.g. AVC_NONBLOCKING, AVC_EXTENDED_PERMS, or 0. + * + * if a valid AVC entry doesn't exist,this function returns -ENOENT. + * if kmalloc() called internal returns NULL, this function returns -ENOMEM. + * otherwise, this function updates the AVC entry. The original AVC-entry object + * will release later by RCU. + */ +static int avc_update_node(struct selinux_avc *avc, + u32 event, u32 perms, u8 driver, u8 xperm, u32 ssid, + u32 tsid, u16 tclass, u32 seqno, + struct extended_perms_decision *xpd, + u32 flags) +{ + int hvalue, rc = 0; + unsigned long flag; + struct avc_node *pos, *node, *orig = NULL; + struct hlist_head *head; + spinlock_t *lock; + + /* + * If we are in a non-blocking code path, e.g. VFS RCU walk, + * then we must not add permissions to a cache entry + * because we will not audit the denial. Otherwise, + * during the subsequent blocking retry (e.g. VFS ref walk), we + * will find the permissions already granted in the cache entry + * and won't audit anything at all, leading to silent denials in + * permissive mode that only appear when in enforcing mode. + * + * See the corresponding handling of MAY_NOT_BLOCK in avc_audit() + * and selinux_inode_permission(). + */ + if (flags & AVC_NONBLOCKING) + return 0; + + node = avc_alloc_node(avc); + if (!node) { + rc = -ENOMEM; + goto out; + } + + /* Lock the target slot */ + hvalue = avc_hash(ssid, tsid, tclass); + + head = &avc->avc_cache.slots[hvalue]; + lock = &avc->avc_cache.slots_lock[hvalue]; + + spin_lock_irqsave(lock, flag); + + hlist_for_each_entry(pos, head, list) { + if (ssid == pos->ae.ssid && + tsid == pos->ae.tsid && + tclass == pos->ae.tclass && + seqno == pos->ae.avd.seqno){ + orig = pos; + break; + } + } + + if (!orig) { + rc = -ENOENT; + avc_node_kill(avc, node); + goto out_unlock; + } + + /* + * Copy and replace original node. + */ + + avc_node_populate(node, ssid, tsid, tclass, &orig->ae.avd); + + if (orig->ae.xp_node) { + rc = avc_xperms_populate(node, orig->ae.xp_node); + if (rc) { + avc_node_kill(avc, node); + goto out_unlock; + } + } + + switch (event) { + case AVC_CALLBACK_GRANT: + node->ae.avd.allowed |= perms; + if (node->ae.xp_node && (flags & AVC_EXTENDED_PERMS)) + avc_xperms_allow_perm(node->ae.xp_node, driver, xperm); + break; + case AVC_CALLBACK_TRY_REVOKE: + case AVC_CALLBACK_REVOKE: + node->ae.avd.allowed &= ~perms; + break; + case AVC_CALLBACK_AUDITALLOW_ENABLE: + node->ae.avd.auditallow |= perms; + break; + case AVC_CALLBACK_AUDITALLOW_DISABLE: + node->ae.avd.auditallow &= ~perms; + break; + case AVC_CALLBACK_AUDITDENY_ENABLE: + node->ae.avd.auditdeny |= perms; + break; + case AVC_CALLBACK_AUDITDENY_DISABLE: + node->ae.avd.auditdeny &= ~perms; + break; + case AVC_CALLBACK_ADD_XPERMS: + avc_add_xperms_decision(node, xpd); + break; + } + avc_node_replace(avc, node, orig); +out_unlock: + spin_unlock_irqrestore(lock, flag); +out: + return rc; +} + +/** + * avc_flush - Flush the cache + */ +static void avc_flush(struct selinux_avc *avc) +{ + struct hlist_head *head; + struct avc_node *node; + spinlock_t *lock; + unsigned long flag; + int i; + + for (i = 0; i < AVC_CACHE_SLOTS; i++) { + head = &avc->avc_cache.slots[i]; + lock = &avc->avc_cache.slots_lock[i]; + + spin_lock_irqsave(lock, flag); + /* + * With preemptable RCU, the outer spinlock does not + * prevent RCU grace periods from ending. + */ + rcu_read_lock(); + hlist_for_each_entry(node, head, list) + avc_node_delete(avc, node); + rcu_read_unlock(); + spin_unlock_irqrestore(lock, flag); + } +} + +/** + * avc_ss_reset - Flush the cache and revalidate migrated permissions. + * @seqno: policy sequence number + */ +int avc_ss_reset(struct selinux_avc *avc, u32 seqno) +{ + struct avc_callback_node *c; + int rc = 0, tmprc; + + avc_flush(avc); + + for (c = avc_callbacks; c; c = c->next) { + if (c->events & AVC_CALLBACK_RESET) { + tmprc = c->callback(AVC_CALLBACK_RESET); + /* save the first error encountered for the return + value and continue processing the callbacks */ + if (!rc) + rc = tmprc; + } + } + + avc_latest_notif_update(avc, seqno, 0); + return rc; +} + +/* + * Slow-path helper function for avc_has_perm_noaudit, + * when the avc_node lookup fails. We get called with + * the RCU read lock held, and need to return with it + * still held, but drop if for the security compute. + * + * Don't inline this, since it's the slow-path and just + * results in a bigger stack frame. + */ +static noinline +struct avc_node *avc_compute_av(struct selinux_state *state, + u32 ssid, u32 tsid, + u16 tclass, struct av_decision *avd, + struct avc_xperms_node *xp_node) +{ + rcu_read_unlock(); + INIT_LIST_HEAD(&xp_node->xpd_head); + security_compute_av(state, ssid, tsid, tclass, avd, &xp_node->xp); + rcu_read_lock(); + return avc_insert(state->avc, ssid, tsid, tclass, avd, xp_node); +} + +static noinline int avc_denied(struct selinux_state *state, + u32 ssid, u32 tsid, + u16 tclass, u32 requested, + u8 driver, u8 xperm, unsigned int flags, + struct av_decision *avd) +{ + if (flags & AVC_STRICT) + return -EACCES; + + if (enforcing_enabled(state) && + !(avd->flags & AVD_FLAGS_PERMISSIVE)) + return -EACCES; + + avc_update_node(state->avc, AVC_CALLBACK_GRANT, requested, driver, + xperm, ssid, tsid, tclass, avd->seqno, NULL, flags); + return 0; +} + +/* + * The avc extended permissions logic adds an additional 256 bits of + * permissions to an avc node when extended permissions for that node are + * specified in the avtab. If the additional 256 permissions is not adequate, + * as-is the case with ioctls, then multiple may be chained together and the + * driver field is used to specify which set contains the permission. + */ +int avc_has_extended_perms(struct selinux_state *state, + u32 ssid, u32 tsid, u16 tclass, u32 requested, + u8 driver, u8 xperm, struct common_audit_data *ad) +{ + struct avc_node *node; + struct av_decision avd; + u32 denied; + struct extended_perms_decision local_xpd; + struct extended_perms_decision *xpd = NULL; + struct extended_perms_data allowed; + struct extended_perms_data auditallow; + struct extended_perms_data dontaudit; + struct avc_xperms_node local_xp_node; + struct avc_xperms_node *xp_node; + int rc = 0, rc2; + + xp_node = &local_xp_node; + if (WARN_ON(!requested)) + return -EACCES; + + rcu_read_lock(); + + node = avc_lookup(state->avc, ssid, tsid, tclass); + if (unlikely(!node)) { + node = avc_compute_av(state, ssid, tsid, tclass, &avd, xp_node); + } else { + memcpy(&avd, &node->ae.avd, sizeof(avd)); + xp_node = node->ae.xp_node; + } + /* if extended permissions are not defined, only consider av_decision */ + if (!xp_node || !xp_node->xp.len) + goto decision; + + local_xpd.allowed = &allowed; + local_xpd.auditallow = &auditallow; + local_xpd.dontaudit = &dontaudit; + + xpd = avc_xperms_decision_lookup(driver, xp_node); + if (unlikely(!xpd)) { + /* + * Compute the extended_perms_decision only if the driver + * is flagged + */ + if (!security_xperm_test(xp_node->xp.drivers.p, driver)) { + avd.allowed &= ~requested; + goto decision; + } + rcu_read_unlock(); + security_compute_xperms_decision(state, ssid, tsid, tclass, + driver, &local_xpd); + rcu_read_lock(); + avc_update_node(state->avc, AVC_CALLBACK_ADD_XPERMS, requested, + driver, xperm, ssid, tsid, tclass, avd.seqno, + &local_xpd, 0); + } else { + avc_quick_copy_xperms_decision(xperm, &local_xpd, xpd); + } + xpd = &local_xpd; + + if (!avc_xperms_has_perm(xpd, xperm, XPERMS_ALLOWED)) + avd.allowed &= ~requested; + +decision: + denied = requested & ~(avd.allowed); + if (unlikely(denied)) + rc = avc_denied(state, ssid, tsid, tclass, requested, + driver, xperm, AVC_EXTENDED_PERMS, &avd); + + rcu_read_unlock(); + + rc2 = avc_xperms_audit(state, ssid, tsid, tclass, requested, + &avd, xpd, xperm, rc, ad); + if (rc2) + return rc2; + return rc; +} + +/** + * avc_has_perm_noaudit - Check permissions but perform no auditing. + * @ssid: source security identifier + * @tsid: target security identifier + * @tclass: target security class + * @requested: requested permissions, interpreted based on @tclass + * @flags: AVC_STRICT, AVC_NONBLOCKING, or 0 + * @avd: access vector decisions + * + * Check the AVC to determine whether the @requested permissions are granted + * for the SID pair (@ssid, @tsid), interpreting the permissions + * based on @tclass, and call the security server on a cache miss to obtain + * a new decision and add it to the cache. Return a copy of the decisions + * in @avd. Return %0 if all @requested permissions are granted, + * -%EACCES if any permissions are denied, or another -errno upon + * other errors. This function is typically called by avc_has_perm(), + * but may also be called directly to separate permission checking from + * auditing, e.g. in cases where a lock must be held for the check but + * should be released for the auditing. + */ +inline int avc_has_perm_noaudit(struct selinux_state *state, + u32 ssid, u32 tsid, + u16 tclass, u32 requested, + unsigned int flags, + struct av_decision *avd) +{ + struct avc_node *node; + struct avc_xperms_node xp_node; + int rc = 0; + u32 denied; + + if (WARN_ON(!requested)) + return -EACCES; + + rcu_read_lock(); + + node = avc_lookup(state->avc, ssid, tsid, tclass); + if (unlikely(!node)) + node = avc_compute_av(state, ssid, tsid, tclass, avd, &xp_node); + else + memcpy(avd, &node->ae.avd, sizeof(*avd)); + + denied = requested & ~(avd->allowed); + if (unlikely(denied)) + rc = avc_denied(state, ssid, tsid, tclass, requested, 0, 0, + flags, avd); + + rcu_read_unlock(); + return rc; +} + +/** + * avc_has_perm - Check permissions and perform any appropriate auditing. + * @ssid: source security identifier + * @tsid: target security identifier + * @tclass: target security class + * @requested: requested permissions, interpreted based on @tclass + * @auditdata: auxiliary audit data + * + * Check the AVC to determine whether the @requested permissions are granted + * for the SID pair (@ssid, @tsid), interpreting the permissions + * based on @tclass, and call the security server on a cache miss to obtain + * a new decision and add it to the cache. Audit the granting or denial of + * permissions in accordance with the policy. Return %0 if all @requested + * permissions are granted, -%EACCES if any permissions are denied, or + * another -errno upon other errors. + */ +int avc_has_perm(struct selinux_state *state, u32 ssid, u32 tsid, u16 tclass, + u32 requested, struct common_audit_data *auditdata) +{ + struct av_decision avd; + int rc, rc2; + + rc = avc_has_perm_noaudit(state, ssid, tsid, tclass, requested, 0, + &avd); + + rc2 = avc_audit(state, ssid, tsid, tclass, requested, &avd, rc, + auditdata, 0); + if (rc2) + return rc2; + return rc; +} + +int avc_has_perm_flags(struct selinux_state *state, + u32 ssid, u32 tsid, u16 tclass, u32 requested, + struct common_audit_data *auditdata, + int flags) +{ + struct av_decision avd; + int rc, rc2; + + rc = avc_has_perm_noaudit(state, ssid, tsid, tclass, requested, + (flags & MAY_NOT_BLOCK) ? AVC_NONBLOCKING : 0, + &avd); + + rc2 = avc_audit(state, ssid, tsid, tclass, requested, &avd, rc, + auditdata, flags); + if (rc2) + return rc2; + return rc; +} + +u32 avc_policy_seqno(struct selinux_state *state) +{ + return state->avc->avc_cache.latest_notif; +} + +void avc_disable(void) +{ + /* + * If you are looking at this because you have realized that we are + * not destroying the avc_node_cachep it might be easy to fix, but + * I don't know the memory barrier semantics well enough to know. It's + * possible that some other task dereferenced security_ops when + * it still pointed to selinux operations. If that is the case it's + * possible that it is about to use the avc and is about to need the + * avc_node_cachep. I know I could wrap the security.c security_ops call + * in an rcu_lock, but seriously, it's not worth it. Instead I just flush + * the cache and get that memory back. + */ + if (avc_node_cachep) { + avc_flush(selinux_state.avc); + /* kmem_cache_destroy(avc_node_cachep); */ + } +} diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c new file mode 100644 index 000000000..f545321d9 --- /dev/null +++ b/security/selinux/hooks.c @@ -0,0 +1,7447 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * NSA Security-Enhanced Linux (SELinux) security module + * + * This file contains the SELinux hook function implementations. + * + * Authors: Stephen Smalley, <sds@tycho.nsa.gov> + * Chris Vance, <cvance@nai.com> + * Wayne Salamon, <wsalamon@nai.com> + * James Morris <jmorris@redhat.com> + * + * Copyright (C) 2001,2002 Networks Associates Technology, Inc. + * Copyright (C) 2003-2008 Red Hat, Inc., James Morris <jmorris@redhat.com> + * Eric Paris <eparis@redhat.com> + * Copyright (C) 2004-2005 Trusted Computer Solutions, Inc. + * <dgoeddel@trustedcs.com> + * Copyright (C) 2006, 2007, 2009 Hewlett-Packard Development Company, L.P. + * Paul Moore <paul@paul-moore.com> + * Copyright (C) 2007 Hitachi Software Engineering Co., Ltd. + * Yuichi Nakamura <ynakam@hitachisoft.jp> + * Copyright (C) 2016 Mellanox Technologies + */ + +#include <linux/init.h> +#include <linux/kd.h> +#include <linux/kernel.h> +#include <linux/kernel_read_file.h> +#include <linux/tracehook.h> +#include <linux/errno.h> +#include <linux/sched/signal.h> +#include <linux/sched/task.h> +#include <linux/lsm_hooks.h> +#include <linux/xattr.h> +#include <linux/capability.h> +#include <linux/unistd.h> +#include <linux/mm.h> +#include <linux/mman.h> +#include <linux/slab.h> +#include <linux/pagemap.h> +#include <linux/proc_fs.h> +#include <linux/swap.h> +#include <linux/spinlock.h> +#include <linux/syscalls.h> +#include <linux/dcache.h> +#include <linux/file.h> +#include <linux/fdtable.h> +#include <linux/namei.h> +#include <linux/mount.h> +#include <linux/fs_context.h> +#include <linux/fs_parser.h> +#include <linux/netfilter_ipv4.h> +#include <linux/netfilter_ipv6.h> +#include <linux/tty.h> +#include <net/icmp.h> +#include <net/ip.h> /* for local_port_range[] */ +#include <net/tcp.h> /* struct or_callable used in sock_rcv_skb */ +#include <net/inet_connection_sock.h> +#include <net/net_namespace.h> +#include <net/netlabel.h> +#include <linux/uaccess.h> +#include <asm/ioctls.h> +#include <linux/atomic.h> +#include <linux/bitops.h> +#include <linux/interrupt.h> +#include <linux/netdevice.h> /* for network interface checks */ +#include <net/netlink.h> +#include <linux/tcp.h> +#include <linux/udp.h> +#include <linux/dccp.h> +#include <linux/sctp.h> +#include <net/sctp/structs.h> +#include <linux/quota.h> +#include <linux/un.h> /* for Unix socket types */ +#include <net/af_unix.h> /* for Unix socket types */ +#include <linux/parser.h> +#include <linux/nfs_mount.h> +#include <net/ipv6.h> +#include <linux/hugetlb.h> +#include <linux/personality.h> +#include <linux/audit.h> +#include <linux/string.h> +#include <linux/mutex.h> +#include <linux/posix-timers.h> +#include <linux/syslog.h> +#include <linux/user_namespace.h> +#include <linux/export.h> +#include <linux/msg.h> +#include <linux/shm.h> +#include <linux/bpf.h> +#include <linux/kernfs.h> +#include <linux/stringhash.h> /* for hashlen_string() */ +#include <uapi/linux/mount.h> +#include <linux/fsnotify.h> +#include <linux/fanotify.h> + +#include "avc.h" +#include "objsec.h" +#include "netif.h" +#include "netnode.h" +#include "netport.h" +#include "ibpkey.h" +#include "xfrm.h" +#include "netlabel.h" +#include "audit.h" +#include "avc_ss.h" + +struct selinux_state selinux_state; + +/* SECMARK reference count */ +static atomic_t selinux_secmark_refcount = ATOMIC_INIT(0); + +#ifdef CONFIG_SECURITY_SELINUX_DEVELOP +static int selinux_enforcing_boot __initdata; + +static int __init enforcing_setup(char *str) +{ + unsigned long enforcing; + if (!kstrtoul(str, 0, &enforcing)) + selinux_enforcing_boot = enforcing ? 1 : 0; + return 1; +} +__setup("enforcing=", enforcing_setup); +#else +#define selinux_enforcing_boot 1 +#endif + +int selinux_enabled_boot __initdata = 1; +#ifdef CONFIG_SECURITY_SELINUX_BOOTPARAM +static int __init selinux_enabled_setup(char *str) +{ + unsigned long enabled; + if (!kstrtoul(str, 0, &enabled)) + selinux_enabled_boot = enabled ? 1 : 0; + return 1; +} +__setup("selinux=", selinux_enabled_setup); +#endif + +static unsigned int selinux_checkreqprot_boot = + CONFIG_SECURITY_SELINUX_CHECKREQPROT_VALUE; + +static int __init checkreqprot_setup(char *str) +{ + unsigned long checkreqprot; + + if (!kstrtoul(str, 0, &checkreqprot)) { + selinux_checkreqprot_boot = checkreqprot ? 1 : 0; + if (checkreqprot) + pr_warn("SELinux: checkreqprot set to 1 via kernel parameter. This is deprecated and will be rejected in a future kernel release.\n"); + } + return 1; +} +__setup("checkreqprot=", checkreqprot_setup); + +/** + * selinux_secmark_enabled - Check to see if SECMARK is currently enabled + * + * Description: + * This function checks the SECMARK reference counter to see if any SECMARK + * targets are currently configured, if the reference counter is greater than + * zero SECMARK is considered to be enabled. Returns true (1) if SECMARK is + * enabled, false (0) if SECMARK is disabled. If the always_check_network + * policy capability is enabled, SECMARK is always considered enabled. + * + */ +static int selinux_secmark_enabled(void) +{ + return (selinux_policycap_alwaysnetwork() || + atomic_read(&selinux_secmark_refcount)); +} + +/** + * selinux_peerlbl_enabled - Check to see if peer labeling is currently enabled + * + * Description: + * This function checks if NetLabel or labeled IPSEC is enabled. Returns true + * (1) if any are enabled or false (0) if neither are enabled. If the + * always_check_network policy capability is enabled, peer labeling + * is always considered enabled. + * + */ +static int selinux_peerlbl_enabled(void) +{ + return (selinux_policycap_alwaysnetwork() || + netlbl_enabled() || selinux_xfrm_enabled()); +} + +static int selinux_netcache_avc_callback(u32 event) +{ + if (event == AVC_CALLBACK_RESET) { + sel_netif_flush(); + sel_netnode_flush(); + sel_netport_flush(); + synchronize_net(); + } + return 0; +} + +static int selinux_lsm_notifier_avc_callback(u32 event) +{ + if (event == AVC_CALLBACK_RESET) { + sel_ib_pkey_flush(); + call_blocking_lsm_notifier(LSM_POLICY_CHANGE, NULL); + } + + return 0; +} + +/* + * initialise the security for the init task + */ +static void cred_init_security(void) +{ + struct cred *cred = (struct cred *) current->real_cred; + struct task_security_struct *tsec; + + tsec = selinux_cred(cred); + tsec->osid = tsec->sid = SECINITSID_KERNEL; +} + +/* + * get the security ID of a set of credentials + */ +static inline u32 cred_sid(const struct cred *cred) +{ + const struct task_security_struct *tsec; + + tsec = selinux_cred(cred); + return tsec->sid; +} + +/* + * get the objective security ID of a task + */ +static inline u32 task_sid(const struct task_struct *task) +{ + u32 sid; + + rcu_read_lock(); + sid = cred_sid(__task_cred(task)); + rcu_read_unlock(); + return sid; +} + +static int inode_doinit_with_dentry(struct inode *inode, struct dentry *opt_dentry); + +/* + * Try reloading inode security labels that have been marked as invalid. The + * @may_sleep parameter indicates when sleeping and thus reloading labels is + * allowed; when set to false, returns -ECHILD when the label is + * invalid. The @dentry parameter should be set to a dentry of the inode. + */ +static int __inode_security_revalidate(struct inode *inode, + struct dentry *dentry, + bool may_sleep) +{ + struct inode_security_struct *isec = selinux_inode(inode); + + might_sleep_if(may_sleep); + + if (selinux_initialized(&selinux_state) && + isec->initialized != LABEL_INITIALIZED) { + if (!may_sleep) + return -ECHILD; + + /* + * Try reloading the inode security label. This will fail if + * @opt_dentry is NULL and no dentry for this inode can be + * found; in that case, continue using the old label. + */ + inode_doinit_with_dentry(inode, dentry); + } + return 0; +} + +static struct inode_security_struct *inode_security_novalidate(struct inode *inode) +{ + return selinux_inode(inode); +} + +static struct inode_security_struct *inode_security_rcu(struct inode *inode, bool rcu) +{ + int error; + + error = __inode_security_revalidate(inode, NULL, !rcu); + if (error) + return ERR_PTR(error); + return selinux_inode(inode); +} + +/* + * Get the security label of an inode. + */ +static struct inode_security_struct *inode_security(struct inode *inode) +{ + __inode_security_revalidate(inode, NULL, true); + return selinux_inode(inode); +} + +static struct inode_security_struct *backing_inode_security_novalidate(struct dentry *dentry) +{ + struct inode *inode = d_backing_inode(dentry); + + return selinux_inode(inode); +} + +/* + * Get the security label of a dentry's backing inode. + */ +static struct inode_security_struct *backing_inode_security(struct dentry *dentry) +{ + struct inode *inode = d_backing_inode(dentry); + + __inode_security_revalidate(inode, dentry, true); + return selinux_inode(inode); +} + +static void inode_free_security(struct inode *inode) +{ + struct inode_security_struct *isec = selinux_inode(inode); + struct superblock_security_struct *sbsec; + + if (!isec) + return; + sbsec = inode->i_sb->s_security; + /* + * As not all inode security structures are in a list, we check for + * empty list outside of the lock to make sure that we won't waste + * time taking a lock doing nothing. + * + * The list_del_init() function can be safely called more than once. + * It should not be possible for this function to be called with + * concurrent list_add(), but for better safety against future changes + * in the code, we use list_empty_careful() here. + */ + if (!list_empty_careful(&isec->list)) { + spin_lock(&sbsec->isec_lock); + list_del_init(&isec->list); + spin_unlock(&sbsec->isec_lock); + } +} + +static void superblock_free_security(struct super_block *sb) +{ + struct superblock_security_struct *sbsec = sb->s_security; + sb->s_security = NULL; + kfree(sbsec); +} + +struct selinux_mnt_opts { + const char *fscontext, *context, *rootcontext, *defcontext; +}; + +static void selinux_free_mnt_opts(void *mnt_opts) +{ + struct selinux_mnt_opts *opts = mnt_opts; + kfree(opts->fscontext); + kfree(opts->context); + kfree(opts->rootcontext); + kfree(opts->defcontext); + kfree(opts); +} + +enum { + Opt_error = -1, + Opt_context = 0, + Opt_defcontext = 1, + Opt_fscontext = 2, + Opt_rootcontext = 3, + Opt_seclabel = 4, +}; + +#define A(s, has_arg) {#s, sizeof(#s) - 1, Opt_##s, has_arg} +static struct { + const char *name; + int len; + int opt; + bool has_arg; +} tokens[] = { + A(context, true), + A(fscontext, true), + A(defcontext, true), + A(rootcontext, true), + A(seclabel, false), +}; +#undef A + +static int match_opt_prefix(char *s, int l, char **arg) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(tokens); i++) { + size_t len = tokens[i].len; + if (len > l || memcmp(s, tokens[i].name, len)) + continue; + if (tokens[i].has_arg) { + if (len == l || s[len] != '=') + continue; + *arg = s + len + 1; + } else if (len != l) + continue; + return tokens[i].opt; + } + return Opt_error; +} + +#define SEL_MOUNT_FAIL_MSG "SELinux: duplicate or incompatible mount options\n" + +static int may_context_mount_sb_relabel(u32 sid, + struct superblock_security_struct *sbsec, + const struct cred *cred) +{ + const struct task_security_struct *tsec = selinux_cred(cred); + int rc; + + rc = avc_has_perm(&selinux_state, + tsec->sid, sbsec->sid, SECCLASS_FILESYSTEM, + FILESYSTEM__RELABELFROM, NULL); + if (rc) + return rc; + + rc = avc_has_perm(&selinux_state, + tsec->sid, sid, SECCLASS_FILESYSTEM, + FILESYSTEM__RELABELTO, NULL); + return rc; +} + +static int may_context_mount_inode_relabel(u32 sid, + struct superblock_security_struct *sbsec, + const struct cred *cred) +{ + const struct task_security_struct *tsec = selinux_cred(cred); + int rc; + rc = avc_has_perm(&selinux_state, + tsec->sid, sbsec->sid, SECCLASS_FILESYSTEM, + FILESYSTEM__RELABELFROM, NULL); + if (rc) + return rc; + + rc = avc_has_perm(&selinux_state, + sid, sbsec->sid, SECCLASS_FILESYSTEM, + FILESYSTEM__ASSOCIATE, NULL); + return rc; +} + +static int selinux_is_genfs_special_handling(struct super_block *sb) +{ + /* Special handling. Genfs but also in-core setxattr handler */ + return !strcmp(sb->s_type->name, "sysfs") || + !strcmp(sb->s_type->name, "pstore") || + !strcmp(sb->s_type->name, "debugfs") || + !strcmp(sb->s_type->name, "tracefs") || + !strcmp(sb->s_type->name, "rootfs") || + (selinux_policycap_cgroupseclabel() && + (!strcmp(sb->s_type->name, "cgroup") || + !strcmp(sb->s_type->name, "cgroup2"))); +} + +static int selinux_is_sblabel_mnt(struct super_block *sb) +{ + struct superblock_security_struct *sbsec = sb->s_security; + + /* + * IMPORTANT: Double-check logic in this function when adding a new + * SECURITY_FS_USE_* definition! + */ + BUILD_BUG_ON(SECURITY_FS_USE_MAX != 7); + + switch (sbsec->behavior) { + case SECURITY_FS_USE_XATTR: + case SECURITY_FS_USE_TRANS: + case SECURITY_FS_USE_TASK: + case SECURITY_FS_USE_NATIVE: + return 1; + + case SECURITY_FS_USE_GENFS: + return selinux_is_genfs_special_handling(sb); + + /* Never allow relabeling on context mounts */ + case SECURITY_FS_USE_MNTPOINT: + case SECURITY_FS_USE_NONE: + default: + return 0; + } +} + +static int sb_finish_set_opts(struct super_block *sb) +{ + struct superblock_security_struct *sbsec = sb->s_security; + struct dentry *root = sb->s_root; + struct inode *root_inode = d_backing_inode(root); + int rc = 0; + + if (sbsec->behavior == SECURITY_FS_USE_XATTR) { + /* Make sure that the xattr handler exists and that no + error other than -ENODATA is returned by getxattr on + the root directory. -ENODATA is ok, as this may be + the first boot of the SELinux kernel before we have + assigned xattr values to the filesystem. */ + if (!(root_inode->i_opflags & IOP_XATTR)) { + pr_warn("SELinux: (dev %s, type %s) has no " + "xattr support\n", sb->s_id, sb->s_type->name); + rc = -EOPNOTSUPP; + goto out; + } + + rc = __vfs_getxattr(root, root_inode, XATTR_NAME_SELINUX, NULL, 0); + if (rc < 0 && rc != -ENODATA) { + if (rc == -EOPNOTSUPP) + pr_warn("SELinux: (dev %s, type " + "%s) has no security xattr handler\n", + sb->s_id, sb->s_type->name); + else + pr_warn("SELinux: (dev %s, type " + "%s) getxattr errno %d\n", sb->s_id, + sb->s_type->name, -rc); + goto out; + } + } + + sbsec->flags |= SE_SBINITIALIZED; + + /* + * Explicitly set or clear SBLABEL_MNT. It's not sufficient to simply + * leave the flag untouched because sb_clone_mnt_opts might be handing + * us a superblock that needs the flag to be cleared. + */ + if (selinux_is_sblabel_mnt(sb)) + sbsec->flags |= SBLABEL_MNT; + else + sbsec->flags &= ~SBLABEL_MNT; + + /* Initialize the root inode. */ + rc = inode_doinit_with_dentry(root_inode, root); + + /* Initialize any other inodes associated with the superblock, e.g. + inodes created prior to initial policy load or inodes created + during get_sb by a pseudo filesystem that directly + populates itself. */ + spin_lock(&sbsec->isec_lock); + while (!list_empty(&sbsec->isec_head)) { + struct inode_security_struct *isec = + list_first_entry(&sbsec->isec_head, + struct inode_security_struct, list); + struct inode *inode = isec->inode; + list_del_init(&isec->list); + spin_unlock(&sbsec->isec_lock); + inode = igrab(inode); + if (inode) { + if (!IS_PRIVATE(inode)) + inode_doinit_with_dentry(inode, NULL); + iput(inode); + } + spin_lock(&sbsec->isec_lock); + } + spin_unlock(&sbsec->isec_lock); +out: + return rc; +} + +static int bad_option(struct superblock_security_struct *sbsec, char flag, + u32 old_sid, u32 new_sid) +{ + char mnt_flags = sbsec->flags & SE_MNTMASK; + + /* check if the old mount command had the same options */ + if (sbsec->flags & SE_SBINITIALIZED) + if (!(sbsec->flags & flag) || + (old_sid != new_sid)) + return 1; + + /* check if we were passed the same options twice, + * aka someone passed context=a,context=b + */ + if (!(sbsec->flags & SE_SBINITIALIZED)) + if (mnt_flags & flag) + return 1; + return 0; +} + +static int parse_sid(struct super_block *sb, const char *s, u32 *sid) +{ + int rc = security_context_str_to_sid(&selinux_state, s, + sid, GFP_KERNEL); + if (rc) + pr_warn("SELinux: security_context_str_to_sid" + "(%s) failed for (dev %s, type %s) errno=%d\n", + s, sb->s_id, sb->s_type->name, rc); + return rc; +} + +/* + * Allow filesystems with binary mount data to explicitly set mount point + * labeling information. + */ +static int selinux_set_mnt_opts(struct super_block *sb, + void *mnt_opts, + unsigned long kern_flags, + unsigned long *set_kern_flags) +{ + const struct cred *cred = current_cred(); + struct superblock_security_struct *sbsec = sb->s_security; + struct dentry *root = sbsec->sb->s_root; + struct selinux_mnt_opts *opts = mnt_opts; + struct inode_security_struct *root_isec; + u32 fscontext_sid = 0, context_sid = 0, rootcontext_sid = 0; + u32 defcontext_sid = 0; + int rc = 0; + + mutex_lock(&sbsec->lock); + + if (!selinux_initialized(&selinux_state)) { + if (!opts) { + /* Defer initialization until selinux_complete_init, + after the initial policy is loaded and the security + server is ready to handle calls. */ + goto out; + } + rc = -EINVAL; + pr_warn("SELinux: Unable to set superblock options " + "before the security server is initialized\n"); + goto out; + } + if (kern_flags && !set_kern_flags) { + /* Specifying internal flags without providing a place to + * place the results is not allowed */ + rc = -EINVAL; + goto out; + } + + /* + * Binary mount data FS will come through this function twice. Once + * from an explicit call and once from the generic calls from the vfs. + * Since the generic VFS calls will not contain any security mount data + * we need to skip the double mount verification. + * + * This does open a hole in which we will not notice if the first + * mount using this sb set explict options and a second mount using + * this sb does not set any security options. (The first options + * will be used for both mounts) + */ + if ((sbsec->flags & SE_SBINITIALIZED) && (sb->s_type->fs_flags & FS_BINARY_MOUNTDATA) + && !opts) + goto out; + + root_isec = backing_inode_security_novalidate(root); + + /* + * parse the mount options, check if they are valid sids. + * also check if someone is trying to mount the same sb more + * than once with different security options. + */ + if (opts) { + if (opts->fscontext) { + rc = parse_sid(sb, opts->fscontext, &fscontext_sid); + if (rc) + goto out; + if (bad_option(sbsec, FSCONTEXT_MNT, sbsec->sid, + fscontext_sid)) + goto out_double_mount; + sbsec->flags |= FSCONTEXT_MNT; + } + if (opts->context) { + rc = parse_sid(sb, opts->context, &context_sid); + if (rc) + goto out; + if (bad_option(sbsec, CONTEXT_MNT, sbsec->mntpoint_sid, + context_sid)) + goto out_double_mount; + sbsec->flags |= CONTEXT_MNT; + } + if (opts->rootcontext) { + rc = parse_sid(sb, opts->rootcontext, &rootcontext_sid); + if (rc) + goto out; + if (bad_option(sbsec, ROOTCONTEXT_MNT, root_isec->sid, + rootcontext_sid)) + goto out_double_mount; + sbsec->flags |= ROOTCONTEXT_MNT; + } + if (opts->defcontext) { + rc = parse_sid(sb, opts->defcontext, &defcontext_sid); + if (rc) + goto out; + if (bad_option(sbsec, DEFCONTEXT_MNT, sbsec->def_sid, + defcontext_sid)) + goto out_double_mount; + sbsec->flags |= DEFCONTEXT_MNT; + } + } + + if (sbsec->flags & SE_SBINITIALIZED) { + /* previously mounted with options, but not on this attempt? */ + if ((sbsec->flags & SE_MNTMASK) && !opts) + goto out_double_mount; + rc = 0; + goto out; + } + + if (strcmp(sb->s_type->name, "proc") == 0) + sbsec->flags |= SE_SBPROC | SE_SBGENFS; + + if (!strcmp(sb->s_type->name, "debugfs") || + !strcmp(sb->s_type->name, "tracefs") || + !strcmp(sb->s_type->name, "binder") || + !strcmp(sb->s_type->name, "bpf") || + !strcmp(sb->s_type->name, "pstore")) + sbsec->flags |= SE_SBGENFS; + + if (!strcmp(sb->s_type->name, "sysfs") || + !strcmp(sb->s_type->name, "cgroup") || + !strcmp(sb->s_type->name, "cgroup2")) + sbsec->flags |= SE_SBGENFS | SE_SBGENFS_XATTR; + + if (!sbsec->behavior) { + /* + * Determine the labeling behavior to use for this + * filesystem type. + */ + rc = security_fs_use(&selinux_state, sb); + if (rc) { + pr_warn("%s: security_fs_use(%s) returned %d\n", + __func__, sb->s_type->name, rc); + goto out; + } + } + + /* + * If this is a user namespace mount and the filesystem type is not + * explicitly whitelisted, then no contexts are allowed on the command + * line and security labels must be ignored. + */ + if (sb->s_user_ns != &init_user_ns && + strcmp(sb->s_type->name, "tmpfs") && + strcmp(sb->s_type->name, "ramfs") && + strcmp(sb->s_type->name, "devpts")) { + if (context_sid || fscontext_sid || rootcontext_sid || + defcontext_sid) { + rc = -EACCES; + goto out; + } + if (sbsec->behavior == SECURITY_FS_USE_XATTR) { + sbsec->behavior = SECURITY_FS_USE_MNTPOINT; + rc = security_transition_sid(&selinux_state, + current_sid(), + current_sid(), + SECCLASS_FILE, NULL, + &sbsec->mntpoint_sid); + if (rc) + goto out; + } + goto out_set_opts; + } + + /* sets the context of the superblock for the fs being mounted. */ + if (fscontext_sid) { + rc = may_context_mount_sb_relabel(fscontext_sid, sbsec, cred); + if (rc) + goto out; + + sbsec->sid = fscontext_sid; + } + + /* + * Switch to using mount point labeling behavior. + * sets the label used on all file below the mountpoint, and will set + * the superblock context if not already set. + */ + if (kern_flags & SECURITY_LSM_NATIVE_LABELS && !context_sid) { + sbsec->behavior = SECURITY_FS_USE_NATIVE; + *set_kern_flags |= SECURITY_LSM_NATIVE_LABELS; + } + + if (context_sid) { + if (!fscontext_sid) { + rc = may_context_mount_sb_relabel(context_sid, sbsec, + cred); + if (rc) + goto out; + sbsec->sid = context_sid; + } else { + rc = may_context_mount_inode_relabel(context_sid, sbsec, + cred); + if (rc) + goto out; + } + if (!rootcontext_sid) + rootcontext_sid = context_sid; + + sbsec->mntpoint_sid = context_sid; + sbsec->behavior = SECURITY_FS_USE_MNTPOINT; + } + + if (rootcontext_sid) { + rc = may_context_mount_inode_relabel(rootcontext_sid, sbsec, + cred); + if (rc) + goto out; + + root_isec->sid = rootcontext_sid; + root_isec->initialized = LABEL_INITIALIZED; + } + + if (defcontext_sid) { + if (sbsec->behavior != SECURITY_FS_USE_XATTR && + sbsec->behavior != SECURITY_FS_USE_NATIVE) { + rc = -EINVAL; + pr_warn("SELinux: defcontext option is " + "invalid for this filesystem type\n"); + goto out; + } + + if (defcontext_sid != sbsec->def_sid) { + rc = may_context_mount_inode_relabel(defcontext_sid, + sbsec, cred); + if (rc) + goto out; + } + + sbsec->def_sid = defcontext_sid; + } + +out_set_opts: + rc = sb_finish_set_opts(sb); +out: + mutex_unlock(&sbsec->lock); + return rc; +out_double_mount: + rc = -EINVAL; + pr_warn("SELinux: mount invalid. Same superblock, different " + "security settings for (dev %s, type %s)\n", sb->s_id, + sb->s_type->name); + goto out; +} + +static int selinux_cmp_sb_context(const struct super_block *oldsb, + const struct super_block *newsb) +{ + struct superblock_security_struct *old = oldsb->s_security; + struct superblock_security_struct *new = newsb->s_security; + char oldflags = old->flags & SE_MNTMASK; + char newflags = new->flags & SE_MNTMASK; + + if (oldflags != newflags) + goto mismatch; + if ((oldflags & FSCONTEXT_MNT) && old->sid != new->sid) + goto mismatch; + if ((oldflags & CONTEXT_MNT) && old->mntpoint_sid != new->mntpoint_sid) + goto mismatch; + if ((oldflags & DEFCONTEXT_MNT) && old->def_sid != new->def_sid) + goto mismatch; + if (oldflags & ROOTCONTEXT_MNT) { + struct inode_security_struct *oldroot = backing_inode_security(oldsb->s_root); + struct inode_security_struct *newroot = backing_inode_security(newsb->s_root); + if (oldroot->sid != newroot->sid) + goto mismatch; + } + return 0; +mismatch: + pr_warn("SELinux: mount invalid. Same superblock, " + "different security settings for (dev %s, " + "type %s)\n", newsb->s_id, newsb->s_type->name); + return -EBUSY; +} + +static int selinux_sb_clone_mnt_opts(const struct super_block *oldsb, + struct super_block *newsb, + unsigned long kern_flags, + unsigned long *set_kern_flags) +{ + int rc = 0; + const struct superblock_security_struct *oldsbsec = oldsb->s_security; + struct superblock_security_struct *newsbsec = newsb->s_security; + + int set_fscontext = (oldsbsec->flags & FSCONTEXT_MNT); + int set_context = (oldsbsec->flags & CONTEXT_MNT); + int set_rootcontext = (oldsbsec->flags & ROOTCONTEXT_MNT); + + /* + * if the parent was able to be mounted it clearly had no special lsm + * mount options. thus we can safely deal with this superblock later + */ + if (!selinux_initialized(&selinux_state)) + return 0; + + /* + * Specifying internal flags without providing a place to + * place the results is not allowed. + */ + if (kern_flags && !set_kern_flags) + return -EINVAL; + + /* how can we clone if the old one wasn't set up?? */ + BUG_ON(!(oldsbsec->flags & SE_SBINITIALIZED)); + + /* if fs is reusing a sb, make sure that the contexts match */ + if (newsbsec->flags & SE_SBINITIALIZED) { + if ((kern_flags & SECURITY_LSM_NATIVE_LABELS) && !set_context) + *set_kern_flags |= SECURITY_LSM_NATIVE_LABELS; + return selinux_cmp_sb_context(oldsb, newsb); + } + + mutex_lock(&newsbsec->lock); + + newsbsec->flags = oldsbsec->flags; + + newsbsec->sid = oldsbsec->sid; + newsbsec->def_sid = oldsbsec->def_sid; + newsbsec->behavior = oldsbsec->behavior; + + if (newsbsec->behavior == SECURITY_FS_USE_NATIVE && + !(kern_flags & SECURITY_LSM_NATIVE_LABELS) && !set_context) { + rc = security_fs_use(&selinux_state, newsb); + if (rc) + goto out; + } + + if (kern_flags & SECURITY_LSM_NATIVE_LABELS && !set_context) { + newsbsec->behavior = SECURITY_FS_USE_NATIVE; + *set_kern_flags |= SECURITY_LSM_NATIVE_LABELS; + } + + if (set_context) { + u32 sid = oldsbsec->mntpoint_sid; + + if (!set_fscontext) + newsbsec->sid = sid; + if (!set_rootcontext) { + struct inode_security_struct *newisec = backing_inode_security(newsb->s_root); + newisec->sid = sid; + } + newsbsec->mntpoint_sid = sid; + } + if (set_rootcontext) { + const struct inode_security_struct *oldisec = backing_inode_security(oldsb->s_root); + struct inode_security_struct *newisec = backing_inode_security(newsb->s_root); + + newisec->sid = oldisec->sid; + } + + sb_finish_set_opts(newsb); +out: + mutex_unlock(&newsbsec->lock); + return rc; +} + +static int selinux_add_opt(int token, const char *s, void **mnt_opts) +{ + struct selinux_mnt_opts *opts = *mnt_opts; + bool is_alloc_opts = false; + + if (token == Opt_seclabel) /* eaten and completely ignored */ + return 0; + + if (!s) + return -ENOMEM; + + if (!opts) { + opts = kzalloc(sizeof(struct selinux_mnt_opts), GFP_KERNEL); + if (!opts) + return -ENOMEM; + *mnt_opts = opts; + is_alloc_opts = true; + } + + switch (token) { + case Opt_context: + if (opts->context || opts->defcontext) + goto Einval; + opts->context = s; + break; + case Opt_fscontext: + if (opts->fscontext) + goto Einval; + opts->fscontext = s; + break; + case Opt_rootcontext: + if (opts->rootcontext) + goto Einval; + opts->rootcontext = s; + break; + case Opt_defcontext: + if (opts->context || opts->defcontext) + goto Einval; + opts->defcontext = s; + break; + } + return 0; +Einval: + if (is_alloc_opts) { + kfree(opts); + *mnt_opts = NULL; + } + pr_warn(SEL_MOUNT_FAIL_MSG); + return -EINVAL; +} + +static int selinux_add_mnt_opt(const char *option, const char *val, int len, + void **mnt_opts) +{ + int token = Opt_error; + int rc, i; + + for (i = 0; i < ARRAY_SIZE(tokens); i++) { + if (strcmp(option, tokens[i].name) == 0) { + token = tokens[i].opt; + break; + } + } + + if (token == Opt_error) + return -EINVAL; + + if (token != Opt_seclabel) { + val = kmemdup_nul(val, len, GFP_KERNEL); + if (!val) { + rc = -ENOMEM; + goto free_opt; + } + } + rc = selinux_add_opt(token, val, mnt_opts); + if (unlikely(rc)) { + kfree(val); + goto free_opt; + } + return rc; + +free_opt: + if (*mnt_opts) { + selinux_free_mnt_opts(*mnt_opts); + *mnt_opts = NULL; + } + return rc; +} + +static int show_sid(struct seq_file *m, u32 sid) +{ + char *context = NULL; + u32 len; + int rc; + + rc = security_sid_to_context(&selinux_state, sid, + &context, &len); + if (!rc) { + bool has_comma = context && strchr(context, ','); + + seq_putc(m, '='); + if (has_comma) + seq_putc(m, '\"'); + seq_escape(m, context, "\"\n\\"); + if (has_comma) + seq_putc(m, '\"'); + } + kfree(context); + return rc; +} + +static int selinux_sb_show_options(struct seq_file *m, struct super_block *sb) +{ + struct superblock_security_struct *sbsec = sb->s_security; + int rc; + + if (!(sbsec->flags & SE_SBINITIALIZED)) + return 0; + + if (!selinux_initialized(&selinux_state)) + return 0; + + if (sbsec->flags & FSCONTEXT_MNT) { + seq_putc(m, ','); + seq_puts(m, FSCONTEXT_STR); + rc = show_sid(m, sbsec->sid); + if (rc) + return rc; + } + if (sbsec->flags & CONTEXT_MNT) { + seq_putc(m, ','); + seq_puts(m, CONTEXT_STR); + rc = show_sid(m, sbsec->mntpoint_sid); + if (rc) + return rc; + } + if (sbsec->flags & DEFCONTEXT_MNT) { + seq_putc(m, ','); + seq_puts(m, DEFCONTEXT_STR); + rc = show_sid(m, sbsec->def_sid); + if (rc) + return rc; + } + if (sbsec->flags & ROOTCONTEXT_MNT) { + struct dentry *root = sbsec->sb->s_root; + struct inode_security_struct *isec = backing_inode_security(root); + seq_putc(m, ','); + seq_puts(m, ROOTCONTEXT_STR); + rc = show_sid(m, isec->sid); + if (rc) + return rc; + } + if (sbsec->flags & SBLABEL_MNT) { + seq_putc(m, ','); + seq_puts(m, SECLABEL_STR); + } + return 0; +} + +static inline u16 inode_mode_to_security_class(umode_t mode) +{ + switch (mode & S_IFMT) { + case S_IFSOCK: + return SECCLASS_SOCK_FILE; + case S_IFLNK: + return SECCLASS_LNK_FILE; + case S_IFREG: + return SECCLASS_FILE; + case S_IFBLK: + return SECCLASS_BLK_FILE; + case S_IFDIR: + return SECCLASS_DIR; + case S_IFCHR: + return SECCLASS_CHR_FILE; + case S_IFIFO: + return SECCLASS_FIFO_FILE; + + } + + return SECCLASS_FILE; +} + +static inline int default_protocol_stream(int protocol) +{ + return (protocol == IPPROTO_IP || protocol == IPPROTO_TCP); +} + +static inline int default_protocol_dgram(int protocol) +{ + return (protocol == IPPROTO_IP || protocol == IPPROTO_UDP); +} + +static inline u16 socket_type_to_security_class(int family, int type, int protocol) +{ + int extsockclass = selinux_policycap_extsockclass(); + + switch (family) { + case PF_UNIX: + switch (type) { + case SOCK_STREAM: + case SOCK_SEQPACKET: + return SECCLASS_UNIX_STREAM_SOCKET; + case SOCK_DGRAM: + case SOCK_RAW: + return SECCLASS_UNIX_DGRAM_SOCKET; + } + break; + case PF_INET: + case PF_INET6: + switch (type) { + case SOCK_STREAM: + case SOCK_SEQPACKET: + if (default_protocol_stream(protocol)) + return SECCLASS_TCP_SOCKET; + else if (extsockclass && protocol == IPPROTO_SCTP) + return SECCLASS_SCTP_SOCKET; + else + return SECCLASS_RAWIP_SOCKET; + case SOCK_DGRAM: + if (default_protocol_dgram(protocol)) + return SECCLASS_UDP_SOCKET; + else if (extsockclass && (protocol == IPPROTO_ICMP || + protocol == IPPROTO_ICMPV6)) + return SECCLASS_ICMP_SOCKET; + else + return SECCLASS_RAWIP_SOCKET; + case SOCK_DCCP: + return SECCLASS_DCCP_SOCKET; + default: + return SECCLASS_RAWIP_SOCKET; + } + break; + case PF_NETLINK: + switch (protocol) { + case NETLINK_ROUTE: + return SECCLASS_NETLINK_ROUTE_SOCKET; + case NETLINK_SOCK_DIAG: + return SECCLASS_NETLINK_TCPDIAG_SOCKET; + case NETLINK_NFLOG: + return SECCLASS_NETLINK_NFLOG_SOCKET; + case NETLINK_XFRM: + return SECCLASS_NETLINK_XFRM_SOCKET; + case NETLINK_SELINUX: + return SECCLASS_NETLINK_SELINUX_SOCKET; + case NETLINK_ISCSI: + return SECCLASS_NETLINK_ISCSI_SOCKET; + case NETLINK_AUDIT: + return SECCLASS_NETLINK_AUDIT_SOCKET; + case NETLINK_FIB_LOOKUP: + return SECCLASS_NETLINK_FIB_LOOKUP_SOCKET; + case NETLINK_CONNECTOR: + return SECCLASS_NETLINK_CONNECTOR_SOCKET; + case NETLINK_NETFILTER: + return SECCLASS_NETLINK_NETFILTER_SOCKET; + case NETLINK_DNRTMSG: + return SECCLASS_NETLINK_DNRT_SOCKET; + case NETLINK_KOBJECT_UEVENT: + return SECCLASS_NETLINK_KOBJECT_UEVENT_SOCKET; + case NETLINK_GENERIC: + return SECCLASS_NETLINK_GENERIC_SOCKET; + case NETLINK_SCSITRANSPORT: + return SECCLASS_NETLINK_SCSITRANSPORT_SOCKET; + case NETLINK_RDMA: + return SECCLASS_NETLINK_RDMA_SOCKET; + case NETLINK_CRYPTO: + return SECCLASS_NETLINK_CRYPTO_SOCKET; + default: + return SECCLASS_NETLINK_SOCKET; + } + case PF_PACKET: + return SECCLASS_PACKET_SOCKET; + case PF_KEY: + return SECCLASS_KEY_SOCKET; + case PF_APPLETALK: + return SECCLASS_APPLETALK_SOCKET; + } + + if (extsockclass) { + switch (family) { + case PF_AX25: + return SECCLASS_AX25_SOCKET; + case PF_IPX: + return SECCLASS_IPX_SOCKET; + case PF_NETROM: + return SECCLASS_NETROM_SOCKET; + case PF_ATMPVC: + return SECCLASS_ATMPVC_SOCKET; + case PF_X25: + return SECCLASS_X25_SOCKET; + case PF_ROSE: + return SECCLASS_ROSE_SOCKET; + case PF_DECnet: + return SECCLASS_DECNET_SOCKET; + case PF_ATMSVC: + return SECCLASS_ATMSVC_SOCKET; + case PF_RDS: + return SECCLASS_RDS_SOCKET; + case PF_IRDA: + return SECCLASS_IRDA_SOCKET; + case PF_PPPOX: + return SECCLASS_PPPOX_SOCKET; + case PF_LLC: + return SECCLASS_LLC_SOCKET; + case PF_CAN: + return SECCLASS_CAN_SOCKET; + case PF_TIPC: + return SECCLASS_TIPC_SOCKET; + case PF_BLUETOOTH: + return SECCLASS_BLUETOOTH_SOCKET; + case PF_IUCV: + return SECCLASS_IUCV_SOCKET; + case PF_RXRPC: + return SECCLASS_RXRPC_SOCKET; + case PF_ISDN: + return SECCLASS_ISDN_SOCKET; + case PF_PHONET: + return SECCLASS_PHONET_SOCKET; + case PF_IEEE802154: + return SECCLASS_IEEE802154_SOCKET; + case PF_CAIF: + return SECCLASS_CAIF_SOCKET; + case PF_ALG: + return SECCLASS_ALG_SOCKET; + case PF_NFC: + return SECCLASS_NFC_SOCKET; + case PF_VSOCK: + return SECCLASS_VSOCK_SOCKET; + case PF_KCM: + return SECCLASS_KCM_SOCKET; + case PF_QIPCRTR: + return SECCLASS_QIPCRTR_SOCKET; + case PF_SMC: + return SECCLASS_SMC_SOCKET; + case PF_XDP: + return SECCLASS_XDP_SOCKET; +#if PF_MAX > 45 +#error New address family defined, please update this function. +#endif + } + } + + return SECCLASS_SOCKET; +} + +static int selinux_genfs_get_sid(struct dentry *dentry, + u16 tclass, + u16 flags, + u32 *sid) +{ + int rc; + struct super_block *sb = dentry->d_sb; + char *buffer, *path; + + buffer = (char *)__get_free_page(GFP_KERNEL); + if (!buffer) + return -ENOMEM; + + path = dentry_path_raw(dentry, buffer, PAGE_SIZE); + if (IS_ERR(path)) + rc = PTR_ERR(path); + else { + if (flags & SE_SBPROC) { + /* each process gets a /proc/PID/ entry. Strip off the + * PID part to get a valid selinux labeling. + * e.g. /proc/1/net/rpc/nfs -> /net/rpc/nfs */ + while (path[1] >= '0' && path[1] <= '9') { + path[1] = '/'; + path++; + } + } + rc = security_genfs_sid(&selinux_state, sb->s_type->name, + path, tclass, sid); + if (rc == -ENOENT) { + /* No match in policy, mark as unlabeled. */ + *sid = SECINITSID_UNLABELED; + rc = 0; + } + } + free_page((unsigned long)buffer); + return rc; +} + +static int inode_doinit_use_xattr(struct inode *inode, struct dentry *dentry, + u32 def_sid, u32 *sid) +{ +#define INITCONTEXTLEN 255 + char *context; + unsigned int len; + int rc; + + len = INITCONTEXTLEN; + context = kmalloc(len + 1, GFP_NOFS); + if (!context) + return -ENOMEM; + + context[len] = '\0'; + rc = __vfs_getxattr(dentry, inode, XATTR_NAME_SELINUX, context, len); + if (rc == -ERANGE) { + kfree(context); + + /* Need a larger buffer. Query for the right size. */ + rc = __vfs_getxattr(dentry, inode, XATTR_NAME_SELINUX, NULL, 0); + if (rc < 0) + return rc; + + len = rc; + context = kmalloc(len + 1, GFP_NOFS); + if (!context) + return -ENOMEM; + + context[len] = '\0'; + rc = __vfs_getxattr(dentry, inode, XATTR_NAME_SELINUX, + context, len); + } + if (rc < 0) { + kfree(context); + if (rc != -ENODATA) { + pr_warn("SELinux: %s: getxattr returned %d for dev=%s ino=%ld\n", + __func__, -rc, inode->i_sb->s_id, inode->i_ino); + return rc; + } + *sid = def_sid; + return 0; + } + + rc = security_context_to_sid_default(&selinux_state, context, rc, sid, + def_sid, GFP_NOFS); + if (rc) { + char *dev = inode->i_sb->s_id; + unsigned long ino = inode->i_ino; + + if (rc == -EINVAL) { + pr_notice_ratelimited("SELinux: inode=%lu on dev=%s was found to have an invalid context=%s. This indicates you may need to relabel the inode or the filesystem in question.\n", + ino, dev, context); + } else { + pr_warn("SELinux: %s: context_to_sid(%s) returned %d for dev=%s ino=%ld\n", + __func__, context, -rc, dev, ino); + } + } + kfree(context); + return 0; +} + +/* The inode's security attributes must be initialized before first use. */ +static int inode_doinit_with_dentry(struct inode *inode, struct dentry *opt_dentry) +{ + struct superblock_security_struct *sbsec = NULL; + struct inode_security_struct *isec = selinux_inode(inode); + u32 task_sid, sid = 0; + u16 sclass; + struct dentry *dentry; + int rc = 0; + + if (isec->initialized == LABEL_INITIALIZED) + return 0; + + spin_lock(&isec->lock); + if (isec->initialized == LABEL_INITIALIZED) + goto out_unlock; + + if (isec->sclass == SECCLASS_FILE) + isec->sclass = inode_mode_to_security_class(inode->i_mode); + + sbsec = inode->i_sb->s_security; + if (!(sbsec->flags & SE_SBINITIALIZED)) { + /* Defer initialization until selinux_complete_init, + after the initial policy is loaded and the security + server is ready to handle calls. */ + spin_lock(&sbsec->isec_lock); + if (list_empty(&isec->list)) + list_add(&isec->list, &sbsec->isec_head); + spin_unlock(&sbsec->isec_lock); + goto out_unlock; + } + + sclass = isec->sclass; + task_sid = isec->task_sid; + sid = isec->sid; + isec->initialized = LABEL_PENDING; + spin_unlock(&isec->lock); + + switch (sbsec->behavior) { + case SECURITY_FS_USE_NATIVE: + break; + case SECURITY_FS_USE_XATTR: + if (!(inode->i_opflags & IOP_XATTR)) { + sid = sbsec->def_sid; + break; + } + /* Need a dentry, since the xattr API requires one. + Life would be simpler if we could just pass the inode. */ + if (opt_dentry) { + /* Called from d_instantiate or d_splice_alias. */ + dentry = dget(opt_dentry); + } else { + /* + * Called from selinux_complete_init, try to find a dentry. + * Some filesystems really want a connected one, so try + * that first. We could split SECURITY_FS_USE_XATTR in + * two, depending upon that... + */ + dentry = d_find_alias(inode); + if (!dentry) + dentry = d_find_any_alias(inode); + } + if (!dentry) { + /* + * this is can be hit on boot when a file is accessed + * before the policy is loaded. When we load policy we + * may find inodes that have no dentry on the + * sbsec->isec_head list. No reason to complain as these + * will get fixed up the next time we go through + * inode_doinit with a dentry, before these inodes could + * be used again by userspace. + */ + goto out_invalid; + } + + rc = inode_doinit_use_xattr(inode, dentry, sbsec->def_sid, + &sid); + dput(dentry); + if (rc) + goto out; + break; + case SECURITY_FS_USE_TASK: + sid = task_sid; + break; + case SECURITY_FS_USE_TRANS: + /* Default to the fs SID. */ + sid = sbsec->sid; + + /* Try to obtain a transition SID. */ + rc = security_transition_sid(&selinux_state, task_sid, sid, + sclass, NULL, &sid); + if (rc) + goto out; + break; + case SECURITY_FS_USE_MNTPOINT: + sid = sbsec->mntpoint_sid; + break; + default: + /* Default to the fs superblock SID. */ + sid = sbsec->sid; + + if ((sbsec->flags & SE_SBGENFS) && + (!S_ISLNK(inode->i_mode) || + selinux_policycap_genfs_seclabel_symlinks())) { + /* We must have a dentry to determine the label on + * procfs inodes */ + if (opt_dentry) { + /* Called from d_instantiate or + * d_splice_alias. */ + dentry = dget(opt_dentry); + } else { + /* Called from selinux_complete_init, try to + * find a dentry. Some filesystems really want + * a connected one, so try that first. + */ + dentry = d_find_alias(inode); + if (!dentry) + dentry = d_find_any_alias(inode); + } + /* + * This can be hit on boot when a file is accessed + * before the policy is loaded. When we load policy we + * may find inodes that have no dentry on the + * sbsec->isec_head list. No reason to complain as + * these will get fixed up the next time we go through + * inode_doinit() with a dentry, before these inodes + * could be used again by userspace. + */ + if (!dentry) + goto out_invalid; + rc = selinux_genfs_get_sid(dentry, sclass, + sbsec->flags, &sid); + if (rc) { + dput(dentry); + goto out; + } + + if ((sbsec->flags & SE_SBGENFS_XATTR) && + (inode->i_opflags & IOP_XATTR)) { + rc = inode_doinit_use_xattr(inode, dentry, + sid, &sid); + if (rc) { + dput(dentry); + goto out; + } + } + dput(dentry); + } + break; + } + +out: + spin_lock(&isec->lock); + if (isec->initialized == LABEL_PENDING) { + if (rc) { + isec->initialized = LABEL_INVALID; + goto out_unlock; + } + isec->initialized = LABEL_INITIALIZED; + isec->sid = sid; + } + +out_unlock: + spin_unlock(&isec->lock); + return rc; + +out_invalid: + spin_lock(&isec->lock); + if (isec->initialized == LABEL_PENDING) { + isec->initialized = LABEL_INVALID; + isec->sid = sid; + } + spin_unlock(&isec->lock); + return 0; +} + +/* Convert a Linux signal to an access vector. */ +static inline u32 signal_to_av(int sig) +{ + u32 perm = 0; + + switch (sig) { + case SIGCHLD: + /* Commonly granted from child to parent. */ + perm = PROCESS__SIGCHLD; + break; + case SIGKILL: + /* Cannot be caught or ignored */ + perm = PROCESS__SIGKILL; + break; + case SIGSTOP: + /* Cannot be caught or ignored */ + perm = PROCESS__SIGSTOP; + break; + default: + /* All other signals. */ + perm = PROCESS__SIGNAL; + break; + } + + return perm; +} + +#if CAP_LAST_CAP > 63 +#error Fix SELinux to handle capabilities > 63. +#endif + +/* Check whether a task is allowed to use a capability. */ +static int cred_has_capability(const struct cred *cred, + int cap, unsigned int opts, bool initns) +{ + struct common_audit_data ad; + struct av_decision avd; + u16 sclass; + u32 sid = cred_sid(cred); + u32 av = CAP_TO_MASK(cap); + int rc; + + ad.type = LSM_AUDIT_DATA_CAP; + ad.u.cap = cap; + + switch (CAP_TO_INDEX(cap)) { + case 0: + sclass = initns ? SECCLASS_CAPABILITY : SECCLASS_CAP_USERNS; + break; + case 1: + sclass = initns ? SECCLASS_CAPABILITY2 : SECCLASS_CAP2_USERNS; + break; + default: + pr_err("SELinux: out of range capability %d\n", cap); + BUG(); + return -EINVAL; + } + + rc = avc_has_perm_noaudit(&selinux_state, + sid, sid, sclass, av, 0, &avd); + if (!(opts & CAP_OPT_NOAUDIT)) { + int rc2 = avc_audit(&selinux_state, + sid, sid, sclass, av, &avd, rc, &ad, 0); + if (rc2) + return rc2; + } + return rc; +} + +/* Check whether a task has a particular permission to an inode. + The 'adp' parameter is optional and allows other audit + data to be passed (e.g. the dentry). */ +static int inode_has_perm(const struct cred *cred, + struct inode *inode, + u32 perms, + struct common_audit_data *adp) +{ + struct inode_security_struct *isec; + u32 sid; + + validate_creds(cred); + + if (unlikely(IS_PRIVATE(inode))) + return 0; + + sid = cred_sid(cred); + isec = selinux_inode(inode); + + return avc_has_perm(&selinux_state, + sid, isec->sid, isec->sclass, perms, adp); +} + +/* Same as inode_has_perm, but pass explicit audit data containing + the dentry to help the auditing code to more easily generate the + pathname if needed. */ +static inline int dentry_has_perm(const struct cred *cred, + struct dentry *dentry, + u32 av) +{ + struct inode *inode = d_backing_inode(dentry); + struct common_audit_data ad; + + ad.type = LSM_AUDIT_DATA_DENTRY; + ad.u.dentry = dentry; + __inode_security_revalidate(inode, dentry, true); + return inode_has_perm(cred, inode, av, &ad); +} + +/* Same as inode_has_perm, but pass explicit audit data containing + the path to help the auditing code to more easily generate the + pathname if needed. */ +static inline int path_has_perm(const struct cred *cred, + const struct path *path, + u32 av) +{ + struct inode *inode = d_backing_inode(path->dentry); + struct common_audit_data ad; + + ad.type = LSM_AUDIT_DATA_PATH; + ad.u.path = *path; + __inode_security_revalidate(inode, path->dentry, true); + return inode_has_perm(cred, inode, av, &ad); +} + +/* Same as path_has_perm, but uses the inode from the file struct. */ +static inline int file_path_has_perm(const struct cred *cred, + struct file *file, + u32 av) +{ + struct common_audit_data ad; + + ad.type = LSM_AUDIT_DATA_FILE; + ad.u.file = file; + return inode_has_perm(cred, file_inode(file), av, &ad); +} + +#ifdef CONFIG_BPF_SYSCALL +static int bpf_fd_pass(struct file *file, u32 sid); +#endif + +/* Check whether a task can use an open file descriptor to + access an inode in a given way. Check access to the + descriptor itself, and then use dentry_has_perm to + check a particular permission to the file. + Access to the descriptor is implicitly granted if it + has the same SID as the process. If av is zero, then + access to the file is not checked, e.g. for cases + where only the descriptor is affected like seek. */ +static int file_has_perm(const struct cred *cred, + struct file *file, + u32 av) +{ + struct file_security_struct *fsec = selinux_file(file); + struct inode *inode = file_inode(file); + struct common_audit_data ad; + u32 sid = cred_sid(cred); + int rc; + + ad.type = LSM_AUDIT_DATA_FILE; + ad.u.file = file; + + if (sid != fsec->sid) { + rc = avc_has_perm(&selinux_state, + sid, fsec->sid, + SECCLASS_FD, + FD__USE, + &ad); + if (rc) + goto out; + } + +#ifdef CONFIG_BPF_SYSCALL + rc = bpf_fd_pass(file, cred_sid(cred)); + if (rc) + return rc; +#endif + + /* av is zero if only checking access to the descriptor. */ + rc = 0; + if (av) + rc = inode_has_perm(cred, inode, av, &ad); + +out: + return rc; +} + +/* + * Determine the label for an inode that might be unioned. + */ +static int +selinux_determine_inode_label(const struct task_security_struct *tsec, + struct inode *dir, + const struct qstr *name, u16 tclass, + u32 *_new_isid) +{ + const struct superblock_security_struct *sbsec = dir->i_sb->s_security; + + if ((sbsec->flags & SE_SBINITIALIZED) && + (sbsec->behavior == SECURITY_FS_USE_MNTPOINT)) { + *_new_isid = sbsec->mntpoint_sid; + } else if ((sbsec->flags & SBLABEL_MNT) && + tsec->create_sid) { + *_new_isid = tsec->create_sid; + } else { + const struct inode_security_struct *dsec = inode_security(dir); + return security_transition_sid(&selinux_state, tsec->sid, + dsec->sid, tclass, + name, _new_isid); + } + + return 0; +} + +/* Check whether a task can create a file. */ +static int may_create(struct inode *dir, + struct dentry *dentry, + u16 tclass) +{ + const struct task_security_struct *tsec = selinux_cred(current_cred()); + struct inode_security_struct *dsec; + struct superblock_security_struct *sbsec; + u32 sid, newsid; + struct common_audit_data ad; + int rc; + + dsec = inode_security(dir); + sbsec = dir->i_sb->s_security; + + sid = tsec->sid; + + ad.type = LSM_AUDIT_DATA_DENTRY; + ad.u.dentry = dentry; + + rc = avc_has_perm(&selinux_state, + sid, dsec->sid, SECCLASS_DIR, + DIR__ADD_NAME | DIR__SEARCH, + &ad); + if (rc) + return rc; + + rc = selinux_determine_inode_label(tsec, dir, &dentry->d_name, tclass, + &newsid); + if (rc) + return rc; + + rc = avc_has_perm(&selinux_state, + sid, newsid, tclass, FILE__CREATE, &ad); + if (rc) + return rc; + + return avc_has_perm(&selinux_state, + newsid, sbsec->sid, + SECCLASS_FILESYSTEM, + FILESYSTEM__ASSOCIATE, &ad); +} + +#define MAY_LINK 0 +#define MAY_UNLINK 1 +#define MAY_RMDIR 2 + +/* Check whether a task can link, unlink, or rmdir a file/directory. */ +static int may_link(struct inode *dir, + struct dentry *dentry, + int kind) + +{ + struct inode_security_struct *dsec, *isec; + struct common_audit_data ad; + u32 sid = current_sid(); + u32 av; + int rc; + + dsec = inode_security(dir); + isec = backing_inode_security(dentry); + + ad.type = LSM_AUDIT_DATA_DENTRY; + ad.u.dentry = dentry; + + av = DIR__SEARCH; + av |= (kind ? DIR__REMOVE_NAME : DIR__ADD_NAME); + rc = avc_has_perm(&selinux_state, + sid, dsec->sid, SECCLASS_DIR, av, &ad); + if (rc) + return rc; + + switch (kind) { + case MAY_LINK: + av = FILE__LINK; + break; + case MAY_UNLINK: + av = FILE__UNLINK; + break; + case MAY_RMDIR: + av = DIR__RMDIR; + break; + default: + pr_warn("SELinux: %s: unrecognized kind %d\n", + __func__, kind); + return 0; + } + + rc = avc_has_perm(&selinux_state, + sid, isec->sid, isec->sclass, av, &ad); + return rc; +} + +static inline int may_rename(struct inode *old_dir, + struct dentry *old_dentry, + struct inode *new_dir, + struct dentry *new_dentry) +{ + struct inode_security_struct *old_dsec, *new_dsec, *old_isec, *new_isec; + struct common_audit_data ad; + u32 sid = current_sid(); + u32 av; + int old_is_dir, new_is_dir; + int rc; + + old_dsec = inode_security(old_dir); + old_isec = backing_inode_security(old_dentry); + old_is_dir = d_is_dir(old_dentry); + new_dsec = inode_security(new_dir); + + ad.type = LSM_AUDIT_DATA_DENTRY; + + ad.u.dentry = old_dentry; + rc = avc_has_perm(&selinux_state, + sid, old_dsec->sid, SECCLASS_DIR, + DIR__REMOVE_NAME | DIR__SEARCH, &ad); + if (rc) + return rc; + rc = avc_has_perm(&selinux_state, + sid, old_isec->sid, + old_isec->sclass, FILE__RENAME, &ad); + if (rc) + return rc; + if (old_is_dir && new_dir != old_dir) { + rc = avc_has_perm(&selinux_state, + sid, old_isec->sid, + old_isec->sclass, DIR__REPARENT, &ad); + if (rc) + return rc; + } + + ad.u.dentry = new_dentry; + av = DIR__ADD_NAME | DIR__SEARCH; + if (d_is_positive(new_dentry)) + av |= DIR__REMOVE_NAME; + rc = avc_has_perm(&selinux_state, + sid, new_dsec->sid, SECCLASS_DIR, av, &ad); + if (rc) + return rc; + if (d_is_positive(new_dentry)) { + new_isec = backing_inode_security(new_dentry); + new_is_dir = d_is_dir(new_dentry); + rc = avc_has_perm(&selinux_state, + sid, new_isec->sid, + new_isec->sclass, + (new_is_dir ? DIR__RMDIR : FILE__UNLINK), &ad); + if (rc) + return rc; + } + + return 0; +} + +/* Check whether a task can perform a filesystem operation. */ +static int superblock_has_perm(const struct cred *cred, + struct super_block *sb, + u32 perms, + struct common_audit_data *ad) +{ + struct superblock_security_struct *sbsec; + u32 sid = cred_sid(cred); + + sbsec = sb->s_security; + return avc_has_perm(&selinux_state, + sid, sbsec->sid, SECCLASS_FILESYSTEM, perms, ad); +} + +/* Convert a Linux mode and permission mask to an access vector. */ +static inline u32 file_mask_to_av(int mode, int mask) +{ + u32 av = 0; + + if (!S_ISDIR(mode)) { + if (mask & MAY_EXEC) + av |= FILE__EXECUTE; + if (mask & MAY_READ) + av |= FILE__READ; + + if (mask & MAY_APPEND) + av |= FILE__APPEND; + else if (mask & MAY_WRITE) + av |= FILE__WRITE; + + } else { + if (mask & MAY_EXEC) + av |= DIR__SEARCH; + if (mask & MAY_WRITE) + av |= DIR__WRITE; + if (mask & MAY_READ) + av |= DIR__READ; + } + + return av; +} + +/* Convert a Linux file to an access vector. */ +static inline u32 file_to_av(struct file *file) +{ + u32 av = 0; + + if (file->f_mode & FMODE_READ) + av |= FILE__READ; + if (file->f_mode & FMODE_WRITE) { + if (file->f_flags & O_APPEND) + av |= FILE__APPEND; + else + av |= FILE__WRITE; + } + if (!av) { + /* + * Special file opened with flags 3 for ioctl-only use. + */ + av = FILE__IOCTL; + } + + return av; +} + +/* + * Convert a file to an access vector and include the correct + * open permission. + */ +static inline u32 open_file_to_av(struct file *file) +{ + u32 av = file_to_av(file); + struct inode *inode = file_inode(file); + + if (selinux_policycap_openperm() && + inode->i_sb->s_magic != SOCKFS_MAGIC) + av |= FILE__OPEN; + + return av; +} + +/* Hook functions begin here. */ + +static int selinux_binder_set_context_mgr(const struct cred *mgr) +{ + return avc_has_perm(&selinux_state, + current_sid(), cred_sid(mgr), SECCLASS_BINDER, + BINDER__SET_CONTEXT_MGR, NULL); +} + +static int selinux_binder_transaction(const struct cred *from, + const struct cred *to) +{ + u32 mysid = current_sid(); + u32 fromsid = cred_sid(from); + u32 tosid = cred_sid(to); + int rc; + + if (mysid != fromsid) { + rc = avc_has_perm(&selinux_state, + mysid, fromsid, SECCLASS_BINDER, + BINDER__IMPERSONATE, NULL); + if (rc) + return rc; + } + + return avc_has_perm(&selinux_state, fromsid, tosid, + SECCLASS_BINDER, BINDER__CALL, NULL); +} + +static int selinux_binder_transfer_binder(const struct cred *from, + const struct cred *to) +{ + return avc_has_perm(&selinux_state, + cred_sid(from), cred_sid(to), + SECCLASS_BINDER, BINDER__TRANSFER, + NULL); +} + +static int selinux_binder_transfer_file(const struct cred *from, + const struct cred *to, + struct file *file) +{ + u32 sid = cred_sid(to); + struct file_security_struct *fsec = selinux_file(file); + struct dentry *dentry = file->f_path.dentry; + struct inode_security_struct *isec; + struct common_audit_data ad; + int rc; + + ad.type = LSM_AUDIT_DATA_PATH; + ad.u.path = file->f_path; + + if (sid != fsec->sid) { + rc = avc_has_perm(&selinux_state, + sid, fsec->sid, + SECCLASS_FD, + FD__USE, + &ad); + if (rc) + return rc; + } + +#ifdef CONFIG_BPF_SYSCALL + rc = bpf_fd_pass(file, sid); + if (rc) + return rc; +#endif + + if (unlikely(IS_PRIVATE(d_backing_inode(dentry)))) + return 0; + + isec = backing_inode_security(dentry); + return avc_has_perm(&selinux_state, + sid, isec->sid, isec->sclass, file_to_av(file), + &ad); +} + +static int selinux_ptrace_access_check(struct task_struct *child, + unsigned int mode) +{ + u32 sid = current_sid(); + u32 csid = task_sid(child); + + if (mode & PTRACE_MODE_READ) + return avc_has_perm(&selinux_state, + sid, csid, SECCLASS_FILE, FILE__READ, NULL); + + return avc_has_perm(&selinux_state, + sid, csid, SECCLASS_PROCESS, PROCESS__PTRACE, NULL); +} + +static int selinux_ptrace_traceme(struct task_struct *parent) +{ + return avc_has_perm(&selinux_state, + task_sid(parent), current_sid(), SECCLASS_PROCESS, + PROCESS__PTRACE, NULL); +} + +static int selinux_capget(struct task_struct *target, kernel_cap_t *effective, + kernel_cap_t *inheritable, kernel_cap_t *permitted) +{ + return avc_has_perm(&selinux_state, + current_sid(), task_sid(target), SECCLASS_PROCESS, + PROCESS__GETCAP, NULL); +} + +static int selinux_capset(struct cred *new, const struct cred *old, + const kernel_cap_t *effective, + const kernel_cap_t *inheritable, + const kernel_cap_t *permitted) +{ + return avc_has_perm(&selinux_state, + cred_sid(old), cred_sid(new), SECCLASS_PROCESS, + PROCESS__SETCAP, NULL); +} + +/* + * (This comment used to live with the selinux_task_setuid hook, + * which was removed). + * + * Since setuid only affects the current process, and since the SELinux + * controls are not based on the Linux identity attributes, SELinux does not + * need to control this operation. However, SELinux does control the use of + * the CAP_SETUID and CAP_SETGID capabilities using the capable hook. + */ + +static int selinux_capable(const struct cred *cred, struct user_namespace *ns, + int cap, unsigned int opts) +{ + return cred_has_capability(cred, cap, opts, ns == &init_user_ns); +} + +static int selinux_quotactl(int cmds, int type, int id, struct super_block *sb) +{ + const struct cred *cred = current_cred(); + int rc = 0; + + if (!sb) + return 0; + + switch (cmds) { + case Q_SYNC: + case Q_QUOTAON: + case Q_QUOTAOFF: + case Q_SETINFO: + case Q_SETQUOTA: + case Q_XQUOTAOFF: + case Q_XQUOTAON: + case Q_XSETQLIM: + rc = superblock_has_perm(cred, sb, FILESYSTEM__QUOTAMOD, NULL); + break; + case Q_GETFMT: + case Q_GETINFO: + case Q_GETQUOTA: + case Q_XGETQUOTA: + case Q_XGETQSTAT: + case Q_XGETQSTATV: + case Q_XGETNEXTQUOTA: + rc = superblock_has_perm(cred, sb, FILESYSTEM__QUOTAGET, NULL); + break; + default: + rc = 0; /* let the kernel handle invalid cmds */ + break; + } + return rc; +} + +static int selinux_quota_on(struct dentry *dentry) +{ + const struct cred *cred = current_cred(); + + return dentry_has_perm(cred, dentry, FILE__QUOTAON); +} + +static int selinux_syslog(int type) +{ + switch (type) { + case SYSLOG_ACTION_READ_ALL: /* Read last kernel messages */ + case SYSLOG_ACTION_SIZE_BUFFER: /* Return size of the log buffer */ + return avc_has_perm(&selinux_state, + current_sid(), SECINITSID_KERNEL, + SECCLASS_SYSTEM, SYSTEM__SYSLOG_READ, NULL); + case SYSLOG_ACTION_CONSOLE_OFF: /* Disable logging to console */ + case SYSLOG_ACTION_CONSOLE_ON: /* Enable logging to console */ + /* Set level of messages printed to console */ + case SYSLOG_ACTION_CONSOLE_LEVEL: + return avc_has_perm(&selinux_state, + current_sid(), SECINITSID_KERNEL, + SECCLASS_SYSTEM, SYSTEM__SYSLOG_CONSOLE, + NULL); + } + /* All other syslog types */ + return avc_has_perm(&selinux_state, + current_sid(), SECINITSID_KERNEL, + SECCLASS_SYSTEM, SYSTEM__SYSLOG_MOD, NULL); +} + +/* + * Check that a process has enough memory to allocate a new virtual + * mapping. 0 means there is enough memory for the allocation to + * succeed and -ENOMEM implies there is not. + * + * Do not audit the selinux permission check, as this is applied to all + * processes that allocate mappings. + */ +static int selinux_vm_enough_memory(struct mm_struct *mm, long pages) +{ + int rc, cap_sys_admin = 0; + + rc = cred_has_capability(current_cred(), CAP_SYS_ADMIN, + CAP_OPT_NOAUDIT, true); + if (rc == 0) + cap_sys_admin = 1; + + return cap_sys_admin; +} + +/* binprm security operations */ + +static u32 ptrace_parent_sid(void) +{ + u32 sid = 0; + struct task_struct *tracer; + + rcu_read_lock(); + tracer = ptrace_parent(current); + if (tracer) + sid = task_sid(tracer); + rcu_read_unlock(); + + return sid; +} + +static int check_nnp_nosuid(const struct linux_binprm *bprm, + const struct task_security_struct *old_tsec, + const struct task_security_struct *new_tsec) +{ + int nnp = (bprm->unsafe & LSM_UNSAFE_NO_NEW_PRIVS); + int nosuid = !mnt_may_suid(bprm->file->f_path.mnt); + int rc; + u32 av; + + if (!nnp && !nosuid) + return 0; /* neither NNP nor nosuid */ + + if (new_tsec->sid == old_tsec->sid) + return 0; /* No change in credentials */ + + /* + * If the policy enables the nnp_nosuid_transition policy capability, + * then we permit transitions under NNP or nosuid if the + * policy allows the corresponding permission between + * the old and new contexts. + */ + if (selinux_policycap_nnp_nosuid_transition()) { + av = 0; + if (nnp) + av |= PROCESS2__NNP_TRANSITION; + if (nosuid) + av |= PROCESS2__NOSUID_TRANSITION; + rc = avc_has_perm(&selinux_state, + old_tsec->sid, new_tsec->sid, + SECCLASS_PROCESS2, av, NULL); + if (!rc) + return 0; + } + + /* + * We also permit NNP or nosuid transitions to bounded SIDs, + * i.e. SIDs that are guaranteed to only be allowed a subset + * of the permissions of the current SID. + */ + rc = security_bounded_transition(&selinux_state, old_tsec->sid, + new_tsec->sid); + if (!rc) + return 0; + + /* + * On failure, preserve the errno values for NNP vs nosuid. + * NNP: Operation not permitted for caller. + * nosuid: Permission denied to file. + */ + if (nnp) + return -EPERM; + return -EACCES; +} + +static int selinux_bprm_creds_for_exec(struct linux_binprm *bprm) +{ + const struct task_security_struct *old_tsec; + struct task_security_struct *new_tsec; + struct inode_security_struct *isec; + struct common_audit_data ad; + struct inode *inode = file_inode(bprm->file); + int rc; + + /* SELinux context only depends on initial program or script and not + * the script interpreter */ + + old_tsec = selinux_cred(current_cred()); + new_tsec = selinux_cred(bprm->cred); + isec = inode_security(inode); + + /* Default to the current task SID. */ + new_tsec->sid = old_tsec->sid; + new_tsec->osid = old_tsec->sid; + + /* Reset fs, key, and sock SIDs on execve. */ + new_tsec->create_sid = 0; + new_tsec->keycreate_sid = 0; + new_tsec->sockcreate_sid = 0; + + if (old_tsec->exec_sid) { + new_tsec->sid = old_tsec->exec_sid; + /* Reset exec SID on execve. */ + new_tsec->exec_sid = 0; + + /* Fail on NNP or nosuid if not an allowed transition. */ + rc = check_nnp_nosuid(bprm, old_tsec, new_tsec); + if (rc) + return rc; + } else { + /* Check for a default transition on this program. */ + rc = security_transition_sid(&selinux_state, old_tsec->sid, + isec->sid, SECCLASS_PROCESS, NULL, + &new_tsec->sid); + if (rc) + return rc; + + /* + * Fallback to old SID on NNP or nosuid if not an allowed + * transition. + */ + rc = check_nnp_nosuid(bprm, old_tsec, new_tsec); + if (rc) + new_tsec->sid = old_tsec->sid; + } + + ad.type = LSM_AUDIT_DATA_FILE; + ad.u.file = bprm->file; + + if (new_tsec->sid == old_tsec->sid) { + rc = avc_has_perm(&selinux_state, + old_tsec->sid, isec->sid, + SECCLASS_FILE, FILE__EXECUTE_NO_TRANS, &ad); + if (rc) + return rc; + } else { + /* Check permissions for the transition. */ + rc = avc_has_perm(&selinux_state, + old_tsec->sid, new_tsec->sid, + SECCLASS_PROCESS, PROCESS__TRANSITION, &ad); + if (rc) + return rc; + + rc = avc_has_perm(&selinux_state, + new_tsec->sid, isec->sid, + SECCLASS_FILE, FILE__ENTRYPOINT, &ad); + if (rc) + return rc; + + /* Check for shared state */ + if (bprm->unsafe & LSM_UNSAFE_SHARE) { + rc = avc_has_perm(&selinux_state, + old_tsec->sid, new_tsec->sid, + SECCLASS_PROCESS, PROCESS__SHARE, + NULL); + if (rc) + return -EPERM; + } + + /* Make sure that anyone attempting to ptrace over a task that + * changes its SID has the appropriate permit */ + if (bprm->unsafe & LSM_UNSAFE_PTRACE) { + u32 ptsid = ptrace_parent_sid(); + if (ptsid != 0) { + rc = avc_has_perm(&selinux_state, + ptsid, new_tsec->sid, + SECCLASS_PROCESS, + PROCESS__PTRACE, NULL); + if (rc) + return -EPERM; + } + } + + /* Clear any possibly unsafe personality bits on exec: */ + bprm->per_clear |= PER_CLEAR_ON_SETID; + + /* Enable secure mode for SIDs transitions unless + the noatsecure permission is granted between + the two SIDs, i.e. ahp returns 0. */ + rc = avc_has_perm(&selinux_state, + old_tsec->sid, new_tsec->sid, + SECCLASS_PROCESS, PROCESS__NOATSECURE, + NULL); + bprm->secureexec |= !!rc; + } + + return 0; +} + +static int match_file(const void *p, struct file *file, unsigned fd) +{ + return file_has_perm(p, file, file_to_av(file)) ? fd + 1 : 0; +} + +/* Derived from fs/exec.c:flush_old_files. */ +static inline void flush_unauthorized_files(const struct cred *cred, + struct files_struct *files) +{ + struct file *file, *devnull = NULL; + struct tty_struct *tty; + int drop_tty = 0; + unsigned n; + + tty = get_current_tty(); + if (tty) { + spin_lock(&tty->files_lock); + if (!list_empty(&tty->tty_files)) { + struct tty_file_private *file_priv; + + /* Revalidate access to controlling tty. + Use file_path_has_perm on the tty path directly + rather than using file_has_perm, as this particular + open file may belong to another process and we are + only interested in the inode-based check here. */ + file_priv = list_first_entry(&tty->tty_files, + struct tty_file_private, list); + file = file_priv->file; + if (file_path_has_perm(cred, file, FILE__READ | FILE__WRITE)) + drop_tty = 1; + } + spin_unlock(&tty->files_lock); + tty_kref_put(tty); + } + /* Reset controlling tty. */ + if (drop_tty) + no_tty(); + + /* Revalidate access to inherited open files. */ + n = iterate_fd(files, 0, match_file, cred); + if (!n) /* none found? */ + return; + + devnull = dentry_open(&selinux_null, O_RDWR, cred); + if (IS_ERR(devnull)) + devnull = NULL; + /* replace all the matching ones with this */ + do { + replace_fd(n - 1, devnull, 0); + } while ((n = iterate_fd(files, n, match_file, cred)) != 0); + if (devnull) + fput(devnull); +} + +/* + * Prepare a process for imminent new credential changes due to exec + */ +static void selinux_bprm_committing_creds(struct linux_binprm *bprm) +{ + struct task_security_struct *new_tsec; + struct rlimit *rlim, *initrlim; + int rc, i; + + new_tsec = selinux_cred(bprm->cred); + if (new_tsec->sid == new_tsec->osid) + return; + + /* Close files for which the new task SID is not authorized. */ + flush_unauthorized_files(bprm->cred, current->files); + + /* Always clear parent death signal on SID transitions. */ + current->pdeath_signal = 0; + + /* Check whether the new SID can inherit resource limits from the old + * SID. If not, reset all soft limits to the lower of the current + * task's hard limit and the init task's soft limit. + * + * Note that the setting of hard limits (even to lower them) can be + * controlled by the setrlimit check. The inclusion of the init task's + * soft limit into the computation is to avoid resetting soft limits + * higher than the default soft limit for cases where the default is + * lower than the hard limit, e.g. RLIMIT_CORE or RLIMIT_STACK. + */ + rc = avc_has_perm(&selinux_state, + new_tsec->osid, new_tsec->sid, SECCLASS_PROCESS, + PROCESS__RLIMITINH, NULL); + if (rc) { + /* protect against do_prlimit() */ + task_lock(current); + for (i = 0; i < RLIM_NLIMITS; i++) { + rlim = current->signal->rlim + i; + initrlim = init_task.signal->rlim + i; + rlim->rlim_cur = min(rlim->rlim_max, initrlim->rlim_cur); + } + task_unlock(current); + if (IS_ENABLED(CONFIG_POSIX_TIMERS)) + update_rlimit_cpu(current, rlimit(RLIMIT_CPU)); + } +} + +/* + * Clean up the process immediately after the installation of new credentials + * due to exec + */ +static void selinux_bprm_committed_creds(struct linux_binprm *bprm) +{ + const struct task_security_struct *tsec = selinux_cred(current_cred()); + u32 osid, sid; + int rc; + + osid = tsec->osid; + sid = tsec->sid; + + if (sid == osid) + return; + + /* Check whether the new SID can inherit signal state from the old SID. + * If not, clear itimers to avoid subsequent signal generation and + * flush and unblock signals. + * + * This must occur _after_ the task SID has been updated so that any + * kill done after the flush will be checked against the new SID. + */ + rc = avc_has_perm(&selinux_state, + osid, sid, SECCLASS_PROCESS, PROCESS__SIGINH, NULL); + if (rc) { + clear_itimer(); + + spin_lock_irq(¤t->sighand->siglock); + if (!fatal_signal_pending(current)) { + flush_sigqueue(¤t->pending); + flush_sigqueue(¤t->signal->shared_pending); + flush_signal_handlers(current, 1); + sigemptyset(¤t->blocked); + recalc_sigpending(); + } + spin_unlock_irq(¤t->sighand->siglock); + } + + /* Wake up the parent if it is waiting so that it can recheck + * wait permission to the new task SID. */ + read_lock(&tasklist_lock); + __wake_up_parent(current, current->real_parent); + read_unlock(&tasklist_lock); +} + +/* superblock security operations */ + +static int selinux_sb_alloc_security(struct super_block *sb) +{ + struct superblock_security_struct *sbsec; + + sbsec = kzalloc(sizeof(struct superblock_security_struct), GFP_KERNEL); + if (!sbsec) + return -ENOMEM; + + mutex_init(&sbsec->lock); + INIT_LIST_HEAD(&sbsec->isec_head); + spin_lock_init(&sbsec->isec_lock); + sbsec->sb = sb; + sbsec->sid = SECINITSID_UNLABELED; + sbsec->def_sid = SECINITSID_FILE; + sbsec->mntpoint_sid = SECINITSID_UNLABELED; + sb->s_security = sbsec; + + return 0; +} + +static void selinux_sb_free_security(struct super_block *sb) +{ + superblock_free_security(sb); +} + +static inline int opt_len(const char *s) +{ + bool open_quote = false; + int len; + char c; + + for (len = 0; (c = s[len]) != '\0'; len++) { + if (c == '"') + open_quote = !open_quote; + if (c == ',' && !open_quote) + break; + } + return len; +} + +static int selinux_sb_eat_lsm_opts(char *options, void **mnt_opts) +{ + char *from = options; + char *to = options; + bool first = true; + int rc; + + while (1) { + int len = opt_len(from); + int token; + char *arg = NULL; + + token = match_opt_prefix(from, len, &arg); + + if (token != Opt_error) { + char *p, *q; + + /* strip quotes */ + if (arg) { + for (p = q = arg; p < from + len; p++) { + char c = *p; + if (c != '"') + *q++ = c; + } + arg = kmemdup_nul(arg, q - arg, GFP_KERNEL); + if (!arg) { + rc = -ENOMEM; + goto free_opt; + } + } + rc = selinux_add_opt(token, arg, mnt_opts); + if (unlikely(rc)) { + kfree(arg); + goto free_opt; + } + } else { + if (!first) { // copy with preceding comma + from--; + len++; + } + if (to != from) + memmove(to, from, len); + to += len; + first = false; + } + if (!from[len]) + break; + from += len + 1; + } + *to = '\0'; + return 0; + +free_opt: + if (*mnt_opts) { + selinux_free_mnt_opts(*mnt_opts); + *mnt_opts = NULL; + } + return rc; +} + +static int selinux_sb_remount(struct super_block *sb, void *mnt_opts) +{ + struct selinux_mnt_opts *opts = mnt_opts; + struct superblock_security_struct *sbsec = sb->s_security; + u32 sid; + int rc; + + if (!(sbsec->flags & SE_SBINITIALIZED)) + return 0; + + if (!opts) + return 0; + + if (opts->fscontext) { + rc = parse_sid(sb, opts->fscontext, &sid); + if (rc) + return rc; + if (bad_option(sbsec, FSCONTEXT_MNT, sbsec->sid, sid)) + goto out_bad_option; + } + if (opts->context) { + rc = parse_sid(sb, opts->context, &sid); + if (rc) + return rc; + if (bad_option(sbsec, CONTEXT_MNT, sbsec->mntpoint_sid, sid)) + goto out_bad_option; + } + if (opts->rootcontext) { + struct inode_security_struct *root_isec; + root_isec = backing_inode_security(sb->s_root); + rc = parse_sid(sb, opts->rootcontext, &sid); + if (rc) + return rc; + if (bad_option(sbsec, ROOTCONTEXT_MNT, root_isec->sid, sid)) + goto out_bad_option; + } + if (opts->defcontext) { + rc = parse_sid(sb, opts->defcontext, &sid); + if (rc) + return rc; + if (bad_option(sbsec, DEFCONTEXT_MNT, sbsec->def_sid, sid)) + goto out_bad_option; + } + return 0; + +out_bad_option: + pr_warn("SELinux: unable to change security options " + "during remount (dev %s, type=%s)\n", sb->s_id, + sb->s_type->name); + return -EINVAL; +} + +static int selinux_sb_kern_mount(struct super_block *sb) +{ + const struct cred *cred = current_cred(); + struct common_audit_data ad; + + ad.type = LSM_AUDIT_DATA_DENTRY; + ad.u.dentry = sb->s_root; + return superblock_has_perm(cred, sb, FILESYSTEM__MOUNT, &ad); +} + +static int selinux_sb_statfs(struct dentry *dentry) +{ + const struct cred *cred = current_cred(); + struct common_audit_data ad; + + ad.type = LSM_AUDIT_DATA_DENTRY; + ad.u.dentry = dentry->d_sb->s_root; + return superblock_has_perm(cred, dentry->d_sb, FILESYSTEM__GETATTR, &ad); +} + +static int selinux_mount(const char *dev_name, + const struct path *path, + const char *type, + unsigned long flags, + void *data) +{ + const struct cred *cred = current_cred(); + + if (flags & MS_REMOUNT) + return superblock_has_perm(cred, path->dentry->d_sb, + FILESYSTEM__REMOUNT, NULL); + else + return path_has_perm(cred, path, FILE__MOUNTON); +} + +static int selinux_move_mount(const struct path *from_path, + const struct path *to_path) +{ + const struct cred *cred = current_cred(); + + return path_has_perm(cred, to_path, FILE__MOUNTON); +} + +static int selinux_umount(struct vfsmount *mnt, int flags) +{ + const struct cred *cred = current_cred(); + + return superblock_has_perm(cred, mnt->mnt_sb, + FILESYSTEM__UNMOUNT, NULL); +} + +static int selinux_fs_context_dup(struct fs_context *fc, + struct fs_context *src_fc) +{ + const struct selinux_mnt_opts *src = src_fc->security; + struct selinux_mnt_opts *opts; + + if (!src) + return 0; + + fc->security = kzalloc(sizeof(struct selinux_mnt_opts), GFP_KERNEL); + if (!fc->security) + return -ENOMEM; + + opts = fc->security; + + if (src->fscontext) { + opts->fscontext = kstrdup(src->fscontext, GFP_KERNEL); + if (!opts->fscontext) + return -ENOMEM; + } + if (src->context) { + opts->context = kstrdup(src->context, GFP_KERNEL); + if (!opts->context) + return -ENOMEM; + } + if (src->rootcontext) { + opts->rootcontext = kstrdup(src->rootcontext, GFP_KERNEL); + if (!opts->rootcontext) + return -ENOMEM; + } + if (src->defcontext) { + opts->defcontext = kstrdup(src->defcontext, GFP_KERNEL); + if (!opts->defcontext) + return -ENOMEM; + } + return 0; +} + +static const struct fs_parameter_spec selinux_fs_parameters[] = { + fsparam_string(CONTEXT_STR, Opt_context), + fsparam_string(DEFCONTEXT_STR, Opt_defcontext), + fsparam_string(FSCONTEXT_STR, Opt_fscontext), + fsparam_string(ROOTCONTEXT_STR, Opt_rootcontext), + fsparam_flag (SECLABEL_STR, Opt_seclabel), + {} +}; + +static int selinux_fs_context_parse_param(struct fs_context *fc, + struct fs_parameter *param) +{ + struct fs_parse_result result; + int opt, rc; + + opt = fs_parse(fc, selinux_fs_parameters, param, &result); + if (opt < 0) + return opt; + + rc = selinux_add_opt(opt, param->string, &fc->security); + if (!rc) + param->string = NULL; + + return rc; +} + +/* inode security operations */ + +static int selinux_inode_alloc_security(struct inode *inode) +{ + struct inode_security_struct *isec = selinux_inode(inode); + u32 sid = current_sid(); + + spin_lock_init(&isec->lock); + INIT_LIST_HEAD(&isec->list); + isec->inode = inode; + isec->sid = SECINITSID_UNLABELED; + isec->sclass = SECCLASS_FILE; + isec->task_sid = sid; + isec->initialized = LABEL_INVALID; + + return 0; +} + +static void selinux_inode_free_security(struct inode *inode) +{ + inode_free_security(inode); +} + +static int selinux_dentry_init_security(struct dentry *dentry, int mode, + const struct qstr *name, void **ctx, + u32 *ctxlen) +{ + u32 newsid; + int rc; + + rc = selinux_determine_inode_label(selinux_cred(current_cred()), + d_inode(dentry->d_parent), name, + inode_mode_to_security_class(mode), + &newsid); + if (rc) + return rc; + + return security_sid_to_context(&selinux_state, newsid, (char **)ctx, + ctxlen); +} + +static int selinux_dentry_create_files_as(struct dentry *dentry, int mode, + struct qstr *name, + const struct cred *old, + struct cred *new) +{ + u32 newsid; + int rc; + struct task_security_struct *tsec; + + rc = selinux_determine_inode_label(selinux_cred(old), + d_inode(dentry->d_parent), name, + inode_mode_to_security_class(mode), + &newsid); + if (rc) + return rc; + + tsec = selinux_cred(new); + tsec->create_sid = newsid; + return 0; +} + +static int selinux_inode_init_security(struct inode *inode, struct inode *dir, + const struct qstr *qstr, + const char **name, + void **value, size_t *len) +{ + const struct task_security_struct *tsec = selinux_cred(current_cred()); + struct superblock_security_struct *sbsec; + u32 newsid, clen; + int rc; + char *context; + + sbsec = dir->i_sb->s_security; + + newsid = tsec->create_sid; + + rc = selinux_determine_inode_label(tsec, dir, qstr, + inode_mode_to_security_class(inode->i_mode), + &newsid); + if (rc) + return rc; + + /* Possibly defer initialization to selinux_complete_init. */ + if (sbsec->flags & SE_SBINITIALIZED) { + struct inode_security_struct *isec = selinux_inode(inode); + isec->sclass = inode_mode_to_security_class(inode->i_mode); + isec->sid = newsid; + isec->initialized = LABEL_INITIALIZED; + } + + if (!selinux_initialized(&selinux_state) || + !(sbsec->flags & SBLABEL_MNT)) + return -EOPNOTSUPP; + + if (name) + *name = XATTR_SELINUX_SUFFIX; + + if (value && len) { + rc = security_sid_to_context_force(&selinux_state, newsid, + &context, &clen); + if (rc) + return rc; + *value = context; + *len = clen; + } + + return 0; +} + +static int selinux_inode_create(struct inode *dir, struct dentry *dentry, umode_t mode) +{ + return may_create(dir, dentry, SECCLASS_FILE); +} + +static int selinux_inode_link(struct dentry *old_dentry, struct inode *dir, struct dentry *new_dentry) +{ + return may_link(dir, old_dentry, MAY_LINK); +} + +static int selinux_inode_unlink(struct inode *dir, struct dentry *dentry) +{ + return may_link(dir, dentry, MAY_UNLINK); +} + +static int selinux_inode_symlink(struct inode *dir, struct dentry *dentry, const char *name) +{ + return may_create(dir, dentry, SECCLASS_LNK_FILE); +} + +static int selinux_inode_mkdir(struct inode *dir, struct dentry *dentry, umode_t mask) +{ + return may_create(dir, dentry, SECCLASS_DIR); +} + +static int selinux_inode_rmdir(struct inode *dir, struct dentry *dentry) +{ + return may_link(dir, dentry, MAY_RMDIR); +} + +static int selinux_inode_mknod(struct inode *dir, struct dentry *dentry, umode_t mode, dev_t dev) +{ + return may_create(dir, dentry, inode_mode_to_security_class(mode)); +} + +static int selinux_inode_rename(struct inode *old_inode, struct dentry *old_dentry, + struct inode *new_inode, struct dentry *new_dentry) +{ + return may_rename(old_inode, old_dentry, new_inode, new_dentry); +} + +static int selinux_inode_readlink(struct dentry *dentry) +{ + const struct cred *cred = current_cred(); + + return dentry_has_perm(cred, dentry, FILE__READ); +} + +static int selinux_inode_follow_link(struct dentry *dentry, struct inode *inode, + bool rcu) +{ + const struct cred *cred = current_cred(); + struct common_audit_data ad; + struct inode_security_struct *isec; + u32 sid; + + validate_creds(cred); + + ad.type = LSM_AUDIT_DATA_DENTRY; + ad.u.dentry = dentry; + sid = cred_sid(cred); + isec = inode_security_rcu(inode, rcu); + if (IS_ERR(isec)) + return PTR_ERR(isec); + + return avc_has_perm_flags(&selinux_state, + sid, isec->sid, isec->sclass, FILE__READ, &ad, + rcu ? MAY_NOT_BLOCK : 0); +} + +static noinline int audit_inode_permission(struct inode *inode, + u32 perms, u32 audited, u32 denied, + int result) +{ + struct common_audit_data ad; + struct inode_security_struct *isec = selinux_inode(inode); + int rc; + + ad.type = LSM_AUDIT_DATA_INODE; + ad.u.inode = inode; + + rc = slow_avc_audit(&selinux_state, + current_sid(), isec->sid, isec->sclass, perms, + audited, denied, result, &ad); + if (rc) + return rc; + return 0; +} + +static int selinux_inode_permission(struct inode *inode, int mask) +{ + const struct cred *cred = current_cred(); + u32 perms; + bool from_access; + bool no_block = mask & MAY_NOT_BLOCK; + struct inode_security_struct *isec; + u32 sid; + struct av_decision avd; + int rc, rc2; + u32 audited, denied; + + from_access = mask & MAY_ACCESS; + mask &= (MAY_READ|MAY_WRITE|MAY_EXEC|MAY_APPEND); + + /* No permission to check. Existence test. */ + if (!mask) + return 0; + + validate_creds(cred); + + if (unlikely(IS_PRIVATE(inode))) + return 0; + + perms = file_mask_to_av(inode->i_mode, mask); + + sid = cred_sid(cred); + isec = inode_security_rcu(inode, no_block); + if (IS_ERR(isec)) + return PTR_ERR(isec); + + rc = avc_has_perm_noaudit(&selinux_state, + sid, isec->sid, isec->sclass, perms, + no_block ? AVC_NONBLOCKING : 0, + &avd); + audited = avc_audit_required(perms, &avd, rc, + from_access ? FILE__AUDIT_ACCESS : 0, + &denied); + if (likely(!audited)) + return rc; + + /* fall back to ref-walk if we have to generate audit */ + if (no_block) + return -ECHILD; + + rc2 = audit_inode_permission(inode, perms, audited, denied, rc); + if (rc2) + return rc2; + return rc; +} + +static int selinux_inode_setattr(struct dentry *dentry, struct iattr *iattr) +{ + const struct cred *cred = current_cred(); + struct inode *inode = d_backing_inode(dentry); + unsigned int ia_valid = iattr->ia_valid; + __u32 av = FILE__WRITE; + + /* ATTR_FORCE is just used for ATTR_KILL_S[UG]ID. */ + if (ia_valid & ATTR_FORCE) { + ia_valid &= ~(ATTR_KILL_SUID | ATTR_KILL_SGID | ATTR_MODE | + ATTR_FORCE); + if (!ia_valid) + return 0; + } + + if (ia_valid & (ATTR_MODE | ATTR_UID | ATTR_GID | + ATTR_ATIME_SET | ATTR_MTIME_SET | ATTR_TIMES_SET)) + return dentry_has_perm(cred, dentry, FILE__SETATTR); + + if (selinux_policycap_openperm() && + inode->i_sb->s_magic != SOCKFS_MAGIC && + (ia_valid & ATTR_SIZE) && + !(ia_valid & ATTR_FILE)) + av |= FILE__OPEN; + + return dentry_has_perm(cred, dentry, av); +} + +static int selinux_inode_getattr(const struct path *path) +{ + return path_has_perm(current_cred(), path, FILE__GETATTR); +} + +static bool has_cap_mac_admin(bool audit) +{ + const struct cred *cred = current_cred(); + unsigned int opts = audit ? CAP_OPT_NONE : CAP_OPT_NOAUDIT; + + if (cap_capable(cred, &init_user_ns, CAP_MAC_ADMIN, opts)) + return false; + if (cred_has_capability(cred, CAP_MAC_ADMIN, opts, true)) + return false; + return true; +} + +static int selinux_inode_setxattr(struct dentry *dentry, const char *name, + const void *value, size_t size, int flags) +{ + struct inode *inode = d_backing_inode(dentry); + struct inode_security_struct *isec; + struct superblock_security_struct *sbsec; + struct common_audit_data ad; + u32 newsid, sid = current_sid(); + int rc = 0; + + if (strcmp(name, XATTR_NAME_SELINUX)) { + rc = cap_inode_setxattr(dentry, name, value, size, flags); + if (rc) + return rc; + + /* Not an attribute we recognize, so just check the + ordinary setattr permission. */ + return dentry_has_perm(current_cred(), dentry, FILE__SETATTR); + } + + if (!selinux_initialized(&selinux_state)) + return (inode_owner_or_capable(inode) ? 0 : -EPERM); + + sbsec = inode->i_sb->s_security; + if (!(sbsec->flags & SBLABEL_MNT)) + return -EOPNOTSUPP; + + if (!inode_owner_or_capable(inode)) + return -EPERM; + + ad.type = LSM_AUDIT_DATA_DENTRY; + ad.u.dentry = dentry; + + isec = backing_inode_security(dentry); + rc = avc_has_perm(&selinux_state, + sid, isec->sid, isec->sclass, + FILE__RELABELFROM, &ad); + if (rc) + return rc; + + rc = security_context_to_sid(&selinux_state, value, size, &newsid, + GFP_KERNEL); + if (rc == -EINVAL) { + if (!has_cap_mac_admin(true)) { + struct audit_buffer *ab; + size_t audit_size; + + /* We strip a nul only if it is at the end, otherwise the + * context contains a nul and we should audit that */ + if (value) { + const char *str = value; + + if (str[size - 1] == '\0') + audit_size = size - 1; + else + audit_size = size; + } else { + audit_size = 0; + } + ab = audit_log_start(audit_context(), + GFP_ATOMIC, AUDIT_SELINUX_ERR); + audit_log_format(ab, "op=setxattr invalid_context="); + audit_log_n_untrustedstring(ab, value, audit_size); + audit_log_end(ab); + + return rc; + } + rc = security_context_to_sid_force(&selinux_state, value, + size, &newsid); + } + if (rc) + return rc; + + rc = avc_has_perm(&selinux_state, + sid, newsid, isec->sclass, + FILE__RELABELTO, &ad); + if (rc) + return rc; + + rc = security_validate_transition(&selinux_state, isec->sid, newsid, + sid, isec->sclass); + if (rc) + return rc; + + return avc_has_perm(&selinux_state, + newsid, + sbsec->sid, + SECCLASS_FILESYSTEM, + FILESYSTEM__ASSOCIATE, + &ad); +} + +static void selinux_inode_post_setxattr(struct dentry *dentry, const char *name, + const void *value, size_t size, + int flags) +{ + struct inode *inode = d_backing_inode(dentry); + struct inode_security_struct *isec; + u32 newsid; + int rc; + + if (strcmp(name, XATTR_NAME_SELINUX)) { + /* Not an attribute we recognize, so nothing to do. */ + return; + } + + if (!selinux_initialized(&selinux_state)) { + /* If we haven't even been initialized, then we can't validate + * against a policy, so leave the label as invalid. It may + * resolve to a valid label on the next revalidation try if + * we've since initialized. + */ + return; + } + + rc = security_context_to_sid_force(&selinux_state, value, size, + &newsid); + if (rc) { + pr_err("SELinux: unable to map context to SID" + "for (%s, %lu), rc=%d\n", + inode->i_sb->s_id, inode->i_ino, -rc); + return; + } + + isec = backing_inode_security(dentry); + spin_lock(&isec->lock); + isec->sclass = inode_mode_to_security_class(inode->i_mode); + isec->sid = newsid; + isec->initialized = LABEL_INITIALIZED; + spin_unlock(&isec->lock); + + return; +} + +static int selinux_inode_getxattr(struct dentry *dentry, const char *name) +{ + const struct cred *cred = current_cred(); + + return dentry_has_perm(cred, dentry, FILE__GETATTR); +} + +static int selinux_inode_listxattr(struct dentry *dentry) +{ + const struct cred *cred = current_cred(); + + return dentry_has_perm(cred, dentry, FILE__GETATTR); +} + +static int selinux_inode_removexattr(struct dentry *dentry, const char *name) +{ + if (strcmp(name, XATTR_NAME_SELINUX)) { + int rc = cap_inode_removexattr(dentry, name); + if (rc) + return rc; + + /* Not an attribute we recognize, so just check the + ordinary setattr permission. */ + return dentry_has_perm(current_cred(), dentry, FILE__SETATTR); + } + + if (!selinux_initialized(&selinux_state)) + return 0; + + /* No one is allowed to remove a SELinux security label. + You can change the label, but all data must be labeled. */ + return -EACCES; +} + +static int selinux_path_notify(const struct path *path, u64 mask, + unsigned int obj_type) +{ + int ret; + u32 perm; + + struct common_audit_data ad; + + ad.type = LSM_AUDIT_DATA_PATH; + ad.u.path = *path; + + /* + * Set permission needed based on the type of mark being set. + * Performs an additional check for sb watches. + */ + switch (obj_type) { + case FSNOTIFY_OBJ_TYPE_VFSMOUNT: + perm = FILE__WATCH_MOUNT; + break; + case FSNOTIFY_OBJ_TYPE_SB: + perm = FILE__WATCH_SB; + ret = superblock_has_perm(current_cred(), path->dentry->d_sb, + FILESYSTEM__WATCH, &ad); + if (ret) + return ret; + break; + case FSNOTIFY_OBJ_TYPE_INODE: + perm = FILE__WATCH; + break; + default: + return -EINVAL; + } + + /* blocking watches require the file:watch_with_perm permission */ + if (mask & (ALL_FSNOTIFY_PERM_EVENTS)) + perm |= FILE__WATCH_WITH_PERM; + + /* watches on read-like events need the file:watch_reads permission */ + if (mask & (FS_ACCESS | FS_ACCESS_PERM | FS_CLOSE_NOWRITE)) + perm |= FILE__WATCH_READS; + + return path_has_perm(current_cred(), path, perm); +} + +/* + * Copy the inode security context value to the user. + * + * Permission check is handled by selinux_inode_getxattr hook. + */ +static int selinux_inode_getsecurity(struct inode *inode, const char *name, void **buffer, bool alloc) +{ + u32 size; + int error; + char *context = NULL; + struct inode_security_struct *isec; + + /* + * If we're not initialized yet, then we can't validate contexts, so + * just let vfs_getxattr fall back to using the on-disk xattr. + */ + if (!selinux_initialized(&selinux_state) || + strcmp(name, XATTR_SELINUX_SUFFIX)) + return -EOPNOTSUPP; + + /* + * If the caller has CAP_MAC_ADMIN, then get the raw context + * value even if it is not defined by current policy; otherwise, + * use the in-core value under current policy. + * Use the non-auditing forms of the permission checks since + * getxattr may be called by unprivileged processes commonly + * and lack of permission just means that we fall back to the + * in-core context value, not a denial. + */ + isec = inode_security(inode); + if (has_cap_mac_admin(false)) + error = security_sid_to_context_force(&selinux_state, + isec->sid, &context, + &size); + else + error = security_sid_to_context(&selinux_state, isec->sid, + &context, &size); + if (error) + return error; + error = size; + if (alloc) { + *buffer = context; + goto out_nofree; + } + kfree(context); +out_nofree: + return error; +} + +static int selinux_inode_setsecurity(struct inode *inode, const char *name, + const void *value, size_t size, int flags) +{ + struct inode_security_struct *isec = inode_security_novalidate(inode); + struct superblock_security_struct *sbsec = inode->i_sb->s_security; + u32 newsid; + int rc; + + if (strcmp(name, XATTR_SELINUX_SUFFIX)) + return -EOPNOTSUPP; + + if (!(sbsec->flags & SBLABEL_MNT)) + return -EOPNOTSUPP; + + if (!value || !size) + return -EACCES; + + rc = security_context_to_sid(&selinux_state, value, size, &newsid, + GFP_KERNEL); + if (rc) + return rc; + + spin_lock(&isec->lock); + isec->sclass = inode_mode_to_security_class(inode->i_mode); + isec->sid = newsid; + isec->initialized = LABEL_INITIALIZED; + spin_unlock(&isec->lock); + return 0; +} + +static int selinux_inode_listsecurity(struct inode *inode, char *buffer, size_t buffer_size) +{ + const int len = sizeof(XATTR_NAME_SELINUX); + + if (!selinux_initialized(&selinux_state)) + return 0; + + if (buffer && len <= buffer_size) + memcpy(buffer, XATTR_NAME_SELINUX, len); + return len; +} + +static void selinux_inode_getsecid(struct inode *inode, u32 *secid) +{ + struct inode_security_struct *isec = inode_security_novalidate(inode); + *secid = isec->sid; +} + +static int selinux_inode_copy_up(struct dentry *src, struct cred **new) +{ + u32 sid; + struct task_security_struct *tsec; + struct cred *new_creds = *new; + + if (new_creds == NULL) { + new_creds = prepare_creds(); + if (!new_creds) + return -ENOMEM; + } + + tsec = selinux_cred(new_creds); + /* Get label from overlay inode and set it in create_sid */ + selinux_inode_getsecid(d_inode(src), &sid); + tsec->create_sid = sid; + *new = new_creds; + return 0; +} + +static int selinux_inode_copy_up_xattr(const char *name) +{ + /* The copy_up hook above sets the initial context on an inode, but we + * don't then want to overwrite it by blindly copying all the lower + * xattrs up. Instead, we have to filter out SELinux-related xattrs. + */ + if (strcmp(name, XATTR_NAME_SELINUX) == 0) + return 1; /* Discard */ + /* + * Any other attribute apart from SELINUX is not claimed, supported + * by selinux. + */ + return -EOPNOTSUPP; +} + +/* kernfs node operations */ + +static int selinux_kernfs_init_security(struct kernfs_node *kn_dir, + struct kernfs_node *kn) +{ + const struct task_security_struct *tsec = selinux_cred(current_cred()); + u32 parent_sid, newsid, clen; + int rc; + char *context; + + rc = kernfs_xattr_get(kn_dir, XATTR_NAME_SELINUX, NULL, 0); + if (rc == -ENODATA) + return 0; + else if (rc < 0) + return rc; + + clen = (u32)rc; + context = kmalloc(clen, GFP_KERNEL); + if (!context) + return -ENOMEM; + + rc = kernfs_xattr_get(kn_dir, XATTR_NAME_SELINUX, context, clen); + if (rc < 0) { + kfree(context); + return rc; + } + + rc = security_context_to_sid(&selinux_state, context, clen, &parent_sid, + GFP_KERNEL); + kfree(context); + if (rc) + return rc; + + if (tsec->create_sid) { + newsid = tsec->create_sid; + } else { + u16 secclass = inode_mode_to_security_class(kn->mode); + struct qstr q; + + q.name = kn->name; + q.hash_len = hashlen_string(kn_dir, kn->name); + + rc = security_transition_sid(&selinux_state, tsec->sid, + parent_sid, secclass, &q, + &newsid); + if (rc) + return rc; + } + + rc = security_sid_to_context_force(&selinux_state, newsid, + &context, &clen); + if (rc) + return rc; + + rc = kernfs_xattr_set(kn, XATTR_NAME_SELINUX, context, clen, + XATTR_CREATE); + kfree(context); + return rc; +} + + +/* file security operations */ + +static int selinux_revalidate_file_permission(struct file *file, int mask) +{ + const struct cred *cred = current_cred(); + struct inode *inode = file_inode(file); + + /* file_mask_to_av won't add FILE__WRITE if MAY_APPEND is set */ + if ((file->f_flags & O_APPEND) && (mask & MAY_WRITE)) + mask |= MAY_APPEND; + + return file_has_perm(cred, file, + file_mask_to_av(inode->i_mode, mask)); +} + +static int selinux_file_permission(struct file *file, int mask) +{ + struct inode *inode = file_inode(file); + struct file_security_struct *fsec = selinux_file(file); + struct inode_security_struct *isec; + u32 sid = current_sid(); + + if (!mask) + /* No permission to check. Existence test. */ + return 0; + + isec = inode_security(inode); + if (sid == fsec->sid && fsec->isid == isec->sid && + fsec->pseqno == avc_policy_seqno(&selinux_state)) + /* No change since file_open check. */ + return 0; + + return selinux_revalidate_file_permission(file, mask); +} + +static int selinux_file_alloc_security(struct file *file) +{ + struct file_security_struct *fsec = selinux_file(file); + u32 sid = current_sid(); + + fsec->sid = sid; + fsec->fown_sid = sid; + + return 0; +} + +/* + * Check whether a task has the ioctl permission and cmd + * operation to an inode. + */ +static int ioctl_has_perm(const struct cred *cred, struct file *file, + u32 requested, u16 cmd) +{ + struct common_audit_data ad; + struct file_security_struct *fsec = selinux_file(file); + struct inode *inode = file_inode(file); + struct inode_security_struct *isec; + struct lsm_ioctlop_audit ioctl; + u32 ssid = cred_sid(cred); + int rc; + u8 driver = cmd >> 8; + u8 xperm = cmd & 0xff; + + ad.type = LSM_AUDIT_DATA_IOCTL_OP; + ad.u.op = &ioctl; + ad.u.op->cmd = cmd; + ad.u.op->path = file->f_path; + + if (ssid != fsec->sid) { + rc = avc_has_perm(&selinux_state, + ssid, fsec->sid, + SECCLASS_FD, + FD__USE, + &ad); + if (rc) + goto out; + } + + if (unlikely(IS_PRIVATE(inode))) + return 0; + + isec = inode_security(inode); + rc = avc_has_extended_perms(&selinux_state, + ssid, isec->sid, isec->sclass, + requested, driver, xperm, &ad); +out: + return rc; +} + +static int selinux_file_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + const struct cred *cred = current_cred(); + int error = 0; + + switch (cmd) { + case FIONREAD: + case FIBMAP: + case FIGETBSZ: + case FS_IOC_GETFLAGS: + case FS_IOC_GETVERSION: + error = file_has_perm(cred, file, FILE__GETATTR); + break; + + case FS_IOC_SETFLAGS: + case FS_IOC_SETVERSION: + error = file_has_perm(cred, file, FILE__SETATTR); + break; + + /* sys_ioctl() checks */ + case FIONBIO: + case FIOASYNC: + error = file_has_perm(cred, file, 0); + break; + + case KDSKBENT: + case KDSKBSENT: + error = cred_has_capability(cred, CAP_SYS_TTY_CONFIG, + CAP_OPT_NONE, true); + break; + + case FIOCLEX: + case FIONCLEX: + if (!selinux_policycap_ioctl_skip_cloexec()) + error = ioctl_has_perm(cred, file, FILE__IOCTL, (u16) cmd); + break; + + /* default case assumes that the command will go + * to the file's ioctl() function. + */ + default: + error = ioctl_has_perm(cred, file, FILE__IOCTL, (u16) cmd); + } + return error; +} + +static int default_noexec __ro_after_init; + +static int file_map_prot_check(struct file *file, unsigned long prot, int shared) +{ + const struct cred *cred = current_cred(); + u32 sid = cred_sid(cred); + int rc = 0; + + if (default_noexec && + (prot & PROT_EXEC) && (!file || IS_PRIVATE(file_inode(file)) || + (!shared && (prot & PROT_WRITE)))) { + /* + * We are making executable an anonymous mapping or a + * private file mapping that will also be writable. + * This has an additional check. + */ + rc = avc_has_perm(&selinux_state, + sid, sid, SECCLASS_PROCESS, + PROCESS__EXECMEM, NULL); + if (rc) + goto error; + } + + if (file) { + /* read access is always possible with a mapping */ + u32 av = FILE__READ; + + /* write access only matters if the mapping is shared */ + if (shared && (prot & PROT_WRITE)) + av |= FILE__WRITE; + + if (prot & PROT_EXEC) + av |= FILE__EXECUTE; + + return file_has_perm(cred, file, av); + } + +error: + return rc; +} + +static int selinux_mmap_addr(unsigned long addr) +{ + int rc = 0; + + if (addr < CONFIG_LSM_MMAP_MIN_ADDR) { + u32 sid = current_sid(); + rc = avc_has_perm(&selinux_state, + sid, sid, SECCLASS_MEMPROTECT, + MEMPROTECT__MMAP_ZERO, NULL); + } + + return rc; +} + +static int selinux_mmap_file(struct file *file, unsigned long reqprot, + unsigned long prot, unsigned long flags) +{ + struct common_audit_data ad; + int rc; + + if (file) { + ad.type = LSM_AUDIT_DATA_FILE; + ad.u.file = file; + rc = inode_has_perm(current_cred(), file_inode(file), + FILE__MAP, &ad); + if (rc) + return rc; + } + + if (checkreqprot_get(&selinux_state)) + prot = reqprot; + + return file_map_prot_check(file, prot, + (flags & MAP_TYPE) == MAP_SHARED); +} + +static int selinux_file_mprotect(struct vm_area_struct *vma, + unsigned long reqprot, + unsigned long prot) +{ + const struct cred *cred = current_cred(); + u32 sid = cred_sid(cred); + + if (checkreqprot_get(&selinux_state)) + prot = reqprot; + + if (default_noexec && + (prot & PROT_EXEC) && !(vma->vm_flags & VM_EXEC)) { + int rc = 0; + if (vma->vm_start >= vma->vm_mm->start_brk && + vma->vm_end <= vma->vm_mm->brk) { + rc = avc_has_perm(&selinux_state, + sid, sid, SECCLASS_PROCESS, + PROCESS__EXECHEAP, NULL); + } else if (!vma->vm_file && + ((vma->vm_start <= vma->vm_mm->start_stack && + vma->vm_end >= vma->vm_mm->start_stack) || + vma_is_stack_for_current(vma))) { + rc = avc_has_perm(&selinux_state, + sid, sid, SECCLASS_PROCESS, + PROCESS__EXECSTACK, NULL); + } else if (vma->vm_file && vma->anon_vma) { + /* + * We are making executable a file mapping that has + * had some COW done. Since pages might have been + * written, check ability to execute the possibly + * modified content. This typically should only + * occur for text relocations. + */ + rc = file_has_perm(cred, vma->vm_file, FILE__EXECMOD); + } + if (rc) + return rc; + } + + return file_map_prot_check(vma->vm_file, prot, vma->vm_flags&VM_SHARED); +} + +static int selinux_file_lock(struct file *file, unsigned int cmd) +{ + const struct cred *cred = current_cred(); + + return file_has_perm(cred, file, FILE__LOCK); +} + +static int selinux_file_fcntl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + const struct cred *cred = current_cred(); + int err = 0; + + switch (cmd) { + case F_SETFL: + if ((file->f_flags & O_APPEND) && !(arg & O_APPEND)) { + err = file_has_perm(cred, file, FILE__WRITE); + break; + } + fallthrough; + case F_SETOWN: + case F_SETSIG: + case F_GETFL: + case F_GETOWN: + case F_GETSIG: + case F_GETOWNER_UIDS: + /* Just check FD__USE permission */ + err = file_has_perm(cred, file, 0); + break; + case F_GETLK: + case F_SETLK: + case F_SETLKW: + case F_OFD_GETLK: + case F_OFD_SETLK: + case F_OFD_SETLKW: +#if BITS_PER_LONG == 32 + case F_GETLK64: + case F_SETLK64: + case F_SETLKW64: +#endif + err = file_has_perm(cred, file, FILE__LOCK); + break; + } + + return err; +} + +static void selinux_file_set_fowner(struct file *file) +{ + struct file_security_struct *fsec; + + fsec = selinux_file(file); + fsec->fown_sid = current_sid(); +} + +static int selinux_file_send_sigiotask(struct task_struct *tsk, + struct fown_struct *fown, int signum) +{ + struct file *file; + u32 sid = task_sid(tsk); + u32 perm; + struct file_security_struct *fsec; + + /* struct fown_struct is never outside the context of a struct file */ + file = container_of(fown, struct file, f_owner); + + fsec = selinux_file(file); + + if (!signum) + perm = signal_to_av(SIGIO); /* as per send_sigio_to_task */ + else + perm = signal_to_av(signum); + + return avc_has_perm(&selinux_state, + fsec->fown_sid, sid, + SECCLASS_PROCESS, perm, NULL); +} + +static int selinux_file_receive(struct file *file) +{ + const struct cred *cred = current_cred(); + + return file_has_perm(cred, file, file_to_av(file)); +} + +static int selinux_file_open(struct file *file) +{ + struct file_security_struct *fsec; + struct inode_security_struct *isec; + + fsec = selinux_file(file); + isec = inode_security(file_inode(file)); + /* + * Save inode label and policy sequence number + * at open-time so that selinux_file_permission + * can determine whether revalidation is necessary. + * Task label is already saved in the file security + * struct as its SID. + */ + fsec->isid = isec->sid; + fsec->pseqno = avc_policy_seqno(&selinux_state); + /* + * Since the inode label or policy seqno may have changed + * between the selinux_inode_permission check and the saving + * of state above, recheck that access is still permitted. + * Otherwise, access might never be revalidated against the + * new inode label or new policy. + * This check is not redundant - do not remove. + */ + return file_path_has_perm(file->f_cred, file, open_file_to_av(file)); +} + +/* task security operations */ + +static int selinux_task_alloc(struct task_struct *task, + unsigned long clone_flags) +{ + u32 sid = current_sid(); + + return avc_has_perm(&selinux_state, + sid, sid, SECCLASS_PROCESS, PROCESS__FORK, NULL); +} + +/* + * prepare a new set of credentials for modification + */ +static int selinux_cred_prepare(struct cred *new, const struct cred *old, + gfp_t gfp) +{ + const struct task_security_struct *old_tsec = selinux_cred(old); + struct task_security_struct *tsec = selinux_cred(new); + + *tsec = *old_tsec; + return 0; +} + +/* + * transfer the SELinux data to a blank set of creds + */ +static void selinux_cred_transfer(struct cred *new, const struct cred *old) +{ + const struct task_security_struct *old_tsec = selinux_cred(old); + struct task_security_struct *tsec = selinux_cred(new); + + *tsec = *old_tsec; +} + +static void selinux_cred_getsecid(const struct cred *c, u32 *secid) +{ + *secid = cred_sid(c); +} + +/* + * set the security data for a kernel service + * - all the creation contexts are set to unlabelled + */ +static int selinux_kernel_act_as(struct cred *new, u32 secid) +{ + struct task_security_struct *tsec = selinux_cred(new); + u32 sid = current_sid(); + int ret; + + ret = avc_has_perm(&selinux_state, + sid, secid, + SECCLASS_KERNEL_SERVICE, + KERNEL_SERVICE__USE_AS_OVERRIDE, + NULL); + if (ret == 0) { + tsec->sid = secid; + tsec->create_sid = 0; + tsec->keycreate_sid = 0; + tsec->sockcreate_sid = 0; + } + return ret; +} + +/* + * set the file creation context in a security record to the same as the + * objective context of the specified inode + */ +static int selinux_kernel_create_files_as(struct cred *new, struct inode *inode) +{ + struct inode_security_struct *isec = inode_security(inode); + struct task_security_struct *tsec = selinux_cred(new); + u32 sid = current_sid(); + int ret; + + ret = avc_has_perm(&selinux_state, + sid, isec->sid, + SECCLASS_KERNEL_SERVICE, + KERNEL_SERVICE__CREATE_FILES_AS, + NULL); + + if (ret == 0) + tsec->create_sid = isec->sid; + return ret; +} + +static int selinux_kernel_module_request(char *kmod_name) +{ + struct common_audit_data ad; + + ad.type = LSM_AUDIT_DATA_KMOD; + ad.u.kmod_name = kmod_name; + + return avc_has_perm(&selinux_state, + current_sid(), SECINITSID_KERNEL, SECCLASS_SYSTEM, + SYSTEM__MODULE_REQUEST, &ad); +} + +static int selinux_kernel_module_from_file(struct file *file) +{ + struct common_audit_data ad; + struct inode_security_struct *isec; + struct file_security_struct *fsec; + u32 sid = current_sid(); + int rc; + + /* init_module */ + if (file == NULL) + return avc_has_perm(&selinux_state, + sid, sid, SECCLASS_SYSTEM, + SYSTEM__MODULE_LOAD, NULL); + + /* finit_module */ + + ad.type = LSM_AUDIT_DATA_FILE; + ad.u.file = file; + + fsec = selinux_file(file); + if (sid != fsec->sid) { + rc = avc_has_perm(&selinux_state, + sid, fsec->sid, SECCLASS_FD, FD__USE, &ad); + if (rc) + return rc; + } + + isec = inode_security(file_inode(file)); + return avc_has_perm(&selinux_state, + sid, isec->sid, SECCLASS_SYSTEM, + SYSTEM__MODULE_LOAD, &ad); +} + +static int selinux_kernel_read_file(struct file *file, + enum kernel_read_file_id id, + bool contents) +{ + int rc = 0; + + switch (id) { + case READING_MODULE: + rc = selinux_kernel_module_from_file(contents ? file : NULL); + break; + default: + break; + } + + return rc; +} + +static int selinux_kernel_load_data(enum kernel_load_data_id id, bool contents) +{ + int rc = 0; + + switch (id) { + case LOADING_MODULE: + rc = selinux_kernel_module_from_file(NULL); + default: + break; + } + + return rc; +} + +static int selinux_task_setpgid(struct task_struct *p, pid_t pgid) +{ + return avc_has_perm(&selinux_state, + current_sid(), task_sid(p), SECCLASS_PROCESS, + PROCESS__SETPGID, NULL); +} + +static int selinux_task_getpgid(struct task_struct *p) +{ + return avc_has_perm(&selinux_state, + current_sid(), task_sid(p), SECCLASS_PROCESS, + PROCESS__GETPGID, NULL); +} + +static int selinux_task_getsid(struct task_struct *p) +{ + return avc_has_perm(&selinux_state, + current_sid(), task_sid(p), SECCLASS_PROCESS, + PROCESS__GETSESSION, NULL); +} + +static void selinux_task_getsecid(struct task_struct *p, u32 *secid) +{ + *secid = task_sid(p); +} + +static int selinux_task_setnice(struct task_struct *p, int nice) +{ + return avc_has_perm(&selinux_state, + current_sid(), task_sid(p), SECCLASS_PROCESS, + PROCESS__SETSCHED, NULL); +} + +static int selinux_task_setioprio(struct task_struct *p, int ioprio) +{ + return avc_has_perm(&selinux_state, + current_sid(), task_sid(p), SECCLASS_PROCESS, + PROCESS__SETSCHED, NULL); +} + +static int selinux_task_getioprio(struct task_struct *p) +{ + return avc_has_perm(&selinux_state, + current_sid(), task_sid(p), SECCLASS_PROCESS, + PROCESS__GETSCHED, NULL); +} + +static int selinux_task_prlimit(const struct cred *cred, const struct cred *tcred, + unsigned int flags) +{ + u32 av = 0; + + if (!flags) + return 0; + if (flags & LSM_PRLIMIT_WRITE) + av |= PROCESS__SETRLIMIT; + if (flags & LSM_PRLIMIT_READ) + av |= PROCESS__GETRLIMIT; + return avc_has_perm(&selinux_state, + cred_sid(cred), cred_sid(tcred), + SECCLASS_PROCESS, av, NULL); +} + +static int selinux_task_setrlimit(struct task_struct *p, unsigned int resource, + struct rlimit *new_rlim) +{ + struct rlimit *old_rlim = p->signal->rlim + resource; + + /* Control the ability to change the hard limit (whether + lowering or raising it), so that the hard limit can + later be used as a safe reset point for the soft limit + upon context transitions. See selinux_bprm_committing_creds. */ + if (old_rlim->rlim_max != new_rlim->rlim_max) + return avc_has_perm(&selinux_state, + current_sid(), task_sid(p), + SECCLASS_PROCESS, PROCESS__SETRLIMIT, NULL); + + return 0; +} + +static int selinux_task_setscheduler(struct task_struct *p) +{ + return avc_has_perm(&selinux_state, + current_sid(), task_sid(p), SECCLASS_PROCESS, + PROCESS__SETSCHED, NULL); +} + +static int selinux_task_getscheduler(struct task_struct *p) +{ + return avc_has_perm(&selinux_state, + current_sid(), task_sid(p), SECCLASS_PROCESS, + PROCESS__GETSCHED, NULL); +} + +static int selinux_task_movememory(struct task_struct *p) +{ + return avc_has_perm(&selinux_state, + current_sid(), task_sid(p), SECCLASS_PROCESS, + PROCESS__SETSCHED, NULL); +} + +static int selinux_task_kill(struct task_struct *p, struct kernel_siginfo *info, + int sig, const struct cred *cred) +{ + u32 secid; + u32 perm; + + if (!sig) + perm = PROCESS__SIGNULL; /* null signal; existence test */ + else + perm = signal_to_av(sig); + if (!cred) + secid = current_sid(); + else + secid = cred_sid(cred); + return avc_has_perm(&selinux_state, + secid, task_sid(p), SECCLASS_PROCESS, perm, NULL); +} + +static void selinux_task_to_inode(struct task_struct *p, + struct inode *inode) +{ + struct inode_security_struct *isec = selinux_inode(inode); + u32 sid = task_sid(p); + + spin_lock(&isec->lock); + isec->sclass = inode_mode_to_security_class(inode->i_mode); + isec->sid = sid; + isec->initialized = LABEL_INITIALIZED; + spin_unlock(&isec->lock); +} + +/* Returns error only if unable to parse addresses */ +static int selinux_parse_skb_ipv4(struct sk_buff *skb, + struct common_audit_data *ad, u8 *proto) +{ + int offset, ihlen, ret = -EINVAL; + struct iphdr _iph, *ih; + + offset = skb_network_offset(skb); + ih = skb_header_pointer(skb, offset, sizeof(_iph), &_iph); + if (ih == NULL) + goto out; + + ihlen = ih->ihl * 4; + if (ihlen < sizeof(_iph)) + goto out; + + ad->u.net->v4info.saddr = ih->saddr; + ad->u.net->v4info.daddr = ih->daddr; + ret = 0; + + if (proto) + *proto = ih->protocol; + + switch (ih->protocol) { + case IPPROTO_TCP: { + struct tcphdr _tcph, *th; + + if (ntohs(ih->frag_off) & IP_OFFSET) + break; + + offset += ihlen; + th = skb_header_pointer(skb, offset, sizeof(_tcph), &_tcph); + if (th == NULL) + break; + + ad->u.net->sport = th->source; + ad->u.net->dport = th->dest; + break; + } + + case IPPROTO_UDP: { + struct udphdr _udph, *uh; + + if (ntohs(ih->frag_off) & IP_OFFSET) + break; + + offset += ihlen; + uh = skb_header_pointer(skb, offset, sizeof(_udph), &_udph); + if (uh == NULL) + break; + + ad->u.net->sport = uh->source; + ad->u.net->dport = uh->dest; + break; + } + + case IPPROTO_DCCP: { + struct dccp_hdr _dccph, *dh; + + if (ntohs(ih->frag_off) & IP_OFFSET) + break; + + offset += ihlen; + dh = skb_header_pointer(skb, offset, sizeof(_dccph), &_dccph); + if (dh == NULL) + break; + + ad->u.net->sport = dh->dccph_sport; + ad->u.net->dport = dh->dccph_dport; + break; + } + +#if IS_ENABLED(CONFIG_IP_SCTP) + case IPPROTO_SCTP: { + struct sctphdr _sctph, *sh; + + if (ntohs(ih->frag_off) & IP_OFFSET) + break; + + offset += ihlen; + sh = skb_header_pointer(skb, offset, sizeof(_sctph), &_sctph); + if (sh == NULL) + break; + + ad->u.net->sport = sh->source; + ad->u.net->dport = sh->dest; + break; + } +#endif + default: + break; + } +out: + return ret; +} + +#if IS_ENABLED(CONFIG_IPV6) + +/* Returns error only if unable to parse addresses */ +static int selinux_parse_skb_ipv6(struct sk_buff *skb, + struct common_audit_data *ad, u8 *proto) +{ + u8 nexthdr; + int ret = -EINVAL, offset; + struct ipv6hdr _ipv6h, *ip6; + __be16 frag_off; + + offset = skb_network_offset(skb); + ip6 = skb_header_pointer(skb, offset, sizeof(_ipv6h), &_ipv6h); + if (ip6 == NULL) + goto out; + + ad->u.net->v6info.saddr = ip6->saddr; + ad->u.net->v6info.daddr = ip6->daddr; + ret = 0; + + nexthdr = ip6->nexthdr; + offset += sizeof(_ipv6h); + offset = ipv6_skip_exthdr(skb, offset, &nexthdr, &frag_off); + if (offset < 0) + goto out; + + if (proto) + *proto = nexthdr; + + switch (nexthdr) { + case IPPROTO_TCP: { + struct tcphdr _tcph, *th; + + th = skb_header_pointer(skb, offset, sizeof(_tcph), &_tcph); + if (th == NULL) + break; + + ad->u.net->sport = th->source; + ad->u.net->dport = th->dest; + break; + } + + case IPPROTO_UDP: { + struct udphdr _udph, *uh; + + uh = skb_header_pointer(skb, offset, sizeof(_udph), &_udph); + if (uh == NULL) + break; + + ad->u.net->sport = uh->source; + ad->u.net->dport = uh->dest; + break; + } + + case IPPROTO_DCCP: { + struct dccp_hdr _dccph, *dh; + + dh = skb_header_pointer(skb, offset, sizeof(_dccph), &_dccph); + if (dh == NULL) + break; + + ad->u.net->sport = dh->dccph_sport; + ad->u.net->dport = dh->dccph_dport; + break; + } + +#if IS_ENABLED(CONFIG_IP_SCTP) + case IPPROTO_SCTP: { + struct sctphdr _sctph, *sh; + + sh = skb_header_pointer(skb, offset, sizeof(_sctph), &_sctph); + if (sh == NULL) + break; + + ad->u.net->sport = sh->source; + ad->u.net->dport = sh->dest; + break; + } +#endif + /* includes fragments */ + default: + break; + } +out: + return ret; +} + +#endif /* IPV6 */ + +static int selinux_parse_skb(struct sk_buff *skb, struct common_audit_data *ad, + char **_addrp, int src, u8 *proto) +{ + char *addrp; + int ret; + + switch (ad->u.net->family) { + case PF_INET: + ret = selinux_parse_skb_ipv4(skb, ad, proto); + if (ret) + goto parse_error; + addrp = (char *)(src ? &ad->u.net->v4info.saddr : + &ad->u.net->v4info.daddr); + goto okay; + +#if IS_ENABLED(CONFIG_IPV6) + case PF_INET6: + ret = selinux_parse_skb_ipv6(skb, ad, proto); + if (ret) + goto parse_error; + addrp = (char *)(src ? &ad->u.net->v6info.saddr : + &ad->u.net->v6info.daddr); + goto okay; +#endif /* IPV6 */ + default: + addrp = NULL; + goto okay; + } + +parse_error: + pr_warn( + "SELinux: failure in selinux_parse_skb()," + " unable to parse packet\n"); + return ret; + +okay: + if (_addrp) + *_addrp = addrp; + return 0; +} + +/** + * selinux_skb_peerlbl_sid - Determine the peer label of a packet + * @skb: the packet + * @family: protocol family + * @sid: the packet's peer label SID + * + * Description: + * Check the various different forms of network peer labeling and determine + * the peer label/SID for the packet; most of the magic actually occurs in + * the security server function security_net_peersid_cmp(). The function + * returns zero if the value in @sid is valid (although it may be SECSID_NULL) + * or -EACCES if @sid is invalid due to inconsistencies with the different + * peer labels. + * + */ +static int selinux_skb_peerlbl_sid(struct sk_buff *skb, u16 family, u32 *sid) +{ + int err; + u32 xfrm_sid; + u32 nlbl_sid; + u32 nlbl_type; + + err = selinux_xfrm_skb_sid(skb, &xfrm_sid); + if (unlikely(err)) + return -EACCES; + err = selinux_netlbl_skbuff_getsid(skb, family, &nlbl_type, &nlbl_sid); + if (unlikely(err)) + return -EACCES; + + err = security_net_peersid_resolve(&selinux_state, nlbl_sid, + nlbl_type, xfrm_sid, sid); + if (unlikely(err)) { + pr_warn( + "SELinux: failure in selinux_skb_peerlbl_sid()," + " unable to determine packet's peer label\n"); + return -EACCES; + } + + return 0; +} + +/** + * selinux_conn_sid - Determine the child socket label for a connection + * @sk_sid: the parent socket's SID + * @skb_sid: the packet's SID + * @conn_sid: the resulting connection SID + * + * If @skb_sid is valid then the user:role:type information from @sk_sid is + * combined with the MLS information from @skb_sid in order to create + * @conn_sid. If @skb_sid is not valid then @conn_sid is simply a copy + * of @sk_sid. Returns zero on success, negative values on failure. + * + */ +static int selinux_conn_sid(u32 sk_sid, u32 skb_sid, u32 *conn_sid) +{ + int err = 0; + + if (skb_sid != SECSID_NULL) + err = security_sid_mls_copy(&selinux_state, sk_sid, skb_sid, + conn_sid); + else + *conn_sid = sk_sid; + + return err; +} + +/* socket security operations */ + +static int socket_sockcreate_sid(const struct task_security_struct *tsec, + u16 secclass, u32 *socksid) +{ + if (tsec->sockcreate_sid > SECSID_NULL) { + *socksid = tsec->sockcreate_sid; + return 0; + } + + return security_transition_sid(&selinux_state, tsec->sid, tsec->sid, + secclass, NULL, socksid); +} + +static int sock_has_perm(struct sock *sk, u32 perms) +{ + struct sk_security_struct *sksec = sk->sk_security; + struct common_audit_data ad; + struct lsm_network_audit net = {0,}; + + if (sksec->sid == SECINITSID_KERNEL) + return 0; + + ad.type = LSM_AUDIT_DATA_NET; + ad.u.net = &net; + ad.u.net->sk = sk; + + return avc_has_perm(&selinux_state, + current_sid(), sksec->sid, sksec->sclass, perms, + &ad); +} + +static int selinux_socket_create(int family, int type, + int protocol, int kern) +{ + const struct task_security_struct *tsec = selinux_cred(current_cred()); + u32 newsid; + u16 secclass; + int rc; + + if (kern) + return 0; + + secclass = socket_type_to_security_class(family, type, protocol); + rc = socket_sockcreate_sid(tsec, secclass, &newsid); + if (rc) + return rc; + + return avc_has_perm(&selinux_state, + tsec->sid, newsid, secclass, SOCKET__CREATE, NULL); +} + +static int selinux_socket_post_create(struct socket *sock, int family, + int type, int protocol, int kern) +{ + const struct task_security_struct *tsec = selinux_cred(current_cred()); + struct inode_security_struct *isec = inode_security_novalidate(SOCK_INODE(sock)); + struct sk_security_struct *sksec; + u16 sclass = socket_type_to_security_class(family, type, protocol); + u32 sid = SECINITSID_KERNEL; + int err = 0; + + if (!kern) { + err = socket_sockcreate_sid(tsec, sclass, &sid); + if (err) + return err; + } + + isec->sclass = sclass; + isec->sid = sid; + isec->initialized = LABEL_INITIALIZED; + + if (sock->sk) { + sksec = sock->sk->sk_security; + sksec->sclass = sclass; + sksec->sid = sid; + /* Allows detection of the first association on this socket */ + if (sksec->sclass == SECCLASS_SCTP_SOCKET) + sksec->sctp_assoc_state = SCTP_ASSOC_UNSET; + + err = selinux_netlbl_socket_post_create(sock->sk, family); + } + + return err; +} + +static int selinux_socket_socketpair(struct socket *socka, + struct socket *sockb) +{ + struct sk_security_struct *sksec_a = socka->sk->sk_security; + struct sk_security_struct *sksec_b = sockb->sk->sk_security; + + sksec_a->peer_sid = sksec_b->sid; + sksec_b->peer_sid = sksec_a->sid; + + return 0; +} + +/* Range of port numbers used to automatically bind. + Need to determine whether we should perform a name_bind + permission check between the socket and the port number. */ + +static int selinux_socket_bind(struct socket *sock, struct sockaddr *address, int addrlen) +{ + struct sock *sk = sock->sk; + struct sk_security_struct *sksec = sk->sk_security; + u16 family; + int err; + + err = sock_has_perm(sk, SOCKET__BIND); + if (err) + goto out; + + /* If PF_INET or PF_INET6, check name_bind permission for the port. */ + family = sk->sk_family; + if (family == PF_INET || family == PF_INET6) { + char *addrp; + struct common_audit_data ad; + struct lsm_network_audit net = {0,}; + struct sockaddr_in *addr4 = NULL; + struct sockaddr_in6 *addr6 = NULL; + u16 family_sa; + unsigned short snum; + u32 sid, node_perm; + + /* + * sctp_bindx(3) calls via selinux_sctp_bind_connect() + * that validates multiple binding addresses. Because of this + * need to check address->sa_family as it is possible to have + * sk->sk_family = PF_INET6 with addr->sa_family = AF_INET. + */ + if (addrlen < offsetofend(struct sockaddr, sa_family)) + return -EINVAL; + family_sa = address->sa_family; + switch (family_sa) { + case AF_UNSPEC: + case AF_INET: + if (addrlen < sizeof(struct sockaddr_in)) + return -EINVAL; + addr4 = (struct sockaddr_in *)address; + if (family_sa == AF_UNSPEC) { + if (family == PF_INET6) { + /* Length check from inet6_bind_sk() */ + if (addrlen < SIN6_LEN_RFC2133) + return -EINVAL; + /* Family check from __inet6_bind() */ + goto err_af; + } + /* see __inet_bind(), we only want to allow + * AF_UNSPEC if the address is INADDR_ANY + */ + if (addr4->sin_addr.s_addr != htonl(INADDR_ANY)) + goto err_af; + family_sa = AF_INET; + } + snum = ntohs(addr4->sin_port); + addrp = (char *)&addr4->sin_addr.s_addr; + break; + case AF_INET6: + if (addrlen < SIN6_LEN_RFC2133) + return -EINVAL; + addr6 = (struct sockaddr_in6 *)address; + snum = ntohs(addr6->sin6_port); + addrp = (char *)&addr6->sin6_addr.s6_addr; + break; + default: + goto err_af; + } + + ad.type = LSM_AUDIT_DATA_NET; + ad.u.net = &net; + ad.u.net->sport = htons(snum); + ad.u.net->family = family_sa; + + if (snum) { + int low, high; + + inet_get_local_port_range(sock_net(sk), &low, &high); + + if (inet_port_requires_bind_service(sock_net(sk), snum) || + snum < low || snum > high) { + err = sel_netport_sid(sk->sk_protocol, + snum, &sid); + if (err) + goto out; + err = avc_has_perm(&selinux_state, + sksec->sid, sid, + sksec->sclass, + SOCKET__NAME_BIND, &ad); + if (err) + goto out; + } + } + + switch (sksec->sclass) { + case SECCLASS_TCP_SOCKET: + node_perm = TCP_SOCKET__NODE_BIND; + break; + + case SECCLASS_UDP_SOCKET: + node_perm = UDP_SOCKET__NODE_BIND; + break; + + case SECCLASS_DCCP_SOCKET: + node_perm = DCCP_SOCKET__NODE_BIND; + break; + + case SECCLASS_SCTP_SOCKET: + node_perm = SCTP_SOCKET__NODE_BIND; + break; + + default: + node_perm = RAWIP_SOCKET__NODE_BIND; + break; + } + + err = sel_netnode_sid(addrp, family_sa, &sid); + if (err) + goto out; + + if (family_sa == AF_INET) + ad.u.net->v4info.saddr = addr4->sin_addr.s_addr; + else + ad.u.net->v6info.saddr = addr6->sin6_addr; + + err = avc_has_perm(&selinux_state, + sksec->sid, sid, + sksec->sclass, node_perm, &ad); + if (err) + goto out; + } +out: + return err; +err_af: + /* Note that SCTP services expect -EINVAL, others -EAFNOSUPPORT. */ + if (sksec->sclass == SECCLASS_SCTP_SOCKET) + return -EINVAL; + return -EAFNOSUPPORT; +} + +/* This supports connect(2) and SCTP connect services such as sctp_connectx(3) + * and sctp_sendmsg(3) as described in Documentation/security/SCTP.rst + */ +static int selinux_socket_connect_helper(struct socket *sock, + struct sockaddr *address, int addrlen) +{ + struct sock *sk = sock->sk; + struct sk_security_struct *sksec = sk->sk_security; + int err; + + err = sock_has_perm(sk, SOCKET__CONNECT); + if (err) + return err; + if (addrlen < offsetofend(struct sockaddr, sa_family)) + return -EINVAL; + + /* connect(AF_UNSPEC) has special handling, as it is a documented + * way to disconnect the socket + */ + if (address->sa_family == AF_UNSPEC) + return 0; + + /* + * If a TCP, DCCP or SCTP socket, check name_connect permission + * for the port. + */ + if (sksec->sclass == SECCLASS_TCP_SOCKET || + sksec->sclass == SECCLASS_DCCP_SOCKET || + sksec->sclass == SECCLASS_SCTP_SOCKET) { + struct common_audit_data ad; + struct lsm_network_audit net = {0,}; + struct sockaddr_in *addr4 = NULL; + struct sockaddr_in6 *addr6 = NULL; + unsigned short snum; + u32 sid, perm; + + /* sctp_connectx(3) calls via selinux_sctp_bind_connect() + * that validates multiple connect addresses. Because of this + * need to check address->sa_family as it is possible to have + * sk->sk_family = PF_INET6 with addr->sa_family = AF_INET. + */ + switch (address->sa_family) { + case AF_INET: + addr4 = (struct sockaddr_in *)address; + if (addrlen < sizeof(struct sockaddr_in)) + return -EINVAL; + snum = ntohs(addr4->sin_port); + break; + case AF_INET6: + addr6 = (struct sockaddr_in6 *)address; + if (addrlen < SIN6_LEN_RFC2133) + return -EINVAL; + snum = ntohs(addr6->sin6_port); + break; + default: + /* Note that SCTP services expect -EINVAL, whereas + * others expect -EAFNOSUPPORT. + */ + if (sksec->sclass == SECCLASS_SCTP_SOCKET) + return -EINVAL; + else + return -EAFNOSUPPORT; + } + + err = sel_netport_sid(sk->sk_protocol, snum, &sid); + if (err) + return err; + + switch (sksec->sclass) { + case SECCLASS_TCP_SOCKET: + perm = TCP_SOCKET__NAME_CONNECT; + break; + case SECCLASS_DCCP_SOCKET: + perm = DCCP_SOCKET__NAME_CONNECT; + break; + case SECCLASS_SCTP_SOCKET: + perm = SCTP_SOCKET__NAME_CONNECT; + break; + } + + ad.type = LSM_AUDIT_DATA_NET; + ad.u.net = &net; + ad.u.net->dport = htons(snum); + ad.u.net->family = address->sa_family; + err = avc_has_perm(&selinux_state, + sksec->sid, sid, sksec->sclass, perm, &ad); + if (err) + return err; + } + + return 0; +} + +/* Supports connect(2), see comments in selinux_socket_connect_helper() */ +static int selinux_socket_connect(struct socket *sock, + struct sockaddr *address, int addrlen) +{ + int err; + struct sock *sk = sock->sk; + + err = selinux_socket_connect_helper(sock, address, addrlen); + if (err) + return err; + + return selinux_netlbl_socket_connect(sk, address); +} + +static int selinux_socket_listen(struct socket *sock, int backlog) +{ + return sock_has_perm(sock->sk, SOCKET__LISTEN); +} + +static int selinux_socket_accept(struct socket *sock, struct socket *newsock) +{ + int err; + struct inode_security_struct *isec; + struct inode_security_struct *newisec; + u16 sclass; + u32 sid; + + err = sock_has_perm(sock->sk, SOCKET__ACCEPT); + if (err) + return err; + + isec = inode_security_novalidate(SOCK_INODE(sock)); + spin_lock(&isec->lock); + sclass = isec->sclass; + sid = isec->sid; + spin_unlock(&isec->lock); + + newisec = inode_security_novalidate(SOCK_INODE(newsock)); + newisec->sclass = sclass; + newisec->sid = sid; + newisec->initialized = LABEL_INITIALIZED; + + return 0; +} + +static int selinux_socket_sendmsg(struct socket *sock, struct msghdr *msg, + int size) +{ + return sock_has_perm(sock->sk, SOCKET__WRITE); +} + +static int selinux_socket_recvmsg(struct socket *sock, struct msghdr *msg, + int size, int flags) +{ + return sock_has_perm(sock->sk, SOCKET__READ); +} + +static int selinux_socket_getsockname(struct socket *sock) +{ + return sock_has_perm(sock->sk, SOCKET__GETATTR); +} + +static int selinux_socket_getpeername(struct socket *sock) +{ + return sock_has_perm(sock->sk, SOCKET__GETATTR); +} + +static int selinux_socket_setsockopt(struct socket *sock, int level, int optname) +{ + int err; + + err = sock_has_perm(sock->sk, SOCKET__SETOPT); + if (err) + return err; + + return selinux_netlbl_socket_setsockopt(sock, level, optname); +} + +static int selinux_socket_getsockopt(struct socket *sock, int level, + int optname) +{ + return sock_has_perm(sock->sk, SOCKET__GETOPT); +} + +static int selinux_socket_shutdown(struct socket *sock, int how) +{ + return sock_has_perm(sock->sk, SOCKET__SHUTDOWN); +} + +static int selinux_socket_unix_stream_connect(struct sock *sock, + struct sock *other, + struct sock *newsk) +{ + struct sk_security_struct *sksec_sock = sock->sk_security; + struct sk_security_struct *sksec_other = other->sk_security; + struct sk_security_struct *sksec_new = newsk->sk_security; + struct common_audit_data ad; + struct lsm_network_audit net = {0,}; + int err; + + ad.type = LSM_AUDIT_DATA_NET; + ad.u.net = &net; + ad.u.net->sk = other; + + err = avc_has_perm(&selinux_state, + sksec_sock->sid, sksec_other->sid, + sksec_other->sclass, + UNIX_STREAM_SOCKET__CONNECTTO, &ad); + if (err) + return err; + + /* server child socket */ + sksec_new->peer_sid = sksec_sock->sid; + err = security_sid_mls_copy(&selinux_state, sksec_other->sid, + sksec_sock->sid, &sksec_new->sid); + if (err) + return err; + + /* connecting socket */ + sksec_sock->peer_sid = sksec_new->sid; + + return 0; +} + +static int selinux_socket_unix_may_send(struct socket *sock, + struct socket *other) +{ + struct sk_security_struct *ssec = sock->sk->sk_security; + struct sk_security_struct *osec = other->sk->sk_security; + struct common_audit_data ad; + struct lsm_network_audit net = {0,}; + + ad.type = LSM_AUDIT_DATA_NET; + ad.u.net = &net; + ad.u.net->sk = other->sk; + + return avc_has_perm(&selinux_state, + ssec->sid, osec->sid, osec->sclass, SOCKET__SENDTO, + &ad); +} + +static int selinux_inet_sys_rcv_skb(struct net *ns, int ifindex, + char *addrp, u16 family, u32 peer_sid, + struct common_audit_data *ad) +{ + int err; + u32 if_sid; + u32 node_sid; + + err = sel_netif_sid(ns, ifindex, &if_sid); + if (err) + return err; + err = avc_has_perm(&selinux_state, + peer_sid, if_sid, + SECCLASS_NETIF, NETIF__INGRESS, ad); + if (err) + return err; + + err = sel_netnode_sid(addrp, family, &node_sid); + if (err) + return err; + return avc_has_perm(&selinux_state, + peer_sid, node_sid, + SECCLASS_NODE, NODE__RECVFROM, ad); +} + +static int selinux_sock_rcv_skb_compat(struct sock *sk, struct sk_buff *skb, + u16 family) +{ + int err = 0; + struct sk_security_struct *sksec = sk->sk_security; + u32 sk_sid = sksec->sid; + struct common_audit_data ad; + struct lsm_network_audit net = {0,}; + char *addrp; + + ad.type = LSM_AUDIT_DATA_NET; + ad.u.net = &net; + ad.u.net->netif = skb->skb_iif; + ad.u.net->family = family; + err = selinux_parse_skb(skb, &ad, &addrp, 1, NULL); + if (err) + return err; + + if (selinux_secmark_enabled()) { + err = avc_has_perm(&selinux_state, + sk_sid, skb->secmark, SECCLASS_PACKET, + PACKET__RECV, &ad); + if (err) + return err; + } + + err = selinux_netlbl_sock_rcv_skb(sksec, skb, family, &ad); + if (err) + return err; + err = selinux_xfrm_sock_rcv_skb(sksec->sid, skb, &ad); + + return err; +} + +static int selinux_socket_sock_rcv_skb(struct sock *sk, struct sk_buff *skb) +{ + int err; + struct sk_security_struct *sksec = sk->sk_security; + u16 family = sk->sk_family; + u32 sk_sid = sksec->sid; + struct common_audit_data ad; + struct lsm_network_audit net = {0,}; + char *addrp; + u8 secmark_active; + u8 peerlbl_active; + + if (family != PF_INET && family != PF_INET6) + return 0; + + /* Handle mapped IPv4 packets arriving via IPv6 sockets */ + if (family == PF_INET6 && skb->protocol == htons(ETH_P_IP)) + family = PF_INET; + + /* If any sort of compatibility mode is enabled then handoff processing + * to the selinux_sock_rcv_skb_compat() function to deal with the + * special handling. We do this in an attempt to keep this function + * as fast and as clean as possible. */ + if (!selinux_policycap_netpeer()) + return selinux_sock_rcv_skb_compat(sk, skb, family); + + secmark_active = selinux_secmark_enabled(); + peerlbl_active = selinux_peerlbl_enabled(); + if (!secmark_active && !peerlbl_active) + return 0; + + ad.type = LSM_AUDIT_DATA_NET; + ad.u.net = &net; + ad.u.net->netif = skb->skb_iif; + ad.u.net->family = family; + err = selinux_parse_skb(skb, &ad, &addrp, 1, NULL); + if (err) + return err; + + if (peerlbl_active) { + u32 peer_sid; + + err = selinux_skb_peerlbl_sid(skb, family, &peer_sid); + if (err) + return err; + err = selinux_inet_sys_rcv_skb(sock_net(sk), skb->skb_iif, + addrp, family, peer_sid, &ad); + if (err) { + selinux_netlbl_err(skb, family, err, 0); + return err; + } + err = avc_has_perm(&selinux_state, + sk_sid, peer_sid, SECCLASS_PEER, + PEER__RECV, &ad); + if (err) { + selinux_netlbl_err(skb, family, err, 0); + return err; + } + } + + if (secmark_active) { + err = avc_has_perm(&selinux_state, + sk_sid, skb->secmark, SECCLASS_PACKET, + PACKET__RECV, &ad); + if (err) + return err; + } + + return err; +} + +static int selinux_socket_getpeersec_stream(struct socket *sock, char __user *optval, + int __user *optlen, unsigned len) +{ + int err = 0; + char *scontext; + u32 scontext_len; + struct sk_security_struct *sksec = sock->sk->sk_security; + u32 peer_sid = SECSID_NULL; + + if (sksec->sclass == SECCLASS_UNIX_STREAM_SOCKET || + sksec->sclass == SECCLASS_TCP_SOCKET || + sksec->sclass == SECCLASS_SCTP_SOCKET) + peer_sid = sksec->peer_sid; + if (peer_sid == SECSID_NULL) + return -ENOPROTOOPT; + + err = security_sid_to_context(&selinux_state, peer_sid, &scontext, + &scontext_len); + if (err) + return err; + + if (scontext_len > len) { + err = -ERANGE; + goto out_len; + } + + if (copy_to_user(optval, scontext, scontext_len)) + err = -EFAULT; + +out_len: + if (put_user(scontext_len, optlen)) + err = -EFAULT; + kfree(scontext); + return err; +} + +static int selinux_socket_getpeersec_dgram(struct socket *sock, struct sk_buff *skb, u32 *secid) +{ + u32 peer_secid = SECSID_NULL; + u16 family; + struct inode_security_struct *isec; + + if (skb && skb->protocol == htons(ETH_P_IP)) + family = PF_INET; + else if (skb && skb->protocol == htons(ETH_P_IPV6)) + family = PF_INET6; + else if (sock) + family = sock->sk->sk_family; + else + goto out; + + if (sock && family == PF_UNIX) { + isec = inode_security_novalidate(SOCK_INODE(sock)); + peer_secid = isec->sid; + } else if (skb) + selinux_skb_peerlbl_sid(skb, family, &peer_secid); + +out: + *secid = peer_secid; + if (peer_secid == SECSID_NULL) + return -EINVAL; + return 0; +} + +static int selinux_sk_alloc_security(struct sock *sk, int family, gfp_t priority) +{ + struct sk_security_struct *sksec; + + sksec = kzalloc(sizeof(*sksec), priority); + if (!sksec) + return -ENOMEM; + + sksec->peer_sid = SECINITSID_UNLABELED; + sksec->sid = SECINITSID_UNLABELED; + sksec->sclass = SECCLASS_SOCKET; + selinux_netlbl_sk_security_reset(sksec); + sk->sk_security = sksec; + + return 0; +} + +static void selinux_sk_free_security(struct sock *sk) +{ + struct sk_security_struct *sksec = sk->sk_security; + + sk->sk_security = NULL; + selinux_netlbl_sk_security_free(sksec); + kfree(sksec); +} + +static void selinux_sk_clone_security(const struct sock *sk, struct sock *newsk) +{ + struct sk_security_struct *sksec = sk->sk_security; + struct sk_security_struct *newsksec = newsk->sk_security; + + newsksec->sid = sksec->sid; + newsksec->peer_sid = sksec->peer_sid; + newsksec->sclass = sksec->sclass; + + selinux_netlbl_sk_security_reset(newsksec); +} + +static void selinux_sk_getsecid(struct sock *sk, u32 *secid) +{ + if (!sk) + *secid = SECINITSID_ANY_SOCKET; + else { + struct sk_security_struct *sksec = sk->sk_security; + + *secid = sksec->sid; + } +} + +static void selinux_sock_graft(struct sock *sk, struct socket *parent) +{ + struct inode_security_struct *isec = + inode_security_novalidate(SOCK_INODE(parent)); + struct sk_security_struct *sksec = sk->sk_security; + + if (sk->sk_family == PF_INET || sk->sk_family == PF_INET6 || + sk->sk_family == PF_UNIX) + isec->sid = sksec->sid; + sksec->sclass = isec->sclass; +} + +/* Called whenever SCTP receives an INIT chunk. This happens when an incoming + * connect(2), sctp_connectx(3) or sctp_sendmsg(3) (with no association + * already present). + */ +static int selinux_sctp_assoc_request(struct sctp_endpoint *ep, + struct sk_buff *skb) +{ + struct sk_security_struct *sksec = ep->base.sk->sk_security; + struct common_audit_data ad; + struct lsm_network_audit net = {0,}; + u8 peerlbl_active; + u32 peer_sid = SECINITSID_UNLABELED; + u32 conn_sid; + int err = 0; + + if (!selinux_policycap_extsockclass()) + return 0; + + peerlbl_active = selinux_peerlbl_enabled(); + + if (peerlbl_active) { + /* This will return peer_sid = SECSID_NULL if there are + * no peer labels, see security_net_peersid_resolve(). + */ + err = selinux_skb_peerlbl_sid(skb, ep->base.sk->sk_family, + &peer_sid); + if (err) + return err; + + if (peer_sid == SECSID_NULL) + peer_sid = SECINITSID_UNLABELED; + } + + if (sksec->sctp_assoc_state == SCTP_ASSOC_UNSET) { + sksec->sctp_assoc_state = SCTP_ASSOC_SET; + + /* Here as first association on socket. As the peer SID + * was allowed by peer recv (and the netif/node checks), + * then it is approved by policy and used as the primary + * peer SID for getpeercon(3). + */ + sksec->peer_sid = peer_sid; + } else if (sksec->peer_sid != peer_sid) { + /* Other association peer SIDs are checked to enforce + * consistency among the peer SIDs. + */ + ad.type = LSM_AUDIT_DATA_NET; + ad.u.net = &net; + ad.u.net->sk = ep->base.sk; + err = avc_has_perm(&selinux_state, + sksec->peer_sid, peer_sid, sksec->sclass, + SCTP_SOCKET__ASSOCIATION, &ad); + if (err) + return err; + } + + /* Compute the MLS component for the connection and store + * the information in ep. This will be used by SCTP TCP type + * sockets and peeled off connections as they cause a new + * socket to be generated. selinux_sctp_sk_clone() will then + * plug this into the new socket. + */ + err = selinux_conn_sid(sksec->sid, peer_sid, &conn_sid); + if (err) + return err; + + ep->secid = conn_sid; + ep->peer_secid = peer_sid; + + /* Set any NetLabel labels including CIPSO/CALIPSO options. */ + return selinux_netlbl_sctp_assoc_request(ep, skb); +} + +/* Check if sctp IPv4/IPv6 addresses are valid for binding or connecting + * based on their @optname. + */ +static int selinux_sctp_bind_connect(struct sock *sk, int optname, + struct sockaddr *address, + int addrlen) +{ + int len, err = 0, walk_size = 0; + void *addr_buf; + struct sockaddr *addr; + struct socket *sock; + + if (!selinux_policycap_extsockclass()) + return 0; + + /* Process one or more addresses that may be IPv4 or IPv6 */ + sock = sk->sk_socket; + addr_buf = address; + + while (walk_size < addrlen) { + if (walk_size + sizeof(sa_family_t) > addrlen) + return -EINVAL; + + addr = addr_buf; + switch (addr->sa_family) { + case AF_UNSPEC: + case AF_INET: + len = sizeof(struct sockaddr_in); + break; + case AF_INET6: + len = sizeof(struct sockaddr_in6); + break; + default: + return -EINVAL; + } + + if (walk_size + len > addrlen) + return -EINVAL; + + err = -EINVAL; + switch (optname) { + /* Bind checks */ + case SCTP_PRIMARY_ADDR: + case SCTP_SET_PEER_PRIMARY_ADDR: + case SCTP_SOCKOPT_BINDX_ADD: + err = selinux_socket_bind(sock, addr, len); + break; + /* Connect checks */ + case SCTP_SOCKOPT_CONNECTX: + case SCTP_PARAM_SET_PRIMARY: + case SCTP_PARAM_ADD_IP: + case SCTP_SENDMSG_CONNECT: + err = selinux_socket_connect_helper(sock, addr, len); + if (err) + return err; + + /* As selinux_sctp_bind_connect() is called by the + * SCTP protocol layer, the socket is already locked, + * therefore selinux_netlbl_socket_connect_locked() + * is called here. The situations handled are: + * sctp_connectx(3), sctp_sendmsg(3), sendmsg(2), + * whenever a new IP address is added or when a new + * primary address is selected. + * Note that an SCTP connect(2) call happens before + * the SCTP protocol layer and is handled via + * selinux_socket_connect(). + */ + err = selinux_netlbl_socket_connect_locked(sk, addr); + break; + } + + if (err) + return err; + + addr_buf += len; + walk_size += len; + } + + return 0; +} + +/* Called whenever a new socket is created by accept(2) or sctp_peeloff(3). */ +static void selinux_sctp_sk_clone(struct sctp_endpoint *ep, struct sock *sk, + struct sock *newsk) +{ + struct sk_security_struct *sksec = sk->sk_security; + struct sk_security_struct *newsksec = newsk->sk_security; + + /* If policy does not support SECCLASS_SCTP_SOCKET then call + * the non-sctp clone version. + */ + if (!selinux_policycap_extsockclass()) + return selinux_sk_clone_security(sk, newsk); + + newsksec->sid = ep->secid; + newsksec->peer_sid = ep->peer_secid; + newsksec->sclass = sksec->sclass; + selinux_netlbl_sctp_sk_clone(sk, newsk); +} + +static int selinux_inet_conn_request(struct sock *sk, struct sk_buff *skb, + struct request_sock *req) +{ + struct sk_security_struct *sksec = sk->sk_security; + int err; + u16 family = req->rsk_ops->family; + u32 connsid; + u32 peersid; + + err = selinux_skb_peerlbl_sid(skb, family, &peersid); + if (err) + return err; + err = selinux_conn_sid(sksec->sid, peersid, &connsid); + if (err) + return err; + req->secid = connsid; + req->peer_secid = peersid; + + return selinux_netlbl_inet_conn_request(req, family); +} + +static void selinux_inet_csk_clone(struct sock *newsk, + const struct request_sock *req) +{ + struct sk_security_struct *newsksec = newsk->sk_security; + + newsksec->sid = req->secid; + newsksec->peer_sid = req->peer_secid; + /* NOTE: Ideally, we should also get the isec->sid for the + new socket in sync, but we don't have the isec available yet. + So we will wait until sock_graft to do it, by which + time it will have been created and available. */ + + /* We don't need to take any sort of lock here as we are the only + * thread with access to newsksec */ + selinux_netlbl_inet_csk_clone(newsk, req->rsk_ops->family); +} + +static void selinux_inet_conn_established(struct sock *sk, struct sk_buff *skb) +{ + u16 family = sk->sk_family; + struct sk_security_struct *sksec = sk->sk_security; + + /* handle mapped IPv4 packets arriving via IPv6 sockets */ + if (family == PF_INET6 && skb->protocol == htons(ETH_P_IP)) + family = PF_INET; + + selinux_skb_peerlbl_sid(skb, family, &sksec->peer_sid); +} + +static int selinux_secmark_relabel_packet(u32 sid) +{ + const struct task_security_struct *__tsec; + u32 tsid; + + __tsec = selinux_cred(current_cred()); + tsid = __tsec->sid; + + return avc_has_perm(&selinux_state, + tsid, sid, SECCLASS_PACKET, PACKET__RELABELTO, + NULL); +} + +static void selinux_secmark_refcount_inc(void) +{ + atomic_inc(&selinux_secmark_refcount); +} + +static void selinux_secmark_refcount_dec(void) +{ + atomic_dec(&selinux_secmark_refcount); +} + +static void selinux_req_classify_flow(const struct request_sock *req, + struct flowi_common *flic) +{ + flic->flowic_secid = req->secid; +} + +static int selinux_tun_dev_alloc_security(void **security) +{ + struct tun_security_struct *tunsec; + + tunsec = kzalloc(sizeof(*tunsec), GFP_KERNEL); + if (!tunsec) + return -ENOMEM; + tunsec->sid = current_sid(); + + *security = tunsec; + return 0; +} + +static void selinux_tun_dev_free_security(void *security) +{ + kfree(security); +} + +static int selinux_tun_dev_create(void) +{ + u32 sid = current_sid(); + + /* we aren't taking into account the "sockcreate" SID since the socket + * that is being created here is not a socket in the traditional sense, + * instead it is a private sock, accessible only to the kernel, and + * representing a wide range of network traffic spanning multiple + * connections unlike traditional sockets - check the TUN driver to + * get a better understanding of why this socket is special */ + + return avc_has_perm(&selinux_state, + sid, sid, SECCLASS_TUN_SOCKET, TUN_SOCKET__CREATE, + NULL); +} + +static int selinux_tun_dev_attach_queue(void *security) +{ + struct tun_security_struct *tunsec = security; + + return avc_has_perm(&selinux_state, + current_sid(), tunsec->sid, SECCLASS_TUN_SOCKET, + TUN_SOCKET__ATTACH_QUEUE, NULL); +} + +static int selinux_tun_dev_attach(struct sock *sk, void *security) +{ + struct tun_security_struct *tunsec = security; + struct sk_security_struct *sksec = sk->sk_security; + + /* we don't currently perform any NetLabel based labeling here and it + * isn't clear that we would want to do so anyway; while we could apply + * labeling without the support of the TUN user the resulting labeled + * traffic from the other end of the connection would almost certainly + * cause confusion to the TUN user that had no idea network labeling + * protocols were being used */ + + sksec->sid = tunsec->sid; + sksec->sclass = SECCLASS_TUN_SOCKET; + + return 0; +} + +static int selinux_tun_dev_open(void *security) +{ + struct tun_security_struct *tunsec = security; + u32 sid = current_sid(); + int err; + + err = avc_has_perm(&selinux_state, + sid, tunsec->sid, SECCLASS_TUN_SOCKET, + TUN_SOCKET__RELABELFROM, NULL); + if (err) + return err; + err = avc_has_perm(&selinux_state, + sid, sid, SECCLASS_TUN_SOCKET, + TUN_SOCKET__RELABELTO, NULL); + if (err) + return err; + tunsec->sid = sid; + + return 0; +} + +#ifdef CONFIG_NETFILTER + +static unsigned int selinux_ip_forward(struct sk_buff *skb, + const struct net_device *indev, + u16 family) +{ + int err; + char *addrp; + u32 peer_sid; + struct common_audit_data ad; + struct lsm_network_audit net = {0,}; + u8 secmark_active; + u8 netlbl_active; + u8 peerlbl_active; + + if (!selinux_policycap_netpeer()) + return NF_ACCEPT; + + secmark_active = selinux_secmark_enabled(); + netlbl_active = netlbl_enabled(); + peerlbl_active = selinux_peerlbl_enabled(); + if (!secmark_active && !peerlbl_active) + return NF_ACCEPT; + + if (selinux_skb_peerlbl_sid(skb, family, &peer_sid) != 0) + return NF_DROP; + + ad.type = LSM_AUDIT_DATA_NET; + ad.u.net = &net; + ad.u.net->netif = indev->ifindex; + ad.u.net->family = family; + if (selinux_parse_skb(skb, &ad, &addrp, 1, NULL) != 0) + return NF_DROP; + + if (peerlbl_active) { + err = selinux_inet_sys_rcv_skb(dev_net(indev), indev->ifindex, + addrp, family, peer_sid, &ad); + if (err) { + selinux_netlbl_err(skb, family, err, 1); + return NF_DROP; + } + } + + if (secmark_active) + if (avc_has_perm(&selinux_state, + peer_sid, skb->secmark, + SECCLASS_PACKET, PACKET__FORWARD_IN, &ad)) + return NF_DROP; + + if (netlbl_active) + /* we do this in the FORWARD path and not the POST_ROUTING + * path because we want to make sure we apply the necessary + * labeling before IPsec is applied so we can leverage AH + * protection */ + if (selinux_netlbl_skbuff_setsid(skb, family, peer_sid) != 0) + return NF_DROP; + + return NF_ACCEPT; +} + +static unsigned int selinux_ipv4_forward(void *priv, + struct sk_buff *skb, + const struct nf_hook_state *state) +{ + return selinux_ip_forward(skb, state->in, PF_INET); +} + +#if IS_ENABLED(CONFIG_IPV6) +static unsigned int selinux_ipv6_forward(void *priv, + struct sk_buff *skb, + const struct nf_hook_state *state) +{ + return selinux_ip_forward(skb, state->in, PF_INET6); +} +#endif /* IPV6 */ + +static unsigned int selinux_ip_output(struct sk_buff *skb, + u16 family) +{ + struct sock *sk; + u32 sid; + + if (!netlbl_enabled()) + return NF_ACCEPT; + + /* we do this in the LOCAL_OUT path and not the POST_ROUTING path + * because we want to make sure we apply the necessary labeling + * before IPsec is applied so we can leverage AH protection */ + sk = skb->sk; + if (sk) { + struct sk_security_struct *sksec; + + if (sk_listener(sk)) + /* if the socket is the listening state then this + * packet is a SYN-ACK packet which means it needs to + * be labeled based on the connection/request_sock and + * not the parent socket. unfortunately, we can't + * lookup the request_sock yet as it isn't queued on + * the parent socket until after the SYN-ACK is sent. + * the "solution" is to simply pass the packet as-is + * as any IP option based labeling should be copied + * from the initial connection request (in the IP + * layer). it is far from ideal, but until we get a + * security label in the packet itself this is the + * best we can do. */ + return NF_ACCEPT; + + /* standard practice, label using the parent socket */ + sksec = sk->sk_security; + sid = sksec->sid; + } else + sid = SECINITSID_KERNEL; + if (selinux_netlbl_skbuff_setsid(skb, family, sid) != 0) + return NF_DROP; + + return NF_ACCEPT; +} + +static unsigned int selinux_ipv4_output(void *priv, + struct sk_buff *skb, + const struct nf_hook_state *state) +{ + return selinux_ip_output(skb, PF_INET); +} + +#if IS_ENABLED(CONFIG_IPV6) +static unsigned int selinux_ipv6_output(void *priv, + struct sk_buff *skb, + const struct nf_hook_state *state) +{ + return selinux_ip_output(skb, PF_INET6); +} +#endif /* IPV6 */ + +static unsigned int selinux_ip_postroute_compat(struct sk_buff *skb, + int ifindex, + u16 family) +{ + struct sock *sk = skb_to_full_sk(skb); + struct sk_security_struct *sksec; + struct common_audit_data ad; + struct lsm_network_audit net = {0,}; + char *addrp; + u8 proto = 0; + + if (sk == NULL) + return NF_ACCEPT; + sksec = sk->sk_security; + + ad.type = LSM_AUDIT_DATA_NET; + ad.u.net = &net; + ad.u.net->netif = ifindex; + ad.u.net->family = family; + if (selinux_parse_skb(skb, &ad, &addrp, 0, &proto)) + return NF_DROP; + + if (selinux_secmark_enabled()) + if (avc_has_perm(&selinux_state, + sksec->sid, skb->secmark, + SECCLASS_PACKET, PACKET__SEND, &ad)) + return NF_DROP_ERR(-ECONNREFUSED); + + if (selinux_xfrm_postroute_last(sksec->sid, skb, &ad, proto)) + return NF_DROP_ERR(-ECONNREFUSED); + + return NF_ACCEPT; +} + +static unsigned int selinux_ip_postroute(struct sk_buff *skb, + const struct net_device *outdev, + u16 family) +{ + u32 secmark_perm; + u32 peer_sid; + int ifindex = outdev->ifindex; + struct sock *sk; + struct common_audit_data ad; + struct lsm_network_audit net = {0,}; + char *addrp; + u8 secmark_active; + u8 peerlbl_active; + + /* If any sort of compatibility mode is enabled then handoff processing + * to the selinux_ip_postroute_compat() function to deal with the + * special handling. We do this in an attempt to keep this function + * as fast and as clean as possible. */ + if (!selinux_policycap_netpeer()) + return selinux_ip_postroute_compat(skb, ifindex, family); + + secmark_active = selinux_secmark_enabled(); + peerlbl_active = selinux_peerlbl_enabled(); + if (!secmark_active && !peerlbl_active) + return NF_ACCEPT; + + sk = skb_to_full_sk(skb); + +#ifdef CONFIG_XFRM + /* If skb->dst->xfrm is non-NULL then the packet is undergoing an IPsec + * packet transformation so allow the packet to pass without any checks + * since we'll have another chance to perform access control checks + * when the packet is on it's final way out. + * NOTE: there appear to be some IPv6 multicast cases where skb->dst + * is NULL, in this case go ahead and apply access control. + * NOTE: if this is a local socket (skb->sk != NULL) that is in the + * TCP listening state we cannot wait until the XFRM processing + * is done as we will miss out on the SA label if we do; + * unfortunately, this means more work, but it is only once per + * connection. */ + if (skb_dst(skb) != NULL && skb_dst(skb)->xfrm != NULL && + !(sk && sk_listener(sk))) + return NF_ACCEPT; +#endif + + if (sk == NULL) { + /* Without an associated socket the packet is either coming + * from the kernel or it is being forwarded; check the packet + * to determine which and if the packet is being forwarded + * query the packet directly to determine the security label. */ + if (skb->skb_iif) { + secmark_perm = PACKET__FORWARD_OUT; + if (selinux_skb_peerlbl_sid(skb, family, &peer_sid)) + return NF_DROP; + } else { + secmark_perm = PACKET__SEND; + peer_sid = SECINITSID_KERNEL; + } + } else if (sk_listener(sk)) { + /* Locally generated packet but the associated socket is in the + * listening state which means this is a SYN-ACK packet. In + * this particular case the correct security label is assigned + * to the connection/request_sock but unfortunately we can't + * query the request_sock as it isn't queued on the parent + * socket until after the SYN-ACK packet is sent; the only + * viable choice is to regenerate the label like we do in + * selinux_inet_conn_request(). See also selinux_ip_output() + * for similar problems. */ + u32 skb_sid; + struct sk_security_struct *sksec; + + sksec = sk->sk_security; + if (selinux_skb_peerlbl_sid(skb, family, &skb_sid)) + return NF_DROP; + /* At this point, if the returned skb peerlbl is SECSID_NULL + * and the packet has been through at least one XFRM + * transformation then we must be dealing with the "final" + * form of labeled IPsec packet; since we've already applied + * all of our access controls on this packet we can safely + * pass the packet. */ + if (skb_sid == SECSID_NULL) { + switch (family) { + case PF_INET: + if (IPCB(skb)->flags & IPSKB_XFRM_TRANSFORMED) + return NF_ACCEPT; + break; + case PF_INET6: + if (IP6CB(skb)->flags & IP6SKB_XFRM_TRANSFORMED) + return NF_ACCEPT; + break; + default: + return NF_DROP_ERR(-ECONNREFUSED); + } + } + if (selinux_conn_sid(sksec->sid, skb_sid, &peer_sid)) + return NF_DROP; + secmark_perm = PACKET__SEND; + } else { + /* Locally generated packet, fetch the security label from the + * associated socket. */ + struct sk_security_struct *sksec = sk->sk_security; + peer_sid = sksec->sid; + secmark_perm = PACKET__SEND; + } + + ad.type = LSM_AUDIT_DATA_NET; + ad.u.net = &net; + ad.u.net->netif = ifindex; + ad.u.net->family = family; + if (selinux_parse_skb(skb, &ad, &addrp, 0, NULL)) + return NF_DROP; + + if (secmark_active) + if (avc_has_perm(&selinux_state, + peer_sid, skb->secmark, + SECCLASS_PACKET, secmark_perm, &ad)) + return NF_DROP_ERR(-ECONNREFUSED); + + if (peerlbl_active) { + u32 if_sid; + u32 node_sid; + + if (sel_netif_sid(dev_net(outdev), ifindex, &if_sid)) + return NF_DROP; + if (avc_has_perm(&selinux_state, + peer_sid, if_sid, + SECCLASS_NETIF, NETIF__EGRESS, &ad)) + return NF_DROP_ERR(-ECONNREFUSED); + + if (sel_netnode_sid(addrp, family, &node_sid)) + return NF_DROP; + if (avc_has_perm(&selinux_state, + peer_sid, node_sid, + SECCLASS_NODE, NODE__SENDTO, &ad)) + return NF_DROP_ERR(-ECONNREFUSED); + } + + return NF_ACCEPT; +} + +static unsigned int selinux_ipv4_postroute(void *priv, + struct sk_buff *skb, + const struct nf_hook_state *state) +{ + return selinux_ip_postroute(skb, state->out, PF_INET); +} + +#if IS_ENABLED(CONFIG_IPV6) +static unsigned int selinux_ipv6_postroute(void *priv, + struct sk_buff *skb, + const struct nf_hook_state *state) +{ + return selinux_ip_postroute(skb, state->out, PF_INET6); +} +#endif /* IPV6 */ + +#endif /* CONFIG_NETFILTER */ + +static int selinux_netlink_send(struct sock *sk, struct sk_buff *skb) +{ + int rc = 0; + unsigned int msg_len; + unsigned int data_len = skb->len; + unsigned char *data = skb->data; + struct nlmsghdr *nlh; + struct sk_security_struct *sksec = sk->sk_security; + u16 sclass = sksec->sclass; + u32 perm; + + while (data_len >= nlmsg_total_size(0)) { + nlh = (struct nlmsghdr *)data; + + /* NOTE: the nlmsg_len field isn't reliably set by some netlink + * users which means we can't reject skb's with bogus + * length fields; our solution is to follow what + * netlink_rcv_skb() does and simply skip processing at + * messages with length fields that are clearly junk + */ + if (nlh->nlmsg_len < NLMSG_HDRLEN || nlh->nlmsg_len > data_len) + return 0; + + rc = selinux_nlmsg_lookup(sclass, nlh->nlmsg_type, &perm); + if (rc == 0) { + rc = sock_has_perm(sk, perm); + if (rc) + return rc; + } else if (rc == -EINVAL) { + /* -EINVAL is a missing msg/perm mapping */ + pr_warn_ratelimited("SELinux: unrecognized netlink" + " message: protocol=%hu nlmsg_type=%hu sclass=%s" + " pid=%d comm=%s\n", + sk->sk_protocol, nlh->nlmsg_type, + secclass_map[sclass - 1].name, + task_pid_nr(current), current->comm); + if (enforcing_enabled(&selinux_state) && + !security_get_allow_unknown(&selinux_state)) + return rc; + rc = 0; + } else if (rc == -ENOENT) { + /* -ENOENT is a missing socket/class mapping, ignore */ + rc = 0; + } else { + return rc; + } + + /* move to the next message after applying netlink padding */ + msg_len = NLMSG_ALIGN(nlh->nlmsg_len); + if (msg_len >= data_len) + return 0; + data_len -= msg_len; + data += msg_len; + } + + return rc; +} + +static void ipc_init_security(struct ipc_security_struct *isec, u16 sclass) +{ + isec->sclass = sclass; + isec->sid = current_sid(); +} + +static int ipc_has_perm(struct kern_ipc_perm *ipc_perms, + u32 perms) +{ + struct ipc_security_struct *isec; + struct common_audit_data ad; + u32 sid = current_sid(); + + isec = selinux_ipc(ipc_perms); + + ad.type = LSM_AUDIT_DATA_IPC; + ad.u.ipc_id = ipc_perms->key; + + return avc_has_perm(&selinux_state, + sid, isec->sid, isec->sclass, perms, &ad); +} + +static int selinux_msg_msg_alloc_security(struct msg_msg *msg) +{ + struct msg_security_struct *msec; + + msec = selinux_msg_msg(msg); + msec->sid = SECINITSID_UNLABELED; + + return 0; +} + +/* message queue security operations */ +static int selinux_msg_queue_alloc_security(struct kern_ipc_perm *msq) +{ + struct ipc_security_struct *isec; + struct common_audit_data ad; + u32 sid = current_sid(); + int rc; + + isec = selinux_ipc(msq); + ipc_init_security(isec, SECCLASS_MSGQ); + + ad.type = LSM_AUDIT_DATA_IPC; + ad.u.ipc_id = msq->key; + + rc = avc_has_perm(&selinux_state, + sid, isec->sid, SECCLASS_MSGQ, + MSGQ__CREATE, &ad); + return rc; +} + +static int selinux_msg_queue_associate(struct kern_ipc_perm *msq, int msqflg) +{ + struct ipc_security_struct *isec; + struct common_audit_data ad; + u32 sid = current_sid(); + + isec = selinux_ipc(msq); + + ad.type = LSM_AUDIT_DATA_IPC; + ad.u.ipc_id = msq->key; + + return avc_has_perm(&selinux_state, + sid, isec->sid, SECCLASS_MSGQ, + MSGQ__ASSOCIATE, &ad); +} + +static int selinux_msg_queue_msgctl(struct kern_ipc_perm *msq, int cmd) +{ + int err; + int perms; + + switch (cmd) { + case IPC_INFO: + case MSG_INFO: + /* No specific object, just general system-wide information. */ + return avc_has_perm(&selinux_state, + current_sid(), SECINITSID_KERNEL, + SECCLASS_SYSTEM, SYSTEM__IPC_INFO, NULL); + case IPC_STAT: + case MSG_STAT: + case MSG_STAT_ANY: + perms = MSGQ__GETATTR | MSGQ__ASSOCIATE; + break; + case IPC_SET: + perms = MSGQ__SETATTR; + break; + case IPC_RMID: + perms = MSGQ__DESTROY; + break; + default: + return 0; + } + + err = ipc_has_perm(msq, perms); + return err; +} + +static int selinux_msg_queue_msgsnd(struct kern_ipc_perm *msq, struct msg_msg *msg, int msqflg) +{ + struct ipc_security_struct *isec; + struct msg_security_struct *msec; + struct common_audit_data ad; + u32 sid = current_sid(); + int rc; + + isec = selinux_ipc(msq); + msec = selinux_msg_msg(msg); + + /* + * First time through, need to assign label to the message + */ + if (msec->sid == SECINITSID_UNLABELED) { + /* + * Compute new sid based on current process and + * message queue this message will be stored in + */ + rc = security_transition_sid(&selinux_state, sid, isec->sid, + SECCLASS_MSG, NULL, &msec->sid); + if (rc) + return rc; + } + + ad.type = LSM_AUDIT_DATA_IPC; + ad.u.ipc_id = msq->key; + + /* Can this process write to the queue? */ + rc = avc_has_perm(&selinux_state, + sid, isec->sid, SECCLASS_MSGQ, + MSGQ__WRITE, &ad); + if (!rc) + /* Can this process send the message */ + rc = avc_has_perm(&selinux_state, + sid, msec->sid, SECCLASS_MSG, + MSG__SEND, &ad); + if (!rc) + /* Can the message be put in the queue? */ + rc = avc_has_perm(&selinux_state, + msec->sid, isec->sid, SECCLASS_MSGQ, + MSGQ__ENQUEUE, &ad); + + return rc; +} + +static int selinux_msg_queue_msgrcv(struct kern_ipc_perm *msq, struct msg_msg *msg, + struct task_struct *target, + long type, int mode) +{ + struct ipc_security_struct *isec; + struct msg_security_struct *msec; + struct common_audit_data ad; + u32 sid = task_sid(target); + int rc; + + isec = selinux_ipc(msq); + msec = selinux_msg_msg(msg); + + ad.type = LSM_AUDIT_DATA_IPC; + ad.u.ipc_id = msq->key; + + rc = avc_has_perm(&selinux_state, + sid, isec->sid, + SECCLASS_MSGQ, MSGQ__READ, &ad); + if (!rc) + rc = avc_has_perm(&selinux_state, + sid, msec->sid, + SECCLASS_MSG, MSG__RECEIVE, &ad); + return rc; +} + +/* Shared Memory security operations */ +static int selinux_shm_alloc_security(struct kern_ipc_perm *shp) +{ + struct ipc_security_struct *isec; + struct common_audit_data ad; + u32 sid = current_sid(); + int rc; + + isec = selinux_ipc(shp); + ipc_init_security(isec, SECCLASS_SHM); + + ad.type = LSM_AUDIT_DATA_IPC; + ad.u.ipc_id = shp->key; + + rc = avc_has_perm(&selinux_state, + sid, isec->sid, SECCLASS_SHM, + SHM__CREATE, &ad); + return rc; +} + +static int selinux_shm_associate(struct kern_ipc_perm *shp, int shmflg) +{ + struct ipc_security_struct *isec; + struct common_audit_data ad; + u32 sid = current_sid(); + + isec = selinux_ipc(shp); + + ad.type = LSM_AUDIT_DATA_IPC; + ad.u.ipc_id = shp->key; + + return avc_has_perm(&selinux_state, + sid, isec->sid, SECCLASS_SHM, + SHM__ASSOCIATE, &ad); +} + +/* Note, at this point, shp is locked down */ +static int selinux_shm_shmctl(struct kern_ipc_perm *shp, int cmd) +{ + int perms; + int err; + + switch (cmd) { + case IPC_INFO: + case SHM_INFO: + /* No specific object, just general system-wide information. */ + return avc_has_perm(&selinux_state, + current_sid(), SECINITSID_KERNEL, + SECCLASS_SYSTEM, SYSTEM__IPC_INFO, NULL); + case IPC_STAT: + case SHM_STAT: + case SHM_STAT_ANY: + perms = SHM__GETATTR | SHM__ASSOCIATE; + break; + case IPC_SET: + perms = SHM__SETATTR; + break; + case SHM_LOCK: + case SHM_UNLOCK: + perms = SHM__LOCK; + break; + case IPC_RMID: + perms = SHM__DESTROY; + break; + default: + return 0; + } + + err = ipc_has_perm(shp, perms); + return err; +} + +static int selinux_shm_shmat(struct kern_ipc_perm *shp, + char __user *shmaddr, int shmflg) +{ + u32 perms; + + if (shmflg & SHM_RDONLY) + perms = SHM__READ; + else + perms = SHM__READ | SHM__WRITE; + + return ipc_has_perm(shp, perms); +} + +/* Semaphore security operations */ +static int selinux_sem_alloc_security(struct kern_ipc_perm *sma) +{ + struct ipc_security_struct *isec; + struct common_audit_data ad; + u32 sid = current_sid(); + int rc; + + isec = selinux_ipc(sma); + ipc_init_security(isec, SECCLASS_SEM); + + ad.type = LSM_AUDIT_DATA_IPC; + ad.u.ipc_id = sma->key; + + rc = avc_has_perm(&selinux_state, + sid, isec->sid, SECCLASS_SEM, + SEM__CREATE, &ad); + return rc; +} + +static int selinux_sem_associate(struct kern_ipc_perm *sma, int semflg) +{ + struct ipc_security_struct *isec; + struct common_audit_data ad; + u32 sid = current_sid(); + + isec = selinux_ipc(sma); + + ad.type = LSM_AUDIT_DATA_IPC; + ad.u.ipc_id = sma->key; + + return avc_has_perm(&selinux_state, + sid, isec->sid, SECCLASS_SEM, + SEM__ASSOCIATE, &ad); +} + +/* Note, at this point, sma is locked down */ +static int selinux_sem_semctl(struct kern_ipc_perm *sma, int cmd) +{ + int err; + u32 perms; + + switch (cmd) { + case IPC_INFO: + case SEM_INFO: + /* No specific object, just general system-wide information. */ + return avc_has_perm(&selinux_state, + current_sid(), SECINITSID_KERNEL, + SECCLASS_SYSTEM, SYSTEM__IPC_INFO, NULL); + case GETPID: + case GETNCNT: + case GETZCNT: + perms = SEM__GETATTR; + break; + case GETVAL: + case GETALL: + perms = SEM__READ; + break; + case SETVAL: + case SETALL: + perms = SEM__WRITE; + break; + case IPC_RMID: + perms = SEM__DESTROY; + break; + case IPC_SET: + perms = SEM__SETATTR; + break; + case IPC_STAT: + case SEM_STAT: + case SEM_STAT_ANY: + perms = SEM__GETATTR | SEM__ASSOCIATE; + break; + default: + return 0; + } + + err = ipc_has_perm(sma, perms); + return err; +} + +static int selinux_sem_semop(struct kern_ipc_perm *sma, + struct sembuf *sops, unsigned nsops, int alter) +{ + u32 perms; + + if (alter) + perms = SEM__READ | SEM__WRITE; + else + perms = SEM__READ; + + return ipc_has_perm(sma, perms); +} + +static int selinux_ipc_permission(struct kern_ipc_perm *ipcp, short flag) +{ + u32 av = 0; + + av = 0; + if (flag & S_IRUGO) + av |= IPC__UNIX_READ; + if (flag & S_IWUGO) + av |= IPC__UNIX_WRITE; + + if (av == 0) + return 0; + + return ipc_has_perm(ipcp, av); +} + +static void selinux_ipc_getsecid(struct kern_ipc_perm *ipcp, u32 *secid) +{ + struct ipc_security_struct *isec = selinux_ipc(ipcp); + *secid = isec->sid; +} + +static void selinux_d_instantiate(struct dentry *dentry, struct inode *inode) +{ + if (inode) + inode_doinit_with_dentry(inode, dentry); +} + +static int selinux_getprocattr(struct task_struct *p, + char *name, char **value) +{ + const struct task_security_struct *__tsec; + u32 sid; + int error; + unsigned len; + + rcu_read_lock(); + __tsec = selinux_cred(__task_cred(p)); + + if (current != p) { + error = avc_has_perm(&selinux_state, + current_sid(), __tsec->sid, + SECCLASS_PROCESS, PROCESS__GETATTR, NULL); + if (error) + goto bad; + } + + if (!strcmp(name, "current")) + sid = __tsec->sid; + else if (!strcmp(name, "prev")) + sid = __tsec->osid; + else if (!strcmp(name, "exec")) + sid = __tsec->exec_sid; + else if (!strcmp(name, "fscreate")) + sid = __tsec->create_sid; + else if (!strcmp(name, "keycreate")) + sid = __tsec->keycreate_sid; + else if (!strcmp(name, "sockcreate")) + sid = __tsec->sockcreate_sid; + else { + error = -EINVAL; + goto bad; + } + rcu_read_unlock(); + + if (!sid) + return 0; + + error = security_sid_to_context(&selinux_state, sid, value, &len); + if (error) + return error; + return len; + +bad: + rcu_read_unlock(); + return error; +} + +static int selinux_setprocattr(const char *name, void *value, size_t size) +{ + struct task_security_struct *tsec; + struct cred *new; + u32 mysid = current_sid(), sid = 0, ptsid; + int error; + char *str = value; + + /* + * Basic control over ability to set these attributes at all. + */ + if (!strcmp(name, "exec")) + error = avc_has_perm(&selinux_state, + mysid, mysid, SECCLASS_PROCESS, + PROCESS__SETEXEC, NULL); + else if (!strcmp(name, "fscreate")) + error = avc_has_perm(&selinux_state, + mysid, mysid, SECCLASS_PROCESS, + PROCESS__SETFSCREATE, NULL); + else if (!strcmp(name, "keycreate")) + error = avc_has_perm(&selinux_state, + mysid, mysid, SECCLASS_PROCESS, + PROCESS__SETKEYCREATE, NULL); + else if (!strcmp(name, "sockcreate")) + error = avc_has_perm(&selinux_state, + mysid, mysid, SECCLASS_PROCESS, + PROCESS__SETSOCKCREATE, NULL); + else if (!strcmp(name, "current")) + error = avc_has_perm(&selinux_state, + mysid, mysid, SECCLASS_PROCESS, + PROCESS__SETCURRENT, NULL); + else + error = -EINVAL; + if (error) + return error; + + /* Obtain a SID for the context, if one was specified. */ + if (size && str[0] && str[0] != '\n') { + if (str[size-1] == '\n') { + str[size-1] = 0; + size--; + } + error = security_context_to_sid(&selinux_state, value, size, + &sid, GFP_KERNEL); + if (error == -EINVAL && !strcmp(name, "fscreate")) { + if (!has_cap_mac_admin(true)) { + struct audit_buffer *ab; + size_t audit_size; + + /* We strip a nul only if it is at the end, otherwise the + * context contains a nul and we should audit that */ + if (str[size - 1] == '\0') + audit_size = size - 1; + else + audit_size = size; + ab = audit_log_start(audit_context(), + GFP_ATOMIC, + AUDIT_SELINUX_ERR); + audit_log_format(ab, "op=fscreate invalid_context="); + audit_log_n_untrustedstring(ab, value, audit_size); + audit_log_end(ab); + + return error; + } + error = security_context_to_sid_force( + &selinux_state, + value, size, &sid); + } + if (error) + return error; + } + + new = prepare_creds(); + if (!new) + return -ENOMEM; + + /* Permission checking based on the specified context is + performed during the actual operation (execve, + open/mkdir/...), when we know the full context of the + operation. See selinux_bprm_creds_for_exec for the execve + checks and may_create for the file creation checks. The + operation will then fail if the context is not permitted. */ + tsec = selinux_cred(new); + if (!strcmp(name, "exec")) { + tsec->exec_sid = sid; + } else if (!strcmp(name, "fscreate")) { + tsec->create_sid = sid; + } else if (!strcmp(name, "keycreate")) { + if (sid) { + error = avc_has_perm(&selinux_state, mysid, sid, + SECCLASS_KEY, KEY__CREATE, NULL); + if (error) + goto abort_change; + } + tsec->keycreate_sid = sid; + } else if (!strcmp(name, "sockcreate")) { + tsec->sockcreate_sid = sid; + } else if (!strcmp(name, "current")) { + error = -EINVAL; + if (sid == 0) + goto abort_change; + + /* Only allow single threaded processes to change context */ + error = -EPERM; + if (!current_is_single_threaded()) { + error = security_bounded_transition(&selinux_state, + tsec->sid, sid); + if (error) + goto abort_change; + } + + /* Check permissions for the transition. */ + error = avc_has_perm(&selinux_state, + tsec->sid, sid, SECCLASS_PROCESS, + PROCESS__DYNTRANSITION, NULL); + if (error) + goto abort_change; + + /* Check for ptracing, and update the task SID if ok. + Otherwise, leave SID unchanged and fail. */ + ptsid = ptrace_parent_sid(); + if (ptsid != 0) { + error = avc_has_perm(&selinux_state, + ptsid, sid, SECCLASS_PROCESS, + PROCESS__PTRACE, NULL); + if (error) + goto abort_change; + } + + tsec->sid = sid; + } else { + error = -EINVAL; + goto abort_change; + } + + commit_creds(new); + return size; + +abort_change: + abort_creds(new); + return error; +} + +static int selinux_ismaclabel(const char *name) +{ + return (strcmp(name, XATTR_SELINUX_SUFFIX) == 0); +} + +static int selinux_secid_to_secctx(u32 secid, char **secdata, u32 *seclen) +{ + return security_sid_to_context(&selinux_state, secid, + secdata, seclen); +} + +static int selinux_secctx_to_secid(const char *secdata, u32 seclen, u32 *secid) +{ + return security_context_to_sid(&selinux_state, secdata, seclen, + secid, GFP_KERNEL); +} + +static void selinux_release_secctx(char *secdata, u32 seclen) +{ + kfree(secdata); +} + +static void selinux_inode_invalidate_secctx(struct inode *inode) +{ + struct inode_security_struct *isec = selinux_inode(inode); + + spin_lock(&isec->lock); + isec->initialized = LABEL_INVALID; + spin_unlock(&isec->lock); +} + +/* + * called with inode->i_mutex locked + */ +static int selinux_inode_notifysecctx(struct inode *inode, void *ctx, u32 ctxlen) +{ + int rc = selinux_inode_setsecurity(inode, XATTR_SELINUX_SUFFIX, + ctx, ctxlen, 0); + /* Do not return error when suppressing label (SBLABEL_MNT not set). */ + return rc == -EOPNOTSUPP ? 0 : rc; +} + +/* + * called with inode->i_mutex locked + */ +static int selinux_inode_setsecctx(struct dentry *dentry, void *ctx, u32 ctxlen) +{ + return __vfs_setxattr_noperm(dentry, XATTR_NAME_SELINUX, ctx, ctxlen, 0); +} + +static int selinux_inode_getsecctx(struct inode *inode, void **ctx, u32 *ctxlen) +{ + int len = 0; + len = selinux_inode_getsecurity(inode, XATTR_SELINUX_SUFFIX, + ctx, true); + if (len < 0) + return len; + *ctxlen = len; + return 0; +} +#ifdef CONFIG_KEYS + +static int selinux_key_alloc(struct key *k, const struct cred *cred, + unsigned long flags) +{ + const struct task_security_struct *tsec; + struct key_security_struct *ksec; + + ksec = kzalloc(sizeof(struct key_security_struct), GFP_KERNEL); + if (!ksec) + return -ENOMEM; + + tsec = selinux_cred(cred); + if (tsec->keycreate_sid) + ksec->sid = tsec->keycreate_sid; + else + ksec->sid = tsec->sid; + + k->security = ksec; + return 0; +} + +static void selinux_key_free(struct key *k) +{ + struct key_security_struct *ksec = k->security; + + k->security = NULL; + kfree(ksec); +} + +static int selinux_key_permission(key_ref_t key_ref, + const struct cred *cred, + enum key_need_perm need_perm) +{ + struct key *key; + struct key_security_struct *ksec; + u32 perm, sid; + + switch (need_perm) { + case KEY_NEED_VIEW: + perm = KEY__VIEW; + break; + case KEY_NEED_READ: + perm = KEY__READ; + break; + case KEY_NEED_WRITE: + perm = KEY__WRITE; + break; + case KEY_NEED_SEARCH: + perm = KEY__SEARCH; + break; + case KEY_NEED_LINK: + perm = KEY__LINK; + break; + case KEY_NEED_SETATTR: + perm = KEY__SETATTR; + break; + case KEY_NEED_UNLINK: + case KEY_SYSADMIN_OVERRIDE: + case KEY_AUTHTOKEN_OVERRIDE: + case KEY_DEFER_PERM_CHECK: + return 0; + default: + WARN_ON(1); + return -EPERM; + + } + + sid = cred_sid(cred); + key = key_ref_to_ptr(key_ref); + ksec = key->security; + + return avc_has_perm(&selinux_state, + sid, ksec->sid, SECCLASS_KEY, perm, NULL); +} + +static int selinux_key_getsecurity(struct key *key, char **_buffer) +{ + struct key_security_struct *ksec = key->security; + char *context = NULL; + unsigned len; + int rc; + + rc = security_sid_to_context(&selinux_state, ksec->sid, + &context, &len); + if (!rc) + rc = len; + *_buffer = context; + return rc; +} + +#ifdef CONFIG_KEY_NOTIFICATIONS +static int selinux_watch_key(struct key *key) +{ + struct key_security_struct *ksec = key->security; + u32 sid = current_sid(); + + return avc_has_perm(&selinux_state, + sid, ksec->sid, SECCLASS_KEY, KEY__VIEW, NULL); +} +#endif +#endif + +#ifdef CONFIG_SECURITY_INFINIBAND +static int selinux_ib_pkey_access(void *ib_sec, u64 subnet_prefix, u16 pkey_val) +{ + struct common_audit_data ad; + int err; + u32 sid = 0; + struct ib_security_struct *sec = ib_sec; + struct lsm_ibpkey_audit ibpkey; + + err = sel_ib_pkey_sid(subnet_prefix, pkey_val, &sid); + if (err) + return err; + + ad.type = LSM_AUDIT_DATA_IBPKEY; + ibpkey.subnet_prefix = subnet_prefix; + ibpkey.pkey = pkey_val; + ad.u.ibpkey = &ibpkey; + return avc_has_perm(&selinux_state, + sec->sid, sid, + SECCLASS_INFINIBAND_PKEY, + INFINIBAND_PKEY__ACCESS, &ad); +} + +static int selinux_ib_endport_manage_subnet(void *ib_sec, const char *dev_name, + u8 port_num) +{ + struct common_audit_data ad; + int err; + u32 sid = 0; + struct ib_security_struct *sec = ib_sec; + struct lsm_ibendport_audit ibendport; + + err = security_ib_endport_sid(&selinux_state, dev_name, port_num, + &sid); + + if (err) + return err; + + ad.type = LSM_AUDIT_DATA_IBENDPORT; + strncpy(ibendport.dev_name, dev_name, sizeof(ibendport.dev_name)); + ibendport.port = port_num; + ad.u.ibendport = &ibendport; + return avc_has_perm(&selinux_state, + sec->sid, sid, + SECCLASS_INFINIBAND_ENDPORT, + INFINIBAND_ENDPORT__MANAGE_SUBNET, &ad); +} + +static int selinux_ib_alloc_security(void **ib_sec) +{ + struct ib_security_struct *sec; + + sec = kzalloc(sizeof(*sec), GFP_KERNEL); + if (!sec) + return -ENOMEM; + sec->sid = current_sid(); + + *ib_sec = sec; + return 0; +} + +static void selinux_ib_free_security(void *ib_sec) +{ + kfree(ib_sec); +} +#endif + +#ifdef CONFIG_BPF_SYSCALL +static int selinux_bpf(int cmd, union bpf_attr *attr, + unsigned int size) +{ + u32 sid = current_sid(); + int ret; + + switch (cmd) { + case BPF_MAP_CREATE: + ret = avc_has_perm(&selinux_state, + sid, sid, SECCLASS_BPF, BPF__MAP_CREATE, + NULL); + break; + case BPF_PROG_LOAD: + ret = avc_has_perm(&selinux_state, + sid, sid, SECCLASS_BPF, BPF__PROG_LOAD, + NULL); + break; + default: + ret = 0; + break; + } + + return ret; +} + +static u32 bpf_map_fmode_to_av(fmode_t fmode) +{ + u32 av = 0; + + if (fmode & FMODE_READ) + av |= BPF__MAP_READ; + if (fmode & FMODE_WRITE) + av |= BPF__MAP_WRITE; + return av; +} + +/* This function will check the file pass through unix socket or binder to see + * if it is a bpf related object. And apply correspinding checks on the bpf + * object based on the type. The bpf maps and programs, not like other files and + * socket, are using a shared anonymous inode inside the kernel as their inode. + * So checking that inode cannot identify if the process have privilege to + * access the bpf object and that's why we have to add this additional check in + * selinux_file_receive and selinux_binder_transfer_files. + */ +static int bpf_fd_pass(struct file *file, u32 sid) +{ + struct bpf_security_struct *bpfsec; + struct bpf_prog *prog; + struct bpf_map *map; + int ret; + + if (file->f_op == &bpf_map_fops) { + map = file->private_data; + bpfsec = map->security; + ret = avc_has_perm(&selinux_state, + sid, bpfsec->sid, SECCLASS_BPF, + bpf_map_fmode_to_av(file->f_mode), NULL); + if (ret) + return ret; + } else if (file->f_op == &bpf_prog_fops) { + prog = file->private_data; + bpfsec = prog->aux->security; + ret = avc_has_perm(&selinux_state, + sid, bpfsec->sid, SECCLASS_BPF, + BPF__PROG_RUN, NULL); + if (ret) + return ret; + } + return 0; +} + +static int selinux_bpf_map(struct bpf_map *map, fmode_t fmode) +{ + u32 sid = current_sid(); + struct bpf_security_struct *bpfsec; + + bpfsec = map->security; + return avc_has_perm(&selinux_state, + sid, bpfsec->sid, SECCLASS_BPF, + bpf_map_fmode_to_av(fmode), NULL); +} + +static int selinux_bpf_prog(struct bpf_prog *prog) +{ + u32 sid = current_sid(); + struct bpf_security_struct *bpfsec; + + bpfsec = prog->aux->security; + return avc_has_perm(&selinux_state, + sid, bpfsec->sid, SECCLASS_BPF, + BPF__PROG_RUN, NULL); +} + +static int selinux_bpf_map_alloc(struct bpf_map *map) +{ + struct bpf_security_struct *bpfsec; + + bpfsec = kzalloc(sizeof(*bpfsec), GFP_KERNEL); + if (!bpfsec) + return -ENOMEM; + + bpfsec->sid = current_sid(); + map->security = bpfsec; + + return 0; +} + +static void selinux_bpf_map_free(struct bpf_map *map) +{ + struct bpf_security_struct *bpfsec = map->security; + + map->security = NULL; + kfree(bpfsec); +} + +static int selinux_bpf_prog_alloc(struct bpf_prog_aux *aux) +{ + struct bpf_security_struct *bpfsec; + + bpfsec = kzalloc(sizeof(*bpfsec), GFP_KERNEL); + if (!bpfsec) + return -ENOMEM; + + bpfsec->sid = current_sid(); + aux->security = bpfsec; + + return 0; +} + +static void selinux_bpf_prog_free(struct bpf_prog_aux *aux) +{ + struct bpf_security_struct *bpfsec = aux->security; + + aux->security = NULL; + kfree(bpfsec); +} +#endif + +static int selinux_lockdown(enum lockdown_reason what) +{ + struct common_audit_data ad; + u32 sid = current_sid(); + int invalid_reason = (what <= LOCKDOWN_NONE) || + (what == LOCKDOWN_INTEGRITY_MAX) || + (what >= LOCKDOWN_CONFIDENTIALITY_MAX); + + if (WARN(invalid_reason, "Invalid lockdown reason")) { + audit_log(audit_context(), + GFP_ATOMIC, AUDIT_SELINUX_ERR, + "lockdown_reason=invalid"); + return -EINVAL; + } + + ad.type = LSM_AUDIT_DATA_LOCKDOWN; + ad.u.reason = what; + + if (what <= LOCKDOWN_INTEGRITY_MAX) + return avc_has_perm(&selinux_state, + sid, sid, SECCLASS_LOCKDOWN, + LOCKDOWN__INTEGRITY, &ad); + else + return avc_has_perm(&selinux_state, + sid, sid, SECCLASS_LOCKDOWN, + LOCKDOWN__CONFIDENTIALITY, &ad); +} + +struct lsm_blob_sizes selinux_blob_sizes __lsm_ro_after_init = { + .lbs_cred = sizeof(struct task_security_struct), + .lbs_file = sizeof(struct file_security_struct), + .lbs_inode = sizeof(struct inode_security_struct), + .lbs_ipc = sizeof(struct ipc_security_struct), + .lbs_msg_msg = sizeof(struct msg_security_struct), +}; + +#ifdef CONFIG_PERF_EVENTS +static int selinux_perf_event_open(struct perf_event_attr *attr, int type) +{ + u32 requested, sid = current_sid(); + + if (type == PERF_SECURITY_OPEN) + requested = PERF_EVENT__OPEN; + else if (type == PERF_SECURITY_CPU) + requested = PERF_EVENT__CPU; + else if (type == PERF_SECURITY_KERNEL) + requested = PERF_EVENT__KERNEL; + else if (type == PERF_SECURITY_TRACEPOINT) + requested = PERF_EVENT__TRACEPOINT; + else + return -EINVAL; + + return avc_has_perm(&selinux_state, sid, sid, SECCLASS_PERF_EVENT, + requested, NULL); +} + +static int selinux_perf_event_alloc(struct perf_event *event) +{ + struct perf_event_security_struct *perfsec; + + perfsec = kzalloc(sizeof(*perfsec), GFP_KERNEL); + if (!perfsec) + return -ENOMEM; + + perfsec->sid = current_sid(); + event->security = perfsec; + + return 0; +} + +static void selinux_perf_event_free(struct perf_event *event) +{ + struct perf_event_security_struct *perfsec = event->security; + + event->security = NULL; + kfree(perfsec); +} + +static int selinux_perf_event_read(struct perf_event *event) +{ + struct perf_event_security_struct *perfsec = event->security; + u32 sid = current_sid(); + + return avc_has_perm(&selinux_state, sid, perfsec->sid, + SECCLASS_PERF_EVENT, PERF_EVENT__READ, NULL); +} + +static int selinux_perf_event_write(struct perf_event *event) +{ + struct perf_event_security_struct *perfsec = event->security; + u32 sid = current_sid(); + + return avc_has_perm(&selinux_state, sid, perfsec->sid, + SECCLASS_PERF_EVENT, PERF_EVENT__WRITE, NULL); +} +#endif + +/* + * IMPORTANT NOTE: When adding new hooks, please be careful to keep this order: + * 1. any hooks that don't belong to (2.) or (3.) below, + * 2. hooks that both access structures allocated by other hooks, and allocate + * structures that can be later accessed by other hooks (mostly "cloning" + * hooks), + * 3. hooks that only allocate structures that can be later accessed by other + * hooks ("allocating" hooks). + * + * Please follow block comment delimiters in the list to keep this order. + * + * This ordering is needed for SELinux runtime disable to work at least somewhat + * safely. Breaking the ordering rules above might lead to NULL pointer derefs + * when disabling SELinux at runtime. + */ +static struct security_hook_list selinux_hooks[] __lsm_ro_after_init = { + LSM_HOOK_INIT(binder_set_context_mgr, selinux_binder_set_context_mgr), + LSM_HOOK_INIT(binder_transaction, selinux_binder_transaction), + LSM_HOOK_INIT(binder_transfer_binder, selinux_binder_transfer_binder), + LSM_HOOK_INIT(binder_transfer_file, selinux_binder_transfer_file), + + LSM_HOOK_INIT(ptrace_access_check, selinux_ptrace_access_check), + LSM_HOOK_INIT(ptrace_traceme, selinux_ptrace_traceme), + LSM_HOOK_INIT(capget, selinux_capget), + LSM_HOOK_INIT(capset, selinux_capset), + LSM_HOOK_INIT(capable, selinux_capable), + LSM_HOOK_INIT(quotactl, selinux_quotactl), + LSM_HOOK_INIT(quota_on, selinux_quota_on), + LSM_HOOK_INIT(syslog, selinux_syslog), + LSM_HOOK_INIT(vm_enough_memory, selinux_vm_enough_memory), + + LSM_HOOK_INIT(netlink_send, selinux_netlink_send), + + LSM_HOOK_INIT(bprm_creds_for_exec, selinux_bprm_creds_for_exec), + LSM_HOOK_INIT(bprm_committing_creds, selinux_bprm_committing_creds), + LSM_HOOK_INIT(bprm_committed_creds, selinux_bprm_committed_creds), + + LSM_HOOK_INIT(sb_free_security, selinux_sb_free_security), + LSM_HOOK_INIT(sb_free_mnt_opts, selinux_free_mnt_opts), + LSM_HOOK_INIT(sb_remount, selinux_sb_remount), + LSM_HOOK_INIT(sb_kern_mount, selinux_sb_kern_mount), + LSM_HOOK_INIT(sb_show_options, selinux_sb_show_options), + LSM_HOOK_INIT(sb_statfs, selinux_sb_statfs), + LSM_HOOK_INIT(sb_mount, selinux_mount), + LSM_HOOK_INIT(sb_umount, selinux_umount), + LSM_HOOK_INIT(sb_set_mnt_opts, selinux_set_mnt_opts), + LSM_HOOK_INIT(sb_clone_mnt_opts, selinux_sb_clone_mnt_opts), + + LSM_HOOK_INIT(move_mount, selinux_move_mount), + + LSM_HOOK_INIT(dentry_init_security, selinux_dentry_init_security), + LSM_HOOK_INIT(dentry_create_files_as, selinux_dentry_create_files_as), + + LSM_HOOK_INIT(inode_free_security, selinux_inode_free_security), + LSM_HOOK_INIT(inode_init_security, selinux_inode_init_security), + LSM_HOOK_INIT(inode_create, selinux_inode_create), + LSM_HOOK_INIT(inode_link, selinux_inode_link), + LSM_HOOK_INIT(inode_unlink, selinux_inode_unlink), + LSM_HOOK_INIT(inode_symlink, selinux_inode_symlink), + LSM_HOOK_INIT(inode_mkdir, selinux_inode_mkdir), + LSM_HOOK_INIT(inode_rmdir, selinux_inode_rmdir), + LSM_HOOK_INIT(inode_mknod, selinux_inode_mknod), + LSM_HOOK_INIT(inode_rename, selinux_inode_rename), + LSM_HOOK_INIT(inode_readlink, selinux_inode_readlink), + LSM_HOOK_INIT(inode_follow_link, selinux_inode_follow_link), + LSM_HOOK_INIT(inode_permission, selinux_inode_permission), + LSM_HOOK_INIT(inode_setattr, selinux_inode_setattr), + LSM_HOOK_INIT(inode_getattr, selinux_inode_getattr), + LSM_HOOK_INIT(inode_setxattr, selinux_inode_setxattr), + LSM_HOOK_INIT(inode_post_setxattr, selinux_inode_post_setxattr), + LSM_HOOK_INIT(inode_getxattr, selinux_inode_getxattr), + LSM_HOOK_INIT(inode_listxattr, selinux_inode_listxattr), + LSM_HOOK_INIT(inode_removexattr, selinux_inode_removexattr), + LSM_HOOK_INIT(inode_getsecurity, selinux_inode_getsecurity), + LSM_HOOK_INIT(inode_setsecurity, selinux_inode_setsecurity), + LSM_HOOK_INIT(inode_listsecurity, selinux_inode_listsecurity), + LSM_HOOK_INIT(inode_getsecid, selinux_inode_getsecid), + LSM_HOOK_INIT(inode_copy_up, selinux_inode_copy_up), + LSM_HOOK_INIT(inode_copy_up_xattr, selinux_inode_copy_up_xattr), + LSM_HOOK_INIT(path_notify, selinux_path_notify), + + LSM_HOOK_INIT(kernfs_init_security, selinux_kernfs_init_security), + + LSM_HOOK_INIT(file_permission, selinux_file_permission), + LSM_HOOK_INIT(file_alloc_security, selinux_file_alloc_security), + LSM_HOOK_INIT(file_ioctl, selinux_file_ioctl), + LSM_HOOK_INIT(mmap_file, selinux_mmap_file), + LSM_HOOK_INIT(mmap_addr, selinux_mmap_addr), + LSM_HOOK_INIT(file_mprotect, selinux_file_mprotect), + LSM_HOOK_INIT(file_lock, selinux_file_lock), + LSM_HOOK_INIT(file_fcntl, selinux_file_fcntl), + LSM_HOOK_INIT(file_set_fowner, selinux_file_set_fowner), + LSM_HOOK_INIT(file_send_sigiotask, selinux_file_send_sigiotask), + LSM_HOOK_INIT(file_receive, selinux_file_receive), + + LSM_HOOK_INIT(file_open, selinux_file_open), + + LSM_HOOK_INIT(task_alloc, selinux_task_alloc), + LSM_HOOK_INIT(cred_prepare, selinux_cred_prepare), + LSM_HOOK_INIT(cred_transfer, selinux_cred_transfer), + LSM_HOOK_INIT(cred_getsecid, selinux_cred_getsecid), + LSM_HOOK_INIT(kernel_act_as, selinux_kernel_act_as), + LSM_HOOK_INIT(kernel_create_files_as, selinux_kernel_create_files_as), + LSM_HOOK_INIT(kernel_module_request, selinux_kernel_module_request), + LSM_HOOK_INIT(kernel_load_data, selinux_kernel_load_data), + LSM_HOOK_INIT(kernel_read_file, selinux_kernel_read_file), + LSM_HOOK_INIT(task_setpgid, selinux_task_setpgid), + LSM_HOOK_INIT(task_getpgid, selinux_task_getpgid), + LSM_HOOK_INIT(task_getsid, selinux_task_getsid), + LSM_HOOK_INIT(task_getsecid, selinux_task_getsecid), + LSM_HOOK_INIT(task_setnice, selinux_task_setnice), + LSM_HOOK_INIT(task_setioprio, selinux_task_setioprio), + LSM_HOOK_INIT(task_getioprio, selinux_task_getioprio), + LSM_HOOK_INIT(task_prlimit, selinux_task_prlimit), + LSM_HOOK_INIT(task_setrlimit, selinux_task_setrlimit), + LSM_HOOK_INIT(task_setscheduler, selinux_task_setscheduler), + LSM_HOOK_INIT(task_getscheduler, selinux_task_getscheduler), + LSM_HOOK_INIT(task_movememory, selinux_task_movememory), + LSM_HOOK_INIT(task_kill, selinux_task_kill), + LSM_HOOK_INIT(task_to_inode, selinux_task_to_inode), + + LSM_HOOK_INIT(ipc_permission, selinux_ipc_permission), + LSM_HOOK_INIT(ipc_getsecid, selinux_ipc_getsecid), + + LSM_HOOK_INIT(msg_queue_associate, selinux_msg_queue_associate), + LSM_HOOK_INIT(msg_queue_msgctl, selinux_msg_queue_msgctl), + LSM_HOOK_INIT(msg_queue_msgsnd, selinux_msg_queue_msgsnd), + LSM_HOOK_INIT(msg_queue_msgrcv, selinux_msg_queue_msgrcv), + + LSM_HOOK_INIT(shm_associate, selinux_shm_associate), + LSM_HOOK_INIT(shm_shmctl, selinux_shm_shmctl), + LSM_HOOK_INIT(shm_shmat, selinux_shm_shmat), + + LSM_HOOK_INIT(sem_associate, selinux_sem_associate), + LSM_HOOK_INIT(sem_semctl, selinux_sem_semctl), + LSM_HOOK_INIT(sem_semop, selinux_sem_semop), + + LSM_HOOK_INIT(d_instantiate, selinux_d_instantiate), + + LSM_HOOK_INIT(getprocattr, selinux_getprocattr), + LSM_HOOK_INIT(setprocattr, selinux_setprocattr), + + LSM_HOOK_INIT(ismaclabel, selinux_ismaclabel), + LSM_HOOK_INIT(secctx_to_secid, selinux_secctx_to_secid), + LSM_HOOK_INIT(release_secctx, selinux_release_secctx), + LSM_HOOK_INIT(inode_invalidate_secctx, selinux_inode_invalidate_secctx), + LSM_HOOK_INIT(inode_notifysecctx, selinux_inode_notifysecctx), + LSM_HOOK_INIT(inode_setsecctx, selinux_inode_setsecctx), + + LSM_HOOK_INIT(unix_stream_connect, selinux_socket_unix_stream_connect), + LSM_HOOK_INIT(unix_may_send, selinux_socket_unix_may_send), + + LSM_HOOK_INIT(socket_create, selinux_socket_create), + LSM_HOOK_INIT(socket_post_create, selinux_socket_post_create), + LSM_HOOK_INIT(socket_socketpair, selinux_socket_socketpair), + LSM_HOOK_INIT(socket_bind, selinux_socket_bind), + LSM_HOOK_INIT(socket_connect, selinux_socket_connect), + LSM_HOOK_INIT(socket_listen, selinux_socket_listen), + LSM_HOOK_INIT(socket_accept, selinux_socket_accept), + LSM_HOOK_INIT(socket_sendmsg, selinux_socket_sendmsg), + LSM_HOOK_INIT(socket_recvmsg, selinux_socket_recvmsg), + LSM_HOOK_INIT(socket_getsockname, selinux_socket_getsockname), + LSM_HOOK_INIT(socket_getpeername, selinux_socket_getpeername), + LSM_HOOK_INIT(socket_getsockopt, selinux_socket_getsockopt), + LSM_HOOK_INIT(socket_setsockopt, selinux_socket_setsockopt), + LSM_HOOK_INIT(socket_shutdown, selinux_socket_shutdown), + LSM_HOOK_INIT(socket_sock_rcv_skb, selinux_socket_sock_rcv_skb), + LSM_HOOK_INIT(socket_getpeersec_stream, + selinux_socket_getpeersec_stream), + LSM_HOOK_INIT(socket_getpeersec_dgram, selinux_socket_getpeersec_dgram), + LSM_HOOK_INIT(sk_free_security, selinux_sk_free_security), + LSM_HOOK_INIT(sk_clone_security, selinux_sk_clone_security), + LSM_HOOK_INIT(sk_getsecid, selinux_sk_getsecid), + LSM_HOOK_INIT(sock_graft, selinux_sock_graft), + LSM_HOOK_INIT(sctp_assoc_request, selinux_sctp_assoc_request), + LSM_HOOK_INIT(sctp_sk_clone, selinux_sctp_sk_clone), + LSM_HOOK_INIT(sctp_bind_connect, selinux_sctp_bind_connect), + LSM_HOOK_INIT(inet_conn_request, selinux_inet_conn_request), + LSM_HOOK_INIT(inet_csk_clone, selinux_inet_csk_clone), + LSM_HOOK_INIT(inet_conn_established, selinux_inet_conn_established), + LSM_HOOK_INIT(secmark_relabel_packet, selinux_secmark_relabel_packet), + LSM_HOOK_INIT(secmark_refcount_inc, selinux_secmark_refcount_inc), + LSM_HOOK_INIT(secmark_refcount_dec, selinux_secmark_refcount_dec), + LSM_HOOK_INIT(req_classify_flow, selinux_req_classify_flow), + LSM_HOOK_INIT(tun_dev_free_security, selinux_tun_dev_free_security), + LSM_HOOK_INIT(tun_dev_create, selinux_tun_dev_create), + LSM_HOOK_INIT(tun_dev_attach_queue, selinux_tun_dev_attach_queue), + LSM_HOOK_INIT(tun_dev_attach, selinux_tun_dev_attach), + LSM_HOOK_INIT(tun_dev_open, selinux_tun_dev_open), +#ifdef CONFIG_SECURITY_INFINIBAND + LSM_HOOK_INIT(ib_pkey_access, selinux_ib_pkey_access), + LSM_HOOK_INIT(ib_endport_manage_subnet, + selinux_ib_endport_manage_subnet), + LSM_HOOK_INIT(ib_free_security, selinux_ib_free_security), +#endif +#ifdef CONFIG_SECURITY_NETWORK_XFRM + LSM_HOOK_INIT(xfrm_policy_free_security, selinux_xfrm_policy_free), + LSM_HOOK_INIT(xfrm_policy_delete_security, selinux_xfrm_policy_delete), + LSM_HOOK_INIT(xfrm_state_free_security, selinux_xfrm_state_free), + LSM_HOOK_INIT(xfrm_state_delete_security, selinux_xfrm_state_delete), + LSM_HOOK_INIT(xfrm_policy_lookup, selinux_xfrm_policy_lookup), + LSM_HOOK_INIT(xfrm_state_pol_flow_match, + selinux_xfrm_state_pol_flow_match), + LSM_HOOK_INIT(xfrm_decode_session, selinux_xfrm_decode_session), +#endif + +#ifdef CONFIG_KEYS + LSM_HOOK_INIT(key_free, selinux_key_free), + LSM_HOOK_INIT(key_permission, selinux_key_permission), + LSM_HOOK_INIT(key_getsecurity, selinux_key_getsecurity), +#ifdef CONFIG_KEY_NOTIFICATIONS + LSM_HOOK_INIT(watch_key, selinux_watch_key), +#endif +#endif + +#ifdef CONFIG_AUDIT + LSM_HOOK_INIT(audit_rule_known, selinux_audit_rule_known), + LSM_HOOK_INIT(audit_rule_match, selinux_audit_rule_match), + LSM_HOOK_INIT(audit_rule_free, selinux_audit_rule_free), +#endif + +#ifdef CONFIG_BPF_SYSCALL + LSM_HOOK_INIT(bpf, selinux_bpf), + LSM_HOOK_INIT(bpf_map, selinux_bpf_map), + LSM_HOOK_INIT(bpf_prog, selinux_bpf_prog), + LSM_HOOK_INIT(bpf_map_free_security, selinux_bpf_map_free), + LSM_HOOK_INIT(bpf_prog_free_security, selinux_bpf_prog_free), +#endif + +#ifdef CONFIG_PERF_EVENTS + LSM_HOOK_INIT(perf_event_open, selinux_perf_event_open), + LSM_HOOK_INIT(perf_event_free, selinux_perf_event_free), + LSM_HOOK_INIT(perf_event_read, selinux_perf_event_read), + LSM_HOOK_INIT(perf_event_write, selinux_perf_event_write), +#endif + + LSM_HOOK_INIT(locked_down, selinux_lockdown), + + /* + * PUT "CLONING" (ACCESSING + ALLOCATING) HOOKS HERE + */ + LSM_HOOK_INIT(fs_context_dup, selinux_fs_context_dup), + LSM_HOOK_INIT(fs_context_parse_param, selinux_fs_context_parse_param), + LSM_HOOK_INIT(sb_eat_lsm_opts, selinux_sb_eat_lsm_opts), + LSM_HOOK_INIT(sb_add_mnt_opt, selinux_add_mnt_opt), +#ifdef CONFIG_SECURITY_NETWORK_XFRM + LSM_HOOK_INIT(xfrm_policy_clone_security, selinux_xfrm_policy_clone), +#endif + + /* + * PUT "ALLOCATING" HOOKS HERE + */ + LSM_HOOK_INIT(msg_msg_alloc_security, selinux_msg_msg_alloc_security), + LSM_HOOK_INIT(msg_queue_alloc_security, + selinux_msg_queue_alloc_security), + LSM_HOOK_INIT(shm_alloc_security, selinux_shm_alloc_security), + LSM_HOOK_INIT(sb_alloc_security, selinux_sb_alloc_security), + LSM_HOOK_INIT(inode_alloc_security, selinux_inode_alloc_security), + LSM_HOOK_INIT(sem_alloc_security, selinux_sem_alloc_security), + LSM_HOOK_INIT(secid_to_secctx, selinux_secid_to_secctx), + LSM_HOOK_INIT(inode_getsecctx, selinux_inode_getsecctx), + LSM_HOOK_INIT(sk_alloc_security, selinux_sk_alloc_security), + LSM_HOOK_INIT(tun_dev_alloc_security, selinux_tun_dev_alloc_security), +#ifdef CONFIG_SECURITY_INFINIBAND + LSM_HOOK_INIT(ib_alloc_security, selinux_ib_alloc_security), +#endif +#ifdef CONFIG_SECURITY_NETWORK_XFRM + LSM_HOOK_INIT(xfrm_policy_alloc_security, selinux_xfrm_policy_alloc), + LSM_HOOK_INIT(xfrm_state_alloc, selinux_xfrm_state_alloc), + LSM_HOOK_INIT(xfrm_state_alloc_acquire, + selinux_xfrm_state_alloc_acquire), +#endif +#ifdef CONFIG_KEYS + LSM_HOOK_INIT(key_alloc, selinux_key_alloc), +#endif +#ifdef CONFIG_AUDIT + LSM_HOOK_INIT(audit_rule_init, selinux_audit_rule_init), +#endif +#ifdef CONFIG_BPF_SYSCALL + LSM_HOOK_INIT(bpf_map_alloc_security, selinux_bpf_map_alloc), + LSM_HOOK_INIT(bpf_prog_alloc_security, selinux_bpf_prog_alloc), +#endif +#ifdef CONFIG_PERF_EVENTS + LSM_HOOK_INIT(perf_event_alloc, selinux_perf_event_alloc), +#endif +}; + +static __init int selinux_init(void) +{ + pr_info("SELinux: Initializing.\n"); + + memset(&selinux_state, 0, sizeof(selinux_state)); + enforcing_set(&selinux_state, selinux_enforcing_boot); + checkreqprot_set(&selinux_state, selinux_checkreqprot_boot); + selinux_avc_init(&selinux_state.avc); + mutex_init(&selinux_state.status_lock); + mutex_init(&selinux_state.policy_mutex); + + /* Set the security state for the initial task. */ + cred_init_security(); + + default_noexec = !(VM_DATA_DEFAULT_FLAGS & VM_EXEC); + + avc_init(); + + avtab_cache_init(); + + ebitmap_cache_init(); + + hashtab_cache_init(); + + security_add_hooks(selinux_hooks, ARRAY_SIZE(selinux_hooks), "selinux"); + + if (avc_add_callback(selinux_netcache_avc_callback, AVC_CALLBACK_RESET)) + panic("SELinux: Unable to register AVC netcache callback\n"); + + if (avc_add_callback(selinux_lsm_notifier_avc_callback, AVC_CALLBACK_RESET)) + panic("SELinux: Unable to register AVC LSM notifier callback\n"); + + if (selinux_enforcing_boot) + pr_debug("SELinux: Starting in enforcing mode\n"); + else + pr_debug("SELinux: Starting in permissive mode\n"); + + fs_validate_description("selinux", selinux_fs_parameters); + + return 0; +} + +static void delayed_superblock_init(struct super_block *sb, void *unused) +{ + selinux_set_mnt_opts(sb, NULL, 0, NULL); +} + +void selinux_complete_init(void) +{ + pr_debug("SELinux: Completing initialization.\n"); + + /* Set up any superblocks initialized prior to the policy load. */ + pr_debug("SELinux: Setting up existing superblocks.\n"); + iterate_supers(delayed_superblock_init, NULL); +} + +/* SELinux requires early initialization in order to label + all processes and objects when they are created. */ +DEFINE_LSM(selinux) = { + .name = "selinux", + .flags = LSM_FLAG_LEGACY_MAJOR | LSM_FLAG_EXCLUSIVE, + .enabled = &selinux_enabled_boot, + .blobs = &selinux_blob_sizes, + .init = selinux_init, +}; + +#if defined(CONFIG_NETFILTER) + +static const struct nf_hook_ops selinux_nf_ops[] = { + { + .hook = selinux_ipv4_postroute, + .pf = NFPROTO_IPV4, + .hooknum = NF_INET_POST_ROUTING, + .priority = NF_IP_PRI_SELINUX_LAST, + }, + { + .hook = selinux_ipv4_forward, + .pf = NFPROTO_IPV4, + .hooknum = NF_INET_FORWARD, + .priority = NF_IP_PRI_SELINUX_FIRST, + }, + { + .hook = selinux_ipv4_output, + .pf = NFPROTO_IPV4, + .hooknum = NF_INET_LOCAL_OUT, + .priority = NF_IP_PRI_SELINUX_FIRST, + }, +#if IS_ENABLED(CONFIG_IPV6) + { + .hook = selinux_ipv6_postroute, + .pf = NFPROTO_IPV6, + .hooknum = NF_INET_POST_ROUTING, + .priority = NF_IP6_PRI_SELINUX_LAST, + }, + { + .hook = selinux_ipv6_forward, + .pf = NFPROTO_IPV6, + .hooknum = NF_INET_FORWARD, + .priority = NF_IP6_PRI_SELINUX_FIRST, + }, + { + .hook = selinux_ipv6_output, + .pf = NFPROTO_IPV6, + .hooknum = NF_INET_LOCAL_OUT, + .priority = NF_IP6_PRI_SELINUX_FIRST, + }, +#endif /* IPV6 */ +}; + +static int __net_init selinux_nf_register(struct net *net) +{ + return nf_register_net_hooks(net, selinux_nf_ops, + ARRAY_SIZE(selinux_nf_ops)); +} + +static void __net_exit selinux_nf_unregister(struct net *net) +{ + nf_unregister_net_hooks(net, selinux_nf_ops, + ARRAY_SIZE(selinux_nf_ops)); +} + +static struct pernet_operations selinux_net_ops = { + .init = selinux_nf_register, + .exit = selinux_nf_unregister, +}; + +static int __init selinux_nf_ip_init(void) +{ + int err; + + if (!selinux_enabled_boot) + return 0; + + pr_debug("SELinux: Registering netfilter hooks\n"); + + err = register_pernet_subsys(&selinux_net_ops); + if (err) + panic("SELinux: register_pernet_subsys: error %d\n", err); + + return 0; +} +__initcall(selinux_nf_ip_init); + +#ifdef CONFIG_SECURITY_SELINUX_DISABLE +static void selinux_nf_ip_exit(void) +{ + pr_debug("SELinux: Unregistering netfilter hooks\n"); + + unregister_pernet_subsys(&selinux_net_ops); +} +#endif + +#else /* CONFIG_NETFILTER */ + +#ifdef CONFIG_SECURITY_SELINUX_DISABLE +#define selinux_nf_ip_exit() +#endif + +#endif /* CONFIG_NETFILTER */ + +#ifdef CONFIG_SECURITY_SELINUX_DISABLE +int selinux_disable(struct selinux_state *state) +{ + if (selinux_initialized(state)) { + /* Not permitted after initial policy load. */ + return -EINVAL; + } + + if (selinux_disabled(state)) { + /* Only do this once. */ + return -EINVAL; + } + + selinux_mark_disabled(state); + + pr_info("SELinux: Disabled at runtime.\n"); + + /* + * Unregister netfilter hooks. + * Must be done before security_delete_hooks() to avoid breaking + * runtime disable. + */ + selinux_nf_ip_exit(); + + security_delete_hooks(selinux_hooks, ARRAY_SIZE(selinux_hooks)); + + /* Try to destroy the avc node cache */ + avc_disable(); + + /* Unregister selinuxfs. */ + exit_sel_fs(); + + return 0; +} +#endif diff --git a/security/selinux/ibpkey.c b/security/selinux/ibpkey.c new file mode 100644 index 000000000..3a63a989e --- /dev/null +++ b/security/selinux/ibpkey.c @@ -0,0 +1,238 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Pkey table + * + * SELinux must keep a mapping of Infinband PKEYs to labels/SIDs. This + * mapping is maintained as part of the normal policy but a fast cache is + * needed to reduce the lookup overhead. + * + * This code is heavily based on the "netif" and "netport" concept originally + * developed by + * James Morris <jmorris@redhat.com> and + * Paul Moore <paul@paul-moore.com> + * (see security/selinux/netif.c and security/selinux/netport.c for more + * information) + */ + +/* + * (c) Mellanox Technologies, 2016 + */ + +#include <linux/types.h> +#include <linux/rcupdate.h> +#include <linux/list.h> +#include <linux/spinlock.h> + +#include "ibpkey.h" +#include "objsec.h" + +#define SEL_PKEY_HASH_SIZE 256 +#define SEL_PKEY_HASH_BKT_LIMIT 16 + +struct sel_ib_pkey_bkt { + int size; + struct list_head list; +}; + +struct sel_ib_pkey { + struct pkey_security_struct psec; + struct list_head list; + struct rcu_head rcu; +}; + +static LIST_HEAD(sel_ib_pkey_list); +static DEFINE_SPINLOCK(sel_ib_pkey_lock); +static struct sel_ib_pkey_bkt sel_ib_pkey_hash[SEL_PKEY_HASH_SIZE]; + +/** + * sel_ib_pkey_hashfn - Hashing function for the pkey table + * @pkey: pkey number + * + * Description: + * This is the hashing function for the pkey table, it returns the bucket + * number for the given pkey. + * + */ +static unsigned int sel_ib_pkey_hashfn(u16 pkey) +{ + return (pkey & (SEL_PKEY_HASH_SIZE - 1)); +} + +/** + * sel_ib_pkey_find - Search for a pkey record + * @subnet_prefix: subnet_prefix + * @pkey_num: pkey_num + * + * Description: + * Search the pkey table and return the matching record. If an entry + * can not be found in the table return NULL. + * + */ +static struct sel_ib_pkey *sel_ib_pkey_find(u64 subnet_prefix, u16 pkey_num) +{ + unsigned int idx; + struct sel_ib_pkey *pkey; + + idx = sel_ib_pkey_hashfn(pkey_num); + list_for_each_entry_rcu(pkey, &sel_ib_pkey_hash[idx].list, list) { + if (pkey->psec.pkey == pkey_num && + pkey->psec.subnet_prefix == subnet_prefix) + return pkey; + } + + return NULL; +} + +/** + * sel_ib_pkey_insert - Insert a new pkey into the table + * @pkey: the new pkey record + * + * Description: + * Add a new pkey record to the hash table. + * + */ +static void sel_ib_pkey_insert(struct sel_ib_pkey *pkey) +{ + unsigned int idx; + + /* we need to impose a limit on the growth of the hash table so check + * this bucket to make sure it is within the specified bounds + */ + idx = sel_ib_pkey_hashfn(pkey->psec.pkey); + list_add_rcu(&pkey->list, &sel_ib_pkey_hash[idx].list); + if (sel_ib_pkey_hash[idx].size == SEL_PKEY_HASH_BKT_LIMIT) { + struct sel_ib_pkey *tail; + + tail = list_entry( + rcu_dereference_protected( + sel_ib_pkey_hash[idx].list.prev, + lockdep_is_held(&sel_ib_pkey_lock)), + struct sel_ib_pkey, list); + list_del_rcu(&tail->list); + kfree_rcu(tail, rcu); + } else { + sel_ib_pkey_hash[idx].size++; + } +} + +/** + * sel_ib_pkey_sid_slow - Lookup the SID of a pkey using the policy + * @subnet_prefix: subnet prefix + * @pkey_num: pkey number + * @sid: pkey SID + * + * Description: + * This function determines the SID of a pkey by querying the security + * policy. The result is added to the pkey table to speedup future + * queries. Returns zero on success, negative values on failure. + * + */ +static int sel_ib_pkey_sid_slow(u64 subnet_prefix, u16 pkey_num, u32 *sid) +{ + int ret; + struct sel_ib_pkey *pkey; + struct sel_ib_pkey *new = NULL; + unsigned long flags; + + spin_lock_irqsave(&sel_ib_pkey_lock, flags); + pkey = sel_ib_pkey_find(subnet_prefix, pkey_num); + if (pkey) { + *sid = pkey->psec.sid; + spin_unlock_irqrestore(&sel_ib_pkey_lock, flags); + return 0; + } + + ret = security_ib_pkey_sid(&selinux_state, subnet_prefix, pkey_num, + sid); + if (ret) + goto out; + + /* If this memory allocation fails still return 0. The SID + * is valid, it just won't be added to the cache. + */ + new = kzalloc(sizeof(*new), GFP_ATOMIC); + if (!new) { + ret = -ENOMEM; + goto out; + } + + new->psec.subnet_prefix = subnet_prefix; + new->psec.pkey = pkey_num; + new->psec.sid = *sid; + sel_ib_pkey_insert(new); + +out: + spin_unlock_irqrestore(&sel_ib_pkey_lock, flags); + return ret; +} + +/** + * sel_ib_pkey_sid - Lookup the SID of a PKEY + * @subnet_prefix: subnet_prefix + * @pkey_num: pkey number + * @sid: pkey SID + * + * Description: + * This function determines the SID of a PKEY using the fastest method + * possible. First the pkey table is queried, but if an entry can't be found + * then the policy is queried and the result is added to the table to speedup + * future queries. Returns zero on success, negative values on failure. + * + */ +int sel_ib_pkey_sid(u64 subnet_prefix, u16 pkey_num, u32 *sid) +{ + struct sel_ib_pkey *pkey; + + rcu_read_lock(); + pkey = sel_ib_pkey_find(subnet_prefix, pkey_num); + if (pkey) { + *sid = pkey->psec.sid; + rcu_read_unlock(); + return 0; + } + rcu_read_unlock(); + + return sel_ib_pkey_sid_slow(subnet_prefix, pkey_num, sid); +} + +/** + * sel_ib_pkey_flush - Flush the entire pkey table + * + * Description: + * Remove all entries from the pkey table + * + */ +void sel_ib_pkey_flush(void) +{ + unsigned int idx; + struct sel_ib_pkey *pkey, *pkey_tmp; + unsigned long flags; + + spin_lock_irqsave(&sel_ib_pkey_lock, flags); + for (idx = 0; idx < SEL_PKEY_HASH_SIZE; idx++) { + list_for_each_entry_safe(pkey, pkey_tmp, + &sel_ib_pkey_hash[idx].list, list) { + list_del_rcu(&pkey->list); + kfree_rcu(pkey, rcu); + } + sel_ib_pkey_hash[idx].size = 0; + } + spin_unlock_irqrestore(&sel_ib_pkey_lock, flags); +} + +static __init int sel_ib_pkey_init(void) +{ + int iter; + + if (!selinux_enabled_boot) + return 0; + + for (iter = 0; iter < SEL_PKEY_HASH_SIZE; iter++) { + INIT_LIST_HEAD(&sel_ib_pkey_hash[iter].list); + sel_ib_pkey_hash[iter].size = 0; + } + + return 0; +} + +subsys_initcall(sel_ib_pkey_init); diff --git a/security/selinux/include/audit.h b/security/selinux/include/audit.h new file mode 100644 index 000000000..073a3d34a --- /dev/null +++ b/security/selinux/include/audit.h @@ -0,0 +1,57 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * SELinux support for the Audit LSM hooks + * + * Author: James Morris <jmorris@redhat.com> + * + * Copyright (C) 2005 Red Hat, Inc., James Morris <jmorris@redhat.com> + * Copyright (C) 2006 Trusted Computer Solutions, Inc. <dgoeddel@trustedcs.com> + * Copyright (C) 2006 IBM Corporation, Timothy R. Chavez <tinytim@us.ibm.com> + */ + +#ifndef _SELINUX_AUDIT_H +#define _SELINUX_AUDIT_H + +/** + * selinux_audit_rule_init - alloc/init an selinux audit rule structure. + * @field: the field this rule refers to + * @op: the operater the rule uses + * @rulestr: the text "target" of the rule + * @rule: pointer to the new rule structure returned via this + * + * Returns 0 if successful, -errno if not. On success, the rule structure + * will be allocated internally. The caller must free this structure with + * selinux_audit_rule_free() after use. + */ +int selinux_audit_rule_init(u32 field, u32 op, char *rulestr, void **rule); + +/** + * selinux_audit_rule_free - free an selinux audit rule structure. + * @rule: pointer to the audit rule to be freed + * + * This will free all memory associated with the given rule. + * If @rule is NULL, no operation is performed. + */ +void selinux_audit_rule_free(void *rule); + +/** + * selinux_audit_rule_match - determine if a context ID matches a rule. + * @sid: the context ID to check + * @field: the field this rule refers to + * @op: the operater the rule uses + * @rule: pointer to the audit rule to check against + * + * Returns 1 if the context id matches the rule, 0 if it does not, and + * -errno on failure. + */ +int selinux_audit_rule_match(u32 sid, u32 field, u32 op, void *rule); + +/** + * selinux_audit_rule_known - check to see if rule contains selinux fields. + * @rule: rule to be checked + * Returns 1 if there are selinux fields specified in the rule, 0 otherwise. + */ +int selinux_audit_rule_known(struct audit_krule *krule); + +#endif /* _SELINUX_AUDIT_H */ + diff --git a/security/selinux/include/avc.h b/security/selinux/include/avc.h new file mode 100644 index 000000000..cf4cc3ef9 --- /dev/null +++ b/security/selinux/include/avc.h @@ -0,0 +1,198 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Access vector cache interface for object managers. + * + * Author : Stephen Smalley, <sds@tycho.nsa.gov> + */ +#ifndef _SELINUX_AVC_H_ +#define _SELINUX_AVC_H_ + +#include <linux/stddef.h> +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/kdev_t.h> +#include <linux/spinlock.h> +#include <linux/init.h> +#include <linux/audit.h> +#include <linux/lsm_audit.h> +#include <linux/in6.h> +#include "flask.h" +#include "av_permissions.h" +#include "security.h" + +/* + * An entry in the AVC. + */ +struct avc_entry; + +struct task_struct; +struct inode; +struct sock; +struct sk_buff; + +/* + * AVC statistics + */ +struct avc_cache_stats { + unsigned int lookups; + unsigned int misses; + unsigned int allocations; + unsigned int reclaims; + unsigned int frees; +}; + +/* + * We only need this data after we have decided to send an audit message. + */ +struct selinux_audit_data { + u32 ssid; + u32 tsid; + u16 tclass; + u32 requested; + u32 audited; + u32 denied; + int result; + struct selinux_state *state; +}; + +/* + * AVC operations + */ + +void __init avc_init(void); + +static inline u32 avc_audit_required(u32 requested, + struct av_decision *avd, + int result, + u32 auditdeny, + u32 *deniedp) +{ + u32 denied, audited; + denied = requested & ~avd->allowed; + if (unlikely(denied)) { + audited = denied & avd->auditdeny; + /* + * auditdeny is TRICKY! Setting a bit in + * this field means that ANY denials should NOT be audited if + * the policy contains an explicit dontaudit rule for that + * permission. Take notice that this is unrelated to the + * actual permissions that were denied. As an example lets + * assume: + * + * denied == READ + * avd.auditdeny & ACCESS == 0 (not set means explicit rule) + * auditdeny & ACCESS == 1 + * + * We will NOT audit the denial even though the denied + * permission was READ and the auditdeny checks were for + * ACCESS + */ + if (auditdeny && !(auditdeny & avd->auditdeny)) + audited = 0; + } else if (result) + audited = denied = requested; + else + audited = requested & avd->auditallow; + *deniedp = denied; + return audited; +} + +int slow_avc_audit(struct selinux_state *state, + u32 ssid, u32 tsid, u16 tclass, + u32 requested, u32 audited, u32 denied, int result, + struct common_audit_data *a); + +/** + * avc_audit - Audit the granting or denial of permissions. + * @ssid: source security identifier + * @tsid: target security identifier + * @tclass: target security class + * @requested: requested permissions + * @avd: access vector decisions + * @result: result from avc_has_perm_noaudit + * @a: auxiliary audit data + * @flags: VFS walk flags + * + * Audit the granting or denial of permissions in accordance + * with the policy. This function is typically called by + * avc_has_perm() after a permission check, but can also be + * called directly by callers who use avc_has_perm_noaudit() + * in order to separate the permission check from the auditing. + * For example, this separation is useful when the permission check must + * be performed under a lock, to allow the lock to be released + * before calling the auditing code. + */ +static inline int avc_audit(struct selinux_state *state, + u32 ssid, u32 tsid, + u16 tclass, u32 requested, + struct av_decision *avd, + int result, + struct common_audit_data *a, + int flags) +{ + u32 audited, denied; + audited = avc_audit_required(requested, avd, result, 0, &denied); + if (likely(!audited)) + return 0; + /* fall back to ref-walk if we have to generate audit */ + if (flags & MAY_NOT_BLOCK) + return -ECHILD; + return slow_avc_audit(state, ssid, tsid, tclass, + requested, audited, denied, result, + a); +} + +#define AVC_STRICT 1 /* Ignore permissive mode. */ +#define AVC_EXTENDED_PERMS 2 /* update extended permissions */ +#define AVC_NONBLOCKING 4 /* non blocking */ +int avc_has_perm_noaudit(struct selinux_state *state, + u32 ssid, u32 tsid, + u16 tclass, u32 requested, + unsigned flags, + struct av_decision *avd); + +int avc_has_perm(struct selinux_state *state, + u32 ssid, u32 tsid, + u16 tclass, u32 requested, + struct common_audit_data *auditdata); +int avc_has_perm_flags(struct selinux_state *state, + u32 ssid, u32 tsid, + u16 tclass, u32 requested, + struct common_audit_data *auditdata, + int flags); + +int avc_has_extended_perms(struct selinux_state *state, + u32 ssid, u32 tsid, u16 tclass, u32 requested, + u8 driver, u8 perm, struct common_audit_data *ad); + + +u32 avc_policy_seqno(struct selinux_state *state); + +#define AVC_CALLBACK_GRANT 1 +#define AVC_CALLBACK_TRY_REVOKE 2 +#define AVC_CALLBACK_REVOKE 4 +#define AVC_CALLBACK_RESET 8 +#define AVC_CALLBACK_AUDITALLOW_ENABLE 16 +#define AVC_CALLBACK_AUDITALLOW_DISABLE 32 +#define AVC_CALLBACK_AUDITDENY_ENABLE 64 +#define AVC_CALLBACK_AUDITDENY_DISABLE 128 +#define AVC_CALLBACK_ADD_XPERMS 256 + +int avc_add_callback(int (*callback)(u32 event), u32 events); + +/* Exported to selinuxfs */ +struct selinux_avc; +int avc_get_hash_stats(struct selinux_avc *avc, char *page); +unsigned int avc_get_cache_threshold(struct selinux_avc *avc); +void avc_set_cache_threshold(struct selinux_avc *avc, + unsigned int cache_threshold); + +/* Attempt to free avc node cache */ +void avc_disable(void); + +#ifdef CONFIG_SECURITY_SELINUX_AVC_STATS +DECLARE_PER_CPU(struct avc_cache_stats, avc_cache_stats); +#endif + +#endif /* _SELINUX_AVC_H_ */ + diff --git a/security/selinux/include/avc_ss.h b/security/selinux/include/avc_ss.h new file mode 100644 index 000000000..88c384c5c --- /dev/null +++ b/security/selinux/include/avc_ss.h @@ -0,0 +1,24 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Access vector cache interface for the security server. + * + * Author : Stephen Smalley, <sds@tycho.nsa.gov> + */ +#ifndef _SELINUX_AVC_SS_H_ +#define _SELINUX_AVC_SS_H_ + +#include "flask.h" + +struct selinux_avc; +int avc_ss_reset(struct selinux_avc *avc, u32 seqno); + +/* Class/perm mapping support */ +struct security_class_mapping { + const char *name; + const char *perms[sizeof(u32) * 8 + 1]; +}; + +extern struct security_class_mapping secclass_map[]; + +#endif /* _SELINUX_AVC_SS_H_ */ + diff --git a/security/selinux/include/classmap.h b/security/selinux/include/classmap.h new file mode 100644 index 000000000..b9fdba2ff --- /dev/null +++ b/security/selinux/include/classmap.h @@ -0,0 +1,258 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#include <linux/capability.h> +#include <linux/socket.h> + +#define COMMON_FILE_SOCK_PERMS "ioctl", "read", "write", "create", \ + "getattr", "setattr", "lock", "relabelfrom", "relabelto", "append", "map" + +#define COMMON_FILE_PERMS COMMON_FILE_SOCK_PERMS, "unlink", "link", \ + "rename", "execute", "quotaon", "mounton", "audit_access", \ + "open", "execmod", "watch", "watch_mount", "watch_sb", \ + "watch_with_perm", "watch_reads" + +#define COMMON_SOCK_PERMS COMMON_FILE_SOCK_PERMS, "bind", "connect", \ + "listen", "accept", "getopt", "setopt", "shutdown", "recvfrom", \ + "sendto", "name_bind" + +#define COMMON_IPC_PERMS "create", "destroy", "getattr", "setattr", "read", \ + "write", "associate", "unix_read", "unix_write" + +#define COMMON_CAP_PERMS "chown", "dac_override", "dac_read_search", \ + "fowner", "fsetid", "kill", "setgid", "setuid", "setpcap", \ + "linux_immutable", "net_bind_service", "net_broadcast", \ + "net_admin", "net_raw", "ipc_lock", "ipc_owner", "sys_module", \ + "sys_rawio", "sys_chroot", "sys_ptrace", "sys_pacct", "sys_admin", \ + "sys_boot", "sys_nice", "sys_resource", "sys_time", \ + "sys_tty_config", "mknod", "lease", "audit_write", \ + "audit_control", "setfcap" + +#define COMMON_CAP2_PERMS "mac_override", "mac_admin", "syslog", \ + "wake_alarm", "block_suspend", "audit_read", "perfmon", "bpf", \ + "checkpoint_restore" + +#if CAP_LAST_CAP > CAP_CHECKPOINT_RESTORE +#error New capability defined, please update COMMON_CAP2_PERMS. +#endif + +/* + * Note: The name for any socket class should be suffixed by "socket", + * and doesn't contain more than one substr of "socket". + */ +struct security_class_mapping secclass_map[] = { + { "security", + { "compute_av", "compute_create", "compute_member", + "check_context", "load_policy", "compute_relabel", + "compute_user", "setenforce", "setbool", "setsecparam", + "setcheckreqprot", "read_policy", "validate_trans", NULL } }, + { "process", + { "fork", "transition", "sigchld", "sigkill", + "sigstop", "signull", "signal", "ptrace", "getsched", "setsched", + "getsession", "getpgid", "setpgid", "getcap", "setcap", "share", + "getattr", "setexec", "setfscreate", "noatsecure", "siginh", + "setrlimit", "rlimitinh", "dyntransition", "setcurrent", + "execmem", "execstack", "execheap", "setkeycreate", + "setsockcreate", "getrlimit", NULL } }, + { "process2", + { "nnp_transition", "nosuid_transition", NULL } }, + { "system", + { "ipc_info", "syslog_read", "syslog_mod", + "syslog_console", "module_request", "module_load", NULL } }, + { "capability", + { COMMON_CAP_PERMS, NULL } }, + { "filesystem", + { "mount", "remount", "unmount", "getattr", + "relabelfrom", "relabelto", "associate", "quotamod", + "quotaget", "watch", NULL } }, + { "file", + { COMMON_FILE_PERMS, + "execute_no_trans", "entrypoint", NULL } }, + { "dir", + { COMMON_FILE_PERMS, "add_name", "remove_name", + "reparent", "search", "rmdir", NULL } }, + { "fd", { "use", NULL } }, + { "lnk_file", + { COMMON_FILE_PERMS, NULL } }, + { "chr_file", + { COMMON_FILE_PERMS, NULL } }, + { "blk_file", + { COMMON_FILE_PERMS, NULL } }, + { "sock_file", + { COMMON_FILE_PERMS, NULL } }, + { "fifo_file", + { COMMON_FILE_PERMS, NULL } }, + { "socket", + { COMMON_SOCK_PERMS, NULL } }, + { "tcp_socket", + { COMMON_SOCK_PERMS, + "node_bind", "name_connect", + NULL } }, + { "udp_socket", + { COMMON_SOCK_PERMS, + "node_bind", NULL } }, + { "rawip_socket", + { COMMON_SOCK_PERMS, + "node_bind", NULL } }, + { "node", + { "recvfrom", "sendto", NULL } }, + { "netif", + { "ingress", "egress", NULL } }, + { "netlink_socket", + { COMMON_SOCK_PERMS, NULL } }, + { "packet_socket", + { COMMON_SOCK_PERMS, NULL } }, + { "key_socket", + { COMMON_SOCK_PERMS, NULL } }, + { "unix_stream_socket", + { COMMON_SOCK_PERMS, "connectto", NULL } }, + { "unix_dgram_socket", + { COMMON_SOCK_PERMS, NULL } }, + { "sem", + { COMMON_IPC_PERMS, NULL } }, + { "msg", { "send", "receive", NULL } }, + { "msgq", + { COMMON_IPC_PERMS, "enqueue", NULL } }, + { "shm", + { COMMON_IPC_PERMS, "lock", NULL } }, + { "ipc", + { COMMON_IPC_PERMS, NULL } }, + { "netlink_route_socket", + { COMMON_SOCK_PERMS, + "nlmsg_read", "nlmsg_write", NULL } }, + { "netlink_tcpdiag_socket", + { COMMON_SOCK_PERMS, + "nlmsg_read", "nlmsg_write", NULL } }, + { "netlink_nflog_socket", + { COMMON_SOCK_PERMS, NULL } }, + { "netlink_xfrm_socket", + { COMMON_SOCK_PERMS, + "nlmsg_read", "nlmsg_write", NULL } }, + { "netlink_selinux_socket", + { COMMON_SOCK_PERMS, NULL } }, + { "netlink_iscsi_socket", + { COMMON_SOCK_PERMS, NULL } }, + { "netlink_audit_socket", + { COMMON_SOCK_PERMS, + "nlmsg_read", "nlmsg_write", "nlmsg_relay", "nlmsg_readpriv", + "nlmsg_tty_audit", NULL } }, + { "netlink_fib_lookup_socket", + { COMMON_SOCK_PERMS, NULL } }, + { "netlink_connector_socket", + { COMMON_SOCK_PERMS, NULL } }, + { "netlink_netfilter_socket", + { COMMON_SOCK_PERMS, NULL } }, + { "netlink_dnrt_socket", + { COMMON_SOCK_PERMS, NULL } }, + { "association", + { "sendto", "recvfrom", "setcontext", "polmatch", NULL } }, + { "netlink_kobject_uevent_socket", + { COMMON_SOCK_PERMS, NULL } }, + { "netlink_generic_socket", + { COMMON_SOCK_PERMS, NULL } }, + { "netlink_scsitransport_socket", + { COMMON_SOCK_PERMS, NULL } }, + { "netlink_rdma_socket", + { COMMON_SOCK_PERMS, NULL } }, + { "netlink_crypto_socket", + { COMMON_SOCK_PERMS, NULL } }, + { "appletalk_socket", + { COMMON_SOCK_PERMS, NULL } }, + { "packet", + { "send", "recv", "relabelto", "forward_in", "forward_out", NULL } }, + { "key", + { "view", "read", "write", "search", "link", "setattr", "create", + NULL } }, + { "dccp_socket", + { COMMON_SOCK_PERMS, + "node_bind", "name_connect", NULL } }, + { "memprotect", { "mmap_zero", NULL } }, + { "peer", { "recv", NULL } }, + { "capability2", + { COMMON_CAP2_PERMS, NULL } }, + { "kernel_service", { "use_as_override", "create_files_as", NULL } }, + { "tun_socket", + { COMMON_SOCK_PERMS, "attach_queue", NULL } }, + { "binder", { "impersonate", "call", "set_context_mgr", "transfer", + NULL } }, + { "cap_userns", + { COMMON_CAP_PERMS, NULL } }, + { "cap2_userns", + { COMMON_CAP2_PERMS, NULL } }, + { "sctp_socket", + { COMMON_SOCK_PERMS, + "node_bind", "name_connect", "association", NULL } }, + { "icmp_socket", + { COMMON_SOCK_PERMS, + "node_bind", NULL } }, + { "ax25_socket", + { COMMON_SOCK_PERMS, NULL } }, + { "ipx_socket", + { COMMON_SOCK_PERMS, NULL } }, + { "netrom_socket", + { COMMON_SOCK_PERMS, NULL } }, + { "atmpvc_socket", + { COMMON_SOCK_PERMS, NULL } }, + { "x25_socket", + { COMMON_SOCK_PERMS, NULL } }, + { "rose_socket", + { COMMON_SOCK_PERMS, NULL } }, + { "decnet_socket", + { COMMON_SOCK_PERMS, NULL } }, + { "atmsvc_socket", + { COMMON_SOCK_PERMS, NULL } }, + { "rds_socket", + { COMMON_SOCK_PERMS, NULL } }, + { "irda_socket", + { COMMON_SOCK_PERMS, NULL } }, + { "pppox_socket", + { COMMON_SOCK_PERMS, NULL } }, + { "llc_socket", + { COMMON_SOCK_PERMS, NULL } }, + { "can_socket", + { COMMON_SOCK_PERMS, NULL } }, + { "tipc_socket", + { COMMON_SOCK_PERMS, NULL } }, + { "bluetooth_socket", + { COMMON_SOCK_PERMS, NULL } }, + { "iucv_socket", + { COMMON_SOCK_PERMS, NULL } }, + { "rxrpc_socket", + { COMMON_SOCK_PERMS, NULL } }, + { "isdn_socket", + { COMMON_SOCK_PERMS, NULL } }, + { "phonet_socket", + { COMMON_SOCK_PERMS, NULL } }, + { "ieee802154_socket", + { COMMON_SOCK_PERMS, NULL } }, + { "caif_socket", + { COMMON_SOCK_PERMS, NULL } }, + { "alg_socket", + { COMMON_SOCK_PERMS, NULL } }, + { "nfc_socket", + { COMMON_SOCK_PERMS, NULL } }, + { "vsock_socket", + { COMMON_SOCK_PERMS, NULL } }, + { "kcm_socket", + { COMMON_SOCK_PERMS, NULL } }, + { "qipcrtr_socket", + { COMMON_SOCK_PERMS, NULL } }, + { "smc_socket", + { COMMON_SOCK_PERMS, NULL } }, + { "infiniband_pkey", + { "access", NULL } }, + { "infiniband_endport", + { "manage_subnet", NULL } }, + { "bpf", + { "map_create", "map_read", "map_write", "prog_load", "prog_run", + NULL } }, + { "xdp_socket", + { COMMON_SOCK_PERMS, NULL } }, + { "perf_event", + { "open", "cpu", "kernel", "tracepoint", "read", "write", NULL } }, + { "lockdown", + { "integrity", "confidentiality", NULL } }, + { NULL } + }; + +#if PF_MAX > 45 +#error New address family defined, please update secclass_map. +#endif diff --git a/security/selinux/include/conditional.h b/security/selinux/include/conditional.h new file mode 100644 index 000000000..b09343346 --- /dev/null +++ b/security/selinux/include/conditional.h @@ -0,0 +1,23 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Interface to booleans in the security server. This is exported + * for the selinuxfs. + * + * Author: Karl MacMillan <kmacmillan@tresys.com> + * + * Copyright (C) 2003 - 2004 Tresys Technology, LLC + */ + +#ifndef _SELINUX_CONDITIONAL_H_ +#define _SELINUX_CONDITIONAL_H_ + +#include "security.h" + +int security_get_bools(struct selinux_policy *policy, + u32 *len, char ***names, int **values); + +int security_set_bools(struct selinux_state *state, u32 len, int *values); + +int security_get_bool_value(struct selinux_state *state, u32 index); + +#endif diff --git a/security/selinux/include/ibpkey.h b/security/selinux/include/ibpkey.h new file mode 100644 index 000000000..e6ac1d233 --- /dev/null +++ b/security/selinux/include/ibpkey.h @@ -0,0 +1,32 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * pkey table + * + * SELinux must keep a mapping of pkeys to labels/SIDs. This + * mapping is maintained as part of the normal policy but a fast cache is + * needed to reduce the lookup overhead. + */ + +/* + * (c) Mellanox Technologies, 2016 + */ + +#ifndef _SELINUX_IB_PKEY_H +#define _SELINUX_IB_PKEY_H + +#ifdef CONFIG_SECURITY_INFINIBAND +void sel_ib_pkey_flush(void); +int sel_ib_pkey_sid(u64 subnet_prefix, u16 pkey, u32 *sid); +#else +static inline void sel_ib_pkey_flush(void) +{ + return; +} +static inline int sel_ib_pkey_sid(u64 subnet_prefix, u16 pkey, u32 *sid) +{ + *sid = SECINITSID_UNLABELED; + return 0; +} +#endif + +#endif diff --git a/security/selinux/include/initial_sid_to_string.h b/security/selinux/include/initial_sid_to_string.h new file mode 100644 index 000000000..5d332aeb8 --- /dev/null +++ b/security/selinux/include/initial_sid_to_string.h @@ -0,0 +1,33 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +static const char *initial_sid_to_string[] = +{ + NULL, + "kernel", + "security", + "unlabeled", + NULL, + "file", + NULL, + NULL, + "any_socket", + "port", + "netif", + "netmsg", + "node", + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + "devnull", +}; + diff --git a/security/selinux/include/netif.h b/security/selinux/include/netif.h new file mode 100644 index 000000000..85ec30d11 --- /dev/null +++ b/security/selinux/include/netif.h @@ -0,0 +1,24 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Network interface table. + * + * Network interfaces (devices) do not have a security field, so we + * maintain a table associating each interface with a SID. + * + * Author: James Morris <jmorris@redhat.com> + * + * Copyright (C) 2003 Red Hat, Inc., James Morris <jmorris@redhat.com> + * Copyright (C) 2007 Hewlett-Packard Development Company, L.P. + * Paul Moore <paul@paul-moore.com> + */ +#ifndef _SELINUX_NETIF_H_ +#define _SELINUX_NETIF_H_ + +#include <net/net_namespace.h> + +void sel_netif_flush(void); + +int sel_netif_sid(struct net *ns, int ifindex, u32 *sid); + +#endif /* _SELINUX_NETIF_H_ */ + diff --git a/security/selinux/include/netlabel.h b/security/selinux/include/netlabel.h new file mode 100644 index 000000000..0c58f62dc --- /dev/null +++ b/security/selinux/include/netlabel.h @@ -0,0 +1,150 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * SELinux interface to the NetLabel subsystem + * + * Author: Paul Moore <paul@paul-moore.com> + */ + +/* + * (c) Copyright Hewlett-Packard Development Company, L.P., 2006 + */ + +#ifndef _SELINUX_NETLABEL_H_ +#define _SELINUX_NETLABEL_H_ + +#include <linux/types.h> +#include <linux/fs.h> +#include <linux/net.h> +#include <linux/skbuff.h> +#include <net/sock.h> +#include <net/request_sock.h> +#include <net/sctp/structs.h> + +#include "avc.h" +#include "objsec.h" + +#ifdef CONFIG_NETLABEL +void selinux_netlbl_cache_invalidate(void); + +void selinux_netlbl_err(struct sk_buff *skb, u16 family, int error, + int gateway); + +void selinux_netlbl_sk_security_free(struct sk_security_struct *sksec); +void selinux_netlbl_sk_security_reset(struct sk_security_struct *sksec); + +int selinux_netlbl_skbuff_getsid(struct sk_buff *skb, + u16 family, + u32 *type, + u32 *sid); +int selinux_netlbl_skbuff_setsid(struct sk_buff *skb, + u16 family, + u32 sid); +int selinux_netlbl_sctp_assoc_request(struct sctp_endpoint *ep, + struct sk_buff *skb); +int selinux_netlbl_inet_conn_request(struct request_sock *req, u16 family); +void selinux_netlbl_inet_csk_clone(struct sock *sk, u16 family); +void selinux_netlbl_sctp_sk_clone(struct sock *sk, struct sock *newsk); +int selinux_netlbl_socket_post_create(struct sock *sk, u16 family); +int selinux_netlbl_sock_rcv_skb(struct sk_security_struct *sksec, + struct sk_buff *skb, + u16 family, + struct common_audit_data *ad); +int selinux_netlbl_socket_setsockopt(struct socket *sock, + int level, + int optname); +int selinux_netlbl_socket_connect(struct sock *sk, struct sockaddr *addr); +int selinux_netlbl_socket_connect_locked(struct sock *sk, + struct sockaddr *addr); + +#else +static inline void selinux_netlbl_cache_invalidate(void) +{ + return; +} + +static inline void selinux_netlbl_err(struct sk_buff *skb, + u16 family, + int error, + int gateway) +{ + return; +} + +static inline void selinux_netlbl_sk_security_free( + struct sk_security_struct *sksec) +{ + return; +} + +static inline void selinux_netlbl_sk_security_reset( + struct sk_security_struct *sksec) +{ + return; +} + +static inline int selinux_netlbl_skbuff_getsid(struct sk_buff *skb, + u16 family, + u32 *type, + u32 *sid) +{ + *type = NETLBL_NLTYPE_NONE; + *sid = SECSID_NULL; + return 0; +} +static inline int selinux_netlbl_skbuff_setsid(struct sk_buff *skb, + u16 family, + u32 sid) +{ + return 0; +} + +static inline int selinux_netlbl_sctp_assoc_request(struct sctp_endpoint *ep, + struct sk_buff *skb) +{ + return 0; +} +static inline int selinux_netlbl_inet_conn_request(struct request_sock *req, + u16 family) +{ + return 0; +} +static inline void selinux_netlbl_inet_csk_clone(struct sock *sk, u16 family) +{ + return; +} +static inline void selinux_netlbl_sctp_sk_clone(struct sock *sk, + struct sock *newsk) +{ + return; +} +static inline int selinux_netlbl_socket_post_create(struct sock *sk, + u16 family) +{ + return 0; +} +static inline int selinux_netlbl_sock_rcv_skb(struct sk_security_struct *sksec, + struct sk_buff *skb, + u16 family, + struct common_audit_data *ad) +{ + return 0; +} +static inline int selinux_netlbl_socket_setsockopt(struct socket *sock, + int level, + int optname) +{ + return 0; +} +static inline int selinux_netlbl_socket_connect(struct sock *sk, + struct sockaddr *addr) +{ + return 0; +} +static inline int selinux_netlbl_socket_connect_locked(struct sock *sk, + struct sockaddr *addr) +{ + return 0; +} +#endif /* CONFIG_NETLABEL */ + +#endif diff --git a/security/selinux/include/netnode.h b/security/selinux/include/netnode.h new file mode 100644 index 000000000..e3f784a85 --- /dev/null +++ b/security/selinux/include/netnode.h @@ -0,0 +1,24 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Network node table + * + * SELinux must keep a mapping of network nodes to labels/SIDs. This + * mapping is maintained as part of the normal policy but a fast cache is + * needed to reduce the lookup overhead since most of these queries happen on + * a per-packet basis. + * + * Author: Paul Moore <paul@paul-moore.com> + */ + +/* + * (c) Copyright Hewlett-Packard Development Company, L.P., 2007 + */ + +#ifndef _SELINUX_NETNODE_H +#define _SELINUX_NETNODE_H + +void sel_netnode_flush(void); + +int sel_netnode_sid(void *addr, u16 family, u32 *sid); + +#endif diff --git a/security/selinux/include/netport.h b/security/selinux/include/netport.h new file mode 100644 index 000000000..31bc16e29 --- /dev/null +++ b/security/selinux/include/netport.h @@ -0,0 +1,23 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Network port table + * + * SELinux must keep a mapping of network ports to labels/SIDs. This + * mapping is maintained as part of the normal policy but a fast cache is + * needed to reduce the lookup overhead. + * + * Author: Paul Moore <paul@paul-moore.com> + */ + +/* + * (c) Copyright Hewlett-Packard Development Company, L.P., 2008 + */ + +#ifndef _SELINUX_NETPORT_H +#define _SELINUX_NETPORT_H + +void sel_netport_flush(void); + +int sel_netport_sid(u8 protocol, u16 pnum, u32 *sid); + +#endif diff --git a/security/selinux/include/objsec.h b/security/selinux/include/objsec.h new file mode 100644 index 000000000..330b7b6d4 --- /dev/null +++ b/security/selinux/include/objsec.h @@ -0,0 +1,192 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * NSA Security-Enhanced Linux (SELinux) security module + * + * This file contains the SELinux security data structures for kernel objects. + * + * Author(s): Stephen Smalley, <sds@tycho.nsa.gov> + * Chris Vance, <cvance@nai.com> + * Wayne Salamon, <wsalamon@nai.com> + * James Morris <jmorris@redhat.com> + * + * Copyright (C) 2001,2002 Networks Associates Technology, Inc. + * Copyright (C) 2003 Red Hat, Inc., James Morris <jmorris@redhat.com> + * Copyright (C) 2016 Mellanox Technologies + */ +#ifndef _SELINUX_OBJSEC_H_ +#define _SELINUX_OBJSEC_H_ + +#include <linux/list.h> +#include <linux/sched.h> +#include <linux/fs.h> +#include <linux/binfmts.h> +#include <linux/in.h> +#include <linux/spinlock.h> +#include <linux/lsm_hooks.h> +#include <linux/msg.h> +#include <net/net_namespace.h> +#include "flask.h" +#include "avc.h" + +struct task_security_struct { + u32 osid; /* SID prior to last execve */ + u32 sid; /* current SID */ + u32 exec_sid; /* exec SID */ + u32 create_sid; /* fscreate SID */ + u32 keycreate_sid; /* keycreate SID */ + u32 sockcreate_sid; /* fscreate SID */ +} __randomize_layout; + +enum label_initialized { + LABEL_INVALID, /* invalid or not initialized */ + LABEL_INITIALIZED, /* initialized */ + LABEL_PENDING +}; + +struct inode_security_struct { + struct inode *inode; /* back pointer to inode object */ + struct list_head list; /* list of inode_security_struct */ + u32 task_sid; /* SID of creating task */ + u32 sid; /* SID of this object */ + u16 sclass; /* security class of this object */ + unsigned char initialized; /* initialization flag */ + spinlock_t lock; +}; + +struct file_security_struct { + u32 sid; /* SID of open file description */ + u32 fown_sid; /* SID of file owner (for SIGIO) */ + u32 isid; /* SID of inode at the time of file open */ + u32 pseqno; /* Policy seqno at the time of file open */ +}; + +struct superblock_security_struct { + struct super_block *sb; /* back pointer to sb object */ + u32 sid; /* SID of file system superblock */ + u32 def_sid; /* default SID for labeling */ + u32 mntpoint_sid; /* SECURITY_FS_USE_MNTPOINT context for files */ + unsigned short behavior; /* labeling behavior */ + unsigned short flags; /* which mount options were specified */ + struct mutex lock; + struct list_head isec_head; + spinlock_t isec_lock; +}; + +struct msg_security_struct { + u32 sid; /* SID of message */ +}; + +struct ipc_security_struct { + u16 sclass; /* security class of this object */ + u32 sid; /* SID of IPC resource */ +}; + +struct netif_security_struct { + struct net *ns; /* network namespace */ + int ifindex; /* device index */ + u32 sid; /* SID for this interface */ +}; + +struct netnode_security_struct { + union { + __be32 ipv4; /* IPv4 node address */ + struct in6_addr ipv6; /* IPv6 node address */ + } addr; + u32 sid; /* SID for this node */ + u16 family; /* address family */ +}; + +struct netport_security_struct { + u32 sid; /* SID for this node */ + u16 port; /* port number */ + u8 protocol; /* transport protocol */ +}; + +struct sk_security_struct { +#ifdef CONFIG_NETLABEL + enum { /* NetLabel state */ + NLBL_UNSET = 0, + NLBL_REQUIRE, + NLBL_LABELED, + NLBL_REQSKB, + NLBL_CONNLABELED, + } nlbl_state; + struct netlbl_lsm_secattr *nlbl_secattr; /* NetLabel sec attributes */ +#endif + u32 sid; /* SID of this object */ + u32 peer_sid; /* SID of peer */ + u16 sclass; /* sock security class */ + enum { /* SCTP association state */ + SCTP_ASSOC_UNSET = 0, + SCTP_ASSOC_SET, + } sctp_assoc_state; +}; + +struct tun_security_struct { + u32 sid; /* SID for the tun device sockets */ +}; + +struct key_security_struct { + u32 sid; /* SID of key */ +}; + +struct ib_security_struct { + u32 sid; /* SID of the queue pair or MAD agent */ +}; + +struct pkey_security_struct { + u64 subnet_prefix; /* Port subnet prefix */ + u16 pkey; /* PKey number */ + u32 sid; /* SID of pkey */ +}; + +struct bpf_security_struct { + u32 sid; /* SID of bpf obj creator */ +}; + +struct perf_event_security_struct { + u32 sid; /* SID of perf_event obj creator */ +}; + +extern struct lsm_blob_sizes selinux_blob_sizes; +static inline struct task_security_struct *selinux_cred(const struct cred *cred) +{ + return cred->security + selinux_blob_sizes.lbs_cred; +} + +static inline struct file_security_struct *selinux_file(const struct file *file) +{ + return file->f_security + selinux_blob_sizes.lbs_file; +} + +static inline struct inode_security_struct *selinux_inode( + const struct inode *inode) +{ + if (unlikely(!inode->i_security)) + return NULL; + return inode->i_security + selinux_blob_sizes.lbs_inode; +} + +static inline struct msg_security_struct *selinux_msg_msg( + const struct msg_msg *msg_msg) +{ + return msg_msg->security + selinux_blob_sizes.lbs_msg_msg; +} + +static inline struct ipc_security_struct *selinux_ipc( + const struct kern_ipc_perm *ipc) +{ + return ipc->security + selinux_blob_sizes.lbs_ipc; +} + +/* + * get the subjective security ID of the current task + */ +static inline u32 current_sid(void) +{ + const struct task_security_struct *tsec = selinux_cred(current_cred()); + + return tsec->sid; +} + +#endif /* _SELINUX_OBJSEC_H_ */ diff --git a/security/selinux/include/policycap.h b/security/selinux/include/policycap.h new file mode 100644 index 000000000..a9e572ca4 --- /dev/null +++ b/security/selinux/include/policycap.h @@ -0,0 +1,21 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _SELINUX_POLICYCAP_H_ +#define _SELINUX_POLICYCAP_H_ + +/* Policy capabilities */ +enum { + POLICYDB_CAPABILITY_NETPEER, + POLICYDB_CAPABILITY_OPENPERM, + POLICYDB_CAPABILITY_EXTSOCKCLASS, + POLICYDB_CAPABILITY_ALWAYSNETWORK, + POLICYDB_CAPABILITY_CGROUPSECLABEL, + POLICYDB_CAPABILITY_NNP_NOSUID_TRANSITION, + POLICYDB_CAPABILITY_GENFS_SECLABEL_SYMLINKS, + POLICYDB_CAPABILITY_IOCTL_SKIP_CLOEXEC, + __POLICYDB_CAPABILITY_MAX +}; +#define POLICYDB_CAPABILITY_MAX (__POLICYDB_CAPABILITY_MAX - 1) + +extern const char *selinux_policycap_names[__POLICYDB_CAPABILITY_MAX]; + +#endif /* _SELINUX_POLICYCAP_H_ */ diff --git a/security/selinux/include/policycap_names.h b/security/selinux/include/policycap_names.h new file mode 100644 index 000000000..ebd64afe1 --- /dev/null +++ b/security/selinux/include/policycap_names.h @@ -0,0 +1,19 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _SELINUX_POLICYCAP_NAMES_H_ +#define _SELINUX_POLICYCAP_NAMES_H_ + +#include "policycap.h" + +/* Policy capability names */ +const char *selinux_policycap_names[__POLICYDB_CAPABILITY_MAX] = { + "network_peer_controls", + "open_perms", + "extended_socket_class", + "always_check_network", + "cgroup_seclabel", + "nnp_nosuid_transition", + "genfs_seclabel_symlinks", + "ioctl_skip_cloexec" +}; + +#endif /* _SELINUX_POLICYCAP_NAMES_H_ */ diff --git a/security/selinux/include/security.h b/security/selinux/include/security.h new file mode 100644 index 000000000..1521460a9 --- /dev/null +++ b/security/selinux/include/security.h @@ -0,0 +1,463 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Security server interface. + * + * Author : Stephen Smalley, <sds@tycho.nsa.gov> + * + */ + +#ifndef _SELINUX_SECURITY_H_ +#define _SELINUX_SECURITY_H_ + +#include <linux/compiler.h> +#include <linux/dcache.h> +#include <linux/magic.h> +#include <linux/types.h> +#include <linux/rcupdate.h> +#include <linux/refcount.h> +#include <linux/workqueue.h> +#include "flask.h" +#include "policycap.h" + +#define SECSID_NULL 0x00000000 /* unspecified SID */ +#define SECSID_WILD 0xffffffff /* wildcard SID */ +#define SECCLASS_NULL 0x0000 /* no class */ + +/* Identify specific policy version changes */ +#define POLICYDB_VERSION_BASE 15 +#define POLICYDB_VERSION_BOOL 16 +#define POLICYDB_VERSION_IPV6 17 +#define POLICYDB_VERSION_NLCLASS 18 +#define POLICYDB_VERSION_VALIDATETRANS 19 +#define POLICYDB_VERSION_MLS 19 +#define POLICYDB_VERSION_AVTAB 20 +#define POLICYDB_VERSION_RANGETRANS 21 +#define POLICYDB_VERSION_POLCAP 22 +#define POLICYDB_VERSION_PERMISSIVE 23 +#define POLICYDB_VERSION_BOUNDARY 24 +#define POLICYDB_VERSION_FILENAME_TRANS 25 +#define POLICYDB_VERSION_ROLETRANS 26 +#define POLICYDB_VERSION_NEW_OBJECT_DEFAULTS 27 +#define POLICYDB_VERSION_DEFAULT_TYPE 28 +#define POLICYDB_VERSION_CONSTRAINT_NAMES 29 +#define POLICYDB_VERSION_XPERMS_IOCTL 30 +#define POLICYDB_VERSION_INFINIBAND 31 +#define POLICYDB_VERSION_GLBLUB 32 +#define POLICYDB_VERSION_COMP_FTRANS 33 /* compressed filename transitions */ + +/* Range of policy versions we understand*/ +#define POLICYDB_VERSION_MIN POLICYDB_VERSION_BASE +#define POLICYDB_VERSION_MAX POLICYDB_VERSION_COMP_FTRANS + +/* Mask for just the mount related flags */ +#define SE_MNTMASK 0x0f +/* Super block security struct flags for mount options */ +/* BE CAREFUL, these need to be the low order bits for selinux_get_mnt_opts */ +#define CONTEXT_MNT 0x01 +#define FSCONTEXT_MNT 0x02 +#define ROOTCONTEXT_MNT 0x04 +#define DEFCONTEXT_MNT 0x08 +#define SBLABEL_MNT 0x10 +/* Non-mount related flags */ +#define SE_SBINITIALIZED 0x0100 +#define SE_SBPROC 0x0200 +#define SE_SBGENFS 0x0400 +#define SE_SBGENFS_XATTR 0x0800 + +#define CONTEXT_STR "context" +#define FSCONTEXT_STR "fscontext" +#define ROOTCONTEXT_STR "rootcontext" +#define DEFCONTEXT_STR "defcontext" +#define SECLABEL_STR "seclabel" + +struct netlbl_lsm_secattr; + +extern int selinux_enabled_boot; + +/* + * type_datum properties + * available at the kernel policy version >= POLICYDB_VERSION_BOUNDARY + */ +#define TYPEDATUM_PROPERTY_PRIMARY 0x0001 +#define TYPEDATUM_PROPERTY_ATTRIBUTE 0x0002 + +/* limitation of boundary depth */ +#define POLICYDB_BOUNDS_MAXDEPTH 4 + +struct selinux_avc; +struct selinux_policy; + +struct selinux_state { +#ifdef CONFIG_SECURITY_SELINUX_DISABLE + bool disabled; +#endif +#ifdef CONFIG_SECURITY_SELINUX_DEVELOP + bool enforcing; +#endif + bool checkreqprot; + bool initialized; + bool policycap[__POLICYDB_CAPABILITY_MAX]; + + struct page *status_page; + struct mutex status_lock; + + struct selinux_avc *avc; + struct selinux_policy __rcu *policy; + struct mutex policy_mutex; +} __randomize_layout; + +void selinux_avc_init(struct selinux_avc **avc); + +extern struct selinux_state selinux_state; + +static inline bool selinux_initialized(const struct selinux_state *state) +{ + /* do a synchronized load to avoid race conditions */ + return smp_load_acquire(&state->initialized); +} + +static inline void selinux_mark_initialized(struct selinux_state *state) +{ + /* do a synchronized write to avoid race conditions */ + smp_store_release(&state->initialized, true); +} + +#ifdef CONFIG_SECURITY_SELINUX_DEVELOP +static inline bool enforcing_enabled(struct selinux_state *state) +{ + return READ_ONCE(state->enforcing); +} + +static inline void enforcing_set(struct selinux_state *state, bool value) +{ + WRITE_ONCE(state->enforcing, value); +} +#else +static inline bool enforcing_enabled(struct selinux_state *state) +{ + return true; +} + +static inline void enforcing_set(struct selinux_state *state, bool value) +{ +} +#endif + +static inline bool checkreqprot_get(const struct selinux_state *state) +{ + return READ_ONCE(state->checkreqprot); +} + +static inline void checkreqprot_set(struct selinux_state *state, bool value) +{ + WRITE_ONCE(state->checkreqprot, value); +} + +#ifdef CONFIG_SECURITY_SELINUX_DISABLE +static inline bool selinux_disabled(struct selinux_state *state) +{ + return READ_ONCE(state->disabled); +} + +static inline void selinux_mark_disabled(struct selinux_state *state) +{ + WRITE_ONCE(state->disabled, true); +} +#else +static inline bool selinux_disabled(struct selinux_state *state) +{ + return false; +} +#endif + +static inline bool selinux_policycap_netpeer(void) +{ + struct selinux_state *state = &selinux_state; + + return READ_ONCE(state->policycap[POLICYDB_CAPABILITY_NETPEER]); +} + +static inline bool selinux_policycap_openperm(void) +{ + struct selinux_state *state = &selinux_state; + + return READ_ONCE(state->policycap[POLICYDB_CAPABILITY_OPENPERM]); +} + +static inline bool selinux_policycap_extsockclass(void) +{ + struct selinux_state *state = &selinux_state; + + return READ_ONCE(state->policycap[POLICYDB_CAPABILITY_EXTSOCKCLASS]); +} + +static inline bool selinux_policycap_alwaysnetwork(void) +{ + struct selinux_state *state = &selinux_state; + + return READ_ONCE(state->policycap[POLICYDB_CAPABILITY_ALWAYSNETWORK]); +} + +static inline bool selinux_policycap_cgroupseclabel(void) +{ + struct selinux_state *state = &selinux_state; + + return READ_ONCE(state->policycap[POLICYDB_CAPABILITY_CGROUPSECLABEL]); +} + +static inline bool selinux_policycap_nnp_nosuid_transition(void) +{ + struct selinux_state *state = &selinux_state; + + return READ_ONCE(state->policycap[POLICYDB_CAPABILITY_NNP_NOSUID_TRANSITION]); +} + +static inline bool selinux_policycap_genfs_seclabel_symlinks(void) +{ + struct selinux_state *state = &selinux_state; + + return READ_ONCE(state->policycap[POLICYDB_CAPABILITY_GENFS_SECLABEL_SYMLINKS]); +} + +static inline bool selinux_policycap_ioctl_skip_cloexec(void) +{ + struct selinux_state *state = &selinux_state; + + return READ_ONCE(state->policycap[POLICYDB_CAPABILITY_IOCTL_SKIP_CLOEXEC]); +} + +struct selinux_policy_convert_data; + +struct selinux_load_state { + struct selinux_policy *policy; + struct selinux_policy_convert_data *convert_data; +}; + +int security_mls_enabled(struct selinux_state *state); +int security_load_policy(struct selinux_state *state, + void *data, size_t len, + struct selinux_load_state *load_state); +void selinux_policy_commit(struct selinux_state *state, + struct selinux_load_state *load_state); +void selinux_policy_cancel(struct selinux_state *state, + struct selinux_load_state *load_state); +int security_read_policy(struct selinux_state *state, + void **data, size_t *len); + +int security_policycap_supported(struct selinux_state *state, + unsigned int req_cap); + +#define SEL_VEC_MAX 32 +struct av_decision { + u32 allowed; + u32 auditallow; + u32 auditdeny; + u32 seqno; + u32 flags; +}; + +#define XPERMS_ALLOWED 1 +#define XPERMS_AUDITALLOW 2 +#define XPERMS_DONTAUDIT 4 + +#define security_xperm_set(perms, x) (perms[x >> 5] |= 1 << (x & 0x1f)) +#define security_xperm_test(perms, x) (1 & (perms[x >> 5] >> (x & 0x1f))) +struct extended_perms_data { + u32 p[8]; +}; + +struct extended_perms_decision { + u8 used; + u8 driver; + struct extended_perms_data *allowed; + struct extended_perms_data *auditallow; + struct extended_perms_data *dontaudit; +}; + +struct extended_perms { + u16 len; /* length associated decision chain */ + struct extended_perms_data drivers; /* flag drivers that are used */ +}; + +/* definitions of av_decision.flags */ +#define AVD_FLAGS_PERMISSIVE 0x0001 + +void security_compute_av(struct selinux_state *state, + u32 ssid, u32 tsid, + u16 tclass, struct av_decision *avd, + struct extended_perms *xperms); + +void security_compute_xperms_decision(struct selinux_state *state, + u32 ssid, u32 tsid, u16 tclass, + u8 driver, + struct extended_perms_decision *xpermd); + +void security_compute_av_user(struct selinux_state *state, + u32 ssid, u32 tsid, + u16 tclass, struct av_decision *avd); + +int security_transition_sid(struct selinux_state *state, + u32 ssid, u32 tsid, u16 tclass, + const struct qstr *qstr, u32 *out_sid); + +int security_transition_sid_user(struct selinux_state *state, + u32 ssid, u32 tsid, u16 tclass, + const char *objname, u32 *out_sid); + +int security_member_sid(struct selinux_state *state, u32 ssid, u32 tsid, + u16 tclass, u32 *out_sid); + +int security_change_sid(struct selinux_state *state, u32 ssid, u32 tsid, + u16 tclass, u32 *out_sid); + +int security_sid_to_context(struct selinux_state *state, u32 sid, + char **scontext, u32 *scontext_len); + +int security_sid_to_context_force(struct selinux_state *state, + u32 sid, char **scontext, u32 *scontext_len); + +int security_sid_to_context_inval(struct selinux_state *state, + u32 sid, char **scontext, u32 *scontext_len); + +int security_context_to_sid(struct selinux_state *state, + const char *scontext, u32 scontext_len, + u32 *out_sid, gfp_t gfp); + +int security_context_str_to_sid(struct selinux_state *state, + const char *scontext, u32 *out_sid, gfp_t gfp); + +int security_context_to_sid_default(struct selinux_state *state, + const char *scontext, u32 scontext_len, + u32 *out_sid, u32 def_sid, gfp_t gfp_flags); + +int security_context_to_sid_force(struct selinux_state *state, + const char *scontext, u32 scontext_len, + u32 *sid); + +int security_get_user_sids(struct selinux_state *state, + u32 callsid, char *username, + u32 **sids, u32 *nel); + +int security_port_sid(struct selinux_state *state, + u8 protocol, u16 port, u32 *out_sid); + +int security_ib_pkey_sid(struct selinux_state *state, + u64 subnet_prefix, u16 pkey_num, u32 *out_sid); + +int security_ib_endport_sid(struct selinux_state *state, + const char *dev_name, u8 port_num, u32 *out_sid); + +int security_netif_sid(struct selinux_state *state, + char *name, u32 *if_sid); + +int security_node_sid(struct selinux_state *state, + u16 domain, void *addr, u32 addrlen, + u32 *out_sid); + +int security_validate_transition(struct selinux_state *state, + u32 oldsid, u32 newsid, u32 tasksid, + u16 tclass); + +int security_validate_transition_user(struct selinux_state *state, + u32 oldsid, u32 newsid, u32 tasksid, + u16 tclass); + +int security_bounded_transition(struct selinux_state *state, + u32 oldsid, u32 newsid); + +int security_sid_mls_copy(struct selinux_state *state, + u32 sid, u32 mls_sid, u32 *new_sid); + +int security_net_peersid_resolve(struct selinux_state *state, + u32 nlbl_sid, u32 nlbl_type, + u32 xfrm_sid, + u32 *peer_sid); + +int security_get_classes(struct selinux_policy *policy, + char ***classes, int *nclasses); +int security_get_permissions(struct selinux_policy *policy, + char *class, char ***perms, int *nperms); +int security_get_reject_unknown(struct selinux_state *state); +int security_get_allow_unknown(struct selinux_state *state); + +#define SECURITY_FS_USE_XATTR 1 /* use xattr */ +#define SECURITY_FS_USE_TRANS 2 /* use transition SIDs, e.g. devpts/tmpfs */ +#define SECURITY_FS_USE_TASK 3 /* use task SIDs, e.g. pipefs/sockfs */ +#define SECURITY_FS_USE_GENFS 4 /* use the genfs support */ +#define SECURITY_FS_USE_NONE 5 /* no labeling support */ +#define SECURITY_FS_USE_MNTPOINT 6 /* use mountpoint labeling */ +#define SECURITY_FS_USE_NATIVE 7 /* use native label support */ +#define SECURITY_FS_USE_MAX 7 /* Highest SECURITY_FS_USE_XXX */ + +int security_fs_use(struct selinux_state *state, struct super_block *sb); + +int security_genfs_sid(struct selinux_state *state, + const char *fstype, char *name, u16 sclass, + u32 *sid); + +int selinux_policy_genfs_sid(struct selinux_policy *policy, + const char *fstype, char *name, u16 sclass, + u32 *sid); + +#ifdef CONFIG_NETLABEL +int security_netlbl_secattr_to_sid(struct selinux_state *state, + struct netlbl_lsm_secattr *secattr, + u32 *sid); + +int security_netlbl_sid_to_secattr(struct selinux_state *state, + u32 sid, + struct netlbl_lsm_secattr *secattr); +#else +static inline int security_netlbl_secattr_to_sid(struct selinux_state *state, + struct netlbl_lsm_secattr *secattr, + u32 *sid) +{ + return -EIDRM; +} + +static inline int security_netlbl_sid_to_secattr(struct selinux_state *state, + u32 sid, + struct netlbl_lsm_secattr *secattr) +{ + return -ENOENT; +} +#endif /* CONFIG_NETLABEL */ + +const char *security_get_initial_sid_context(u32 sid); + +/* + * status notifier using mmap interface + */ +extern struct page *selinux_kernel_status_page(struct selinux_state *state); + +#define SELINUX_KERNEL_STATUS_VERSION 1 +struct selinux_kernel_status { + u32 version; /* version number of thie structure */ + u32 sequence; /* sequence number of seqlock logic */ + u32 enforcing; /* current setting of enforcing mode */ + u32 policyload; /* times of policy reloaded */ + u32 deny_unknown; /* current setting of deny_unknown */ + /* + * The version > 0 supports above members. + */ +} __packed; + +extern void selinux_status_update_setenforce(struct selinux_state *state, + int enforcing); +extern void selinux_status_update_policyload(struct selinux_state *state, + int seqno); +extern void selinux_complete_init(void); +extern int selinux_disable(struct selinux_state *state); +extern void exit_sel_fs(void); +extern struct path selinux_null; +extern struct vfsmount *selinuxfs_mount; +extern void selnl_notify_setenforce(int val); +extern void selnl_notify_policyload(u32 seqno); +extern int selinux_nlmsg_lookup(u16 sclass, u16 nlmsg_type, u32 *perm); + +extern void avtab_cache_init(void); +extern void ebitmap_cache_init(void); +extern void hashtab_cache_init(void); +extern int security_sidtab_hash_stats(struct selinux_state *state, char *page); + +#endif /* _SELINUX_SECURITY_H_ */ diff --git a/security/selinux/include/xfrm.h b/security/selinux/include/xfrm.h new file mode 100644 index 000000000..0a6f34a7a --- /dev/null +++ b/security/selinux/include/xfrm.h @@ -0,0 +1,92 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * SELinux support for the XFRM LSM hooks + * + * Author : Trent Jaeger, <jaegert@us.ibm.com> + * Updated : Venkat Yekkirala, <vyekkirala@TrustedCS.com> + */ +#ifndef _SELINUX_XFRM_H_ +#define _SELINUX_XFRM_H_ + +#include <net/flow.h> + +int selinux_xfrm_policy_alloc(struct xfrm_sec_ctx **ctxp, + struct xfrm_user_sec_ctx *uctx, + gfp_t gfp); +int selinux_xfrm_policy_clone(struct xfrm_sec_ctx *old_ctx, + struct xfrm_sec_ctx **new_ctxp); +void selinux_xfrm_policy_free(struct xfrm_sec_ctx *ctx); +int selinux_xfrm_policy_delete(struct xfrm_sec_ctx *ctx); +int selinux_xfrm_state_alloc(struct xfrm_state *x, + struct xfrm_user_sec_ctx *uctx); +int selinux_xfrm_state_alloc_acquire(struct xfrm_state *x, + struct xfrm_sec_ctx *polsec, u32 secid); +void selinux_xfrm_state_free(struct xfrm_state *x); +int selinux_xfrm_state_delete(struct xfrm_state *x); +int selinux_xfrm_policy_lookup(struct xfrm_sec_ctx *ctx, u32 fl_secid, u8 dir); +int selinux_xfrm_state_pol_flow_match(struct xfrm_state *x, + struct xfrm_policy *xp, + const struct flowi_common *flic); + +#ifdef CONFIG_SECURITY_NETWORK_XFRM +extern atomic_t selinux_xfrm_refcount; + +static inline int selinux_xfrm_enabled(void) +{ + return (atomic_read(&selinux_xfrm_refcount) > 0); +} + +int selinux_xfrm_sock_rcv_skb(u32 sk_sid, struct sk_buff *skb, + struct common_audit_data *ad); +int selinux_xfrm_postroute_last(u32 sk_sid, struct sk_buff *skb, + struct common_audit_data *ad, u8 proto); +int selinux_xfrm_decode_session(struct sk_buff *skb, u32 *sid, int ckall); +int selinux_xfrm_skb_sid(struct sk_buff *skb, u32 *sid); + +static inline void selinux_xfrm_notify_policyload(void) +{ + struct net *net; + + down_read(&net_rwsem); + for_each_net(net) + rt_genid_bump_all(net); + up_read(&net_rwsem); +} +#else +static inline int selinux_xfrm_enabled(void) +{ + return 0; +} + +static inline int selinux_xfrm_sock_rcv_skb(u32 sk_sid, struct sk_buff *skb, + struct common_audit_data *ad) +{ + return 0; +} + +static inline int selinux_xfrm_postroute_last(u32 sk_sid, struct sk_buff *skb, + struct common_audit_data *ad, + u8 proto) +{ + return 0; +} + +static inline int selinux_xfrm_decode_session(struct sk_buff *skb, u32 *sid, + int ckall) +{ + *sid = SECSID_NULL; + return 0; +} + +static inline void selinux_xfrm_notify_policyload(void) +{ +} + +static inline int selinux_xfrm_skb_sid(struct sk_buff *skb, u32 *sid) +{ + *sid = SECSID_NULL; + return 0; +} +#endif + +#endif /* _SELINUX_XFRM_H_ */ diff --git a/security/selinux/netif.c b/security/selinux/netif.c new file mode 100644 index 000000000..86813b46f --- /dev/null +++ b/security/selinux/netif.c @@ -0,0 +1,281 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Network interface table. + * + * Network interfaces (devices) do not have a security field, so we + * maintain a table associating each interface with a SID. + * + * Author: James Morris <jmorris@redhat.com> + * + * Copyright (C) 2003 Red Hat, Inc., James Morris <jmorris@redhat.com> + * Copyright (C) 2007 Hewlett-Packard Development Company, L.P. + * Paul Moore <paul@paul-moore.com> + */ +#include <linux/init.h> +#include <linux/types.h> +#include <linux/slab.h> +#include <linux/stddef.h> +#include <linux/kernel.h> +#include <linux/list.h> +#include <linux/notifier.h> +#include <linux/netdevice.h> +#include <linux/rcupdate.h> +#include <net/net_namespace.h> + +#include "security.h" +#include "objsec.h" +#include "netif.h" + +#define SEL_NETIF_HASH_SIZE 64 +#define SEL_NETIF_HASH_MAX 1024 + +struct sel_netif { + struct list_head list; + struct netif_security_struct nsec; + struct rcu_head rcu_head; +}; + +static u32 sel_netif_total; +static LIST_HEAD(sel_netif_list); +static DEFINE_SPINLOCK(sel_netif_lock); +static struct list_head sel_netif_hash[SEL_NETIF_HASH_SIZE]; + +/** + * sel_netif_hashfn - Hashing function for the interface table + * @ns: the network namespace + * @ifindex: the network interface + * + * Description: + * This is the hashing function for the network interface table, it returns the + * bucket number for the given interface. + * + */ +static inline u32 sel_netif_hashfn(const struct net *ns, int ifindex) +{ + return (((uintptr_t)ns + ifindex) & (SEL_NETIF_HASH_SIZE - 1)); +} + +/** + * sel_netif_find - Search for an interface record + * @ns: the network namespace + * @ifindex: the network interface + * + * Description: + * Search the network interface table and return the record matching @ifindex. + * If an entry can not be found in the table return NULL. + * + */ +static inline struct sel_netif *sel_netif_find(const struct net *ns, + int ifindex) +{ + int idx = sel_netif_hashfn(ns, ifindex); + struct sel_netif *netif; + + list_for_each_entry_rcu(netif, &sel_netif_hash[idx], list) + if (net_eq(netif->nsec.ns, ns) && + netif->nsec.ifindex == ifindex) + return netif; + + return NULL; +} + +/** + * sel_netif_insert - Insert a new interface into the table + * @netif: the new interface record + * + * Description: + * Add a new interface record to the network interface hash table. Returns + * zero on success, negative values on failure. + * + */ +static int sel_netif_insert(struct sel_netif *netif) +{ + int idx; + + if (sel_netif_total >= SEL_NETIF_HASH_MAX) + return -ENOSPC; + + idx = sel_netif_hashfn(netif->nsec.ns, netif->nsec.ifindex); + list_add_rcu(&netif->list, &sel_netif_hash[idx]); + sel_netif_total++; + + return 0; +} + +/** + * sel_netif_destroy - Remove an interface record from the table + * @netif: the existing interface record + * + * Description: + * Remove an existing interface record from the network interface table. + * + */ +static void sel_netif_destroy(struct sel_netif *netif) +{ + list_del_rcu(&netif->list); + sel_netif_total--; + kfree_rcu(netif, rcu_head); +} + +/** + * sel_netif_sid_slow - Lookup the SID of a network interface using the policy + * @ns: the network namespace + * @ifindex: the network interface + * @sid: interface SID + * + * Description: + * This function determines the SID of a network interface by querying the + * security policy. The result is added to the network interface table to + * speedup future queries. Returns zero on success, negative values on + * failure. + * + */ +static int sel_netif_sid_slow(struct net *ns, int ifindex, u32 *sid) +{ + int ret = 0; + struct sel_netif *netif; + struct sel_netif *new; + struct net_device *dev; + + /* NOTE: we always use init's network namespace since we don't + * currently support containers */ + + dev = dev_get_by_index(ns, ifindex); + if (unlikely(dev == NULL)) { + pr_warn("SELinux: failure in %s(), invalid network interface (%d)\n", + __func__, ifindex); + return -ENOENT; + } + + spin_lock_bh(&sel_netif_lock); + netif = sel_netif_find(ns, ifindex); + if (netif != NULL) { + *sid = netif->nsec.sid; + goto out; + } + + ret = security_netif_sid(&selinux_state, dev->name, sid); + if (ret != 0) + goto out; + new = kzalloc(sizeof(*new), GFP_ATOMIC); + if (new) { + new->nsec.ns = ns; + new->nsec.ifindex = ifindex; + new->nsec.sid = *sid; + if (sel_netif_insert(new)) + kfree(new); + } + +out: + spin_unlock_bh(&sel_netif_lock); + dev_put(dev); + if (unlikely(ret)) + pr_warn("SELinux: failure in %s(), unable to determine network interface label (%d)\n", + __func__, ifindex); + return ret; +} + +/** + * sel_netif_sid - Lookup the SID of a network interface + * @ns: the network namespace + * @ifindex: the network interface + * @sid: interface SID + * + * Description: + * This function determines the SID of a network interface using the fastest + * method possible. First the interface table is queried, but if an entry + * can't be found then the policy is queried and the result is added to the + * table to speedup future queries. Returns zero on success, negative values + * on failure. + * + */ +int sel_netif_sid(struct net *ns, int ifindex, u32 *sid) +{ + struct sel_netif *netif; + + rcu_read_lock(); + netif = sel_netif_find(ns, ifindex); + if (likely(netif != NULL)) { + *sid = netif->nsec.sid; + rcu_read_unlock(); + return 0; + } + rcu_read_unlock(); + + return sel_netif_sid_slow(ns, ifindex, sid); +} + +/** + * sel_netif_kill - Remove an entry from the network interface table + * @ns: the network namespace + * @ifindex: the network interface + * + * Description: + * This function removes the entry matching @ifindex from the network interface + * table if it exists. + * + */ +static void sel_netif_kill(const struct net *ns, int ifindex) +{ + struct sel_netif *netif; + + rcu_read_lock(); + spin_lock_bh(&sel_netif_lock); + netif = sel_netif_find(ns, ifindex); + if (netif) + sel_netif_destroy(netif); + spin_unlock_bh(&sel_netif_lock); + rcu_read_unlock(); +} + +/** + * sel_netif_flush - Flush the entire network interface table + * + * Description: + * Remove all entries from the network interface table. + * + */ +void sel_netif_flush(void) +{ + int idx; + struct sel_netif *netif; + + spin_lock_bh(&sel_netif_lock); + for (idx = 0; idx < SEL_NETIF_HASH_SIZE; idx++) + list_for_each_entry(netif, &sel_netif_hash[idx], list) + sel_netif_destroy(netif); + spin_unlock_bh(&sel_netif_lock); +} + +static int sel_netif_netdev_notifier_handler(struct notifier_block *this, + unsigned long event, void *ptr) +{ + struct net_device *dev = netdev_notifier_info_to_dev(ptr); + + if (event == NETDEV_DOWN) + sel_netif_kill(dev_net(dev), dev->ifindex); + + return NOTIFY_DONE; +} + +static struct notifier_block sel_netif_netdev_notifier = { + .notifier_call = sel_netif_netdev_notifier_handler, +}; + +static __init int sel_netif_init(void) +{ + int i; + + if (!selinux_enabled_boot) + return 0; + + for (i = 0; i < SEL_NETIF_HASH_SIZE; i++) + INIT_LIST_HEAD(&sel_netif_hash[i]); + + register_netdevice_notifier(&sel_netif_netdev_notifier); + + return 0; +} + +__initcall(sel_netif_init); + diff --git a/security/selinux/netlabel.c b/security/selinux/netlabel.c new file mode 100644 index 000000000..abaab7683 --- /dev/null +++ b/security/selinux/netlabel.c @@ -0,0 +1,612 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * SELinux NetLabel Support + * + * This file provides the necessary glue to tie NetLabel into the SELinux + * subsystem. + * + * Author: Paul Moore <paul@paul-moore.com> + */ + +/* + * (c) Copyright Hewlett-Packard Development Company, L.P., 2007, 2008 + */ + +#include <linux/spinlock.h> +#include <linux/rcupdate.h> +#include <linux/gfp.h> +#include <linux/ip.h> +#include <linux/ipv6.h> +#include <net/sock.h> +#include <net/netlabel.h> +#include <net/ip.h> +#include <net/ipv6.h> + +#include "objsec.h" +#include "security.h" +#include "netlabel.h" + +/** + * selinux_netlbl_sidlookup_cached - Cache a SID lookup + * @skb: the packet + * @secattr: the NetLabel security attributes + * @sid: the SID + * + * Description: + * Query the SELinux security server to lookup the correct SID for the given + * security attributes. If the query is successful, cache the result to speed + * up future lookups. Returns zero on success, negative values on failure. + * + */ +static int selinux_netlbl_sidlookup_cached(struct sk_buff *skb, + u16 family, + struct netlbl_lsm_secattr *secattr, + u32 *sid) +{ + int rc; + + rc = security_netlbl_secattr_to_sid(&selinux_state, secattr, sid); + if (rc == 0 && + (secattr->flags & NETLBL_SECATTR_CACHEABLE) && + (secattr->flags & NETLBL_SECATTR_CACHE)) + netlbl_cache_add(skb, family, secattr); + + return rc; +} + +/** + * selinux_netlbl_sock_genattr - Generate the NetLabel socket secattr + * @sk: the socket + * + * Description: + * Generate the NetLabel security attributes for a socket, making full use of + * the socket's attribute cache. Returns a pointer to the security attributes + * on success, NULL on failure. + * + */ +static struct netlbl_lsm_secattr *selinux_netlbl_sock_genattr(struct sock *sk) +{ + int rc; + struct sk_security_struct *sksec = sk->sk_security; + struct netlbl_lsm_secattr *secattr; + + if (sksec->nlbl_secattr != NULL) + return sksec->nlbl_secattr; + + secattr = netlbl_secattr_alloc(GFP_ATOMIC); + if (secattr == NULL) + return NULL; + rc = security_netlbl_sid_to_secattr(&selinux_state, sksec->sid, + secattr); + if (rc != 0) { + netlbl_secattr_free(secattr); + return NULL; + } + sksec->nlbl_secattr = secattr; + + return secattr; +} + +/** + * selinux_netlbl_sock_getattr - Get the cached NetLabel secattr + * @sk: the socket + * @sid: the SID + * + * Query the socket's cached secattr and if the SID matches the cached value + * return the cache, otherwise return NULL. + * + */ +static struct netlbl_lsm_secattr *selinux_netlbl_sock_getattr( + const struct sock *sk, + u32 sid) +{ + struct sk_security_struct *sksec = sk->sk_security; + struct netlbl_lsm_secattr *secattr = sksec->nlbl_secattr; + + if (secattr == NULL) + return NULL; + + if ((secattr->flags & NETLBL_SECATTR_SECID) && + (secattr->attr.secid == sid)) + return secattr; + + return NULL; +} + +/** + * selinux_netlbl_cache_invalidate - Invalidate the NetLabel cache + * + * Description: + * Invalidate the NetLabel security attribute mapping cache. + * + */ +void selinux_netlbl_cache_invalidate(void) +{ + netlbl_cache_invalidate(); +} + +/** + * selinux_netlbl_err - Handle a NetLabel packet error + * @skb: the packet + * @error: the error code + * @gateway: true if host is acting as a gateway, false otherwise + * + * Description: + * When a packet is dropped due to a call to avc_has_perm() pass the error + * code to the NetLabel subsystem so any protocol specific processing can be + * done. This is safe to call even if you are unsure if NetLabel labeling is + * present on the packet, NetLabel is smart enough to only act when it should. + * + */ +void selinux_netlbl_err(struct sk_buff *skb, u16 family, int error, int gateway) +{ + netlbl_skbuff_err(skb, family, error, gateway); +} + +/** + * selinux_netlbl_sk_security_free - Free the NetLabel fields + * @sksec: the sk_security_struct + * + * Description: + * Free all of the memory in the NetLabel fields of a sk_security_struct. + * + */ +void selinux_netlbl_sk_security_free(struct sk_security_struct *sksec) +{ + if (sksec->nlbl_secattr != NULL) + netlbl_secattr_free(sksec->nlbl_secattr); +} + +/** + * selinux_netlbl_sk_security_reset - Reset the NetLabel fields + * @sksec: the sk_security_struct + * @family: the socket family + * + * Description: + * Called when the NetLabel state of a sk_security_struct needs to be reset. + * The caller is responsible for all the NetLabel sk_security_struct locking. + * + */ +void selinux_netlbl_sk_security_reset(struct sk_security_struct *sksec) +{ + sksec->nlbl_state = NLBL_UNSET; +} + +/** + * selinux_netlbl_skbuff_getsid - Get the sid of a packet using NetLabel + * @skb: the packet + * @family: protocol family + * @type: NetLabel labeling protocol type + * @sid: the SID + * + * Description: + * Call the NetLabel mechanism to get the security attributes of the given + * packet and use those attributes to determine the correct context/SID to + * assign to the packet. Returns zero on success, negative values on failure. + * + */ +int selinux_netlbl_skbuff_getsid(struct sk_buff *skb, + u16 family, + u32 *type, + u32 *sid) +{ + int rc; + struct netlbl_lsm_secattr secattr; + + if (!netlbl_enabled()) { + *sid = SECSID_NULL; + return 0; + } + + netlbl_secattr_init(&secattr); + rc = netlbl_skbuff_getattr(skb, family, &secattr); + if (rc == 0 && secattr.flags != NETLBL_SECATTR_NONE) + rc = selinux_netlbl_sidlookup_cached(skb, family, + &secattr, sid); + else + *sid = SECSID_NULL; + *type = secattr.type; + netlbl_secattr_destroy(&secattr); + + return rc; +} + +/** + * selinux_netlbl_skbuff_setsid - Set the NetLabel on a packet given a sid + * @skb: the packet + * @family: protocol family + * @sid: the SID + * + * Description + * Call the NetLabel mechanism to set the label of a packet using @sid. + * Returns zero on success, negative values on failure. + * + */ +int selinux_netlbl_skbuff_setsid(struct sk_buff *skb, + u16 family, + u32 sid) +{ + int rc; + struct netlbl_lsm_secattr secattr_storage; + struct netlbl_lsm_secattr *secattr = NULL; + struct sock *sk; + + /* if this is a locally generated packet check to see if it is already + * being labeled by it's parent socket, if it is just exit */ + sk = skb_to_full_sk(skb); + if (sk != NULL) { + struct sk_security_struct *sksec = sk->sk_security; + + if (sksec->nlbl_state != NLBL_REQSKB) + return 0; + secattr = selinux_netlbl_sock_getattr(sk, sid); + } + if (secattr == NULL) { + secattr = &secattr_storage; + netlbl_secattr_init(secattr); + rc = security_netlbl_sid_to_secattr(&selinux_state, sid, + secattr); + if (rc != 0) + goto skbuff_setsid_return; + } + + rc = netlbl_skbuff_setattr(skb, family, secattr); + +skbuff_setsid_return: + if (secattr == &secattr_storage) + netlbl_secattr_destroy(secattr); + return rc; +} + +/** + * selinux_netlbl_sctp_assoc_request - Label an incoming sctp association. + * @ep: incoming association endpoint. + * @skb: the packet. + * + * Description: + * A new incoming connection is represented by @ep, ...... + * Returns zero on success, negative values on failure. + * + */ +int selinux_netlbl_sctp_assoc_request(struct sctp_endpoint *ep, + struct sk_buff *skb) +{ + int rc; + struct netlbl_lsm_secattr secattr; + struct sk_security_struct *sksec = ep->base.sk->sk_security; + struct sockaddr_in addr4; + struct sockaddr_in6 addr6; + + if (ep->base.sk->sk_family != PF_INET && + ep->base.sk->sk_family != PF_INET6) + return 0; + + netlbl_secattr_init(&secattr); + rc = security_netlbl_sid_to_secattr(&selinux_state, + ep->secid, &secattr); + if (rc != 0) + goto assoc_request_return; + + /* Move skb hdr address info to a struct sockaddr and then call + * netlbl_conn_setattr(). + */ + if (ip_hdr(skb)->version == 4) { + addr4.sin_family = AF_INET; + addr4.sin_addr.s_addr = ip_hdr(skb)->saddr; + rc = netlbl_conn_setattr(ep->base.sk, (void *)&addr4, &secattr); + } else if (IS_ENABLED(CONFIG_IPV6) && ip_hdr(skb)->version == 6) { + addr6.sin6_family = AF_INET6; + addr6.sin6_addr = ipv6_hdr(skb)->saddr; + rc = netlbl_conn_setattr(ep->base.sk, (void *)&addr6, &secattr); + } else { + rc = -EAFNOSUPPORT; + } + + if (rc == 0) + sksec->nlbl_state = NLBL_LABELED; + +assoc_request_return: + netlbl_secattr_destroy(&secattr); + return rc; +} + +/** + * selinux_netlbl_inet_conn_request - Label an incoming stream connection + * @req: incoming connection request socket + * + * Description: + * A new incoming connection request is represented by @req, we need to label + * the new request_sock here and the stack will ensure the on-the-wire label + * will get preserved when a full sock is created once the connection handshake + * is complete. Returns zero on success, negative values on failure. + * + */ +int selinux_netlbl_inet_conn_request(struct request_sock *req, u16 family) +{ + int rc; + struct netlbl_lsm_secattr secattr; + + if (family != PF_INET && family != PF_INET6) + return 0; + + netlbl_secattr_init(&secattr); + rc = security_netlbl_sid_to_secattr(&selinux_state, req->secid, + &secattr); + if (rc != 0) + goto inet_conn_request_return; + rc = netlbl_req_setattr(req, &secattr); +inet_conn_request_return: + netlbl_secattr_destroy(&secattr); + return rc; +} + +/** + * selinux_netlbl_inet_csk_clone - Initialize the newly created sock + * @sk: the new sock + * + * Description: + * A new connection has been established using @sk, we've already labeled the + * socket via the request_sock struct in selinux_netlbl_inet_conn_request() but + * we need to set the NetLabel state here since we now have a sock structure. + * + */ +void selinux_netlbl_inet_csk_clone(struct sock *sk, u16 family) +{ + struct sk_security_struct *sksec = sk->sk_security; + + if (family == PF_INET) + sksec->nlbl_state = NLBL_LABELED; + else + sksec->nlbl_state = NLBL_UNSET; +} + +/** + * selinux_netlbl_sctp_sk_clone - Copy state to the newly created sock + * @sk: current sock + * @newsk: the new sock + * + * Description: + * Called whenever a new socket is created by accept(2) or sctp_peeloff(3). + */ +void selinux_netlbl_sctp_sk_clone(struct sock *sk, struct sock *newsk) +{ + struct sk_security_struct *sksec = sk->sk_security; + struct sk_security_struct *newsksec = newsk->sk_security; + + newsksec->nlbl_state = sksec->nlbl_state; +} + +/** + * selinux_netlbl_socket_post_create - Label a socket using NetLabel + * @sock: the socket to label + * @family: protocol family + * + * Description: + * Attempt to label a socket using the NetLabel mechanism using the given + * SID. Returns zero values on success, negative values on failure. + * + */ +int selinux_netlbl_socket_post_create(struct sock *sk, u16 family) +{ + int rc; + struct sk_security_struct *sksec = sk->sk_security; + struct netlbl_lsm_secattr *secattr; + + if (family != PF_INET && family != PF_INET6) + return 0; + + secattr = selinux_netlbl_sock_genattr(sk); + if (secattr == NULL) + return -ENOMEM; + rc = netlbl_sock_setattr(sk, family, secattr); + switch (rc) { + case 0: + sksec->nlbl_state = NLBL_LABELED; + break; + case -EDESTADDRREQ: + sksec->nlbl_state = NLBL_REQSKB; + rc = 0; + break; + } + + return rc; +} + +/** + * selinux_netlbl_sock_rcv_skb - Do an inbound access check using NetLabel + * @sksec: the sock's sk_security_struct + * @skb: the packet + * @family: protocol family + * @ad: the audit data + * + * Description: + * Fetch the NetLabel security attributes from @skb and perform an access check + * against the receiving socket. Returns zero on success, negative values on + * error. + * + */ +int selinux_netlbl_sock_rcv_skb(struct sk_security_struct *sksec, + struct sk_buff *skb, + u16 family, + struct common_audit_data *ad) +{ + int rc; + u32 nlbl_sid; + u32 perm; + struct netlbl_lsm_secattr secattr; + + if (!netlbl_enabled()) + return 0; + + netlbl_secattr_init(&secattr); + rc = netlbl_skbuff_getattr(skb, family, &secattr); + if (rc == 0 && secattr.flags != NETLBL_SECATTR_NONE) + rc = selinux_netlbl_sidlookup_cached(skb, family, + &secattr, &nlbl_sid); + else + nlbl_sid = SECINITSID_UNLABELED; + netlbl_secattr_destroy(&secattr); + if (rc != 0) + return rc; + + switch (sksec->sclass) { + case SECCLASS_UDP_SOCKET: + perm = UDP_SOCKET__RECVFROM; + break; + case SECCLASS_TCP_SOCKET: + perm = TCP_SOCKET__RECVFROM; + break; + default: + perm = RAWIP_SOCKET__RECVFROM; + } + + rc = avc_has_perm(&selinux_state, + sksec->sid, nlbl_sid, sksec->sclass, perm, ad); + if (rc == 0) + return 0; + + if (nlbl_sid != SECINITSID_UNLABELED) + netlbl_skbuff_err(skb, family, rc, 0); + return rc; +} + +/** + * selinux_netlbl_option - Is this a NetLabel option + * @level: the socket level or protocol + * @optname: the socket option name + * + * Description: + * Returns true if @level and @optname refer to a NetLabel option. + * Helper for selinux_netlbl_socket_setsockopt(). + */ +static inline int selinux_netlbl_option(int level, int optname) +{ + return (level == IPPROTO_IP && optname == IP_OPTIONS) || + (level == IPPROTO_IPV6 && optname == IPV6_HOPOPTS); +} + +/** + * selinux_netlbl_socket_setsockopt - Do not allow users to remove a NetLabel + * @sock: the socket + * @level: the socket level or protocol + * @optname: the socket option name + * + * Description: + * Check the setsockopt() call and if the user is trying to replace the IP + * options on a socket and a NetLabel is in place for the socket deny the + * access; otherwise allow the access. Returns zero when the access is + * allowed, -EACCES when denied, and other negative values on error. + * + */ +int selinux_netlbl_socket_setsockopt(struct socket *sock, + int level, + int optname) +{ + int rc = 0; + struct sock *sk = sock->sk; + struct sk_security_struct *sksec = sk->sk_security; + struct netlbl_lsm_secattr secattr; + + if (selinux_netlbl_option(level, optname) && + (sksec->nlbl_state == NLBL_LABELED || + sksec->nlbl_state == NLBL_CONNLABELED)) { + netlbl_secattr_init(&secattr); + lock_sock(sk); + /* call the netlabel function directly as we want to see the + * on-the-wire label that is assigned via the socket's options + * and not the cached netlabel/lsm attributes */ + rc = netlbl_sock_getattr(sk, &secattr); + release_sock(sk); + if (rc == 0) + rc = -EACCES; + else if (rc == -ENOMSG) + rc = 0; + netlbl_secattr_destroy(&secattr); + } + + return rc; +} + +/** + * selinux_netlbl_socket_connect_helper - Help label a client-side socket on + * connect + * @sk: the socket to label + * @addr: the destination address + * + * Description: + * Attempt to label a connected socket with NetLabel using the given address. + * Returns zero values on success, negative values on failure. + * + */ +static int selinux_netlbl_socket_connect_helper(struct sock *sk, + struct sockaddr *addr) +{ + int rc; + struct sk_security_struct *sksec = sk->sk_security; + struct netlbl_lsm_secattr *secattr; + + /* connected sockets are allowed to disconnect when the address family + * is set to AF_UNSPEC, if that is what is happening we want to reset + * the socket */ + if (addr->sa_family == AF_UNSPEC) { + netlbl_sock_delattr(sk); + sksec->nlbl_state = NLBL_REQSKB; + rc = 0; + return rc; + } + secattr = selinux_netlbl_sock_genattr(sk); + if (secattr == NULL) { + rc = -ENOMEM; + return rc; + } + rc = netlbl_conn_setattr(sk, addr, secattr); + if (rc == 0) + sksec->nlbl_state = NLBL_CONNLABELED; + + return rc; +} + +/** + * selinux_netlbl_socket_connect_locked - Label a client-side socket on + * connect + * @sk: the socket to label + * @addr: the destination address + * + * Description: + * Attempt to label a connected socket that already has the socket locked + * with NetLabel using the given address. + * Returns zero values on success, negative values on failure. + * + */ +int selinux_netlbl_socket_connect_locked(struct sock *sk, + struct sockaddr *addr) +{ + struct sk_security_struct *sksec = sk->sk_security; + + if (sksec->nlbl_state != NLBL_REQSKB && + sksec->nlbl_state != NLBL_CONNLABELED) + return 0; + + return selinux_netlbl_socket_connect_helper(sk, addr); +} + +/** + * selinux_netlbl_socket_connect - Label a client-side socket on connect + * @sk: the socket to label + * @addr: the destination address + * + * Description: + * Attempt to label a connected socket with NetLabel using the given address. + * Returns zero values on success, negative values on failure. + * + */ +int selinux_netlbl_socket_connect(struct sock *sk, struct sockaddr *addr) +{ + int rc; + + lock_sock(sk); + rc = selinux_netlbl_socket_connect_locked(sk, addr); + release_sock(sk); + + return rc; +} diff --git a/security/selinux/netlink.c b/security/selinux/netlink.c new file mode 100644 index 000000000..621e2e9cd --- /dev/null +++ b/security/selinux/netlink.c @@ -0,0 +1,121 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Netlink event notifications for SELinux. + * + * Author: James Morris <jmorris@redhat.com> + * + * Copyright (C) 2004 Red Hat, Inc., James Morris <jmorris@redhat.com> + */ +#include <linux/init.h> +#include <linux/types.h> +#include <linux/slab.h> +#include <linux/stddef.h> +#include <linux/kernel.h> +#include <linux/export.h> +#include <linux/skbuff.h> +#include <linux/selinux_netlink.h> +#include <net/net_namespace.h> +#include <net/netlink.h> + +#include "security.h" + +static struct sock *selnl; + +static int selnl_msglen(int msgtype) +{ + int ret = 0; + + switch (msgtype) { + case SELNL_MSG_SETENFORCE: + ret = sizeof(struct selnl_msg_setenforce); + break; + + case SELNL_MSG_POLICYLOAD: + ret = sizeof(struct selnl_msg_policyload); + break; + + default: + BUG(); + } + return ret; +} + +static void selnl_add_payload(struct nlmsghdr *nlh, int len, int msgtype, void *data) +{ + switch (msgtype) { + case SELNL_MSG_SETENFORCE: { + struct selnl_msg_setenforce *msg = nlmsg_data(nlh); + + memset(msg, 0, len); + msg->val = *((int *)data); + break; + } + + case SELNL_MSG_POLICYLOAD: { + struct selnl_msg_policyload *msg = nlmsg_data(nlh); + + memset(msg, 0, len); + msg->seqno = *((u32 *)data); + break; + } + + default: + BUG(); + } +} + +static void selnl_notify(int msgtype, void *data) +{ + int len; + sk_buff_data_t tmp; + struct sk_buff *skb; + struct nlmsghdr *nlh; + + len = selnl_msglen(msgtype); + + skb = nlmsg_new(len, GFP_USER); + if (!skb) + goto oom; + + tmp = skb->tail; + nlh = nlmsg_put(skb, 0, 0, msgtype, len, 0); + if (!nlh) + goto out_kfree_skb; + selnl_add_payload(nlh, len, msgtype, data); + nlh->nlmsg_len = skb->tail - tmp; + NETLINK_CB(skb).dst_group = SELNLGRP_AVC; + netlink_broadcast(selnl, skb, 0, SELNLGRP_AVC, GFP_USER); +out: + return; + +out_kfree_skb: + kfree_skb(skb); +oom: + pr_err("SELinux: OOM in %s\n", __func__); + goto out; +} + +void selnl_notify_setenforce(int val) +{ + selnl_notify(SELNL_MSG_SETENFORCE, &val); +} + +void selnl_notify_policyload(u32 seqno) +{ + selnl_notify(SELNL_MSG_POLICYLOAD, &seqno); +} + +static int __init selnl_init(void) +{ + struct netlink_kernel_cfg cfg = { + .groups = SELNLGRP_MAX, + .flags = NL_CFG_F_NONROOT_RECV, + }; + + selnl = netlink_kernel_create(&init_net, NETLINK_SELINUX, &cfg); + if (selnl == NULL) + panic("SELinux: Cannot create netlink socket."); + return 0; +} + +__initcall(selnl_init); diff --git a/security/selinux/netnode.c b/security/selinux/netnode.c new file mode 100644 index 000000000..461fb5484 --- /dev/null +++ b/security/selinux/netnode.c @@ -0,0 +1,305 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Network node table + * + * SELinux must keep a mapping of network nodes to labels/SIDs. This + * mapping is maintained as part of the normal policy but a fast cache is + * needed to reduce the lookup overhead since most of these queries happen on + * a per-packet basis. + * + * Author: Paul Moore <paul@paul-moore.com> + * + * This code is heavily based on the "netif" concept originally developed by + * James Morris <jmorris@redhat.com> + * (see security/selinux/netif.c for more information) + */ + +/* + * (c) Copyright Hewlett-Packard Development Company, L.P., 2007 + */ + +#include <linux/types.h> +#include <linux/rcupdate.h> +#include <linux/list.h> +#include <linux/slab.h> +#include <linux/spinlock.h> +#include <linux/in.h> +#include <linux/in6.h> +#include <linux/ip.h> +#include <linux/ipv6.h> +#include <net/ip.h> +#include <net/ipv6.h> + +#include "netnode.h" +#include "objsec.h" + +#define SEL_NETNODE_HASH_SIZE 256 +#define SEL_NETNODE_HASH_BKT_LIMIT 16 + +struct sel_netnode_bkt { + unsigned int size; + struct list_head list; +}; + +struct sel_netnode { + struct netnode_security_struct nsec; + + struct list_head list; + struct rcu_head rcu; +}; + +/* NOTE: we are using a combined hash table for both IPv4 and IPv6, the reason + * for this is that I suspect most users will not make heavy use of both + * address families at the same time so one table will usually end up wasted, + * if this becomes a problem we can always add a hash table for each address + * family later */ + +static LIST_HEAD(sel_netnode_list); +static DEFINE_SPINLOCK(sel_netnode_lock); +static struct sel_netnode_bkt sel_netnode_hash[SEL_NETNODE_HASH_SIZE]; + +/** + * sel_netnode_hashfn_ipv4 - IPv4 hashing function for the node table + * @addr: IPv4 address + * + * Description: + * This is the IPv4 hashing function for the node interface table, it returns + * the bucket number for the given IP address. + * + */ +static unsigned int sel_netnode_hashfn_ipv4(__be32 addr) +{ + /* at some point we should determine if the mismatch in byte order + * affects the hash function dramatically */ + return (addr & (SEL_NETNODE_HASH_SIZE - 1)); +} + +/** + * sel_netnode_hashfn_ipv6 - IPv6 hashing function for the node table + * @addr: IPv6 address + * + * Description: + * This is the IPv6 hashing function for the node interface table, it returns + * the bucket number for the given IP address. + * + */ +static unsigned int sel_netnode_hashfn_ipv6(const struct in6_addr *addr) +{ + /* just hash the least significant 32 bits to keep things fast (they + * are the most likely to be different anyway), we can revisit this + * later if needed */ + return (addr->s6_addr32[3] & (SEL_NETNODE_HASH_SIZE - 1)); +} + +/** + * sel_netnode_find - Search for a node record + * @addr: IP address + * @family: address family + * + * Description: + * Search the network node table and return the record matching @addr. If an + * entry can not be found in the table return NULL. + * + */ +static struct sel_netnode *sel_netnode_find(const void *addr, u16 family) +{ + unsigned int idx; + struct sel_netnode *node; + + switch (family) { + case PF_INET: + idx = sel_netnode_hashfn_ipv4(*(__be32 *)addr); + break; + case PF_INET6: + idx = sel_netnode_hashfn_ipv6(addr); + break; + default: + BUG(); + return NULL; + } + + list_for_each_entry_rcu(node, &sel_netnode_hash[idx].list, list) + if (node->nsec.family == family) + switch (family) { + case PF_INET: + if (node->nsec.addr.ipv4 == *(__be32 *)addr) + return node; + break; + case PF_INET6: + if (ipv6_addr_equal(&node->nsec.addr.ipv6, + addr)) + return node; + break; + } + + return NULL; +} + +/** + * sel_netnode_insert - Insert a new node into the table + * @node: the new node record + * + * Description: + * Add a new node record to the network address hash table. + * + */ +static void sel_netnode_insert(struct sel_netnode *node) +{ + unsigned int idx; + + switch (node->nsec.family) { + case PF_INET: + idx = sel_netnode_hashfn_ipv4(node->nsec.addr.ipv4); + break; + case PF_INET6: + idx = sel_netnode_hashfn_ipv6(&node->nsec.addr.ipv6); + break; + default: + BUG(); + return; + } + + /* we need to impose a limit on the growth of the hash table so check + * this bucket to make sure it is within the specified bounds */ + list_add_rcu(&node->list, &sel_netnode_hash[idx].list); + if (sel_netnode_hash[idx].size == SEL_NETNODE_HASH_BKT_LIMIT) { + struct sel_netnode *tail; + tail = list_entry( + rcu_dereference_protected(sel_netnode_hash[idx].list.prev, + lockdep_is_held(&sel_netnode_lock)), + struct sel_netnode, list); + list_del_rcu(&tail->list); + kfree_rcu(tail, rcu); + } else + sel_netnode_hash[idx].size++; +} + +/** + * sel_netnode_sid_slow - Lookup the SID of a network address using the policy + * @addr: the IP address + * @family: the address family + * @sid: node SID + * + * Description: + * This function determines the SID of a network address by querying the + * security policy. The result is added to the network address table to + * speedup future queries. Returns zero on success, negative values on + * failure. + * + */ +static int sel_netnode_sid_slow(void *addr, u16 family, u32 *sid) +{ + int ret; + struct sel_netnode *node; + struct sel_netnode *new; + + spin_lock_bh(&sel_netnode_lock); + node = sel_netnode_find(addr, family); + if (node != NULL) { + *sid = node->nsec.sid; + spin_unlock_bh(&sel_netnode_lock); + return 0; + } + + new = kzalloc(sizeof(*new), GFP_ATOMIC); + switch (family) { + case PF_INET: + ret = security_node_sid(&selinux_state, PF_INET, + addr, sizeof(struct in_addr), sid); + if (new) + new->nsec.addr.ipv4 = *(__be32 *)addr; + break; + case PF_INET6: + ret = security_node_sid(&selinux_state, PF_INET6, + addr, sizeof(struct in6_addr), sid); + if (new) + new->nsec.addr.ipv6 = *(struct in6_addr *)addr; + break; + default: + BUG(); + ret = -EINVAL; + } + if (ret == 0 && new) { + new->nsec.family = family; + new->nsec.sid = *sid; + sel_netnode_insert(new); + } else + kfree(new); + + spin_unlock_bh(&sel_netnode_lock); + if (unlikely(ret)) + pr_warn("SELinux: failure in %s(), unable to determine network node label\n", + __func__); + return ret; +} + +/** + * sel_netnode_sid - Lookup the SID of a network address + * @addr: the IP address + * @family: the address family + * @sid: node SID + * + * Description: + * This function determines the SID of a network address using the fastest + * method possible. First the address table is queried, but if an entry + * can't be found then the policy is queried and the result is added to the + * table to speedup future queries. Returns zero on success, negative values + * on failure. + * + */ +int sel_netnode_sid(void *addr, u16 family, u32 *sid) +{ + struct sel_netnode *node; + + rcu_read_lock(); + node = sel_netnode_find(addr, family); + if (node != NULL) { + *sid = node->nsec.sid; + rcu_read_unlock(); + return 0; + } + rcu_read_unlock(); + + return sel_netnode_sid_slow(addr, family, sid); +} + +/** + * sel_netnode_flush - Flush the entire network address table + * + * Description: + * Remove all entries from the network address table. + * + */ +void sel_netnode_flush(void) +{ + unsigned int idx; + struct sel_netnode *node, *node_tmp; + + spin_lock_bh(&sel_netnode_lock); + for (idx = 0; idx < SEL_NETNODE_HASH_SIZE; idx++) { + list_for_each_entry_safe(node, node_tmp, + &sel_netnode_hash[idx].list, list) { + list_del_rcu(&node->list); + kfree_rcu(node, rcu); + } + sel_netnode_hash[idx].size = 0; + } + spin_unlock_bh(&sel_netnode_lock); +} + +static __init int sel_netnode_init(void) +{ + int iter; + + if (!selinux_enabled_boot) + return 0; + + for (iter = 0; iter < SEL_NETNODE_HASH_SIZE; iter++) { + INIT_LIST_HEAD(&sel_netnode_hash[iter].list); + sel_netnode_hash[iter].size = 0; + } + + return 0; +} + +__initcall(sel_netnode_init); diff --git a/security/selinux/netport.c b/security/selinux/netport.c new file mode 100644 index 000000000..d340f4dcd --- /dev/null +++ b/security/selinux/netport.c @@ -0,0 +1,239 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Network port table + * + * SELinux must keep a mapping of network ports to labels/SIDs. This + * mapping is maintained as part of the normal policy but a fast cache is + * needed to reduce the lookup overhead. + * + * Author: Paul Moore <paul@paul-moore.com> + * + * This code is heavily based on the "netif" concept originally developed by + * James Morris <jmorris@redhat.com> + * (see security/selinux/netif.c for more information) + */ + +/* + * (c) Copyright Hewlett-Packard Development Company, L.P., 2008 + */ + +#include <linux/types.h> +#include <linux/rcupdate.h> +#include <linux/list.h> +#include <linux/slab.h> +#include <linux/spinlock.h> +#include <linux/in.h> +#include <linux/in6.h> +#include <linux/ip.h> +#include <linux/ipv6.h> +#include <net/ip.h> +#include <net/ipv6.h> + +#include "netport.h" +#include "objsec.h" + +#define SEL_NETPORT_HASH_SIZE 256 +#define SEL_NETPORT_HASH_BKT_LIMIT 16 + +struct sel_netport_bkt { + int size; + struct list_head list; +}; + +struct sel_netport { + struct netport_security_struct psec; + + struct list_head list; + struct rcu_head rcu; +}; + +/* NOTE: we are using a combined hash table for both IPv4 and IPv6, the reason + * for this is that I suspect most users will not make heavy use of both + * address families at the same time so one table will usually end up wasted, + * if this becomes a problem we can always add a hash table for each address + * family later */ + +static LIST_HEAD(sel_netport_list); +static DEFINE_SPINLOCK(sel_netport_lock); +static struct sel_netport_bkt sel_netport_hash[SEL_NETPORT_HASH_SIZE]; + +/** + * sel_netport_hashfn - Hashing function for the port table + * @pnum: port number + * + * Description: + * This is the hashing function for the port table, it returns the bucket + * number for the given port. + * + */ +static unsigned int sel_netport_hashfn(u16 pnum) +{ + return (pnum & (SEL_NETPORT_HASH_SIZE - 1)); +} + +/** + * sel_netport_find - Search for a port record + * @protocol: protocol + * @port: pnum + * + * Description: + * Search the network port table and return the matching record. If an entry + * can not be found in the table return NULL. + * + */ +static struct sel_netport *sel_netport_find(u8 protocol, u16 pnum) +{ + unsigned int idx; + struct sel_netport *port; + + idx = sel_netport_hashfn(pnum); + list_for_each_entry_rcu(port, &sel_netport_hash[idx].list, list) + if (port->psec.port == pnum && port->psec.protocol == protocol) + return port; + + return NULL; +} + +/** + * sel_netport_insert - Insert a new port into the table + * @port: the new port record + * + * Description: + * Add a new port record to the network address hash table. + * + */ +static void sel_netport_insert(struct sel_netport *port) +{ + unsigned int idx; + + /* we need to impose a limit on the growth of the hash table so check + * this bucket to make sure it is within the specified bounds */ + idx = sel_netport_hashfn(port->psec.port); + list_add_rcu(&port->list, &sel_netport_hash[idx].list); + if (sel_netport_hash[idx].size == SEL_NETPORT_HASH_BKT_LIMIT) { + struct sel_netport *tail; + tail = list_entry( + rcu_dereference_protected( + sel_netport_hash[idx].list.prev, + lockdep_is_held(&sel_netport_lock)), + struct sel_netport, list); + list_del_rcu(&tail->list); + kfree_rcu(tail, rcu); + } else + sel_netport_hash[idx].size++; +} + +/** + * sel_netport_sid_slow - Lookup the SID of a network address using the policy + * @protocol: protocol + * @pnum: port + * @sid: port SID + * + * Description: + * This function determines the SID of a network port by querying the security + * policy. The result is added to the network port table to speedup future + * queries. Returns zero on success, negative values on failure. + * + */ +static int sel_netport_sid_slow(u8 protocol, u16 pnum, u32 *sid) +{ + int ret; + struct sel_netport *port; + struct sel_netport *new; + + spin_lock_bh(&sel_netport_lock); + port = sel_netport_find(protocol, pnum); + if (port != NULL) { + *sid = port->psec.sid; + spin_unlock_bh(&sel_netport_lock); + return 0; + } + + ret = security_port_sid(&selinux_state, protocol, pnum, sid); + if (ret != 0) + goto out; + new = kzalloc(sizeof(*new), GFP_ATOMIC); + if (new) { + new->psec.port = pnum; + new->psec.protocol = protocol; + new->psec.sid = *sid; + sel_netport_insert(new); + } + +out: + spin_unlock_bh(&sel_netport_lock); + if (unlikely(ret)) + pr_warn("SELinux: failure in %s(), unable to determine network port label\n", + __func__); + return ret; +} + +/** + * sel_netport_sid - Lookup the SID of a network port + * @protocol: protocol + * @pnum: port + * @sid: port SID + * + * Description: + * This function determines the SID of a network port using the fastest method + * possible. First the port table is queried, but if an entry can't be found + * then the policy is queried and the result is added to the table to speedup + * future queries. Returns zero on success, negative values on failure. + * + */ +int sel_netport_sid(u8 protocol, u16 pnum, u32 *sid) +{ + struct sel_netport *port; + + rcu_read_lock(); + port = sel_netport_find(protocol, pnum); + if (port != NULL) { + *sid = port->psec.sid; + rcu_read_unlock(); + return 0; + } + rcu_read_unlock(); + + return sel_netport_sid_slow(protocol, pnum, sid); +} + +/** + * sel_netport_flush - Flush the entire network port table + * + * Description: + * Remove all entries from the network address table. + * + */ +void sel_netport_flush(void) +{ + unsigned int idx; + struct sel_netport *port, *port_tmp; + + spin_lock_bh(&sel_netport_lock); + for (idx = 0; idx < SEL_NETPORT_HASH_SIZE; idx++) { + list_for_each_entry_safe(port, port_tmp, + &sel_netport_hash[idx].list, list) { + list_del_rcu(&port->list); + kfree_rcu(port, rcu); + } + sel_netport_hash[idx].size = 0; + } + spin_unlock_bh(&sel_netport_lock); +} + +static __init int sel_netport_init(void) +{ + int iter; + + if (!selinux_enabled_boot) + return 0; + + for (iter = 0; iter < SEL_NETPORT_HASH_SIZE; iter++) { + INIT_LIST_HEAD(&sel_netport_hash[iter].list); + sel_netport_hash[iter].size = 0; + } + + return 0; +} + +__initcall(sel_netport_init); diff --git a/security/selinux/nlmsgtab.c b/security/selinux/nlmsgtab.c new file mode 100644 index 000000000..c4fb57e90 --- /dev/null +++ b/security/selinux/nlmsgtab.c @@ -0,0 +1,215 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Netlink message type permission tables, for user generated messages. + * + * Author: James Morris <jmorris@redhat.com> + * + * Copyright (C) 2004 Red Hat, Inc., James Morris <jmorris@redhat.com> + */ +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/netlink.h> +#include <linux/rtnetlink.h> +#include <linux/if.h> +#include <linux/inet_diag.h> +#include <linux/xfrm.h> +#include <linux/audit.h> +#include <linux/sock_diag.h> + +#include "flask.h" +#include "av_permissions.h" +#include "security.h" + +struct nlmsg_perm { + u16 nlmsg_type; + u32 perm; +}; + +static const struct nlmsg_perm nlmsg_route_perms[] = +{ + { RTM_NEWLINK, NETLINK_ROUTE_SOCKET__NLMSG_WRITE }, + { RTM_DELLINK, NETLINK_ROUTE_SOCKET__NLMSG_WRITE }, + { RTM_GETLINK, NETLINK_ROUTE_SOCKET__NLMSG_READ }, + { RTM_SETLINK, NETLINK_ROUTE_SOCKET__NLMSG_WRITE }, + { RTM_NEWADDR, NETLINK_ROUTE_SOCKET__NLMSG_WRITE }, + { RTM_DELADDR, NETLINK_ROUTE_SOCKET__NLMSG_WRITE }, + { RTM_GETADDR, NETLINK_ROUTE_SOCKET__NLMSG_READ }, + { RTM_NEWROUTE, NETLINK_ROUTE_SOCKET__NLMSG_WRITE }, + { RTM_DELROUTE, NETLINK_ROUTE_SOCKET__NLMSG_WRITE }, + { RTM_GETROUTE, NETLINK_ROUTE_SOCKET__NLMSG_READ }, + { RTM_NEWNEIGH, NETLINK_ROUTE_SOCKET__NLMSG_WRITE }, + { RTM_DELNEIGH, NETLINK_ROUTE_SOCKET__NLMSG_WRITE }, + { RTM_GETNEIGH, NETLINK_ROUTE_SOCKET__NLMSG_READ }, + { RTM_NEWRULE, NETLINK_ROUTE_SOCKET__NLMSG_WRITE }, + { RTM_DELRULE, NETLINK_ROUTE_SOCKET__NLMSG_WRITE }, + { RTM_GETRULE, NETLINK_ROUTE_SOCKET__NLMSG_READ }, + { RTM_NEWQDISC, NETLINK_ROUTE_SOCKET__NLMSG_WRITE }, + { RTM_DELQDISC, NETLINK_ROUTE_SOCKET__NLMSG_WRITE }, + { RTM_GETQDISC, NETLINK_ROUTE_SOCKET__NLMSG_READ }, + { RTM_NEWTCLASS, NETLINK_ROUTE_SOCKET__NLMSG_WRITE }, + { RTM_DELTCLASS, NETLINK_ROUTE_SOCKET__NLMSG_WRITE }, + { RTM_GETTCLASS, NETLINK_ROUTE_SOCKET__NLMSG_READ }, + { RTM_NEWTFILTER, NETLINK_ROUTE_SOCKET__NLMSG_WRITE }, + { RTM_DELTFILTER, NETLINK_ROUTE_SOCKET__NLMSG_WRITE }, + { RTM_GETTFILTER, NETLINK_ROUTE_SOCKET__NLMSG_READ }, + { RTM_NEWACTION, NETLINK_ROUTE_SOCKET__NLMSG_WRITE }, + { RTM_DELACTION, NETLINK_ROUTE_SOCKET__NLMSG_WRITE }, + { RTM_GETACTION, NETLINK_ROUTE_SOCKET__NLMSG_READ }, + { RTM_NEWPREFIX, NETLINK_ROUTE_SOCKET__NLMSG_WRITE }, + { RTM_GETMULTICAST, NETLINK_ROUTE_SOCKET__NLMSG_READ }, + { RTM_GETANYCAST, NETLINK_ROUTE_SOCKET__NLMSG_READ }, + { RTM_GETNEIGHTBL, NETLINK_ROUTE_SOCKET__NLMSG_READ }, + { RTM_SETNEIGHTBL, NETLINK_ROUTE_SOCKET__NLMSG_WRITE }, + { RTM_NEWADDRLABEL, NETLINK_ROUTE_SOCKET__NLMSG_WRITE }, + { RTM_DELADDRLABEL, NETLINK_ROUTE_SOCKET__NLMSG_WRITE }, + { RTM_GETADDRLABEL, NETLINK_ROUTE_SOCKET__NLMSG_READ }, + { RTM_GETDCB, NETLINK_ROUTE_SOCKET__NLMSG_READ }, + { RTM_SETDCB, NETLINK_ROUTE_SOCKET__NLMSG_WRITE }, + { RTM_NEWNETCONF, NETLINK_ROUTE_SOCKET__NLMSG_WRITE }, + { RTM_DELNETCONF, NETLINK_ROUTE_SOCKET__NLMSG_WRITE }, + { RTM_GETNETCONF, NETLINK_ROUTE_SOCKET__NLMSG_READ }, + { RTM_NEWMDB, NETLINK_ROUTE_SOCKET__NLMSG_WRITE }, + { RTM_DELMDB, NETLINK_ROUTE_SOCKET__NLMSG_WRITE }, + { RTM_GETMDB, NETLINK_ROUTE_SOCKET__NLMSG_READ }, + { RTM_NEWNSID, NETLINK_ROUTE_SOCKET__NLMSG_WRITE }, + { RTM_DELNSID, NETLINK_ROUTE_SOCKET__NLMSG_READ }, + { RTM_GETNSID, NETLINK_ROUTE_SOCKET__NLMSG_READ }, + { RTM_NEWSTATS, NETLINK_ROUTE_SOCKET__NLMSG_READ }, + { RTM_GETSTATS, NETLINK_ROUTE_SOCKET__NLMSG_READ }, + { RTM_NEWCACHEREPORT, NETLINK_ROUTE_SOCKET__NLMSG_READ }, + { RTM_NEWCHAIN, NETLINK_ROUTE_SOCKET__NLMSG_WRITE }, + { RTM_DELCHAIN, NETLINK_ROUTE_SOCKET__NLMSG_WRITE }, + { RTM_GETCHAIN, NETLINK_ROUTE_SOCKET__NLMSG_READ }, + { RTM_NEWNEXTHOP, NETLINK_ROUTE_SOCKET__NLMSG_WRITE }, + { RTM_DELNEXTHOP, NETLINK_ROUTE_SOCKET__NLMSG_WRITE }, + { RTM_GETNEXTHOP, NETLINK_ROUTE_SOCKET__NLMSG_READ }, + { RTM_NEWLINKPROP, NETLINK_ROUTE_SOCKET__NLMSG_WRITE }, + { RTM_DELLINKPROP, NETLINK_ROUTE_SOCKET__NLMSG_WRITE }, + { RTM_NEWVLAN, NETLINK_ROUTE_SOCKET__NLMSG_WRITE }, + { RTM_DELVLAN, NETLINK_ROUTE_SOCKET__NLMSG_WRITE }, + { RTM_GETVLAN, NETLINK_ROUTE_SOCKET__NLMSG_READ }, +}; + +static const struct nlmsg_perm nlmsg_tcpdiag_perms[] = +{ + { TCPDIAG_GETSOCK, NETLINK_TCPDIAG_SOCKET__NLMSG_READ }, + { DCCPDIAG_GETSOCK, NETLINK_TCPDIAG_SOCKET__NLMSG_READ }, + { SOCK_DIAG_BY_FAMILY, NETLINK_TCPDIAG_SOCKET__NLMSG_READ }, + { SOCK_DESTROY, NETLINK_TCPDIAG_SOCKET__NLMSG_WRITE }, +}; + +static const struct nlmsg_perm nlmsg_xfrm_perms[] = +{ + { XFRM_MSG_NEWSA, NETLINK_XFRM_SOCKET__NLMSG_WRITE }, + { XFRM_MSG_DELSA, NETLINK_XFRM_SOCKET__NLMSG_WRITE }, + { XFRM_MSG_GETSA, NETLINK_XFRM_SOCKET__NLMSG_READ }, + { XFRM_MSG_NEWPOLICY, NETLINK_XFRM_SOCKET__NLMSG_WRITE }, + { XFRM_MSG_DELPOLICY, NETLINK_XFRM_SOCKET__NLMSG_WRITE }, + { XFRM_MSG_GETPOLICY, NETLINK_XFRM_SOCKET__NLMSG_READ }, + { XFRM_MSG_ALLOCSPI, NETLINK_XFRM_SOCKET__NLMSG_WRITE }, + { XFRM_MSG_ACQUIRE, NETLINK_XFRM_SOCKET__NLMSG_WRITE }, + { XFRM_MSG_EXPIRE, NETLINK_XFRM_SOCKET__NLMSG_WRITE }, + { XFRM_MSG_UPDPOLICY, NETLINK_XFRM_SOCKET__NLMSG_WRITE }, + { XFRM_MSG_UPDSA, NETLINK_XFRM_SOCKET__NLMSG_WRITE }, + { XFRM_MSG_POLEXPIRE, NETLINK_XFRM_SOCKET__NLMSG_WRITE }, + { XFRM_MSG_FLUSHSA, NETLINK_XFRM_SOCKET__NLMSG_WRITE }, + { XFRM_MSG_FLUSHPOLICY, NETLINK_XFRM_SOCKET__NLMSG_WRITE }, + { XFRM_MSG_NEWAE, NETLINK_XFRM_SOCKET__NLMSG_WRITE }, + { XFRM_MSG_GETAE, NETLINK_XFRM_SOCKET__NLMSG_READ }, + { XFRM_MSG_REPORT, NETLINK_XFRM_SOCKET__NLMSG_READ }, + { XFRM_MSG_MIGRATE, NETLINK_XFRM_SOCKET__NLMSG_WRITE }, + { XFRM_MSG_NEWSADINFO, NETLINK_XFRM_SOCKET__NLMSG_READ }, + { XFRM_MSG_GETSADINFO, NETLINK_XFRM_SOCKET__NLMSG_READ }, + { XFRM_MSG_NEWSPDINFO, NETLINK_XFRM_SOCKET__NLMSG_WRITE }, + { XFRM_MSG_GETSPDINFO, NETLINK_XFRM_SOCKET__NLMSG_READ }, + { XFRM_MSG_MAPPING, NETLINK_XFRM_SOCKET__NLMSG_READ }, + { XFRM_MSG_SETDEFAULT, NETLINK_XFRM_SOCKET__NLMSG_WRITE }, + { XFRM_MSG_GETDEFAULT, NETLINK_XFRM_SOCKET__NLMSG_READ }, +}; + +static const struct nlmsg_perm nlmsg_audit_perms[] = +{ + { AUDIT_GET, NETLINK_AUDIT_SOCKET__NLMSG_READ }, + { AUDIT_SET, NETLINK_AUDIT_SOCKET__NLMSG_WRITE }, + { AUDIT_LIST, NETLINK_AUDIT_SOCKET__NLMSG_READPRIV }, + { AUDIT_ADD, NETLINK_AUDIT_SOCKET__NLMSG_WRITE }, + { AUDIT_DEL, NETLINK_AUDIT_SOCKET__NLMSG_WRITE }, + { AUDIT_LIST_RULES, NETLINK_AUDIT_SOCKET__NLMSG_READPRIV }, + { AUDIT_ADD_RULE, NETLINK_AUDIT_SOCKET__NLMSG_WRITE }, + { AUDIT_DEL_RULE, NETLINK_AUDIT_SOCKET__NLMSG_WRITE }, + { AUDIT_USER, NETLINK_AUDIT_SOCKET__NLMSG_RELAY }, + { AUDIT_SIGNAL_INFO, NETLINK_AUDIT_SOCKET__NLMSG_READ }, + { AUDIT_TRIM, NETLINK_AUDIT_SOCKET__NLMSG_WRITE }, + { AUDIT_MAKE_EQUIV, NETLINK_AUDIT_SOCKET__NLMSG_WRITE }, + { AUDIT_TTY_GET, NETLINK_AUDIT_SOCKET__NLMSG_READ }, + { AUDIT_TTY_SET, NETLINK_AUDIT_SOCKET__NLMSG_TTY_AUDIT }, + { AUDIT_GET_FEATURE, NETLINK_AUDIT_SOCKET__NLMSG_READ }, + { AUDIT_SET_FEATURE, NETLINK_AUDIT_SOCKET__NLMSG_WRITE }, +}; + + +static int nlmsg_perm(u16 nlmsg_type, u32 *perm, const struct nlmsg_perm *tab, size_t tabsize) +{ + int i, err = -EINVAL; + + for (i = 0; i < tabsize/sizeof(struct nlmsg_perm); i++) + if (nlmsg_type == tab[i].nlmsg_type) { + *perm = tab[i].perm; + err = 0; + break; + } + + return err; +} + +int selinux_nlmsg_lookup(u16 sclass, u16 nlmsg_type, u32 *perm) +{ + int err = 0; + + switch (sclass) { + case SECCLASS_NETLINK_ROUTE_SOCKET: + /* RTM_MAX always points to RTM_SETxxxx, ie RTM_NEWxxx + 3. + * If the BUILD_BUG_ON() below fails you must update the + * structures at the top of this file with the new mappings + * before updating the BUILD_BUG_ON() macro! + */ + BUILD_BUG_ON(RTM_MAX != (RTM_NEWVLAN + 3)); + err = nlmsg_perm(nlmsg_type, perm, nlmsg_route_perms, + sizeof(nlmsg_route_perms)); + break; + + case SECCLASS_NETLINK_TCPDIAG_SOCKET: + err = nlmsg_perm(nlmsg_type, perm, nlmsg_tcpdiag_perms, + sizeof(nlmsg_tcpdiag_perms)); + break; + + case SECCLASS_NETLINK_XFRM_SOCKET: + /* If the BUILD_BUG_ON() below fails you must update the + * structures at the top of this file with the new mappings + * before updating the BUILD_BUG_ON() macro! + */ + BUILD_BUG_ON(XFRM_MSG_MAX != XFRM_MSG_GETDEFAULT); + err = nlmsg_perm(nlmsg_type, perm, nlmsg_xfrm_perms, + sizeof(nlmsg_xfrm_perms)); + break; + + case SECCLASS_NETLINK_AUDIT_SOCKET: + if ((nlmsg_type >= AUDIT_FIRST_USER_MSG && + nlmsg_type <= AUDIT_LAST_USER_MSG) || + (nlmsg_type >= AUDIT_FIRST_USER_MSG2 && + nlmsg_type <= AUDIT_LAST_USER_MSG2)) { + *perm = NETLINK_AUDIT_SOCKET__NLMSG_RELAY; + } else { + err = nlmsg_perm(nlmsg_type, perm, nlmsg_audit_perms, + sizeof(nlmsg_audit_perms)); + } + break; + + /* No messaging from userspace, or class unknown/unhandled */ + default: + err = -ENOENT; + break; + } + + return err; +} diff --git a/security/selinux/selinuxfs.c b/security/selinux/selinuxfs.c new file mode 100644 index 000000000..d893c2280 --- /dev/null +++ b/security/selinux/selinuxfs.c @@ -0,0 +1,2257 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Updated: Karl MacMillan <kmacmillan@tresys.com> + * + * Added conditional policy language extensions + * + * Updated: Hewlett-Packard <paul@paul-moore.com> + * + * Added support for the policy capability bitmap + * + * Copyright (C) 2007 Hewlett-Packard Development Company, L.P. + * Copyright (C) 2003 - 2004 Tresys Technology, LLC + * Copyright (C) 2004 Red Hat, Inc., James Morris <jmorris@redhat.com> + */ + +#include <linux/kernel.h> +#include <linux/pagemap.h> +#include <linux/slab.h> +#include <linux/vmalloc.h> +#include <linux/fs.h> +#include <linux/fs_context.h> +#include <linux/mount.h> +#include <linux/mutex.h> +#include <linux/namei.h> +#include <linux/init.h> +#include <linux/string.h> +#include <linux/security.h> +#include <linux/major.h> +#include <linux/seq_file.h> +#include <linux/percpu.h> +#include <linux/audit.h> +#include <linux/uaccess.h> +#include <linux/kobject.h> +#include <linux/ctype.h> + +/* selinuxfs pseudo filesystem for exporting the security policy API. + Based on the proc code and the fs/nfsd/nfsctl.c code. */ + +#include "flask.h" +#include "avc.h" +#include "avc_ss.h" +#include "security.h" +#include "objsec.h" +#include "conditional.h" + +enum sel_inos { + SEL_ROOT_INO = 2, + SEL_LOAD, /* load policy */ + SEL_ENFORCE, /* get or set enforcing status */ + SEL_CONTEXT, /* validate context */ + SEL_ACCESS, /* compute access decision */ + SEL_CREATE, /* compute create labeling decision */ + SEL_RELABEL, /* compute relabeling decision */ + SEL_USER, /* compute reachable user contexts */ + SEL_POLICYVERS, /* return policy version for this kernel */ + SEL_COMMIT_BOOLS, /* commit new boolean values */ + SEL_MLS, /* return if MLS policy is enabled */ + SEL_DISABLE, /* disable SELinux until next reboot */ + SEL_MEMBER, /* compute polyinstantiation membership decision */ + SEL_CHECKREQPROT, /* check requested protection, not kernel-applied one */ + SEL_COMPAT_NET, /* whether to use old compat network packet controls */ + SEL_REJECT_UNKNOWN, /* export unknown reject handling to userspace */ + SEL_DENY_UNKNOWN, /* export unknown deny handling to userspace */ + SEL_STATUS, /* export current status using mmap() */ + SEL_POLICY, /* allow userspace to read the in kernel policy */ + SEL_VALIDATE_TRANS, /* compute validatetrans decision */ + SEL_INO_NEXT, /* The next inode number to use */ +}; + +struct selinux_fs_info { + struct dentry *bool_dir; + unsigned int bool_num; + char **bool_pending_names; + unsigned int *bool_pending_values; + struct dentry *class_dir; + unsigned long last_class_ino; + bool policy_opened; + struct dentry *policycap_dir; + unsigned long last_ino; + struct selinux_state *state; + struct super_block *sb; +}; + +static int selinux_fs_info_create(struct super_block *sb) +{ + struct selinux_fs_info *fsi; + + fsi = kzalloc(sizeof(*fsi), GFP_KERNEL); + if (!fsi) + return -ENOMEM; + + fsi->last_ino = SEL_INO_NEXT - 1; + fsi->state = &selinux_state; + fsi->sb = sb; + sb->s_fs_info = fsi; + return 0; +} + +static void selinux_fs_info_free(struct super_block *sb) +{ + struct selinux_fs_info *fsi = sb->s_fs_info; + int i; + + if (fsi) { + for (i = 0; i < fsi->bool_num; i++) + kfree(fsi->bool_pending_names[i]); + kfree(fsi->bool_pending_names); + kfree(fsi->bool_pending_values); + } + kfree(sb->s_fs_info); + sb->s_fs_info = NULL; +} + +#define SEL_INITCON_INO_OFFSET 0x01000000 +#define SEL_BOOL_INO_OFFSET 0x02000000 +#define SEL_CLASS_INO_OFFSET 0x04000000 +#define SEL_POLICYCAP_INO_OFFSET 0x08000000 +#define SEL_INO_MASK 0x00ffffff + +#define BOOL_DIR_NAME "booleans" +#define CLASS_DIR_NAME "class" +#define POLICYCAP_DIR_NAME "policy_capabilities" + +#define TMPBUFLEN 12 +static ssize_t sel_read_enforce(struct file *filp, char __user *buf, + size_t count, loff_t *ppos) +{ + struct selinux_fs_info *fsi = file_inode(filp)->i_sb->s_fs_info; + char tmpbuf[TMPBUFLEN]; + ssize_t length; + + length = scnprintf(tmpbuf, TMPBUFLEN, "%d", + enforcing_enabled(fsi->state)); + return simple_read_from_buffer(buf, count, ppos, tmpbuf, length); +} + +#ifdef CONFIG_SECURITY_SELINUX_DEVELOP +static ssize_t sel_write_enforce(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) + +{ + struct selinux_fs_info *fsi = file_inode(file)->i_sb->s_fs_info; + struct selinux_state *state = fsi->state; + char *page = NULL; + ssize_t length; + int old_value, new_value; + + if (count >= PAGE_SIZE) + return -ENOMEM; + + /* No partial writes. */ + if (*ppos != 0) + return -EINVAL; + + page = memdup_user_nul(buf, count); + if (IS_ERR(page)) + return PTR_ERR(page); + + length = -EINVAL; + if (sscanf(page, "%d", &new_value) != 1) + goto out; + + new_value = !!new_value; + + old_value = enforcing_enabled(state); + if (new_value != old_value) { + length = avc_has_perm(&selinux_state, + current_sid(), SECINITSID_SECURITY, + SECCLASS_SECURITY, SECURITY__SETENFORCE, + NULL); + if (length) + goto out; + audit_log(audit_context(), GFP_KERNEL, AUDIT_MAC_STATUS, + "enforcing=%d old_enforcing=%d auid=%u ses=%u" + " enabled=1 old-enabled=1 lsm=selinux res=1", + new_value, old_value, + from_kuid(&init_user_ns, audit_get_loginuid(current)), + audit_get_sessionid(current)); + enforcing_set(state, new_value); + if (new_value) + avc_ss_reset(state->avc, 0); + selnl_notify_setenforce(new_value); + selinux_status_update_setenforce(state, new_value); + if (!new_value) + call_blocking_lsm_notifier(LSM_POLICY_CHANGE, NULL); + } + length = count; +out: + kfree(page); + return length; +} +#else +#define sel_write_enforce NULL +#endif + +static const struct file_operations sel_enforce_ops = { + .read = sel_read_enforce, + .write = sel_write_enforce, + .llseek = generic_file_llseek, +}; + +static ssize_t sel_read_handle_unknown(struct file *filp, char __user *buf, + size_t count, loff_t *ppos) +{ + struct selinux_fs_info *fsi = file_inode(filp)->i_sb->s_fs_info; + struct selinux_state *state = fsi->state; + char tmpbuf[TMPBUFLEN]; + ssize_t length; + ino_t ino = file_inode(filp)->i_ino; + int handle_unknown = (ino == SEL_REJECT_UNKNOWN) ? + security_get_reject_unknown(state) : + !security_get_allow_unknown(state); + + length = scnprintf(tmpbuf, TMPBUFLEN, "%d", handle_unknown); + return simple_read_from_buffer(buf, count, ppos, tmpbuf, length); +} + +static const struct file_operations sel_handle_unknown_ops = { + .read = sel_read_handle_unknown, + .llseek = generic_file_llseek, +}; + +static int sel_open_handle_status(struct inode *inode, struct file *filp) +{ + struct selinux_fs_info *fsi = file_inode(filp)->i_sb->s_fs_info; + struct page *status = selinux_kernel_status_page(fsi->state); + + if (!status) + return -ENOMEM; + + filp->private_data = status; + + return 0; +} + +static ssize_t sel_read_handle_status(struct file *filp, char __user *buf, + size_t count, loff_t *ppos) +{ + struct page *status = filp->private_data; + + BUG_ON(!status); + + return simple_read_from_buffer(buf, count, ppos, + page_address(status), + sizeof(struct selinux_kernel_status)); +} + +static int sel_mmap_handle_status(struct file *filp, + struct vm_area_struct *vma) +{ + struct page *status = filp->private_data; + unsigned long size = vma->vm_end - vma->vm_start; + + BUG_ON(!status); + + /* only allows one page from the head */ + if (vma->vm_pgoff > 0 || size != PAGE_SIZE) + return -EIO; + /* disallow writable mapping */ + if (vma->vm_flags & VM_WRITE) + return -EPERM; + /* disallow mprotect() turns it into writable */ + vma->vm_flags &= ~VM_MAYWRITE; + + return remap_pfn_range(vma, vma->vm_start, + page_to_pfn(status), + size, vma->vm_page_prot); +} + +static const struct file_operations sel_handle_status_ops = { + .open = sel_open_handle_status, + .read = sel_read_handle_status, + .mmap = sel_mmap_handle_status, + .llseek = generic_file_llseek, +}; + +#ifdef CONFIG_SECURITY_SELINUX_DISABLE +static ssize_t sel_write_disable(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) + +{ + struct selinux_fs_info *fsi = file_inode(file)->i_sb->s_fs_info; + char *page; + ssize_t length; + int new_value; + int enforcing; + + /* NOTE: we are now officially considering runtime disable as + * deprecated, and using it will become increasingly painful + * (e.g. sleeping/blocking) as we progress through future + * kernel releases until eventually it is removed + */ + pr_err("SELinux: Runtime disable is deprecated, use selinux=0 on the kernel cmdline.\n"); + + if (count >= PAGE_SIZE) + return -ENOMEM; + + /* No partial writes. */ + if (*ppos != 0) + return -EINVAL; + + page = memdup_user_nul(buf, count); + if (IS_ERR(page)) + return PTR_ERR(page); + + length = -EINVAL; + if (sscanf(page, "%d", &new_value) != 1) + goto out; + + if (new_value) { + enforcing = enforcing_enabled(fsi->state); + length = selinux_disable(fsi->state); + if (length) + goto out; + audit_log(audit_context(), GFP_KERNEL, AUDIT_MAC_STATUS, + "enforcing=%d old_enforcing=%d auid=%u ses=%u" + " enabled=0 old-enabled=1 lsm=selinux res=1", + enforcing, enforcing, + from_kuid(&init_user_ns, audit_get_loginuid(current)), + audit_get_sessionid(current)); + } + + length = count; +out: + kfree(page); + return length; +} +#else +#define sel_write_disable NULL +#endif + +static const struct file_operations sel_disable_ops = { + .write = sel_write_disable, + .llseek = generic_file_llseek, +}; + +static ssize_t sel_read_policyvers(struct file *filp, char __user *buf, + size_t count, loff_t *ppos) +{ + char tmpbuf[TMPBUFLEN]; + ssize_t length; + + length = scnprintf(tmpbuf, TMPBUFLEN, "%u", POLICYDB_VERSION_MAX); + return simple_read_from_buffer(buf, count, ppos, tmpbuf, length); +} + +static const struct file_operations sel_policyvers_ops = { + .read = sel_read_policyvers, + .llseek = generic_file_llseek, +}; + +/* declaration for sel_write_load */ +static int sel_make_bools(struct selinux_policy *newpolicy, struct dentry *bool_dir, + unsigned int *bool_num, char ***bool_pending_names, + unsigned int **bool_pending_values); +static int sel_make_classes(struct selinux_policy *newpolicy, + struct dentry *class_dir, + unsigned long *last_class_ino); + +/* declaration for sel_make_class_dirs */ +static struct dentry *sel_make_dir(struct dentry *dir, const char *name, + unsigned long *ino); + +/* declaration for sel_make_policy_nodes */ +static struct dentry *sel_make_disconnected_dir(struct super_block *sb, + unsigned long *ino); + +/* declaration for sel_make_policy_nodes */ +static void sel_remove_entries(struct dentry *de); + +static ssize_t sel_read_mls(struct file *filp, char __user *buf, + size_t count, loff_t *ppos) +{ + struct selinux_fs_info *fsi = file_inode(filp)->i_sb->s_fs_info; + char tmpbuf[TMPBUFLEN]; + ssize_t length; + + length = scnprintf(tmpbuf, TMPBUFLEN, "%d", + security_mls_enabled(fsi->state)); + return simple_read_from_buffer(buf, count, ppos, tmpbuf, length); +} + +static const struct file_operations sel_mls_ops = { + .read = sel_read_mls, + .llseek = generic_file_llseek, +}; + +struct policy_load_memory { + size_t len; + void *data; +}; + +static int sel_open_policy(struct inode *inode, struct file *filp) +{ + struct selinux_fs_info *fsi = inode->i_sb->s_fs_info; + struct selinux_state *state = fsi->state; + struct policy_load_memory *plm = NULL; + int rc; + + BUG_ON(filp->private_data); + + mutex_lock(&fsi->state->policy_mutex); + + rc = avc_has_perm(&selinux_state, + current_sid(), SECINITSID_SECURITY, + SECCLASS_SECURITY, SECURITY__READ_POLICY, NULL); + if (rc) + goto err; + + rc = -EBUSY; + if (fsi->policy_opened) + goto err; + + rc = -ENOMEM; + plm = kzalloc(sizeof(*plm), GFP_KERNEL); + if (!plm) + goto err; + + rc = security_read_policy(state, &plm->data, &plm->len); + if (rc) + goto err; + + if ((size_t)i_size_read(inode) != plm->len) { + inode_lock(inode); + i_size_write(inode, plm->len); + inode_unlock(inode); + } + + fsi->policy_opened = 1; + + filp->private_data = plm; + + mutex_unlock(&fsi->state->policy_mutex); + + return 0; +err: + mutex_unlock(&fsi->state->policy_mutex); + + if (plm) + vfree(plm->data); + kfree(plm); + return rc; +} + +static int sel_release_policy(struct inode *inode, struct file *filp) +{ + struct selinux_fs_info *fsi = inode->i_sb->s_fs_info; + struct policy_load_memory *plm = filp->private_data; + + BUG_ON(!plm); + + fsi->policy_opened = 0; + + vfree(plm->data); + kfree(plm); + + return 0; +} + +static ssize_t sel_read_policy(struct file *filp, char __user *buf, + size_t count, loff_t *ppos) +{ + struct policy_load_memory *plm = filp->private_data; + int ret; + + ret = avc_has_perm(&selinux_state, + current_sid(), SECINITSID_SECURITY, + SECCLASS_SECURITY, SECURITY__READ_POLICY, NULL); + if (ret) + return ret; + + return simple_read_from_buffer(buf, count, ppos, plm->data, plm->len); +} + +static vm_fault_t sel_mmap_policy_fault(struct vm_fault *vmf) +{ + struct policy_load_memory *plm = vmf->vma->vm_file->private_data; + unsigned long offset; + struct page *page; + + if (vmf->flags & (FAULT_FLAG_MKWRITE | FAULT_FLAG_WRITE)) + return VM_FAULT_SIGBUS; + + offset = vmf->pgoff << PAGE_SHIFT; + if (offset >= roundup(plm->len, PAGE_SIZE)) + return VM_FAULT_SIGBUS; + + page = vmalloc_to_page(plm->data + offset); + get_page(page); + + vmf->page = page; + + return 0; +} + +static const struct vm_operations_struct sel_mmap_policy_ops = { + .fault = sel_mmap_policy_fault, + .page_mkwrite = sel_mmap_policy_fault, +}; + +static int sel_mmap_policy(struct file *filp, struct vm_area_struct *vma) +{ + if (vma->vm_flags & VM_SHARED) { + /* do not allow mprotect to make mapping writable */ + vma->vm_flags &= ~VM_MAYWRITE; + + if (vma->vm_flags & VM_WRITE) + return -EACCES; + } + + vma->vm_flags |= VM_DONTEXPAND | VM_DONTDUMP; + vma->vm_ops = &sel_mmap_policy_ops; + + return 0; +} + +static const struct file_operations sel_policy_ops = { + .open = sel_open_policy, + .read = sel_read_policy, + .mmap = sel_mmap_policy, + .release = sel_release_policy, + .llseek = generic_file_llseek, +}; + +static void sel_remove_old_bool_data(unsigned int bool_num, char **bool_names, + unsigned int *bool_values) +{ + u32 i; + + /* bool_dir cleanup */ + for (i = 0; i < bool_num; i++) + kfree(bool_names[i]); + kfree(bool_names); + kfree(bool_values); +} + +static int sel_make_policy_nodes(struct selinux_fs_info *fsi, + struct selinux_policy *newpolicy) +{ + int ret = 0; + struct dentry *tmp_parent, *tmp_bool_dir, *tmp_class_dir, *old_dentry; + unsigned int tmp_bool_num, old_bool_num; + char **tmp_bool_names, **old_bool_names; + unsigned int *tmp_bool_values, *old_bool_values; + unsigned long tmp_ino = fsi->last_ino; /* Don't increment last_ino in this function */ + + tmp_parent = sel_make_disconnected_dir(fsi->sb, &tmp_ino); + if (IS_ERR(tmp_parent)) + return PTR_ERR(tmp_parent); + + tmp_ino = fsi->bool_dir->d_inode->i_ino - 1; /* sel_make_dir will increment and set */ + tmp_bool_dir = sel_make_dir(tmp_parent, BOOL_DIR_NAME, &tmp_ino); + if (IS_ERR(tmp_bool_dir)) { + ret = PTR_ERR(tmp_bool_dir); + goto out; + } + + tmp_ino = fsi->class_dir->d_inode->i_ino - 1; /* sel_make_dir will increment and set */ + tmp_class_dir = sel_make_dir(tmp_parent, CLASS_DIR_NAME, &tmp_ino); + if (IS_ERR(tmp_class_dir)) { + ret = PTR_ERR(tmp_class_dir); + goto out; + } + + ret = sel_make_bools(newpolicy, tmp_bool_dir, &tmp_bool_num, + &tmp_bool_names, &tmp_bool_values); + if (ret) { + pr_err("SELinux: failed to load policy booleans\n"); + goto out; + } + + ret = sel_make_classes(newpolicy, tmp_class_dir, + &fsi->last_class_ino); + if (ret) { + pr_err("SELinux: failed to load policy classes\n"); + goto out; + } + + /* booleans */ + old_dentry = fsi->bool_dir; + lock_rename(tmp_bool_dir, old_dentry); + d_exchange(tmp_bool_dir, fsi->bool_dir); + + old_bool_num = fsi->bool_num; + old_bool_names = fsi->bool_pending_names; + old_bool_values = fsi->bool_pending_values; + + fsi->bool_num = tmp_bool_num; + fsi->bool_pending_names = tmp_bool_names; + fsi->bool_pending_values = tmp_bool_values; + + sel_remove_old_bool_data(old_bool_num, old_bool_names, old_bool_values); + + fsi->bool_dir = tmp_bool_dir; + unlock_rename(tmp_bool_dir, old_dentry); + + /* classes */ + old_dentry = fsi->class_dir; + lock_rename(tmp_class_dir, old_dentry); + d_exchange(tmp_class_dir, fsi->class_dir); + fsi->class_dir = tmp_class_dir; + unlock_rename(tmp_class_dir, old_dentry); + +out: + /* Since the other temporary dirs are children of tmp_parent + * this will handle all the cleanup in the case of a failure before + * the swapover + */ + sel_remove_entries(tmp_parent); + dput(tmp_parent); /* d_genocide() only handles the children */ + + return ret; +} + +static ssize_t sel_write_load(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) + +{ + struct selinux_fs_info *fsi = file_inode(file)->i_sb->s_fs_info; + struct selinux_load_state load_state; + ssize_t length; + void *data = NULL; + + mutex_lock(&fsi->state->policy_mutex); + + length = avc_has_perm(&selinux_state, + current_sid(), SECINITSID_SECURITY, + SECCLASS_SECURITY, SECURITY__LOAD_POLICY, NULL); + if (length) + goto out; + + /* No partial writes. */ + length = -EINVAL; + if (*ppos != 0) + goto out; + + length = -ENOMEM; + data = vmalloc(count); + if (!data) + goto out; + + length = -EFAULT; + if (copy_from_user(data, buf, count) != 0) + goto out; + + length = security_load_policy(fsi->state, data, count, &load_state); + if (length) { + pr_warn_ratelimited("SELinux: failed to load policy\n"); + goto out; + } + + length = sel_make_policy_nodes(fsi, load_state.policy); + if (length) { + selinux_policy_cancel(fsi->state, &load_state); + goto out; + } + + selinux_policy_commit(fsi->state, &load_state); + + length = count; + + audit_log(audit_context(), GFP_KERNEL, AUDIT_MAC_POLICY_LOAD, + "auid=%u ses=%u lsm=selinux res=1", + from_kuid(&init_user_ns, audit_get_loginuid(current)), + audit_get_sessionid(current)); +out: + mutex_unlock(&fsi->state->policy_mutex); + vfree(data); + return length; +} + +static const struct file_operations sel_load_ops = { + .write = sel_write_load, + .llseek = generic_file_llseek, +}; + +static ssize_t sel_write_context(struct file *file, char *buf, size_t size) +{ + struct selinux_fs_info *fsi = file_inode(file)->i_sb->s_fs_info; + struct selinux_state *state = fsi->state; + char *canon = NULL; + u32 sid, len; + ssize_t length; + + length = avc_has_perm(&selinux_state, + current_sid(), SECINITSID_SECURITY, + SECCLASS_SECURITY, SECURITY__CHECK_CONTEXT, NULL); + if (length) + goto out; + + length = security_context_to_sid(state, buf, size, &sid, GFP_KERNEL); + if (length) + goto out; + + length = security_sid_to_context(state, sid, &canon, &len); + if (length) + goto out; + + length = -ERANGE; + if (len > SIMPLE_TRANSACTION_LIMIT) { + pr_err("SELinux: %s: context size (%u) exceeds " + "payload max\n", __func__, len); + goto out; + } + + memcpy(buf, canon, len); + length = len; +out: + kfree(canon); + return length; +} + +static ssize_t sel_read_checkreqprot(struct file *filp, char __user *buf, + size_t count, loff_t *ppos) +{ + struct selinux_fs_info *fsi = file_inode(filp)->i_sb->s_fs_info; + char tmpbuf[TMPBUFLEN]; + ssize_t length; + + length = scnprintf(tmpbuf, TMPBUFLEN, "%u", + checkreqprot_get(fsi->state)); + return simple_read_from_buffer(buf, count, ppos, tmpbuf, length); +} + +static ssize_t sel_write_checkreqprot(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + struct selinux_fs_info *fsi = file_inode(file)->i_sb->s_fs_info; + char *page; + ssize_t length; + unsigned int new_value; + + length = avc_has_perm(&selinux_state, + current_sid(), SECINITSID_SECURITY, + SECCLASS_SECURITY, SECURITY__SETCHECKREQPROT, + NULL); + if (length) + return length; + + if (count >= PAGE_SIZE) + return -ENOMEM; + + /* No partial writes. */ + if (*ppos != 0) + return -EINVAL; + + page = memdup_user_nul(buf, count); + if (IS_ERR(page)) + return PTR_ERR(page); + + length = -EINVAL; + if (sscanf(page, "%u", &new_value) != 1) + goto out; + + if (new_value) { + char comm[sizeof(current->comm)]; + + memcpy(comm, current->comm, sizeof(comm)); + pr_warn_once("SELinux: %s (%d) set checkreqprot to 1. This is deprecated and will be rejected in a future kernel release.\n", + comm, current->pid); + } + + checkreqprot_set(fsi->state, (new_value ? 1 : 0)); + length = count; +out: + kfree(page); + return length; +} +static const struct file_operations sel_checkreqprot_ops = { + .read = sel_read_checkreqprot, + .write = sel_write_checkreqprot, + .llseek = generic_file_llseek, +}; + +static ssize_t sel_write_validatetrans(struct file *file, + const char __user *buf, + size_t count, loff_t *ppos) +{ + struct selinux_fs_info *fsi = file_inode(file)->i_sb->s_fs_info; + struct selinux_state *state = fsi->state; + char *oldcon = NULL, *newcon = NULL, *taskcon = NULL; + char *req = NULL; + u32 osid, nsid, tsid; + u16 tclass; + int rc; + + rc = avc_has_perm(&selinux_state, + current_sid(), SECINITSID_SECURITY, + SECCLASS_SECURITY, SECURITY__VALIDATE_TRANS, NULL); + if (rc) + goto out; + + rc = -ENOMEM; + if (count >= PAGE_SIZE) + goto out; + + /* No partial writes. */ + rc = -EINVAL; + if (*ppos != 0) + goto out; + + req = memdup_user_nul(buf, count); + if (IS_ERR(req)) { + rc = PTR_ERR(req); + req = NULL; + goto out; + } + + rc = -ENOMEM; + oldcon = kzalloc(count + 1, GFP_KERNEL); + if (!oldcon) + goto out; + + newcon = kzalloc(count + 1, GFP_KERNEL); + if (!newcon) + goto out; + + taskcon = kzalloc(count + 1, GFP_KERNEL); + if (!taskcon) + goto out; + + rc = -EINVAL; + if (sscanf(req, "%s %s %hu %s", oldcon, newcon, &tclass, taskcon) != 4) + goto out; + + rc = security_context_str_to_sid(state, oldcon, &osid, GFP_KERNEL); + if (rc) + goto out; + + rc = security_context_str_to_sid(state, newcon, &nsid, GFP_KERNEL); + if (rc) + goto out; + + rc = security_context_str_to_sid(state, taskcon, &tsid, GFP_KERNEL); + if (rc) + goto out; + + rc = security_validate_transition_user(state, osid, nsid, tsid, tclass); + if (!rc) + rc = count; +out: + kfree(req); + kfree(oldcon); + kfree(newcon); + kfree(taskcon); + return rc; +} + +static const struct file_operations sel_transition_ops = { + .write = sel_write_validatetrans, + .llseek = generic_file_llseek, +}; + +/* + * Remaining nodes use transaction based IO methods like nfsd/nfsctl.c + */ +static ssize_t sel_write_access(struct file *file, char *buf, size_t size); +static ssize_t sel_write_create(struct file *file, char *buf, size_t size); +static ssize_t sel_write_relabel(struct file *file, char *buf, size_t size); +static ssize_t sel_write_user(struct file *file, char *buf, size_t size); +static ssize_t sel_write_member(struct file *file, char *buf, size_t size); + +static ssize_t (*const write_op[])(struct file *, char *, size_t) = { + [SEL_ACCESS] = sel_write_access, + [SEL_CREATE] = sel_write_create, + [SEL_RELABEL] = sel_write_relabel, + [SEL_USER] = sel_write_user, + [SEL_MEMBER] = sel_write_member, + [SEL_CONTEXT] = sel_write_context, +}; + +static ssize_t selinux_transaction_write(struct file *file, const char __user *buf, size_t size, loff_t *pos) +{ + ino_t ino = file_inode(file)->i_ino; + char *data; + ssize_t rv; + + if (ino >= ARRAY_SIZE(write_op) || !write_op[ino]) + return -EINVAL; + + data = simple_transaction_get(file, buf, size); + if (IS_ERR(data)) + return PTR_ERR(data); + + rv = write_op[ino](file, data, size); + if (rv > 0) { + simple_transaction_set(file, rv); + rv = size; + } + return rv; +} + +static const struct file_operations transaction_ops = { + .write = selinux_transaction_write, + .read = simple_transaction_read, + .release = simple_transaction_release, + .llseek = generic_file_llseek, +}; + +/* + * payload - write methods + * If the method has a response, the response should be put in buf, + * and the length returned. Otherwise return 0 or and -error. + */ + +static ssize_t sel_write_access(struct file *file, char *buf, size_t size) +{ + struct selinux_fs_info *fsi = file_inode(file)->i_sb->s_fs_info; + struct selinux_state *state = fsi->state; + char *scon = NULL, *tcon = NULL; + u32 ssid, tsid; + u16 tclass; + struct av_decision avd; + ssize_t length; + + length = avc_has_perm(&selinux_state, + current_sid(), SECINITSID_SECURITY, + SECCLASS_SECURITY, SECURITY__COMPUTE_AV, NULL); + if (length) + goto out; + + length = -ENOMEM; + scon = kzalloc(size + 1, GFP_KERNEL); + if (!scon) + goto out; + + length = -ENOMEM; + tcon = kzalloc(size + 1, GFP_KERNEL); + if (!tcon) + goto out; + + length = -EINVAL; + if (sscanf(buf, "%s %s %hu", scon, tcon, &tclass) != 3) + goto out; + + length = security_context_str_to_sid(state, scon, &ssid, GFP_KERNEL); + if (length) + goto out; + + length = security_context_str_to_sid(state, tcon, &tsid, GFP_KERNEL); + if (length) + goto out; + + security_compute_av_user(state, ssid, tsid, tclass, &avd); + + length = scnprintf(buf, SIMPLE_TRANSACTION_LIMIT, + "%x %x %x %x %u %x", + avd.allowed, 0xffffffff, + avd.auditallow, avd.auditdeny, + avd.seqno, avd.flags); +out: + kfree(tcon); + kfree(scon); + return length; +} + +static ssize_t sel_write_create(struct file *file, char *buf, size_t size) +{ + struct selinux_fs_info *fsi = file_inode(file)->i_sb->s_fs_info; + struct selinux_state *state = fsi->state; + char *scon = NULL, *tcon = NULL; + char *namebuf = NULL, *objname = NULL; + u32 ssid, tsid, newsid; + u16 tclass; + ssize_t length; + char *newcon = NULL; + u32 len; + int nargs; + + length = avc_has_perm(&selinux_state, + current_sid(), SECINITSID_SECURITY, + SECCLASS_SECURITY, SECURITY__COMPUTE_CREATE, + NULL); + if (length) + goto out; + + length = -ENOMEM; + scon = kzalloc(size + 1, GFP_KERNEL); + if (!scon) + goto out; + + length = -ENOMEM; + tcon = kzalloc(size + 1, GFP_KERNEL); + if (!tcon) + goto out; + + length = -ENOMEM; + namebuf = kzalloc(size + 1, GFP_KERNEL); + if (!namebuf) + goto out; + + length = -EINVAL; + nargs = sscanf(buf, "%s %s %hu %s", scon, tcon, &tclass, namebuf); + if (nargs < 3 || nargs > 4) + goto out; + if (nargs == 4) { + /* + * If and when the name of new object to be queried contains + * either whitespace or multibyte characters, they shall be + * encoded based on the percentage-encoding rule. + * If not encoded, the sscanf logic picks up only left-half + * of the supplied name; splitted by a whitespace unexpectedly. + */ + char *r, *w; + int c1, c2; + + r = w = namebuf; + do { + c1 = *r++; + if (c1 == '+') + c1 = ' '; + else if (c1 == '%') { + c1 = hex_to_bin(*r++); + if (c1 < 0) + goto out; + c2 = hex_to_bin(*r++); + if (c2 < 0) + goto out; + c1 = (c1 << 4) | c2; + } + *w++ = c1; + } while (c1 != '\0'); + + objname = namebuf; + } + + length = security_context_str_to_sid(state, scon, &ssid, GFP_KERNEL); + if (length) + goto out; + + length = security_context_str_to_sid(state, tcon, &tsid, GFP_KERNEL); + if (length) + goto out; + + length = security_transition_sid_user(state, ssid, tsid, tclass, + objname, &newsid); + if (length) + goto out; + + length = security_sid_to_context(state, newsid, &newcon, &len); + if (length) + goto out; + + length = -ERANGE; + if (len > SIMPLE_TRANSACTION_LIMIT) { + pr_err("SELinux: %s: context size (%u) exceeds " + "payload max\n", __func__, len); + goto out; + } + + memcpy(buf, newcon, len); + length = len; +out: + kfree(newcon); + kfree(namebuf); + kfree(tcon); + kfree(scon); + return length; +} + +static ssize_t sel_write_relabel(struct file *file, char *buf, size_t size) +{ + struct selinux_fs_info *fsi = file_inode(file)->i_sb->s_fs_info; + struct selinux_state *state = fsi->state; + char *scon = NULL, *tcon = NULL; + u32 ssid, tsid, newsid; + u16 tclass; + ssize_t length; + char *newcon = NULL; + u32 len; + + length = avc_has_perm(&selinux_state, + current_sid(), SECINITSID_SECURITY, + SECCLASS_SECURITY, SECURITY__COMPUTE_RELABEL, + NULL); + if (length) + goto out; + + length = -ENOMEM; + scon = kzalloc(size + 1, GFP_KERNEL); + if (!scon) + goto out; + + length = -ENOMEM; + tcon = kzalloc(size + 1, GFP_KERNEL); + if (!tcon) + goto out; + + length = -EINVAL; + if (sscanf(buf, "%s %s %hu", scon, tcon, &tclass) != 3) + goto out; + + length = security_context_str_to_sid(state, scon, &ssid, GFP_KERNEL); + if (length) + goto out; + + length = security_context_str_to_sid(state, tcon, &tsid, GFP_KERNEL); + if (length) + goto out; + + length = security_change_sid(state, ssid, tsid, tclass, &newsid); + if (length) + goto out; + + length = security_sid_to_context(state, newsid, &newcon, &len); + if (length) + goto out; + + length = -ERANGE; + if (len > SIMPLE_TRANSACTION_LIMIT) + goto out; + + memcpy(buf, newcon, len); + length = len; +out: + kfree(newcon); + kfree(tcon); + kfree(scon); + return length; +} + +static ssize_t sel_write_user(struct file *file, char *buf, size_t size) +{ + struct selinux_fs_info *fsi = file_inode(file)->i_sb->s_fs_info; + struct selinux_state *state = fsi->state; + char *con = NULL, *user = NULL, *ptr; + u32 sid, *sids = NULL; + ssize_t length; + char *newcon; + int i, rc; + u32 len, nsids; + + length = avc_has_perm(&selinux_state, + current_sid(), SECINITSID_SECURITY, + SECCLASS_SECURITY, SECURITY__COMPUTE_USER, + NULL); + if (length) + goto out; + + length = -ENOMEM; + con = kzalloc(size + 1, GFP_KERNEL); + if (!con) + goto out; + + length = -ENOMEM; + user = kzalloc(size + 1, GFP_KERNEL); + if (!user) + goto out; + + length = -EINVAL; + if (sscanf(buf, "%s %s", con, user) != 2) + goto out; + + length = security_context_str_to_sid(state, con, &sid, GFP_KERNEL); + if (length) + goto out; + + length = security_get_user_sids(state, sid, user, &sids, &nsids); + if (length) + goto out; + + length = sprintf(buf, "%u", nsids) + 1; + ptr = buf + length; + for (i = 0; i < nsids; i++) { + rc = security_sid_to_context(state, sids[i], &newcon, &len); + if (rc) { + length = rc; + goto out; + } + if ((length + len) >= SIMPLE_TRANSACTION_LIMIT) { + kfree(newcon); + length = -ERANGE; + goto out; + } + memcpy(ptr, newcon, len); + kfree(newcon); + ptr += len; + length += len; + } +out: + kfree(sids); + kfree(user); + kfree(con); + return length; +} + +static ssize_t sel_write_member(struct file *file, char *buf, size_t size) +{ + struct selinux_fs_info *fsi = file_inode(file)->i_sb->s_fs_info; + struct selinux_state *state = fsi->state; + char *scon = NULL, *tcon = NULL; + u32 ssid, tsid, newsid; + u16 tclass; + ssize_t length; + char *newcon = NULL; + u32 len; + + length = avc_has_perm(&selinux_state, + current_sid(), SECINITSID_SECURITY, + SECCLASS_SECURITY, SECURITY__COMPUTE_MEMBER, + NULL); + if (length) + goto out; + + length = -ENOMEM; + scon = kzalloc(size + 1, GFP_KERNEL); + if (!scon) + goto out; + + length = -ENOMEM; + tcon = kzalloc(size + 1, GFP_KERNEL); + if (!tcon) + goto out; + + length = -EINVAL; + if (sscanf(buf, "%s %s %hu", scon, tcon, &tclass) != 3) + goto out; + + length = security_context_str_to_sid(state, scon, &ssid, GFP_KERNEL); + if (length) + goto out; + + length = security_context_str_to_sid(state, tcon, &tsid, GFP_KERNEL); + if (length) + goto out; + + length = security_member_sid(state, ssid, tsid, tclass, &newsid); + if (length) + goto out; + + length = security_sid_to_context(state, newsid, &newcon, &len); + if (length) + goto out; + + length = -ERANGE; + if (len > SIMPLE_TRANSACTION_LIMIT) { + pr_err("SELinux: %s: context size (%u) exceeds " + "payload max\n", __func__, len); + goto out; + } + + memcpy(buf, newcon, len); + length = len; +out: + kfree(newcon); + kfree(tcon); + kfree(scon); + return length; +} + +static struct inode *sel_make_inode(struct super_block *sb, int mode) +{ + struct inode *ret = new_inode(sb); + + if (ret) { + ret->i_mode = mode; + ret->i_atime = ret->i_mtime = ret->i_ctime = current_time(ret); + } + return ret; +} + +static ssize_t sel_read_bool(struct file *filep, char __user *buf, + size_t count, loff_t *ppos) +{ + struct selinux_fs_info *fsi = file_inode(filep)->i_sb->s_fs_info; + char *page = NULL; + ssize_t length; + ssize_t ret; + int cur_enforcing; + unsigned index = file_inode(filep)->i_ino & SEL_INO_MASK; + const char *name = filep->f_path.dentry->d_name.name; + + mutex_lock(&fsi->state->policy_mutex); + + ret = -EINVAL; + if (index >= fsi->bool_num || strcmp(name, + fsi->bool_pending_names[index])) + goto out_unlock; + + ret = -ENOMEM; + page = (char *)get_zeroed_page(GFP_KERNEL); + if (!page) + goto out_unlock; + + cur_enforcing = security_get_bool_value(fsi->state, index); + if (cur_enforcing < 0) { + ret = cur_enforcing; + goto out_unlock; + } + length = scnprintf(page, PAGE_SIZE, "%d %d", cur_enforcing, + fsi->bool_pending_values[index]); + mutex_unlock(&fsi->state->policy_mutex); + ret = simple_read_from_buffer(buf, count, ppos, page, length); +out_free: + free_page((unsigned long)page); + return ret; + +out_unlock: + mutex_unlock(&fsi->state->policy_mutex); + goto out_free; +} + +static ssize_t sel_write_bool(struct file *filep, const char __user *buf, + size_t count, loff_t *ppos) +{ + struct selinux_fs_info *fsi = file_inode(filep)->i_sb->s_fs_info; + char *page = NULL; + ssize_t length; + int new_value; + unsigned index = file_inode(filep)->i_ino & SEL_INO_MASK; + const char *name = filep->f_path.dentry->d_name.name; + + if (count >= PAGE_SIZE) + return -ENOMEM; + + /* No partial writes. */ + if (*ppos != 0) + return -EINVAL; + + page = memdup_user_nul(buf, count); + if (IS_ERR(page)) + return PTR_ERR(page); + + mutex_lock(&fsi->state->policy_mutex); + + length = avc_has_perm(&selinux_state, + current_sid(), SECINITSID_SECURITY, + SECCLASS_SECURITY, SECURITY__SETBOOL, + NULL); + if (length) + goto out; + + length = -EINVAL; + if (index >= fsi->bool_num || strcmp(name, + fsi->bool_pending_names[index])) + goto out; + + length = -EINVAL; + if (sscanf(page, "%d", &new_value) != 1) + goto out; + + if (new_value) + new_value = 1; + + fsi->bool_pending_values[index] = new_value; + length = count; + +out: + mutex_unlock(&fsi->state->policy_mutex); + kfree(page); + return length; +} + +static const struct file_operations sel_bool_ops = { + .read = sel_read_bool, + .write = sel_write_bool, + .llseek = generic_file_llseek, +}; + +static ssize_t sel_commit_bools_write(struct file *filep, + const char __user *buf, + size_t count, loff_t *ppos) +{ + struct selinux_fs_info *fsi = file_inode(filep)->i_sb->s_fs_info; + char *page = NULL; + ssize_t length; + int new_value; + + if (count >= PAGE_SIZE) + return -ENOMEM; + + /* No partial writes. */ + if (*ppos != 0) + return -EINVAL; + + page = memdup_user_nul(buf, count); + if (IS_ERR(page)) + return PTR_ERR(page); + + mutex_lock(&fsi->state->policy_mutex); + + length = avc_has_perm(&selinux_state, + current_sid(), SECINITSID_SECURITY, + SECCLASS_SECURITY, SECURITY__SETBOOL, + NULL); + if (length) + goto out; + + length = -EINVAL; + if (sscanf(page, "%d", &new_value) != 1) + goto out; + + length = 0; + if (new_value && fsi->bool_pending_values) + length = security_set_bools(fsi->state, fsi->bool_num, + fsi->bool_pending_values); + + if (!length) + length = count; + +out: + mutex_unlock(&fsi->state->policy_mutex); + kfree(page); + return length; +} + +static const struct file_operations sel_commit_bools_ops = { + .write = sel_commit_bools_write, + .llseek = generic_file_llseek, +}; + +static void sel_remove_entries(struct dentry *de) +{ + d_genocide(de); + shrink_dcache_parent(de); +} + +static int sel_make_bools(struct selinux_policy *newpolicy, struct dentry *bool_dir, + unsigned int *bool_num, char ***bool_pending_names, + unsigned int **bool_pending_values) +{ + int ret; + ssize_t len; + struct dentry *dentry = NULL; + struct inode *inode = NULL; + struct inode_security_struct *isec; + char **names = NULL, *page; + u32 i, num; + int *values = NULL; + u32 sid; + + ret = -ENOMEM; + page = (char *)get_zeroed_page(GFP_KERNEL); + if (!page) + goto out; + + ret = security_get_bools(newpolicy, &num, &names, &values); + if (ret) + goto out; + + for (i = 0; i < num; i++) { + ret = -ENOMEM; + dentry = d_alloc_name(bool_dir, names[i]); + if (!dentry) + goto out; + + ret = -ENOMEM; + inode = sel_make_inode(bool_dir->d_sb, S_IFREG | S_IRUGO | S_IWUSR); + if (!inode) { + dput(dentry); + goto out; + } + + ret = -ENAMETOOLONG; + len = snprintf(page, PAGE_SIZE, "/%s/%s", BOOL_DIR_NAME, names[i]); + if (len >= PAGE_SIZE) { + dput(dentry); + iput(inode); + goto out; + } + + isec = selinux_inode(inode); + ret = selinux_policy_genfs_sid(newpolicy, "selinuxfs", page, + SECCLASS_FILE, &sid); + if (ret) { + pr_warn_ratelimited("SELinux: no sid found, defaulting to security isid for %s\n", + page); + sid = SECINITSID_SECURITY; + } + + isec->sid = sid; + isec->initialized = LABEL_INITIALIZED; + inode->i_fop = &sel_bool_ops; + inode->i_ino = i|SEL_BOOL_INO_OFFSET; + d_add(dentry, inode); + } + *bool_num = num; + *bool_pending_names = names; + *bool_pending_values = values; + + free_page((unsigned long)page); + return 0; +out: + free_page((unsigned long)page); + + if (names) { + for (i = 0; i < num; i++) + kfree(names[i]); + kfree(names); + } + kfree(values); + sel_remove_entries(bool_dir); + + return ret; +} + +static ssize_t sel_read_avc_cache_threshold(struct file *filp, char __user *buf, + size_t count, loff_t *ppos) +{ + struct selinux_fs_info *fsi = file_inode(filp)->i_sb->s_fs_info; + struct selinux_state *state = fsi->state; + char tmpbuf[TMPBUFLEN]; + ssize_t length; + + length = scnprintf(tmpbuf, TMPBUFLEN, "%u", + avc_get_cache_threshold(state->avc)); + return simple_read_from_buffer(buf, count, ppos, tmpbuf, length); +} + +static ssize_t sel_write_avc_cache_threshold(struct file *file, + const char __user *buf, + size_t count, loff_t *ppos) + +{ + struct selinux_fs_info *fsi = file_inode(file)->i_sb->s_fs_info; + struct selinux_state *state = fsi->state; + char *page; + ssize_t ret; + unsigned int new_value; + + ret = avc_has_perm(&selinux_state, + current_sid(), SECINITSID_SECURITY, + SECCLASS_SECURITY, SECURITY__SETSECPARAM, + NULL); + if (ret) + return ret; + + if (count >= PAGE_SIZE) + return -ENOMEM; + + /* No partial writes. */ + if (*ppos != 0) + return -EINVAL; + + page = memdup_user_nul(buf, count); + if (IS_ERR(page)) + return PTR_ERR(page); + + ret = -EINVAL; + if (sscanf(page, "%u", &new_value) != 1) + goto out; + + avc_set_cache_threshold(state->avc, new_value); + + ret = count; +out: + kfree(page); + return ret; +} + +static ssize_t sel_read_avc_hash_stats(struct file *filp, char __user *buf, + size_t count, loff_t *ppos) +{ + struct selinux_fs_info *fsi = file_inode(filp)->i_sb->s_fs_info; + struct selinux_state *state = fsi->state; + char *page; + ssize_t length; + + page = (char *)__get_free_page(GFP_KERNEL); + if (!page) + return -ENOMEM; + + length = avc_get_hash_stats(state->avc, page); + if (length >= 0) + length = simple_read_from_buffer(buf, count, ppos, page, length); + free_page((unsigned long)page); + + return length; +} + +static ssize_t sel_read_sidtab_hash_stats(struct file *filp, char __user *buf, + size_t count, loff_t *ppos) +{ + struct selinux_fs_info *fsi = file_inode(filp)->i_sb->s_fs_info; + struct selinux_state *state = fsi->state; + char *page; + ssize_t length; + + page = (char *)__get_free_page(GFP_KERNEL); + if (!page) + return -ENOMEM; + + length = security_sidtab_hash_stats(state, page); + if (length >= 0) + length = simple_read_from_buffer(buf, count, ppos, page, + length); + free_page((unsigned long)page); + + return length; +} + +static const struct file_operations sel_sidtab_hash_stats_ops = { + .read = sel_read_sidtab_hash_stats, + .llseek = generic_file_llseek, +}; + +static const struct file_operations sel_avc_cache_threshold_ops = { + .read = sel_read_avc_cache_threshold, + .write = sel_write_avc_cache_threshold, + .llseek = generic_file_llseek, +}; + +static const struct file_operations sel_avc_hash_stats_ops = { + .read = sel_read_avc_hash_stats, + .llseek = generic_file_llseek, +}; + +#ifdef CONFIG_SECURITY_SELINUX_AVC_STATS +static struct avc_cache_stats *sel_avc_get_stat_idx(loff_t *idx) +{ + int cpu; + + for (cpu = *idx; cpu < nr_cpu_ids; ++cpu) { + if (!cpu_possible(cpu)) + continue; + *idx = cpu + 1; + return &per_cpu(avc_cache_stats, cpu); + } + (*idx)++; + return NULL; +} + +static void *sel_avc_stats_seq_start(struct seq_file *seq, loff_t *pos) +{ + loff_t n = *pos - 1; + + if (*pos == 0) + return SEQ_START_TOKEN; + + return sel_avc_get_stat_idx(&n); +} + +static void *sel_avc_stats_seq_next(struct seq_file *seq, void *v, loff_t *pos) +{ + return sel_avc_get_stat_idx(pos); +} + +static int sel_avc_stats_seq_show(struct seq_file *seq, void *v) +{ + struct avc_cache_stats *st = v; + + if (v == SEQ_START_TOKEN) { + seq_puts(seq, + "lookups hits misses allocations reclaims frees\n"); + } else { + unsigned int lookups = st->lookups; + unsigned int misses = st->misses; + unsigned int hits = lookups - misses; + seq_printf(seq, "%u %u %u %u %u %u\n", lookups, + hits, misses, st->allocations, + st->reclaims, st->frees); + } + return 0; +} + +static void sel_avc_stats_seq_stop(struct seq_file *seq, void *v) +{ } + +static const struct seq_operations sel_avc_cache_stats_seq_ops = { + .start = sel_avc_stats_seq_start, + .next = sel_avc_stats_seq_next, + .show = sel_avc_stats_seq_show, + .stop = sel_avc_stats_seq_stop, +}; + +static int sel_open_avc_cache_stats(struct inode *inode, struct file *file) +{ + return seq_open(file, &sel_avc_cache_stats_seq_ops); +} + +static const struct file_operations sel_avc_cache_stats_ops = { + .open = sel_open_avc_cache_stats, + .read = seq_read, + .llseek = seq_lseek, + .release = seq_release, +}; +#endif + +static int sel_make_avc_files(struct dentry *dir) +{ + struct super_block *sb = dir->d_sb; + struct selinux_fs_info *fsi = sb->s_fs_info; + int i; + static const struct tree_descr files[] = { + { "cache_threshold", + &sel_avc_cache_threshold_ops, S_IRUGO|S_IWUSR }, + { "hash_stats", &sel_avc_hash_stats_ops, S_IRUGO }, +#ifdef CONFIG_SECURITY_SELINUX_AVC_STATS + { "cache_stats", &sel_avc_cache_stats_ops, S_IRUGO }, +#endif + }; + + for (i = 0; i < ARRAY_SIZE(files); i++) { + struct inode *inode; + struct dentry *dentry; + + dentry = d_alloc_name(dir, files[i].name); + if (!dentry) + return -ENOMEM; + + inode = sel_make_inode(dir->d_sb, S_IFREG|files[i].mode); + if (!inode) { + dput(dentry); + return -ENOMEM; + } + + inode->i_fop = files[i].ops; + inode->i_ino = ++fsi->last_ino; + d_add(dentry, inode); + } + + return 0; +} + +static int sel_make_ss_files(struct dentry *dir) +{ + struct super_block *sb = dir->d_sb; + struct selinux_fs_info *fsi = sb->s_fs_info; + int i; + static struct tree_descr files[] = { + { "sidtab_hash_stats", &sel_sidtab_hash_stats_ops, S_IRUGO }, + }; + + for (i = 0; i < ARRAY_SIZE(files); i++) { + struct inode *inode; + struct dentry *dentry; + + dentry = d_alloc_name(dir, files[i].name); + if (!dentry) + return -ENOMEM; + + inode = sel_make_inode(dir->d_sb, S_IFREG|files[i].mode); + if (!inode) { + dput(dentry); + return -ENOMEM; + } + + inode->i_fop = files[i].ops; + inode->i_ino = ++fsi->last_ino; + d_add(dentry, inode); + } + + return 0; +} + +static ssize_t sel_read_initcon(struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + struct selinux_fs_info *fsi = file_inode(file)->i_sb->s_fs_info; + char *con; + u32 sid, len; + ssize_t ret; + + sid = file_inode(file)->i_ino&SEL_INO_MASK; + ret = security_sid_to_context(fsi->state, sid, &con, &len); + if (ret) + return ret; + + ret = simple_read_from_buffer(buf, count, ppos, con, len); + kfree(con); + return ret; +} + +static const struct file_operations sel_initcon_ops = { + .read = sel_read_initcon, + .llseek = generic_file_llseek, +}; + +static int sel_make_initcon_files(struct dentry *dir) +{ + int i; + + for (i = 1; i <= SECINITSID_NUM; i++) { + struct inode *inode; + struct dentry *dentry; + const char *s = security_get_initial_sid_context(i); + + if (!s) + continue; + dentry = d_alloc_name(dir, s); + if (!dentry) + return -ENOMEM; + + inode = sel_make_inode(dir->d_sb, S_IFREG|S_IRUGO); + if (!inode) { + dput(dentry); + return -ENOMEM; + } + + inode->i_fop = &sel_initcon_ops; + inode->i_ino = i|SEL_INITCON_INO_OFFSET; + d_add(dentry, inode); + } + + return 0; +} + +static inline unsigned long sel_class_to_ino(u16 class) +{ + return (class * (SEL_VEC_MAX + 1)) | SEL_CLASS_INO_OFFSET; +} + +static inline u16 sel_ino_to_class(unsigned long ino) +{ + return (ino & SEL_INO_MASK) / (SEL_VEC_MAX + 1); +} + +static inline unsigned long sel_perm_to_ino(u16 class, u32 perm) +{ + return (class * (SEL_VEC_MAX + 1) + perm) | SEL_CLASS_INO_OFFSET; +} + +static inline u32 sel_ino_to_perm(unsigned long ino) +{ + return (ino & SEL_INO_MASK) % (SEL_VEC_MAX + 1); +} + +static ssize_t sel_read_class(struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + unsigned long ino = file_inode(file)->i_ino; + char res[TMPBUFLEN]; + ssize_t len = scnprintf(res, sizeof(res), "%d", sel_ino_to_class(ino)); + return simple_read_from_buffer(buf, count, ppos, res, len); +} + +static const struct file_operations sel_class_ops = { + .read = sel_read_class, + .llseek = generic_file_llseek, +}; + +static ssize_t sel_read_perm(struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + unsigned long ino = file_inode(file)->i_ino; + char res[TMPBUFLEN]; + ssize_t len = scnprintf(res, sizeof(res), "%d", sel_ino_to_perm(ino)); + return simple_read_from_buffer(buf, count, ppos, res, len); +} + +static const struct file_operations sel_perm_ops = { + .read = sel_read_perm, + .llseek = generic_file_llseek, +}; + +static ssize_t sel_read_policycap(struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + struct selinux_fs_info *fsi = file_inode(file)->i_sb->s_fs_info; + int value; + char tmpbuf[TMPBUFLEN]; + ssize_t length; + unsigned long i_ino = file_inode(file)->i_ino; + + value = security_policycap_supported(fsi->state, i_ino & SEL_INO_MASK); + length = scnprintf(tmpbuf, TMPBUFLEN, "%d", value); + + return simple_read_from_buffer(buf, count, ppos, tmpbuf, length); +} + +static const struct file_operations sel_policycap_ops = { + .read = sel_read_policycap, + .llseek = generic_file_llseek, +}; + +static int sel_make_perm_files(struct selinux_policy *newpolicy, + char *objclass, int classvalue, + struct dentry *dir) +{ + int i, rc, nperms; + char **perms; + + rc = security_get_permissions(newpolicy, objclass, &perms, &nperms); + if (rc) + return rc; + + for (i = 0; i < nperms; i++) { + struct inode *inode; + struct dentry *dentry; + + rc = -ENOMEM; + dentry = d_alloc_name(dir, perms[i]); + if (!dentry) + goto out; + + rc = -ENOMEM; + inode = sel_make_inode(dir->d_sb, S_IFREG|S_IRUGO); + if (!inode) { + dput(dentry); + goto out; + } + + inode->i_fop = &sel_perm_ops; + /* i+1 since perm values are 1-indexed */ + inode->i_ino = sel_perm_to_ino(classvalue, i + 1); + d_add(dentry, inode); + } + rc = 0; +out: + for (i = 0; i < nperms; i++) + kfree(perms[i]); + kfree(perms); + return rc; +} + +static int sel_make_class_dir_entries(struct selinux_policy *newpolicy, + char *classname, int index, + struct dentry *dir) +{ + struct super_block *sb = dir->d_sb; + struct selinux_fs_info *fsi = sb->s_fs_info; + struct dentry *dentry = NULL; + struct inode *inode = NULL; + int rc; + + dentry = d_alloc_name(dir, "index"); + if (!dentry) + return -ENOMEM; + + inode = sel_make_inode(dir->d_sb, S_IFREG|S_IRUGO); + if (!inode) { + dput(dentry); + return -ENOMEM; + } + + inode->i_fop = &sel_class_ops; + inode->i_ino = sel_class_to_ino(index); + d_add(dentry, inode); + + dentry = sel_make_dir(dir, "perms", &fsi->last_class_ino); + if (IS_ERR(dentry)) + return PTR_ERR(dentry); + + rc = sel_make_perm_files(newpolicy, classname, index, dentry); + + return rc; +} + +static int sel_make_classes(struct selinux_policy *newpolicy, + struct dentry *class_dir, + unsigned long *last_class_ino) +{ + + int rc, nclasses, i; + char **classes; + + rc = security_get_classes(newpolicy, &classes, &nclasses); + if (rc) + return rc; + + /* +2 since classes are 1-indexed */ + *last_class_ino = sel_class_to_ino(nclasses + 2); + + for (i = 0; i < nclasses; i++) { + struct dentry *class_name_dir; + + class_name_dir = sel_make_dir(class_dir, classes[i], + last_class_ino); + if (IS_ERR(class_name_dir)) { + rc = PTR_ERR(class_name_dir); + goto out; + } + + /* i+1 since class values are 1-indexed */ + rc = sel_make_class_dir_entries(newpolicy, classes[i], i + 1, + class_name_dir); + if (rc) + goto out; + } + rc = 0; +out: + for (i = 0; i < nclasses; i++) + kfree(classes[i]); + kfree(classes); + return rc; +} + +static int sel_make_policycap(struct selinux_fs_info *fsi) +{ + unsigned int iter; + struct dentry *dentry = NULL; + struct inode *inode = NULL; + + for (iter = 0; iter <= POLICYDB_CAPABILITY_MAX; iter++) { + if (iter < ARRAY_SIZE(selinux_policycap_names)) + dentry = d_alloc_name(fsi->policycap_dir, + selinux_policycap_names[iter]); + else + dentry = d_alloc_name(fsi->policycap_dir, "unknown"); + + if (dentry == NULL) + return -ENOMEM; + + inode = sel_make_inode(fsi->sb, S_IFREG | 0444); + if (inode == NULL) { + dput(dentry); + return -ENOMEM; + } + + inode->i_fop = &sel_policycap_ops; + inode->i_ino = iter | SEL_POLICYCAP_INO_OFFSET; + d_add(dentry, inode); + } + + return 0; +} + +static struct dentry *sel_make_dir(struct dentry *dir, const char *name, + unsigned long *ino) +{ + struct dentry *dentry = d_alloc_name(dir, name); + struct inode *inode; + + if (!dentry) + return ERR_PTR(-ENOMEM); + + inode = sel_make_inode(dir->d_sb, S_IFDIR | S_IRUGO | S_IXUGO); + if (!inode) { + dput(dentry); + return ERR_PTR(-ENOMEM); + } + + inode->i_op = &simple_dir_inode_operations; + inode->i_fop = &simple_dir_operations; + inode->i_ino = ++(*ino); + /* directory inodes start off with i_nlink == 2 (for "." entry) */ + inc_nlink(inode); + d_add(dentry, inode); + /* bump link count on parent directory, too */ + inc_nlink(d_inode(dir)); + + return dentry; +} + +static struct dentry *sel_make_disconnected_dir(struct super_block *sb, + unsigned long *ino) +{ + struct inode *inode = sel_make_inode(sb, S_IFDIR | S_IRUGO | S_IXUGO); + + if (!inode) + return ERR_PTR(-ENOMEM); + + inode->i_op = &simple_dir_inode_operations; + inode->i_fop = &simple_dir_operations; + inode->i_ino = ++(*ino); + /* directory inodes start off with i_nlink == 2 (for "." entry) */ + inc_nlink(inode); + return d_obtain_alias(inode); +} + +#define NULL_FILE_NAME "null" + +static int sel_fill_super(struct super_block *sb, struct fs_context *fc) +{ + struct selinux_fs_info *fsi; + int ret; + struct dentry *dentry; + struct inode *inode; + struct inode_security_struct *isec; + + static const struct tree_descr selinux_files[] = { + [SEL_LOAD] = {"load", &sel_load_ops, S_IRUSR|S_IWUSR}, + [SEL_ENFORCE] = {"enforce", &sel_enforce_ops, S_IRUGO|S_IWUSR}, + [SEL_CONTEXT] = {"context", &transaction_ops, S_IRUGO|S_IWUGO}, + [SEL_ACCESS] = {"access", &transaction_ops, S_IRUGO|S_IWUGO}, + [SEL_CREATE] = {"create", &transaction_ops, S_IRUGO|S_IWUGO}, + [SEL_RELABEL] = {"relabel", &transaction_ops, S_IRUGO|S_IWUGO}, + [SEL_USER] = {"user", &transaction_ops, S_IRUGO|S_IWUGO}, + [SEL_POLICYVERS] = {"policyvers", &sel_policyvers_ops, S_IRUGO}, + [SEL_COMMIT_BOOLS] = {"commit_pending_bools", &sel_commit_bools_ops, S_IWUSR}, + [SEL_MLS] = {"mls", &sel_mls_ops, S_IRUGO}, + [SEL_DISABLE] = {"disable", &sel_disable_ops, S_IWUSR}, + [SEL_MEMBER] = {"member", &transaction_ops, S_IRUGO|S_IWUGO}, + [SEL_CHECKREQPROT] = {"checkreqprot", &sel_checkreqprot_ops, S_IRUGO|S_IWUSR}, + [SEL_REJECT_UNKNOWN] = {"reject_unknown", &sel_handle_unknown_ops, S_IRUGO}, + [SEL_DENY_UNKNOWN] = {"deny_unknown", &sel_handle_unknown_ops, S_IRUGO}, + [SEL_STATUS] = {"status", &sel_handle_status_ops, S_IRUGO}, + [SEL_POLICY] = {"policy", &sel_policy_ops, S_IRUGO}, + [SEL_VALIDATE_TRANS] = {"validatetrans", &sel_transition_ops, + S_IWUGO}, + /* last one */ {""} + }; + + ret = selinux_fs_info_create(sb); + if (ret) + goto err; + + ret = simple_fill_super(sb, SELINUX_MAGIC, selinux_files); + if (ret) + goto err; + + fsi = sb->s_fs_info; + fsi->bool_dir = sel_make_dir(sb->s_root, BOOL_DIR_NAME, &fsi->last_ino); + if (IS_ERR(fsi->bool_dir)) { + ret = PTR_ERR(fsi->bool_dir); + fsi->bool_dir = NULL; + goto err; + } + + ret = -ENOMEM; + dentry = d_alloc_name(sb->s_root, NULL_FILE_NAME); + if (!dentry) + goto err; + + ret = -ENOMEM; + inode = sel_make_inode(sb, S_IFCHR | S_IRUGO | S_IWUGO); + if (!inode) { + dput(dentry); + goto err; + } + + inode->i_ino = ++fsi->last_ino; + isec = selinux_inode(inode); + isec->sid = SECINITSID_DEVNULL; + isec->sclass = SECCLASS_CHR_FILE; + isec->initialized = LABEL_INITIALIZED; + + init_special_inode(inode, S_IFCHR | S_IRUGO | S_IWUGO, MKDEV(MEM_MAJOR, 3)); + d_add(dentry, inode); + + dentry = sel_make_dir(sb->s_root, "avc", &fsi->last_ino); + if (IS_ERR(dentry)) { + ret = PTR_ERR(dentry); + goto err; + } + + ret = sel_make_avc_files(dentry); + if (ret) + goto err; + + dentry = sel_make_dir(sb->s_root, "ss", &fsi->last_ino); + if (IS_ERR(dentry)) { + ret = PTR_ERR(dentry); + goto err; + } + + ret = sel_make_ss_files(dentry); + if (ret) + goto err; + + dentry = sel_make_dir(sb->s_root, "initial_contexts", &fsi->last_ino); + if (IS_ERR(dentry)) { + ret = PTR_ERR(dentry); + goto err; + } + + ret = sel_make_initcon_files(dentry); + if (ret) + goto err; + + fsi->class_dir = sel_make_dir(sb->s_root, CLASS_DIR_NAME, &fsi->last_ino); + if (IS_ERR(fsi->class_dir)) { + ret = PTR_ERR(fsi->class_dir); + fsi->class_dir = NULL; + goto err; + } + + fsi->policycap_dir = sel_make_dir(sb->s_root, POLICYCAP_DIR_NAME, + &fsi->last_ino); + if (IS_ERR(fsi->policycap_dir)) { + ret = PTR_ERR(fsi->policycap_dir); + fsi->policycap_dir = NULL; + goto err; + } + + ret = sel_make_policycap(fsi); + if (ret) { + pr_err("SELinux: failed to load policy capabilities\n"); + goto err; + } + + return 0; +err: + pr_err("SELinux: %s: failed while creating inodes\n", + __func__); + + selinux_fs_info_free(sb); + + return ret; +} + +static int sel_get_tree(struct fs_context *fc) +{ + return get_tree_single(fc, sel_fill_super); +} + +static const struct fs_context_operations sel_context_ops = { + .get_tree = sel_get_tree, +}; + +static int sel_init_fs_context(struct fs_context *fc) +{ + fc->ops = &sel_context_ops; + return 0; +} + +static void sel_kill_sb(struct super_block *sb) +{ + selinux_fs_info_free(sb); + kill_litter_super(sb); +} + +static struct file_system_type sel_fs_type = { + .name = "selinuxfs", + .init_fs_context = sel_init_fs_context, + .kill_sb = sel_kill_sb, +}; + +struct vfsmount *selinuxfs_mount; +struct path selinux_null; + +static int __init init_sel_fs(void) +{ + struct qstr null_name = QSTR_INIT(NULL_FILE_NAME, + sizeof(NULL_FILE_NAME)-1); + int err; + + if (!selinux_enabled_boot) + return 0; + + err = sysfs_create_mount_point(fs_kobj, "selinux"); + if (err) + return err; + + err = register_filesystem(&sel_fs_type); + if (err) { + sysfs_remove_mount_point(fs_kobj, "selinux"); + return err; + } + + selinux_null.mnt = selinuxfs_mount = kern_mount(&sel_fs_type); + if (IS_ERR(selinuxfs_mount)) { + pr_err("selinuxfs: could not mount!\n"); + err = PTR_ERR(selinuxfs_mount); + selinuxfs_mount = NULL; + } + selinux_null.dentry = d_hash_and_lookup(selinux_null.mnt->mnt_root, + &null_name); + if (IS_ERR(selinux_null.dentry)) { + pr_err("selinuxfs: could not lookup null!\n"); + err = PTR_ERR(selinux_null.dentry); + selinux_null.dentry = NULL; + } + + return err; +} + +__initcall(init_sel_fs); + +#ifdef CONFIG_SECURITY_SELINUX_DISABLE +void exit_sel_fs(void) +{ + sysfs_remove_mount_point(fs_kobj, "selinux"); + dput(selinux_null.dentry); + kern_unmount(selinuxfs_mount); + unregister_filesystem(&sel_fs_type); +} +#endif diff --git a/security/selinux/ss/avtab.c b/security/selinux/ss/avtab.c new file mode 100644 index 000000000..364b2ef9b --- /dev/null +++ b/security/selinux/ss/avtab.c @@ -0,0 +1,677 @@ +/* + * Implementation of the access vector table type. + * + * Author : Stephen Smalley, <sds@tycho.nsa.gov> + */ + +/* Updated: Frank Mayer <mayerf@tresys.com> and Karl MacMillan <kmacmillan@tresys.com> + * + * Added conditional policy language extensions + * + * Copyright (C) 2003 Tresys Technology, LLC + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 2. + * + * Updated: Yuichi Nakamura <ynakam@hitachisoft.jp> + * Tuned number of hash slots for avtab to reduce memory usage + */ + +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/errno.h> +#include "avtab.h" +#include "policydb.h" + +static struct kmem_cache *avtab_node_cachep; +static struct kmem_cache *avtab_xperms_cachep; + +/* Based on MurmurHash3, written by Austin Appleby and placed in the + * public domain. + */ +static inline int avtab_hash(struct avtab_key *keyp, u32 mask) +{ + static const u32 c1 = 0xcc9e2d51; + static const u32 c2 = 0x1b873593; + static const u32 r1 = 15; + static const u32 r2 = 13; + static const u32 m = 5; + static const u32 n = 0xe6546b64; + + u32 hash = 0; + +#define mix(input) { \ + u32 v = input; \ + v *= c1; \ + v = (v << r1) | (v >> (32 - r1)); \ + v *= c2; \ + hash ^= v; \ + hash = (hash << r2) | (hash >> (32 - r2)); \ + hash = hash * m + n; \ +} + + mix(keyp->target_class); + mix(keyp->target_type); + mix(keyp->source_type); + +#undef mix + + hash ^= hash >> 16; + hash *= 0x85ebca6b; + hash ^= hash >> 13; + hash *= 0xc2b2ae35; + hash ^= hash >> 16; + + return hash & mask; +} + +static struct avtab_node* +avtab_insert_node(struct avtab *h, int hvalue, + struct avtab_node *prev, struct avtab_node *cur, + struct avtab_key *key, struct avtab_datum *datum) +{ + struct avtab_node *newnode; + struct avtab_extended_perms *xperms; + newnode = kmem_cache_zalloc(avtab_node_cachep, GFP_KERNEL); + if (newnode == NULL) + return NULL; + newnode->key = *key; + + if (key->specified & AVTAB_XPERMS) { + xperms = kmem_cache_zalloc(avtab_xperms_cachep, GFP_KERNEL); + if (xperms == NULL) { + kmem_cache_free(avtab_node_cachep, newnode); + return NULL; + } + *xperms = *(datum->u.xperms); + newnode->datum.u.xperms = xperms; + } else { + newnode->datum.u.data = datum->u.data; + } + + if (prev) { + newnode->next = prev->next; + prev->next = newnode; + } else { + struct avtab_node **n = &h->htable[hvalue]; + + newnode->next = *n; + *n = newnode; + } + + h->nel++; + return newnode; +} + +static int avtab_insert(struct avtab *h, struct avtab_key *key, struct avtab_datum *datum) +{ + int hvalue; + struct avtab_node *prev, *cur, *newnode; + u16 specified = key->specified & ~(AVTAB_ENABLED|AVTAB_ENABLED_OLD); + + if (!h || !h->nslot) + return -EINVAL; + + hvalue = avtab_hash(key, h->mask); + for (prev = NULL, cur = h->htable[hvalue]; + cur; + prev = cur, cur = cur->next) { + if (key->source_type == cur->key.source_type && + key->target_type == cur->key.target_type && + key->target_class == cur->key.target_class && + (specified & cur->key.specified)) { + /* extended perms may not be unique */ + if (specified & AVTAB_XPERMS) + break; + return -EEXIST; + } + if (key->source_type < cur->key.source_type) + break; + if (key->source_type == cur->key.source_type && + key->target_type < cur->key.target_type) + break; + if (key->source_type == cur->key.source_type && + key->target_type == cur->key.target_type && + key->target_class < cur->key.target_class) + break; + } + + newnode = avtab_insert_node(h, hvalue, prev, cur, key, datum); + if (!newnode) + return -ENOMEM; + + return 0; +} + +/* Unlike avtab_insert(), this function allow multiple insertions of the same + * key/specified mask into the table, as needed by the conditional avtab. + * It also returns a pointer to the node inserted. + */ +struct avtab_node * +avtab_insert_nonunique(struct avtab *h, struct avtab_key *key, struct avtab_datum *datum) +{ + int hvalue; + struct avtab_node *prev, *cur; + u16 specified = key->specified & ~(AVTAB_ENABLED|AVTAB_ENABLED_OLD); + + if (!h || !h->nslot) + return NULL; + hvalue = avtab_hash(key, h->mask); + for (prev = NULL, cur = h->htable[hvalue]; + cur; + prev = cur, cur = cur->next) { + if (key->source_type == cur->key.source_type && + key->target_type == cur->key.target_type && + key->target_class == cur->key.target_class && + (specified & cur->key.specified)) + break; + if (key->source_type < cur->key.source_type) + break; + if (key->source_type == cur->key.source_type && + key->target_type < cur->key.target_type) + break; + if (key->source_type == cur->key.source_type && + key->target_type == cur->key.target_type && + key->target_class < cur->key.target_class) + break; + } + return avtab_insert_node(h, hvalue, prev, cur, key, datum); +} + +struct avtab_datum *avtab_search(struct avtab *h, struct avtab_key *key) +{ + int hvalue; + struct avtab_node *cur; + u16 specified = key->specified & ~(AVTAB_ENABLED|AVTAB_ENABLED_OLD); + + if (!h || !h->nslot) + return NULL; + + hvalue = avtab_hash(key, h->mask); + for (cur = h->htable[hvalue]; cur; + cur = cur->next) { + if (key->source_type == cur->key.source_type && + key->target_type == cur->key.target_type && + key->target_class == cur->key.target_class && + (specified & cur->key.specified)) + return &cur->datum; + + if (key->source_type < cur->key.source_type) + break; + if (key->source_type == cur->key.source_type && + key->target_type < cur->key.target_type) + break; + if (key->source_type == cur->key.source_type && + key->target_type == cur->key.target_type && + key->target_class < cur->key.target_class) + break; + } + + return NULL; +} + +/* This search function returns a node pointer, and can be used in + * conjunction with avtab_search_next_node() + */ +struct avtab_node* +avtab_search_node(struct avtab *h, struct avtab_key *key) +{ + int hvalue; + struct avtab_node *cur; + u16 specified = key->specified & ~(AVTAB_ENABLED|AVTAB_ENABLED_OLD); + + if (!h || !h->nslot) + return NULL; + + hvalue = avtab_hash(key, h->mask); + for (cur = h->htable[hvalue]; cur; + cur = cur->next) { + if (key->source_type == cur->key.source_type && + key->target_type == cur->key.target_type && + key->target_class == cur->key.target_class && + (specified & cur->key.specified)) + return cur; + + if (key->source_type < cur->key.source_type) + break; + if (key->source_type == cur->key.source_type && + key->target_type < cur->key.target_type) + break; + if (key->source_type == cur->key.source_type && + key->target_type == cur->key.target_type && + key->target_class < cur->key.target_class) + break; + } + return NULL; +} + +struct avtab_node* +avtab_search_node_next(struct avtab_node *node, int specified) +{ + struct avtab_node *cur; + + if (!node) + return NULL; + + specified &= ~(AVTAB_ENABLED|AVTAB_ENABLED_OLD); + for (cur = node->next; cur; cur = cur->next) { + if (node->key.source_type == cur->key.source_type && + node->key.target_type == cur->key.target_type && + node->key.target_class == cur->key.target_class && + (specified & cur->key.specified)) + return cur; + + if (node->key.source_type < cur->key.source_type) + break; + if (node->key.source_type == cur->key.source_type && + node->key.target_type < cur->key.target_type) + break; + if (node->key.source_type == cur->key.source_type && + node->key.target_type == cur->key.target_type && + node->key.target_class < cur->key.target_class) + break; + } + return NULL; +} + +void avtab_destroy(struct avtab *h) +{ + int i; + struct avtab_node *cur, *temp; + + if (!h) + 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) { + u32 shift = 1; + u32 work = nrules >> 3; + while (work) { + work >>= 1; + shift++; + } + nslot = 1 << shift; + 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); +} + +void avtab_hash_eval(struct avtab *h, char *tag) +{ + int i, chain_len, slots_used, max_chain_len; + unsigned long long chain2_len_sum; + struct avtab_node *cur; + + slots_used = 0; + max_chain_len = 0; + chain2_len_sum = 0; + for (i = 0; i < h->nslot; i++) { + cur = h->htable[i]; + if (cur) { + slots_used++; + chain_len = 0; + while (cur) { + chain_len++; + cur = cur->next; + } + + if (chain_len > max_chain_len) + max_chain_len = chain_len; + chain2_len_sum += chain_len * chain_len; + } + } + + pr_debug("SELinux: %s: %d entries and %d/%d buckets used, " + "longest chain length %d sum of chain length^2 %llu\n", + tag, h->nel, slots_used, h->nslot, max_chain_len, + chain2_len_sum); +} + +static uint16_t spec_order[] = { + AVTAB_ALLOWED, + AVTAB_AUDITDENY, + AVTAB_AUDITALLOW, + AVTAB_TRANSITION, + AVTAB_CHANGE, + AVTAB_MEMBER, + AVTAB_XPERMS_ALLOWED, + AVTAB_XPERMS_AUDITALLOW, + AVTAB_XPERMS_DONTAUDIT +}; + +int avtab_read_item(struct avtab *a, void *fp, struct policydb *pol, + int (*insertf)(struct avtab *a, struct avtab_key *k, + struct avtab_datum *d, void *p), + void *p) +{ + __le16 buf16[4]; + u16 enabled; + u32 items, items2, val, vers = pol->policyvers; + struct avtab_key key; + struct avtab_datum datum; + struct avtab_extended_perms xperms; + __le32 buf32[ARRAY_SIZE(xperms.perms.p)]; + int i, rc; + unsigned set; + + memset(&key, 0, sizeof(struct avtab_key)); + memset(&datum, 0, sizeof(struct avtab_datum)); + + if (vers < POLICYDB_VERSION_AVTAB) { + rc = next_entry(buf32, fp, sizeof(u32)); + if (rc) { + pr_err("SELinux: avtab: truncated entry\n"); + return rc; + } + items2 = le32_to_cpu(buf32[0]); + if (items2 > ARRAY_SIZE(buf32)) { + pr_err("SELinux: avtab: entry overflow\n"); + return -EINVAL; + + } + rc = next_entry(buf32, fp, sizeof(u32)*items2); + if (rc) { + pr_err("SELinux: avtab: truncated entry\n"); + return rc; + } + items = 0; + + val = le32_to_cpu(buf32[items++]); + key.source_type = (u16)val; + if (key.source_type != val) { + pr_err("SELinux: avtab: truncated source type\n"); + return -EINVAL; + } + val = le32_to_cpu(buf32[items++]); + key.target_type = (u16)val; + if (key.target_type != val) { + pr_err("SELinux: avtab: truncated target type\n"); + return -EINVAL; + } + val = le32_to_cpu(buf32[items++]); + key.target_class = (u16)val; + if (key.target_class != val) { + pr_err("SELinux: avtab: truncated target class\n"); + return -EINVAL; + } + + val = le32_to_cpu(buf32[items++]); + enabled = (val & AVTAB_ENABLED_OLD) ? AVTAB_ENABLED : 0; + + if (!(val & (AVTAB_AV | AVTAB_TYPE))) { + pr_err("SELinux: avtab: null entry\n"); + return -EINVAL; + } + if ((val & AVTAB_AV) && + (val & AVTAB_TYPE)) { + pr_err("SELinux: avtab: entry has both access vectors and types\n"); + return -EINVAL; + } + if (val & AVTAB_XPERMS) { + pr_err("SELinux: avtab: entry has extended permissions\n"); + return -EINVAL; + } + + for (i = 0; i < ARRAY_SIZE(spec_order); i++) { + if (val & spec_order[i]) { + key.specified = spec_order[i] | enabled; + datum.u.data = le32_to_cpu(buf32[items++]); + rc = insertf(a, &key, &datum, p); + if (rc) + return rc; + } + } + + if (items != items2) { + pr_err("SELinux: avtab: entry only had %d items, expected %d\n", + items2, items); + return -EINVAL; + } + return 0; + } + + rc = next_entry(buf16, fp, sizeof(u16)*4); + if (rc) { + pr_err("SELinux: avtab: truncated entry\n"); + return rc; + } + + items = 0; + key.source_type = le16_to_cpu(buf16[items++]); + key.target_type = le16_to_cpu(buf16[items++]); + key.target_class = le16_to_cpu(buf16[items++]); + key.specified = le16_to_cpu(buf16[items++]); + + if (!policydb_type_isvalid(pol, key.source_type) || + !policydb_type_isvalid(pol, key.target_type) || + !policydb_class_isvalid(pol, key.target_class)) { + pr_err("SELinux: avtab: invalid type or class\n"); + return -EINVAL; + } + + set = 0; + for (i = 0; i < ARRAY_SIZE(spec_order); i++) { + if (key.specified & spec_order[i]) + set++; + } + if (!set || set > 1) { + pr_err("SELinux: avtab: more than one specifier\n"); + return -EINVAL; + } + + if ((vers < POLICYDB_VERSION_XPERMS_IOCTL) && + (key.specified & AVTAB_XPERMS)) { + pr_err("SELinux: avtab: policy version %u does not " + "support extended permissions rules and one " + "was specified\n", vers); + return -EINVAL; + } else if (key.specified & AVTAB_XPERMS) { + memset(&xperms, 0, sizeof(struct avtab_extended_perms)); + rc = next_entry(&xperms.specified, fp, sizeof(u8)); + if (rc) { + pr_err("SELinux: avtab: truncated entry\n"); + return rc; + } + rc = next_entry(&xperms.driver, fp, sizeof(u8)); + if (rc) { + pr_err("SELinux: avtab: truncated entry\n"); + return rc; + } + rc = next_entry(buf32, fp, sizeof(u32)*ARRAY_SIZE(xperms.perms.p)); + if (rc) { + pr_err("SELinux: avtab: truncated entry\n"); + return rc; + } + for (i = 0; i < ARRAY_SIZE(xperms.perms.p); i++) + xperms.perms.p[i] = le32_to_cpu(buf32[i]); + datum.u.xperms = &xperms; + } else { + rc = next_entry(buf32, fp, sizeof(u32)); + if (rc) { + pr_err("SELinux: avtab: truncated entry\n"); + return rc; + } + datum.u.data = le32_to_cpu(*buf32); + } + if ((key.specified & AVTAB_TYPE) && + !policydb_type_isvalid(pol, datum.u.data)) { + pr_err("SELinux: avtab: invalid type\n"); + return -EINVAL; + } + return insertf(a, &key, &datum, p); +} + +static int avtab_insertf(struct avtab *a, struct avtab_key *k, + struct avtab_datum *d, void *p) +{ + return avtab_insert(a, k, d); +} + +int avtab_read(struct avtab *a, void *fp, struct policydb *pol) +{ + int rc; + __le32 buf[1]; + u32 nel, i; + + + rc = next_entry(buf, fp, sizeof(u32)); + if (rc < 0) { + pr_err("SELinux: avtab: truncated table\n"); + goto bad; + } + nel = le32_to_cpu(buf[0]); + if (!nel) { + pr_err("SELinux: avtab: table is empty\n"); + rc = -EINVAL; + goto bad; + } + + rc = avtab_alloc(a, nel); + if (rc) + goto bad; + + for (i = 0; i < nel; i++) { + rc = avtab_read_item(a, fp, pol, avtab_insertf, NULL); + if (rc) { + if (rc == -ENOMEM) + pr_err("SELinux: avtab: out of memory\n"); + else if (rc == -EEXIST) + pr_err("SELinux: avtab: duplicate entry\n"); + + goto bad; + } + } + + rc = 0; +out: + return rc; + +bad: + avtab_destroy(a); + goto out; +} + +int avtab_write_item(struct policydb *p, struct avtab_node *cur, void *fp) +{ + __le16 buf16[4]; + __le32 buf32[ARRAY_SIZE(cur->datum.u.xperms->perms.p)]; + int rc; + unsigned int i; + + buf16[0] = cpu_to_le16(cur->key.source_type); + buf16[1] = cpu_to_le16(cur->key.target_type); + buf16[2] = cpu_to_le16(cur->key.target_class); + buf16[3] = cpu_to_le16(cur->key.specified); + rc = put_entry(buf16, sizeof(u16), 4, fp); + if (rc) + return rc; + + if (cur->key.specified & AVTAB_XPERMS) { + rc = put_entry(&cur->datum.u.xperms->specified, sizeof(u8), 1, fp); + if (rc) + return rc; + rc = put_entry(&cur->datum.u.xperms->driver, sizeof(u8), 1, fp); + if (rc) + return rc; + for (i = 0; i < ARRAY_SIZE(cur->datum.u.xperms->perms.p); i++) + buf32[i] = cpu_to_le32(cur->datum.u.xperms->perms.p[i]); + rc = put_entry(buf32, sizeof(u32), + ARRAY_SIZE(cur->datum.u.xperms->perms.p), fp); + } else { + buf32[0] = cpu_to_le32(cur->datum.u.data); + rc = put_entry(buf32, sizeof(u32), 1, fp); + } + if (rc) + return rc; + return 0; +} + +int avtab_write(struct policydb *p, struct avtab *a, void *fp) +{ + unsigned int i; + int rc = 0; + struct avtab_node *cur; + __le32 buf[1]; + + buf[0] = cpu_to_le32(a->nel); + rc = put_entry(buf, sizeof(u32), 1, fp); + if (rc) + return rc; + + for (i = 0; i < a->nslot; i++) { + for (cur = a->htable[i]; cur; + cur = cur->next) { + rc = avtab_write_item(p, cur, fp); + if (rc) + return rc; + } + } + + return rc; +} + +void __init avtab_cache_init(void) +{ + avtab_node_cachep = kmem_cache_create("avtab_node", + sizeof(struct avtab_node), + 0, SLAB_PANIC, NULL); + avtab_xperms_cachep = kmem_cache_create("avtab_extended_perms", + sizeof(struct avtab_extended_perms), + 0, SLAB_PANIC, NULL); +} diff --git a/security/selinux/ss/avtab.h b/security/selinux/ss/avtab.h new file mode 100644 index 000000000..f2eeb3626 --- /dev/null +++ b/security/selinux/ss/avtab.h @@ -0,0 +1,118 @@ +/* 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, <sds@tycho.nsa.gov> + */ + +/* Updated: Frank Mayer <mayerf@tresys.com> and Karl MacMillan <kmacmillan@tresys.com> + * + * Added conditional policy language extensions + * + * Copyright (C) 2003 Tresys Technology, LLC + * + * 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 */ + u16 specified; /* what field is specified */ +}; + +/* + * For operations that require more than the 32 permissions provided by the avc + * extended permissions may be used to provide 256 bits of permissions. + */ +struct avtab_extended_perms { +/* These are not flags. All 256 values may be used */ +#define AVTAB_XPERMS_IOCTLFUNCTION 0x01 +#define AVTAB_XPERMS_IOCTLDRIVER 0x02 + /* extension of the avtab_key specified */ + u8 specified; /* ioctl, netfilter, ... */ + /* + * if 256 bits is not adequate as is often the case with ioctls, then + * multiple extended perms may be used and the driver field + * specifies which permissions are included. + */ + u8 driver; + /* 256 bits of permissions */ + struct extended_perms_data perms; +}; + +struct avtab_datum { + union { + u32 data; /* access vector or type value */ + struct avtab_extended_perms *xperms; + } u; +}; + +struct avtab_node { + struct avtab_key key; + struct avtab_datum datum; + struct avtab_node *next; +}; + +struct avtab { + struct 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 *, u32); +int avtab_alloc_dup(struct avtab *new, const struct avtab *orig); +struct avtab_datum *avtab_search(struct avtab *h, struct avtab_key *k); +void avtab_destroy(struct avtab *h); +void avtab_hash_eval(struct avtab *h, char *tag); + +struct policydb; +int avtab_read_item(struct avtab *a, void *fp, struct policydb *pol, + int (*insert)(struct avtab *a, struct avtab_key *k, + struct avtab_datum *d, void *p), + void *p); + +int avtab_read(struct avtab *a, void *fp, struct policydb *pol); +int avtab_write_item(struct policydb *p, struct avtab_node *cur, void *fp); +int avtab_write(struct policydb *p, struct avtab *a, void *fp); + +struct avtab_node *avtab_insert_nonunique(struct avtab *h, struct avtab_key *key, + struct avtab_datum *datum); + +struct avtab_node *avtab_search_node(struct avtab *h, struct avtab_key *key); + +struct avtab_node *avtab_search_node_next(struct avtab_node *node, int specified); + +#define MAX_AVTAB_HASH_BITS 16 +#define MAX_AVTAB_HASH_BUCKETS (1 << MAX_AVTAB_HASH_BITS) + +#endif /* _SS_AVTAB_H_ */ + diff --git a/security/selinux/ss/conditional.c b/security/selinux/ss/conditional.c new file mode 100644 index 000000000..865611127 --- /dev/null +++ b/security/selinux/ss/conditional.c @@ -0,0 +1,758 @@ +// 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 "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->bool - 1]->state; + break; + case COND_NOT: + if (sp < 0) + return -1; + s[sp] = !s[sp]; + break; + case COND_OR: + if (sp < 1) + return -1; + sp--; + s[sp] |= s[sp + 1]; + break; + case COND_AND: + if (sp < 1) + return -1; + sp--; + s[sp] &= s[sp + 1]; + break; + case COND_XOR: + if (sp < 1) + return -1; + sp--; + s[sp] ^= s[sp + 1]; + break; + case COND_EQ: + if (sp < 1) + return -1; + sp--; + s[sp] = (s[sp] == s[sp + 1]); + break; + case COND_NEQ: + if (sp < 1) + return -1; + sp--; + s[sp] = (s[sp] != s[sp + 1]); + break; + default: + return -1; + } + } + return s[0]; +} + +/* + * evaluate_cond_node evaluates the conditional stored in + * a struct cond_node and if the result is different than the + * current state of the node it sets the rules in the true/false + * list appropriately. If the result of the expression is undefined + * all of the rules are disabled for safety. + */ +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 = kmalloc_array(p->p_bools.nprim, + sizeof(*p->bool_val_to_struct), + GFP_KERNEL); + if (!p->bool_val_to_struct) + return -ENOMEM; + return 0; +} + +int cond_destroy_bool(void *key, void *datum, void *p) +{ + kfree(key); + kfree(datum); + return 0; +} + +int cond_index_bool(void *key, void *datum, void *datap) +{ + struct policydb *p; + struct cond_bool_datum *booldatum; + + 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; +} + +static int bool_isvalid(struct cond_bool_datum *b) +{ + if (!(b->state == 0 || b->state == 1)) + return 0; + return 1; +} + +int cond_read_bool(struct policydb *p, struct symtab *s, void *fp) +{ + char *key = NULL; + struct cond_bool_datum *booldatum; + __le32 buf[3]; + u32 len; + int rc; + + booldatum = kzalloc(sizeof(*booldatum), GFP_KERNEL); + if (!booldatum) + return -ENOMEM; + + rc = next_entry(buf, fp, sizeof(buf)); + if (rc) + goto err; + + booldatum->value = le32_to_cpu(buf[0]); + booldatum->state = le32_to_cpu(buf[1]); + + rc = -EINVAL; + if (!bool_isvalid(booldatum)) + goto err; + + len = le32_to_cpu(buf[2]); + if (((len == 0) || (len == (u32)-1))) + goto err; + + rc = -ENOMEM; + key = kmalloc(len + 1, GFP_KERNEL); + if (!key) + goto err; + rc = next_entry(key, fp, len); + if (rc) + goto err; + key[len] = '\0'; + rc = symtab_insert(s, key, booldatum); + if (rc) + goto err; + + return 0; +err: + cond_destroy_bool(key, booldatum, NULL); + return rc; +} + +struct cond_insertf_data { + struct policydb *p; + struct avtab_node **dst; + struct cond_av_list *other; +}; + +static int cond_insertf(struct avtab *a, struct avtab_key *k, struct avtab_datum *d, void *ptr) +{ + struct cond_insertf_data *data = ptr; + struct policydb *p = data->p; + struct cond_av_list *other = data->other; + 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(&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(&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, void *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; + + list->nodes = kcalloc(len, sizeof(*list->nodes), GFP_KERNEL); + 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); + 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->bool > p->p_bools.nprim) { + pr_err("SELinux: conditional expressions uses unknown bool.\n"); + return 0; + } + return 1; +} + +static int cond_read_node(struct policydb *p, struct cond_node *node, void *fp) +{ + __le32 buf[2]; + u32 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]); + node->expr.nodes = kcalloc(len, sizeof(*node->expr.nodes), GFP_KERNEL); + 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->bool = 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, void *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]); + + p->cond_list = kcalloc(len, sizeof(*p->cond_list), GFP_KERNEL); + 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; + void *fp = pd->fp; + __le32 buf[3]; + u32 len; + int rc; + + len = strlen(key); + buf[0] = cpu_to_le32(booldatum->value); + buf[1] = cpu_to_le32(booldatum->state); + buf[2] = cpu_to_le32(len); + rc = put_entry(buf, sizeof(u32), 3, fp); + if (rc) + return rc; + rc = put_entry(key, 1, len, fp); + if (rc) + return rc; + return 0; +} + +/* + * cond_write_cond_av_list doesn't write out the av_list nodes. + * Instead it writes out the key/value pairs from the avtab. This + * is necessary because there is no way to uniquely identifying rules + * in the avtab so it is not possible to associate individual rules + * in the avtab with a conditional without saving them as part of + * the conditional. This means that the avtab with the conditional + * rules will not be saved but will be rebuilt on policy load. + */ +static int cond_write_av_list(struct policydb *p, + struct cond_av_list *list, struct policy_file *fp) +{ + __le32 buf[1]; + 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].bool); + rc = put_entry(buf, sizeof(u32), 2, fp); + if (rc) + return rc; + } + + rc = cond_write_av_list(p, &node->true_list, fp); + if (rc) + return rc; + rc = cond_write_av_list(p, &node->false_list, fp); + if (rc) + return rc; + + return 0; +} + +int cond_write_list(struct policydb *p, void *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); + } + return; + +} +/* Determine whether additional permissions are granted by the conditional + * av table, and if so, add them to the result + */ +void cond_compute_av(struct avtab *ctab, struct avtab_key *key, + struct av_decision *avd, struct extended_perms *xperms) +{ + struct avtab_node *node; + + if (!ctab || !key || !avd) + return; + + for (node = avtab_search_node(ctab, key); node; + node = avtab_search_node_next(node, key->specified)) { + if ((u16)(AVTAB_ALLOWED|AVTAB_ENABLED) == + (node->key.specified & (AVTAB_ALLOWED|AVTAB_ENABLED))) + avd->allowed |= node->datum.u.data; + if ((u16)(AVTAB_AUDITDENY|AVTAB_ENABLED) == + (node->key.specified & (AVTAB_AUDITDENY|AVTAB_ENABLED))) + /* Since a '0' in an auditdeny mask represents a + * permission we do NOT want to audit (dontaudit), we use + * the '&' operand to ensure that all '0's in the mask + * are retained (much unlike the allow and auditallow cases). + */ + avd->auditdeny &= node->datum.u.data; + if ((u16)(AVTAB_AUDITALLOW|AVTAB_ENABLED) == + (node->key.specified & (AVTAB_AUDITALLOW|AVTAB_ENABLED))) + avd->auditallow |= node->datum.u.data; + if (xperms && (node->key.specified & AVTAB_ENABLED) && + (node->key.specified & AVTAB_XPERMS)) + services_compute_xperms_drivers(xperms, node); + } +} + +static int cond_dup_av_list(struct cond_av_list *new, + struct cond_av_list *orig, + struct avtab *avtab) +{ + u32 i; + + memset(new, 0, sizeof(*new)); + + new->nodes = kcalloc(orig->len, sizeof(*new->nodes), GFP_KERNEL); + 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, + struct policydb *origp) +{ + int rc, i, j; + + rc = avtab_alloc_dup(&newp->te_cond_avtab, &origp->te_cond_avtab); + if (rc) + return rc; + + newp->cond_list_len = 0; + newp->cond_list = kcalloc(origp->cond_list_len, + sizeof(*newp->cond_list), + GFP_KERNEL); + if (!newp->cond_list) + goto error; + + for (i = 0; i < origp->cond_list_len; i++) { + struct cond_node *newn = &newp->cond_list[i]; + struct cond_node *orign = &origp->cond_list[i]; + + newp->cond_list_len++; + + newn->cur_state = orign->cur_state; + newn->expr.nodes = kcalloc(orign->expr.len, + sizeof(*newn->expr.nodes), GFP_KERNEL); + if (!newn->expr.nodes) + goto error; + for (j = 0; j < orign->expr.len; j++) + newn->expr.nodes[j] = orign->expr.nodes[j]; + 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, 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, + struct policydb *orig) +{ + struct cond_bool_datum **cond_bool_array; + int rc; + + cond_bool_array = kmalloc_array(orig->p_bools.nprim, + sizeof(*orig->bool_val_to_struct), + GFP_KERNEL); + 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, 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..e47ec6dde --- /dev/null +++ b/security/selinux/ss/conditional.h @@ -0,0 +1,85 @@ +/* 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 bool; +}; + +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, void *fp); +int cond_read_list(struct policydb *p, void *fp); +int cond_write_bool(void *key, void *datum, void *ptr); +int cond_write_list(struct policydb *p, void *fp); + +void cond_compute_av(struct avtab *ctab, struct avtab_key *key, + struct av_decision *avd, struct extended_perms *xperms); +void cond_compute_xperms(struct avtab *ctab, struct avtab_key *key, + struct extended_perms_decision *xpermd); +void evaluate_cond_nodes(struct policydb *p); +void cond_policydb_destroy_dup(struct policydb *p); +int cond_policydb_dup(struct policydb *new, 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..4e563be9e --- /dev/null +++ b/security/selinux/ss/constraint.h @@ -0,0 +1,63 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * A constraint is a condition that must be satisfied in + * order for one or more permissions to be granted. + * Constraints are used to impose additional restrictions + * beyond the type-based rules in `te' or the role-based + * transition rules in `rbac'. Constraints are typically + * used to prevent a process from transitioning to a new user + * identity or role unless it is in a privileged type. + * Constraints are likewise typically used to prevent a + * process from labeling an object with a different user + * identity. + * + * Author : Stephen Smalley, <sds@tycho.nsa.gov> + */ +#ifndef _SS_CONSTRAINT_H_ +#define _SS_CONSTRAINT_H_ + +#include "ebitmap.h" + +#define CEXPR_MAXDEPTH 5 + +struct constraint_expr { +#define CEXPR_NOT 1 /* not expr */ +#define CEXPR_AND 2 /* expr and expr */ +#define CEXPR_OR 3 /* expr or expr */ +#define CEXPR_ATTR 4 /* attr op attr */ +#define CEXPR_NAMES 5 /* attr op names */ + u32 expr_type; /* expression type */ + +#define CEXPR_USER 1 /* user */ +#define CEXPR_ROLE 2 /* role */ +#define CEXPR_TYPE 4 /* type */ +#define CEXPR_TARGET 8 /* target if set, source otherwise */ +#define CEXPR_XTARGET 16 /* special 3rd target for validatetrans rule */ +#define CEXPR_L1L2 32 /* low level 1 vs. low level 2 */ +#define CEXPR_L1H2 64 /* low level 1 vs. high level 2 */ +#define CEXPR_H1L2 128 /* high level 1 vs. low level 2 */ +#define CEXPR_H1H2 256 /* high level 1 vs. high level 2 */ +#define CEXPR_L1H1 512 /* low level 1 vs. high level 1 */ +#define CEXPR_L2H2 1024 /* low level 2 vs. high level 2 */ + u32 attr; /* attribute */ + +#define CEXPR_EQ 1 /* == or eq */ +#define CEXPR_NEQ 2 /* != */ +#define CEXPR_DOM 3 /* dom */ +#define CEXPR_DOMBY 4 /* domby */ +#define CEXPR_INCOMP 5 /* incomp */ + u32 op; /* operator */ + + struct ebitmap names; /* names */ + struct type_set *type_names; + + struct constraint_expr *next; /* next expression */ +}; + +struct constraint_node { + u32 permissions; /* constrained permissions */ + struct constraint_expr *expr; /* constraint on permissions */ + struct constraint_node *next; /* next constraint */ +}; + +#endif /* _SS_CONSTRAINT_H_ */ diff --git a/security/selinux/ss/context.c b/security/selinux/ss/context.c new file mode 100644 index 000000000..38bc0aa52 --- /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_cmp() 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..62990aa1e --- /dev/null +++ b/security/selinux/ss/context.h @@ -0,0 +1,198 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * A security context is a set of security attributes + * associated with each subject and object controlled + * by the security policy. Security contexts are + * externally represented as variable-length strings + * that can be interpreted by a user or application + * with an understanding of the security policy. + * Internally, the security server uses a simple + * structure. This structure is private to the + * security server and can be changed without affecting + * clients of the security server. + * + * Author : Stephen Smalley, <sds@tycho.nsa.gov> + */ +#ifndef _SS_CONTEXT_H_ +#define _SS_CONTEXT_H_ + +#include "ebitmap.h" +#include "mls_types.h" +#include "security.h" + +/* + * A security context consists of an authenticated user + * identity, a role, a type and a MLS range. + */ +struct context { + u32 user; + u32 role; + u32 type; + u32 len; /* length of string in bytes */ + struct mls_range range; + char *str; /* string representation if context cannot be mapped. */ +}; + +static inline void mls_context_init(struct context *c) +{ + memset(&c->range, 0, sizeof(c->range)); +} + +static inline int mls_context_cpy(struct context *dst, struct context *src) +{ + int rc; + + dst->range.level[0].sens = src->range.level[0].sens; + rc = ebitmap_cpy(&dst->range.level[0].cat, &src->range.level[0].cat); + if (rc) + goto out; + + dst->range.level[1].sens = src->range.level[1].sens; + rc = ebitmap_cpy(&dst->range.level[1].cat, &src->range.level[1].cat); + if (rc) + ebitmap_destroy(&dst->range.level[0].cat); +out: + return rc; +} + +/* + * Sets both levels in the MLS range of 'dst' to the low level of 'src'. + */ +static inline int mls_context_cpy_low(struct context *dst, struct context *src) +{ + int rc; + + dst->range.level[0].sens = src->range.level[0].sens; + rc = ebitmap_cpy(&dst->range.level[0].cat, &src->range.level[0].cat); + if (rc) + goto out; + + dst->range.level[1].sens = src->range.level[0].sens; + rc = ebitmap_cpy(&dst->range.level[1].cat, &src->range.level[0].cat); + if (rc) + ebitmap_destroy(&dst->range.level[0].cat); +out: + return rc; +} + +/* + * Sets both levels in the MLS range of 'dst' to the high level of 'src'. + */ +static inline int mls_context_cpy_high(struct context *dst, struct context *src) +{ + int rc; + + dst->range.level[0].sens = src->range.level[1].sens; + rc = ebitmap_cpy(&dst->range.level[0].cat, &src->range.level[1].cat); + if (rc) + goto out; + + dst->range.level[1].sens = src->range.level[1].sens; + rc = ebitmap_cpy(&dst->range.level[1].cat, &src->range.level[1].cat); + if (rc) + ebitmap_destroy(&dst->range.level[0].cat); +out: + return rc; +} + + +static inline int mls_context_glblub(struct context *dst, + struct context *c1, struct context *c2) +{ + struct mls_range *dr = &dst->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 int mls_context_cmp(struct context *c1, struct context *c2) +{ + return ((c1->range.level[0].sens == c2->range.level[0].sens) && + ebitmap_cmp(&c1->range.level[0].cat, &c2->range.level[0].cat) && + (c1->range.level[1].sens == c2->range.level[1].sens) && + ebitmap_cmp(&c1->range.level[1].cat, &c2->range.level[1].cat)); +} + +static inline void mls_context_destroy(struct context *c) +{ + ebitmap_destroy(&c->range.level[0].cat); + ebitmap_destroy(&c->range.level[1].cat); + mls_context_init(c); +} + +static inline void context_init(struct context *c) +{ + memset(c, 0, sizeof(*c)); +} + +static inline int context_cpy(struct context *dst, struct context *src) +{ + int rc; + + dst->user = src->user; + dst->role = src->role; + dst->type = src->type; + if (src->str) { + dst->str = kstrdup(src->str, GFP_ATOMIC); + if (!dst->str) + return -ENOMEM; + dst->len = src->len; + } else { + dst->str = NULL; + dst->len = 0; + } + rc = mls_context_cpy(dst, src); + if (rc) { + kfree(dst->str); + return rc; + } + return 0; +} + +static inline void context_destroy(struct context *c) +{ + c->user = c->role = c->type = 0; + kfree(c->str); + c->str = NULL; + c->len = 0; + mls_context_destroy(c); +} + +static inline int context_cmp(struct context *c1, struct context *c2) +{ + if (c1->len && c2->len) + return (c1->len == c2->len && !strcmp(c1->str, c2->str)); + if (c1->len || c2->len) + return 0; + return ((c1->user == c2->user) && + (c1->role == c2->role) && + (c1->type == c2->type) && + mls_context_cmp(c1, c2)); +} + +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..14bedc95c --- /dev/null +++ b/security/selinux/ss/ebitmap.c @@ -0,0 +1,564 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Implementation of the extensible bitmap type. + * + * Author : Stephen Smalley, <sds@tycho.nsa.gov> + */ +/* + * Updated: Hewlett-Packard <paul@paul-moore.com> + * + * Added support to import/export the NetLabel category bitmap + * + * (c) Copyright Hewlett-Packard Development Company, L.P., 2006 + */ +/* + * Updated: KaiGai Kohei <kaigai@ak.jp.nec.com> + * Applied standard bit operations to improve bitmap scanning. + */ + +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/errno.h> +#include <linux/jhash.h> +#include <net/netlabel.h> +#include "ebitmap.h" +#include "policydb.h" + +#define BITS_PER_U64 (sizeof(u64) * 8) + +static struct kmem_cache *ebitmap_node_cachep; + +int ebitmap_cmp(struct ebitmap *e1, struct ebitmap *e2) +{ + struct ebitmap_node *n1, *n2; + + if (e1->highbit != e2->highbit) + return 0; + + n1 = e1->node; + n2 = e2->node; + while (n1 && n2 && + (n1->startbit == n2->startbit) && + !memcmp(n1->maps, n2->maps, EBITMAP_SIZE / 8)) { + n1 = n1->next; + n2 = n2->next; + } + + if (n1 || n2) + return 0; + + return 1; +} + +int ebitmap_cpy(struct ebitmap *dst, struct ebitmap *src) +{ + struct ebitmap_node *n, *new, *prev; + + ebitmap_init(dst); + n = src->node; + prev = NULL; + while (n) { + new = kmem_cache_zalloc(ebitmap_node_cachep, GFP_ATOMIC); + if (!new) { + ebitmap_destroy(dst); + return -ENOMEM; + } + new->startbit = n->startbit; + memcpy(new->maps, n->maps, EBITMAP_SIZE / 8); + new->next = NULL; + if (prev) + prev->next = new; + else + dst->node = new; + prev = new; + n = n->next; + } + + dst->highbit = src->highbit; + return 0; +} + +int ebitmap_and(struct ebitmap *dst, struct ebitmap *e1, struct ebitmap *e2) +{ + struct ebitmap_node *n; + int bit, 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(struct ebitmap *e1, struct ebitmap *e2, u32 last_e2bit) +{ + struct ebitmap_node *n1, *n2; + int i; + + if (e1->highbit < e2->highbit) + return 0; + + n1 = e1->node; + n2 = e2->node; + + while (n1 && n2 && (n1->startbit <= n2->startbit)) { + if (n1->startbit < n2->startbit) { + n1 = n1->next; + continue; + } + for (i = EBITMAP_UNIT_NUMS - 1; (i >= 0) && !n2->maps[i]; ) + i--; /* Skip trailing NULL map entries */ + if (last_e2bit && (i >= 0)) { + u32 lastsetbit = n2->startbit + i * EBITMAP_UNIT_SIZE + + __fls(n2->maps[i]); + if (lastsetbit > last_e2bit) + return 0; + } + + while (i >= 0) { + if ((n1->maps[i] & n2->maps[i]) != n2->maps[i]) + return 0; + i--; + } + + n1 = n1->next; + n2 = n2->next; + } + + if (n2) + return 0; + + return 1; +} + +int ebitmap_get_bit(struct ebitmap *e, unsigned long bit) +{ + struct ebitmap_node *n; + + if (e->highbit < bit) + return 0; + + n = e->node; + while (n && (n->startbit <= bit)) { + if ((n->startbit + EBITMAP_SIZE) > bit) + return ebitmap_node_get_bit(n, bit); + n = n->next; + } + + return 0; +} + +int ebitmap_set_bit(struct ebitmap *e, unsigned long bit, int value) +{ + struct ebitmap_node *n, *prev, *new; + + prev = NULL; + n = e->node; + while (n && n->startbit <= bit) { + if ((n->startbit + EBITMAP_SIZE) > bit) { + if (value) { + ebitmap_node_set_bit(n, bit); + } else { + unsigned int s; + + ebitmap_node_clr_bit(n, bit); + + s = find_first_bit(n->maps, EBITMAP_SIZE); + if (s < EBITMAP_SIZE) + return 0; + + /* drop this node from the bitmap */ + if (!n->next) { + /* + * this was the highest map + * within the bitmap + */ + if (prev) + e->highbit = prev->startbit + + EBITMAP_SIZE; + else + e->highbit = 0; + } + if (prev) + prev->next = n->next; + else + e->node = n->next; + kmem_cache_free(ebitmap_node_cachep, n); + } + return 0; + } + prev = n; + n = n->next; + } + + if (!value) + return 0; + + new = kmem_cache_zalloc(ebitmap_node_cachep, GFP_ATOMIC); + if (!new) + return -ENOMEM; + + new->startbit = bit - (bit % EBITMAP_SIZE); + ebitmap_node_set_bit(new, bit); + + if (!n) + /* this node will be the highest map within the bitmap */ + e->highbit = new->startbit + EBITMAP_SIZE; + + if (prev) { + new->next = prev->next; + prev->next = new; + } else { + new->next = e->node; + e->node = new; + } + + return 0; +} + +void ebitmap_destroy(struct ebitmap *e) +{ + struct ebitmap_node *n, *temp; + + if (!e) + return; + + n = e->node; + while (n) { + temp = n; + n = n->next; + kmem_cache_free(ebitmap_node_cachep, temp); + } + + e->highbit = 0; + e->node = NULL; + return; +} + +int ebitmap_read(struct ebitmap *e, void *fp) +{ + struct ebitmap_node *n = NULL; + u32 mapunit, count, startbit, index; + __le32 ebitmap_start; + u64 map; + __le64 mapbits; + __le32 buf[3]; + int rc, i; + + ebitmap_init(e); + + rc = next_entry(buf, fp, sizeof buf); + if (rc < 0) + goto out; + + mapunit = le32_to_cpu(buf[0]); + e->highbit = le32_to_cpu(buf[1]); + count = le32_to_cpu(buf[2]); + + if (mapunit != BITS_PER_U64) { + pr_err("SELinux: ebitmap: map size %u does not " + "match my size %zd (high bit was %d)\n", + mapunit, BITS_PER_U64, e->highbit); + goto bad; + } + + /* round up e->highbit */ + e->highbit += EBITMAP_SIZE - 1; + e->highbit -= (e->highbit % EBITMAP_SIZE); + + if (!e->highbit) { + e->node = NULL; + goto ok; + } + + if (e->highbit && !count) + goto bad; + + for (i = 0; i < count; i++) { + rc = next_entry(&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 (%d) is " + "not a multiple of the map unit size (%u)\n", + startbit, mapunit); + goto bad; + } + if (startbit > e->highbit - mapunit) { + pr_err("SELinux: ebitmap start bit (%d) is " + "beyond the end of the bitmap (%u)\n", + startbit, (e->highbit - mapunit)); + goto bad; + } + + if (!n || startbit >= n->startbit + EBITMAP_SIZE) { + struct ebitmap_node *tmp; + tmp = kmem_cache_zalloc(ebitmap_node_cachep, GFP_KERNEL); + if (!tmp) { + pr_err("SELinux: ebitmap: out of memory\n"); + rc = -ENOMEM; + goto bad; + } + /* round down */ + tmp->startbit = startbit - (startbit % EBITMAP_SIZE); + if (n) + n->next = tmp; + else + e->node = tmp; + n = tmp; + } else if (startbit <= n->startbit) { + pr_err("SELinux: ebitmap: start bit %d" + " comes after start bit %d\n", + startbit, n->startbit); + goto bad; + } + + rc = next_entry(&mapbits, fp, sizeof(u64)); + if (rc < 0) { + pr_err("SELinux: ebitmap: truncated map\n"); + goto bad; + } + map = le64_to_cpu(mapbits); + + index = (startbit - n->startbit) / EBITMAP_UNIT_SIZE; + while (map) { + n->maps[index++] = map & (-1UL); + map = EBITMAP_SHIFT_UNIT_SIZE(map); + } + } +ok: + rc = 0; +out: + return rc; +bad: + if (!rc) + rc = -EINVAL; + ebitmap_destroy(e); + goto out; +} + +int ebitmap_write(struct ebitmap *e, void *fp) +{ + struct ebitmap_node *n; + u32 count; + __le32 buf[3]; + u64 map; + int bit, last_bit, last_startbit, rc; + + buf[0] = cpu_to_le32(BITS_PER_U64); + + count = 0; + last_bit = 0; + last_startbit = -1; + ebitmap_for_each_positive_bit(e, n, bit) { + if (rounddown(bit, (int)BITS_PER_U64) > last_startbit) { + count++; + last_startbit = rounddown(bit, BITS_PER_U64); + } + last_bit = roundup(bit + 1, BITS_PER_U64); + } + buf[1] = cpu_to_le32(last_bit); + buf[2] = cpu_to_le32(count); + + rc = put_entry(buf, sizeof(u32), 3, fp); + if (rc) + return rc; + + map = 0; + last_startbit = INT_MIN; + ebitmap_for_each_positive_bit(e, n, bit) { + if (rounddown(bit, (int)BITS_PER_U64) > last_startbit) { + __le64 buf64[1]; + + /* this is the very first bit */ + if (!map) { + last_startbit = rounddown(bit, BITS_PER_U64); + map = (u64)1 << (bit - last_startbit); + continue; + } + + /* write the last node */ + buf[0] = cpu_to_le32(last_startbit); + rc = put_entry(buf, sizeof(u32), 1, fp); + if (rc) + return rc; + + buf64[0] = cpu_to_le64(map); + rc = put_entry(buf64, sizeof(u64), 1, fp); + if (rc) + return rc; + + /* set up for the next node */ + map = 0; + last_startbit = rounddown(bit, BITS_PER_U64); + } + map |= (u64)1 << (bit - last_startbit); + } + /* write the last node */ + if (map) { + __le64 buf64[1]; + + /* write the last node */ + buf[0] = cpu_to_le32(last_startbit); + rc = put_entry(buf, sizeof(u32), 1, fp); + if (rc) + return rc; + + buf64[0] = cpu_to_le64(map); + rc = put_entry(buf64, sizeof(u64), 1, fp); + if (rc) + return rc; + } + return 0; +} + +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_create("ebitmap_node", + sizeof(struct ebitmap_node), + 0, SLAB_PANIC, NULL); +} diff --git a/security/selinux/ss/ebitmap.h b/security/selinux/ss/ebitmap.h new file mode 100644 index 000000000..9eb2d0af2 --- /dev/null +++ b/security/selinux/ss/ebitmap.h @@ -0,0 +1,154 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * An extensible bitmap is a bitmap that supports an + * arbitrary number of bits. Extensible bitmaps are + * used to represent sets of values, such as types, + * roles, categories, and classes. + * + * Each extensible bitmap is implemented as a linked + * list of bitmap nodes, where each bitmap node has + * an explicitly specified starting bit position within + * the total bitmap. + * + * Author : Stephen Smalley, <sds@tycho.nsa.gov> + */ +#ifndef _SS_EBITMAP_H_ +#define _SS_EBITMAP_H_ + +#include <net/netlabel.h> + +#ifdef CONFIG_64BIT +#define EBITMAP_NODE_SIZE 64 +#else +#define EBITMAP_NODE_SIZE 32 +#endif + +#define EBITMAP_UNIT_NUMS ((EBITMAP_NODE_SIZE-sizeof(void *)-sizeof(u32))\ + / sizeof(unsigned long)) +#define EBITMAP_UNIT_SIZE BITS_PER_LONG +#define EBITMAP_SIZE (EBITMAP_UNIT_NUMS * EBITMAP_UNIT_SIZE) +#define EBITMAP_BIT 1ULL +#define EBITMAP_SHIFT_UNIT_SIZE(x) \ + (((x) >> EBITMAP_UNIT_SIZE / 2) >> EBITMAP_UNIT_SIZE / 2) + +struct ebitmap_node { + struct ebitmap_node *next; + unsigned long maps[EBITMAP_UNIT_NUMS]; + u32 startbit; +}; + +struct ebitmap { + struct ebitmap_node *node; /* first node in the bitmap */ + u32 highbit; /* highest position in the total bitmap */ +}; + +#define ebitmap_length(e) ((e)->highbit) + +static inline unsigned int ebitmap_start_positive(struct ebitmap *e, + struct ebitmap_node **n) +{ + unsigned int ofs; + + for (*n = e->node; *n; *n = (*n)->next) { + ofs = find_first_bit((*n)->maps, EBITMAP_SIZE); + if (ofs < EBITMAP_SIZE) + return (*n)->startbit + ofs; + } + return ebitmap_length(e); +} + +static inline void ebitmap_init(struct ebitmap *e) +{ + memset(e, 0, sizeof(*e)); +} + +static inline unsigned int ebitmap_next_positive(struct ebitmap *e, + struct ebitmap_node **n, + unsigned int bit) +{ + unsigned int ofs; + + ofs = find_next_bit((*n)->maps, EBITMAP_SIZE, bit - (*n)->startbit + 1); + if (ofs < EBITMAP_SIZE) + return ofs + (*n)->startbit; + + for (*n = (*n)->next; *n; *n = (*n)->next) { + ofs = find_first_bit((*n)->maps, EBITMAP_SIZE); + if (ofs < EBITMAP_SIZE) + return ofs + (*n)->startbit; + } + return ebitmap_length(e); +} + +#define EBITMAP_NODE_INDEX(node, bit) \ + (((bit) - (node)->startbit) / EBITMAP_UNIT_SIZE) +#define EBITMAP_NODE_OFFSET(node, bit) \ + (((bit) - (node)->startbit) % EBITMAP_UNIT_SIZE) + +static inline int ebitmap_node_get_bit(struct ebitmap_node *n, + unsigned int bit) +{ + unsigned int index = EBITMAP_NODE_INDEX(n, bit); + unsigned int ofs = EBITMAP_NODE_OFFSET(n, bit); + + BUG_ON(index >= EBITMAP_UNIT_NUMS); + if ((n->maps[index] & (EBITMAP_BIT << ofs))) + return 1; + return 0; +} + +static inline void ebitmap_node_set_bit(struct ebitmap_node *n, + unsigned int bit) +{ + unsigned int index = EBITMAP_NODE_INDEX(n, bit); + unsigned int ofs = EBITMAP_NODE_OFFSET(n, bit); + + BUG_ON(index >= EBITMAP_UNIT_NUMS); + n->maps[index] |= (EBITMAP_BIT << ofs); +} + +static inline void ebitmap_node_clr_bit(struct ebitmap_node *n, + unsigned int bit) +{ + unsigned int index = EBITMAP_NODE_INDEX(n, bit); + unsigned int ofs = EBITMAP_NODE_OFFSET(n, bit); + + BUG_ON(index >= EBITMAP_UNIT_NUMS); + n->maps[index] &= ~(EBITMAP_BIT << ofs); +} + +#define ebitmap_for_each_positive_bit(e, n, bit) \ + for (bit = ebitmap_start_positive(e, &n); \ + bit < ebitmap_length(e); \ + bit = ebitmap_next_positive(e, &n, bit)) \ + +int ebitmap_cmp(struct ebitmap *e1, struct ebitmap *e2); +int ebitmap_cpy(struct ebitmap *dst, struct ebitmap *src); +int ebitmap_and(struct ebitmap *dst, struct ebitmap *e1, struct ebitmap *e2); +int ebitmap_contains(struct ebitmap *e1, struct ebitmap *e2, u32 last_e2bit); +int ebitmap_get_bit(struct ebitmap *e, unsigned long bit); +int ebitmap_set_bit(struct ebitmap *e, unsigned long bit, int value); +void ebitmap_destroy(struct ebitmap *e); +int ebitmap_read(struct ebitmap *e, void *fp); +int ebitmap_write(struct ebitmap *e, void *fp); +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..e8960a595 --- /dev/null +++ b/security/selinux/ss/hashtab.c @@ -0,0 +1,191 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Implementation of the hash table type. + * + * Author : Stephen Smalley, <sds@tycho.nsa.gov> + */ +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/errno.h> +#include "hashtab.h" + +static struct kmem_cache *hashtab_node_cachep; + +/* + * Here we simply round the number of elements up to the nearest power of two. + * I tried also other options like rouding 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 = kcalloc(size, sizeof(*h->htable), GFP_KERNEL); + 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; +} + + +void hashtab_stat(struct hashtab *h, struct hashtab_info *info) +{ + u32 i, chain_len, slots_used, max_chain_len; + struct hashtab_node *cur; + + slots_used = 0; + max_chain_len = 0; + for (i = 0; i < h->size; i++) { + cur = h->htable[i]; + if (cur) { + slots_used++; + chain_len = 0; + while (cur) { + chain_len++; + cur = cur->next; + } + + if (chain_len > max_chain_len) + max_chain_len = chain_len; + } + } + + info->slots_used = slots_used; + info->max_chain_len = max_chain_len; +} + +int hashtab_duplicate(struct hashtab *new, struct hashtab *orig, + int (*copy)(struct hashtab_node *new, + struct hashtab_node *orig, void *args), + int (*destroy)(void *k, void *d, void *args), + void *args) +{ + struct hashtab_node *cur, *tmp, *tail; + int i, rc; + + memset(new, 0, sizeof(*new)); + + new->htable = kcalloc(orig->size, sizeof(*new->htable), GFP_KERNEL); + if (!new->htable) + return -ENOMEM; + + new->size = orig->size; + + for (i = 0; i < orig->size; i++) { + tail = NULL; + for (cur = orig->htable[i]; cur; cur = cur->next) { + tmp = kmem_cache_zalloc(hashtab_node_cachep, + GFP_KERNEL); + if (!tmp) + goto error; + rc = copy(tmp, 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_create("hashtab_node", + sizeof(struct hashtab_node), + 0, SLAB_PANIC, NULL); +} diff --git a/security/selinux/ss/hashtab.h b/security/selinux/ss/hashtab.h new file mode 100644 index 000000000..043a773bf --- /dev/null +++ b/security/selinux/ss/hashtab.h @@ -0,0 +1,148 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * A hash table (hashtab) maintains associations between + * key values and datum values. The type of the key values + * and the type of the datum values is arbitrary. The + * functions for hash computation and key comparison are + * provided by the creator of the table. + * + * Author : Stephen Smalley, <sds@tycho.nsa.gov> + */ +#ifndef _SS_HASHTAB_H_ +#define _SS_HASHTAB_H_ + +#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 function */ + int (*cmp)(const void *key1, const void *key2); + /* key comparison function */ +}; + +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; +}; + +/* + * 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(struct hashtab *h, const void *key, + struct hashtab_key_params key_params) +{ + u32 hvalue; + 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, struct hashtab *orig, + int (*copy)(struct hashtab_node *new, + struct hashtab_node *orig, void *args), + int (*destroy)(void *k, void *d, void *args), + void *args); + +/* Fill info with some hash table statistics */ +void hashtab_stat(struct hashtab *h, struct hashtab_info *info); + +#endif /* _SS_HASHTAB_H */ diff --git a/security/selinux/ss/mls.c b/security/selinux/ss/mls.c new file mode 100644 index 000000000..d338962fb --- /dev/null +++ b/security/selinux/ss/mls.c @@ -0,0 +1,657 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Implementation of the multi-level security (MLS) policy. + * + * Author : Stephen Smalley, <sds@tycho.nsa.gov> + */ +/* + * Updated: Trusted Computer Solutions, Inc. <dgoeddel@trustedcs.com> + * + * Support for enhanced MLS infrastructure. + * + * Copyright (C) 2004-2006 Trusted Computer Solutions, Inc. + */ +/* + * Updated: Hewlett-Packard <paul@paul-moore.com> + * + * Added support to import/export the MLS label from NetLabel + * + * (c) Copyright Hewlett-Packard Development Company, L.P., 2006 + */ + +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/string.h> +#include <linux/errno.h> +#include <net/netlabel.h> +#include "sidtab.h" +#include "mls.h" +#include "policydb.h" +#include "services.h" + +/* + * Return the length in bytes for the MLS fields of the + * security context string representation of `context'. + */ +int mls_compute_context_len(struct policydb *p, struct context *context) +{ + int i, l, len, head, prev; + char *nm; + struct ebitmap *e; + struct ebitmap_node *node; + + if (!p->mls_enabled) + return 0; + + len = 1; /* for the beginning ":" */ + for (l = 0; l < 2; l++) { + int index_sens = context->range.level[l].sens; + len += strlen(sym_name(p, SYM_LEVELS, index_sens - 1)); + + /* categories */ + head = -2; + prev = -2; + e = &context->range.level[l].cat; + ebitmap_for_each_positive_bit(e, node, i) { + if (i - prev > 1) { + /* one or more negative bits are skipped */ + if (head != prev) { + nm = sym_name(p, SYM_CATS, prev); + len += strlen(nm) + 1; + } + nm = sym_name(p, SYM_CATS, i); + len += strlen(nm) + 1; + head = i; + } + prev = i; + } + if (prev != head) { + nm = sym_name(p, SYM_CATS, prev); + len += strlen(nm) + 1; + } + if (l == 0) { + if (mls_level_eq(&context->range.level[0], + &context->range.level[1])) + break; + else + len++; + } + } + + return len; +} + +/* + * Write the security context string representation of + * the MLS fields of `context' into the string `*scontext'. + * Update `*scontext' to point to the end of the MLS fields. + */ +void mls_sid_to_context(struct policydb *p, + struct context *context, + char **scontext) +{ + char *scontextp, *nm; + int i, l, head, prev; + struct ebitmap *e; + struct ebitmap_node *node; + + if (!p->mls_enabled) + return; + + scontextp = *scontext; + + *scontextp = ':'; + scontextp++; + + for (l = 0; l < 2; l++) { + strcpy(scontextp, sym_name(p, SYM_LEVELS, + context->range.level[l].sens - 1)); + scontextp += strlen(scontextp); + + /* categories */ + head = -2; + prev = -2; + e = &context->range.level[l].cat; + ebitmap_for_each_positive_bit(e, node, i) { + if (i - prev > 1) { + /* one or more negative bits are skipped */ + if (prev != head) { + if (prev - head > 1) + *scontextp++ = '.'; + else + *scontextp++ = ','; + nm = sym_name(p, SYM_CATS, prev); + strcpy(scontextp, nm); + scontextp += strlen(nm); + } + if (prev < 0) + *scontextp++ = ':'; + else + *scontextp++ = ','; + nm = sym_name(p, SYM_CATS, i); + strcpy(scontextp, nm); + scontextp += strlen(nm); + head = i; + } + prev = i; + } + + if (prev != head) { + if (prev - head > 1) + *scontextp++ = '.'; + else + *scontextp++ = ','; + nm = sym_name(p, SYM_CATS, prev); + strcpy(scontextp, nm); + scontextp += strlen(nm); + } + + if (l == 0) { + if (mls_level_eq(&context->range.level[0], + &context->range.level[1])) + break; + else + *scontextp++ = '-'; + } + } + + *scontext = scontextp; + return; +} + +int mls_level_isvalid(struct policydb *p, struct mls_level *l) +{ + struct level_datum *levdatum; + + if (!l->sens || l->sens > p->p_levels.nprim) + return 0; + levdatum = symtab_search(&p->p_levels, + sym_name(p, SYM_LEVELS, l->sens - 1)); + if (!levdatum) + return 0; + + /* + * Return 1 iff all the bits set in l->cat are also be set in + * levdatum->level->cat and no bit in l->cat is larger than + * p->p_cats.nprim. + */ + return ebitmap_contains(&levdatum->level->cat, &l->cat, + p->p_cats.nprim); +} + +int mls_range_isvalid(struct policydb *p, struct mls_range *r) +{ + return (mls_level_isvalid(p, &r->level[0]) && + mls_level_isvalid(p, &r->level[1]) && + mls_level_dom(&r->level[1], &r->level[0])); +} + +/* + * Return 1 if the MLS fields in the security context + * structure `c' are valid. Return 0 otherwise. + */ +int mls_context_isvalid(struct policydb *p, struct context *c) +{ + struct user_datum *usrdatum; + + if (!p->mls_enabled) + return 1; + + if (!mls_range_isvalid(p, &c->range)) + return 0; + + if (c->role == OBJECT_R_VAL) + return 1; + + /* + * User must be authorized for the MLS range. + */ + if (!c->user || c->user > p->p_users.nprim) + return 0; + usrdatum = p->user_val_to_struct[c->user - 1]; + if (!mls_range_contains(usrdatum->range, c->range)) + return 0; /* user may not be associated with range */ + + return 1; +} + +/* + * Set the MLS fields in the security context structure + * `context' based on the string representation in + * the string `scontext'. + * + * 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; + int l, rc, i; + 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; + int l, i; + + if (!oldp->mls_enabled || !newp->mls_enabled) + return 0; + + for (l = 0; l < 2; l++) { + 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; + int default_range = 0; + + if (!p->mls_enabled) + return 0; + + switch (specified) { + case AVTAB_TRANSITION: + /* Look for a range transition rule. */ + rtr.source_type = scontext->type; + rtr.target_type = tcontext->type; + rtr.target_class = tclass; + r = 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 + * @context: the security context + * @secattr: the NetLabel security attributes + * + * Description: + * Given the security context copy the low MLS sensitivity level into the + * NetLabel MLS sensitivity level field. + * + */ +void mls_export_netlbl_lvl(struct policydb *p, + struct context *context, + struct netlbl_lsm_secattr *secattr) +{ + if (!p->mls_enabled) + return; + + secattr->attr.mls.lvl = context->range.level[0].sens - 1; + secattr->flags |= NETLBL_SECATTR_MLS_LVL; +} + +/** + * mls_import_netlbl_lvl - Import the NetLabel MLS sensitivity levels + * @context: the security context + * @secattr: the NetLabel security attributes + * + * Description: + * Given the security context and the NetLabel security attributes, copy the + * NetLabel MLS sensitivity level into the context. + * + */ +void mls_import_netlbl_lvl(struct policydb *p, + struct context *context, + struct netlbl_lsm_secattr *secattr) +{ + if (!p->mls_enabled) + return; + + context->range.level[0].sens = secattr->attr.mls.lvl + 1; + context->range.level[1].sens = context->range.level[0].sens; +} + +/** + * mls_export_netlbl_cat - Export the MLS categories to NetLabel + * @context: the security context + * @secattr: the NetLabel security attributes + * + * Description: + * Given the security context copy the low MLS categories into the NetLabel + * MLS category field. Returns zero on success, negative values on failure. + * + */ +int mls_export_netlbl_cat(struct policydb *p, + struct context *context, + struct netlbl_lsm_secattr *secattr) +{ + int rc; + + if (!p->mls_enabled) + return 0; + + rc = ebitmap_netlbl_export(&context->range.level[0].cat, + &secattr->attr.mls.cat); + if (rc == 0 && secattr->attr.mls.cat != NULL) + secattr->flags |= NETLBL_SECATTR_MLS_CAT; + + return rc; +} + +/** + * mls_import_netlbl_cat - Import the MLS categories from NetLabel + * @context: the security context + * @secattr: the NetLabel security attributes + * + * Description: + * Copy the NetLabel security attributes into the SELinux context; since the + * NetLabel security attribute only contains a single MLS category use it for + * both the low and high categories of the context. Returns zero on success, + * negative values on failure. + * + */ +int mls_import_netlbl_cat(struct policydb *p, + struct context *context, + struct netlbl_lsm_secattr *secattr) +{ + int rc; + + if (!p->mls_enabled) + return 0; + + rc = ebitmap_netlbl_import(&context->range.level[0].cat, + secattr->attr.mls.cat); + if (rc) + goto import_netlbl_cat_failure; + memcpy(&context->range.level[1].cat, &context->range.level[0].cat, + sizeof(context->range.level[0].cat)); + + return 0; + +import_netlbl_cat_failure: + ebitmap_destroy(&context->range.level[0].cat); + return rc; +} +#endif /* CONFIG_NETLABEL */ diff --git a/security/selinux/ss/mls.h b/security/selinux/ss/mls.h new file mode 100644 index 000000000..15cacde0f --- /dev/null +++ b/security/selinux/ss/mls.h @@ -0,0 +1,116 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Multi-level security (MLS) policy operations. + * + * Author : Stephen Smalley, <sds@tycho.nsa.gov> + */ +/* + * Updated: Trusted Computer Solutions, Inc. <dgoeddel@trustedcs.com> + * + * Support for enhanced MLS infrastructure. + * + * Copyright (C) 2004-2006 Trusted Computer Solutions, Inc. + */ +/* + * Updated: Hewlett-Packard <paul@paul-moore.com> + * + * Added support to import/export the MLS label from NetLabel + * + * (c) Copyright Hewlett-Packard Development Company, L.P., 2006 + */ + +#ifndef _SS_MLS_H_ +#define _SS_MLS_H_ + +#include <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); +int mls_context_isvalid(struct policydb *p, struct context *c); +int mls_range_isvalid(struct policydb *p, struct mls_range *r); +int mls_level_isvalid(struct policydb *p, struct mls_level *l); + +int mls_context_to_sid(struct policydb *p, + char oldc, + char *scontext, + struct context *context, + struct sidtab *s, + u32 def_sid); + +int mls_from_string(struct policydb *p, char *str, struct context *context, + gfp_t gfp_mask); + +int mls_range_set(struct context *context, struct mls_range *range); + +int mls_convert_context(struct policydb *oldp, + struct policydb *newp, + struct context *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..068e0d780 --- /dev/null +++ b/security/selinux/ss/mls_types.h @@ -0,0 +1,52 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Type definitions for the multi-level security (MLS) policy. + * + * Author : Stephen Smalley, <sds@tycho.nsa.gov> + */ +/* + * Updated: Trusted Computer Solutions, Inc. <dgoeddel@trustedcs.com> + * + * Support for enhanced MLS infrastructure. + * + * Copyright (C) 2004-2005 Trusted Computer Solutions, Inc. + */ + +#ifndef _SS_MLS_TYPES_H_ +#define _SS_MLS_TYPES_H_ + +#include "security.h" +#include "ebitmap.h" + +struct mls_level { + u32 sens; /* sensitivity */ + struct ebitmap cat; /* category set */ +}; + +struct mls_range { + struct mls_level level[2]; /* low == level[0], high == level[1] */ +}; + +static inline int mls_level_eq(struct mls_level *l1, struct mls_level *l2) +{ + return ((l1->sens == l2->sens) && + ebitmap_cmp(&l1->cat, &l2->cat)); +} + +static inline int mls_level_dom(struct mls_level *l1, struct mls_level *l2) +{ + return ((l1->sens >= l2->sens) && + ebitmap_contains(&l1->cat, &l2->cat, 0)); +} + +#define mls_level_incomp(l1, l2) \ +(!mls_level_dom((l1), (l2)) && !mls_level_dom((l2), (l1))) + +#define mls_level_between(l1, l2, l3) \ +(mls_level_dom((l1), (l2)) && mls_level_dom((l3), (l1))) + +#define mls_range_contains(r1, r2) \ +(mls_level_dom(&(r2).level[0], &(r1).level[0]) && \ + mls_level_dom(&(r1).level[1], &(r2).level[1])) + +#endif /* _SS_MLS_TYPES_H_ */ diff --git a/security/selinux/ss/policydb.c b/security/selinux/ss/policydb.c new file mode 100644 index 000000000..82cfeab16 --- /dev/null +++ b/security/selinux/ss/policydb.c @@ -0,0 +1,3740 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Implementation of the policy database. + * + * Author : Stephen Smalley, <sds@tycho.nsa.gov> + */ + +/* + * Updated: Trusted Computer Solutions, Inc. <dgoeddel@trustedcs.com> + * + * Support for enhanced MLS infrastructure. + * + * Updated: Frank Mayer <mayerf@tresys.com> and Karl MacMillan <kmacmillan@tresys.com> + * + * Added conditional policy language extensions + * + * Updated: Hewlett-Packard <paul@paul-moore.com> + * + * Added support for the policy capability bitmap + * + * Update: Mellanox Techonologies + * + * Added Infiniband support + * + * Copyright (C) 2016 Mellanox Techonologies + * Copyright (C) 2007 Hewlett-Packard Development Company, L.P. + * Copyright (C) 2004-2005 Trusted Computer Solutions, Inc. + * Copyright (C) 2003 - 2004 Tresys Technology, LLC + */ + +#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 "security.h" + +#include "policydb.h" +#include "conditional.h" +#include "mls.h" +#include "services.h" + +#define _DEBUG_HASHES + +#ifdef DEBUG_HASHES +static const char *symtab_name[SYM_NUM] = { + "common prefixes", + "classes", + "roles", + "types", + "users", + "bools", + "levels", + "categories", +}; +#endif + +struct policydb_compat_info { + int version; + int sym_num; + int ocon_num; +}; + +/* These need to be updated if SYM_NUM or OCON_NUM changes */ +static struct policydb_compat_info policydb_compat[] = { + { + .version = POLICYDB_VERSION_BASE, + .sym_num = SYM_NUM - 3, + .ocon_num = OCON_NUM - 3, + }, + { + .version = POLICYDB_VERSION_BOOL, + .sym_num = SYM_NUM - 2, + .ocon_num = OCON_NUM - 3, + }, + { + .version = POLICYDB_VERSION_IPV6, + .sym_num = SYM_NUM - 2, + .ocon_num = OCON_NUM - 2, + }, + { + .version = POLICYDB_VERSION_NLCLASS, + .sym_num = SYM_NUM - 2, + .ocon_num = OCON_NUM - 2, + }, + { + .version = POLICYDB_VERSION_MLS, + .sym_num = SYM_NUM, + .ocon_num = OCON_NUM - 2, + }, + { + .version = POLICYDB_VERSION_AVTAB, + .sym_num = SYM_NUM, + .ocon_num = OCON_NUM - 2, + }, + { + .version = POLICYDB_VERSION_RANGETRANS, + .sym_num = SYM_NUM, + .ocon_num = OCON_NUM - 2, + }, + { + .version = POLICYDB_VERSION_POLCAP, + .sym_num = SYM_NUM, + .ocon_num = OCON_NUM - 2, + }, + { + .version = POLICYDB_VERSION_PERMISSIVE, + .sym_num = SYM_NUM, + .ocon_num = OCON_NUM - 2, + }, + { + .version = POLICYDB_VERSION_BOUNDARY, + .sym_num = SYM_NUM, + .ocon_num = OCON_NUM - 2, + }, + { + .version = POLICYDB_VERSION_FILENAME_TRANS, + .sym_num = SYM_NUM, + .ocon_num = OCON_NUM - 2, + }, + { + .version = POLICYDB_VERSION_ROLETRANS, + .sym_num = SYM_NUM, + .ocon_num = OCON_NUM - 2, + }, + { + .version = POLICYDB_VERSION_NEW_OBJECT_DEFAULTS, + .sym_num = SYM_NUM, + .ocon_num = OCON_NUM - 2, + }, + { + .version = POLICYDB_VERSION_DEFAULT_TYPE, + .sym_num = SYM_NUM, + .ocon_num = OCON_NUM - 2, + }, + { + .version = POLICYDB_VERSION_CONSTRAINT_NAMES, + .sym_num = SYM_NUM, + .ocon_num = OCON_NUM - 2, + }, + { + .version = POLICYDB_VERSION_XPERMS_IOCTL, + .sym_num = SYM_NUM, + .ocon_num = OCON_NUM - 2, + }, + { + .version = POLICYDB_VERSION_INFINIBAND, + .sym_num = SYM_NUM, + .ocon_num = OCON_NUM, + }, + { + .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, + }, +}; + +static struct policydb_compat_info *policydb_lookup_compat(int version) +{ + int i; + struct policydb_compat_info *info = NULL; + + for (i = 0; i < ARRAY_SIZE(policydb_compat); i++) { + if (policydb_compat[i].version == version) { + info = &policydb_compat[i]; + break; + } + } + return info; +} + +/* + * The following *_destroy functions are used to + * free any memory allocated for each kind of + * symbol data in the policy database. + */ + +static int perm_destroy(void *key, void *datum, void *p) +{ + kfree(key); + kfree(datum); + return 0; +} + +static int common_destroy(void *key, void *datum, void *p) +{ + struct common_datum *comdatum; + + kfree(key); + if (datum) { + comdatum = datum; + hashtab_map(&comdatum->permissions.table, perm_destroy, NULL); + hashtab_destroy(&comdatum->permissions.table); + } + kfree(datum); + return 0; +} + +static void constraint_expr_destroy(struct constraint_expr *expr) +{ + if (expr) { + ebitmap_destroy(&expr->names); + if (expr->type_names) { + ebitmap_destroy(&expr->type_names->types); + ebitmap_destroy(&expr->type_names->negset); + kfree(expr->type_names); + } + kfree(expr); + } +} + +static int cls_destroy(void *key, void *datum, void *p) +{ + struct class_datum *cladatum; + struct constraint_node *constraint, *ctemp; + struct constraint_expr *e, *etmp; + + kfree(key); + if (datum) { + cladatum = datum; + hashtab_map(&cladatum->permissions.table, perm_destroy, NULL); + hashtab_destroy(&cladatum->permissions.table); + constraint = cladatum->constraints; + while (constraint) { + e = constraint->expr; + while (e) { + etmp = e; + e = e->next; + constraint_expr_destroy(etmp); + } + ctemp = constraint; + constraint = constraint->next; + kfree(ctemp); + } + + constraint = cladatum->validatetrans; + while (constraint) { + e = constraint->expr; + while (e) { + etmp = e; + e = e->next; + constraint_expr_destroy(etmp); + } + ctemp = constraint; + constraint = constraint->next; + kfree(ctemp); + } + kfree(cladatum->comkey); + } + kfree(datum); + return 0; +} + +static int role_destroy(void *key, void *datum, void *p) +{ + struct role_datum *role; + + kfree(key); + if (datum) { + role = datum; + ebitmap_destroy(&role->dominates); + ebitmap_destroy(&role->types); + } + kfree(datum); + return 0; +} + +static int type_destroy(void *key, void *datum, void *p) +{ + kfree(key); + kfree(datum); + return 0; +} + +static int user_destroy(void *key, void *datum, void *p) +{ + struct user_datum *usrdatum; + + kfree(key); + if (datum) { + usrdatum = datum; + ebitmap_destroy(&usrdatum->roles); + ebitmap_destroy(&usrdatum->range.level[0].cat); + ebitmap_destroy(&usrdatum->range.level[1].cat); + ebitmap_destroy(&usrdatum->dfltlevel.cat); + } + kfree(datum); + return 0; +} + +static int sens_destroy(void *key, void *datum, void *p) +{ + struct level_datum *levdatum; + + kfree(key); + if (datum) { + levdatum = datum; + if (levdatum->level) + ebitmap_destroy(&levdatum->level->cat); + kfree(levdatum->level); + } + kfree(datum); + return 0; +} + +static int cat_destroy(void *key, void *datum, void *p) +{ + kfree(key); + kfree(datum); + return 0; +} + +static int (*destroy_f[SYM_NUM]) (void *key, void *datum, void *datap) = +{ + common_destroy, + cls_destroy, + role_destroy, + type_destroy, + user_destroy, + cond_destroy_bool, + sens_destroy, + cat_destroy, +}; + +static int filenametr_destroy(void *key, void *datum, void *p) +{ + struct filename_trans_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, 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(sizeof(*role), GFP_KERNEL); + if (!role) + return -ENOMEM; + + rc = -EINVAL; + role->value = ++p->p_roles.nprim; + if (role->value != OBJECT_R_VAL) + goto out; + + rc = -ENOMEM; + key = kstrdup(OBJECT_R, GFP_KERNEL); + if (!key) + goto out; + + rc = 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 hash; + unsigned int byte_num; + unsigned char focus; + + hash = ft->ttype ^ ft->tclass; + + byte_num = 0; + while ((focus = ft->name[byte_num++])) + hash = partial_name_hash(focus, hash); + return hash; +} + +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 = ft1->ttype - ft2->ttype; + if (v) + return v; + + v = 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 = key1->source_type - key2->source_type; + if (v) + return v; + + v = key1->target_type - key2->target_type; + if (v) + return v; + + v = key1->target_class - key2->target_class; + + return v; +} + +static 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 key->role + (key->type << 3) + (key->tclass << 5); +} + +static int role_trans_cmp(const void *k1, const void *k2) +{ + const struct role_trans_key *key1 = k1, *key2 = k2; + int v; + + v = key1->role - key2->role; + if (v) + return v; + + v = key1->type - key2->type; + if (v) + return v; + + return 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); +} + +/* + * 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->primary) { + if (!typdatum->value + || typdatum->value > p->p_types.nprim + || typdatum->bounds > p->p_types.nprim) + return -EINVAL; + 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->isalias) { + if (!levdatum->level->sens || + levdatum->level->sens > p->p_levels.nprim) + return -EINVAL; + + 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->isalias) { + if (!catdatum->value || catdatum->value > p->p_cats.nprim) + return -EINVAL; + + p->sym_val_to_name[SYM_CATS][catdatum->value - 1] = key; + } + + return 0; +} + +static int (*index_f[SYM_NUM]) (void *key, void *datum, void *datap) = +{ + common_index, + class_index, + role_index, + type_index, + user_index, + cond_index_bool, + sens_index, + cat_index, +}; + +#ifdef DEBUG_HASHES +static void hash_eval(struct hashtab *h, const char *hash_name) +{ + struct hashtab_info info; + + hashtab_stat(h, &info); + pr_debug("SELinux: %s: %d entries and %d/%d buckets used, longest chain length %d\n", + hash_name, h->nel, info.slots_used, h->size, + info.max_chain_len); +} + +static void symtab_hash_eval(struct symtab *s) +{ + int i; + + for (i = 0; i < SYM_NUM; i++) + hash_eval(&s[i].table, symtab_name[i]); +} + +#else +static inline void hash_eval(struct hashtab *h, char *hash_name) +{ +} +#endif + +/* + * Define the other val_to_name and val_to_struct arrays + * in a policy database structure. + * + * Caller must clean up on failure. + */ +static int policydb_index(struct policydb *p) +{ + int i, rc; + + if (p->mls_enabled) + pr_debug("SELinux: %d users, %d roles, %d types, %d bools, %d sens, %d cats\n", + p->p_users.nprim, p->p_roles.nprim, p->p_types.nprim, + p->p_bools.nprim, p->p_levels.nprim, p->p_cats.nprim); + else + pr_debug("SELinux: %d users, %d roles, %d types, %d bools\n", + p->p_users.nprim, p->p_roles.nprim, p->p_types.nprim, + p->p_bools.nprim); + + pr_debug("SELinux: %d classes, %d rules\n", + p->p_classes.nprim, p->te_avtab.nel); + +#ifdef DEBUG_HASHES + avtab_hash_eval(&p->te_avtab, "rules"); + symtab_hash_eval(p->symtab); +#endif + + p->class_val_to_struct = kcalloc(p->p_classes.nprim, + sizeof(*p->class_val_to_struct), + GFP_KERNEL); + if (!p->class_val_to_struct) + return -ENOMEM; + + p->role_val_to_struct = kcalloc(p->p_roles.nprim, + sizeof(*p->role_val_to_struct), + GFP_KERNEL); + if (!p->role_val_to_struct) + return -ENOMEM; + + p->user_val_to_struct = kcalloc(p->p_users.nprim, + sizeof(*p->user_val_to_struct), + GFP_KERNEL); + if (!p->user_val_to_struct) + return -ENOMEM; + + p->type_val_to_struct = kvcalloc(p->p_types.nprim, + sizeof(*p->type_val_to_struct), + GFP_KERNEL); + 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; + } + 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; + int 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); +} + +/* + * Load the initial SIDs specified in a policy database + * structure into a SID table. + */ +int policydb_load_isids(struct policydb *p, struct sidtab *s) +{ + struct ocontext *head, *c; + int rc; + + rc = sidtab_init(s); + if (rc) { + pr_err("SELinux: out of memory on SID table init\n"); + return rc; + } + + 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; + + 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; + } + } + return 0; +} + +int policydb_class_isvalid(struct policydb *p, unsigned int class) +{ + if (!class || class > p->p_classes.nprim) + return 0; + return 1; +} + +int policydb_role_isvalid(struct policydb *p, unsigned int role) +{ + if (!role || role > p->p_roles.nprim) + return 0; + return 1; +} + +int policydb_type_isvalid(struct policydb *p, unsigned int type) +{ + if (!type || type > p->p_types.nprim) + return 0; + return 1; +} + +/* + * Return 1 if the fields in the security context + * structure `c' are valid. Return 0 otherwise. + */ +int policydb_context_isvalid(struct policydb *p, struct context *c) +{ + struct role_datum *role; + struct user_datum *usrdatum; + + if (!c->role || c->role > p->p_roles.nprim) + return 0; + + if (!c->user || c->user > p->p_users.nprim) + return 0; + + if (!c->type || c->type > p->p_types.nprim) + return 0; + + if (c->role != OBJECT_R_VAL) { + /* + * Role must be authorized for the type. + */ + role = p->role_val_to_struct[c->role - 1]; + if (!role || !ebitmap_get_bit(&role->types, c->type - 1)) + /* role may not be associated with type */ + return 0; + + /* + * User must be authorized for the role. + */ + usrdatum = p->user_val_to_struct[c->user - 1]; + if (!usrdatum) + return 0; + + if (!ebitmap_get_bit(&usrdatum->roles, c->role - 1)) + /* user may not be associated with role */ + return 0; + } + + if (!mls_context_isvalid(p, c)) + return 0; + + return 1; +} + +/* + * Read a MLS range structure from a policydb binary + * representation file. + */ +static int mls_read_range_helper(struct mls_range *r, void *fp) +{ + __le32 buf[2]; + u32 items; + int rc; + + rc = next_entry(buf, fp, sizeof(u32)); + if (rc) + goto out; + + rc = -EINVAL; + items = le32_to_cpu(buf[0]); + if (items > ARRAY_SIZE(buf)) { + pr_err("SELinux: mls: range overflow\n"); + goto out; + } + + rc = next_entry(buf, fp, sizeof(u32) * items); + if (rc) { + pr_err("SELinux: mls: truncated range\n"); + goto out; + } + + r->level[0].sens = le32_to_cpu(buf[0]); + if (items > 1) + r->level[1].sens = le32_to_cpu(buf[1]); + else + r->level[1].sens = r->level[0].sens; + + rc = ebitmap_read(&r->level[0].cat, fp); + if (rc) { + pr_err("SELinux: mls: error reading low categories\n"); + goto out; + } + if (items > 1) { + rc = ebitmap_read(&r->level[1].cat, fp); + if (rc) { + pr_err("SELinux: mls: error reading high categories\n"); + goto bad_high; + } + } else { + rc = ebitmap_cpy(&r->level[1].cat, &r->level[0].cat); + if (rc) { + pr_err("SELinux: mls: out of memory\n"); + goto bad_high; + } + } + + return 0; +bad_high: + ebitmap_destroy(&r->level[0].cat); +out: + return rc; +} + +/* + * Read and validate a security context structure + * from a policydb binary representation file. + */ +static int context_read_and_validate(struct context *c, + struct policydb *p, + void *fp) +{ + __le32 buf[3]; + int rc; + + rc = next_entry(buf, fp, sizeof buf); + if (rc) { + pr_err("SELinux: context truncated\n"); + goto out; + } + c->user = le32_to_cpu(buf[0]); + c->role = le32_to_cpu(buf[1]); + c->type = le32_to_cpu(buf[2]); + if (p->policyvers >= POLICYDB_VERSION_MLS) { + rc = mls_read_range_helper(&c->range, fp); + if (rc) { + pr_err("SELinux: error reading MLS range of context\n"); + goto out; + } + } + + rc = -EINVAL; + if (!policydb_context_isvalid(p, c)) { + pr_err("SELinux: invalid security context\n"); + context_destroy(c); + goto out; + } + rc = 0; +out: + return rc; +} + +/* + * The following *_read functions are used to + * read the symbol data from a policy database + * binary representation file. + */ + +static int str_read(char **strp, gfp_t flags, void *fp, u32 len) +{ + int rc; + char *str; + + if ((len == 0) || (len == (u32)-1)) + return -EINVAL; + + str = kmalloc(len + 1, flags | __GFP_NOWARN); + if (!str) + return -ENOMEM; + + rc = next_entry(str, fp, len); + if (rc) { + kfree(str); + return rc; + } + + str[len] = '\0'; + *strp = str; + return 0; +} + +static int perm_read(struct policydb *p, struct symtab *s, void *fp) +{ + char *key = NULL; + struct perm_datum *perdatum; + int rc; + __le32 buf[2]; + u32 len; + + perdatum = kzalloc(sizeof(*perdatum), GFP_KERNEL); + if (!perdatum) + return -ENOMEM; + + rc = next_entry(buf, fp, sizeof buf); + if (rc) + goto bad; + + len = le32_to_cpu(buf[0]); + perdatum->value = le32_to_cpu(buf[1]); + + rc = str_read(&key, GFP_KERNEL, fp, len); + if (rc) + goto bad; + + rc = 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, void *fp) +{ + char *key = NULL; + struct common_datum *comdatum; + __le32 buf[4]; + u32 len, nel; + int i, rc; + + comdatum = kzalloc(sizeof(*comdatum), GFP_KERNEL); + if (!comdatum) + return -ENOMEM; + + rc = next_entry(buf, fp, sizeof buf); + if (rc) + goto bad; + + len = le32_to_cpu(buf[0]); + comdatum->value = le32_to_cpu(buf[1]); + nel = le32_to_cpu(buf[3]); + + rc = symtab_init(&comdatum->permissions, nel); + if (rc) + goto bad; + comdatum->permissions.nprim = le32_to_cpu(buf[2]); + + 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); + if (rc) + goto bad; + } + + 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, void *fp) +{ + __le32 buf[1]; + int rc; + + if (ebitmap_read(&t->types, fp)) + return -EINVAL; + if (ebitmap_read(&t->negset, fp)) + return -EINVAL; + + rc = next_entry(buf, fp, sizeof(u32)); + if (rc < 0) + return -EINVAL; + t->flags = le32_to_cpu(buf[0]); + + return 0; +} + + +static int read_cons_helper(struct policydb *p, + struct constraint_node **nodep, + int ncons, int allowxtarget, void *fp) +{ + struct constraint_node *c, *lc; + struct constraint_expr *e, *le; + __le32 buf[3]; + u32 nexpr; + int rc, i, j, depth; + + lc = NULL; + for (i = 0; i < ncons; i++) { + c = kzalloc(sizeof(*c), GFP_KERNEL); + if (!c) + return -ENOMEM; + + if (lc) + lc->next = c; + else + *nodep = c; + + rc = next_entry(buf, fp, (sizeof(u32) * 2)); + if (rc) + return rc; + c->permissions = le32_to_cpu(buf[0]); + nexpr = le32_to_cpu(buf[1]); + le = NULL; + depth = -1; + for (j = 0; j < nexpr; j++) { + e = kzalloc(sizeof(*e), GFP_KERNEL); + if (!e) + return -ENOMEM; + + if (le) + le->next = e; + else + c->expr = e; + + rc = next_entry(buf, fp, (sizeof(u32) * 3)); + if (rc) + return rc; + e->expr_type = le32_to_cpu(buf[0]); + e->attr = le32_to_cpu(buf[1]); + e->op = le32_to_cpu(buf[2]); + + switch (e->expr_type) { + case CEXPR_NOT: + if (depth < 0) + return -EINVAL; + break; + case CEXPR_AND: + case CEXPR_OR: + if (depth < 1) + return -EINVAL; + depth--; + break; + case CEXPR_ATTR: + if (depth == (CEXPR_MAXDEPTH - 1)) + return -EINVAL; + depth++; + break; + case CEXPR_NAMES: + if (!allowxtarget && (e->attr & CEXPR_XTARGET)) + return -EINVAL; + if (depth == (CEXPR_MAXDEPTH - 1)) + return -EINVAL; + depth++; + rc = ebitmap_read(&e->names, fp); + if (rc) + return rc; + if (p->policyvers >= + POLICYDB_VERSION_CONSTRAINT_NAMES) { + e->type_names = kzalloc(sizeof + (*e->type_names), GFP_KERNEL); + if (!e->type_names) + return -ENOMEM; + type_set_init(e->type_names); + rc = type_set_read(e->type_names, fp); + if (rc) + return rc; + } + break; + default: + return -EINVAL; + } + le = e; + } + if (depth != 0) + return -EINVAL; + lc = c; + } + + return 0; +} + +static int class_read(struct policydb *p, struct symtab *s, void *fp) +{ + char *key = NULL; + struct class_datum *cladatum; + __le32 buf[6]; + u32 len, len2, ncons, nel; + int i, rc; + + cladatum = kzalloc(sizeof(*cladatum), GFP_KERNEL); + if (!cladatum) + return -ENOMEM; + + rc = next_entry(buf, fp, sizeof(u32)*6); + if (rc) + goto bad; + + len = le32_to_cpu(buf[0]); + len2 = le32_to_cpu(buf[1]); + cladatum->value = le32_to_cpu(buf[2]); + nel = le32_to_cpu(buf[4]); + + rc = symtab_init(&cladatum->permissions, nel); + if (rc) + goto bad; + cladatum->permissions.nprim = le32_to_cpu(buf[3]); + + 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; + } + } + for (i = 0; i < nel; i++) { + rc = perm_read(p, &cladatum->permissions, fp); + if (rc) + goto bad; + } + + rc = read_cons_helper(p, &cladatum->constraints, ncons, 0, fp); + if (rc) + goto bad; + + if (p->policyvers >= POLICYDB_VERSION_VALIDATETRANS) { + /* grab the validatetrans rules */ + rc = next_entry(buf, fp, sizeof(u32)); + if (rc) + goto bad; + ncons = le32_to_cpu(buf[0]); + rc = read_cons_helper(p, &cladatum->validatetrans, + ncons, 1, fp); + if (rc) + goto bad; + } + + if (p->policyvers >= POLICYDB_VERSION_NEW_OBJECT_DEFAULTS) { + rc = next_entry(buf, fp, sizeof(u32) * 3); + if (rc) + goto bad; + + cladatum->default_user = le32_to_cpu(buf[0]); + cladatum->default_role = le32_to_cpu(buf[1]); + cladatum->default_range = le32_to_cpu(buf[2]); + } + + if (p->policyvers >= POLICYDB_VERSION_DEFAULT_TYPE) { + rc = next_entry(buf, fp, sizeof(u32) * 1); + if (rc) + goto bad; + cladatum->default_type = le32_to_cpu(buf[0]); + } + + rc = symtab_insert(s, key, cladatum); + if (rc) + goto bad; + + return 0; +bad: + cls_destroy(key, cladatum, NULL); + return rc; +} + +static int role_read(struct policydb *p, struct symtab *s, void *fp) +{ + char *key = NULL; + struct role_datum *role; + int rc, to_read = 2; + __le32 buf[3]; + u32 len; + + role = kzalloc(sizeof(*role), GFP_KERNEL); + if (!role) + return -ENOMEM; + + if (p->policyvers >= POLICYDB_VERSION_BOUNDARY) + to_read = 3; + + rc = next_entry(buf, fp, sizeof(buf[0]) * to_read); + if (rc) + goto bad; + + len = le32_to_cpu(buf[0]); + role->value = le32_to_cpu(buf[1]); + if (p->policyvers >= POLICYDB_VERSION_BOUNDARY) + role->bounds = le32_to_cpu(buf[2]); + + rc = str_read(&key, GFP_KERNEL, fp, len); + if (rc) + goto bad; + + rc = ebitmap_read(&role->dominates, fp); + if (rc) + goto bad; + + rc = ebitmap_read(&role->types, fp); + if (rc) + goto bad; + + if (strcmp(key, OBJECT_R) == 0) { + rc = -EINVAL; + if (role->value != OBJECT_R_VAL) { + pr_err("SELinux: Role %s has wrong value %d\n", + OBJECT_R, role->value); + goto bad; + } + rc = 0; + goto bad; + } + + rc = 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, void *fp) +{ + char *key = NULL; + struct type_datum *typdatum; + int rc, to_read = 3; + __le32 buf[4]; + u32 len; + + typdatum = kzalloc(sizeof(*typdatum), GFP_KERNEL); + if (!typdatum) + return -ENOMEM; + + if (p->policyvers >= POLICYDB_VERSION_BOUNDARY) + to_read = 4; + + rc = next_entry(buf, fp, sizeof(buf[0]) * to_read); + if (rc) + goto bad; + + len = le32_to_cpu(buf[0]); + typdatum->value = le32_to_cpu(buf[1]); + if (p->policyvers >= POLICYDB_VERSION_BOUNDARY) { + u32 prop = le32_to_cpu(buf[2]); + + if (prop & TYPEDATUM_PROPERTY_PRIMARY) + typdatum->primary = 1; + if (prop & TYPEDATUM_PROPERTY_ATTRIBUTE) + typdatum->attribute = 1; + + typdatum->bounds = le32_to_cpu(buf[3]); + } else { + typdatum->primary = le32_to_cpu(buf[2]); + } + + rc = str_read(&key, GFP_KERNEL, fp, len); + if (rc) + goto bad; + + rc = 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, void *fp) +{ + __le32 buf[1]; + int rc; + + memset(lp, 0, sizeof(*lp)); + + rc = next_entry(buf, fp, sizeof buf); + if (rc) { + pr_err("SELinux: mls: truncated level\n"); + return rc; + } + lp->sens = le32_to_cpu(buf[0]); + + rc = ebitmap_read(&lp->cat, fp); + if (rc) { + pr_err("SELinux: mls: error reading level categories\n"); + return rc; + } + return 0; +} + +static int user_read(struct policydb *p, struct symtab *s, void *fp) +{ + char *key = NULL; + struct user_datum *usrdatum; + int rc, to_read = 2; + __le32 buf[3]; + u32 len; + + usrdatum = kzalloc(sizeof(*usrdatum), GFP_KERNEL); + if (!usrdatum) + return -ENOMEM; + + if (p->policyvers >= POLICYDB_VERSION_BOUNDARY) + to_read = 3; + + rc = next_entry(buf, fp, sizeof(buf[0]) * to_read); + if (rc) + goto bad; + + len = le32_to_cpu(buf[0]); + usrdatum->value = le32_to_cpu(buf[1]); + if (p->policyvers >= POLICYDB_VERSION_BOUNDARY) + usrdatum->bounds = le32_to_cpu(buf[2]); + + rc = str_read(&key, GFP_KERNEL, fp, len); + if (rc) + goto bad; + + rc = ebitmap_read(&usrdatum->roles, fp); + if (rc) + goto bad; + + if (p->policyvers >= POLICYDB_VERSION_MLS) { + rc = mls_read_range_helper(&usrdatum->range, fp); + if (rc) + goto bad; + rc = mls_read_level(&usrdatum->dfltlevel, fp); + if (rc) + goto bad; + } + + rc = 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, void *fp) +{ + char *key = NULL; + struct level_datum *levdatum; + int rc; + __le32 buf[2]; + u32 len; + + levdatum = kzalloc(sizeof(*levdatum), GFP_ATOMIC); + if (!levdatum) + return -ENOMEM; + + rc = next_entry(buf, fp, sizeof buf); + if (rc) + goto bad; + + len = le32_to_cpu(buf[0]); + levdatum->isalias = le32_to_cpu(buf[1]); + + rc = str_read(&key, GFP_ATOMIC, fp, len); + if (rc) + goto bad; + + rc = -ENOMEM; + levdatum->level = kmalloc(sizeof(*levdatum->level), GFP_ATOMIC); + if (!levdatum->level) + goto bad; + + rc = mls_read_level(levdatum->level, fp); + if (rc) + goto bad; + + rc = symtab_insert(s, key, levdatum); + if (rc) + goto bad; + return 0; +bad: + sens_destroy(key, levdatum, NULL); + return rc; +} + +static int cat_read(struct policydb *p, struct symtab *s, void *fp) +{ + char *key = NULL; + struct cat_datum *catdatum; + int rc; + __le32 buf[3]; + u32 len; + + catdatum = kzalloc(sizeof(*catdatum), GFP_ATOMIC); + if (!catdatum) + return -ENOMEM; + + rc = next_entry(buf, fp, sizeof buf); + if (rc) + goto bad; + + len = le32_to_cpu(buf[0]); + catdatum->value = le32_to_cpu(buf[1]); + catdatum->isalias = le32_to_cpu(buf[2]); + + rc = str_read(&key, GFP_ATOMIC, fp, len); + if (rc) + goto bad; + + rc = symtab_insert(s, key, catdatum); + if (rc) + goto bad; + return 0; +bad: + cat_destroy(key, catdatum, NULL); + return rc; +} + +static int (*read_f[SYM_NUM]) (struct policydb *p, struct symtab *s, void *fp) = +{ + common_read, + class_read, + role_read, + type_read, + user_read, + cond_read_bool, + sens_read, + cat_read, +}; + +static int user_bounds_sanity_check(void *key, void *datum, void *datap) +{ + struct user_datum *upper, *user; + struct policydb *p = datap; + int depth = 0; + + upper = user = datum; + while (upper->bounds) { + struct ebitmap_node *node; + unsigned long bit; + + if (++depth == POLICYDB_BOUNDS_MAXDEPTH) { + pr_err("SELinux: user %s: " + "too deep or looped boundary", + (char *) key); + return -EINVAL; + } + + upper = p->user_val_to_struct[upper->bounds - 1]; + ebitmap_for_each_positive_bit(&user->roles, node, bit) { + if (ebitmap_get_bit(&upper->roles, bit)) + continue; + + pr_err("SELinux: boundary violated policy: " + "user=%s role=%s bounds=%s\n", + sym_name(p, SYM_USERS, user->value - 1), + sym_name(p, SYM_ROLES, bit), + sym_name(p, SYM_USERS, upper->value - 1)); + + return -EINVAL; + } + } + + return 0; +} + +static int role_bounds_sanity_check(void *key, void *datum, void *datap) +{ + struct role_datum *upper, *role; + struct policydb *p = datap; + int depth = 0; + + upper = role = datum; + while (upper->bounds) { + struct ebitmap_node *node; + unsigned long bit; + + if (++depth == POLICYDB_BOUNDS_MAXDEPTH) { + pr_err("SELinux: role %s: " + "too deep or looped bounds\n", + (char *) key); + return -EINVAL; + } + + upper = p->role_val_to_struct[upper->bounds - 1]; + ebitmap_for_each_positive_bit(&role->types, node, bit) { + if (ebitmap_get_bit(&upper->types, bit)) + continue; + + pr_err("SELinux: boundary violated policy: " + "role=%s type=%s bounds=%s\n", + sym_name(p, SYM_ROLES, role->value - 1), + sym_name(p, SYM_TYPES, bit), + sym_name(p, SYM_ROLES, upper->value - 1)); + + return -EINVAL; + } + } + + return 0; +} + +static int type_bounds_sanity_check(void *key, void *datum, void *datap) +{ + struct type_datum *upper; + struct policydb *p = datap; + int depth = 0; + + upper = datum; + while (upper->bounds) { + if (++depth == POLICYDB_BOUNDS_MAXDEPTH) { + pr_err("SELinux: type %s: " + "too deep or looped boundary\n", + (char *) key); + return -EINVAL; + } + + upper = p->type_val_to_struct[upper->bounds - 1]; + BUG_ON(!upper); + + if (upper->attribute) { + pr_err("SELinux: type %s: " + "bounded by attribute %s", + (char *) key, + sym_name(p, SYM_TYPES, upper->value - 1)); + return -EINVAL; + } + } + + return 0; +} + +static int policydb_bounds_sanity_check(struct policydb *p) +{ + int rc; + + if (p->policyvers < POLICYDB_VERSION_BOUNDARY) + return 0; + + rc = hashtab_map(&p->p_users.table, user_bounds_sanity_check, p); + if (rc) + return rc; + + rc = hashtab_map(&p->p_roles.table, role_bounds_sanity_check, p); + if (rc) + return rc; + + rc = hashtab_map(&p->p_types.table, type_bounds_sanity_check, p); + if (rc) + return rc; + + return 0; +} + +u16 string_to_security_class(struct policydb *p, const char *name) +{ + struct class_datum *cladatum; + + cladatum = 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, void *fp) +{ + struct range_trans *rt = NULL; + struct mls_range *r = NULL; + int i, rc; + __le32 buf[2]; + u32 nel; + + if (p->policyvers < POLICYDB_VERSION_MLS) + return 0; + + rc = next_entry(buf, fp, sizeof(u32)); + if (rc) + return rc; + + nel = le32_to_cpu(buf[0]); + + rc = hashtab_init(&p->range_tr, nel); + if (rc) + return rc; + + for (i = 0; i < nel; i++) { + rc = -ENOMEM; + rt = kzalloc(sizeof(*rt), GFP_KERNEL); + if (!rt) + goto out; + + rc = next_entry(buf, fp, (sizeof(u32) * 2)); + if (rc) + goto out; + + rt->source_type = le32_to_cpu(buf[0]); + rt->target_type = le32_to_cpu(buf[1]); + if (p->policyvers >= POLICYDB_VERSION_RANGETRANS) { + rc = next_entry(buf, fp, sizeof(u32)); + if (rc) + goto out; + rt->target_class = le32_to_cpu(buf[0]); + } else + rt->target_class = p->process_class; + + rc = -EINVAL; + if (!policydb_type_isvalid(p, rt->source_type) || + !policydb_type_isvalid(p, rt->target_type) || + !policydb_class_isvalid(p, rt->target_class)) + goto out; + + rc = -ENOMEM; + r = kzalloc(sizeof(*r), GFP_KERNEL); + if (!r) + goto out; + + rc = mls_read_range_helper(r, fp); + if (rc) + goto out; + + rc = -EINVAL; + if (!mls_range_isvalid(p, r)) { + pr_warn("SELinux: rangetrans: invalid range\n"); + goto out; + } + + rc = hashtab_insert(&p->range_tr, rt, r, rangetr_key_params); + if (rc) + goto out; + + rt = NULL; + r = NULL; + } + hash_eval(&p->range_tr, "rangetr"); + rc = 0; +out: + kfree(rt); + kfree(r); + return rc; +} + +static int filename_trans_read_helper_compat(struct policydb *p, void *fp) +{ + struct filename_trans_key key, *ft = NULL; + struct filename_trans_datum *last, *datum = NULL; + char *name = NULL; + u32 len, stype, otype; + __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; + + stype = le32_to_cpu(buf[0]); + key.ttype = le32_to_cpu(buf[1]); + key.tclass = le32_to_cpu(buf[2]); + key.name = name; + + otype = le32_to_cpu(buf[3]); + + 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; + goto out; + } + if (likely(datum->otype == otype)) + break; + last = datum; + datum = datum->next; + } + if (!datum) { + rc = -ENOMEM; + datum = kmalloc(sizeof(*datum), GFP_KERNEL); + 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); + return rc; +} + +static int filename_trans_read_helper(struct policydb *p, void *fp) +{ + struct filename_trans_key *ft = NULL; + struct filename_trans_datum **dst, *datum, *first = NULL; + char *name = NULL; + u32 len, ttype, tclass, ndatum, i; + __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; + + ttype = le32_to_cpu(buf[0]); + tclass = le32_to_cpu(buf[1]); + + 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(sizeof(*datum), GFP_KERNEL); + 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]); + + dst = &datum->next; + } + + rc = -ENOMEM; + ft = kmalloc(sizeof(*ft), GFP_KERNEL); + 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); + } + return rc; +} + +static int filename_trans_read(struct policydb *p, void *fp) +{ + u32 nel; + __le32 buf[1]; + int rc, i; + + if (p->policyvers < POLICYDB_VERSION_FILENAME_TRANS) + return 0; + + rc = next_entry(buf, fp, sizeof(u32)); + if (rc) + return rc; + nel = le32_to_cpu(buf[0]); + + 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"); + return 0; +} + +static int genfs_read(struct policydb *p, void *fp) +{ + int i, j, rc; + u32 nel, nel2, len, len2; + __le32 buf[1]; + struct ocontext *l, *c; + struct ocontext *newc = NULL; + struct genfs *genfs_p, *genfs; + struct genfs *newgenfs = NULL; + + rc = next_entry(buf, fp, sizeof(u32)); + if (rc) + return rc; + nel = le32_to_cpu(buf[0]); + + for (i = 0; i < nel; i++) { + rc = next_entry(buf, fp, sizeof(u32)); + if (rc) + goto out; + len = le32_to_cpu(buf[0]); + + rc = -ENOMEM; + newgenfs = kzalloc(sizeof(*newgenfs), GFP_KERNEL); + if (!newgenfs) + goto out; + + rc = str_read(&newgenfs->fstype, GFP_KERNEL, fp, len); + if (rc) + goto out; + + for (genfs_p = NULL, genfs = p->genfs; genfs; + genfs_p = genfs, genfs = genfs->next) { + rc = -EINVAL; + if (strcmp(newgenfs->fstype, genfs->fstype) == 0) { + pr_err("SELinux: dup genfs fstype %s\n", + newgenfs->fstype); + goto out; + } + if (strcmp(newgenfs->fstype, genfs->fstype) < 0) + break; + } + newgenfs->next = genfs; + if (genfs_p) + genfs_p->next = newgenfs; + else + p->genfs = newgenfs; + genfs = newgenfs; + newgenfs = NULL; + + rc = next_entry(buf, fp, sizeof(u32)); + if (rc) + goto out; + + nel2 = le32_to_cpu(buf[0]); + for (j = 0; j < nel2; j++) { + rc = next_entry(buf, fp, sizeof(u32)); + if (rc) + goto out; + len = le32_to_cpu(buf[0]); + + rc = -ENOMEM; + newc = kzalloc(sizeof(*newc), GFP_KERNEL); + if (!newc) + goto out; + + rc = str_read(&newc->u.name, GFP_KERNEL, fp, len); + if (rc) + goto out; + + rc = next_entry(buf, fp, sizeof(u32)); + if (rc) + goto out; + + newc->v.sclass = le32_to_cpu(buf[0]); + rc = context_read_and_validate(&newc->context[0], p, fp); + if (rc) + goto out; + + for (l = NULL, c = genfs->head; c; + l = c, c = c->next) { + rc = -EINVAL; + if (!strcmp(newc->u.name, c->u.name) && + (!c->v.sclass || !newc->v.sclass || + newc->v.sclass == c->v.sclass)) { + pr_err("SELinux: dup genfs entry (%s,%s)\n", + genfs->fstype, c->u.name); + goto out; + } + len = strlen(newc->u.name); + len2 = strlen(c->u.name); + if (len > len2) + break; + } + + newc->next = c; + if (l) + l->next = newc; + else + genfs->head = newc; + newc = NULL; + } + } + rc = 0; +out: + if (newgenfs) { + kfree(newgenfs->fstype); + kfree(newgenfs); + } + ocontext_destroy(newc, OCON_FSUSE); + + return rc; +} + +static int ocontext_read(struct policydb *p, struct policydb_compat_info *info, + void *fp) +{ + int i, j, rc; + u32 nel, len; + __be64 prefixbuf[1]; + __le32 buf[3]; + struct ocontext *l, *c; + u32 nodebuf[8]; + + for (i = 0; i < info->ocon_num; i++) { + rc = next_entry(buf, fp, sizeof(u32)); + if (rc) + goto out; + nel = le32_to_cpu(buf[0]); + + l = NULL; + for (j = 0; j < nel; j++) { + rc = -ENOMEM; + c = kzalloc(sizeof(*c), GFP_KERNEL); + if (!c) + goto out; + if (l) + l->next = c; + else + p->ocontexts[i] = c; + l = c; + + switch (i) { + case OCON_ISID: + rc = next_entry(buf, fp, sizeof(u32)); + if (rc) + goto out; + + c->sid[0] = le32_to_cpu(buf[0]); + rc = context_read_and_validate(&c->context[0], p, fp); + if (rc) + goto out; + break; + case OCON_FS: + case OCON_NETIF: + rc = next_entry(buf, fp, sizeof(u32)); + if (rc) + goto out; + len = le32_to_cpu(buf[0]); + + rc = str_read(&c->u.name, GFP_KERNEL, fp, len); + if (rc) + goto out; + + rc = context_read_and_validate(&c->context[0], p, fp); + if (rc) + goto out; + rc = context_read_and_validate(&c->context[1], p, fp); + if (rc) + goto out; + break; + case OCON_PORT: + rc = next_entry(buf, fp, sizeof(u32)*3); + if (rc) + goto out; + c->u.port.protocol = le32_to_cpu(buf[0]); + c->u.port.low_port = le32_to_cpu(buf[1]); + c->u.port.high_port = le32_to_cpu(buf[2]); + rc = context_read_and_validate(&c->context[0], p, fp); + if (rc) + goto out; + break; + case OCON_NODE: + rc = next_entry(nodebuf, fp, sizeof(u32) * 2); + if (rc) + goto out; + c->u.node.addr = nodebuf[0]; /* network order */ + c->u.node.mask = nodebuf[1]; /* network order */ + rc = context_read_and_validate(&c->context[0], p, fp); + if (rc) + goto out; + break; + case OCON_FSUSE: + rc = next_entry(buf, fp, sizeof(u32)*2); + if (rc) + goto out; + + rc = -EINVAL; + c->v.behavior = le32_to_cpu(buf[0]); + /* Determined at runtime, not in policy DB. */ + if (c->v.behavior == SECURITY_FS_USE_MNTPOINT) + goto out; + if (c->v.behavior > SECURITY_FS_USE_MAX) + goto out; + + len = le32_to_cpu(buf[1]); + rc = str_read(&c->u.name, GFP_KERNEL, fp, len); + if (rc) + goto out; + + rc = context_read_and_validate(&c->context[0], p, fp); + if (rc) + goto out; + break; + case OCON_NODE6: { + int k; + + rc = next_entry(nodebuf, fp, sizeof(u32) * 8); + if (rc) + goto out; + for (k = 0; k < 4; k++) + c->u.node6.addr[k] = nodebuf[k]; + for (k = 0; k < 4; k++) + c->u.node6.mask[k] = nodebuf[k+4]; + rc = context_read_and_validate(&c->context[0], p, fp); + if (rc) + goto out; + break; + } + case OCON_IBPKEY: { + u32 pkey_lo, pkey_hi; + + rc = next_entry(prefixbuf, fp, sizeof(u64)); + if (rc) + goto out; + + /* we need to have subnet_prefix in CPU order */ + c->u.ibpkey.subnet_prefix = be64_to_cpu(prefixbuf[0]); + + rc = next_entry(buf, fp, sizeof(u32) * 2); + if (rc) + goto out; + + pkey_lo = le32_to_cpu(buf[0]); + pkey_hi = le32_to_cpu(buf[1]); + + if (pkey_lo > U16_MAX || pkey_hi > U16_MAX) { + rc = -EINVAL; + goto out; + } + + c->u.ibpkey.low_pkey = pkey_lo; + c->u.ibpkey.high_pkey = pkey_hi; + + rc = context_read_and_validate(&c->context[0], + p, + fp); + if (rc) + goto out; + break; + } + case OCON_IBENDPORT: { + u32 port; + + rc = next_entry(buf, fp, sizeof(u32) * 2); + if (rc) + goto out; + len = le32_to_cpu(buf[0]); + + rc = str_read(&c->u.ibendport.dev_name, GFP_KERNEL, fp, len); + if (rc) + goto out; + + port = le32_to_cpu(buf[1]); + if (port > U8_MAX || port == 0) { + rc = -EINVAL; + goto out; + } + + c->u.ibendport.port = port; + + rc = context_read_and_validate(&c->context[0], + p, + fp); + if (rc) + goto out; + break; + } /* end case */ + } /* end switch */ + } + } + rc = 0; +out: + return rc; +} + +/* + * Read the configuration data from a policy database binary + * representation file into a policy database structure. + */ +int policydb_read(struct policydb *p, void *fp) +{ + struct role_allow *ra, *lra; + struct role_trans_key *rtk = NULL; + struct role_trans_datum *rtd = NULL; + int i, j, rc; + __le32 buf[4]; + u32 len, nprim, nel, perm; + + char *policydb_str; + 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 = -ENOMEM; + policydb_str = kmalloc(len + 1, GFP_KERNEL); + if (!policydb_str) { + pr_err("SELinux: unable to allocate memory for policydb " + "string of length %d\n", len); + goto bad; + } + + rc = next_entry(policydb_str, fp, len); + if (rc) { + pr_err("SELinux: truncated policydb string identifier\n"); + kfree(policydb_str); + goto bad; + } + + rc = -EINVAL; + policydb_str[len] = '\0'; + if (strcmp(policydb_str, POLICYDB_STRING)) { + pr_err("SELinux: policydb string %s does not match " + "my string %s\n", policydb_str, POLICYDB_STRING); + kfree(policydb_str); + goto bad; + } + /* Done with policydb_str. */ + kfree(policydb_str); + policydb_str = NULL; + + /* Read the version and table sizes. */ + rc = next_entry(buf, fp, sizeof(u32)*4); + if (rc) + goto bad; + + rc = -EINVAL; + p->policyvers = le32_to_cpu(buf[0]); + if (p->policyvers < POLICYDB_VERSION_MIN || + p->policyvers > POLICYDB_VERSION_MAX) { + pr_err("SELinux: policydb version %d does not match " + "my version range %d-%d\n", + le32_to_cpu(buf[0]), POLICYDB_VERSION_MIN, POLICYDB_VERSION_MAX); + goto bad; + } + + if ((le32_to_cpu(buf[1]) & POLICYDB_CONFIG_MLS)) { + p->mls_enabled = 1; + + rc = -EINVAL; + if (p->policyvers < POLICYDB_VERSION_MLS) { + pr_err("SELinux: security policydb version %d " + "(MLS) not backwards compatible\n", + p->policyvers); + goto bad; + } + } + p->reject_unknown = !!(le32_to_cpu(buf[1]) & REJECT_UNKNOWN); + p->allow_unknown = !!(le32_to_cpu(buf[1]) & ALLOW_UNKNOWN); + + if (p->policyvers >= POLICYDB_VERSION_POLCAP) { + rc = ebitmap_read(&p->policycaps, fp); + if (rc) + goto bad; + } + + if (p->policyvers >= POLICYDB_VERSION_PERMISSIVE) { + rc = ebitmap_read(&p->permissive_map, fp); + if (rc) + goto bad; + } + + rc = -EINVAL; + info = policydb_lookup_compat(p->policyvers); + if (!info) { + pr_err("SELinux: unable to find policy compat info " + "for version %d\n", p->policyvers); + goto bad; + } + + rc = -EINVAL; + if (le32_to_cpu(buf[2]) != info->sym_num || + le32_to_cpu(buf[3]) != info->ocon_num) { + pr_err("SELinux: policydb table sizes (%d,%d) do " + "not match mine (%d,%d)\n", le32_to_cpu(buf[2]), + le32_to_cpu(buf[3]), + info->sym_num, info->ocon_num); + goto bad; + } + + for (i = 0; i < info->sym_num; i++) { + rc = next_entry(buf, fp, sizeof(u32)*2); + if (rc) + goto bad; + nprim = le32_to_cpu(buf[0]); + nel = le32_to_cpu(buf[1]); + + 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 = -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; + + 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]); + + rc = hashtab_init(&p->role_tr, nel); + if (rc) + goto bad; + for (i = 0; i < nel; i++) { + rc = -ENOMEM; + rtk = kmalloc(sizeof(*rtk), GFP_KERNEL); + if (!rtk) + goto bad; + + rc = -ENOMEM; + rtd = kmalloc(sizeof(*rtd), GFP_KERNEL); + if (!rtd) + goto bad; + + rc = next_entry(buf, fp, sizeof(u32)*3); + if (rc) + goto bad; + + rc = -EINVAL; + 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; + rtk->tclass = le32_to_cpu(buf[0]); + } 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; + } + + rc = next_entry(buf, fp, sizeof(u32)); + if (rc) + goto bad; + nel = le32_to_cpu(buf[0]); + lra = NULL; + for (i = 0; i < nel; i++) { + rc = -ENOMEM; + ra = kzalloc(sizeof(*ra), GFP_KERNEL); + if (!ra) + goto bad; + if (lra) + lra->next = ra; + else + p->role_allow = ra; + rc = next_entry(buf, fp, sizeof(u32)*2); + if (rc) + goto bad; + + rc = -EINVAL; + ra->role = le32_to_cpu(buf[0]); + ra->new_role = le32_to_cpu(buf[1]); + if (!policydb_role_isvalid(p, ra->role) || + !policydb_role_isvalid(p, ra->new_role)) + goto bad; + lra = ra; + } + + rc = filename_trans_read(p, fp); + if (rc) + goto bad; + + rc = policydb_index(p); + if (rc) + goto bad; + + rc = -EINVAL; + 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 = kvcalloc(p->p_types.nprim, + sizeof(*p->type_attr_map_array), + GFP_KERNEL); + 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; + } + /* 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, void *fp) +{ + __le32 buf[1]; + int rc; + + buf[0] = cpu_to_le32(l->sens); + rc = put_entry(buf, sizeof(u32), 1, fp); + if (rc) + return rc; + + rc = ebitmap_write(&l->cat, fp); + if (rc) + return rc; + + return 0; +} + +/* + * Write a MLS range structure to a policydb binary + * representation file. + */ +static int mls_write_range_helper(struct mls_range *r, void *fp) +{ + __le32 buf[3]; + size_t items; + int rc, eq; + + eq = mls_level_eq(&r->level[1], &r->level[0]); + + if (eq) + items = 2; + else + items = 3; + buf[0] = cpu_to_le32(items-1); + buf[1] = cpu_to_le32(r->level[0].sens); + if (!eq) + buf[2] = cpu_to_le32(r->level[1].sens); + + BUG_ON(items > ARRAY_SIZE(buf)); + + rc = put_entry(buf, sizeof(u32), items, fp); + if (rc) + return rc; + + rc = ebitmap_write(&r->level[0].cat, fp); + if (rc) + return rc; + if (!eq) { + rc = ebitmap_write(&r->level[1].cat, fp); + if (rc) + return rc; + } + + return 0; +} + +static int sens_write(void *vkey, void *datum, void *ptr) +{ + char *key = vkey; + struct level_datum *levdatum = datum; + struct policy_data *pd = ptr; + void *fp = pd->fp; + __le32 buf[2]; + size_t len; + int rc; + + len = strlen(key); + buf[0] = cpu_to_le32(len); + buf[1] = cpu_to_le32(levdatum->isalias); + rc = put_entry(buf, sizeof(u32), 2, fp); + if (rc) + return rc; + + rc = put_entry(key, 1, len, fp); + if (rc) + return rc; + + rc = mls_write_level(levdatum->level, fp); + if (rc) + return rc; + + return 0; +} + +static int cat_write(void *vkey, void *datum, void *ptr) +{ + char *key = vkey; + struct cat_datum *catdatum = datum; + struct policy_data *pd = ptr; + void *fp = pd->fp; + __le32 buf[3]; + size_t len; + int rc; + + len = strlen(key); + buf[0] = cpu_to_le32(len); + buf[1] = cpu_to_le32(catdatum->value); + buf[2] = cpu_to_le32(catdatum->isalias); + rc = put_entry(buf, sizeof(u32), 3, fp); + if (rc) + return rc; + + rc = put_entry(key, 1, len, fp); + if (rc) + return rc; + + return 0; +} + +static int role_trans_write_one(void *key, void *datum, void *ptr) +{ + struct role_trans_key *rtk = key; + struct role_trans_datum *rtd = datum; + struct policy_data *pd = ptr; + void *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, void *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, void *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, + void *fp) +{ + int rc; + __le32 buf[3]; + + buf[0] = cpu_to_le32(c->user); + buf[1] = cpu_to_le32(c->role); + buf[2] = cpu_to_le32(c->type); + + rc = put_entry(buf, sizeof(u32), 3, fp); + if (rc) + return rc; + + rc = mls_write_range_helper(&c->range, fp); + if (rc) + return rc; + + return 0; +} + +/* + * The following *_write functions are used to + * write the symbol data to a policy database + * binary representation file. + */ + +static int perm_write(void *vkey, void *datum, void *fp) +{ + char *key = vkey; + struct perm_datum *perdatum = datum; + __le32 buf[2]; + size_t len; + int rc; + + len = strlen(key); + buf[0] = cpu_to_le32(len); + buf[1] = cpu_to_le32(perdatum->value); + rc = put_entry(buf, sizeof(u32), 2, fp); + if (rc) + return rc; + + rc = put_entry(key, 1, len, fp); + if (rc) + return rc; + + return 0; +} + +static int common_write(void *vkey, void *datum, void *ptr) +{ + char *key = vkey; + struct common_datum *comdatum = datum; + struct policy_data *pd = ptr; + void *fp = pd->fp; + __le32 buf[4]; + size_t len; + int rc; + + len = strlen(key); + buf[0] = cpu_to_le32(len); + buf[1] = cpu_to_le32(comdatum->value); + buf[2] = cpu_to_le32(comdatum->permissions.nprim); + buf[3] = cpu_to_le32(comdatum->permissions.table.nel); + rc = put_entry(buf, sizeof(u32), 4, fp); + if (rc) + return rc; + + rc = put_entry(key, 1, len, fp); + if (rc) + return rc; + + rc = hashtab_map(&comdatum->permissions.table, perm_write, fp); + if (rc) + return rc; + + return 0; +} + +static int type_set_write(struct type_set *t, void *fp) +{ + int rc; + __le32 buf[1]; + + if (ebitmap_write(&t->types, fp)) + return -EINVAL; + if (ebitmap_write(&t->negset, fp)) + return -EINVAL; + + buf[0] = cpu_to_le32(t->flags); + rc = put_entry(buf, sizeof(u32), 1, fp); + if (rc) + return -EINVAL; + + return 0; +} + +static int write_cons_helper(struct policydb *p, struct constraint_node *node, + void *fp) +{ + struct constraint_node *c; + struct constraint_expr *e; + __le32 buf[3]; + u32 nel; + int rc; + + for (c = node; c; c = c->next) { + nel = 0; + for (e = c->expr; e; e = e->next) + nel++; + buf[0] = cpu_to_le32(c->permissions); + buf[1] = cpu_to_le32(nel); + rc = put_entry(buf, sizeof(u32), 2, fp); + if (rc) + return rc; + for (e = c->expr; e; e = e->next) { + buf[0] = cpu_to_le32(e->expr_type); + buf[1] = cpu_to_le32(e->attr); + buf[2] = cpu_to_le32(e->op); + rc = put_entry(buf, sizeof(u32), 3, fp); + if (rc) + return rc; + + switch (e->expr_type) { + case CEXPR_NAMES: + rc = ebitmap_write(&e->names, fp); + if (rc) + return rc; + if (p->policyvers >= + POLICYDB_VERSION_CONSTRAINT_NAMES) { + rc = type_set_write(e->type_names, fp); + if (rc) + return rc; + } + break; + default: + break; + } + } + } + + return 0; +} + +static int class_write(void *vkey, void *datum, void *ptr) +{ + char *key = vkey; + struct class_datum *cladatum = datum; + struct policy_data *pd = ptr; + void *fp = pd->fp; + struct policydb *p = pd->p; + struct constraint_node *c; + __le32 buf[6]; + u32 ncons; + size_t len, len2; + int rc; + + len = strlen(key); + if (cladatum->comkey) + len2 = strlen(cladatum->comkey); + else + len2 = 0; + + ncons = 0; + for (c = cladatum->constraints; c; c = c->next) + ncons++; + + buf[0] = cpu_to_le32(len); + buf[1] = cpu_to_le32(len2); + buf[2] = cpu_to_le32(cladatum->value); + buf[3] = cpu_to_le32(cladatum->permissions.nprim); + 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; + void *fp = pd->fp; + struct policydb *p = pd->p; + __le32 buf[3]; + size_t items, len; + int rc; + + len = strlen(key); + items = 0; + buf[items++] = cpu_to_le32(len); + buf[items++] = cpu_to_le32(role->value); + if (p->policyvers >= POLICYDB_VERSION_BOUNDARY) + buf[items++] = cpu_to_le32(role->bounds); + + BUG_ON(items > ARRAY_SIZE(buf)); + + rc = put_entry(buf, sizeof(u32), items, fp); + if (rc) + return rc; + + rc = put_entry(key, 1, len, fp); + if (rc) + return rc; + + rc = ebitmap_write(&role->dominates, fp); + if (rc) + return rc; + + rc = ebitmap_write(&role->types, fp); + if (rc) + return rc; + + return 0; +} + +static int type_write(void *vkey, void *datum, void *ptr) +{ + char *key = vkey; + struct type_datum *typdatum = datum; + struct policy_data *pd = ptr; + struct policydb *p = pd->p; + void *fp = pd->fp; + __le32 buf[4]; + int rc; + size_t items, len; + + len = strlen(key); + items = 0; + buf[items++] = cpu_to_le32(len); + buf[items++] = cpu_to_le32(typdatum->value); + if (p->policyvers >= POLICYDB_VERSION_BOUNDARY) { + u32 properties = 0; + + if (typdatum->primary) + properties |= TYPEDATUM_PROPERTY_PRIMARY; + + if (typdatum->attribute) + properties |= TYPEDATUM_PROPERTY_ATTRIBUTE; + + buf[items++] = cpu_to_le32(properties); + buf[items++] = cpu_to_le32(typdatum->bounds); + } else { + buf[items++] = cpu_to_le32(typdatum->primary); + } + BUG_ON(items > ARRAY_SIZE(buf)); + rc = put_entry(buf, sizeof(u32), items, fp); + if (rc) + return rc; + + rc = put_entry(key, 1, len, fp); + if (rc) + return rc; + + return 0; +} + +static int user_write(void *vkey, void *datum, void *ptr) +{ + char *key = vkey; + struct user_datum *usrdatum = datum; + struct policy_data *pd = ptr; + struct policydb *p = pd->p; + void *fp = pd->fp; + __le32 buf[3]; + size_t items, len; + int rc; + + len = strlen(key); + items = 0; + buf[items++] = cpu_to_le32(len); + buf[items++] = cpu_to_le32(usrdatum->value); + if (p->policyvers >= POLICYDB_VERSION_BOUNDARY) + buf[items++] = cpu_to_le32(usrdatum->bounds); + BUG_ON(items > ARRAY_SIZE(buf)); + rc = put_entry(buf, sizeof(u32), items, fp); + if (rc) + return rc; + + rc = put_entry(key, 1, len, fp); + if (rc) + return rc; + + rc = ebitmap_write(&usrdatum->roles, fp); + if (rc) + return rc; + + rc = mls_write_range_helper(&usrdatum->range, fp); + if (rc) + return rc; + + rc = mls_write_level(&usrdatum->dfltlevel, fp); + if (rc) + return rc; + + return 0; +} + +static int (*write_f[SYM_NUM]) (void *key, void *datum, + void *datap) = +{ + common_write, + class_write, + role_write, + type_write, + user_write, + cond_write_bool, + sens_write, + cat_write, +}; + +static int ocontext_write(struct policydb *p, struct policydb_compat_info *info, + void *fp) +{ + unsigned int i, j, rc; + size_t nel, len; + __be64 prefixbuf[1]; + __le32 buf[3]; + u32 nodebuf[8]; + struct ocontext *c; + for (i = 0; i < info->ocon_num; i++) { + nel = 0; + for (c = p->ocontexts[i]; c; c = c->next) + nel++; + buf[0] = cpu_to_le32(nel); + rc = put_entry(buf, sizeof(u32), 1, fp); + if (rc) + return rc; + for (c = p->ocontexts[i]; c; c = c->next) { + switch (i) { + case OCON_ISID: + buf[0] = cpu_to_le32(c->sid[0]); + rc = put_entry(buf, sizeof(u32), 1, fp); + if (rc) + return rc; + rc = context_write(p, &c->context[0], fp); + if (rc) + return rc; + break; + case OCON_FS: + case OCON_NETIF: + len = strlen(c->u.name); + buf[0] = cpu_to_le32(len); + rc = put_entry(buf, sizeof(u32), 1, fp); + if (rc) + return rc; + rc = put_entry(c->u.name, 1, len, fp); + if (rc) + return rc; + rc = context_write(p, &c->context[0], fp); + if (rc) + return rc; + rc = context_write(p, &c->context[1], fp); + if (rc) + return rc; + break; + case OCON_PORT: + buf[0] = cpu_to_le32(c->u.port.protocol); + buf[1] = cpu_to_le32(c->u.port.low_port); + buf[2] = cpu_to_le32(c->u.port.high_port); + rc = put_entry(buf, sizeof(u32), 3, fp); + if (rc) + return rc; + rc = context_write(p, &c->context[0], fp); + if (rc) + return rc; + break; + case OCON_NODE: + nodebuf[0] = c->u.node.addr; /* network order */ + nodebuf[1] = c->u.node.mask; /* network order */ + rc = put_entry(nodebuf, sizeof(u32), 2, fp); + if (rc) + return rc; + rc = context_write(p, &c->context[0], fp); + if (rc) + return rc; + break; + case OCON_FSUSE: + buf[0] = cpu_to_le32(c->v.behavior); + len = strlen(c->u.name); + buf[1] = cpu_to_le32(len); + rc = put_entry(buf, sizeof(u32), 2, fp); + if (rc) + return rc; + rc = put_entry(c->u.name, 1, len, fp); + if (rc) + return rc; + rc = context_write(p, &c->context[0], fp); + if (rc) + return rc; + break; + case OCON_NODE6: + for (j = 0; j < 4; j++) + nodebuf[j] = c->u.node6.addr[j]; /* network order */ + for (j = 0; j < 4; j++) + nodebuf[j + 4] = c->u.node6.mask[j]; /* network order */ + rc = put_entry(nodebuf, sizeof(u32), 8, fp); + if (rc) + return rc; + rc = context_write(p, &c->context[0], fp); + if (rc) + return rc; + break; + case OCON_IBPKEY: + /* subnet_prefix is in CPU order */ + prefixbuf[0] = cpu_to_be64(c->u.ibpkey.subnet_prefix); + + rc = put_entry(prefixbuf, sizeof(u64), 1, fp); + if (rc) + return rc; + + buf[0] = cpu_to_le32(c->u.ibpkey.low_pkey); + buf[1] = cpu_to_le32(c->u.ibpkey.high_pkey); + + rc = put_entry(buf, sizeof(u32), 2, fp); + if (rc) + return rc; + rc = context_write(p, &c->context[0], fp); + if (rc) + return rc; + break; + case OCON_IBENDPORT: + len = strlen(c->u.ibendport.dev_name); + buf[0] = cpu_to_le32(len); + buf[1] = cpu_to_le32(c->u.ibendport.port); + rc = put_entry(buf, sizeof(u32), 2, fp); + if (rc) + return rc; + rc = put_entry(c->u.ibendport.dev_name, 1, len, fp); + if (rc) + return rc; + rc = context_write(p, &c->context[0], fp); + if (rc) + return rc; + break; + } + } + } + return 0; +} + +static int genfs_write(struct policydb *p, void *fp) +{ + struct genfs *genfs; + struct ocontext *c; + size_t len; + __le32 buf[1]; + int rc; + + len = 0; + for (genfs = p->genfs; genfs; genfs = genfs->next) + len++; + buf[0] = cpu_to_le32(len); + rc = put_entry(buf, sizeof(u32), 1, fp); + if (rc) + return rc; + for (genfs = p->genfs; genfs; genfs = genfs->next) { + len = strlen(genfs->fstype); + buf[0] = cpu_to_le32(len); + rc = put_entry(buf, sizeof(u32), 1, fp); + if (rc) + return rc; + rc = put_entry(genfs->fstype, 1, len, fp); + if (rc) + return rc; + len = 0; + for (c = genfs->head; c; c = c->next) + len++; + buf[0] = cpu_to_le32(len); + rc = put_entry(buf, sizeof(u32), 1, fp); + if (rc) + return rc; + for (c = genfs->head; c; c = c->next) { + len = strlen(c->u.name); + buf[0] = cpu_to_le32(len); + rc = put_entry(buf, sizeof(u32), 1, fp); + if (rc) + return rc; + rc = put_entry(c->u.name, 1, len, fp); + if (rc) + return rc; + buf[0] = cpu_to_le32(c->v.sclass); + rc = put_entry(buf, sizeof(u32), 1, fp); + if (rc) + return rc; + rc = context_write(p, &c->context[0], fp); + if (rc) + return rc; + } + } + return 0; +} + +static int range_write_helper(void *key, void *data, void *ptr) +{ + __le32 buf[2]; + struct range_trans *rt = key; + struct mls_range *r = data; + struct policy_data *pd = ptr; + void *fp = pd->fp; + struct policydb *p = pd->p; + int rc; + + buf[0] = cpu_to_le32(rt->source_type); + buf[1] = cpu_to_le32(rt->target_type); + rc = put_entry(buf, sizeof(u32), 2, fp); + if (rc) + return rc; + if (p->policyvers >= POLICYDB_VERSION_RANGETRANS) { + buf[0] = cpu_to_le32(rt->target_class); + rc = put_entry(buf, sizeof(u32), 1, fp); + if (rc) + return rc; + } + rc = mls_write_range_helper(r, fp); + if (rc) + return rc; + + return 0; +} + +static int range_write(struct policydb *p, void *fp) +{ + __le32 buf[1]; + int rc; + 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; + void *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; + void *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, void *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, void *fp) +{ + unsigned int i, num_syms; + int rc; + __le32 buf[4]; + u32 config; + size_t len; + struct policydb_compat_info *info; + + /* + * refuse to write policy older than compressed avtab + * to simplify the writer. There are other tests dropped + * since we assume this throughout the writer code. Be + * careful if you ever try to remove this restriction + */ + if (p->policyvers < POLICYDB_VERSION_AVTAB) { + pr_err("SELinux: refusing to write policy version %d." + " Because it is less than version %d\n", p->policyvers, + POLICYDB_VERSION_AVTAB); + return -EINVAL; + } + + config = 0; + if (p->mls_enabled) + config |= POLICYDB_CONFIG_MLS; + + if (p->reject_unknown) + config |= REJECT_UNKNOWN; + if (p->allow_unknown) + config |= ALLOW_UNKNOWN; + + /* Write the magic number and string identifiers. */ + buf[0] = cpu_to_le32(POLICYDB_MAGIC); + len = strlen(POLICYDB_STRING); + buf[1] = cpu_to_le32(len); + rc = put_entry(buf, sizeof(u32), 2, fp); + if (rc) + return rc; + rc = put_entry(POLICYDB_STRING, 1, len, fp); + if (rc) + return rc; + + /* Write the version, config, and table sizes. */ + info = policydb_lookup_compat(p->policyvers); + if (!info) { + pr_err("SELinux: compatibility lookup failed for policy " + "version %d", p->policyvers); + return -EINVAL; + } + + buf[0] = cpu_to_le32(p->policyvers); + buf[1] = cpu_to_le32(config); + buf[2] = cpu_to_le32(info->sym_num); + buf[3] = cpu_to_le32(info->ocon_num); + + rc = put_entry(buf, sizeof(u32), 4, fp); + if (rc) + return rc; + + if (p->policyvers >= POLICYDB_VERSION_POLCAP) { + rc = ebitmap_write(&p->policycaps, fp); + if (rc) + return rc; + } + + if (p->policyvers >= POLICYDB_VERSION_PERMISSIVE) { + rc = ebitmap_write(&p->permissive_map, fp); + if (rc) + return rc; + } + + num_syms = info->sym_num; + for (i = 0; i < num_syms; i++) { + struct policy_data pd; + + pd.fp = fp; + pd.p = p; + + buf[0] = cpu_to_le32(p->symtab[i].nprim); + buf[1] = cpu_to_le32(p->symtab[i].table.nel); + + rc = put_entry(buf, sizeof(u32), 2, fp); + if (rc) + return rc; + rc = hashtab_map(&p->symtab[i].table, write_f[i], &pd); + if (rc) + return rc; + } + + rc = avtab_write(p, &p->te_avtab, fp); + if (rc) + return rc; + + rc = cond_write_list(p, 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..ffc4e7bad --- /dev/null +++ b/security/selinux/ss/policydb.h @@ -0,0 +1,391 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * A policy database (policydb) specifies the + * configuration data for the security policy. + * + * Author : Stephen Smalley, <sds@tycho.nsa.gov> + */ + +/* + * Updated: Trusted Computer Solutions, Inc. <dgoeddel@trustedcs.com> + * + * Support for enhanced MLS infrastructure. + * + * Updated: Frank Mayer <mayerf@tresys.com> and Karl MacMillan <kmacmillan@tresys.com> + * + * Added conditional policy language extensions + * + * Copyright (C) 2004-2005 Trusted Computer Solutions, Inc. + * Copyright (C) 2003 - 2004 Tresys Technology, LLC + */ + +#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 { + u32 value; /* class value */ + char *comkey; /* common name */ + struct common_datum *comdatum; /* common datum */ + struct symtab permissions; /* class-specific permission symbol table */ + struct constraint_node *constraints; /* constraints on class permissions */ + struct constraint_node *validatetrans; /* special transition rules */ +/* Options how a new object user, role, and type should be decided */ +#define DEFAULT_SOURCE 1 +#define DEFAULT_TARGET 2 + char default_user; + char default_role; + char default_type; +/* Options how a new object range should be decided */ +#define DEFAULT_SOURCE_LOW 1 +#define DEFAULT_SOURCE_HIGH 2 +#define DEFAULT_SOURCE_LOW_HIGH 3 +#define DEFAULT_TARGET_LOW 4 +#define DEFAULT_TARGET_HIGH 5 +#define DEFAULT_TARGET_LOW_HIGH 6 +#define DEFAULT_GLBLUB 7 + char default_range; +}; + +/* Role attributes */ +struct role_datum { + u32 value; /* internal role value */ + u32 bounds; /* boundary of role */ + struct ebitmap dominates; /* set of roles dominated by this role */ + struct ebitmap types; /* set of authorized types for role */ +}; + +struct role_trans_key { + u32 role; /* current role */ + u32 type; /* program executable type, or new object type */ + u32 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 */ + unsigned char primary; /* primary name? */ + unsigned char attribute;/* attribute ?*/ +}; + +/* User attributes */ +struct user_datum { + u32 value; /* internal user value */ + u32 bounds; /* bounds of user */ + struct ebitmap roles; /* set of authorized roles for user */ + struct mls_range range; /* MLS range (min - max) for user */ + struct mls_level dfltlevel; /* default login MLS level for user */ +}; + + +/* Sensitivity attributes */ +struct level_datum { + struct mls_level *level; /* sensitivity and associated categories */ + unsigned char isalias; /* is this sensitivity an alias for another? */ +}; + +/* Category attributes */ +struct cat_datum { + u32 value; /* internal category bit + 1 */ + unsigned char isalias; /* is this category an alias for another? */ +}; + +struct range_trans { + u32 source_type; + u32 target_type; + u32 target_class; +}; + +/* Boolean data type */ +struct cond_bool_datum { + __u32 value; /* internal type value */ + int state; +}; + +struct cond_node; + +/* + * type set preserves data needed to determine constraint info from + * policy source. This is not used by the kernel policy but allows + * utilities such as audit2allow to determine constraint denials. + */ +struct type_set { + struct ebitmap types; + struct ebitmap negset; + u32 flags; +}; + +/* + * The configuration data includes security contexts for + * initial SIDs, unlabeled file systems, TCP and UDP port numbers, + * network interfaces, and nodes. This structure stores the + * relevant data for one such entry. Entries of the same kind + * (e.g. all initial SIDs) are linked together into a list. + */ +struct ocontext { + union { + char *name; /* name of initial SID, fs, netif, fstype, path */ + struct { + u8 protocol; + u16 low_port; + u16 high_port; + } port; /* TCP or UDP port information */ + struct { + u32 addr; + u32 mask; + } node; /* node information */ + struct { + u32 addr[4]; + u32 mask[4]; + } node6; /* IPv6 node information */ + struct { + u64 subnet_prefix; + u16 low_pkey; + u16 high_pkey; + } ibpkey; + struct { + char *dev_name; + u8 port; + } ibendport; + } u; + union { + u32 sclass; /* security class for genfs */ + u32 behavior; /* labeling behavior for fs_use */ + } v; + struct context context[2]; /* security context(s) */ + u32 sid[2]; /* SID(s) */ + struct ocontext *next; +}; + +struct genfs { + char *fstype; + struct ocontext *head; + struct genfs *next; +}; + +/* symbol table array indices */ +#define SYM_COMMONS 0 +#define SYM_CLASSES 1 +#define SYM_ROLES 2 +#define SYM_TYPES 3 +#define SYM_USERS 4 +#define SYM_BOOLS 5 +#define SYM_LEVELS 6 +#define SYM_CATS 7 +#define SYM_NUM 8 + +/* object context array indices */ +#define OCON_ISID 0 /* initial SIDs */ +#define OCON_FS 1 /* unlabeled file systems */ +#define OCON_PORT 2 /* TCP and UDP port numbers */ +#define OCON_NETIF 3 /* network interfaces */ +#define OCON_NODE 4 /* nodes */ +#define OCON_FSUSE 5 /* fs_use */ +#define OCON_NODE6 6 /* IPv6 nodes */ +#define OCON_IBPKEY 7 /* Infiniband PKeys */ +#define OCON_IBENDPORT 8 /* Infiniband end ports */ +#define OCON_NUM 9 + +/* The policy database */ +struct policydb { + int mls_enabled; + + /* symbol tables */ + struct symtab symtab[SYM_NUM]; +#define p_commons symtab[SYM_COMMONS] +#define p_classes symtab[SYM_CLASSES] +#define p_roles symtab[SYM_ROLES] +#define p_types symtab[SYM_TYPES] +#define p_users symtab[SYM_USERS] +#define p_bools symtab[SYM_BOOLS] +#define p_levels symtab[SYM_LEVELS] +#define p_cats symtab[SYM_CATS] + + /* symbol names indexed by (value - 1) */ + 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; + + /* 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; + +extern void policydb_destroy(struct policydb *p); +extern int policydb_load_isids(struct policydb *p, struct sidtab *s); +extern int policydb_context_isvalid(struct policydb *p, struct context *c); +extern int policydb_class_isvalid(struct policydb *p, unsigned int class); +extern int policydb_type_isvalid(struct policydb *p, unsigned int type); +extern int policydb_role_isvalid(struct policydb *p, unsigned int role); +extern int policydb_read(struct policydb *p, void *fp); +extern int policydb_write(struct policydb *p, void *fp); + +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_file { + char *data; + size_t len; +}; + +struct policy_data { + struct policydb *p; + void *fp; +}; + +static inline int next_entry(void *buf, struct policy_file *fp, size_t bytes) +{ + if (bytes > fp->len) + return -EINVAL; + + memcpy(buf, fp->data, bytes); + fp->data += bytes; + fp->len -= bytes; + return 0; +} + +static inline int put_entry(const void *buf, size_t bytes, int num, struct policy_file *fp) +{ + size_t len = bytes * num; + + if (len > fp->len) + return -EINVAL; + memcpy(fp->data, buf, len); + fp->data += len; + fp->len -= len; + + return 0; +} + +static inline char *sym_name(struct policydb *p, unsigned int sym_num, unsigned int element_nr) +{ + return p->sym_val_to_name[sym_num][element_nr]; +} + +extern u16 string_to_security_class(struct policydb *p, const char *name); +extern u32 string_to_av_perm(struct policydb *p, u16 tclass, const char *name); + +#endif /* _SS_POLICYDB_H_ */ + diff --git a/security/selinux/ss/services.c b/security/selinux/ss/services.c new file mode 100644 index 000000000..3db8bd215 --- /dev/null +++ b/security/selinux/ss/services.c @@ -0,0 +1,4002 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Implementation of the security services. + * + * Authors : Stephen Smalley, <sds@tycho.nsa.gov> + * James Morris <jmorris@redhat.com> + * + * Updated: Trusted Computer Solutions, Inc. <dgoeddel@trustedcs.com> + * + * Support for enhanced MLS infrastructure. + * Support for context based audit filters. + * + * Updated: Frank Mayer <mayerf@tresys.com> and Karl MacMillan <kmacmillan@tresys.com> + * + * Added conditional policy language extensions + * + * Updated: Hewlett-Packard <paul@paul-moore.com> + * + * Added support for NetLabel + * Added support for the policy capability bitmap + * + * Updated: Chad Sellers <csellers@tresys.com> + * + * Added validation of kernel classes and permissions + * + * Updated: KaiGai Kohei <kaigai@ak.jp.nec.com> + * + * Added support for bounds domain and audit messaged on masked permissions + * + * Updated: Guido Trentalancia <guido@trentalancia.com> + * + * Added support for runtime switching of the policy type + * + * Copyright (C) 2008, 2009 NEC Corporation + * Copyright (C) 2006, 2007 Hewlett-Packard Development Company, L.P. + * Copyright (C) 2004-2006 Trusted Computer Solutions, Inc. + * Copyright (C) 2003 - 2004, 2006 Tresys Technology, LLC + * Copyright (C) 2003 Red Hat, Inc., James Morris <jmorris@redhat.com> + */ +#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/vmalloc.h> +#include <net/netlabel.h> + +#include "flask.h" +#include "avc.h" +#include "avc_ss.h" +#include "security.h" +#include "context.h" +#include "policydb.h" +#include "sidtab.h" +#include "services.h" +#include "conditional.h" +#include "mls.h" +#include "objsec.h" +#include "netlabel.h" +#include "xfrm.h" +#include "ebitmap.h" +#include "audit.h" +#include "policycap_names.h" + +struct convert_context_args { + struct selinux_state *state; + struct policydb *oldp; + struct policydb *newp; +}; + +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, + struct security_class_mapping *map, + struct selinux_map *out_map) +{ + u16 i, j; + unsigned k; + bool print_unknown_handle = false; + + /* Find number of classes in the input mapping */ + if (!map) + return -EINVAL; + i = 0; + while (map[i].name) + i++; + + /* Allocate space for the class records, plus one for class zero */ + out_map->mapping = kcalloc(++i, sizeof(*out_map->mapping), GFP_ATOMIC); + if (!out_map->mapping) + return -ENOMEM; + + /* Store the raw class and permission values */ + j = 0; + while (map[j].name) { + struct security_class_mapping *p_in = map + (j++); + struct selinux_mapping *p_out = out_map->mapping + j; + + /* An empty class string skips ahead */ + if (!strcmp(p_in->name, "")) { + p_out->num_perms = 0; + continue; + } + + p_out->value = string_to_security_class(pol, p_in->name); + if (!p_out->value) { + pr_info("SELinux: Class %s not defined in policy.\n", + p_in->name); + if (pol->reject_unknown) + goto err; + p_out->num_perms = 0; + print_unknown_handle = true; + continue; + } + + k = 0; + while (p_in->perms[k]) { + /* An empty permission string skips ahead */ + if (!*p_in->perms[k]) { + k++; + continue; + } + p_out->perms[k] = string_to_av_perm(pol, p_out->value, + p_in->perms[k]); + if (!p_out->perms[k]) { + pr_info("SELinux: Permission %s in class %s not defined in policy.\n", + p_in->perms[k], p_in->name); + if (pol->reject_unknown) + goto err; + print_unknown_handle = true; + } + + k++; + } + p_out->num_perms = k; + } + + if (print_unknown_handle) + pr_info("SELinux: the above unknown classes and permissions will be %s\n", + pol->allow_unknown ? "allowed" : "denied"); + + out_map->size = i; + return 0; +err: + kfree(out_map->mapping); + out_map->mapping = NULL; + return -EINVAL; +} + +/* + * Get real, policy values from mapped values + */ + +static u16 unmap_class(struct selinux_map *map, u16 tclass) +{ + if (tclass < map->size) + return map->mapping[tclass].value; + + return tclass; +} + +/* + * Get kernel value for class from its policy value + */ +static u16 map_class(struct selinux_map *map, u16 pol_value) +{ + u16 i; + + for (i = 1; i < map->size; i++) { + if (map->mapping[i].value == pol_value) + return i; + } + + return SECCLASS_NULL; +} + +static void map_decision(struct selinux_map *map, + u16 tclass, struct av_decision *avd, + int allow_unknown) +{ + if (tclass < map->size) { + struct selinux_mapping *mapping = &map->mapping[tclass]; + unsigned int i, n = mapping->num_perms; + u32 result; + + for (i = 0, result = 0; i < n; i++) { + if (avd->allowed & mapping->perms[i]) + result |= 1<<i; + if (allow_unknown && !mapping->perms[i]) + result |= 1<<i; + } + avd->allowed = result; + + for (i = 0, result = 0; i < n; i++) + if (avd->auditallow & mapping->perms[i]) + result |= 1<<i; + avd->auditallow = result; + + for (i = 0, result = 0; i < n; i++) { + if (avd->auditdeny & mapping->perms[i]) + result |= 1<<i; + if (!allow_unknown && !mapping->perms[i]) + result |= 1<<i; + } + /* + * In case the kernel has a bug and requests a permission + * between num_perms and the maximum permission number, we + * should audit that denial + */ + for (; i < (sizeof(u32)*8); i++) + result |= 1<<i; + avd->auditdeny = result; + } +} + +int security_mls_enabled(struct selinux_state *state) +{ + int mls_enabled; + struct selinux_policy *policy; + + if (!selinux_initialized(state)) + return 0; + + rcu_read_lock(); + policy = rcu_dereference(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; + + BUG_ON(pdatum->value < 1 || pdatum->value > 32); + + permission_names[pdatum->value - 1] = (char *)k; + + return 0; +} + +static void security_dump_masked_av(struct policydb *policydb, + struct context *scontext, + struct context *tcontext, + u16 tclass, + u32 permissions, + const char *reason) +{ + struct common_datum *common_dat; + struct class_datum *tclass_dat; + struct audit_buffer *ab; + char *tclass_name; + char *scontext_name = NULL; + char *tcontext_name = NULL; + char *permission_names[32]; + int index; + u32 length; + bool need_comma = false; + + if (!permissions) + return; + + tclass_name = sym_name(policydb, SYM_CLASSES, tclass - 1); + tclass_dat = policydb->class_val_to_struct[tclass - 1]; + common_dat = tclass_dat->comdatum; + + /* init permission_names */ + if (common_dat && + hashtab_map(&common_dat->permissions.table, + dump_masked_av_helper, permission_names) < 0) + goto out; + + if (hashtab_map(&tclass_dat->permissions.table, + dump_masked_av_helper, permission_names) < 0) + goto out; + + /* get scontext/tcontext in text form */ + if (context_struct_to_string(policydb, scontext, + &scontext_name, &length) < 0) + goto out; + + if (context_struct_to_string(policydb, tcontext, + &tcontext_name, &length) < 0) + goto out; + + /* audit a message */ + ab = audit_log_start(audit_context(), + GFP_ATOMIC, AUDIT_SELINUX_ERR); + if (!ab) + goto out; + + audit_log_format(ab, "op=security_compute_av reason=%s " + "scontext=%s tcontext=%s tclass=%s perms=", + reason, scontext_name, tcontext_name, tclass_name); + + for (index = 0; index < 32; index++) { + u32 mask = (1 << index); + + if ((mask & permissions) == 0) + continue; + + audit_log_format(ab, "%s%s", + need_comma ? "," : "", + permission_names[index] + ? permission_names[index] : "????"); + need_comma = true; + } + audit_log_end(ab); +out: + /* release scontext/tcontext */ + kfree(tcontext_name); + kfree(scontext_name); + + return; +} + +/* + * security_boundary_permission - drops violated permissions + * on boundary constraint. + */ +static void type_attribute_bounds_av(struct policydb *policydb, + struct context *scontext, + struct context *tcontext, + u16 tclass, + struct av_decision *avd) +{ + struct context lo_scontext; + struct context lo_tcontext, *tcontextp = tcontext; + struct av_decision lo_avd; + struct type_datum *source; + struct type_datum *target; + u32 masked = 0; + + source = 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 + * only looking for ioctl based extended permssions + */ +void services_compute_xperms_drivers( + struct extended_perms *xperms, + struct avtab_node *node) +{ + unsigned int i; + + if (node->datum.u.xperms->specified == AVTAB_XPERMS_IOCTLDRIVER) { + /* if one or more driver has all permissions allowed */ + for (i = 0; i < ARRAY_SIZE(xperms->drivers.p); i++) + xperms->drivers.p[i] |= node->datum.u.xperms->perms.p[i]; + } else if (node->datum.u.xperms->specified == AVTAB_XPERMS_IOCTLFUNCTION) { + /* if allowing permissions within a driver */ + security_xperm_set(xperms->drivers.p, + node->datum.u.xperms->driver); + } + + /* If no ioctl commands are allowed, ignore auditallow and auditdeny */ + if (node->key.specified & AVTAB_XPERMS_ALLOWED) + xperms->len = 1; +} + +/* + * Compute access vectors and extended permissions based on a context + * structure pair for the permissions in a particular class. + */ +static void context_struct_compute_av(struct policydb *policydb, + struct context *scontext, + struct context *tcontext, + u16 tclass, + struct av_decision *avd, + struct extended_perms *xperms) +{ + struct constraint_node *constraint; + struct role_allow *ra; + struct avtab_key avkey; + struct avtab_node *node; + struct class_datum *tclass_datum; + struct ebitmap *sattr, *tattr; + struct ebitmap_node *snode, *tnode; + unsigned int i, j; + + avd->allowed = 0; + avd->auditallow = 0; + avd->auditdeny = 0xffffffff; + if (xperms) { + memset(&xperms->drivers, 0, sizeof(xperms->drivers)); + xperms->len = 0; + } + + if (unlikely(!tclass || tclass > policydb->p_classes.nprim)) { + if (printk_ratelimit()) + pr_warn("SELinux: Invalid class %hu\n", tclass); + return; + } + + tclass_datum = policydb->class_val_to_struct[tclass - 1]; + + /* + * If a specific type enforcement rule was defined for + * this permission check, then use it. + */ + avkey.target_class = tclass; + avkey.specified = AVTAB_AV | AVTAB_XPERMS; + sattr = &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. + */ + type_attribute_bounds_av(policydb, scontext, tcontext, + tclass, avd); +} + +static int security_validtrans_handle_fail(struct selinux_state *state, + 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(state)) + return 0; + return -EPERM; +} + +static int security_compute_validatetrans(struct selinux_state *state, + u32 oldsid, u32 newsid, u32 tasksid, + u16 orig_tclass, bool user) +{ + struct 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(state)) + return 0; + + rcu_read_lock(); + + policy = rcu_dereference(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(state, + policy, + oentry, + nentry, + tentry, + tclass); + goto out; + } + constraint = constraint->next; + } + +out: + rcu_read_unlock(); + return rc; +} + +int security_validate_transition_user(struct selinux_state *state, + u32 oldsid, u32 newsid, u32 tasksid, + u16 tclass) +{ + return security_compute_validatetrans(state, oldsid, newsid, tasksid, + tclass, true); +} + +int security_validate_transition(struct selinux_state *state, + u32 oldsid, u32 newsid, u32 tasksid, + u16 orig_tclass) +{ + return security_compute_validatetrans(state, oldsid, newsid, tasksid, + orig_tclass, false); +} + +/* + * security_bounded_transition - check whether the given + * transition is directed to bounded, or not. + * It returns 0, if @newsid is bounded by @oldsid. + * Otherwise, it returns error code. + * + * @oldsid : current security identifier + * @newsid : destinated security identifier + */ +int security_bounded_transition(struct selinux_state *state, + u32 old_sid, u32 new_sid) +{ + struct selinux_policy *policy; + struct policydb *policydb; + struct sidtab *sidtab; + struct sidtab_entry *old_entry, *new_entry; + struct type_datum *type; + int index; + int rc; + + if (!selinux_initialized(state)) + return 0; + + rcu_read_lock(); + policy = rcu_dereference(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; +} + +void services_compute_xperms_decision(struct extended_perms_decision *xpermd, + struct avtab_node *node) +{ + unsigned int i; + + if (node->datum.u.xperms->specified == AVTAB_XPERMS_IOCTLFUNCTION) { + if (xpermd->driver != node->datum.u.xperms->driver) + return; + } else if (node->datum.u.xperms->specified == AVTAB_XPERMS_IOCTLDRIVER) { + if (!security_xperm_test(node->datum.u.xperms->perms.p, + xpermd->driver)) + return; + } else { + BUG(); + } + + if (node->key.specified == AVTAB_XPERMS_ALLOWED) { + xpermd->used |= XPERMS_ALLOWED; + if (node->datum.u.xperms->specified == AVTAB_XPERMS_IOCTLDRIVER) { + memset(xpermd->allowed->p, 0xff, + sizeof(xpermd->allowed->p)); + } + if (node->datum.u.xperms->specified == AVTAB_XPERMS_IOCTLFUNCTION) { + for (i = 0; i < ARRAY_SIZE(xpermd->allowed->p); i++) + xpermd->allowed->p[i] |= + node->datum.u.xperms->perms.p[i]; + } + } else if (node->key.specified == AVTAB_XPERMS_AUDITALLOW) { + xpermd->used |= XPERMS_AUDITALLOW; + if (node->datum.u.xperms->specified == AVTAB_XPERMS_IOCTLDRIVER) { + memset(xpermd->auditallow->p, 0xff, + sizeof(xpermd->auditallow->p)); + } + if (node->datum.u.xperms->specified == AVTAB_XPERMS_IOCTLFUNCTION) { + for (i = 0; i < ARRAY_SIZE(xpermd->auditallow->p); i++) + xpermd->auditallow->p[i] |= + node->datum.u.xperms->perms.p[i]; + } + } else if (node->key.specified == AVTAB_XPERMS_DONTAUDIT) { + xpermd->used |= XPERMS_DONTAUDIT; + if (node->datum.u.xperms->specified == AVTAB_XPERMS_IOCTLDRIVER) { + memset(xpermd->dontaudit->p, 0xff, + sizeof(xpermd->dontaudit->p)); + } + if (node->datum.u.xperms->specified == AVTAB_XPERMS_IOCTLFUNCTION) { + for (i = 0; i < ARRAY_SIZE(xpermd->dontaudit->p); i++) + xpermd->dontaudit->p[i] |= + node->datum.u.xperms->perms.p[i]; + } + } else { + BUG(); + } +} + +void security_compute_xperms_decision(struct selinux_state *state, + u32 ssid, + u32 tsid, + u16 orig_tclass, + u8 driver, + struct extended_perms_decision *xpermd) +{ + struct 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->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(state)) + goto allow; + + policy = rcu_dereference(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 + * @tclass: target security class + * @avd: access vector decisions + * @xperms: extended permissions + * + * Compute a set of access vector decisions based on the + * SID pair (@ssid, @tsid) for the permissions in @tclass. + */ +void security_compute_av(struct selinux_state *state, + u32 ssid, + u32 tsid, + u16 orig_tclass, + struct av_decision *avd, + struct extended_perms *xperms) +{ + struct selinux_policy *policy; + struct policydb *policydb; + struct sidtab *sidtab; + u16 tclass; + struct context *scontext = NULL, *tcontext = NULL; + + rcu_read_lock(); + policy = rcu_dereference(state->policy); + avd_init(policy, avd); + xperms->len = 0; + if (!selinux_initialized(state)) + 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; + + 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(); + return; +allow: + avd->allowed = 0xffffffff; + goto out; +} + +void security_compute_av_user(struct selinux_state *state, + u32 ssid, + u32 tsid, + u16 tclass, + struct av_decision *avd) +{ + struct selinux_policy *policy; + struct policydb *policydb; + struct sidtab *sidtab; + struct context *scontext = NULL, *tcontext = NULL; + + rcu_read_lock(); + policy = rcu_dereference(state->policy); + avd_init(policy, avd); + if (!selinux_initialized(state)) + 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; + + 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(); + 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(struct selinux_state *state, char *page) +{ + struct selinux_policy *policy; + int rc; + + if (!selinux_initialized(state)) { + pr_err("SELinux: %s: called before initial load_policy\n", + __func__); + return -EINVAL; + } + + rcu_read_lock(); + policy = rcu_dereference(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(struct selinux_state *state, + u32 sid, char **scontext, + u32 *scontext_len, int force, + int 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(state)) { + if (sid <= SECINITSID_NUM) { + char *scontextp; + const char *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(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(struct selinux_state *state, + u32 sid, char **scontext, u32 *scontext_len) +{ + return security_sid_to_context_core(state, sid, scontext, + scontext_len, 0, 0); +} + +int security_sid_to_context_force(struct selinux_state *state, u32 sid, + char **scontext, u32 *scontext_len) +{ + return security_sid_to_context_core(state, sid, scontext, + scontext_len, 1, 0); +} + +/** + * 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(struct selinux_state *state, u32 sid, + char **scontext, u32 *scontext_len) +{ + return security_sid_to_context_core(state, sid, scontext, + scontext_len, 1, 1); +} + +/* + * 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 = (char *) 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(struct selinux_state *state, + const char *scontext, u32 scontext_len, + u32 *sid, u32 def_sid, gfp_t gfp_flags, + int 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(state)) { + int 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(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(struct selinux_state *state, + const char *scontext, u32 scontext_len, u32 *sid, + gfp_t gfp) +{ + return security_context_to_sid_core(state, scontext, scontext_len, + sid, SECSID_NULL, gfp, 0); +} + +int security_context_str_to_sid(struct selinux_state *state, + const char *scontext, u32 *sid, gfp_t gfp) +{ + return security_context_to_sid(state, scontext, strlen(scontext), + sid, gfp); +} + +/** + * security_context_to_sid_default - Obtain a SID for a given security context, + * falling back to specified default if needed. + * + * @scontext: security context + * @scontext_len: length in bytes + * @sid: security identifier, SID + * @def_sid: default SID to assign on error + * + * Obtains a SID associated with the security context that + * has the string representation specified by @scontext. + * The default SID is passed to the MLS layer to be used to allow + * kernel labeling of the MLS field if the MLS field is not present + * (for upgrading to MLS without full relabel). + * Implicitly forces adding of the context even if it cannot be mapped yet. + * Returns -%EINVAL if the context is invalid, -%ENOMEM if insufficient + * memory is available, or 0 on success. + */ +int security_context_to_sid_default(struct selinux_state *state, + const char *scontext, u32 scontext_len, + u32 *sid, u32 def_sid, gfp_t gfp_flags) +{ + return security_context_to_sid_core(state, scontext, scontext_len, + sid, def_sid, gfp_flags, 1); +} + +int security_context_to_sid_force(struct selinux_state *state, + const char *scontext, u32 scontext_len, + u32 *sid) +{ + return security_context_to_sid_core(state, scontext, scontext_len, + sid, SECSID_NULL, GFP_KERNEL, 1); +} + +static int compute_sid_handle_invalid_context( + struct selinux_state *state, + struct 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); + 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(state)) + return 0; + return -EACCES; +} + +static void filename_compute_type(struct policydb *policydb, + struct context *newcontext, + u32 stype, u32 ttype, u16 tclass, + const char *objname) +{ + struct filename_trans_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(struct selinux_state *state, + u32 ssid, + u32 tsid, + u16 orig_tclass, + u32 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_datum *avdatum; + struct avtab_node *node; + u16 tclass; + int rc = 0; + bool sock; + + if (!selinux_initialized(state)) { + 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(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 to default values. */ + if (cladatum && cladatum->default_type == DEFAULT_SOURCE) { + newcontext.type = scontext->type; + } else if (cladatum && cladatum->default_type == DEFAULT_TARGET) { + newcontext.type = tcontext->type; + } else { + if ((tclass == policydb->process_class) || sock) { + /* Use the type of process. */ + newcontext.type = scontext->type; + } else { + /* Use the type of the related object. */ + newcontext.type = tcontext->type; + } + } + + /* Look for a type transition/member/change rule. */ + avkey.source_type = scontext->type; + avkey.target_type = tcontext->type; + avkey.target_class = tclass; + avkey.specified = specified; + avdatum = avtab_search(&policydb->te_avtab, &avkey); + + /* If no permanent rule, also check for enabled conditional rules */ + if (!avdatum) { + node = avtab_search_node(&policydb->te_cond_avtab, &avkey); + for (; node; node = avtab_search_node_next(node, specified)) { + if (node->key.specified & AVTAB_ENABLED) { + avdatum = &node->datum; + break; + } + } + } + + if (avdatum) { + /* Use the type from the type transition/member/change rule. */ + newcontext.type = avdatum->u.data; + } + + /* if we have a objname this is a file trans check so check those rules */ + if (objname) + filename_compute_type(policydb, &newcontext, scontext->type, + tcontext->type, tclass, objname); + + /* Check for class-specific changes. */ + if (specified & AVTAB_TRANSITION) { + /* Look for a role transition rule. */ + 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(state, policy, sentry, + tentry, tclass, + &newcontext); + if (rc) + goto out_unlock; + } + /* Obtain the sid for the context. */ + 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 + * @out_sid: security identifier for new subject/object + * + * Compute a SID to use for labeling a new subject or object in the + * class @tclass based on a SID pair (@ssid, @tsid). + * Return -%EINVAL if any of the parameters are invalid, -%ENOMEM + * if insufficient memory is available, or %0 if the new SID was + * computed successfully. + */ +int security_transition_sid(struct selinux_state *state, + u32 ssid, u32 tsid, u16 tclass, + const struct qstr *qstr, u32 *out_sid) +{ + return security_compute_sid(state, ssid, tsid, tclass, + AVTAB_TRANSITION, + qstr ? qstr->name : NULL, out_sid, true); +} + +int security_transition_sid_user(struct selinux_state *state, + u32 ssid, u32 tsid, u16 tclass, + const char *objname, u32 *out_sid) +{ + return security_compute_sid(state, ssid, tsid, tclass, + AVTAB_TRANSITION, + objname, out_sid, false); +} + +/** + * security_member_sid - Compute the SID for member selection. + * @ssid: source security identifier + * @tsid: target security identifier + * @tclass: target security class + * @out_sid: security identifier for selected member + * + * Compute a SID to use when selecting a member of a polyinstantiated + * object of class @tclass based on a SID pair (@ssid, @tsid). + * Return -%EINVAL if any of the parameters are invalid, -%ENOMEM + * if insufficient memory is available, or %0 if the SID was + * computed successfully. + */ +int security_member_sid(struct selinux_state *state, + u32 ssid, + u32 tsid, + u16 tclass, + u32 *out_sid) +{ + return security_compute_sid(state, ssid, tsid, tclass, + AVTAB_MEMBER, NULL, + out_sid, false); +} + +/** + * security_change_sid - Compute the SID for object relabeling. + * @ssid: source security identifier + * @tsid: target security identifier + * @tclass: target security class + * @out_sid: security identifier for selected member + * + * Compute a SID to use for relabeling an object of class @tclass + * based on a SID pair (@ssid, @tsid). + * Return -%EINVAL if any of the parameters are invalid, -%ENOMEM + * if insufficient memory is available, or %0 if the SID was + * computed successfully. + */ +int security_change_sid(struct selinux_state *state, + u32 ssid, + u32 tsid, + u16 tclass, + u32 *out_sid) +{ + return security_compute_sid(state, + ssid, tsid, tclass, AVTAB_CHANGE, NULL, + out_sid, false); +} + +static inline int convert_context_handle_invalid_context( + struct selinux_state *state, + struct policydb *policydb, + struct context *context) +{ + char *s; + u32 len; + + if (enforcing_enabled(state)) + return -EINVAL; + + if (!context_struct_to_string(policydb, context, &s, &len)) { + pr_warn("SELinux: Context %s would be invalid if enforcing\n", + s); + kfree(s); + } + return 0; +} + +/* + * Convert the values in the security context + * structure `oldc' from the values specified + * in the policy `p->oldp' to the values specified + * in the policy `p->newp', storing the new context + * in `newc'. Verify that the context is valid + * under the new policy. + */ +static int convert_context(struct context *oldc, struct context *newc, void *p, + gfp_t gfp_flags) +{ + struct convert_context_args *args; + struct ocontext *oc; + struct role_datum *role; + struct type_datum *typdatum; + struct user_datum *usrdatum; + char *s; + u32 len; + int rc; + + args = p; + + 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. */ + rc = -EINVAL; + 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. */ + rc = -EINVAL; + 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. */ + rc = -EINVAL; + 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; + rc = -EINVAL; + 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->state, + 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_state *state, + struct selinux_policy *policy) +{ + struct policydb *p; + unsigned int i; + struct ebitmap_node *node; + + p = &policy->policydb; + + for (i = 0; i < ARRAY_SIZE(state->policycap); i++) + WRITE_ONCE(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_state *state, + struct selinux_load_state *load_state) +{ + struct selinux_policy *oldpolicy; + + oldpolicy = rcu_dereference_protected(state->policy, + lockdep_is_held(&state->policy_mutex)); + + sidtab_cancel_convert(oldpolicy->sidtab); + selinux_policy_free(load_state->policy); + kfree(load_state->convert_data); +} + +static void selinux_notify_policy_change(struct selinux_state *state, + u32 seqno) +{ + /* Flush external caches and notify userspace of policy load */ + avc_ss_reset(state->avc, seqno); + selnl_notify_policyload(seqno); + selinux_status_update_policyload(state, seqno); + selinux_netlbl_cache_invalidate(); + selinux_xfrm_notify_policyload(); +} + +void selinux_policy_commit(struct selinux_state *state, + struct selinux_load_state *load_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(state, newpolicy); + + if (!selinux_initialized(state)) { + /* + * 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(state); + 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(state, seqno); +} + +/** + * security_load_policy - Load a security policy configuration. + * @data: binary policy data + * @len: length of data in bytes + * + * Load a new set of security policy configuration data, + * validate it and convert the SID table as necessary. + * This function will flush the access vector cache after + * loading the new policy. + */ +int security_load_policy(struct selinux_state *state, void *data, size_t len, + struct selinux_load_state *load_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(sizeof(*newpolicy), GFP_KERNEL); + if (!newpolicy) + return -ENOMEM; + + newpolicy->sidtab = kzalloc(sizeof(*newpolicy->sidtab), GFP_KERNEL); + 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(state)) { + /* 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_data = kmalloc(sizeof(*convert_data), GFP_KERNEL); + if (!convert_data) { + rc = -ENOMEM; + goto err_free_isids; + } + + /* + * Convert the internal representations of contexts + * in the new SID table. + */ + convert_data->args.state = state; + convert_data->args.oldp = &oldpolicy->policydb; + convert_data->args.newp = &newpolicy->policydb; + + convert_data->sidtab_params.func = convert_context; + 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(struct selinux_state *state, + 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(state)) { + *out_sid = SECINITSID_PORT; + return 0; + } + +retry: + rc = 0; + rcu_read_lock(); + policy = rcu_dereference(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_pkey_sid - Obtain the SID for a pkey. + * @subnet_prefix: Subnet Prefix + * @pkey_num: pkey number + * @out_sid: security identifier + */ +int security_ib_pkey_sid(struct selinux_state *state, + u64 subnet_prefix, u16 pkey_num, u32 *out_sid) +{ + struct selinux_policy *policy; + struct policydb *policydb; + struct sidtab *sidtab; + struct ocontext *c; + int rc; + + if (!selinux_initialized(state)) { + *out_sid = SECINITSID_UNLABELED; + return 0; + } + +retry: + rc = 0; + rcu_read_lock(); + policy = rcu_dereference(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: port number + * @out_sid: security identifier + */ +int security_ib_endport_sid(struct selinux_state *state, + const char *dev_name, u8 port_num, u32 *out_sid) +{ + struct selinux_policy *policy; + struct policydb *policydb; + struct sidtab *sidtab; + struct ocontext *c; + int rc; + + if (!selinux_initialized(state)) { + *out_sid = SECINITSID_UNLABELED; + return 0; + } + +retry: + rc = 0; + rcu_read_lock(); + policy = rcu_dereference(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(struct selinux_state *state, + char *name, u32 *if_sid) +{ + struct selinux_policy *policy; + struct policydb *policydb; + struct sidtab *sidtab; + int rc; + struct ocontext *c; + + if (!selinux_initialized(state)) { + *if_sid = SECINITSID_NETIF; + return 0; + } + +retry: + rc = 0; + rcu_read_lock(); + policy = rcu_dereference(state->policy); + policydb = &policy->policydb; + sidtab = policy->sidtab; + + c = policydb->ocontexts[OCON_NETIF]; + while (c) { + if (strcmp(name, c->u.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 int match_ipv6_addrmask(u32 *input, u32 *addr, u32 *mask) +{ + int i, fail = 0; + + for (i = 0; i < 4; i++) + if (addr[i] != (input[i] & mask[i])) { + fail = 1; + break; + } + + return !fail; +} + +/** + * security_node_sid - Obtain the SID for a node (host). + * @domain: communication domain aka address family + * @addrp: address + * @addrlen: address length in bytes + * @out_sid: security identifier + */ +int security_node_sid(struct selinux_state *state, + u16 domain, + void *addrp, + u32 addrlen, + u32 *out_sid) +{ + struct selinux_policy *policy; + struct policydb *policydb; + struct sidtab *sidtab; + int rc; + struct ocontext *c; + + if (!selinux_initialized(state)) { + *out_sid = SECINITSID_NODE; + return 0; + } + +retry: + rcu_read_lock(); + policy = rcu_dereference(state->policy); + policydb = &policy->policydb; + sidtab = policy->sidtab; + + switch (domain) { + case AF_INET: { + u32 addr; + + rc = -EINVAL; + if (addrlen != sizeof(u32)) + goto out; + + addr = *((u32 *)addrp); + + c = policydb->ocontexts[OCON_NODE]; + while (c) { + if (c->u.node.addr == (addr & c->u.node.mask)) + break; + c = c->next; + } + break; + } + + case AF_INET6: + rc = -EINVAL; + if (addrlen != sizeof(u64) * 2) + goto out; + c = policydb->ocontexts[OCON_NODE6]; + while (c) { + if (match_ipv6_addrmask(addrp, c->u.node6.addr, + c->u.node6.mask)) + break; + c = c->next; + } + break; + + default: + rc = 0; + *out_sid = SECINITSID_NODE; + goto out; + } + + if (c) { + 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; +} + +#define SIDS_NEL 25 + +/** + * security_get_user_sids - Obtain reachable SIDs for a user. + * @fromsid: starting SID + * @username: username + * @sids: array of reachable SIDs for user + * @nel: number of elements in @sids + * + * Generate the set of SIDs for legal security contexts + * for a given user that can be reached by @fromsid. + * Set *@sids to point to a dynamically allocated + * array containing the set of SIDs. Set *@nel to the + * number of elements in the array. + */ + +int security_get_user_sids(struct selinux_state *state, + u32 fromsid, + char *username, + u32 **sids, + u32 *nel) +{ + struct selinux_policy *policy; + struct policydb *policydb; + struct sidtab *sidtab; + struct context *fromcon, usercon; + u32 *mysids = NULL, *mysids2, sid; + u32 i, j, mynel, maxnel = SIDS_NEL; + struct user_datum *user; + struct role_datum *role; + struct ebitmap_node *rnode, *tnode; + int rc; + + *sids = NULL; + *nel = 0; + + if (!selinux_initialized(state)) + return 0; + + mysids = kcalloc(maxnel, sizeof(*mysids), GFP_KERNEL); + if (!mysids) + return -ENOMEM; + +retry: + mynel = 0; + rcu_read_lock(); + policy = rcu_dereference(state->policy); + policydb = &policy->policydb; + sidtab = policy->sidtab; + + context_init(&usercon); + + rc = -EINVAL; + fromcon = sidtab_search(sidtab, fromsid); + if (!fromcon) + goto out_unlock; + + rc = -EINVAL; + user = symtab_search(&policydb->p_users, username); + if (!user) + goto out_unlock; + + usercon.user = user->value; + + ebitmap_for_each_positive_bit(&user->roles, rnode, i) { + role = policydb->role_val_to_struct[i]; + usercon.role = i + 1; + ebitmap_for_each_positive_bit(&role->types, tnode, j) { + usercon.type = j + 1; + + if (mls_setup_user_range(policydb, fromcon, user, + &usercon)) + continue; + + rc = sidtab_context_to_sid(sidtab, &usercon, &sid); + if (rc == -ESTALE) { + rcu_read_unlock(); + goto retry; + } + if (rc) + goto out_unlock; + if (mynel < maxnel) { + mysids[mynel++] = sid; + } else { + rc = -ENOMEM; + maxnel += SIDS_NEL; + mysids2 = kcalloc(maxnel, sizeof(*mysids2), GFP_ATOMIC); + if (!mysids2) + goto out_unlock; + memcpy(mysids2, mysids, mynel * sizeof(*mysids2)); + kfree(mysids); + mysids = mysids2; + mysids[mynel++] = sid; + } + } + } + rc = 0; +out_unlock: + rcu_read_unlock(); + if (rc || !mynel) { + kfree(mysids); + return rc; + } + + rc = -ENOMEM; + mysids2 = kcalloc(mynel, sizeof(*mysids2), GFP_KERNEL); + if (!mysids2) { + kfree(mysids); + return rc; + } + for (i = 0, j = 0; i < mynel; i++) { + struct av_decision dummy_avd; + rc = avc_has_perm_noaudit(state, + fromsid, mysids[i], + SECCLASS_PROCESS, /* kernel value */ + PROCESS__TRANSITION, AVC_STRICT, + &dummy_avd); + if (!rc) + mysids2[j++] = mysids[i]; + cond_resched(); + } + kfree(mysids); + *sids = mysids2; + *nel = j; + return 0; +} + +/** + * __security_genfs_sid - Helper to obtain a SID for a file in a filesystem + * @fstype: filesystem type + * @path: path from root of mount + * @sclass: file security class + * @sid: SID for path + * + * Obtain a SID to use for a file in a filesystem that + * cannot support xattr or use a fixed labeling behavior like + * transition SIDs or task SIDs. + * + * 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, + char *path, + u16 orig_sclass, + u32 *sid) +{ + struct policydb *policydb = &policy->policydb; + struct sidtab *sidtab = policy->sidtab; + int len; + u16 sclass; + struct genfs *genfs; + struct ocontext *c; + int cmp = 0; + + 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; + + for (c = genfs->head; c; c = c->next) { + len = strlen(c->u.name); + if ((!c->v.sclass || sclass == c->v.sclass) && + (strncmp(c->u.name, path, len) == 0)) + break; + } + + 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 + * @sclass: file security class + * @sid: SID for path + * + * Acquire policy_rwlock before calling __security_genfs_sid() and release + * it afterward. + */ +int security_genfs_sid(struct selinux_state *state, + const char *fstype, + char *path, + u16 orig_sclass, + u32 *sid) +{ + struct selinux_policy *policy; + int retval; + + if (!selinux_initialized(state)) { + *sid = SECINITSID_UNLABELED; + return 0; + } + + do { + rcu_read_lock(); + policy = rcu_dereference(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, + 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 selinux_state *state, struct super_block *sb) +{ + struct selinux_policy *policy; + struct policydb *policydb; + struct sidtab *sidtab; + int rc; + struct ocontext *c; + struct superblock_security_struct *sbsec = sb->s_security; + const char *fstype = sb->s_type->name; + + if (!selinux_initialized(state)) { + sbsec->behavior = SECURITY_FS_USE_NONE; + sbsec->sid = SECINITSID_UNLABELED; + return 0; + } + +retry: + rc = 0; + rcu_read_lock(); + policy = rcu_dereference(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 = kcalloc(*len, sizeof(int), GFP_ATOMIC); + if (!*values) + goto err; + + for (i = 0; i < *len; i++) { + (*values)[i] = policydb->bool_val_to_struct[i]->state; + + rc = -ENOMEM; + (*names)[i] = kstrdup(sym_name(policydb, SYM_BOOLS, i), + GFP_ATOMIC); + if (!(*names)[i]) + goto err; + } + rc = 0; +out: + return rc; +err: + if (*names) { + for (i = 0; i < *len; i++) + kfree((*names)[i]); + kfree(*names); + } + kfree(*values); + *len = 0; + *names = NULL; + *values = NULL; + goto out; +} + + +int security_set_bools(struct selinux_state *state, u32 len, int *values) +{ + struct selinux_policy *newpolicy, *oldpolicy; + int rc; + u32 i, seqno = 0; + + if (!selinux_initialized(state)) + 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(state, seqno); + return 0; +} + +int security_get_bool_value(struct selinux_state *state, + u32 index) +{ + struct selinux_policy *policy; + struct policydb *policydb; + int rc; + u32 len; + + if (!selinux_initialized(state)) + return 0; + + rcu_read_lock(); + policy = rcu_dereference(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(struct selinux_state *state, + 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(state)) { + *new_sid = sid; + return 0; + } + +retry: + rc = 0; + context_init(&newcon); + + rcu_read_lock(); + policy = rcu_dereference(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(state, 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 + * + * Description: + * Compare the @nlbl_sid and @xfrm_sid values and if the two SIDs can be + * resolved into a single SID it is returned via @peer_sid and the function + * returns zero. Otherwise @peer_sid is set to SECSID_NULL and the function + * returns a negative value. A table summarizing the behavior is below: + * + * | function return | @sid + * ------------------------------+-----------------+----------------- + * no peer labels | 0 | SECSID_NULL + * single peer label | 0 | <peer_label> + * multiple, consistent labels | 0 | <peer_label> + * multiple, inconsistent labels | -<errno> | SECSID_NULL + * + */ +int security_net_peersid_resolve(struct selinux_state *state, + u32 nlbl_sid, u32 nlbl_type, + u32 xfrm_sid, + u32 *peer_sid) +{ + struct 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(state)) + return 0; + + rcu_read_lock(); + policy = rcu_dereference(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_cmp(nlbl_ctx, xfrm_ctx) ? 0 : -EACCES); + if (rc) + goto out; + + /* at present NetLabel SIDs/labels really only carry MLS + * information so if the MLS portion of the NetLabel SID + * matches the MLS portion of the labeled XFRM SID/label + * then pass along the XFRM SID as it is the most + * expressive */ + *peer_sid = xfrm_sid; +out: + 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; + int 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, int *nclasses) +{ + struct policydb *policydb; + 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) { + int i; + for (i = 0; i < *nclasses; i++) + kfree((*classes)[i]); + kfree(*classes); + } + +out: + return rc; +} + +static int get_permissions_callback(void *k, void *d, void *args) +{ + struct perm_datum *datum = d; + char *name = k, **perms = args; + int value = datum->value - 1; + + perms[value] = kstrdup(name, GFP_ATOMIC); + if (!perms[value]) + return -ENOMEM; + + return 0; +} + +int security_get_permissions(struct selinux_policy *policy, + char *class, char ***perms, int *nperms) +{ + struct policydb *policydb; + int rc, i; + 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(struct selinux_state *state) +{ + struct selinux_policy *policy; + int value; + + if (!selinux_initialized(state)) + return 0; + + rcu_read_lock(); + policy = rcu_dereference(state->policy); + value = policy->policydb.reject_unknown; + rcu_read_unlock(); + return value; +} + +int security_get_allow_unknown(struct selinux_state *state) +{ + struct selinux_policy *policy; + int value; + + if (!selinux_initialized(state)) + return 0; + + rcu_read_lock(); + policy = rcu_dereference(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(struct selinux_state *state, + unsigned int req_cap) +{ + struct selinux_policy *policy; + int rc; + + if (!selinux_initialized(state)) + return 0; + + rcu_read_lock(); + policy = rcu_dereference(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; +}; + +void selinux_audit_rule_free(void *vrule) +{ + struct selinux_audit_rule *rule = vrule; + + if (rule) { + context_destroy(&rule->au_ctxt); + kfree(rule); + } +} + +int selinux_audit_rule_init(u32 field, u32 op, char *rulestr, void **vrule) +{ + struct selinux_state *state = &selinux_state; + struct 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(state)) + return -EOPNOTSUPP; + + switch (field) { + case AUDIT_SUBJ_USER: + case AUDIT_SUBJ_ROLE: + case AUDIT_SUBJ_TYPE: + case AUDIT_OBJ_USER: + case AUDIT_OBJ_ROLE: + case AUDIT_OBJ_TYPE: + /* only 'equals' and 'not equals' fit user, role, and type */ + if (op != Audit_equal && op != Audit_not_equal) + return -EINVAL; + break; + case AUDIT_SUBJ_SEN: + case AUDIT_SUBJ_CLR: + case AUDIT_OBJ_LEV_LOW: + case AUDIT_OBJ_LEV_HIGH: + /* we do not allow a range, indicated by the presence of '-' */ + if (strchr(rulestr, '-')) + return -EINVAL; + break; + default: + /* only the above fields are valid */ + return -EINVAL; + } + + tmprule = kzalloc(sizeof(struct selinux_audit_rule), GFP_KERNEL); + if (!tmprule) + return -ENOMEM; + + context_init(&tmprule->au_ctxt); + + 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: + rc = -EINVAL; + userdatum = symtab_search(&policydb->p_users, rulestr); + if (!userdatum) + goto out; + tmprule->au_ctxt.user = userdatum->value; + break; + case AUDIT_SUBJ_ROLE: + case AUDIT_OBJ_ROLE: + rc = -EINVAL; + roledatum = symtab_search(&policydb->p_roles, rulestr); + if (!roledatum) + goto out; + tmprule->au_ctxt.role = roledatum->value; + break; + case AUDIT_SUBJ_TYPE: + case AUDIT_OBJ_TYPE: + rc = -EINVAL; + typedatum = symtab_search(&policydb->p_types, rulestr); + if (!typedatum) + goto out; + tmprule->au_ctxt.type = typedatum->value; + break; + case AUDIT_SUBJ_SEN: + case AUDIT_SUBJ_CLR: + case AUDIT_OBJ_LEV_LOW: + case AUDIT_OBJ_LEV_HIGH: + rc = mls_from_string(policydb, rulestr, &tmprule->au_ctxt, + GFP_ATOMIC); + if (rc) + goto out; + break; + } + rc = 0; +out: + rcu_read_unlock(); + + if (rc) { + selinux_audit_rule_free(tmprule); + tmprule = NULL; + } + + *rule = tmprule; + + return rc; +} + +/* Check to see if the rule contains any selinux fields */ +int selinux_audit_rule_known(struct audit_krule *rule) +{ + int i; + + for (i = 0; i < rule->field_count; i++) { + struct audit_field *f = &rule->fields[i]; + switch (f->type) { + case AUDIT_SUBJ_USER: + case AUDIT_SUBJ_ROLE: + case AUDIT_SUBJ_TYPE: + case AUDIT_SUBJ_SEN: + case AUDIT_SUBJ_CLR: + case AUDIT_OBJ_USER: + case AUDIT_OBJ_ROLE: + case AUDIT_OBJ_TYPE: + case AUDIT_OBJ_LEV_LOW: + case AUDIT_OBJ_LEV_HIGH: + return 1; + } + } + + return 0; +} + +int selinux_audit_rule_match(u32 sid, u32 field, u32 op, void *vrule) +{ + struct 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(state)) + 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, sid); + if (unlikely(!ctxt)) { + WARN_ONCE(1, "selinux_audit_rule_match: unrecognized SID %d\n", + sid); + match = -ENOENT; + goto out; + } + + /* a field/op pair that is not caught here will simply fall through + without a match */ + switch (field) { + case AUDIT_SUBJ_USER: + case AUDIT_OBJ_USER: + switch (op) { + case Audit_equal: + match = (ctxt->user == rule->au_ctxt.user); + break; + case Audit_not_equal: + match = (ctxt->user != rule->au_ctxt.user); + break; + } + break; + case AUDIT_SUBJ_ROLE: + case AUDIT_OBJ_ROLE: + switch (op) { + case Audit_equal: + match = (ctxt->role == rule->au_ctxt.role); + break; + case Audit_not_equal: + match = (ctxt->role != rule->au_ctxt.role); + break; + } + break; + case AUDIT_SUBJ_TYPE: + case AUDIT_OBJ_TYPE: + switch (op) { + case Audit_equal: + match = (ctxt->type == rule->au_ctxt.type); + break; + case Audit_not_equal: + match = (ctxt->type != rule->au_ctxt.type); + break; + } + break; + case AUDIT_SUBJ_SEN: + case AUDIT_SUBJ_CLR: + case AUDIT_OBJ_LEV_LOW: + case AUDIT_OBJ_LEV_HIGH: + level = ((field == AUDIT_SUBJ_SEN || + field == AUDIT_OBJ_LEV_LOW) ? + &ctxt->range.level[0] : &ctxt->range.level[1]); + switch (op) { + case Audit_equal: + match = mls_level_eq(&rule->au_ctxt.range.level[0], + level); + break; + case Audit_not_equal: + match = !mls_level_eq(&rule->au_ctxt.range.level[0], + level); + break; + case Audit_lt: + match = (mls_level_dom(&rule->au_ctxt.range.level[0], + level) && + !mls_level_eq(&rule->au_ctxt.range.level[0], + level)); + break; + case Audit_le: + match = mls_level_dom(&rule->au_ctxt.range.level[0], + level); + break; + case Audit_gt: + match = (mls_level_dom(level, + &rule->au_ctxt.range.level[0]) && + !mls_level_eq(level, + &rule->au_ctxt.range.level[0])); + break; + case Audit_ge: + match = mls_level_dom(level, + &rule->au_ctxt.range.level[0]); + break; + } + } + +out: + rcu_read_unlock(); + return match; +} + +static int (*aurule_callback)(void) = audit_update_lsm_rules; + +static int aurule_avc_callback(u32 event) +{ + int err = 0; + + if (event == AVC_CALLBACK_RESET && aurule_callback) + err = aurule_callback(); + return err; +} + +static int __init aurule_init(void) +{ + int err; + + err = avc_add_callback(aurule_avc_callback, AVC_CALLBACK_RESET); + if (err) + panic("avc_add_callback() failed, error %d\n", err); + + return err; +} +__initcall(aurule_init); + +#ifdef CONFIG_NETLABEL +/** + * security_netlbl_cache_add - Add an entry to the NetLabel cache + * @secattr: the NetLabel packet security attributes + * @sid: the SELinux SID + * + * Description: + * Attempt to cache the context in @ctx, which was derived from the packet in + * @skb, in the NetLabel subsystem cache. This function assumes @secattr has + * already been initialized. + * + */ +static void security_netlbl_cache_add(struct netlbl_lsm_secattr *secattr, + u32 sid) +{ + u32 *sid_cache; + + sid_cache = kmalloc(sizeof(*sid_cache), GFP_ATOMIC); + if (sid_cache == NULL) + return; + secattr->cache = netlbl_secattr_cache_alloc(GFP_ATOMIC); + if (secattr->cache == NULL) { + kfree(sid_cache); + return; + } + + *sid_cache = sid; + secattr->cache->free = kfree; + secattr->cache->data = sid_cache; + secattr->flags |= NETLBL_SECATTR_CACHE; +} + +/** + * security_netlbl_secattr_to_sid - Convert a NetLabel secattr to a SELinux SID + * @secattr: the NetLabel packet security attributes + * @sid: the SELinux SID + * + * Description: + * Convert the given NetLabel security attributes in @secattr into a + * SELinux SID. If the @secattr field does not contain a full SELinux + * SID/context then use SECINITSID_NETMSG as the foundation. If possible the + * 'cache' field of @secattr is set and the CACHE flag is set; this is to + * allow the @secattr to be used by NetLabel to cache the secattr to SID + * conversion for future lookups. Returns zero on success, negative values on + * failure. + * + */ +int security_netlbl_secattr_to_sid(struct selinux_state *state, + struct netlbl_lsm_secattr *secattr, + u32 *sid) +{ + struct selinux_policy *policy; + struct policydb *policydb; + struct sidtab *sidtab; + int rc; + struct context *ctx; + struct context ctx_new; + + if (!selinux_initialized(state)) { + *sid = SECSID_NULL; + return 0; + } + +retry: + rc = 0; + rcu_read_lock(); + policy = rcu_dereference(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(struct selinux_state *state, + u32 sid, struct netlbl_lsm_secattr *secattr) +{ + struct selinux_policy *policy; + struct policydb *policydb; + int rc; + struct context *ctx; + + if (!selinux_initialized(state)) + return 0; + + rcu_read_lock(); + policy = rcu_dereference(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. + * @data: binary policy data + * @len: length of data in bytes + * + */ +int security_read_policy(struct selinux_state *state, + void **data, size_t *len) +{ + struct selinux_policy *policy; + int rc; + struct policy_file fp; + + 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; + + 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; + +} diff --git a/security/selinux/ss/services.h b/security/selinux/ss/services.h new file mode 100644 index 000000000..9555ad074 --- /dev/null +++ b/security/selinux/ss/services.h @@ -0,0 +1,38 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Implementation of the security services. + * + * Author : Stephen Smalley, <sds@tycho.nsa.gov> + */ +#ifndef _SS_SERVICES_H_ +#define _SS_SERVICES_H_ + +#include "policydb.h" + +/* Mapping for a single class */ +struct selinux_mapping { + u16 value; /* policy value for class */ + unsigned int num_perms; /* number of permissions in class */ + u32 perms[sizeof(u32) * 8]; /* policy values for permissions */ +}; + +/* Map for all of the classes, with array size */ +struct selinux_map { + struct selinux_mapping *mapping; /* indexed by class */ + u16 size; /* array size of mapping */ +}; + +struct selinux_policy { + struct sidtab *sidtab; + struct policydb policydb; + struct selinux_map map; + u32 latest_granting; +} __randomize_layout; + +void services_compute_xperms_drivers(struct extended_perms *xperms, + struct avtab_node *node); + +void services_compute_xperms_decision(struct extended_perms_decision *xpermd, + struct avtab_node *node); + +#endif /* _SS_SERVICES_H_ */ diff --git a/security/selinux/ss/sidtab.c b/security/selinux/ss/sidtab.c new file mode 100644 index 000000000..1981c5af1 --- /dev/null +++ b/security/selinux/ss/sidtab.c @@ -0,0 +1,628 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Implementation of the SID table type. + * + * Original author: Stephen Smalley, <sds@tycho.nsa.gov> + * Author: Ondrej Mosnacek, <omosnacek@gmail.com> + * + * Copyright (C) 2018 Red Hat, Inc. + */ +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/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" + +struct sidtab_str_cache { + struct rcu_head rcu_member; + struct list_head lru_member; + struct sidtab_entry *parent; + u32 len; + char str[]; +}; + +#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_cmp(&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) +{ + int i; + int chain_len = 0; + int slots_used = 0; + int entries = 0; + int max_chain_len = 0; + 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; + convert = s->convert; + + /* 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 + */ + if (convert) { + rc = -ENOMEM; + dst_convert = sidtab_do_lookup(convert->target, count, 1); + if (!dst_convert) { + context_destroy(&dst->context); + goto out_unlock; + } + + rc = convert->func(context, &dst_convert->context, + convert->args, 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); + convert->target->count = count + 1; + + hash_add_rcu(convert->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 = convert->func(&esrc->ptr_leaf->entries[i].context, + &edst->ptr_leaf->entries[i].context, + convert->args, 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(¶ms->target->roots[level], + &s->roots[level], &pos, count, level, params); + if (rc) { + /* we need to keep the old table - disable live convert */ + spin_lock_irqsave(&s->lock, flags); + s->convert = NULL; + spin_unlock_irqrestore(&s->lock, flags); + return rc; + } + /* + * 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(sizeof(struct sidtab_str_cache) + 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..9fce0d553 --- /dev/null +++ b/security/selinux/ss/sidtab.h @@ -0,0 +1,159 @@ +/* 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, <sds@tycho.nsa.gov> + * 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 { + int (*func)(struct context *oldc, struct context *newc, void *args, gfp_t gfp_flags); + void *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..c42a6648a --- /dev/null +++ b/security/selinux/ss/symtab.c @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Implementation of the symbol table type. + * + * Author : Stephen Smalley, <sds@tycho.nsa.gov> + */ +#include <linux/kernel.h> +#include <linux/string.h> +#include <linux/errno.h> +#include "symtab.h" + +static unsigned int symhash(const void *key) +{ + const char *p, *keyp; + unsigned int size; + unsigned int val; + + val = 0; + keyp = key; + size = strlen(keyp); + for (p = keyp; (p - keyp) < size; p++) + val = (val << 4 | (val >> (8*sizeof(unsigned int)-4))) ^ (*p); + return val; +} + +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, unsigned int 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(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..f2614138d --- /dev/null +++ b/security/selinux/ss/symtab.h @@ -0,0 +1,27 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * A symbol table (symtab) maintains associations between symbol + * strings and datum values. The type of the datum values + * is arbitrary. The symbol table type is implemented + * using the hash table type (hashtab). + * + * Author : Stephen Smalley, <sds@tycho.nsa.gov> + */ +#ifndef _SS_SYMTAB_H_ +#define _SS_SYMTAB_H_ + +#include "hashtab.h" + +struct symtab { + struct hashtab table; /* hash table (keyed on a string) */ + u32 nprim; /* number of primary names in table */ +}; + +int symtab_init(struct symtab *s, unsigned int size); + +int symtab_insert(struct symtab *s, char *name, void *datum); +void *symtab_search(struct symtab *s, const char *name); + +#endif /* _SS_SYMTAB_H_ */ + + diff --git a/security/selinux/status.c b/security/selinux/status.c new file mode 100644 index 000000000..4bc8f8099 --- /dev/null +++ b/security/selinux/status.c @@ -0,0 +1,124 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * mmap based event notifications for SELinux + * + * Author: KaiGai Kohei <kaigai@ak.jp.nec.com> + * + * Copyright (C) 2010 NEC corporation + */ +#include <linux/kernel.h> +#include <linux/gfp.h> +#include <linux/mm.h> +#include <linux/mutex.h> +#include "avc.h" +#include "security.h" + +/* + * The selinux_status_page shall be exposed to userspace applications + * using mmap interface on /selinux/status. + * It enables to notify applications a few events that will cause reset + * of userspace access vector without context switching. + * + * The selinux_kernel_status structure on the head of status page is + * protected from concurrent accesses using seqlock logic, so userspace + * application should reference the status page according to the seqlock + * logic. + * + * Typically, application checks status->sequence at the head of access + * control routine. If it is odd-number, kernel is updating the status, + * so please wait for a moment. If it is changed from the last sequence + * number, it means something happen, so application will reset userspace + * avc, if needed. + * In most cases, application shall confirm the kernel status is not + * changed without any system call invocations. + */ + +/* + * selinux_kernel_status_page + * + * It returns a reference to selinux_status_page. If the status page is + * not allocated yet, it also tries to allocate it at the first time. + */ +struct page *selinux_kernel_status_page(struct selinux_state *state) +{ + struct selinux_kernel_status *status; + struct page *result = NULL; + + mutex_lock(&state->status_lock); + if (!state->status_page) { + state->status_page = alloc_page(GFP_KERNEL|__GFP_ZERO); + + if (state->status_page) { + status = page_address(state->status_page); + + status->version = SELINUX_KERNEL_STATUS_VERSION; + status->sequence = 0; + status->enforcing = enforcing_enabled(state); + /* + * NOTE: the next policyload event shall set + * a positive value on the status->policyload, + * although it may not be 1, but never zero. + * So, application can know it was updated. + */ + status->policyload = 0; + status->deny_unknown = + !security_get_allow_unknown(state); + } + } + result = state->status_page; + mutex_unlock(&state->status_lock); + + return result; +} + +/* + * selinux_status_update_setenforce + * + * It updates status of the current enforcing/permissive mode. + */ +void selinux_status_update_setenforce(struct selinux_state *state, + int enforcing) +{ + struct selinux_kernel_status *status; + + mutex_lock(&state->status_lock); + if (state->status_page) { + status = page_address(state->status_page); + + status->sequence++; + smp_wmb(); + + status->enforcing = enforcing; + + smp_wmb(); + status->sequence++; + } + mutex_unlock(&state->status_lock); +} + +/* + * selinux_status_update_policyload + * + * It updates status of the times of policy reloaded, and current + * setting of deny_unknown. + */ +void selinux_status_update_policyload(struct selinux_state *state, + int seqno) +{ + struct selinux_kernel_status *status; + + mutex_lock(&state->status_lock); + if (state->status_page) { + status = page_address(state->status_page); + + status->sequence++; + smp_wmb(); + + status->policyload = seqno; + status->deny_unknown = !security_get_allow_unknown(state); + + smp_wmb(); + status->sequence++; + } + mutex_unlock(&state->status_lock); +} diff --git a/security/selinux/xfrm.c b/security/selinux/xfrm.c new file mode 100644 index 000000000..114245b6f --- /dev/null +++ b/security/selinux/xfrm.c @@ -0,0 +1,473 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * NSA Security-Enhanced Linux (SELinux) security module + * + * This file contains the SELinux XFRM hook function implementations. + * + * Authors: Serge Hallyn <sergeh@us.ibm.com> + * Trent Jaeger <jaegert@us.ibm.com> + * + * Updated: Venkat Yekkirala <vyekkirala@TrustedCS.com> + * + * Granular IPSec Associations for use in MLS environments. + * + * Copyright (C) 2005 International Business Machines Corporation + * Copyright (C) 2006 Trusted Computer Solutions, Inc. + */ + +/* + * USAGE: + * NOTES: + * 1. Make sure to enable the following options in your kernel config: + * CONFIG_SECURITY=y + * CONFIG_SECURITY_NETWORK=y + * CONFIG_SECURITY_NETWORK_XFRM=y + * CONFIG_SECURITY_SELINUX=m/y + * ISSUES: + * 1. Caching packets, so they are not dropped during negotiation + * 2. Emulating a reasonable SO_PEERSEC across machines + * 3. Testing addition of sk_policy's with security context via setsockopt + */ +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/security.h> +#include <linux/types.h> +#include <linux/slab.h> +#include <linux/ip.h> +#include <linux/tcp.h> +#include <linux/skbuff.h> +#include <linux/xfrm.h> +#include <net/xfrm.h> +#include <net/checksum.h> +#include <net/udp.h> +#include <linux/atomic.h> + +#include "avc.h" +#include "objsec.h" +#include "xfrm.h" + +/* Labeled XFRM instance counter */ +atomic_t selinux_xfrm_refcount = ATOMIC_INIT(0); + +/* + * Returns true if the context is an LSM/SELinux context. + */ +static inline int selinux_authorizable_ctx(struct xfrm_sec_ctx *ctx) +{ + return (ctx && + (ctx->ctx_doi == XFRM_SC_DOI_LSM) && + (ctx->ctx_alg == XFRM_SC_ALG_SELINUX)); +} + +/* + * Returns true if the xfrm contains a security blob for SELinux. + */ +static inline int selinux_authorizable_xfrm(struct xfrm_state *x) +{ + return selinux_authorizable_ctx(x->security); +} + +/* + * Allocates a xfrm_sec_state and populates it using the supplied security + * xfrm_user_sec_ctx context. + */ +static int selinux_xfrm_alloc_user(struct xfrm_sec_ctx **ctxp, + struct xfrm_user_sec_ctx *uctx, + gfp_t gfp) +{ + int rc; + const struct task_security_struct *tsec = selinux_cred(current_cred()); + struct xfrm_sec_ctx *ctx = NULL; + u32 str_len; + + if (ctxp == NULL || uctx == NULL || + uctx->ctx_doi != XFRM_SC_DOI_LSM || + uctx->ctx_alg != XFRM_SC_ALG_SELINUX) + return -EINVAL; + + str_len = uctx->ctx_len; + if (str_len >= PAGE_SIZE) + return -ENOMEM; + + ctx = kmalloc(sizeof(*ctx) + str_len + 1, gfp); + if (!ctx) + return -ENOMEM; + + ctx->ctx_doi = XFRM_SC_DOI_LSM; + ctx->ctx_alg = XFRM_SC_ALG_SELINUX; + ctx->ctx_len = str_len; + memcpy(ctx->ctx_str, &uctx[1], str_len); + ctx->ctx_str[str_len] = '\0'; + rc = security_context_to_sid(&selinux_state, ctx->ctx_str, str_len, + &ctx->ctx_sid, gfp); + if (rc) + goto err; + + rc = avc_has_perm(&selinux_state, + tsec->sid, ctx->ctx_sid, + SECCLASS_ASSOCIATION, ASSOCIATION__SETCONTEXT, NULL); + if (rc) + goto err; + + *ctxp = ctx; + atomic_inc(&selinux_xfrm_refcount); + return 0; + +err: + kfree(ctx); + return rc; +} + +/* + * Free the xfrm_sec_ctx structure. + */ +static void selinux_xfrm_free(struct xfrm_sec_ctx *ctx) +{ + if (!ctx) + return; + + atomic_dec(&selinux_xfrm_refcount); + kfree(ctx); +} + +/* + * Authorize the deletion of a labeled SA or policy rule. + */ +static int selinux_xfrm_delete(struct xfrm_sec_ctx *ctx) +{ + const struct task_security_struct *tsec = selinux_cred(current_cred()); + + if (!ctx) + return 0; + + return avc_has_perm(&selinux_state, + tsec->sid, ctx->ctx_sid, + SECCLASS_ASSOCIATION, ASSOCIATION__SETCONTEXT, + NULL); +} + +/* + * LSM hook implementation that authorizes that a flow can use a xfrm policy + * rule. + */ +int selinux_xfrm_policy_lookup(struct xfrm_sec_ctx *ctx, u32 fl_secid, u8 dir) +{ + int rc; + + /* All flows should be treated as polmatch'ing an otherwise applicable + * "non-labeled" policy. This would prevent inadvertent "leaks". */ + if (!ctx) + return 0; + + /* Context sid is either set to label or ANY_ASSOC */ + if (!selinux_authorizable_ctx(ctx)) + return -EINVAL; + + rc = avc_has_perm(&selinux_state, + fl_secid, ctx->ctx_sid, + SECCLASS_ASSOCIATION, ASSOCIATION__POLMATCH, NULL); + return (rc == -EACCES ? -ESRCH : rc); +} + +/* + * LSM hook implementation that authorizes that a state matches + * the given policy, flow combo. + */ +int selinux_xfrm_state_pol_flow_match(struct xfrm_state *x, + struct xfrm_policy *xp, + const struct flowi_common *flic) +{ + u32 state_sid; + u32 flic_sid; + + if (!xp->security) + if (x->security) + /* unlabeled policy and labeled SA can't match */ + return 0; + else + /* unlabeled policy and unlabeled SA match all flows */ + return 1; + else + if (!x->security) + /* unlabeled SA and labeled policy can't match */ + return 0; + else + if (!selinux_authorizable_xfrm(x)) + /* Not a SELinux-labeled SA */ + return 0; + + state_sid = x->security->ctx_sid; + flic_sid = flic->flowic_secid; + + if (flic_sid != state_sid) + return 0; + + /* We don't need a separate SA Vs. policy polmatch check since the SA + * is now of the same label as the flow and a flow Vs. policy polmatch + * check had already happened in selinux_xfrm_policy_lookup() above. */ + return (avc_has_perm(&selinux_state, flic_sid, state_sid, + SECCLASS_ASSOCIATION, ASSOCIATION__SENDTO, + NULL) ? 0 : 1); +} + +static u32 selinux_xfrm_skb_sid_egress(struct sk_buff *skb) +{ + struct dst_entry *dst = skb_dst(skb); + struct xfrm_state *x; + + if (dst == NULL) + return SECSID_NULL; + x = dst->xfrm; + if (x == NULL || !selinux_authorizable_xfrm(x)) + return SECSID_NULL; + + return x->security->ctx_sid; +} + +static int selinux_xfrm_skb_sid_ingress(struct sk_buff *skb, + u32 *sid, int ckall) +{ + u32 sid_session = SECSID_NULL; + struct sec_path *sp = skb_sec_path(skb); + + if (sp) { + int i; + + for (i = sp->len - 1; i >= 0; i--) { + struct xfrm_state *x = sp->xvec[i]; + if (selinux_authorizable_xfrm(x)) { + struct xfrm_sec_ctx *ctx = x->security; + + if (sid_session == SECSID_NULL) { + sid_session = ctx->ctx_sid; + if (!ckall) + goto out; + } else if (sid_session != ctx->ctx_sid) { + *sid = SECSID_NULL; + return -EINVAL; + } + } + } + } + +out: + *sid = sid_session; + return 0; +} + +/* + * LSM hook implementation that checks and/or returns the xfrm sid for the + * incoming packet. + */ +int selinux_xfrm_decode_session(struct sk_buff *skb, u32 *sid, int ckall) +{ + if (skb == NULL) { + *sid = SECSID_NULL; + return 0; + } + return selinux_xfrm_skb_sid_ingress(skb, sid, ckall); +} + +int selinux_xfrm_skb_sid(struct sk_buff *skb, u32 *sid) +{ + int rc; + + rc = selinux_xfrm_skb_sid_ingress(skb, sid, 0); + if (rc == 0 && *sid == SECSID_NULL) + *sid = selinux_xfrm_skb_sid_egress(skb); + + return rc; +} + +/* + * LSM hook implementation that allocs and transfers uctx spec to xfrm_policy. + */ +int selinux_xfrm_policy_alloc(struct xfrm_sec_ctx **ctxp, + struct xfrm_user_sec_ctx *uctx, + gfp_t gfp) +{ + return selinux_xfrm_alloc_user(ctxp, uctx, gfp); +} + +/* + * LSM hook implementation that copies security data structure from old to new + * for policy cloning. + */ +int selinux_xfrm_policy_clone(struct xfrm_sec_ctx *old_ctx, + struct xfrm_sec_ctx **new_ctxp) +{ + struct xfrm_sec_ctx *new_ctx; + + if (!old_ctx) + return 0; + + new_ctx = kmemdup(old_ctx, sizeof(*old_ctx) + old_ctx->ctx_len, + GFP_ATOMIC); + if (!new_ctx) + return -ENOMEM; + atomic_inc(&selinux_xfrm_refcount); + *new_ctxp = new_ctx; + + return 0; +} + +/* + * LSM hook implementation that frees xfrm_sec_ctx security information. + */ +void selinux_xfrm_policy_free(struct xfrm_sec_ctx *ctx) +{ + selinux_xfrm_free(ctx); +} + +/* + * LSM hook implementation that authorizes deletion of labeled policies. + */ +int selinux_xfrm_policy_delete(struct xfrm_sec_ctx *ctx) +{ + return selinux_xfrm_delete(ctx); +} + +/* + * LSM hook implementation that allocates a xfrm_sec_state, populates it using + * the supplied security context, and assigns it to the xfrm_state. + */ +int selinux_xfrm_state_alloc(struct xfrm_state *x, + struct xfrm_user_sec_ctx *uctx) +{ + return selinux_xfrm_alloc_user(&x->security, uctx, GFP_KERNEL); +} + +/* + * LSM hook implementation that allocates a xfrm_sec_state and populates based + * on a secid. + */ +int selinux_xfrm_state_alloc_acquire(struct xfrm_state *x, + struct xfrm_sec_ctx *polsec, u32 secid) +{ + int rc; + struct xfrm_sec_ctx *ctx; + char *ctx_str = NULL; + u32 str_len; + + if (!polsec) + return 0; + + if (secid == 0) + return -EINVAL; + + rc = security_sid_to_context(&selinux_state, secid, &ctx_str, + &str_len); + if (rc) + return rc; + + ctx = kmalloc(sizeof(*ctx) + str_len, GFP_ATOMIC); + if (!ctx) { + rc = -ENOMEM; + goto out; + } + + ctx->ctx_doi = XFRM_SC_DOI_LSM; + ctx->ctx_alg = XFRM_SC_ALG_SELINUX; + ctx->ctx_sid = secid; + ctx->ctx_len = str_len; + memcpy(ctx->ctx_str, ctx_str, str_len); + + x->security = ctx; + atomic_inc(&selinux_xfrm_refcount); +out: + kfree(ctx_str); + return rc; +} + +/* + * LSM hook implementation that frees xfrm_state security information. + */ +void selinux_xfrm_state_free(struct xfrm_state *x) +{ + selinux_xfrm_free(x->security); +} + +/* + * LSM hook implementation that authorizes deletion of labeled SAs. + */ +int selinux_xfrm_state_delete(struct xfrm_state *x) +{ + return selinux_xfrm_delete(x->security); +} + +/* + * LSM hook that controls access to unlabelled packets. If + * a xfrm_state is authorizable (defined by macro) then it was + * already authorized by the IPSec process. If not, then + * we need to check for unlabelled access since this may not have + * gone thru the IPSec process. + */ +int selinux_xfrm_sock_rcv_skb(u32 sk_sid, struct sk_buff *skb, + struct common_audit_data *ad) +{ + int i; + struct sec_path *sp = skb_sec_path(skb); + u32 peer_sid = SECINITSID_UNLABELED; + + if (sp) { + for (i = 0; i < sp->len; i++) { + struct xfrm_state *x = sp->xvec[i]; + + if (x && selinux_authorizable_xfrm(x)) { + struct xfrm_sec_ctx *ctx = x->security; + peer_sid = ctx->ctx_sid; + break; + } + } + } + + /* This check even when there's no association involved is intended, + * according to Trent Jaeger, to make sure a process can't engage in + * non-IPsec communication unless explicitly allowed by policy. */ + return avc_has_perm(&selinux_state, + sk_sid, peer_sid, + SECCLASS_ASSOCIATION, ASSOCIATION__RECVFROM, ad); +} + +/* + * POSTROUTE_LAST hook's XFRM processing: + * If we have no security association, then we need to determine + * whether the socket is allowed to send to an unlabelled destination. + * If we do have a authorizable security association, then it has already been + * checked in the selinux_xfrm_state_pol_flow_match hook above. + */ +int selinux_xfrm_postroute_last(u32 sk_sid, struct sk_buff *skb, + struct common_audit_data *ad, u8 proto) +{ + struct dst_entry *dst; + + switch (proto) { + case IPPROTO_AH: + case IPPROTO_ESP: + case IPPROTO_COMP: + /* We should have already seen this packet once before it + * underwent xfrm(s). No need to subject it to the unlabeled + * check. */ + return 0; + default: + break; + } + + dst = skb_dst(skb); + if (dst) { + struct dst_entry *iter; + + for (iter = dst; iter != NULL; iter = xfrm_dst_child(iter)) { + struct xfrm_state *x = iter->xfrm; + + if (x && selinux_authorizable_xfrm(x)) + return 0; + } + } + + /* This check even when there's no association involved is intended, + * according to Trent Jaeger, to make sure a process can't engage in + * non-IPsec communication unless explicitly allowed by policy. */ + return avc_has_perm(&selinux_state, sk_sid, SECINITSID_UNLABELED, + SECCLASS_ASSOCIATION, ASSOCIATION__SENDTO, ad); +} |