diff options
Diffstat (limited to 'security/selinux/hooks.c')
| -rw-r--r-- | security/selinux/hooks.c | 229 | 
1 files changed, 155 insertions, 74 deletions
diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index 1d0b37af2444..c61787b15f27 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -89,6 +89,8 @@  #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 "avc.h" @@ -751,11 +753,13 @@ static int selinux_set_mnt_opts(struct super_block *sb,  	if (!strcmp(sb->s_type->name, "debugfs") ||  	    !strcmp(sb->s_type->name, "tracefs") || -	    !strcmp(sb->s_type->name, "sysfs") || -	    !strcmp(sb->s_type->name, "pstore") || +	    !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; +		sbsec->flags |= SE_SBGENFS | SE_SBGENFS_XATTR;  	if (!sbsec->behavior) {  		/* @@ -1354,6 +1358,67 @@ static int selinux_genfs_get_sid(struct dentry *dentry,  	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)  { @@ -1362,9 +1427,6 @@ static int inode_doinit_with_dentry(struct inode *inode, struct dentry *opt_dent  	u32 task_sid, sid = 0;  	u16 sclass;  	struct dentry *dentry; -#define INITCONTEXTLEN 255 -	char *context = NULL; -	unsigned len = 0;  	int rc = 0;  	if (isec->initialized == LABEL_INITIALIZED) @@ -1432,72 +1494,11 @@ static int inode_doinit_with_dentry(struct inode *inode, struct dentry *opt_dent  			goto out;  		} -		len = INITCONTEXTLEN; -		context = kmalloc(len+1, GFP_NOFS); -		if (!context) { -			rc = -ENOMEM; -			dput(dentry); -			goto out; -		} -		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) { -				dput(dentry); -				goto out; -			} -			len = rc; -			context = kmalloc(len+1, GFP_NOFS); -			if (!context) { -				rc = -ENOMEM; -				dput(dentry); -				goto out; -			} -			context[len] = '\0'; -			rc = __vfs_getxattr(dentry, inode, XATTR_NAME_SELINUX, context, len); -		} +		rc = inode_doinit_use_xattr(inode, dentry, sbsec->def_sid, +					    &sid);  		dput(dentry); -		if (rc < 0) { -			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); -				kfree(context); -				goto out; -			} -			/* Map ENODATA to the default file SID */ -			sid = sbsec->def_sid; -			rc = 0; -		} else { -			rc = security_context_to_sid_default(&selinux_state, -							     context, rc, &sid, -							     sbsec->def_sid, -							     GFP_NOFS); -			if (rc) { -				char *dev = inode->i_sb->s_id; -				unsigned long ino = inode->i_ino; - -				if (rc == -EINVAL) { -					if (printk_ratelimit()) -						pr_notice("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); -				/* Leave with the unlabeled SID */ -				rc = 0; -				break; -			} -		} -		kfree(context); +		if (rc) +			goto out;  		break;  	case SECURITY_FS_USE_TASK:  		sid = task_sid; @@ -1548,9 +1549,21 @@ static int inode_doinit_with_dentry(struct inode *inode, struct dentry *opt_dent  				goto out;  			rc = selinux_genfs_get_sid(dentry, sclass,  						   sbsec->flags, &sid); -			dput(dentry); -			if (rc) +			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;  	} @@ -3371,6 +3384,67 @@ static int selinux_inode_copy_up_xattr(const char *name)  	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 = current_security(); +	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) @@ -4438,7 +4512,7 @@ static int selinux_socket_bind(struct socket *sock, struct sockaddr *address, in  		struct lsm_network_audit net = {0,};  		struct sockaddr_in *addr4 = NULL;  		struct sockaddr_in6 *addr6 = NULL; -		u16 family_sa = address->sa_family; +		u16 family_sa;  		unsigned short snum;  		u32 sid, node_perm; @@ -4448,6 +4522,9 @@ static int selinux_socket_bind(struct socket *sock, struct sockaddr *address, in  		 * 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: @@ -4580,6 +4657,8 @@ static int selinux_socket_connect_helper(struct socket *sock,  		 * 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;  		switch (address->sa_family) {  		case AF_INET:  			addr4 = (struct sockaddr_in *)address; @@ -6719,6 +6798,8 @@ static struct security_hook_list selinux_hooks[] __lsm_ro_after_init = {  	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(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),  |