diff options
| author | Mark Brown <[email protected]> | 2015-10-12 18:09:27 +0100 | 
|---|---|---|
| committer | Mark Brown <[email protected]> | 2015-10-12 18:09:27 +0100 | 
| commit | 79828b4fa835f73cdaf4bffa48696abdcbea9d02 (patch) | |
| tree | 5e0fa7156acb75ba603022bc807df8f2fedb97a8 /security/selinux/avc.c | |
| parent | 721b51fcf91898299d96f4b72cb9434cda29dce6 (diff) | |
| parent | 8c1a9d6323abf0fb1e5dad96cf3f1c783505ea5a (diff) | |
Merge remote-tracking branch 'asoc/fix/rt5645' into asoc-fix-rt5645
Diffstat (limited to 'security/selinux/avc.c')
| -rw-r--r-- | security/selinux/avc.c | 418 | 
1 files changed, 402 insertions, 16 deletions
diff --git a/security/selinux/avc.c b/security/selinux/avc.c index 0b122b1421a9..e60c79de13e1 100644 --- a/security/selinux/avc.c +++ b/security/selinux/avc.c @@ -22,6 +22,7 @@  #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> @@ -48,6 +49,7 @@ struct avc_entry {  	u32			tsid;  	u16			tclass;  	struct av_decision	avd; +	struct avc_xperms_node	*xp_node;  };  struct avc_node { @@ -56,6 +58,16 @@ struct avc_node {  	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 */ @@ -80,6 +92,9 @@ DEFINE_PER_CPU(struct avc_cache_stats, avc_cache_stats) = { 0 };  static struct avc_cache avc_cache;  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)  { @@ -101,6 +116,7 @@ static void avc_dump_av(struct audit_buffer *ab, u16 tclass, u32 av)  		return;  	} +	BUG_ON(!tclass || tclass >= ARRAY_SIZE(secclass_map));  	perms = secclass_map[tclass-1].perms;  	audit_log_format(ab, " {"); @@ -149,7 +165,7 @@ static void avc_dump_query(struct audit_buffer *ab, u32 ssid, u32 tsid, u16 tcla  		kfree(scontext);  	} -	BUG_ON(tclass >= ARRAY_SIZE(secclass_map)); +	BUG_ON(!tclass || tclass >= ARRAY_SIZE(secclass_map));  	audit_log_format(ab, " tclass=%s", secclass_map[tclass-1].name);  } @@ -170,7 +186,17 @@ void __init avc_init(void)  	atomic_set(&avc_cache.lru_hint, 0);  	avc_node_cachep = kmem_cache_create("avc_node", sizeof(struct avc_node), -					     0, SLAB_PANIC, NULL); +					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);  	audit_log(current->audit_context, GFP_KERNEL, AUDIT_KERNEL, "AVC INITIALIZED\n");  } @@ -205,9 +231,261 @@ int avc_get_hash_stats(char *page)  			 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_ATOMIC | __GFP_NOMEMALLOC); +	if (!xpd_node) +		return NULL; + +	xpd = &xpd_node->xpd; +	if (which & XPERMS_ALLOWED) { +		xpd->allowed = kmem_cache_zalloc(avc_xperms_data_cachep, +						GFP_ATOMIC | __GFP_NOMEMALLOC); +		if (!xpd->allowed) +			goto error; +	} +	if (which & XPERMS_AUDITALLOW) { +		xpd->auditallow = kmem_cache_zalloc(avc_xperms_data_cachep, +						GFP_ATOMIC | __GFP_NOMEMALLOC); +		if (!xpd->auditallow) +			goto error; +	} +	if (which & XPERMS_DONTAUDIT) { +		xpd->dontaudit = kmem_cache_zalloc(avc_xperms_data_cachep, +						GFP_ATOMIC | __GFP_NOMEMALLOC); +		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_ATOMIC|__GFP_NOMEMALLOC); +	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(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(ssid, tsid, tclass, requested, +			audited, denied, result, ad, 0); +} +  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);  } @@ -221,6 +499,7 @@ static void avc_node_delete(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_cache.active_nodes); @@ -367,6 +646,7 @@ static int avc_latest_notif_update(int seqno, int is_insert)   * @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. @@ -378,7 +658,9 @@ static int avc_latest_notif_update(int seqno, int is_insert)   * the access vectors into a cache entry, returns   * avc_node inserted. Otherwise, this function returns NULL.   */ -static struct avc_node *avc_insert(u32 ssid, u32 tsid, u16 tclass, struct av_decision *avd) +static struct avc_node *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; @@ -391,10 +673,15 @@ static struct avc_node *avc_insert(u32 ssid, u32 tsid, u16 tclass, struct av_dec  	if (node) {  		struct hlist_head *head;  		spinlock_t *lock; +		int rc = 0;  		hvalue = avc_hash(ssid, tsid, tclass);  		avc_node_populate(node, ssid, tsid, tclass, avd); - +		rc = avc_xperms_populate(node, xp_node); +		if (rc) { +			kmem_cache_free(avc_node_cachep, node); +			return NULL; +		}  		head = &avc_cache.slots[hvalue];  		lock = &avc_cache.slots_lock[hvalue]; @@ -523,14 +810,17 @@ out:   * @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   *   * 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(u32 event, u32 perms, u32 ssid, u32 tsid, u16 tclass, -			   u32 seqno) +static int avc_update_node(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; @@ -574,9 +864,19 @@ static int avc_update_node(u32 event, u32 perms, u32 ssid, u32 tsid, u16 tclass,  	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) { +			kmem_cache_free(avc_node_cachep, 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: @@ -594,6 +894,9 @@ static int avc_update_node(u32 event, u32 perms, u32 ssid, u32 tsid, u16 tclass,  	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(node, orig);  out_unlock: @@ -665,18 +968,20 @@ int avc_ss_reset(u32 seqno)   * results in a bigger stack frame.   */  static noinline struct avc_node *avc_compute_av(u32 ssid, u32 tsid, -			 u16 tclass, struct av_decision *avd) +			 u16 tclass, struct av_decision *avd, +			 struct avc_xperms_node *xp_node)  {  	rcu_read_unlock(); -	security_compute_av(ssid, tsid, tclass, avd); +	INIT_LIST_HEAD(&xp_node->xpd_head); +	security_compute_av(ssid, tsid, tclass, avd, &xp_node->xp);  	rcu_read_lock(); -	return avc_insert(ssid, tsid, tclass, avd); +	return avc_insert(ssid, tsid, tclass, avd, xp_node);  }  static noinline int avc_denied(u32 ssid, u32 tsid, -			 u16 tclass, u32 requested, -			 unsigned flags, -			 struct av_decision *avd) +				u16 tclass, u32 requested, +				u8 driver, u8 xperm, unsigned flags, +				struct av_decision *avd)  {  	if (flags & AVC_STRICT)  		return -EACCES; @@ -684,11 +989,91 @@ static noinline int avc_denied(u32 ssid, u32 tsid,  	if (selinux_enforcing && !(avd->flags & AVD_FLAGS_PERMISSIVE))  		return -EACCES; -	avc_update_node(AVC_CALLBACK_GRANT, requested, ssid, -				tsid, tclass, avd->seqno); +	avc_update_node(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(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; +	BUG_ON(!requested); + +	rcu_read_lock(); + +	node = avc_lookup(ssid, tsid, tclass); +	if (unlikely(!node)) { +		node = avc_compute_av(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(ssid, tsid, tclass, driver, +						&local_xpd); +		rcu_read_lock(); +		avc_update_node(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(ssid, tsid, tclass, requested, driver, xperm, +				AVC_EXTENDED_PERMS, &avd); + +	rcu_read_unlock(); + +	rc2 = avc_xperms_audit(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. @@ -716,6 +1101,7 @@ inline int avc_has_perm_noaudit(u32 ssid, u32 tsid,  			 struct av_decision *avd)  {  	struct avc_node *node; +	struct avc_xperms_node xp_node;  	int rc = 0;  	u32 denied; @@ -725,13 +1111,13 @@ inline int avc_has_perm_noaudit(u32 ssid, u32 tsid,  	node = avc_lookup(ssid, tsid, tclass);  	if (unlikely(!node)) -		node = avc_compute_av(ssid, tsid, tclass, avd); +		node = avc_compute_av(ssid, tsid, tclass, avd, &xp_node);  	else  		memcpy(avd, &node->ae.avd, sizeof(*avd));  	denied = requested & ~(avd->allowed);  	if (unlikely(denied)) -		rc = avc_denied(ssid, tsid, tclass, requested, flags, avd); +		rc = avc_denied(ssid, tsid, tclass, requested, 0, 0, flags, avd);  	rcu_read_unlock();  	return rc;  |