diff options
Diffstat (limited to 'security/selinux/hooks.c')
| -rw-r--r-- | security/selinux/hooks.c | 217 | 
1 files changed, 178 insertions, 39 deletions
| diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index e67a526d1f30..33fd061305c4 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -17,6 +17,7 @@   *	Paul Moore <[email protected]>   *  Copyright (C) 2007 Hitachi Software Engineering Co., Ltd.   *		       Yuichi Nakamura <[email protected]> + *  Copyright (C) 2016 Mellanox Technologies   *   *	This program is free software; you can redistribute it and/or modify   *	it under the terms of the GNU General Public License version 2, @@ -90,6 +91,7 @@  #include "netif.h"  #include "netnode.h"  #include "netport.h" +#include "ibpkey.h"  #include "xfrm.h"  #include "netlabel.h"  #include "audit.h" @@ -171,6 +173,16 @@ static int selinux_netcache_avc_callback(u32 event)  	return 0;  } +static int selinux_lsm_notifier_avc_callback(u32 event) +{ +	if (event == AVC_CALLBACK_RESET) { +		sel_ib_pkey_flush(); +		call_lsm_notifier(LSM_POLICY_CHANGE, NULL); +	} + +	return 0; +} +  /*   * initialise the security for the init task   */ @@ -398,18 +410,6 @@ static void superblock_free_security(struct super_block *sb)  	kfree(sbsec);  } -/* The file system's label must be initialized prior to use. */ - -static const char *labeling_behaviors[7] = { -	"uses xattr", -	"uses transition SIDs", -	"uses task SIDs", -	"uses genfs_contexts", -	"not configured for labeling", -	"uses mountpoint labeling", -	"uses native labeling", -}; -  static inline int inode_doinit(struct inode *inode)  {  	return inode_doinit_with_dentry(inode, NULL); @@ -524,13 +524,17 @@ static int sb_finish_set_opts(struct super_block *sb)  		}  	} -	if (sbsec->behavior > ARRAY_SIZE(labeling_behaviors)) -		printk(KERN_ERR "SELinux: initialized (dev %s, type %s), unknown behavior\n", -		       sb->s_id, sb->s_type->name); -  	sbsec->flags |= SE_SBINITIALIZED; + +	/* +	 * Explicitly set or clear SBLABEL_MNT.  It's not sufficient to simply +	 * leave the flag untouched because sb_clone_mnt_opts might be handing +	 * us a superblock that needs the flag to be cleared. +	 */  	if (selinux_is_sblabel_mnt(sb))  		sbsec->flags |= SBLABEL_MNT; +	else +		sbsec->flags &= ~SBLABEL_MNT;  	/* Initialize the root inode. */  	rc = inode_doinit_with_dentry(root_inode, root); @@ -809,6 +813,7 @@ static int selinux_set_mnt_opts(struct super_block *sb,  		sbsec->flags |= SE_SBPROC | SE_SBGENFS;  	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"))  		sbsec->flags |= SE_SBGENFS; @@ -963,8 +968,11 @@ mismatch:  }  static int selinux_sb_clone_mnt_opts(const struct super_block *oldsb, -					struct super_block *newsb) +					struct super_block *newsb, +					unsigned long kern_flags, +					unsigned long *set_kern_flags)  { +	int rc = 0;  	const struct superblock_security_struct *oldsbsec = oldsb->s_security;  	struct superblock_security_struct *newsbsec = newsb->s_security; @@ -979,6 +987,13 @@ static int selinux_sb_clone_mnt_opts(const struct super_block *oldsb,  	if (!ss_initialized)  		return 0; +	/* +	 * Specifying internal flags without providing a place to +	 * place the results is not allowed. +	 */ +	if (kern_flags && !set_kern_flags) +		return -EINVAL; +  	/* how can we clone if the old one wasn't set up?? */  	BUG_ON(!(oldsbsec->flags & SE_SBINITIALIZED)); @@ -994,6 +1009,18 @@ static int selinux_sb_clone_mnt_opts(const struct super_block *oldsb,  	newsbsec->def_sid = oldsbsec->def_sid;  	newsbsec->behavior = oldsbsec->behavior; +	if (newsbsec->behavior == SECURITY_FS_USE_NATIVE && +		!(kern_flags & SECURITY_LSM_NATIVE_LABELS) && !set_context) { +		rc = security_fs_use(newsb); +		if (rc) +			goto out; +	} + +	if (kern_flags & SECURITY_LSM_NATIVE_LABELS && !set_context) { +		newsbsec->behavior = SECURITY_FS_USE_NATIVE; +		*set_kern_flags |= SECURITY_LSM_NATIVE_LABELS; +	} +  	if (set_context) {  		u32 sid = oldsbsec->mntpoint_sid; @@ -1013,8 +1040,9 @@ static int selinux_sb_clone_mnt_opts(const struct super_block *oldsb,  	}  	sb_finish_set_opts(newsb); +out:  	mutex_unlock(&newsbsec->lock); -	return 0; +	return rc;  }  static int selinux_parse_opts_str(char *options, @@ -1106,10 +1134,8 @@ static int selinux_parse_opts_str(char *options,  	opts->mnt_opts_flags = kcalloc(NUM_SEL_MNT_OPTS, sizeof(int),  				       GFP_KERNEL); -	if (!opts->mnt_opts_flags) { -		kfree(opts->mnt_opts); +	if (!opts->mnt_opts_flags)  		goto out_err; -	}  	if (fscontext) {  		opts->mnt_opts[num_mnt_opts] = fscontext; @@ -1132,6 +1158,7 @@ static int selinux_parse_opts_str(char *options,  	return 0;  out_err: +	security_free_mnt_opts(opts);  	kfree(context);  	kfree(defcontext);  	kfree(fscontext); @@ -2063,8 +2090,9 @@ static inline u32 file_to_av(struct file *file)  static inline u32 open_file_to_av(struct file *file)  {  	u32 av = file_to_av(file); +	struct inode *inode = file_inode(file); -	if (selinux_policycap_openperm) +	if (selinux_policycap_openperm && inode->i_sb->s_magic != SOCKFS_MAGIC)  		av |= FILE__OPEN;  	return av; @@ -3059,6 +3087,7 @@ static int selinux_inode_permission(struct inode *inode, int mask)  static int selinux_inode_setattr(struct dentry *dentry, struct iattr *iattr)  {  	const struct cred *cred = current_cred(); +	struct inode *inode = d_backing_inode(dentry);  	unsigned int ia_valid = iattr->ia_valid;  	__u32 av = FILE__WRITE; @@ -3074,8 +3103,10 @@ static int selinux_inode_setattr(struct dentry *dentry, struct iattr *iattr)  			ATTR_ATIME_SET | ATTR_MTIME_SET | ATTR_TIMES_SET))  		return dentry_has_perm(cred, dentry, FILE__SETATTR); -	if (selinux_policycap_openperm && (ia_valid & ATTR_SIZE) -			&& !(ia_valid & ATTR_FILE)) +	if (selinux_policycap_openperm && +	    inode->i_sb->s_magic != SOCKFS_MAGIC && +	    (ia_valid & ATTR_SIZE) && +	    !(ia_valid & ATTR_FILE))  		av |= FILE__OPEN;  	return dentry_has_perm(cred, dentry, av); @@ -3107,6 +3138,18 @@ static int selinux_inode_setotherxattr(struct dentry *dentry, const char *name)  	return dentry_has_perm(cred, dentry, FILE__SETATTR);  } +static bool has_cap_mac_admin(bool audit) +{ +	const struct cred *cred = current_cred(); +	int cap_audit = audit ? SECURITY_CAP_AUDIT : SECURITY_CAP_NOAUDIT; + +	if (cap_capable(cred, &init_user_ns, CAP_MAC_ADMIN, cap_audit)) +		return false; +	if (cred_has_capability(cred, CAP_MAC_ADMIN, cap_audit, true)) +		return false; +	return true; +} +  static int selinux_inode_setxattr(struct dentry *dentry, const char *name,  				  const void *value, size_t size, int flags)  { @@ -3138,7 +3181,7 @@ static int selinux_inode_setxattr(struct dentry *dentry, const char *name,  	rc = security_context_to_sid(value, size, &newsid, GFP_KERNEL);  	if (rc == -EINVAL) { -		if (!capable(CAP_MAC_ADMIN)) { +		if (!has_cap_mac_admin(true)) {  			struct audit_buffer *ab;  			size_t audit_size;  			const char *str; @@ -3264,13 +3307,8 @@ static int selinux_inode_getsecurity(struct inode *inode, const char *name, void  	 * and lack of permission just means that we fall back to the  	 * in-core context value, not a denial.  	 */ -	error = cap_capable(current_cred(), &init_user_ns, CAP_MAC_ADMIN, -			    SECURITY_CAP_NOAUDIT); -	if (!error) -		error = cred_has_capability(current_cred(), CAP_MAC_ADMIN, -					    SECURITY_CAP_NOAUDIT, true);  	isec = inode_security(inode); -	if (!error) +	if (has_cap_mac_admin(false))  		error = security_sid_to_context_force(isec->sid, &context,  						      &size);  	else @@ -3550,6 +3588,18 @@ static int selinux_mmap_addr(unsigned long addr)  static int selinux_mmap_file(struct file *file, unsigned long reqprot,  			     unsigned long prot, unsigned long flags)  { +	struct common_audit_data ad; +	int rc; + +	if (file) { +		ad.type = LSM_AUDIT_DATA_FILE; +		ad.u.file = file; +		rc = inode_has_perm(current_cred(), file_inode(file), +				    FILE__MAP, &ad); +		if (rc) +			return rc; +	} +  	if (selinux_checkreqprot)  		prot = reqprot; @@ -3710,7 +3760,8 @@ static int selinux_file_open(struct file *file, const struct cred *cred)  /* task security operations */ -static int selinux_task_create(unsigned long clone_flags) +static int selinux_task_alloc(struct task_struct *task, +			      unsigned long clone_flags)  {  	u32 sid = current_sid(); @@ -5918,7 +5969,7 @@ static int selinux_setprocattr(const char *name, void *value, size_t size)  		}  		error = security_context_to_sid(value, size, &sid, GFP_KERNEL);  		if (error == -EINVAL && !strcmp(name, "fscreate")) { -			if (!capable(CAP_MAC_ADMIN)) { +			if (!has_cap_mac_admin(true)) {  				struct audit_buffer *ab;  				size_t audit_size; @@ -6128,7 +6179,70 @@ static int selinux_key_getsecurity(struct key *key, char **_buffer)  	*_buffer = context;  	return rc;  } +#endif + +#ifdef CONFIG_SECURITY_INFINIBAND +static int selinux_ib_pkey_access(void *ib_sec, u64 subnet_prefix, u16 pkey_val) +{ +	struct common_audit_data ad; +	int err; +	u32 sid = 0; +	struct ib_security_struct *sec = ib_sec; +	struct lsm_ibpkey_audit ibpkey; + +	err = sel_ib_pkey_sid(subnet_prefix, pkey_val, &sid); +	if (err) +		return err; +	ad.type = LSM_AUDIT_DATA_IBPKEY; +	ibpkey.subnet_prefix = subnet_prefix; +	ibpkey.pkey = pkey_val; +	ad.u.ibpkey = &ibpkey; +	return avc_has_perm(sec->sid, sid, +			    SECCLASS_INFINIBAND_PKEY, +			    INFINIBAND_PKEY__ACCESS, &ad); +} + +static int selinux_ib_endport_manage_subnet(void *ib_sec, const char *dev_name, +					    u8 port_num) +{ +	struct common_audit_data ad; +	int err; +	u32 sid = 0; +	struct ib_security_struct *sec = ib_sec; +	struct lsm_ibendport_audit ibendport; + +	err = security_ib_endport_sid(dev_name, port_num, &sid); + +	if (err) +		return err; + +	ad.type = LSM_AUDIT_DATA_IBENDPORT; +	strncpy(ibendport.dev_name, dev_name, sizeof(ibendport.dev_name)); +	ibendport.port = port_num; +	ad.u.ibendport = &ibendport; +	return avc_has_perm(sec->sid, sid, +			    SECCLASS_INFINIBAND_ENDPORT, +			    INFINIBAND_ENDPORT__MANAGE_SUBNET, &ad); +} + +static int selinux_ib_alloc_security(void **ib_sec) +{ +	struct ib_security_struct *sec; + +	sec = kzalloc(sizeof(*sec), GFP_KERNEL); +	if (!sec) +		return -ENOMEM; +	sec->sid = current_sid(); + +	*ib_sec = sec; +	return 0; +} + +static void selinux_ib_free_security(void *ib_sec) +{ +	kfree(ib_sec); +}  #endif  static struct security_hook_list selinux_hooks[] __lsm_ro_after_init = { @@ -6213,7 +6327,7 @@ static struct security_hook_list selinux_hooks[] __lsm_ro_after_init = {  	LSM_HOOK_INIT(file_open, selinux_file_open), -	LSM_HOOK_INIT(task_create, selinux_task_create), +	LSM_HOOK_INIT(task_alloc, selinux_task_alloc),  	LSM_HOOK_INIT(cred_alloc_blank, selinux_cred_alloc_blank),  	LSM_HOOK_INIT(cred_free, selinux_cred_free),  	LSM_HOOK_INIT(cred_prepare, selinux_cred_prepare), @@ -6315,7 +6429,13 @@ static struct security_hook_list selinux_hooks[] __lsm_ro_after_init = {  	LSM_HOOK_INIT(tun_dev_attach_queue, selinux_tun_dev_attach_queue),  	LSM_HOOK_INIT(tun_dev_attach, selinux_tun_dev_attach),  	LSM_HOOK_INIT(tun_dev_open, selinux_tun_dev_open), - +#ifdef CONFIG_SECURITY_INFINIBAND +	LSM_HOOK_INIT(ib_pkey_access, selinux_ib_pkey_access), +	LSM_HOOK_INIT(ib_endport_manage_subnet, +		      selinux_ib_endport_manage_subnet), +	LSM_HOOK_INIT(ib_alloc_security, selinux_ib_alloc_security), +	LSM_HOOK_INIT(ib_free_security, selinux_ib_free_security), +#endif  #ifdef CONFIG_SECURITY_NETWORK_XFRM  	LSM_HOOK_INIT(xfrm_policy_alloc_security, selinux_xfrm_policy_alloc),  	LSM_HOOK_INIT(xfrm_policy_clone_security, selinux_xfrm_policy_clone), @@ -6379,6 +6499,9 @@ static __init int selinux_init(void)  	if (avc_add_callback(selinux_netcache_avc_callback, AVC_CALLBACK_RESET))  		panic("SELinux: Unable to register AVC netcache callback\n"); +	if (avc_add_callback(selinux_lsm_notifier_avc_callback, AVC_CALLBACK_RESET)) +		panic("SELinux: Unable to register AVC LSM notifier callback\n"); +  	if (selinux_enforcing)  		printk(KERN_DEBUG "SELinux:  Starting in enforcing mode\n");  	else @@ -6448,6 +6571,23 @@ static struct nf_hook_ops selinux_nf_ops[] = {  #endif	/* IPV6 */  }; +static int __net_init selinux_nf_register(struct net *net) +{ +	return nf_register_net_hooks(net, selinux_nf_ops, +				     ARRAY_SIZE(selinux_nf_ops)); +} + +static void __net_exit selinux_nf_unregister(struct net *net) +{ +	nf_unregister_net_hooks(net, selinux_nf_ops, +				ARRAY_SIZE(selinux_nf_ops)); +} + +static struct pernet_operations selinux_net_ops = { +	.init = selinux_nf_register, +	.exit = selinux_nf_unregister, +}; +  static int __init selinux_nf_ip_init(void)  {  	int err; @@ -6457,13 +6597,12 @@ static int __init selinux_nf_ip_init(void)  	printk(KERN_DEBUG "SELinux:  Registering netfilter hooks\n"); -	err = nf_register_hooks(selinux_nf_ops, ARRAY_SIZE(selinux_nf_ops)); +	err = register_pernet_subsys(&selinux_net_ops);  	if (err) -		panic("SELinux: nf_register_hooks: error %d\n", err); +		panic("SELinux: register_pernet_subsys: error %d\n", err);  	return 0;  } -  __initcall(selinux_nf_ip_init);  #ifdef CONFIG_SECURITY_SELINUX_DISABLE @@ -6471,7 +6610,7 @@ static void selinux_nf_ip_exit(void)  {  	printk(KERN_DEBUG "SELinux:  Unregistering netfilter hooks\n"); -	nf_unregister_hooks(selinux_nf_ops, ARRAY_SIZE(selinux_nf_ops)); +	unregister_pernet_subsys(&selinux_net_ops);  }  #endif |