diff options
Diffstat (limited to 'security/selinux/hooks.c')
| -rw-r--r-- | security/selinux/hooks.c | 205 | 
1 files changed, 153 insertions, 52 deletions
diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index 794c3ca49ea..57b0b49f4e6 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -53,6 +53,7 @@  #include <net/ip.h>		/* for local_port_range[] */  #include <net/sock.h>  #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> @@ -95,10 +96,6 @@  #include "audit.h"  #include "avc_ss.h" -#define SB_TYPE_FMT "%s%s%s" -#define SB_SUBTYPE(sb) (sb->s_subtype && sb->s_subtype[0]) -#define SB_TYPE_ARGS(sb) sb->s_type->name, SB_SUBTYPE(sb) ? "." : "", SB_SUBTYPE(sb) ? sb->s_subtype : "" -  extern struct security_operations *security_ops;  /* SECMARK reference count */ @@ -237,6 +234,14 @@ static int inode_alloc_security(struct inode *inode)  	return 0;  } +static void inode_free_rcu(struct rcu_head *head) +{ +	struct inode_security_struct *isec; + +	isec = container_of(head, struct inode_security_struct, rcu); +	kmem_cache_free(sel_inode_cache, isec); +} +  static void inode_free_security(struct inode *inode)  {  	struct inode_security_struct *isec = inode->i_security; @@ -247,8 +252,16 @@ static void inode_free_security(struct inode *inode)  		list_del_init(&isec->list);  	spin_unlock(&sbsec->isec_lock); -	inode->i_security = NULL; -	kmem_cache_free(sel_inode_cache, isec); +	/* +	 * The inode may still be referenced in a path walk and +	 * a call to selinux_inode_permission() can be made +	 * after inode_free_security() is called. Ideally, the VFS +	 * wouldn't do this, but fixing that is a much harder +	 * job. For now, simply free the i_security via RCU, and +	 * leave the current inode->i_security pointer intact. +	 * The inode will be freed after the RCU grace period too. +	 */ +	call_rcu(&isec->rcu, inode_free_rcu);  }  static int file_alloc_security(struct file *file) @@ -413,8 +426,8 @@ static int sb_finish_set_opts(struct super_block *sb)  		   the first boot of the SELinux kernel before we have  		   assigned xattr values to the filesystem. */  		if (!root_inode->i_op->getxattr) { -			printk(KERN_WARNING "SELinux: (dev %s, type "SB_TYPE_FMT") has no " -			       "xattr support\n", sb->s_id, SB_TYPE_ARGS(sb)); +			printk(KERN_WARNING "SELinux: (dev %s, type %s) has no " +			       "xattr support\n", sb->s_id, sb->s_type->name);  			rc = -EOPNOTSUPP;  			goto out;  		} @@ -422,22 +435,22 @@ static int sb_finish_set_opts(struct super_block *sb)  		if (rc < 0 && rc != -ENODATA) {  			if (rc == -EOPNOTSUPP)  				printk(KERN_WARNING "SELinux: (dev %s, type " -				       SB_TYPE_FMT") has no security xattr handler\n", -				       sb->s_id, SB_TYPE_ARGS(sb)); +				       "%s) has no security xattr handler\n", +				       sb->s_id, sb->s_type->name);  			else  				printk(KERN_WARNING "SELinux: (dev %s, type " -				       SB_TYPE_FMT") getxattr errno %d\n", sb->s_id, -				       SB_TYPE_ARGS(sb), -rc); +				       "%s) getxattr errno %d\n", sb->s_id, +				       sb->s_type->name, -rc);  			goto out;  		}  	}  	if (sbsec->behavior > ARRAY_SIZE(labeling_behaviors)) -		printk(KERN_ERR "SELinux: initialized (dev %s, type "SB_TYPE_FMT"), unknown behavior\n", -		       sb->s_id, SB_TYPE_ARGS(sb)); +		printk(KERN_ERR "SELinux: initialized (dev %s, type %s), unknown behavior\n", +		       sb->s_id, sb->s_type->name);  	else -		printk(KERN_DEBUG "SELinux: initialized (dev %s, type "SB_TYPE_FMT"), %s\n", -		       sb->s_id, SB_TYPE_ARGS(sb), +		printk(KERN_DEBUG "SELinux: initialized (dev %s, type %s), %s\n", +		       sb->s_id, sb->s_type->name,  		       labeling_behaviors[sbsec->behavior-1]);  	sbsec->flags |= SE_SBINITIALIZED; @@ -600,6 +613,7 @@ static int selinux_set_mnt_opts(struct super_block *sb,  	const struct cred *cred = current_cred();  	int rc = 0, i;  	struct superblock_security_struct *sbsec = sb->s_security; +	const char *name = sb->s_type->name;  	struct inode *inode = sbsec->sb->s_root->d_inode;  	struct inode_security_struct *root_isec = inode->i_security;  	u32 fscontext_sid = 0, context_sid = 0, rootcontext_sid = 0; @@ -658,8 +672,8 @@ static int selinux_set_mnt_opts(struct super_block *sb,  					     strlen(mount_options[i]), &sid);  		if (rc) {  			printk(KERN_WARNING "SELinux: security_context_to_sid" -			       "(%s) failed for (dev %s, type "SB_TYPE_FMT") errno=%d\n", -			       mount_options[i], sb->s_id, SB_TYPE_ARGS(sb), rc); +			       "(%s) failed for (dev %s, type %s) errno=%d\n", +			       mount_options[i], sb->s_id, name, rc);  			goto out;  		}  		switch (flags[i]) { @@ -806,8 +820,7 @@ out:  out_double_mount:  	rc = -EINVAL;  	printk(KERN_WARNING "SELinux: mount invalid.  Same superblock, different " -	       "security settings for (dev %s, type "SB_TYPE_FMT")\n", sb->s_id, -	       SB_TYPE_ARGS(sb)); +	       "security settings for (dev %s, type %s)\n", sb->s_id, name);  	goto out;  } @@ -2480,8 +2493,8 @@ static int selinux_sb_remount(struct super_block *sb, void *data)  		rc = security_context_to_sid(mount_options[i], len, &sid);  		if (rc) {  			printk(KERN_WARNING "SELinux: security_context_to_sid" -			       "(%s) failed for (dev %s, type "SB_TYPE_FMT") errno=%d\n", -			       mount_options[i], sb->s_id, SB_TYPE_ARGS(sb), rc); +			       "(%s) failed for (dev %s, type %s) errno=%d\n", +			       mount_options[i], sb->s_id, sb->s_type->name, rc);  			goto out_free_opts;  		}  		rc = -EINVAL; @@ -2519,8 +2532,8 @@ out_free_secdata:  	return rc;  out_bad_option:  	printk(KERN_WARNING "SELinux: unable to change security options " -	       "during remount (dev %s, type "SB_TYPE_FMT")\n", sb->s_id, -	       SB_TYPE_ARGS(sb)); +	       "during remount (dev %s, type=%s)\n", sb->s_id, +	       sb->s_type->name);  	goto out_free_opts;  } @@ -3828,7 +3841,7 @@ static int selinux_skb_peerlbl_sid(struct sk_buff *skb, u16 family, u32 *sid)  	u32 nlbl_sid;  	u32 nlbl_type; -	err = selinux_skb_xfrm_sid(skb, &xfrm_sid); +	err = selinux_xfrm_skb_sid(skb, &xfrm_sid);  	if (unlikely(err))  		return -EACCES;  	err = selinux_netlbl_skbuff_getsid(skb, family, &nlbl_type, &nlbl_sid); @@ -3846,6 +3859,30 @@ static int selinux_skb_peerlbl_sid(struct sk_buff *skb, u16 family, u32 *sid)  	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 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(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, @@ -4313,8 +4350,10 @@ static int selinux_socket_sock_rcv_skb(struct sock *sk, struct sk_buff *skb)  		}  		err = avc_has_perm(sk_sid, peer_sid, SECCLASS_PEER,  				   PEER__RECV, &ad); -		if (err) +		if (err) {  			selinux_netlbl_err(skb, err, 0); +			return err; +		}  	}  	if (secmark_active) { @@ -4452,7 +4491,7 @@ static int selinux_inet_conn_request(struct sock *sk, struct sk_buff *skb,  	struct sk_security_struct *sksec = sk->sk_security;  	int err;  	u16 family = sk->sk_family; -	u32 newsid; +	u32 connsid;  	u32 peersid;  	/* handle mapped IPv4 packets arriving via IPv6 sockets */ @@ -4462,16 +4501,11 @@ static int selinux_inet_conn_request(struct sock *sk, struct sk_buff *skb,  	err = selinux_skb_peerlbl_sid(skb, family, &peersid);  	if (err)  		return err; -	if (peersid == SECSID_NULL) { -		req->secid = sksec->sid; -		req->peer_secid = SECSID_NULL; -	} else { -		err = security_sid_mls_copy(sksec->sid, peersid, &newsid); -		if (err) -			return err; -		req->secid = newsid; -		req->peer_secid = peersid; -	} +	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);  } @@ -4731,6 +4765,7 @@ static unsigned int selinux_ipv6_forward(const struct nf_hook_ops *ops,  static unsigned int selinux_ip_output(struct sk_buff *skb,  				      u16 family)  { +	struct sock *sk;  	u32 sid;  	if (!netlbl_enabled()) @@ -4739,8 +4774,27 @@ static unsigned int selinux_ip_output(struct sk_buff *skb,  	/* 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 */ -	if (skb->sk) { -		struct sk_security_struct *sksec = skb->sk->sk_security; +	sk = skb->sk; +	if (sk) { +		struct sk_security_struct *sksec; + +		if (sk->sk_state == TCP_LISTEN) +			/* 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; @@ -4810,27 +4864,36 @@ static unsigned int selinux_ip_postroute(struct sk_buff *skb, int ifindex,  	 * 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->sk; +  #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. */ -	if (skb_dst(skb) != NULL && skb_dst(skb)->xfrm != NULL) +	 *       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 != NULL && sk->sk_state == TCP_LISTEN))  		return NF_ACCEPT;  #endif -	secmark_active = selinux_secmark_enabled(); -	peerlbl_active = selinux_peerlbl_enabled(); -	if (!secmark_active && !peerlbl_active) -		return NF_ACCEPT; -	/* if the packet is being forwarded then get the peer label from the -	 * packet itself; otherwise check to see if it is from a local -	 * application or the kernel, if from an application get the peer label -	 * from the sending socket, otherwise use the kernel's sid */ -	sk = skb->sk;  	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)) @@ -4839,7 +4902,45 @@ static unsigned int selinux_ip_postroute(struct sk_buff *skb, int ifindex,  			secmark_perm = PACKET__SEND;  			peer_sid = SECINITSID_KERNEL;  		} +	} else if (sk->sk_state == TCP_LISTEN) { +		/* 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 = 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; +			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; @@ -5503,11 +5604,11 @@ static int selinux_setprocattr(struct task_struct *p,  		/* Check for ptracing, and update the task SID if ok.  		   Otherwise, leave SID unchanged and fail. */  		ptsid = 0; -		task_lock(p); +		rcu_read_lock();  		tracer = ptrace_parent(p);  		if (tracer)  			ptsid = task_sid(tracer); -		task_unlock(p); +		rcu_read_unlock();  		if (tracer) {  			error = avc_has_perm(ptsid, sid, SECCLASS_PROCESS,  | 
