diff options
Diffstat (limited to 'security/selinux/avc.c')
| -rw-r--r-- | security/selinux/avc.c | 404 |
1 files changed, 195 insertions, 209 deletions
diff --git a/security/selinux/avc.c b/security/selinux/avc.c index 9a43af0ebd7d..8f77b9a732e1 100644 --- a/security/selinux/avc.c +++ b/security/selinux/avc.c @@ -2,7 +2,7 @@ /* * Implementation of the kernel access vector cache (AVC). * - * Authors: Stephen Smalley, <sds@tycho.nsa.gov> + * Authors: Stephen Smalley, <stephen.smalley.work@gmail.com> * James Morris <jmorris@redhat.com> * * Update: KaiGai, Kohei <kaigai@ak.jp.nec.com> @@ -30,13 +30,14 @@ #include "avc.h" #include "avc_ss.h" #include "classmap.h" +#include "hash.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 +#define AVC_CACHE_SLOTS (1 << CONFIG_SECURITY_SELINUX_AVC_HASH_BITS) +#define AVC_DEF_CACHE_THRESHOLD AVC_CACHE_SLOTS +#define AVC_CACHE_RECLAIM 16 #ifdef CONFIG_SECURITY_SELINUX_AVC_STATS #define avc_cache_stats_incr(field) this_cpu_inc(avc_cache_stats.field) @@ -93,7 +94,7 @@ struct selinux_avc { static struct selinux_avc selinux_avc; -void selinux_avc_init(struct selinux_avc **avc) +void selinux_avc_init(void) { int i; @@ -104,18 +105,16 @@ void selinux_avc_init(struct selinux_avc **avc) } 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) +unsigned int avc_get_cache_threshold(void) { - return avc->avc_cache_threshold; + return selinux_avc.avc_cache_threshold; } -void avc_set_cache_threshold(struct selinux_avc *avc, - unsigned int cache_threshold) +void avc_set_cache_threshold(unsigned int cache_threshold) { - avc->avc_cache_threshold = cache_threshold; + selinux_avc.avc_cache_threshold = cache_threshold; } static struct avc_callback_node *avc_callbacks __ro_after_init; @@ -124,9 +123,9 @@ static struct kmem_cache *avc_xperms_data_cachep __ro_after_init; static struct kmem_cache *avc_xperms_decision_cachep __ro_after_init; static struct kmem_cache *avc_xperms_cachep __ro_after_init; -static inline int avc_hash(u32 ssid, u32 tsid, u16 tclass) +static inline u32 avc_hash(u32 ssid, u32 tsid, u16 tclass) { - return (ssid ^ (tsid<<2) ^ (tclass<<4)) & (AVC_CACHE_SLOTS - 1); + return av_hash(ssid, tsid, (u32)tclass, (u32)(AVC_CACHE_SLOTS - 1)); } /** @@ -136,21 +135,13 @@ static inline int avc_hash(u32 ssid, u32 tsid, u16 tclass) */ 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); + avc_node_cachep = KMEM_CACHE(avc_node, SLAB_PANIC); + avc_xperms_cachep = KMEM_CACHE(avc_xperms_node, SLAB_PANIC); + avc_xperms_decision_cachep = KMEM_CACHE(avc_xperms_decision_node, SLAB_PANIC); + avc_xperms_data_cachep = KMEM_CACHE(extended_perms_data, SLAB_PANIC); } -int avc_get_hash_stats(struct selinux_avc *avc, char *page) +int avc_get_hash_stats(char *page) { int i, chain_len, max_chain_len, slots_used; struct avc_node *node; @@ -161,7 +152,7 @@ int avc_get_hash_stats(struct selinux_avc *avc, char *page) slots_used = 0; max_chain_len = 0; for (i = 0; i < AVC_CACHE_SLOTS; i++) { - head = &avc->avc_cache.slots[i]; + head = &selinux_avc.avc_cache.slots[i]; if (!hlist_empty(head)) { slots_used++; chain_len = 0; @@ -176,7 +167,7 @@ int avc_get_hash_stats(struct selinux_avc *avc, char *page) return scnprintf(page, PAGE_SIZE, "entries: %d\nbuckets used: %d/%d\n" "longest chain: %d\n", - atomic_read(&avc->avc_cache.active_nodes), + atomic_read(&selinux_avc.avc_cache.active_nodes), slots_used, AVC_CACHE_SLOTS, max_chain_len); } @@ -184,13 +175,15 @@ int avc_get_hash_stats(struct selinux_avc *avc, char *page) * 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) +static struct extended_perms_decision * +avc_xperms_decision_lookup(u8 driver, u8 base_perm, + 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) + if (xpd_node->xpd.driver == driver && + xpd_node->xpd.base_perm == base_perm) return &xpd_node->xpd; } return NULL; @@ -215,11 +208,12 @@ avc_xperms_has_perm(struct extended_perms_decision *xpd, } static void avc_xperms_allow_perm(struct avc_xperms_node *xp_node, - u8 driver, u8 perm) + u8 driver, u8 base_perm, u8 perm) { struct extended_perms_decision *xpd; security_xperm_set(xp_node->xp.drivers.p, driver); - xpd = avc_xperms_decision_lookup(driver, xp_node); + xp_node->xp.base_perms |= base_perm; + xpd = avc_xperms_decision_lookup(driver, base_perm, xp_node); if (xpd && xpd->allowed) security_xperm_set(xpd->allowed->p, perm); } @@ -255,6 +249,7 @@ static void avc_xperms_free(struct avc_xperms_node *xp_node) static void avc_copy_xperms_decision(struct extended_perms_decision *dest, struct extended_perms_decision *src) { + dest->base_perm = src->base_perm; dest->driver = src->driver; dest->used = src->used; if (dest->used & XPERMS_ALLOWED) @@ -282,6 +277,7 @@ static inline void avc_quick_copy_xperms_decision(u8 perm, */ u8 i = perm >> 5; + dest->base_perm = src->base_perm; dest->used = src->used; if (dest->used & XPERMS_ALLOWED) dest->allowed->p[i] = src->allowed->p[i]; @@ -297,27 +293,26 @@ static struct avc_xperms_decision_node 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); + xpd_node = kmem_cache_zalloc(avc_xperms_decision_cachep, GFP_NOWAIT); 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); + GFP_NOWAIT); if (!xpd->allowed) goto error; } if (which & XPERMS_AUDITALLOW) { xpd->auditallow = kmem_cache_zalloc(avc_xperms_data_cachep, - GFP_NOWAIT | __GFP_NOWARN); + GFP_NOWAIT); if (!xpd->auditallow) goto error; } if (which & XPERMS_DONTAUDIT) { xpd->dontaudit = kmem_cache_zalloc(avc_xperms_data_cachep, - GFP_NOWAIT | __GFP_NOWARN); + GFP_NOWAIT); if (!xpd->dontaudit) goto error; } @@ -332,12 +327,12 @@ static int avc_add_xperms_decision(struct avc_node *node, { 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); + node->ae.xp_node->xp.len++; return 0; } @@ -345,7 +340,7 @@ 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); + xp_node = kmem_cache_zalloc(avc_xperms_cachep, GFP_NOWAIT); if (!xp_node) return xp_node; INIT_LIST_HEAD(&xp_node->xpd_head); @@ -367,6 +362,7 @@ static int avc_xperms_populate(struct avc_node *node, memcpy(dest->xp.drivers.p, src->xp.drivers.p, sizeof(dest->xp.drivers.p)); dest->xp.len = src->xp.len; + dest->xp.base_perms = src->xp.base_perms; /* for each source xpd allocate a destination xpd and copy */ list_for_each_entry(src_xpd, &src->xpd_head, xpd_list) { @@ -398,7 +394,7 @@ static inline u32 avc_xperms_audit_required(u32 requested, audited = denied & avd->auditdeny; if (audited && xpd) { if (avc_xperms_has_perm(xpd, perm, XPERMS_DONTAUDIT)) - audited &= ~requested; + audited = 0; } } else if (result) { audited = denied = requested; @@ -406,7 +402,7 @@ static inline u32 avc_xperms_audit_required(u32 requested, audited = requested & avd->auditallow; if (audited && xpd) { if (!avc_xperms_has_perm(xpd, perm, XPERMS_AUDITALLOW)) - audited &= ~requested; + audited = 0; } } @@ -414,8 +410,7 @@ static inline u32 avc_xperms_audit_required(u32 requested, return audited; } -static inline int avc_xperms_audit(struct selinux_state *state, - u32 ssid, u32 tsid, u16 tclass, +static inline int avc_xperms_audit(u32 ssid, u32 tsid, u16 tclass, u32 requested, struct av_decision *avd, struct extended_perms_decision *xpd, u8 perm, int result, @@ -427,7 +422,7 @@ static inline int avc_xperms_audit(struct selinux_state *state, requested, avd, xpd, perm, result, &denied); if (likely(!audited)) return 0; - return slow_avc_audit(state, ssid, tsid, tclass, requested, + return slow_avc_audit(ssid, tsid, tclass, requested, audited, denied, result, ad); } @@ -439,30 +434,29 @@ static void avc_node_free(struct rcu_head *rhead) avc_cache_stats_incr(frees); } -static void avc_node_delete(struct selinux_avc *avc, struct avc_node *node) +static void avc_node_delete(struct avc_node *node) { hlist_del_rcu(&node->list); call_rcu(&node->rhead, avc_node_free); - atomic_dec(&avc->avc_cache.active_nodes); + atomic_dec(&selinux_avc.avc_cache.active_nodes); } -static void avc_node_kill(struct selinux_avc *avc, struct avc_node *node) +static void avc_node_kill(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); + atomic_dec(&selinux_avc.avc_cache.active_nodes); } -static void avc_node_replace(struct selinux_avc *avc, - struct avc_node *new, struct avc_node *old) +static void avc_node_replace(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); + atomic_dec(&selinux_avc.avc_cache.active_nodes); } -static inline int avc_reclaim_node(struct selinux_avc *avc) +static inline int avc_reclaim_node(void) { struct avc_node *node; int hvalue, try, ecx; @@ -471,17 +465,17 @@ static inline int avc_reclaim_node(struct selinux_avc *avc) spinlock_t *lock; for (try = 0, ecx = 0; try < AVC_CACHE_SLOTS; try++) { - hvalue = atomic_inc_return(&avc->avc_cache.lru_hint) & + hvalue = atomic_inc_return(&selinux_avc.avc_cache.lru_hint) & (AVC_CACHE_SLOTS - 1); - head = &avc->avc_cache.slots[hvalue]; - lock = &avc->avc_cache.slots_lock[hvalue]; + head = &selinux_avc.avc_cache.slots[hvalue]; + lock = &selinux_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_node_delete(node); avc_cache_stats_incr(reclaims); ecx++; if (ecx >= AVC_CACHE_RECLAIM) { @@ -497,20 +491,20 @@ out: return ecx; } -static struct avc_node *avc_alloc_node(struct selinux_avc *avc) +static struct avc_node *avc_alloc_node(void) { struct avc_node *node; - node = kmem_cache_zalloc(avc_node_cachep, GFP_NOWAIT | __GFP_NOWARN); + node = kmem_cache_zalloc(avc_node_cachep, GFP_NOWAIT); 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); + if (atomic_inc_return(&selinux_avc.avc_cache.active_nodes) > + selinux_avc.avc_cache_threshold) + avc_reclaim_node(); out: return node; @@ -524,15 +518,14 @@ static void avc_node_populate(struct avc_node *node, u32 ssid, u32 tsid, u16 tcl 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) +static inline struct avc_node *avc_search_node(u32 ssid, u32 tsid, u16 tclass) { struct avc_node *node, *ret = NULL; - int hvalue; + u32 hvalue; struct hlist_head *head; hvalue = avc_hash(ssid, tsid, tclass); - head = &avc->avc_cache.slots[hvalue]; + head = &selinux_avc.avc_cache.slots[hvalue]; hlist_for_each_entry_rcu(node, head, list) { if (ssid == node->ae.ssid && tclass == node->ae.tclass && @@ -547,7 +540,6 @@ static inline struct avc_node *avc_search_node(struct selinux_avc *avc, /** * avc_lookup - Look up an AVC entry. - * @avc: the access vector cache * @ssid: source security identifier * @tsid: target security identifier * @tclass: target security class @@ -558,13 +550,12 @@ static inline struct avc_node *avc_search_node(struct selinux_avc *avc, * 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) +static struct avc_node *avc_lookup(u32 ssid, u32 tsid, u16 tclass) { struct avc_node *node; avc_cache_stats_incr(lookups); - node = avc_search_node(avc, ssid, tsid, tclass); + node = avc_search_node(ssid, tsid, tclass); if (node) return node; @@ -573,8 +564,7 @@ static struct avc_node *avc_lookup(struct selinux_avc *avc, return NULL; } -static int avc_latest_notif_update(struct selinux_avc *avc, - int seqno, int is_insert) +static int avc_latest_notif_update(u32 seqno, int is_insert) { int ret = 0; static DEFINE_SPINLOCK(notif_lock); @@ -582,14 +572,14 @@ static int avc_latest_notif_update(struct selinux_avc *avc, spin_lock_irqsave(¬if_lock, flag); if (is_insert) { - if (seqno < avc->avc_cache.latest_notif) { + if (seqno < selinux_avc.avc_cache.latest_notif) { pr_warn("SELinux: avc: seqno %d < latest_notif %d\n", - seqno, avc->avc_cache.latest_notif); + seqno, selinux_avc.avc_cache.latest_notif); ret = -EAGAIN; } } else { - if (seqno > avc->avc_cache.latest_notif) - avc->avc_cache.latest_notif = seqno; + if (seqno > selinux_avc.avc_cache.latest_notif) + selinux_avc.avc_cache.latest_notif = seqno; } spin_unlock_irqrestore(¬if_lock, flag); @@ -598,7 +588,6 @@ static int avc_latest_notif_update(struct selinux_avc *avc, /** * avc_insert - Insert an AVC entry. - * @avc: the access vector cache * @ssid: source security identifier * @tsid: target security identifier * @tclass: target security class @@ -612,49 +601,45 @@ static int avc_latest_notif_update(struct selinux_avc *avc, * 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. + * the access vectors into a cache entry. */ -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) +static void avc_insert(u32 ssid, u32 tsid, u16 tclass, + struct av_decision *avd, struct avc_xperms_node *xp_node) { struct avc_node *pos, *node = NULL; - int hvalue; + u32 hvalue; unsigned long flag; spinlock_t *lock; struct hlist_head *head; - if (avc_latest_notif_update(avc, avd->seqno, 1)) - return NULL; + if (avc_latest_notif_update(avd->seqno, 1)) + return; - node = avc_alloc_node(avc); + node = avc_alloc_node(); if (!node) - return NULL; + return; avc_node_populate(node, ssid, tsid, tclass, avd); if (avc_xperms_populate(node, xp_node)) { - avc_node_kill(avc, node); - return NULL; + avc_node_kill(node); + return; } hvalue = avc_hash(ssid, tsid, tclass); - head = &avc->avc_cache.slots[hvalue]; - lock = &avc->avc_cache.slots_lock[hvalue]; + head = &selinux_avc.avc_cache.slots[hvalue]; + lock = &selinux_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); + avc_node_replace(node, pos); goto found; } } hlist_add_head_rcu(&node->list, head); found: spin_unlock_irqrestore(lock, flag); - return node; } /** @@ -667,9 +652,9 @@ 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; + u32 av = sad->audited, perm; const char *const *perms; - int i, perm; + u32 i; audit_log_format(ab, "avc: %s ", sad->denied ? "denied" : "granted"); @@ -715,14 +700,14 @@ static void avc_audit_post_callback(struct audit_buffer *ab, void *a) u32 tcontext_len; int rc; - rc = security_sid_to_context(sad->state, sad->ssid, &scontext, + rc = security_sid_to_context(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, + rc = security_sid_to_context(sad->tsid, &tcontext, &tcontext_len); if (rc) audit_log_format(ab, " tsid=%d", sad->tsid); @@ -740,7 +725,7 @@ static void avc_audit_post_callback(struct audit_buffer *ab, void *a) kfree(scontext); /* in case of invalid context report also the actual context string */ - rc = security_sid_to_context_inval(sad->state, sad->ssid, &scontext, + rc = security_sid_to_context_inval(sad->ssid, &scontext, &scontext_len); if (!rc && scontext) { if (scontext_len && scontext[scontext_len - 1] == '\0') @@ -750,7 +735,7 @@ static void avc_audit_post_callback(struct audit_buffer *ab, void *a) kfree(scontext); } - rc = security_sid_to_context_inval(sad->state, sad->tsid, &scontext, + rc = security_sid_to_context_inval(sad->tsid, &scontext, &scontext_len); if (!rc && scontext) { if (scontext_len && scontext[scontext_len - 1] == '\0') @@ -766,8 +751,7 @@ static void avc_audit_post_callback(struct audit_buffer *ab, void *a) * Note that it is non-blocking and can be called from under * rcu_read_lock(). */ -noinline int slow_avc_audit(struct selinux_state *state, - u32 ssid, u32 tsid, u16 tclass, +noinline int slow_avc_audit(u32 ssid, u32 tsid, u16 tclass, u32 requested, u32 audited, u32 denied, int result, struct common_audit_data *a) { @@ -789,7 +773,6 @@ noinline int slow_avc_audit(struct selinux_state *state, sad.audited = audited; sad.denied = denied; sad.result = result; - sad.state = state; a->selinux_audit_data = &sad; @@ -827,10 +810,10 @@ out: /** * avc_update_node - Update an AVC entry - * @avc: the access vector cache * @event : Updating event * @perms : Permission mask bits * @driver: xperm driver information + * @base_perm: the base permission associated with the extended permission * @xperm: xperm permissions * @ssid: AVC entry source sid * @tsid: AVC entry target sid @@ -844,19 +827,18 @@ out: * 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) +static int avc_update_node(u32 event, u32 perms, u8 driver, u8 base_perm, + u8 xperm, u32 ssid, u32 tsid, u16 tclass, u32 seqno, + struct extended_perms_decision *xpd, u32 flags) { - int hvalue, rc = 0; + u32 hvalue; + int rc = 0; unsigned long flag; struct avc_node *pos, *node, *orig = NULL; struct hlist_head *head; spinlock_t *lock; - node = avc_alloc_node(avc); + node = avc_alloc_node(); if (!node) { rc = -ENOMEM; goto out; @@ -865,8 +847,8 @@ static int avc_update_node(struct selinux_avc *avc, /* Lock the target slot */ hvalue = avc_hash(ssid, tsid, tclass); - head = &avc->avc_cache.slots[hvalue]; - lock = &avc->avc_cache.slots_lock[hvalue]; + head = &selinux_avc.avc_cache.slots[hvalue]; + lock = &selinux_avc.avc_cache.slots_lock[hvalue]; spin_lock_irqsave(lock, flag); @@ -882,7 +864,7 @@ static int avc_update_node(struct selinux_avc *avc, if (!orig) { rc = -ENOENT; - avc_node_kill(avc, node); + avc_node_kill(node); goto out_unlock; } @@ -895,7 +877,7 @@ static int avc_update_node(struct selinux_avc *avc, if (orig->ae.xp_node) { rc = avc_xperms_populate(node, orig->ae.xp_node); if (rc) { - avc_node_kill(avc, node); + avc_node_kill(node); goto out_unlock; } } @@ -904,7 +886,7 @@ static int avc_update_node(struct selinux_avc *avc, 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); + avc_xperms_allow_perm(node->ae.xp_node, driver, base_perm, xperm); break; case AVC_CALLBACK_TRY_REVOKE: case AVC_CALLBACK_REVOKE: @@ -923,10 +905,14 @@ static int avc_update_node(struct selinux_avc *avc, node->ae.avd.auditdeny &= ~perms; break; case AVC_CALLBACK_ADD_XPERMS: - avc_add_xperms_decision(node, xpd); + rc = avc_add_xperms_decision(node, xpd); + if (rc) { + avc_node_kill(node); + goto out_unlock; + } break; } - avc_node_replace(avc, node, orig); + avc_node_replace(node, orig); out_unlock: spin_unlock_irqrestore(lock, flag); out: @@ -935,9 +921,8 @@ out: /** * avc_flush - Flush the cache - * @avc: the access vector cache */ -static void avc_flush(struct selinux_avc *avc) +static void avc_flush(void) { struct hlist_head *head; struct avc_node *node; @@ -946,17 +931,17 @@ static void avc_flush(struct selinux_avc *avc) int i; for (i = 0; i < AVC_CACHE_SLOTS; i++) { - head = &avc->avc_cache.slots[i]; - lock = &avc->avc_cache.slots_lock[i]; + head = &selinux_avc.avc_cache.slots[i]; + lock = &selinux_avc.avc_cache.slots_lock[i]; spin_lock_irqsave(lock, flag); /* - * With preemptable RCU, the outer spinlock does not + * With preemptible 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); + avc_node_delete(node); rcu_read_unlock(); spin_unlock_irqrestore(lock, flag); } @@ -964,15 +949,14 @@ static void avc_flush(struct selinux_avc *avc) /** * avc_ss_reset - Flush the cache and revalidate migrated permissions. - * @avc: the access vector cache * @seqno: policy sequence number */ -int avc_ss_reset(struct selinux_avc *avc, u32 seqno) +int avc_ss_reset(u32 seqno) { struct avc_callback_node *c; int rc = 0, tmprc; - avc_flush(avc); + avc_flush(); for (c = avc_callbacks; c; c = c->next) { if (c->events & AVC_CALLBACK_RESET) { @@ -984,46 +968,43 @@ int avc_ss_reset(struct selinux_avc *avc, u32 seqno) } } - avc_latest_notif_update(avc, seqno, 0); + avc_latest_notif_update(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. +/** + * avc_compute_av - Add an entry to the AVC based on the security policy + * @ssid: subject + * @tsid: object/target + * @tclass: object class + * @avd: access vector decision + * @xp_node: AVC extended permissions node * - * Don't inline this, since it's the slow-path and just - * results in a bigger stack frame. + * Slow-path helper function for avc_has_perm_noaudit, when the avc_node lookup + * fails. 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) +static noinline void avc_compute_av(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); + security_compute_av(ssid, tsid, tclass, avd, &xp_node->xp); + avc_insert(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) +static noinline int avc_denied(u32 ssid, u32 tsid, u16 tclass, u32 requested, + u8 driver, u8 base_perm, u8 xperm, + unsigned int flags, struct av_decision *avd) { if (flags & AVC_STRICT) return -EACCES; - if (enforcing_enabled(state) && + if (enforcing_enabled() && !(avd->flags & AVD_FLAGS_PERMISSIVE)) return -EACCES; - avc_update_node(state->avc, AVC_CALLBACK_GRANT, requested, driver, + avc_update_node(AVC_CALLBACK_GRANT, requested, driver, base_perm, xperm, ssid, tsid, tclass, avd->seqno, NULL, flags); return 0; } @@ -1035,9 +1016,9 @@ static noinline int avc_denied(struct selinux_state *state, * 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) +int avc_has_extended_perms(u32 ssid, u32 tsid, u16 tclass, u32 requested, + u8 driver, u8 base_perm, u8 xperm, + struct common_audit_data *ad) { struct avc_node *node; struct av_decision avd; @@ -1057,9 +1038,9 @@ int avc_has_extended_perms(struct selinux_state *state, rcu_read_lock(); - node = avc_lookup(state->avc, ssid, tsid, tclass); + node = avc_lookup(ssid, tsid, tclass); if (unlikely(!node)) { - avc_compute_av(state, ssid, tsid, tclass, &avd, xp_node); + avc_compute_av(ssid, tsid, tclass, &avd, xp_node); } else { memcpy(&avd, &node->ae.avd, sizeof(avd)); xp_node = node->ae.xp_node; @@ -1072,22 +1053,23 @@ int avc_has_extended_perms(struct selinux_state *state, local_xpd.auditallow = &auditallow; local_xpd.dontaudit = &dontaudit; - xpd = avc_xperms_decision_lookup(driver, xp_node); + xpd = avc_xperms_decision_lookup(driver, base_perm, xp_node); if (unlikely(!xpd)) { /* * Compute the extended_perms_decision only if the driver - * is flagged + * is flagged and the base permission is known. */ - if (!security_xperm_test(xp_node->xp.drivers.p, driver)) { + if (!security_xperm_test(xp_node->xp.drivers.p, driver) || + !(xp_node->xp.base_perms & base_perm)) { avd.allowed &= ~requested; goto decision; } rcu_read_unlock(); - security_compute_xperms_decision(state, ssid, tsid, tclass, - driver, &local_xpd); + security_compute_xperms_decision(ssid, tsid, tclass, driver, + base_perm, &local_xpd); rcu_read_lock(); - avc_update_node(state->avc, AVC_CALLBACK_ADD_XPERMS, requested, - driver, xperm, ssid, tsid, tclass, avd.seqno, + avc_update_node(AVC_CALLBACK_ADD_XPERMS, requested, driver, + base_perm, xperm, ssid, tsid, tclass, avd.seqno, &local_xpd, 0); } else { avc_quick_copy_xperms_decision(xperm, &local_xpd, xpd); @@ -1100,12 +1082,12 @@ int avc_has_extended_perms(struct selinux_state *state, decision: denied = requested & ~(avd.allowed); if (unlikely(denied)) - rc = avc_denied(state, ssid, tsid, tclass, requested, - driver, xperm, AVC_EXTENDED_PERMS, &avd); + rc = avc_denied(ssid, tsid, tclass, requested, driver, + base_perm, xperm, AVC_EXTENDED_PERMS, &avd); rcu_read_unlock(); - rc2 = avc_xperms_audit(state, ssid, tsid, tclass, requested, + rc2 = avc_xperms_audit(ssid, tsid, tclass, requested, &avd, xpd, xperm, rc, ad); if (rc2) return rc2; @@ -1113,8 +1095,35 @@ decision: } /** + * avc_perm_nonode - Add an entry to the AVC + * @ssid: subject + * @tsid: object/target + * @tclass: object class + * @requested: requested permissions + * @flags: AVC flags + * @avd: access vector decision + * + * This is the "we have no node" part of avc_has_perm_noaudit(), which is + * unlikely and needs extra stack space for the new node that we generate, so + * don't inline it. + */ +static noinline int avc_perm_nonode(u32 ssid, u32 tsid, u16 tclass, + u32 requested, unsigned int flags, + struct av_decision *avd) +{ + u32 denied; + struct avc_xperms_node xp_node; + + avc_compute_av(ssid, tsid, tclass, avd, &xp_node); + denied = requested & ~(avd->allowed); + if (unlikely(denied)) + return avc_denied(ssid, tsid, tclass, requested, 0, 0, 0, + flags, avd); + return 0; +} + +/** * avc_has_perm_noaudit - Check permissions but perform no auditing. - * @state: SELinux state * @ssid: source security identifier * @tsid: target security identifier * @tclass: target security class @@ -1133,40 +1142,36 @@ decision: * 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, +inline int avc_has_perm_noaudit(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; + struct avc_node *node; if (WARN_ON(!requested)) return -EACCES; rcu_read_lock(); + node = avc_lookup(ssid, tsid, tclass); + if (unlikely(!node)) { + rcu_read_unlock(); + return avc_perm_nonode(ssid, tsid, tclass, requested, + flags, avd); + } + denied = requested & ~node->ae.avd.allowed; + memcpy(avd, &node->ae.avd, sizeof(*avd)); + rcu_read_unlock(); - node = avc_lookup(state->avc, ssid, tsid, tclass); - if (unlikely(!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; + return avc_denied(ssid, tsid, tclass, requested, 0, 0, 0, + flags, avd); + return 0; } /** * avc_has_perm - Check permissions and perform any appropriate auditing. - * @state: SELinux state * @ssid: source security identifier * @tsid: target security identifier * @tclass: target security class @@ -1181,42 +1186,23 @@ inline int avc_has_perm_noaudit(struct selinux_state *state, * 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, +int avc_has_perm(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, + rc = avc_has_perm_noaudit(ssid, tsid, tclass, requested, 0, &avd); - rc2 = avc_audit(state, ssid, tsid, tclass, requested, &avd, rc, + rc2 = avc_audit(ssid, tsid, tclass, requested, &avd, rc, auditdata); if (rc2) return rc2; return rc; } -u32 avc_policy_seqno(struct selinux_state *state) +u32 avc_policy_seqno(void) { - 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); */ - } + return selinux_avc.avc_cache.latest_notif; } |
