diff options
Diffstat (limited to 'security/selinux/xfrm.c')
| -rw-r--r-- | security/selinux/xfrm.c | 495 | 
1 files changed, 238 insertions, 257 deletions
diff --git a/security/selinux/xfrm.c b/security/selinux/xfrm.c index fff78d3b51a..98b042630a9 100644 --- a/security/selinux/xfrm.c +++ b/security/selinux/xfrm.c @@ -46,7 +46,7 @@  #include <net/xfrm.h>  #include <net/checksum.h>  #include <net/udp.h> -#include <asm/atomic.h> +#include <linux/atomic.h>  #include "avc.h"  #include "objsec.h" @@ -56,7 +56,7 @@  atomic_t selinux_xfrm_refcount = ATOMIC_INIT(0);  /* - * Returns true if an LSM/SELinux context + * Returns true if the context is an LSM/SELinux context.   */  static inline int selinux_authorizable_ctx(struct xfrm_sec_ctx *ctx)  { @@ -66,7 +66,7 @@ static inline int selinux_authorizable_ctx(struct xfrm_sec_ctx *ctx)  }  /* - * Returns true if the xfrm contains a security blob for SELinux + * Returns true if the xfrm contains a security blob for SELinux.   */  static inline int selinux_authorizable_xfrm(struct xfrm_state *x)  { @@ -74,48 +74,112 @@ static inline int selinux_authorizable_xfrm(struct xfrm_state *x)  }  /* - * LSM hook implementation that authorizes that a flow can use - * a xfrm policy rule. + * Allocates a xfrm_sec_state and populates it using the supplied security + * xfrm_user_sec_ctx context.   */ -int selinux_xfrm_policy_lookup(struct xfrm_sec_ctx *ctx, u32 fl_secid, u8 dir) +static int selinux_xfrm_alloc_user(struct xfrm_sec_ctx **ctxp, +				   struct xfrm_user_sec_ctx *uctx, +				   gfp_t gfp)  {  	int rc; -	u32 sel_sid; +	const struct task_security_struct *tsec = current_security(); +	struct xfrm_sec_ctx *ctx = NULL; +	u32 str_len; -	/* Context sid is either set to label or ANY_ASSOC */ -	if (ctx) { -		if (!selinux_authorizable_ctx(ctx)) -			return -EINVAL; - -		sel_sid = ctx->ctx_sid; -	} else -		/* -		 * All flows should be treated as polmatch'ing an -		 * otherwise applicable "non-labeled" policy. This -		 * would prevent inadvertent "leaks". -		 */ -		return 0; +	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(ctx->ctx_str, str_len, &ctx->ctx_sid, gfp); +	if (rc) +		goto err; -	rc = avc_has_perm(fl_secid, sel_sid, SECCLASS_ASSOCIATION, -			  ASSOCIATION__POLMATCH, -			  NULL); +	rc = avc_has_perm(tsec->sid, ctx->ctx_sid, +			  SECCLASS_ASSOCIATION, ASSOCIATION__SETCONTEXT, NULL); +	if (rc) +		goto err; -	if (rc == -EACCES) -		return -ESRCH; +	*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 = current_security(); + +	if (!ctx) +		return 0; + +	return avc_has_perm(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(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, -			struct flowi *fl) +int selinux_xfrm_state_pol_flow_match(struct xfrm_state *x, +				      struct xfrm_policy *xp, +				      const struct flowi *fl)  {  	u32 state_sid; -	int rc;  	if (!xp->security)  		if (x->security) @@ -135,189 +199,115 @@ int selinux_xfrm_state_pol_flow_match(struct xfrm_state *x, struct xfrm_policy *  	state_sid = x->security->ctx_sid; -	if (fl->secid != state_sid) +	if (fl->flowi_secid != state_sid)  		return 0; -	rc = avc_has_perm(fl->secid, state_sid, SECCLASS_ASSOCIATION, -			  ASSOCIATION__SENDTO, -			  NULL)? 0:1; - -	/* -	 * 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 rc; +	/* 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(fl->flowi_secid, state_sid, +			    SECCLASS_ASSOCIATION, ASSOCIATION__SENDTO, +			    NULL) ? 0 : 1);  } -/* - * 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) +static u32 selinux_xfrm_skb_sid_egress(struct sk_buff *skb)  { -	struct sec_path *sp; +	struct dst_entry *dst = skb_dst(skb); +	struct xfrm_state *x; -	*sid = SECSID_NULL; +	if (dst == NULL) +		return SECSID_NULL; +	x = dst->xfrm; +	if (x == NULL || !selinux_authorizable_xfrm(x)) +		return SECSID_NULL; -	if (skb == NULL) -		return 0; +	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->sp; -	sp = skb->sp;  	if (sp) { -		int i, sid_set = 0; +		int i; -		for (i = sp->len-1; i >= 0; 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_set) { -					*sid = ctx->ctx_sid; -					sid_set = 1; - +				if (sid_session == SECSID_NULL) { +					sid_session = ctx->ctx_sid;  					if (!ckall) -						break; -				} else if (*sid != ctx->ctx_sid) +						goto out; +				} else if (sid_session != ctx->ctx_sid) { +					*sid = SECSID_NULL;  					return -EINVAL; +				}  			}  		}  	} +out: +	*sid = sid_session;  	return 0;  }  /* - * Security blob allocation for xfrm_policy and xfrm_state - * CTX does not have a meaningful value on input + * LSM hook implementation that checks and/or returns the xfrm sid for the + * incoming packet.   */ -static int selinux_xfrm_sec_ctx_alloc(struct xfrm_sec_ctx **ctxp, -	struct xfrm_user_sec_ctx *uctx, u32 sid) +int selinux_xfrm_decode_session(struct sk_buff *skb, u32 *sid, int ckall)  { -	int rc = 0; -	const struct task_security_struct *tsec = current_security(); -	struct xfrm_sec_ctx *ctx = NULL; -	char *ctx_str = NULL; -	u32 str_len; - -	BUG_ON(uctx && sid); - -	if (!uctx) -		goto not_from_user; - -	if (uctx->ctx_doi != XFRM_SC_ALG_SELINUX) -		return -EINVAL; - -	str_len = uctx->ctx_len; -	if (str_len >= PAGE_SIZE) -		return -ENOMEM; - -	*ctxp = ctx = kmalloc(sizeof(*ctx) + -			      str_len + 1, -			      GFP_KERNEL); - -	if (!ctx) -		return -ENOMEM; - -	ctx->ctx_doi = uctx->ctx_doi; -	ctx->ctx_len = str_len; -	ctx->ctx_alg = uctx->ctx_alg; - -	memcpy(ctx->ctx_str, -	       uctx+1, -	       str_len); -	ctx->ctx_str[str_len] = 0; -	rc = security_context_to_sid(ctx->ctx_str, -				     str_len, -				     &ctx->ctx_sid); - -	if (rc) -		goto out; - -	/* -	 * Does the subject have permission to set security context? -	 */ -	rc = avc_has_perm(tsec->sid, ctx->ctx_sid, -			  SECCLASS_ASSOCIATION, -			  ASSOCIATION__SETCONTEXT, NULL); -	if (rc) -		goto out; - -	return rc; - -not_from_user: -	rc = security_sid_to_context(sid, &ctx_str, &str_len); -	if (rc) -		goto out; - -	*ctxp = ctx = kmalloc(sizeof(*ctx) + -			      str_len, -			      GFP_ATOMIC); - -	if (!ctx) { -		rc = -ENOMEM; -		goto out; +	if (skb == NULL) { +		*sid = SECSID_NULL; +		return 0;  	} +	return selinux_xfrm_skb_sid_ingress(skb, sid, ckall); +} -	ctx->ctx_doi = XFRM_SC_DOI_LSM; -	ctx->ctx_alg = XFRM_SC_ALG_SELINUX; -	ctx->ctx_sid = sid; -	ctx->ctx_len = str_len; -	memcpy(ctx->ctx_str, -	       ctx_str, -	       str_len); +int selinux_xfrm_skb_sid(struct sk_buff *skb, u32 *sid) +{ +	int rc; -	goto out2; +	rc = selinux_xfrm_skb_sid_ingress(skb, sid, 0); +	if (rc == 0 && *sid == SECSID_NULL) +		*sid = selinux_xfrm_skb_sid_egress(skb); -out: -	*ctxp = NULL; -	kfree(ctx); -out2: -	kfree(ctx_str);  	return rc;  }  /* - * LSM hook implementation that allocs and transfers uctx spec to - * xfrm_policy. + * 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) +			      struct xfrm_user_sec_ctx *uctx, +			      gfp_t gfp)  { -	int err; - -	BUG_ON(!uctx); - -	err = selinux_xfrm_sec_ctx_alloc(ctxp, uctx, 0); -	if (err == 0) -		atomic_inc(&selinux_xfrm_refcount); - -	return err; +	return selinux_xfrm_alloc_user(ctxp, uctx, gfp);  } -  /* - * LSM hook implementation that copies security data structure from old to - * new for policy cloning. + * 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) { -		new_ctx = kmalloc(sizeof(*old_ctx) + old_ctx->ctx_len, -				  GFP_KERNEL); -		if (!new_ctx) -			return -ENOMEM; +	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; -		memcpy(new_ctx, old_ctx, sizeof(*new_ctx)); -		memcpy(new_ctx->ctx_str, old_ctx->ctx_str, new_ctx->ctx_len); -		*new_ctxp = new_ctx; -	}  	return 0;  } @@ -326,7 +316,7 @@ int selinux_xfrm_policy_clone(struct xfrm_sec_ctx *old_ctx,   */  void selinux_xfrm_policy_free(struct xfrm_sec_ctx *ctx)  { -	kfree(ctx); +	selinux_xfrm_free(ctx);  }  /* @@ -334,35 +324,58 @@ void selinux_xfrm_policy_free(struct xfrm_sec_ctx *ctx)   */  int selinux_xfrm_policy_delete(struct xfrm_sec_ctx *ctx)  { -	const struct task_security_struct *tsec = current_security(); -	int rc = 0; - -	if (ctx) { -		rc = avc_has_perm(tsec->sid, ctx->ctx_sid, -				  SECCLASS_ASSOCIATION, -				  ASSOCIATION__SETCONTEXT, NULL); -		if (rc == 0) -			atomic_dec(&selinux_xfrm_refcount); -	} +	return selinux_xfrm_delete(ctx); +} -	return rc; +/* + * 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 allocs and transfers sec_ctx spec to - * xfrm_state. + * LSM hook implementation that allocates a xfrm_sec_state and populates based + * on a secid.   */ -int selinux_xfrm_state_alloc(struct xfrm_state *x, struct xfrm_user_sec_ctx *uctx, -		u32 secid) +int selinux_xfrm_state_alloc_acquire(struct xfrm_state *x, +				     struct xfrm_sec_ctx *polsec, u32 secid)  { -	int err; +	int rc; +	struct xfrm_sec_ctx *ctx; +	char *ctx_str = NULL; +	int str_len; + +	if (!polsec) +		return 0; -	BUG_ON(!x); +	if (secid == 0) +		return -EINVAL; + +	rc = security_sid_to_context(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); -	err = selinux_xfrm_sec_ctx_alloc(&x->security, uctx, secid); -	if (err == 0) -		atomic_inc(&selinux_xfrm_refcount); -	return err; +	x->security = ctx; +	atomic_inc(&selinux_xfrm_refcount); +out: +	kfree(ctx_str); +	return rc;  }  /* @@ -370,28 +383,15 @@ int selinux_xfrm_state_alloc(struct xfrm_state *x, struct xfrm_user_sec_ctx *uct   */  void selinux_xfrm_state_free(struct xfrm_state *x)  { -	struct xfrm_sec_ctx *ctx = x->security; -	kfree(ctx); +	selinux_xfrm_free(x->security);  } - /* -  * LSM hook implementation that authorizes deletion of labeled SAs. -  */ +/* + * LSM hook implementation that authorizes deletion of labeled SAs. + */  int selinux_xfrm_state_delete(struct xfrm_state *x)  { -	const struct task_security_struct *tsec = current_security(); -	struct xfrm_sec_ctx *ctx = x->security; -	int rc = 0; - -	if (ctx) { -		rc = avc_has_perm(tsec->sid, ctx->ctx_sid, -				  SECCLASS_ASSOCIATION, -				  ASSOCIATION__SETCONTEXT, NULL); -		if (rc == 0) -			atomic_dec(&selinux_xfrm_refcount); -	} - -	return rc; +	return selinux_xfrm_delete(x->security);  }  /* @@ -401,14 +401,12 @@ int selinux_xfrm_state_delete(struct xfrm_state *x)   * we need to check for unlabelled access since this may not have   * gone thru the IPSec process.   */ -int selinux_xfrm_sock_rcv_skb(u32 isec_sid, struct sk_buff *skb, -				struct common_audit_data *ad) +int selinux_xfrm_sock_rcv_skb(u32 sk_sid, struct sk_buff *skb, +			      struct common_audit_data *ad)  { -	int i, rc = 0; -	struct sec_path *sp; -	u32 sel_sid = SECINITSID_UNLABELED; - -	sp = skb->sp; +	int i; +	struct sec_path *sp = skb->sp; +	u32 peer_sid = SECINITSID_UNLABELED;  	if (sp) {  		for (i = 0; i < sp->len; i++) { @@ -416,23 +414,17 @@ int selinux_xfrm_sock_rcv_skb(u32 isec_sid, struct sk_buff *skb,  			if (x && selinux_authorizable_xfrm(x)) {  				struct xfrm_sec_ctx *ctx = x->security; -				sel_sid = ctx->ctx_sid; +				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. -	 */ - -	rc = avc_has_perm(isec_sid, sel_sid, SECCLASS_ASSOCIATION, -			  ASSOCIATION__RECVFROM, ad); - -	return rc; +	/* 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(sk_sid, peer_sid, +			    SECCLASS_ASSOCIATION, ASSOCIATION__RECVFROM, ad);  }  /* @@ -442,49 +434,38 @@ int selinux_xfrm_sock_rcv_skb(u32 isec_sid, struct sk_buff *skb,   * 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 isec_sid, struct sk_buff *skb, -					struct common_audit_data *ad, u8 proto) +int selinux_xfrm_postroute_last(u32 sk_sid, struct sk_buff *skb, +				struct common_audit_data *ad, u8 proto)  {  	struct dst_entry *dst; -	int rc = 0; - -	dst = skb_dst(skb); - -	if (dst) { -		struct dst_entry *dst_test; - -		for (dst_test = dst; dst_test != NULL; -		     dst_test = dst_test->child) { -			struct xfrm_state *x = dst_test->xfrm; - -			if (x && selinux_authorizable_xfrm(x)) -				goto out; -		} -	}  	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. -		 */ -		goto out; +		/* 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;  	} -	/* -	 * 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. -	 */ +	dst = skb_dst(skb); +	if (dst) { +		struct dst_entry *iter; + +		for (iter = dst; iter != NULL; iter = iter->child) { +			struct xfrm_state *x = iter->xfrm; -	rc = avc_has_perm(isec_sid, SECINITSID_UNLABELED, SECCLASS_ASSOCIATION, -			  ASSOCIATION__SENDTO, ad); -out: -	return rc; +			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(sk_sid, SECINITSID_UNLABELED, +			    SECCLASS_ASSOCIATION, ASSOCIATION__SENDTO, ad);  }  | 
