diff options
Diffstat (limited to 'security')
72 files changed, 4161 insertions, 1197 deletions
diff --git a/security/Kconfig b/security/Kconfig index e0f08b52e4a..51bd5a0b69a 100644 --- a/security/Kconfig +++ b/security/Kconfig @@ -38,7 +38,9 @@ config TRUSTED_KEYS  config ENCRYPTED_KEYS  	tristate "ENCRYPTED KEYS" -	depends on KEYS && TRUSTED_KEYS +	depends on KEYS +	select CRYPTO +	select CRYPTO_HMAC  	select CRYPTO_AES  	select CRYPTO_CBC  	select CRYPTO_SHA256 @@ -186,7 +188,7 @@ source security/smack/Kconfig  source security/tomoyo/Kconfig  source security/apparmor/Kconfig -source security/integrity/ima/Kconfig +source security/integrity/Kconfig  choice  	prompt "Default security module" diff --git a/security/Makefile b/security/Makefile index 8bb0fe9e1ca..a5e502f8a05 100644 --- a/security/Makefile +++ b/security/Makefile @@ -24,5 +24,5 @@ obj-$(CONFIG_SECURITY_APPARMOR)		+= apparmor/built-in.o  obj-$(CONFIG_CGROUP_DEVICE)		+= device_cgroup.o  # Object integrity file lists -subdir-$(CONFIG_IMA)			+= integrity/ima -obj-$(CONFIG_IMA)			+= integrity/ima/built-in.o +subdir-$(CONFIG_INTEGRITY)		+= integrity +obj-$(CONFIG_INTEGRITY)			+= integrity/built-in.o diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c index 0848292982a..69ddb47787b 100644 --- a/security/apparmor/apparmorfs.c +++ b/security/apparmor/apparmorfs.c @@ -200,7 +200,7 @@ void __init aa_destroy_aafs(void)   *   * Returns: error on failure   */ -int __init aa_create_aafs(void) +static int __init aa_create_aafs(void)  {  	int error; diff --git a/security/apparmor/ipc.c b/security/apparmor/ipc.c index 649fad88869..7ee05c6f3c6 100644 --- a/security/apparmor/ipc.c +++ b/security/apparmor/ipc.c @@ -19,6 +19,7 @@  #include "include/capability.h"  #include "include/context.h"  #include "include/policy.h" +#include "include/ipc.h"  /* call back to audit ptrace fields */  static void audit_cb(struct audit_buffer *ab, void *va) diff --git a/security/apparmor/lib.c b/security/apparmor/lib.c index b82e383beb7..9516948041a 100644 --- a/security/apparmor/lib.c +++ b/security/apparmor/lib.c @@ -18,6 +18,7 @@  #include <linux/vmalloc.h>  #include "include/audit.h" +#include "include/apparmor.h"  /** diff --git a/security/apparmor/policy_unpack.c b/security/apparmor/policy_unpack.c index d6d9a57b565..741dd13e089 100644 --- a/security/apparmor/policy_unpack.c +++ b/security/apparmor/policy_unpack.c @@ -381,11 +381,11 @@ static bool unpack_trans_table(struct aa_ext *e, struct aa_profile *profile)  		profile->file.trans.size = size;  		for (i = 0; i < size; i++) {  			char *str; -			int c, j, size = unpack_strdup(e, &str, NULL); +			int c, j, size2 = unpack_strdup(e, &str, NULL);  			/* unpack_strdup verifies that the last character is  			 * null termination byte.  			 */ -			if (!size) +			if (!size2)  				goto fail;  			profile->file.trans.table[i] = str;  			/* verify that name doesn't start with space */ @@ -393,7 +393,7 @@ static bool unpack_trans_table(struct aa_ext *e, struct aa_profile *profile)  				goto fail;  			/* count internal #  of internal \0 */ -			for (c = j = 0; j < size - 2; j++) { +			for (c = j = 0; j < size2 - 2; j++) {  				if (!str[j])  					c++;  			} @@ -440,11 +440,11 @@ static bool unpack_rlimits(struct aa_ext *e, struct aa_profile *profile)  		if (size > RLIM_NLIMITS)  			goto fail;  		for (i = 0; i < size; i++) { -			u64 tmp = 0; +			u64 tmp2 = 0;  			int a = aa_map_resource(i); -			if (!unpack_u64(e, &tmp, NULL)) +			if (!unpack_u64(e, &tmp2, NULL))  				goto fail; -			profile->rlimits.limits[a].rlim_max = tmp; +			profile->rlimits.limits[a].rlim_max = tmp2;  		}  		if (!unpack_nameX(e, AA_ARRAYEND, NULL))  			goto fail; diff --git a/security/apparmor/procattr.c b/security/apparmor/procattr.c index 04a2cf8d1b6..1b41c542d37 100644 --- a/security/apparmor/procattr.c +++ b/security/apparmor/procattr.c @@ -16,6 +16,7 @@  #include "include/context.h"  #include "include/policy.h"  #include "include/domain.h" +#include "include/procattr.h"  /** diff --git a/security/commoncap.c b/security/commoncap.c index a93b3b73307..ee4f8486e5f 100644 --- a/security/commoncap.c +++ b/security/commoncap.c @@ -332,7 +332,8 @@ int cap_inode_killpriv(struct dentry *dentry)   */  static inline int bprm_caps_from_vfs_caps(struct cpu_vfs_cap_data *caps,  					  struct linux_binprm *bprm, -					  bool *effective) +					  bool *effective, +					  bool *has_cap)  {  	struct cred *new = bprm->cred;  	unsigned i; @@ -341,6 +342,9 @@ static inline int bprm_caps_from_vfs_caps(struct cpu_vfs_cap_data *caps,  	if (caps->magic_etc & VFS_CAP_FLAGS_EFFECTIVE)  		*effective = true; +	if (caps->magic_etc & VFS_CAP_REVISION_MASK) +		*has_cap = true; +  	CAP_FOR_EACH_U32(i) {  		__u32 permitted = caps->permitted.cap[i];  		__u32 inheritable = caps->inheritable.cap[i]; @@ -424,7 +428,7 @@ int get_vfs_caps_from_disk(const struct dentry *dentry, struct cpu_vfs_cap_data   * its xattrs and, if present, apply them to the proposed credentials being   * constructed by execve().   */ -static int get_file_caps(struct linux_binprm *bprm, bool *effective) +static int get_file_caps(struct linux_binprm *bprm, bool *effective, bool *has_cap)  {  	struct dentry *dentry;  	int rc = 0; @@ -450,7 +454,7 @@ static int get_file_caps(struct linux_binprm *bprm, bool *effective)  		goto out;  	} -	rc = bprm_caps_from_vfs_caps(&vcaps, bprm, effective); +	rc = bprm_caps_from_vfs_caps(&vcaps, bprm, effective, has_cap);  	if (rc == -EINVAL)  		printk(KERN_NOTICE "%s: cap_from_disk returned %d for %s\n",  		       __func__, rc, bprm->filename); @@ -475,11 +479,11 @@ int cap_bprm_set_creds(struct linux_binprm *bprm)  {  	const struct cred *old = current_cred();  	struct cred *new = bprm->cred; -	bool effective; +	bool effective, has_cap = false;  	int ret;  	effective = false; -	ret = get_file_caps(bprm, &effective); +	ret = get_file_caps(bprm, &effective, &has_cap);  	if (ret < 0)  		return ret; @@ -489,7 +493,7 @@ int cap_bprm_set_creds(struct linux_binprm *bprm)  		 * for a setuid root binary run by a non-root user.  Do set it  		 * for a root user just to cause least surprise to an admin.  		 */ -		if (effective && new->uid != 0 && new->euid == 0) { +		if (has_cap && new->uid != 0 && new->euid == 0) {  			warn_setuid_and_fcaps_mixed(bprm->filename);  			goto skip;  		} diff --git a/security/integrity/Kconfig b/security/integrity/Kconfig new file mode 100644 index 00000000000..4bf00acf793 --- /dev/null +++ b/security/integrity/Kconfig @@ -0,0 +1,7 @@ +# +config INTEGRITY +	def_bool y +	depends on IMA || EVM + +source security/integrity/ima/Kconfig +source security/integrity/evm/Kconfig diff --git a/security/integrity/Makefile b/security/integrity/Makefile new file mode 100644 index 00000000000..0ae44aea651 --- /dev/null +++ b/security/integrity/Makefile @@ -0,0 +1,12 @@ +# +# Makefile for caching inode integrity data (iint) +# + +obj-$(CONFIG_INTEGRITY) += integrity.o + +integrity-y := iint.o + +subdir-$(CONFIG_IMA)			+= ima +obj-$(CONFIG_IMA)			+= ima/built-in.o +subdir-$(CONFIG_EVM)			+= evm +obj-$(CONFIG_EVM)			+= evm/built-in.o diff --git a/security/integrity/evm/Kconfig b/security/integrity/evm/Kconfig new file mode 100644 index 00000000000..afbb59dd262 --- /dev/null +++ b/security/integrity/evm/Kconfig @@ -0,0 +1,13 @@ +config EVM +	boolean "EVM support" +	depends on SECURITY && KEYS && (TRUSTED_KEYS=y || TRUSTED_KEYS=n) +	select CRYPTO_HMAC +	select CRYPTO_MD5 +	select CRYPTO_SHA1 +	select ENCRYPTED_KEYS +	default n +	help +	  EVM protects a file's security extended attributes against +	  integrity attacks. + +	  If you are unsure how to answer this question, answer N. diff --git a/security/integrity/evm/Makefile b/security/integrity/evm/Makefile new file mode 100644 index 00000000000..7393c415a06 --- /dev/null +++ b/security/integrity/evm/Makefile @@ -0,0 +1,7 @@ +# +# Makefile for building the Extended Verification Module(EVM) +# +obj-$(CONFIG_EVM) += evm.o + +evm-y := evm_main.o evm_crypto.o evm_secfs.o +evm-$(CONFIG_FS_POSIX_ACL) += evm_posix_acl.o diff --git a/security/integrity/evm/evm.h b/security/integrity/evm/evm.h new file mode 100644 index 00000000000..d320f519743 --- /dev/null +++ b/security/integrity/evm/evm.h @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2005-2010 IBM Corporation + * + * Authors: + * Mimi Zohar <zohar@us.ibm.com> + * Kylene Hall <kjhall@us.ibm.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 2 of the License. + * + * File: evm.h + * + */ +#include <linux/xattr.h> +#include <linux/security.h> +#include "../integrity.h" + +extern int evm_initialized; +extern char *evm_hmac; + +extern struct crypto_shash *hmac_tfm; + +/* List of EVM protected security xattrs */ +extern char *evm_config_xattrnames[]; + +extern int evm_init_key(void); +extern int evm_update_evmxattr(struct dentry *dentry, +			       const char *req_xattr_name, +			       const char *req_xattr_value, +			       size_t req_xattr_value_len); +extern int evm_calc_hmac(struct dentry *dentry, const char *req_xattr_name, +			 const char *req_xattr_value, +			 size_t req_xattr_value_len, char *digest); +extern int evm_init_hmac(struct inode *inode, const struct xattr *xattr, +			 char *hmac_val); +extern int evm_init_secfs(void); +extern void evm_cleanup_secfs(void); diff --git a/security/integrity/evm/evm_crypto.c b/security/integrity/evm/evm_crypto.c new file mode 100644 index 00000000000..5dd5b140242 --- /dev/null +++ b/security/integrity/evm/evm_crypto.c @@ -0,0 +1,216 @@ +/* + * Copyright (C) 2005-2010 IBM Corporation + * + * Authors: + * Mimi Zohar <zohar@us.ibm.com> + * Kylene Hall <kjhall@us.ibm.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 2 of the License. + * + * File: evm_crypto.c + *	 Using root's kernel master key (kmk), calculate the HMAC + */ + +#include <linux/module.h> +#include <linux/crypto.h> +#include <linux/xattr.h> +#include <keys/encrypted-type.h> +#include <crypto/hash.h> +#include "evm.h" + +#define EVMKEY "evm-key" +#define MAX_KEY_SIZE 128 +static unsigned char evmkey[MAX_KEY_SIZE]; +static int evmkey_len = MAX_KEY_SIZE; + +struct crypto_shash *hmac_tfm; + +static struct shash_desc *init_desc(void) +{ +	int rc; +	struct shash_desc *desc; + +	if (hmac_tfm == NULL) { +		hmac_tfm = crypto_alloc_shash(evm_hmac, 0, CRYPTO_ALG_ASYNC); +		if (IS_ERR(hmac_tfm)) { +			pr_err("Can not allocate %s (reason: %ld)\n", +			       evm_hmac, PTR_ERR(hmac_tfm)); +			rc = PTR_ERR(hmac_tfm); +			hmac_tfm = NULL; +			return ERR_PTR(rc); +		} +	} + +	desc = kmalloc(sizeof(*desc) + crypto_shash_descsize(hmac_tfm), +			GFP_KERNEL); +	if (!desc) +		return ERR_PTR(-ENOMEM); + +	desc->tfm = hmac_tfm; +	desc->flags = CRYPTO_TFM_REQ_MAY_SLEEP; + +	rc = crypto_shash_setkey(hmac_tfm, evmkey, evmkey_len); +	if (rc) +		goto out; +	rc = crypto_shash_init(desc); +out: +	if (rc) { +		kfree(desc); +		return ERR_PTR(rc); +	} +	return desc; +} + +/* Protect against 'cutting & pasting' security.evm xattr, include inode + * specific info. + * + * (Additional directory/file metadata needs to be added for more complete + * protection.) + */ +static void hmac_add_misc(struct shash_desc *desc, struct inode *inode, +			  char *digest) +{ +	struct h_misc { +		unsigned long ino; +		__u32 generation; +		uid_t uid; +		gid_t gid; +		umode_t mode; +	} hmac_misc; + +	memset(&hmac_misc, 0, sizeof hmac_misc); +	hmac_misc.ino = inode->i_ino; +	hmac_misc.generation = inode->i_generation; +	hmac_misc.uid = inode->i_uid; +	hmac_misc.gid = inode->i_gid; +	hmac_misc.mode = inode->i_mode; +	crypto_shash_update(desc, (const u8 *)&hmac_misc, sizeof hmac_misc); +	crypto_shash_final(desc, digest); +} + +/* + * Calculate the HMAC value across the set of protected security xattrs. + * + * Instead of retrieving the requested xattr, for performance, calculate + * the hmac using the requested xattr value. Don't alloc/free memory for + * each xattr, but attempt to re-use the previously allocated memory. + */ +int evm_calc_hmac(struct dentry *dentry, const char *req_xattr_name, +		  const char *req_xattr_value, size_t req_xattr_value_len, +		  char *digest) +{ +	struct inode *inode = dentry->d_inode; +	struct shash_desc *desc; +	char **xattrname; +	size_t xattr_size = 0; +	char *xattr_value = NULL; +	int error; +	int size; + +	if (!inode->i_op || !inode->i_op->getxattr) +		return -EOPNOTSUPP; +	desc = init_desc(); +	if (IS_ERR(desc)) +		return PTR_ERR(desc); + +	error = -ENODATA; +	for (xattrname = evm_config_xattrnames; *xattrname != NULL; xattrname++) { +		if ((req_xattr_name && req_xattr_value) +		    && !strcmp(*xattrname, req_xattr_name)) { +			error = 0; +			crypto_shash_update(desc, (const u8 *)req_xattr_value, +					     req_xattr_value_len); +			continue; +		} +		size = vfs_getxattr_alloc(dentry, *xattrname, +					  &xattr_value, xattr_size, GFP_NOFS); +		if (size == -ENOMEM) { +			error = -ENOMEM; +			goto out; +		} +		if (size < 0) +			continue; + +		error = 0; +		xattr_size = size; +		crypto_shash_update(desc, (const u8 *)xattr_value, xattr_size); +	} +	hmac_add_misc(desc, inode, digest); + +out: +	kfree(xattr_value); +	kfree(desc); +	return error; +} + +/* + * Calculate the hmac and update security.evm xattr + * + * Expects to be called with i_mutex locked. + */ +int evm_update_evmxattr(struct dentry *dentry, const char *xattr_name, +			const char *xattr_value, size_t xattr_value_len) +{ +	struct inode *inode = dentry->d_inode; +	struct evm_ima_xattr_data xattr_data; +	int rc = 0; + +	rc = evm_calc_hmac(dentry, xattr_name, xattr_value, +			   xattr_value_len, xattr_data.digest); +	if (rc == 0) { +		xattr_data.type = EVM_XATTR_HMAC; +		rc = __vfs_setxattr_noperm(dentry, XATTR_NAME_EVM, +					   &xattr_data, +					   sizeof(xattr_data), 0); +	} +	else if (rc == -ENODATA) +		rc = inode->i_op->removexattr(dentry, XATTR_NAME_EVM); +	return rc; +} + +int evm_init_hmac(struct inode *inode, const struct xattr *lsm_xattr, +		  char *hmac_val) +{ +	struct shash_desc *desc; + +	desc = init_desc(); +	if (IS_ERR(desc)) { +		printk(KERN_INFO "init_desc failed\n"); +		return PTR_ERR(desc); +	} + +	crypto_shash_update(desc, lsm_xattr->value, lsm_xattr->value_len); +	hmac_add_misc(desc, inode, hmac_val); +	kfree(desc); +	return 0; +} + +/* + * Get the key from the TPM for the SHA1-HMAC + */ +int evm_init_key(void) +{ +	struct key *evm_key; +	struct encrypted_key_payload *ekp; +	int rc = 0; + +	evm_key = request_key(&key_type_encrypted, EVMKEY, NULL); +	if (IS_ERR(evm_key)) +		return -ENOENT; + +	down_read(&evm_key->sem); +	ekp = evm_key->payload.data; +	if (ekp->decrypted_datalen > MAX_KEY_SIZE) { +		rc = -EINVAL; +		goto out; +	} +	memcpy(evmkey, ekp->decrypted_data, ekp->decrypted_datalen); +out: +	/* burn the original key contents */ +	memset(ekp->decrypted_data, 0, ekp->decrypted_datalen); +	up_read(&evm_key->sem); +	key_put(evm_key); +	return rc; +} diff --git a/security/integrity/evm/evm_main.c b/security/integrity/evm/evm_main.c new file mode 100644 index 00000000000..92d3d99a9f7 --- /dev/null +++ b/security/integrity/evm/evm_main.c @@ -0,0 +1,384 @@ +/* + * Copyright (C) 2005-2010 IBM Corporation + * + * Author: + * Mimi Zohar <zohar@us.ibm.com> + * Kylene Hall <kjhall@us.ibm.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 2 of the License. + * + * File: evm_main.c + *	implements evm_inode_setxattr, evm_inode_post_setxattr, + *	evm_inode_removexattr, and evm_verifyxattr + */ + +#include <linux/module.h> +#include <linux/crypto.h> +#include <linux/xattr.h> +#include <linux/integrity.h> +#include <linux/evm.h> +#include <crypto/hash.h> +#include "evm.h" + +int evm_initialized; + +char *evm_hmac = "hmac(sha1)"; + +char *evm_config_xattrnames[] = { +#ifdef CONFIG_SECURITY_SELINUX +	XATTR_NAME_SELINUX, +#endif +#ifdef CONFIG_SECURITY_SMACK +	XATTR_NAME_SMACK, +#endif +	XATTR_NAME_CAPS, +	NULL +}; + +static int evm_fixmode; +static int __init evm_set_fixmode(char *str) +{ +	if (strncmp(str, "fix", 3) == 0) +		evm_fixmode = 1; +	return 0; +} +__setup("evm=", evm_set_fixmode); + +/* + * evm_verify_hmac - calculate and compare the HMAC with the EVM xattr + * + * Compute the HMAC on the dentry's protected set of extended attributes + * and compare it against the stored security.evm xattr. + * + * For performance: + * - use the previoulsy retrieved xattr value and length to calculate the + *   HMAC.) + * - cache the verification result in the iint, when available. + * + * Returns integrity status + */ +static enum integrity_status evm_verify_hmac(struct dentry *dentry, +					     const char *xattr_name, +					     char *xattr_value, +					     size_t xattr_value_len, +					     struct integrity_iint_cache *iint) +{ +	struct evm_ima_xattr_data xattr_data; +	enum integrity_status evm_status = INTEGRITY_PASS; +	int rc; + +	if (iint && iint->evm_status == INTEGRITY_PASS) +		return iint->evm_status; + +	/* if status is not PASS, try to check again - against -ENOMEM */ + +	rc = evm_calc_hmac(dentry, xattr_name, xattr_value, +			   xattr_value_len, xattr_data.digest); +	if (rc < 0) { +		evm_status = (rc == -ENODATA) +		    ? INTEGRITY_NOXATTRS : INTEGRITY_FAIL; +		goto out; +	} + +	xattr_data.type = EVM_XATTR_HMAC; +	rc = vfs_xattr_cmp(dentry, XATTR_NAME_EVM, (u8 *)&xattr_data, +			   sizeof xattr_data, GFP_NOFS); +	if (rc < 0) +		evm_status = (rc == -ENODATA) +		    ? INTEGRITY_NOLABEL : INTEGRITY_FAIL; +out: +	if (iint) +		iint->evm_status = evm_status; +	return evm_status; +} + +static int evm_protected_xattr(const char *req_xattr_name) +{ +	char **xattrname; +	int namelen; +	int found = 0; + +	namelen = strlen(req_xattr_name); +	for (xattrname = evm_config_xattrnames; *xattrname != NULL; xattrname++) { +		if ((strlen(*xattrname) == namelen) +		    && (strncmp(req_xattr_name, *xattrname, namelen) == 0)) { +			found = 1; +			break; +		} +		if (strncmp(req_xattr_name, +			    *xattrname + XATTR_SECURITY_PREFIX_LEN, +			    strlen(req_xattr_name)) == 0) { +			found = 1; +			break; +		} +	} +	return found; +} + +/** + * evm_verifyxattr - verify the integrity of the requested xattr + * @dentry: object of the verify xattr + * @xattr_name: requested xattr + * @xattr_value: requested xattr value + * @xattr_value_len: requested xattr value length + * + * Calculate the HMAC for the given dentry and verify it against the stored + * security.evm xattr. For performance, use the xattr value and length + * previously retrieved to calculate the HMAC. + * + * Returns the xattr integrity status. + * + * This function requires the caller to lock the inode's i_mutex before it + * is executed. + */ +enum integrity_status evm_verifyxattr(struct dentry *dentry, +				      const char *xattr_name, +				      void *xattr_value, size_t xattr_value_len, +				      struct integrity_iint_cache *iint) +{ +	if (!evm_initialized || !evm_protected_xattr(xattr_name)) +		return INTEGRITY_UNKNOWN; + +	if (!iint) { +		iint = integrity_iint_find(dentry->d_inode); +		if (!iint) +			return INTEGRITY_UNKNOWN; +	} +	return evm_verify_hmac(dentry, xattr_name, xattr_value, +				 xattr_value_len, iint); +} +EXPORT_SYMBOL_GPL(evm_verifyxattr); + +/* + * evm_verify_current_integrity - verify the dentry's metadata integrity + * @dentry: pointer to the affected dentry + * + * Verify and return the dentry's metadata integrity. The exceptions are + * before EVM is initialized or in 'fix' mode. + */ +static enum integrity_status evm_verify_current_integrity(struct dentry *dentry) +{ +	struct inode *inode = dentry->d_inode; + +	if (!evm_initialized || !S_ISREG(inode->i_mode) || evm_fixmode) +		return 0; +	return evm_verify_hmac(dentry, NULL, NULL, 0, NULL); +} + +/* + * evm_protect_xattr - protect the EVM extended attribute + * + * Prevent security.evm from being modified or removed without the + * necessary permissions or when the existing value is invalid. + * + * The posix xattr acls are 'system' prefixed, which normally would not + * affect security.evm.  An interesting side affect of writing posix xattr + * acls is their modifying of the i_mode, which is included in security.evm. + * For posix xattr acls only, permit security.evm, even if it currently + * doesn't exist, to be updated. + */ +static int evm_protect_xattr(struct dentry *dentry, const char *xattr_name, +			     const void *xattr_value, size_t xattr_value_len) +{ +	enum integrity_status evm_status; + +	if (strcmp(xattr_name, XATTR_NAME_EVM) == 0) { +		if (!capable(CAP_SYS_ADMIN)) +			return -EPERM; +	} else if (!evm_protected_xattr(xattr_name)) { +		if (!posix_xattr_acl(xattr_name)) +			return 0; +		evm_status = evm_verify_current_integrity(dentry); +		if ((evm_status == INTEGRITY_PASS) || +		    (evm_status == INTEGRITY_NOXATTRS)) +			return 0; +		return -EPERM; +	} +	evm_status = evm_verify_current_integrity(dentry); +	return evm_status == INTEGRITY_PASS ? 0 : -EPERM; +} + +/** + * evm_inode_setxattr - protect the EVM extended attribute + * @dentry: pointer to the affected dentry + * @xattr_name: pointer to the affected extended attribute name + * @xattr_value: pointer to the new extended attribute value + * @xattr_value_len: pointer to the new extended attribute value length + * + * Updating 'security.evm' requires CAP_SYS_ADMIN privileges and that + * the current value is valid. + */ +int evm_inode_setxattr(struct dentry *dentry, const char *xattr_name, +		       const void *xattr_value, size_t xattr_value_len) +{ +	return evm_protect_xattr(dentry, xattr_name, xattr_value, +				 xattr_value_len); +} + +/** + * evm_inode_removexattr - protect the EVM extended attribute + * @dentry: pointer to the affected dentry + * @xattr_name: pointer to the affected extended attribute name + * + * Removing 'security.evm' requires CAP_SYS_ADMIN privileges and that + * the current value is valid. + */ +int evm_inode_removexattr(struct dentry *dentry, const char *xattr_name) +{ +	return evm_protect_xattr(dentry, xattr_name, NULL, 0); +} + +/** + * evm_inode_post_setxattr - update 'security.evm' to reflect the changes + * @dentry: pointer to the affected dentry + * @xattr_name: pointer to the affected extended attribute name + * @xattr_value: pointer to the new extended attribute value + * @xattr_value_len: pointer to the new extended attribute value length + * + * Update the HMAC stored in 'security.evm' to reflect the change. + * + * No need to take the i_mutex lock here, as this function is called from + * __vfs_setxattr_noperm().  The caller of which has taken the inode's + * i_mutex lock. + */ +void evm_inode_post_setxattr(struct dentry *dentry, const char *xattr_name, +			     const void *xattr_value, size_t xattr_value_len) +{ +	if (!evm_initialized || (!evm_protected_xattr(xattr_name) +				 && !posix_xattr_acl(xattr_name))) +		return; + +	evm_update_evmxattr(dentry, xattr_name, xattr_value, xattr_value_len); +	return; +} + +/** + * evm_inode_post_removexattr - update 'security.evm' after removing the xattr + * @dentry: pointer to the affected dentry + * @xattr_name: pointer to the affected extended attribute name + * + * Update the HMAC stored in 'security.evm' to reflect removal of the xattr. + */ +void evm_inode_post_removexattr(struct dentry *dentry, const char *xattr_name) +{ +	struct inode *inode = dentry->d_inode; + +	if (!evm_initialized || !evm_protected_xattr(xattr_name)) +		return; + +	mutex_lock(&inode->i_mutex); +	evm_update_evmxattr(dentry, xattr_name, NULL, 0); +	mutex_unlock(&inode->i_mutex); +	return; +} + +/** + * evm_inode_setattr - prevent updating an invalid EVM extended attribute + * @dentry: pointer to the affected dentry + */ +int evm_inode_setattr(struct dentry *dentry, struct iattr *attr) +{ +	unsigned int ia_valid = attr->ia_valid; +	enum integrity_status evm_status; + +	if (!(ia_valid & (ATTR_MODE | ATTR_UID | ATTR_GID))) +		return 0; +	evm_status = evm_verify_current_integrity(dentry); +	if ((evm_status == INTEGRITY_PASS) || +	    (evm_status == INTEGRITY_NOXATTRS)) +		return 0; +	return -EPERM; +} + +/** + * evm_inode_post_setattr - update 'security.evm' after modifying metadata + * @dentry: pointer to the affected dentry + * @ia_valid: for the UID and GID status + * + * For now, update the HMAC stored in 'security.evm' to reflect UID/GID + * changes. + * + * This function is called from notify_change(), which expects the caller + * to lock the inode's i_mutex. + */ +void evm_inode_post_setattr(struct dentry *dentry, int ia_valid) +{ +	if (!evm_initialized) +		return; + +	if (ia_valid & (ATTR_MODE | ATTR_UID | ATTR_GID)) +		evm_update_evmxattr(dentry, NULL, NULL, 0); +	return; +} + +/* + * evm_inode_init_security - initializes security.evm + */ +int evm_inode_init_security(struct inode *inode, +				 const struct xattr *lsm_xattr, +				 struct xattr *evm_xattr) +{ +	struct evm_ima_xattr_data *xattr_data; +	int rc; + +	if (!evm_initialized || !evm_protected_xattr(lsm_xattr->name)) +		return 0; + +	xattr_data = kzalloc(sizeof(*xattr_data), GFP_NOFS); +	if (!xattr_data) +		return -ENOMEM; + +	xattr_data->type = EVM_XATTR_HMAC; +	rc = evm_init_hmac(inode, lsm_xattr, xattr_data->digest); +	if (rc < 0) +		goto out; + +	evm_xattr->value = xattr_data; +	evm_xattr->value_len = sizeof(*xattr_data); +	evm_xattr->name = kstrdup(XATTR_EVM_SUFFIX, GFP_NOFS); +	return 0; +out: +	kfree(xattr_data); +	return rc; +} +EXPORT_SYMBOL_GPL(evm_inode_init_security); + +static int __init init_evm(void) +{ +	int error; + +	error = evm_init_secfs(); +	if (error < 0) { +		printk(KERN_INFO "EVM: Error registering secfs\n"); +		goto err; +	} +err: +	return error; +} + +static void __exit cleanup_evm(void) +{ +	evm_cleanup_secfs(); +	if (hmac_tfm) +		crypto_free_shash(hmac_tfm); +} + +/* + * evm_display_config - list the EVM protected security extended attributes + */ +static int __init evm_display_config(void) +{ +	char **xattrname; + +	for (xattrname = evm_config_xattrnames; *xattrname != NULL; xattrname++) +		printk(KERN_INFO "EVM: %s\n", *xattrname); +	return 0; +} + +pure_initcall(evm_display_config); +late_initcall(init_evm); + +MODULE_DESCRIPTION("Extended Verification Module"); +MODULE_LICENSE("GPL"); diff --git a/security/integrity/evm/evm_posix_acl.c b/security/integrity/evm/evm_posix_acl.c new file mode 100644 index 00000000000..b1753e98bf9 --- /dev/null +++ b/security/integrity/evm/evm_posix_acl.c @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2011 IBM Corporation + * + * Author: + * Mimi Zohar <zohar@us.ibm.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 2 of the License. + */ + +#include <linux/module.h> +#include <linux/xattr.h> + +int posix_xattr_acl(char *xattr) +{ +	int xattr_len = strlen(xattr); + +	if ((strlen(XATTR_NAME_POSIX_ACL_ACCESS) == xattr_len) +	     && (strncmp(XATTR_NAME_POSIX_ACL_ACCESS, xattr, xattr_len) == 0)) +		return 1; +	if ((strlen(XATTR_NAME_POSIX_ACL_DEFAULT) == xattr_len) +	     && (strncmp(XATTR_NAME_POSIX_ACL_DEFAULT, xattr, xattr_len) == 0)) +		return 1; +	return 0; +} diff --git a/security/integrity/evm/evm_secfs.c b/security/integrity/evm/evm_secfs.c new file mode 100644 index 00000000000..ac762995057 --- /dev/null +++ b/security/integrity/evm/evm_secfs.c @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2010 IBM Corporation + * + * Authors: + * Mimi Zohar <zohar@us.ibm.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 2 of the License. + * + * File: evm_secfs.c + *	- Used to signal when key is on keyring + *	- Get the key and enable EVM + */ + +#include <linux/uaccess.h> +#include <linux/module.h> +#include "evm.h" + +static struct dentry *evm_init_tpm; + +/** + * evm_read_key - read() for <securityfs>/evm + * + * @filp: file pointer, not actually used + * @buf: where to put the result + * @count: maximum to send along + * @ppos: where to start + * + * Returns number of bytes read or error code, as appropriate + */ +static ssize_t evm_read_key(struct file *filp, char __user *buf, +			    size_t count, loff_t *ppos) +{ +	char temp[80]; +	ssize_t rc; + +	if (*ppos != 0) +		return 0; + +	sprintf(temp, "%d", evm_initialized); +	rc = simple_read_from_buffer(buf, count, ppos, temp, strlen(temp)); + +	return rc; +} + +/** + * evm_write_key - write() for <securityfs>/evm + * @file: file pointer, not actually used + * @buf: where to get the data from + * @count: bytes sent + * @ppos: where to start + * + * Used to signal that key is on the kernel key ring. + * - get the integrity hmac key from the kernel key ring + * - create list of hmac protected extended attributes + * Returns number of bytes written or error code, as appropriate + */ +static ssize_t evm_write_key(struct file *file, const char __user *buf, +			     size_t count, loff_t *ppos) +{ +	char temp[80]; +	int i, error; + +	if (!capable(CAP_SYS_ADMIN) || evm_initialized) +		return -EPERM; + +	if (count >= sizeof(temp) || count == 0) +		return -EINVAL; + +	if (copy_from_user(temp, buf, count) != 0) +		return -EFAULT; + +	temp[count] = '\0'; + +	if ((sscanf(temp, "%d", &i) != 1) || (i != 1)) +		return -EINVAL; + +	error = evm_init_key(); +	if (!error) { +		evm_initialized = 1; +		pr_info("EVM: initialized\n"); +	} else +		pr_err("EVM: initialization failed\n"); +	return count; +} + +static const struct file_operations evm_key_ops = { +	.read		= evm_read_key, +	.write		= evm_write_key, +}; + +int __init evm_init_secfs(void) +{ +	int error = 0; + +	evm_init_tpm = securityfs_create_file("evm", S_IRUSR | S_IRGRP, +					      NULL, NULL, &evm_key_ops); +	if (!evm_init_tpm || IS_ERR(evm_init_tpm)) +		error = -EFAULT; +	return error; +} + +void __exit evm_cleanup_secfs(void) +{ +	if (evm_init_tpm) +		securityfs_remove(evm_init_tpm); +} diff --git a/security/integrity/iint.c b/security/integrity/iint.c new file mode 100644 index 00000000000..399641c3e84 --- /dev/null +++ b/security/integrity/iint.c @@ -0,0 +1,172 @@ +/* + * Copyright (C) 2008 IBM Corporation + * + * Authors: + * Mimi Zohar <zohar@us.ibm.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2 of the + * License. + * + * File: integrity_iint.c + *	- implements the integrity hooks: integrity_inode_alloc, + *	  integrity_inode_free + *	- cache integrity information associated with an inode + *	  using a rbtree tree. + */ +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/spinlock.h> +#include <linux/rbtree.h> +#include "integrity.h" + +static struct rb_root integrity_iint_tree = RB_ROOT; +static DEFINE_SPINLOCK(integrity_iint_lock); +static struct kmem_cache *iint_cache __read_mostly; + +int iint_initialized; + +/* + * __integrity_iint_find - return the iint associated with an inode + */ +static struct integrity_iint_cache *__integrity_iint_find(struct inode *inode) +{ +	struct integrity_iint_cache *iint; +	struct rb_node *n = integrity_iint_tree.rb_node; + +	assert_spin_locked(&integrity_iint_lock); + +	while (n) { +		iint = rb_entry(n, struct integrity_iint_cache, rb_node); + +		if (inode < iint->inode) +			n = n->rb_left; +		else if (inode > iint->inode) +			n = n->rb_right; +		else +			break; +	} +	if (!n) +		return NULL; + +	return iint; +} + +/* + * integrity_iint_find - return the iint associated with an inode + */ +struct integrity_iint_cache *integrity_iint_find(struct inode *inode) +{ +	struct integrity_iint_cache *iint; + +	if (!IS_IMA(inode)) +		return NULL; + +	spin_lock(&integrity_iint_lock); +	iint = __integrity_iint_find(inode); +	spin_unlock(&integrity_iint_lock); + +	return iint; +} + +static void iint_free(struct integrity_iint_cache *iint) +{ +	iint->version = 0; +	iint->flags = 0UL; +	iint->evm_status = INTEGRITY_UNKNOWN; +	kmem_cache_free(iint_cache, iint); +} + +/** + * integrity_inode_alloc - allocate an iint associated with an inode + * @inode: pointer to the inode + */ +int integrity_inode_alloc(struct inode *inode) +{ +	struct rb_node **p; +	struct rb_node *new_node, *parent = NULL; +	struct integrity_iint_cache *new_iint, *test_iint; +	int rc; + +	new_iint = kmem_cache_alloc(iint_cache, GFP_NOFS); +	if (!new_iint) +		return -ENOMEM; + +	new_iint->inode = inode; +	new_node = &new_iint->rb_node; + +	mutex_lock(&inode->i_mutex);	/* i_flags */ +	spin_lock(&integrity_iint_lock); + +	p = &integrity_iint_tree.rb_node; +	while (*p) { +		parent = *p; +		test_iint = rb_entry(parent, struct integrity_iint_cache, +				     rb_node); +		rc = -EEXIST; +		if (inode < test_iint->inode) +			p = &(*p)->rb_left; +		else if (inode > test_iint->inode) +			p = &(*p)->rb_right; +		else +			goto out_err; +	} + +	inode->i_flags |= S_IMA; +	rb_link_node(new_node, parent, p); +	rb_insert_color(new_node, &integrity_iint_tree); + +	spin_unlock(&integrity_iint_lock); +	mutex_unlock(&inode->i_mutex);	/* i_flags */ + +	return 0; +out_err: +	spin_unlock(&integrity_iint_lock); +	mutex_unlock(&inode->i_mutex);	/* i_flags */ +	iint_free(new_iint); + +	return rc; +} + +/** + * integrity_inode_free - called on security_inode_free + * @inode: pointer to the inode + * + * Free the integrity information(iint) associated with an inode. + */ +void integrity_inode_free(struct inode *inode) +{ +	struct integrity_iint_cache *iint; + +	if (!IS_IMA(inode)) +		return; + +	spin_lock(&integrity_iint_lock); +	iint = __integrity_iint_find(inode); +	rb_erase(&iint->rb_node, &integrity_iint_tree); +	spin_unlock(&integrity_iint_lock); + +	iint_free(iint); +} + +static void init_once(void *foo) +{ +	struct integrity_iint_cache *iint = foo; + +	memset(iint, 0, sizeof *iint); +	iint->version = 0; +	iint->flags = 0UL; +	mutex_init(&iint->mutex); +	iint->evm_status = INTEGRITY_UNKNOWN; +} + +static int __init integrity_iintcache_init(void) +{ +	iint_cache = +	    kmem_cache_create("iint_cache", sizeof(struct integrity_iint_cache), +			      0, SLAB_PANIC, init_once); +	iint_initialized = 1; +	return 0; +} +security_initcall(integrity_iintcache_init); diff --git a/security/integrity/ima/Kconfig b/security/integrity/ima/Kconfig index b6ecfd4d8d7..19c053b8230 100644 --- a/security/integrity/ima/Kconfig +++ b/security/integrity/ima/Kconfig @@ -3,6 +3,7 @@  config IMA  	bool "Integrity Measurement Architecture(IMA)"  	depends on SECURITY +	select INTEGRITY  	select SECURITYFS  	select CRYPTO  	select CRYPTO_HMAC diff --git a/security/integrity/ima/Makefile b/security/integrity/ima/Makefile index 787c4cb916c..5690c021de8 100644 --- a/security/integrity/ima/Makefile +++ b/security/integrity/ima/Makefile @@ -6,4 +6,4 @@  obj-$(CONFIG_IMA) += ima.o  ima-y := ima_fs.o ima_queue.o ima_init.o ima_main.o ima_crypto.o ima_api.o \ -	 ima_policy.o ima_iint.o ima_audit.o +	 ima_policy.o ima_audit.o diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h index 08408bd7146..3ccf7acac6d 100644 --- a/security/integrity/ima/ima.h +++ b/security/integrity/ima/ima.h @@ -24,18 +24,19 @@  #include <linux/tpm.h>  #include <linux/audit.h> +#include "../integrity.h" +  enum ima_show_type { IMA_SHOW_BINARY, IMA_SHOW_ASCII };  enum tpm_pcrs { TPM_PCR0 = 0, TPM_PCR8 = 8 };  /* digest size for IMA, fits SHA1 or MD5 */ -#define IMA_DIGEST_SIZE		20 +#define IMA_DIGEST_SIZE		SHA1_DIGEST_SIZE  #define IMA_EVENT_NAME_LEN_MAX	255  #define IMA_HASH_BITS 9  #define IMA_MEASURE_HTABLE_SIZE (1 << IMA_HASH_BITS)  /* set during initialization */ -extern int iint_initialized;  extern int ima_initialized;  extern int ima_used_chip;  extern char *ima_hash; @@ -96,34 +97,21 @@ static inline unsigned long ima_hash_key(u8 *digest)  	return hash_long(*digest, IMA_HASH_BITS);  } -/* iint cache flags */ -#define IMA_MEASURED		0x01 - -/* integrity data associated with an inode */ -struct ima_iint_cache { -	struct rb_node rb_node; /* rooted in ima_iint_tree */ -	struct inode *inode;	/* back pointer to inode in question */ -	u64 version;		/* track inode changes */ -	unsigned char flags; -	u8 digest[IMA_DIGEST_SIZE]; -	struct mutex mutex;	/* protects: version, flags, digest */ -}; -  /* LIM API function definitions */  int ima_must_measure(struct inode *inode, int mask, int function); -int ima_collect_measurement(struct ima_iint_cache *iint, struct file *file); -void ima_store_measurement(struct ima_iint_cache *iint, struct file *file, +int ima_collect_measurement(struct integrity_iint_cache *iint, +			    struct file *file); +void ima_store_measurement(struct integrity_iint_cache *iint, struct file *file,  			   const unsigned char *filename);  int ima_store_template(struct ima_template_entry *entry, int violation,  		       struct inode *inode); -void ima_template_show(struct seq_file *m, void *e, -		       enum ima_show_type show); +void ima_template_show(struct seq_file *m, void *e, enum ima_show_type show);  /* rbtree tree calls to lookup, insert, delete   * integrity data associated with an inode.   */ -struct ima_iint_cache *ima_iint_insert(struct inode *inode); -struct ima_iint_cache *ima_iint_find(struct inode *inode); +struct integrity_iint_cache *integrity_iint_insert(struct inode *inode); +struct integrity_iint_cache *integrity_iint_find(struct inode *inode);  /* IMA policy related functions */  enum ima_hooks { FILE_CHECK = 1, FILE_MMAP, BPRM_CHECK }; diff --git a/security/integrity/ima/ima_api.c b/security/integrity/ima/ima_api.c index da36d2c085a..0d50df04ccc 100644 --- a/security/integrity/ima/ima_api.c +++ b/security/integrity/ima/ima_api.c @@ -126,7 +126,8 @@ int ima_must_measure(struct inode *inode, int mask, int function)   *   * Return 0 on success, error code otherwise   */ -int ima_collect_measurement(struct ima_iint_cache *iint, struct file *file) +int ima_collect_measurement(struct integrity_iint_cache *iint, +			    struct file *file)  {  	int result = -EEXIST; @@ -156,8 +157,8 @@ int ima_collect_measurement(struct ima_iint_cache *iint, struct file *file)   *   * Must be called with iint->mutex held.   */ -void ima_store_measurement(struct ima_iint_cache *iint, struct file *file, -			   const unsigned char *filename) +void ima_store_measurement(struct integrity_iint_cache *iint, +			   struct file *file, const unsigned char *filename)  {  	const char *op = "add_template_measure";  	const char *audit_cause = "ENOMEM"; diff --git a/security/integrity/ima/ima_fs.c b/security/integrity/ima/ima_fs.c index ef21b96a0b4..e1aa2b482dd 100644 --- a/security/integrity/ima/ima_fs.c +++ b/security/integrity/ima/ima_fs.c @@ -287,7 +287,7 @@ static atomic_t policy_opencount = ATOMIC_INIT(1);  /*   * ima_open_policy: sequentialize access to the policy file   */ -int ima_open_policy(struct inode * inode, struct file * filp) +static int ima_open_policy(struct inode * inode, struct file * filp)  {  	/* No point in being allowed to open it if you aren't going to write */  	if (!(filp->f_flags & O_WRONLY)) diff --git a/security/integrity/ima/ima_iint.c b/security/integrity/ima/ima_iint.c deleted file mode 100644 index 4ae73040ab7..00000000000 --- a/security/integrity/ima/ima_iint.c +++ /dev/null @@ -1,169 +0,0 @@ -/* - * Copyright (C) 2008 IBM Corporation - * - * Authors: - * Mimi Zohar <zohar@us.ibm.com> - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, version 2 of the - * License. - * - * File: ima_iint.c - * 	- implements the IMA hooks: ima_inode_alloc, ima_inode_free - *	- cache integrity information associated with an inode - *	  using a rbtree tree. - */ -#include <linux/slab.h> -#include <linux/module.h> -#include <linux/spinlock.h> -#include <linux/rbtree.h> -#include "ima.h" - -static struct rb_root ima_iint_tree = RB_ROOT; -static DEFINE_SPINLOCK(ima_iint_lock); -static struct kmem_cache *iint_cache __read_mostly; - -int iint_initialized = 0; - -/* - * __ima_iint_find - return the iint associated with an inode - */ -static struct ima_iint_cache *__ima_iint_find(struct inode *inode) -{ -	struct ima_iint_cache *iint; -	struct rb_node *n = ima_iint_tree.rb_node; - -	assert_spin_locked(&ima_iint_lock); - -	while (n) { -		iint = rb_entry(n, struct ima_iint_cache, rb_node); - -		if (inode < iint->inode) -			n = n->rb_left; -		else if (inode > iint->inode) -			n = n->rb_right; -		else -			break; -	} -	if (!n) -		return NULL; - -	return iint; -} - -/* - * ima_iint_find - return the iint associated with an inode - */ -struct ima_iint_cache *ima_iint_find(struct inode *inode) -{ -	struct ima_iint_cache *iint; - -	if (!IS_IMA(inode)) -		return NULL; - -	spin_lock(&ima_iint_lock); -	iint = __ima_iint_find(inode); -	spin_unlock(&ima_iint_lock); - -	return iint; -} - -static void iint_free(struct ima_iint_cache *iint) -{ -	iint->version = 0; -	iint->flags = 0UL; -	kmem_cache_free(iint_cache, iint); -} - -/** - * ima_inode_alloc - allocate an iint associated with an inode - * @inode: pointer to the inode - */ -int ima_inode_alloc(struct inode *inode) -{ -	struct rb_node **p; -	struct rb_node *new_node, *parent = NULL; -	struct ima_iint_cache *new_iint, *test_iint; -	int rc; - -	new_iint = kmem_cache_alloc(iint_cache, GFP_NOFS); -	if (!new_iint) -		return -ENOMEM; - -	new_iint->inode = inode; -	new_node = &new_iint->rb_node; - -	mutex_lock(&inode->i_mutex); /* i_flags */ -	spin_lock(&ima_iint_lock); - -	p = &ima_iint_tree.rb_node; -	while (*p) { -		parent = *p; -		test_iint = rb_entry(parent, struct ima_iint_cache, rb_node); - -		rc = -EEXIST; -		if (inode < test_iint->inode) -			p = &(*p)->rb_left; -		else if (inode > test_iint->inode) -			p = &(*p)->rb_right; -		else -			goto out_err; -	} - -	inode->i_flags |= S_IMA; -	rb_link_node(new_node, parent, p); -	rb_insert_color(new_node, &ima_iint_tree); - -	spin_unlock(&ima_iint_lock); -	mutex_unlock(&inode->i_mutex); /* i_flags */ - -	return 0; -out_err: -	spin_unlock(&ima_iint_lock); -	mutex_unlock(&inode->i_mutex); /* i_flags */ -	iint_free(new_iint); - -	return rc; -} - -/** - * ima_inode_free - called on security_inode_free - * @inode: pointer to the inode - * - * Free the integrity information(iint) associated with an inode. - */ -void ima_inode_free(struct inode *inode) -{ -	struct ima_iint_cache *iint; - -	if (!IS_IMA(inode)) -		return; - -	spin_lock(&ima_iint_lock); -	iint = __ima_iint_find(inode); -	rb_erase(&iint->rb_node, &ima_iint_tree); -	spin_unlock(&ima_iint_lock); - -	iint_free(iint); -} - -static void init_once(void *foo) -{ -	struct ima_iint_cache *iint = foo; - -	memset(iint, 0, sizeof *iint); -	iint->version = 0; -	iint->flags = 0UL; -	mutex_init(&iint->mutex); -} - -static int __init ima_iintcache_init(void) -{ -	iint_cache = -	    kmem_cache_create("iint_cache", sizeof(struct ima_iint_cache), 0, -			      SLAB_PANIC, init_once); -	iint_initialized = 1; -	return 0; -} -security_initcall(ima_iintcache_init); diff --git a/security/integrity/ima/ima_main.c b/security/integrity/ima/ima_main.c index 26b46ff7466..1eff5cb001e 100644 --- a/security/integrity/ima/ima_main.c +++ b/security/integrity/ima/ima_main.c @@ -22,6 +22,7 @@  #include <linux/mount.h>  #include <linux/mman.h>  #include <linux/slab.h> +#include <linux/ima.h>  #include "ima.h" @@ -82,7 +83,7 @@ out:  				  "open_writers");  } -static void ima_check_last_writer(struct ima_iint_cache *iint, +static void ima_check_last_writer(struct integrity_iint_cache *iint,  				  struct inode *inode,  				  struct file *file)  { @@ -105,12 +106,12 @@ static void ima_check_last_writer(struct ima_iint_cache *iint,  void ima_file_free(struct file *file)  {  	struct inode *inode = file->f_dentry->d_inode; -	struct ima_iint_cache *iint; +	struct integrity_iint_cache *iint;  	if (!iint_initialized || !S_ISREG(inode->i_mode))  		return; -	iint = ima_iint_find(inode); +	iint = integrity_iint_find(inode);  	if (!iint)  		return; @@ -121,7 +122,7 @@ static int process_measurement(struct file *file, const unsigned char *filename,  			       int mask, int function)  {  	struct inode *inode = file->f_dentry->d_inode; -	struct ima_iint_cache *iint; +	struct integrity_iint_cache *iint;  	int rc = 0;  	if (!ima_initialized || !S_ISREG(inode->i_mode)) @@ -131,9 +132,9 @@ static int process_measurement(struct file *file, const unsigned char *filename,  	if (rc != 0)  		return rc;  retry: -	iint = ima_iint_find(inode); +	iint = integrity_iint_find(inode);  	if (!iint) { -		rc = ima_inode_alloc(inode); +		rc = integrity_inode_alloc(inode);  		if (!rc || rc == -EEXIST)  			goto retry;  		return rc; diff --git a/security/integrity/integrity.h b/security/integrity/integrity.h new file mode 100644 index 00000000000..3143a3c3986 --- /dev/null +++ b/security/integrity/integrity.h @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2009-2010 IBM Corporation + * + * Authors: + * Mimi Zohar <zohar@us.ibm.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2 of the + * License. + * + */ + +#include <linux/types.h> +#include <linux/integrity.h> +#include <crypto/sha.h> + +/* iint cache flags */ +#define IMA_MEASURED		0x01 + +enum evm_ima_xattr_type { +	IMA_XATTR_DIGEST = 0x01, +	EVM_XATTR_HMAC, +	EVM_IMA_XATTR_DIGSIG, +}; + +struct evm_ima_xattr_data { +	u8 type; +	u8 digest[SHA1_DIGEST_SIZE]; +}  __attribute__((packed)); + +/* integrity data associated with an inode */ +struct integrity_iint_cache { +	struct rb_node rb_node; /* rooted in integrity_iint_tree */ +	struct inode *inode;	/* back pointer to inode in question */ +	u64 version;		/* track inode changes */ +	unsigned char flags; +	u8 digest[SHA1_DIGEST_SIZE]; +	struct mutex mutex;	/* protects: version, flags, digest */ +	enum integrity_status evm_status; +}; + +/* rbtree tree calls to lookup, insert, delete + * integrity data associated with an inode. + */ +struct integrity_iint_cache *integrity_iint_insert(struct inode *inode); +struct integrity_iint_cache *integrity_iint_find(struct inode *inode); + +/* set during initialization */ +extern int iint_initialized; diff --git a/security/keys/Makefile b/security/keys/Makefile index b34cc6ee690..a56f1ffdc64 100644 --- a/security/keys/Makefile +++ b/security/keys/Makefile @@ -14,7 +14,7 @@ obj-y := \  	user_defined.o  obj-$(CONFIG_TRUSTED_KEYS) += trusted.o -obj-$(CONFIG_ENCRYPTED_KEYS) += ecryptfs_format.o encrypted.o +obj-$(CONFIG_ENCRYPTED_KEYS) += encrypted-keys/  obj-$(CONFIG_KEYS_COMPAT) += compat.o  obj-$(CONFIG_PROC_FS) += proc.o  obj-$(CONFIG_SYSCTL) += sysctl.o diff --git a/security/keys/encrypted-keys/Makefile b/security/keys/encrypted-keys/Makefile new file mode 100644 index 00000000000..6bc7a86d102 --- /dev/null +++ b/security/keys/encrypted-keys/Makefile @@ -0,0 +1,6 @@ +# +# Makefile for encrypted keys +# + +obj-$(CONFIG_ENCRYPTED_KEYS) += encrypted.o ecryptfs_format.o +obj-$(CONFIG_TRUSTED_KEYS) += masterkey_trusted.o diff --git a/security/keys/ecryptfs_format.c b/security/keys/encrypted-keys/ecryptfs_format.c index 6daa3b6ff9e..6daa3b6ff9e 100644 --- a/security/keys/ecryptfs_format.c +++ b/security/keys/encrypted-keys/ecryptfs_format.c diff --git a/security/keys/ecryptfs_format.h b/security/keys/encrypted-keys/ecryptfs_format.h index 40294de238b..40294de238b 100644 --- a/security/keys/ecryptfs_format.h +++ b/security/keys/encrypted-keys/ecryptfs_format.h diff --git a/security/keys/encrypted.c b/security/keys/encrypted-keys/encrypted.c index e7eca9ec4c6..f33804c1b4c 100644 --- a/security/keys/encrypted.c +++ b/security/keys/encrypted-keys/encrypted.c @@ -299,31 +299,6 @@ out:  }  /* - * request_trusted_key - request the trusted key - * - * Trusted keys are sealed to PCRs and other metadata. Although userspace - * manages both trusted/encrypted key-types, like the encrypted key type - * data, trusted key type data is not visible decrypted from userspace. - */ -static struct key *request_trusted_key(const char *trusted_desc, -				       u8 **master_key, size_t *master_keylen) -{ -	struct trusted_key_payload *tpayload; -	struct key *tkey; - -	tkey = request_key(&key_type_trusted, trusted_desc, NULL); -	if (IS_ERR(tkey)) -		goto error; - -	down_read(&tkey->sem); -	tpayload = rcu_dereference(tkey->payload.data); -	*master_key = tpayload->key; -	*master_keylen = tpayload->key_len; -error: -	return tkey; -} - -/*   * request_user_key - request the user key   *   * Use a user provided key to encrypt/decrypt an encrypted-key. @@ -469,8 +444,14 @@ static struct key *request_master_key(struct encrypted_key_payload *epayload,  		goto out;  	if (IS_ERR(mkey)) { -		pr_info("encrypted_key: key %s not found", -			epayload->master_desc); +		int ret = PTR_ERR(epayload); + +		if (ret == -ENOTSUPP) +			pr_info("encrypted_key: key %s not supported", +				epayload->master_desc); +		else +			pr_info("encrypted_key: key %s not found", +				epayload->master_desc);  		goto out;  	} @@ -686,11 +667,19 @@ static int encrypted_key_decrypt(struct encrypted_key_payload *epayload,  		return -EINVAL;  	hex_encoded_data = hex_encoded_iv + (2 * ivsize) + 2; -	hex2bin(epayload->iv, hex_encoded_iv, ivsize); -	hex2bin(epayload->encrypted_data, hex_encoded_data, encrypted_datalen); +	ret = hex2bin(epayload->iv, hex_encoded_iv, ivsize); +	if (ret < 0) +		return -EINVAL; +	ret = hex2bin(epayload->encrypted_data, hex_encoded_data, +		      encrypted_datalen); +	if (ret < 0) +		return -EINVAL;  	hmac = epayload->format + epayload->datablob_len; -	hex2bin(hmac, hex_encoded_data + (encrypted_datalen * 2), HASH_SIZE); +	ret = hex2bin(hmac, hex_encoded_data + (encrypted_datalen * 2), +		      HASH_SIZE); +	if (ret < 0) +		return -EINVAL;  	mkey = request_master_key(epayload, &master_key, &master_keylen);  	if (IS_ERR(mkey)) diff --git a/security/keys/encrypted.h b/security/keys/encrypted-keys/encrypted.h index cef5e2f2b7d..b6ade894525 100644 --- a/security/keys/encrypted.h +++ b/security/keys/encrypted-keys/encrypted.h @@ -2,6 +2,17 @@  #define __ENCRYPTED_KEY_H  #define ENCRYPTED_DEBUG 0 +#ifdef CONFIG_TRUSTED_KEYS +extern struct key *request_trusted_key(const char *trusted_desc, +				       u8 **master_key, size_t *master_keylen); +#else +static inline struct key *request_trusted_key(const char *trusted_desc, +					      u8 **master_key, +					      size_t *master_keylen) +{ +	return ERR_PTR(-EOPNOTSUPP); +} +#endif  #if ENCRYPTED_DEBUG  static inline void dump_master_key(const u8 *master_key, size_t master_keylen) diff --git a/security/keys/encrypted-keys/masterkey_trusted.c b/security/keys/encrypted-keys/masterkey_trusted.c new file mode 100644 index 00000000000..df87272e3f5 --- /dev/null +++ b/security/keys/encrypted-keys/masterkey_trusted.c @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2010 IBM Corporation + * Copyright (C) 2010 Politecnico di Torino, Italy + *                    TORSEC group -- http://security.polito.it + * + * Authors: + * Mimi Zohar <zohar@us.ibm.com> + * Roberto Sassu <roberto.sassu@polito.it> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 2 of the License. + * + * See Documentation/security/keys-trusted-encrypted.txt + */ + +#include <linux/uaccess.h> +#include <linux/module.h> +#include <linux/err.h> +#include <keys/trusted-type.h> + +/* + * request_trusted_key - request the trusted key + * + * Trusted keys are sealed to PCRs and other metadata. Although userspace + * manages both trusted/encrypted key-types, like the encrypted key type + * data, trusted key type data is not visible decrypted from userspace. + */ +struct key *request_trusted_key(const char *trusted_desc, +				u8 **master_key, size_t *master_keylen) +{ +	struct trusted_key_payload *tpayload; +	struct key *tkey; + +	tkey = request_key(&key_type_trusted, trusted_desc, NULL); +	if (IS_ERR(tkey)) +		goto error; + +	down_read(&tkey->sem); +	tpayload = rcu_dereference(tkey->payload.data); +	*master_key = tpayload->key; +	*master_keylen = tpayload->key_len; +error: +	return tkey; +} diff --git a/security/keys/gc.c b/security/keys/gc.c index 89df6b5f203..bf4d8da5a79 100644 --- a/security/keys/gc.c +++ b/security/keys/gc.c @@ -1,6 +1,6 @@  /* Key garbage collector   * - * Copyright (C) 2009 Red Hat, Inc. All Rights Reserved. + * Copyright (C) 2009-2011 Red Hat, Inc. All Rights Reserved.   * Written by David Howells (dhowells@redhat.com)   *   * This program is free software; you can redistribute it and/or @@ -10,6 +10,8 @@   */  #include <linux/module.h> +#include <linux/slab.h> +#include <linux/security.h>  #include <keys/keyring-type.h>  #include "internal.h" @@ -19,17 +21,33 @@  unsigned key_gc_delay = 5 * 60;  /* - * Reaper + * Reaper for unused keys. + */ +static void key_garbage_collector(struct work_struct *work); +DECLARE_WORK(key_gc_work, key_garbage_collector); + +/* + * Reaper for links from keyrings to dead keys.   */  static void key_gc_timer_func(unsigned long); -static void key_garbage_collector(struct work_struct *);  static DEFINE_TIMER(key_gc_timer, key_gc_timer_func, 0, 0); -static DECLARE_WORK(key_gc_work, key_garbage_collector); -static key_serial_t key_gc_cursor; /* the last key the gc considered */ -static bool key_gc_again; -static unsigned long key_gc_executing; +  static time_t key_gc_next_run = LONG_MAX; -static time_t key_gc_new_timer; +static struct key_type *key_gc_dead_keytype; + +static unsigned long key_gc_flags; +#define KEY_GC_KEY_EXPIRED	0	/* A key expired and needs unlinking */ +#define KEY_GC_REAP_KEYTYPE	1	/* A keytype is being unregistered */ +#define KEY_GC_REAPING_KEYTYPE	2	/* Cleared when keytype reaped */ + + +/* + * Any key whose type gets unregistered will be re-typed to this if it can't be + * immediately unlinked. + */ +struct key_type key_type_dead = { +	.name = "dead", +};  /*   * Schedule a garbage collection run. @@ -42,31 +60,75 @@ void key_schedule_gc(time_t gc_at)  	kenter("%ld", gc_at - now); -	if (gc_at <= now) { -		schedule_work(&key_gc_work); +	if (gc_at <= now || test_bit(KEY_GC_REAP_KEYTYPE, &key_gc_flags)) { +		kdebug("IMMEDIATE"); +		queue_work(system_nrt_wq, &key_gc_work);  	} else if (gc_at < key_gc_next_run) { +		kdebug("DEFERRED"); +		key_gc_next_run = gc_at;  		expires = jiffies + (gc_at - now) * HZ;  		mod_timer(&key_gc_timer, expires);  	}  }  /* - * The garbage collector timer kicked off + * Some key's cleanup time was met after it expired, so we need to get the + * reaper to go through a cycle finding expired keys.   */  static void key_gc_timer_func(unsigned long data)  {  	kenter("");  	key_gc_next_run = LONG_MAX; -	schedule_work(&key_gc_work); +	set_bit(KEY_GC_KEY_EXPIRED, &key_gc_flags); +	queue_work(system_nrt_wq, &key_gc_work); +} + +/* + * wait_on_bit() sleep function for uninterruptible waiting + */ +static int key_gc_wait_bit(void *flags) +{ +	schedule(); +	return 0; +} + +/* + * Reap keys of dead type. + * + * We use three flags to make sure we see three complete cycles of the garbage + * collector: the first to mark keys of that type as being dead, the second to + * collect dead links and the third to clean up the dead keys.  We have to be + * careful as there may already be a cycle in progress. + * + * The caller must be holding key_types_sem. + */ +void key_gc_keytype(struct key_type *ktype) +{ +	kenter("%s", ktype->name); + +	key_gc_dead_keytype = ktype; +	set_bit(KEY_GC_REAPING_KEYTYPE, &key_gc_flags); +	smp_mb(); +	set_bit(KEY_GC_REAP_KEYTYPE, &key_gc_flags); + +	kdebug("schedule"); +	queue_work(system_nrt_wq, &key_gc_work); + +	kdebug("sleep"); +	wait_on_bit(&key_gc_flags, KEY_GC_REAPING_KEYTYPE, key_gc_wait_bit, +		    TASK_UNINTERRUPTIBLE); + +	key_gc_dead_keytype = NULL; +	kleave("");  }  /*   * Garbage collect pointers from a keyring.   * - * Return true if we altered the keyring. + * Not called with any locks held.  The keyring's key struct will not be + * deallocated under us as only our caller may deallocate it.   */ -static bool key_gc_keyring(struct key *keyring, time_t limit) -	__releases(key_serial_lock) +static void key_gc_keyring(struct key *keyring, time_t limit)  {  	struct keyring_list *klist;  	struct key *key; @@ -93,130 +155,234 @@ static bool key_gc_keyring(struct key *keyring, time_t limit)  unlock_dont_gc:  	rcu_read_unlock();  dont_gc: -	kleave(" = false"); -	return false; +	kleave(" [no gc]"); +	return;  do_gc:  	rcu_read_unlock(); -	key_gc_cursor = keyring->serial; -	key_get(keyring); -	spin_unlock(&key_serial_lock); +  	keyring_gc(keyring, limit); -	key_put(keyring); -	kleave(" = true"); -	return true; +	kleave(" [gc]");  }  /* - * Garbage collector for keys.  This involves scanning the keyrings for dead, - * expired and revoked keys that have overstayed their welcome + * Garbage collect an unreferenced, detached key   */ -static void key_garbage_collector(struct work_struct *work) +static noinline void key_gc_unused_key(struct key *key)  { -	struct rb_node *rb; -	key_serial_t cursor; -	struct key *key, *xkey; -	time_t new_timer = LONG_MAX, limit, now; +	key_check(key); -	now = current_kernel_time().tv_sec; -	kenter("[%x,%ld]", key_gc_cursor, key_gc_new_timer - now); +	security_key_free(key); -	if (test_and_set_bit(0, &key_gc_executing)) { -		key_schedule_gc(current_kernel_time().tv_sec + 1); -		kleave(" [busy; deferring]"); -		return; +	/* deal with the user's key tracking and quota */ +	if (test_bit(KEY_FLAG_IN_QUOTA, &key->flags)) { +		spin_lock(&key->user->lock); +		key->user->qnkeys--; +		key->user->qnbytes -= key->quotalen; +		spin_unlock(&key->user->lock);  	} -	limit = now; +	atomic_dec(&key->user->nkeys); +	if (test_bit(KEY_FLAG_INSTANTIATED, &key->flags)) +		atomic_dec(&key->user->nikeys); + +	key_user_put(key->user); + +	/* now throw away the key memory */ +	if (key->type->destroy) +		key->type->destroy(key); + +	kfree(key->description); + +#ifdef KEY_DEBUGGING +	key->magic = KEY_DEBUG_MAGIC_X; +#endif +	kmem_cache_free(key_jar, key); +} + +/* + * Garbage collector for unused keys. + * + * This is done in process context so that we don't have to disable interrupts + * all over the place.  key_put() schedules this rather than trying to do the + * cleanup itself, which means key_put() doesn't have to sleep. + */ +static void key_garbage_collector(struct work_struct *work) +{ +	static u8 gc_state;		/* Internal persistent state */ +#define KEY_GC_REAP_AGAIN	0x01	/* - Need another cycle */ +#define KEY_GC_REAPING_LINKS	0x02	/* - We need to reap links */ +#define KEY_GC_SET_TIMER	0x04	/* - We need to restart the timer */ +#define KEY_GC_REAPING_DEAD_1	0x10	/* - We need to mark dead keys */ +#define KEY_GC_REAPING_DEAD_2	0x20	/* - We need to reap dead key links */ +#define KEY_GC_REAPING_DEAD_3	0x40	/* - We need to reap dead keys */ +#define KEY_GC_FOUND_DEAD_KEY	0x80	/* - We found at least one dead key */ + +	struct rb_node *cursor; +	struct key *key; +	time_t new_timer, limit; + +	kenter("[%lx,%x]", key_gc_flags, gc_state); + +	limit = current_kernel_time().tv_sec;  	if (limit > key_gc_delay)  		limit -= key_gc_delay;  	else  		limit = key_gc_delay; +	/* Work out what we're going to be doing in this pass */ +	gc_state &= KEY_GC_REAPING_DEAD_1 | KEY_GC_REAPING_DEAD_2; +	gc_state <<= 1; +	if (test_and_clear_bit(KEY_GC_KEY_EXPIRED, &key_gc_flags)) +		gc_state |= KEY_GC_REAPING_LINKS | KEY_GC_SET_TIMER; + +	if (test_and_clear_bit(KEY_GC_REAP_KEYTYPE, &key_gc_flags)) +		gc_state |= KEY_GC_REAPING_DEAD_1; +	kdebug("new pass %x", gc_state); + +	new_timer = LONG_MAX; + +	/* As only this function is permitted to remove things from the key +	 * serial tree, if cursor is non-NULL then it will always point to a +	 * valid node in the tree - even if lock got dropped. +	 */  	spin_lock(&key_serial_lock); +	cursor = rb_first(&key_serial_tree); -	if (unlikely(RB_EMPTY_ROOT(&key_serial_tree))) { -		spin_unlock(&key_serial_lock); -		clear_bit(0, &key_gc_executing); -		return; -	} +continue_scanning: +	while (cursor) { +		key = rb_entry(cursor, struct key, serial_node); +		cursor = rb_next(cursor); -	cursor = key_gc_cursor; -	if (cursor < 0) -		cursor = 0; -	if (cursor > 0) -		new_timer = key_gc_new_timer; -	else -		key_gc_again = false; +		if (atomic_read(&key->usage) == 0) +			goto found_unreferenced_key; -	/* find the first key above the cursor */ -	key = NULL; -	rb = key_serial_tree.rb_node; -	while (rb) { -		xkey = rb_entry(rb, struct key, serial_node); -		if (cursor < xkey->serial) { -			key = xkey; -			rb = rb->rb_left; -		} else if (cursor > xkey->serial) { -			rb = rb->rb_right; -		} else { -			rb = rb_next(rb); -			if (!rb) -				goto reached_the_end; -			key = rb_entry(rb, struct key, serial_node); -			break; +		if (unlikely(gc_state & KEY_GC_REAPING_DEAD_1)) { +			if (key->type == key_gc_dead_keytype) { +				gc_state |= KEY_GC_FOUND_DEAD_KEY; +				set_bit(KEY_FLAG_DEAD, &key->flags); +				key->perm = 0; +				goto skip_dead_key; +			} +		} + +		if (gc_state & KEY_GC_SET_TIMER) { +			if (key->expiry > limit && key->expiry < new_timer) { +				kdebug("will expire %x in %ld", +				       key_serial(key), key->expiry - limit); +				new_timer = key->expiry; +			}  		} -	} -	if (!key) -		goto reached_the_end; +		if (unlikely(gc_state & KEY_GC_REAPING_DEAD_2)) +			if (key->type == key_gc_dead_keytype) +				gc_state |= KEY_GC_FOUND_DEAD_KEY; -	/* trawl through the keys looking for keyrings */ -	for (;;) { -		if (key->expiry > limit && key->expiry < new_timer) { -			kdebug("will expire %x in %ld", -			       key_serial(key), key->expiry - limit); -			new_timer = key->expiry; +		if ((gc_state & KEY_GC_REAPING_LINKS) || +		    unlikely(gc_state & KEY_GC_REAPING_DEAD_2)) { +			if (key->type == &key_type_keyring) +				goto found_keyring;  		} -		if (key->type == &key_type_keyring && -		    key_gc_keyring(key, limit)) -			/* the gc had to release our lock so that the keyring -			 * could be modified, so we have to get it again */ -			goto gc_released_our_lock; +		if (unlikely(gc_state & KEY_GC_REAPING_DEAD_3)) +			if (key->type == key_gc_dead_keytype) +				goto destroy_dead_key; -		rb = rb_next(&key->serial_node); -		if (!rb) -			goto reached_the_end; -		key = rb_entry(rb, struct key, serial_node); +	skip_dead_key: +		if (spin_is_contended(&key_serial_lock) || need_resched()) +			goto contended;  	} -gc_released_our_lock: -	kdebug("gc_released_our_lock"); -	key_gc_new_timer = new_timer; -	key_gc_again = true; -	clear_bit(0, &key_gc_executing); -	schedule_work(&key_gc_work); -	kleave(" [continue]"); -	return; - -	/* when we reach the end of the run, we set the timer for the next one */ -reached_the_end: -	kdebug("reached_the_end"); +contended:  	spin_unlock(&key_serial_lock); -	key_gc_new_timer = new_timer; -	key_gc_cursor = 0; -	clear_bit(0, &key_gc_executing); -	if (key_gc_again) { -		/* there may have been a key that expired whilst we were -		 * scanning, so if we discarded any links we should do another -		 * scan */ -		new_timer = now + 1; -		key_schedule_gc(new_timer); -	} else if (new_timer < LONG_MAX) { +maybe_resched: +	if (cursor) { +		cond_resched(); +		spin_lock(&key_serial_lock); +		goto continue_scanning; +	} + +	/* We've completed the pass.  Set the timer if we need to and queue a +	 * new cycle if necessary.  We keep executing cycles until we find one +	 * where we didn't reap any keys. +	 */ +	kdebug("pass complete"); + +	if (gc_state & KEY_GC_SET_TIMER && new_timer != (time_t)LONG_MAX) {  		new_timer += key_gc_delay;  		key_schedule_gc(new_timer);  	} -	kleave(" [end]"); + +	if (unlikely(gc_state & KEY_GC_REAPING_DEAD_2)) { +		/* Make sure everyone revalidates their keys if we marked a +		 * bunch as being dead and make sure all keyring ex-payloads +		 * are destroyed. +		 */ +		kdebug("dead sync"); +		synchronize_rcu(); +	} + +	if (unlikely(gc_state & (KEY_GC_REAPING_DEAD_1 | +				 KEY_GC_REAPING_DEAD_2))) { +		if (!(gc_state & KEY_GC_FOUND_DEAD_KEY)) { +			/* No remaining dead keys: short circuit the remaining +			 * keytype reap cycles. +			 */ +			kdebug("dead short"); +			gc_state &= ~(KEY_GC_REAPING_DEAD_1 | KEY_GC_REAPING_DEAD_2); +			gc_state |= KEY_GC_REAPING_DEAD_3; +		} else { +			gc_state |= KEY_GC_REAP_AGAIN; +		} +	} + +	if (unlikely(gc_state & KEY_GC_REAPING_DEAD_3)) { +		kdebug("dead wake"); +		smp_mb(); +		clear_bit(KEY_GC_REAPING_KEYTYPE, &key_gc_flags); +		wake_up_bit(&key_gc_flags, KEY_GC_REAPING_KEYTYPE); +	} + +	if (gc_state & KEY_GC_REAP_AGAIN) +		queue_work(system_nrt_wq, &key_gc_work); +	kleave(" [end %x]", gc_state); +	return; + +	/* We found an unreferenced key - once we've removed it from the tree, +	 * we can safely drop the lock. +	 */ +found_unreferenced_key: +	kdebug("unrefd key %d", key->serial); +	rb_erase(&key->serial_node, &key_serial_tree); +	spin_unlock(&key_serial_lock); + +	key_gc_unused_key(key); +	gc_state |= KEY_GC_REAP_AGAIN; +	goto maybe_resched; + +	/* We found a keyring and we need to check the payload for links to +	 * dead or expired keys.  We don't flag another reap immediately as we +	 * have to wait for the old payload to be destroyed by RCU before we +	 * can reap the keys to which it refers. +	 */ +found_keyring: +	spin_unlock(&key_serial_lock); +	kdebug("scan keyring %d", key->serial); +	key_gc_keyring(key, limit); +	goto maybe_resched; + +	/* We found a dead key that is still referenced.  Reset its type and +	 * destroy its payload with its semaphore held. +	 */ +destroy_dead_key: +	spin_unlock(&key_serial_lock); +	kdebug("destroy key %d", key->serial); +	down_write(&key->sem); +	key->type = &key_type_dead; +	if (key_gc_dead_keytype->destroy) +		key_gc_dead_keytype->destroy(key); +	memset(&key->payload, KEY_DESTROY, sizeof(key->payload)); +	up_write(&key->sem); +	goto maybe_resched;  } diff --git a/security/keys/internal.h b/security/keys/internal.h index f375152a250..c7a7caec483 100644 --- a/security/keys/internal.h +++ b/security/keys/internal.h @@ -31,6 +31,7 @@  	no_printk(KERN_DEBUG FMT"\n", ##__VA_ARGS__)  #endif +extern struct key_type key_type_dead;  extern struct key_type key_type_user;  /*****************************************************************************/ @@ -75,6 +76,7 @@ extern unsigned key_quota_maxbytes;  #define KEYQUOTA_LINK_BYTES	4		/* a link in a keyring is worth 4 bytes */ +extern struct kmem_cache *key_jar;  extern struct rb_root key_serial_tree;  extern spinlock_t key_serial_lock;  extern struct mutex key_construction_mutex; @@ -146,9 +148,11 @@ extern key_ref_t lookup_user_key(key_serial_t id, unsigned long flags,  extern long join_session_keyring(const char *name); +extern struct work_struct key_gc_work;  extern unsigned key_gc_delay;  extern void keyring_gc(struct key *keyring, time_t limit);  extern void key_schedule_gc(time_t expiry_at); +extern void key_gc_keytype(struct key_type *ktype);  extern int key_task_permission(const key_ref_t key_ref,  			       const struct cred *cred, diff --git a/security/keys/key.c b/security/keys/key.c index f7f9d93f08d..4414abddcb5 100644 --- a/security/keys/key.c +++ b/security/keys/key.c @@ -21,7 +21,7 @@  #include <linux/user_namespace.h>  #include "internal.h" -static struct kmem_cache	*key_jar; +struct kmem_cache *key_jar;  struct rb_root		key_serial_tree; /* tree of keys indexed by serial */  DEFINE_SPINLOCK(key_serial_lock); @@ -36,17 +36,9 @@ unsigned int key_quota_maxbytes = 20000;	/* general key space quota */  static LIST_HEAD(key_types_list);  static DECLARE_RWSEM(key_types_sem); -static void key_cleanup(struct work_struct *work); -static DECLARE_WORK(key_cleanup_task, key_cleanup); -  /* We serialise key instantiation and link */  DEFINE_MUTEX(key_construction_mutex); -/* Any key who's type gets unegistered will be re-typed to this */ -static struct key_type key_type_dead = { -	.name		= "dead", -}; -  #ifdef KEY_DEBUGGING  void __key_check(const struct key *key)  { @@ -591,71 +583,6 @@ int key_reject_and_link(struct key *key,  }  EXPORT_SYMBOL(key_reject_and_link); -/* - * Garbage collect keys in process context so that we don't have to disable - * interrupts all over the place. - * - * key_put() schedules this rather than trying to do the cleanup itself, which - * means key_put() doesn't have to sleep. - */ -static void key_cleanup(struct work_struct *work) -{ -	struct rb_node *_n; -	struct key *key; - -go_again: -	/* look for a dead key in the tree */ -	spin_lock(&key_serial_lock); - -	for (_n = rb_first(&key_serial_tree); _n; _n = rb_next(_n)) { -		key = rb_entry(_n, struct key, serial_node); - -		if (atomic_read(&key->usage) == 0) -			goto found_dead_key; -	} - -	spin_unlock(&key_serial_lock); -	return; - -found_dead_key: -	/* we found a dead key - once we've removed it from the tree, we can -	 * drop the lock */ -	rb_erase(&key->serial_node, &key_serial_tree); -	spin_unlock(&key_serial_lock); - -	key_check(key); - -	security_key_free(key); - -	/* deal with the user's key tracking and quota */ -	if (test_bit(KEY_FLAG_IN_QUOTA, &key->flags)) { -		spin_lock(&key->user->lock); -		key->user->qnkeys--; -		key->user->qnbytes -= key->quotalen; -		spin_unlock(&key->user->lock); -	} - -	atomic_dec(&key->user->nkeys); -	if (test_bit(KEY_FLAG_INSTANTIATED, &key->flags)) -		atomic_dec(&key->user->nikeys); - -	key_user_put(key->user); - -	/* now throw away the key memory */ -	if (key->type->destroy) -		key->type->destroy(key); - -	kfree(key->description); - -#ifdef KEY_DEBUGGING -	key->magic = KEY_DEBUG_MAGIC_X; -#endif -	kmem_cache_free(key_jar, key); - -	/* there may, of course, be more than one key to destroy */ -	goto go_again; -} -  /**   * key_put - Discard a reference to a key.   * @key: The key to discard a reference from. @@ -670,7 +597,7 @@ void key_put(struct key *key)  		key_check(key);  		if (atomic_dec_and_test(&key->usage)) -			schedule_work(&key_cleanup_task); +			queue_work(system_nrt_wq, &key_gc_work);  	}  }  EXPORT_SYMBOL(key_put); @@ -1048,49 +975,11 @@ EXPORT_SYMBOL(register_key_type);   */  void unregister_key_type(struct key_type *ktype)  { -	struct rb_node *_n; -	struct key *key; -  	down_write(&key_types_sem); - -	/* withdraw the key type */  	list_del_init(&ktype->link); - -	/* mark all the keys of this type dead */ -	spin_lock(&key_serial_lock); - -	for (_n = rb_first(&key_serial_tree); _n; _n = rb_next(_n)) { -		key = rb_entry(_n, struct key, serial_node); - -		if (key->type == ktype) { -			key->type = &key_type_dead; -			set_bit(KEY_FLAG_DEAD, &key->flags); -		} -	} - -	spin_unlock(&key_serial_lock); - -	/* make sure everyone revalidates their keys */ -	synchronize_rcu(); - -	/* we should now be able to destroy the payloads of all the keys of -	 * this type with impunity */ -	spin_lock(&key_serial_lock); - -	for (_n = rb_first(&key_serial_tree); _n; _n = rb_next(_n)) { -		key = rb_entry(_n, struct key, serial_node); - -		if (key->type == ktype) { -			if (ktype->destroy) -				ktype->destroy(key); -			memset(&key->payload, KEY_DESTROY, sizeof(key->payload)); -		} -	} - -	spin_unlock(&key_serial_lock); -	up_write(&key_types_sem); - -	key_schedule_gc(0); +	downgrade_write(&key_types_sem); +	key_gc_keytype(ktype); +	up_read(&key_types_sem);  }  EXPORT_SYMBOL(unregister_key_type); diff --git a/security/keys/keyring.c b/security/keys/keyring.c index 30e242f7bd0..37a7f3b2885 100644 --- a/security/keys/keyring.c +++ b/security/keys/keyring.c @@ -860,8 +860,7 @@ void __key_link(struct key *keyring, struct key *key,  	kenter("%d,%d,%p", keyring->serial, key->serial, nklist); -	klist = rcu_dereference_protected(keyring->payload.subscriptions, -					  rwsem_is_locked(&keyring->sem)); +	klist = rcu_dereference_locked_keyring(keyring);  	atomic_inc(&key->usage); diff --git a/security/keys/process_keys.c b/security/keys/process_keys.c index a3063eb3dc2..1068cb1939b 100644 --- a/security/keys/process_keys.c +++ b/security/keys/process_keys.c @@ -270,7 +270,7 @@ static int install_session_keyring(struct key *keyring)  	if (!new)  		return -ENOMEM; -	ret = install_session_keyring_to_cred(new, NULL); +	ret = install_session_keyring_to_cred(new, keyring);  	if (ret < 0) {  		abort_creds(new);  		return ret; @@ -589,12 +589,22 @@ try_again:  			ret = install_user_keyrings();  			if (ret < 0)  				goto error; -			ret = install_session_keyring( -				cred->user->session_keyring); +			if (lflags & KEY_LOOKUP_CREATE) +				ret = join_session_keyring(NULL); +			else +				ret = install_session_keyring( +					cred->user->session_keyring);  			if (ret < 0)  				goto error;  			goto reget_creds; +		} else if (cred->tgcred->session_keyring == +			   cred->user->session_keyring && +			   lflags & KEY_LOOKUP_CREATE) { +			ret = join_session_keyring(NULL); +			if (ret < 0) +				goto error; +			goto reget_creds;  		}  		rcu_read_lock(); diff --git a/security/keys/trusted.c b/security/keys/trusted.c index 0c33e2ea1f3..0964fc23694 100644 --- a/security/keys/trusted.c +++ b/security/keys/trusted.c @@ -779,7 +779,10 @@ static int getoptions(char *c, struct trusted_key_payload *pay,  			opt->pcrinfo_len = strlen(args[0].from) / 2;  			if (opt->pcrinfo_len > MAX_PCRINFO_SIZE)  				return -EINVAL; -			hex2bin(opt->pcrinfo, args[0].from, opt->pcrinfo_len); +			res = hex2bin(opt->pcrinfo, args[0].from, +				      opt->pcrinfo_len); +			if (res < 0) +				return -EINVAL;  			break;  		case Opt_keyhandle:  			res = strict_strtoul(args[0].from, 16, &handle); @@ -791,12 +794,18 @@ static int getoptions(char *c, struct trusted_key_payload *pay,  		case Opt_keyauth:  			if (strlen(args[0].from) != 2 * SHA1_DIGEST_SIZE)  				return -EINVAL; -			hex2bin(opt->keyauth, args[0].from, SHA1_DIGEST_SIZE); +			res = hex2bin(opt->keyauth, args[0].from, +				      SHA1_DIGEST_SIZE); +			if (res < 0) +				return -EINVAL;  			break;  		case Opt_blobauth:  			if (strlen(args[0].from) != 2 * SHA1_DIGEST_SIZE)  				return -EINVAL; -			hex2bin(opt->blobauth, args[0].from, SHA1_DIGEST_SIZE); +			res = hex2bin(opt->blobauth, args[0].from, +				      SHA1_DIGEST_SIZE); +			if (res < 0) +				return -EINVAL;  			break;  		case Opt_migratable:  			if (*args[0].from == '0') @@ -860,7 +869,9 @@ static int datablob_parse(char *datablob, struct trusted_key_payload *p,  		p->blob_len = strlen(c) / 2;  		if (p->blob_len > MAX_BLOB_SIZE)  			return -EINVAL; -		hex2bin(p->blob, c, p->blob_len); +		ret = hex2bin(p->blob, c, p->blob_len); +		if (ret < 0) +			return -EINVAL;  		ret = getoptions(datablob, p, o);  		if (ret < 0)  			return ret; diff --git a/security/security.c b/security/security.c index 0e4fccfef12..0c6cc69c8f8 100644 --- a/security/security.c +++ b/security/security.c @@ -16,15 +16,16 @@  #include <linux/init.h>  #include <linux/kernel.h>  #include <linux/security.h> +#include <linux/integrity.h>  #include <linux/ima.h> +#include <linux/evm.h> + +#define MAX_LSM_EVM_XATTR	2  /* Boot-time LSM user choice */  static __initdata char chosen_lsm[SECURITY_NAME_MAX + 1] =  	CONFIG_DEFAULT_SECURITY; -/* things that live in capability.c */ -extern void __init security_fixup_ops(struct security_operations *ops); -  static struct security_operations *security_ops;  static struct security_operations default_security_ops = {  	.name	= "default", @@ -334,20 +335,57 @@ int security_inode_alloc(struct inode *inode)  void security_inode_free(struct inode *inode)  { -	ima_inode_free(inode); +	integrity_inode_free(inode);  	security_ops->inode_free_security(inode);  }  int security_inode_init_security(struct inode *inode, struct inode *dir, -				 const struct qstr *qstr, char **name, -				 void **value, size_t *len) +				 const struct qstr *qstr, +				 const initxattrs initxattrs, void *fs_data)  { +	struct xattr new_xattrs[MAX_LSM_EVM_XATTR + 1]; +	struct xattr *lsm_xattr, *evm_xattr, *xattr; +	int ret; +  	if (unlikely(IS_PRIVATE(inode))) -		return -EOPNOTSUPP; +		return 0; + +	memset(new_xattrs, 0, sizeof new_xattrs); +	if (!initxattrs) +		return security_ops->inode_init_security(inode, dir, qstr, +							 NULL, NULL, NULL); +	lsm_xattr = new_xattrs; +	ret = security_ops->inode_init_security(inode, dir, qstr, +						&lsm_xattr->name, +						&lsm_xattr->value, +						&lsm_xattr->value_len); +	if (ret) +		goto out; + +	evm_xattr = lsm_xattr + 1; +	ret = evm_inode_init_security(inode, lsm_xattr, evm_xattr); +	if (ret) +		goto out; +	ret = initxattrs(inode, new_xattrs, fs_data); +out: +	for (xattr = new_xattrs; xattr->name != NULL; xattr++) { +		kfree(xattr->name); +		kfree(xattr->value); +	} +	return (ret == -EOPNOTSUPP) ? 0 : ret; +} +EXPORT_SYMBOL(security_inode_init_security); + +int security_old_inode_init_security(struct inode *inode, struct inode *dir, +				     const struct qstr *qstr, char **name, +				     void **value, size_t *len) +{ +	if (unlikely(IS_PRIVATE(inode))) +		return 0;  	return security_ops->inode_init_security(inode, dir, qstr, name, value,  						 len);  } -EXPORT_SYMBOL(security_inode_init_security); +EXPORT_SYMBOL(security_old_inode_init_security);  #ifdef CONFIG_SECURITY_PATH  int security_path_mknod(struct path *dir, struct dentry *dentry, int mode, @@ -523,9 +561,14 @@ int security_inode_permission(struct inode *inode, int mask)  int security_inode_setattr(struct dentry *dentry, struct iattr *attr)  { +	int ret; +  	if (unlikely(IS_PRIVATE(dentry->d_inode)))  		return 0; -	return security_ops->inode_setattr(dentry, attr); +	ret = security_ops->inode_setattr(dentry, attr); +	if (ret) +		return ret; +	return evm_inode_setattr(dentry, attr);  }  EXPORT_SYMBOL_GPL(security_inode_setattr); @@ -539,9 +582,14 @@ int security_inode_getattr(struct vfsmount *mnt, struct dentry *dentry)  int security_inode_setxattr(struct dentry *dentry, const char *name,  			    const void *value, size_t size, int flags)  { +	int ret; +  	if (unlikely(IS_PRIVATE(dentry->d_inode)))  		return 0; -	return security_ops->inode_setxattr(dentry, name, value, size, flags); +	ret = security_ops->inode_setxattr(dentry, name, value, size, flags); +	if (ret) +		return ret; +	return evm_inode_setxattr(dentry, name, value, size);  }  void security_inode_post_setxattr(struct dentry *dentry, const char *name, @@ -550,6 +598,7 @@ void security_inode_post_setxattr(struct dentry *dentry, const char *name,  	if (unlikely(IS_PRIVATE(dentry->d_inode)))  		return;  	security_ops->inode_post_setxattr(dentry, name, value, size, flags); +	evm_inode_post_setxattr(dentry, name, value, size);  }  int security_inode_getxattr(struct dentry *dentry, const char *name) @@ -568,9 +617,14 @@ int security_inode_listxattr(struct dentry *dentry)  int security_inode_removexattr(struct dentry *dentry, const char *name)  { +	int ret; +  	if (unlikely(IS_PRIVATE(dentry->d_inode)))  		return 0; -	return security_ops->inode_removexattr(dentry, name); +	ret = security_ops->inode_removexattr(dentry, name); +	if (ret) +		return ret; +	return evm_inode_removexattr(dentry, name);  }  int security_inode_need_killpriv(struct dentry *dentry) @@ -1097,6 +1151,7 @@ void security_sk_clone(const struct sock *sk, struct sock *newsk)  {  	security_ops->sk_clone_security(sk, newsk);  } +EXPORT_SYMBOL(security_sk_clone);  void security_sk_classify_flow(struct sock *sk, struct flowi *fl)  { diff --git a/security/selinux/exports.c b/security/selinux/exports.c index 90664385dea..e75dd94e2d2 100644 --- a/security/selinux/exports.c +++ b/security/selinux/exports.c @@ -12,6 +12,7 @@   * as published by the Free Software Foundation.   */  #include <linux/module.h> +#include <linux/selinux.h>  #include "security.h" diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index 266a2292451..e545b9f6707 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -89,14 +89,14 @@  #include "xfrm.h"  #include "netlabel.h"  #include "audit.h" +#include "avc_ss.h"  #define NUM_SEL_MNT_OPTS 5 -extern int selinux_nlmsg_lookup(u16 sclass, u16 nlmsg_type, u32 *perm);  extern struct security_operations *security_ops;  /* SECMARK reference count */ -atomic_t selinux_secmark_refcount = ATOMIC_INIT(0); +static atomic_t selinux_secmark_refcount = ATOMIC_INIT(0);  #ifdef CONFIG_SECURITY_SELINUX_DEVELOP  int selinux_enforcing; @@ -279,10 +279,6 @@ static void superblock_free_security(struct super_block *sb)  	kfree(sbsec);  } -/* The security server must be initialized before -   any labeling or access decisions can be provided. */ -extern int ss_initialized; -  /* The file system's label must be initialized prior to use. */  static const char *labeling_behaviors[6] = { @@ -2097,9 +2093,6 @@ static int selinux_bprm_secureexec(struct linux_binprm *bprm)  	return (atsecure || cap_bprm_secureexec(bprm));  } -extern struct vfsmount *selinuxfs_mount; -extern struct dentry *selinux_null; -  /* Derived from fs/exec.c:flush_old_files. */  static inline void flush_unauthorized_files(const struct cred *cred,  					    struct files_struct *files) @@ -5803,8 +5796,6 @@ static int selinux_disabled;  int selinux_disable(void)  { -	extern void exit_sel_fs(void); -  	if (ss_initialized) {  		/* Not permitted after initial policy load. */  		return -EINVAL; diff --git a/security/selinux/include/avc_ss.h b/security/selinux/include/avc_ss.h index 4677aa519b0..d5c328452df 100644 --- a/security/selinux/include/avc_ss.h +++ b/security/selinux/include/avc_ss.h @@ -18,5 +18,11 @@ struct security_class_mapping {  extern struct security_class_mapping secclass_map[]; +/* + * The security server must be initialized before + * any labeling or access decisions can be provided. + */ +extern int ss_initialized; +  #endif /* _SELINUX_AVC_SS_H_ */ diff --git a/security/selinux/include/security.h b/security/selinux/include/security.h index 3ba4feba048..d871e8ad210 100644 --- a/security/selinux/include/security.h +++ b/security/selinux/include/security.h @@ -216,6 +216,14 @@ struct selinux_kernel_status {  extern void selinux_status_update_setenforce(int enforcing);  extern void selinux_status_update_policyload(int seqno); +extern void selinux_complete_init(void); +extern int selinux_disable(void); +extern void exit_sel_fs(void); +extern struct dentry *selinux_null; +extern struct vfsmount *selinuxfs_mount; +extern void selnl_notify_setenforce(int val); +extern void selnl_notify_policyload(u32 seqno); +extern int selinux_nlmsg_lookup(u16 sclass, u16 nlmsg_type, u32 *perm);  #endif /* _SELINUX_SECURITY_H_ */ diff --git a/security/selinux/netlink.c b/security/selinux/netlink.c index 36ac257cec9..ce3f481558d 100644 --- a/security/selinux/netlink.c +++ b/security/selinux/netlink.c @@ -19,6 +19,8 @@  #include <linux/selinux_netlink.h>  #include <net/net_namespace.h> +#include "security.h" +  static struct sock *selnl;  static int selnl_msglen(int msgtype) diff --git a/security/selinux/nlmsgtab.c b/security/selinux/nlmsgtab.c index 8b02b2137da..0920ea3bf59 100644 --- a/security/selinux/nlmsgtab.c +++ b/security/selinux/nlmsgtab.c @@ -21,6 +21,7 @@  #include "flask.h"  #include "av_permissions.h" +#include "security.h"  struct nlmsg_perm {  	u16	nlmsg_type; diff --git a/security/selinux/selinuxfs.c b/security/selinux/selinuxfs.c index 55d92cbb177..f46658722c7 100644 --- a/security/selinux/selinuxfs.c +++ b/security/selinux/selinuxfs.c @@ -75,8 +75,6 @@ static char policy_opened;  /* global data for policy capabilities */  static struct dentry *policycap_dir; -extern void selnl_notify_setenforce(int val); -  /* Check whether a task is allowed to use a security operation. */  static int task_has_security(struct task_struct *tsk,  			     u32 perms) @@ -278,7 +276,6 @@ static ssize_t sel_write_disable(struct file *file, const char __user *buf,  	char *page = NULL;  	ssize_t length;  	int new_value; -	extern int selinux_disable(void);  	length = -ENOMEM;  	if (count >= PAGE_SIZE) @@ -478,7 +475,7 @@ static struct vm_operations_struct sel_mmap_policy_ops = {  	.page_mkwrite = sel_mmap_policy_fault,  }; -int sel_mmap_policy(struct file *filp, struct vm_area_struct *vma) +static int sel_mmap_policy(struct file *filp, struct vm_area_struct *vma)  {  	if (vma->vm_flags & VM_SHARED) {  		/* do not allow mprotect to make mapping writable */ diff --git a/security/selinux/ss/conditional.c b/security/selinux/ss/conditional.c index a53373207fb..2ec904177fe 100644 --- a/security/selinux/ss/conditional.c +++ b/security/selinux/ss/conditional.c @@ -555,7 +555,7 @@ static int cond_write_av_list(struct policydb *p,  	return 0;  } -int cond_write_node(struct policydb *p, struct cond_node *node, +static int cond_write_node(struct policydb *p, struct cond_node *node,  		    struct policy_file *fp)  {  	struct cond_expr *cur_expr; diff --git a/security/selinux/ss/conditional.h b/security/selinux/ss/conditional.h index 3f209c63529..4d1f8746650 100644 --- a/security/selinux/ss/conditional.h +++ b/security/selinux/ss/conditional.h @@ -13,6 +13,7 @@  #include "avtab.h"  #include "symtab.h"  #include "policydb.h" +#include "../include/conditional.h"  #define COND_EXPR_MAXDEPTH 10 diff --git a/security/selinux/ss/policydb.c b/security/selinux/ss/policydb.c index 2381d0ded22..a7f61d52f05 100644 --- a/security/selinux/ss/policydb.c +++ b/security/selinux/ss/policydb.c @@ -1743,8 +1743,6 @@ static int policydb_bounds_sanity_check(struct policydb *p)  	return 0;  } -extern int ss_initialized; -  u16 string_to_security_class(struct policydb *p, const char *name)  {  	struct class_datum *cladatum; diff --git a/security/selinux/ss/services.c b/security/selinux/ss/services.c index f6917bc0aa0..185f849a26f 100644 --- a/security/selinux/ss/services.c +++ b/security/selinux/ss/services.c @@ -70,8 +70,6 @@  #include "ebitmap.h"  #include "audit.h" -extern void selnl_notify_policyload(u32 seqno); -  int selinux_policycap_netpeer;  int selinux_policycap_openperm; @@ -1790,7 +1788,6 @@ static void security_load_policycaps(void)  						  POLICYDB_CAPABILITY_OPENPERM);  } -extern void selinux_complete_init(void);  static int security_preserve_bools(struct policydb *p);  /** diff --git a/security/smack/smack.h b/security/smack/smack.h index 2b6c6a51612..2ad00657b80 100644 --- a/security/smack/smack.h +++ b/security/smack/smack.h @@ -41,9 +41,9 @@ struct superblock_smack {  };  struct socket_smack { -	char		*smk_out;			/* outbound label */ -	char		*smk_in;			/* inbound label */ -	char		smk_packet[SMK_LABELLEN];	/* TCP peer label */ +	char		*smk_out;	/* outbound label */ +	char		*smk_in;	/* inbound label */ +	char		*smk_packet;	/* TCP peer label */  };  /* @@ -116,13 +116,19 @@ struct smk_netlbladdr {   * If there is a cipso value associated with the label it   * gets stored here, too. This will most likely be rare as   * the cipso direct mapping in used internally. + * + * Keep the access rules for this subject label here so that + * the entire set of rules does not need to be examined every + * time.   */  struct smack_known {  	struct list_head	list;  	char			smk_known[SMK_LABELLEN];  	u32			smk_secid;  	struct smack_cipso	*smk_cipso; -	spinlock_t		smk_cipsolock; /* for changing cipso map */ +	spinlock_t		smk_cipsolock;	/* for changing cipso map */ +	struct list_head	smk_rules;	/* access rules */ +	struct mutex		smk_rules_lock;	/* lock for the rules */  };  /* @@ -150,7 +156,6 @@ struct smack_known {  /*   * smackfs magic number - * smackfs macic number   */  #define SMACK_MAGIC	0x43415d53 /* "SMAC" */ @@ -176,9 +181,9 @@ struct smack_known {  #define MAY_NOT		0  /* - * Number of access types used by Smack (rwxa) + * Number of access types used by Smack (rwxat)   */ -#define SMK_NUM_ACCESS_TYPE 4 +#define SMK_NUM_ACCESS_TYPE 5  /*   * Smack audit data; is empty if CONFIG_AUDIT not set @@ -201,10 +206,12 @@ int smk_access_entry(char *, char *, struct list_head *);  int smk_access(char *, char *, int, struct smk_audit_info *);  int smk_curacc(char *, u32, struct smk_audit_info *);  int smack_to_cipso(const char *, struct smack_cipso *); -void smack_from_cipso(u32, char *, char *); +char *smack_from_cipso(u32, char *);  char *smack_from_secid(const u32); +void smk_parse_smack(const char *string, int len, char *smack);  char *smk_import(const char *, int);  struct smack_known *smk_import_entry(const char *, int); +struct smack_known *smk_find_entry(const char *);  u32 smack_to_secid(const char *);  /* @@ -223,7 +230,6 @@ extern struct smack_known smack_known_star;  extern struct smack_known smack_known_web;  extern struct list_head smack_known_list; -extern struct list_head smack_rule_list;  extern struct list_head smk_netlbladdr_list;  extern struct security_operations smack_ops; diff --git a/security/smack/smack_access.c b/security/smack/smack_access.c index 9637e107f7e..cc7cb6edba0 100644 --- a/security/smack/smack_access.c +++ b/security/smack/smack_access.c @@ -77,14 +77,19 @@ int log_policy = SMACK_AUDIT_DENIED;   * entry is found returns -ENOENT.   *   * NOTE: - * Even though Smack labels are usually shared on smack_list - * labels that come in off the network can't be imported - * and added to the list for locking reasons.   * - * Therefore, it is necessary to check the contents of the labels, - * not just the pointer values. Of course, in most cases the labels - * will be on the list, so checking the pointers may be a worthwhile - * optimization. + * Earlier versions of this function allowed for labels that + * were not on the label list. This was done to allow for + * labels to come over the network that had never been seen + * before on this host. Unless the receiving socket has the + * star label this will always result in a failure check. The + * star labeled socket case is now handled in the networking + * hooks so there is no case where the label is not on the + * label list. Checking to see if the address of two labels + * is the same is now a reliable test. + * + * Do the object check first because that is more + * likely to differ.   */  int smk_access_entry(char *subject_label, char *object_label,  			struct list_head *rule_list) @@ -93,13 +98,10 @@ int smk_access_entry(char *subject_label, char *object_label,  	struct smack_rule *srp;  	list_for_each_entry_rcu(srp, rule_list, list) { -		if (srp->smk_subject == subject_label || -		    strcmp(srp->smk_subject, subject_label) == 0) { -			if (srp->smk_object == object_label || -			    strcmp(srp->smk_object, object_label) == 0) { -				may = srp->smk_access; -				break; -			} +		if (srp->smk_object == object_label && +		    srp->smk_subject == subject_label) { +			may = srp->smk_access; +			break;  		}  	} @@ -117,18 +119,12 @@ int smk_access_entry(char *subject_label, char *object_label,   * access rule list and returns 0 if the access is permitted,   * non zero otherwise.   * - * Even though Smack labels are usually shared on smack_list - * labels that come in off the network can't be imported - * and added to the list for locking reasons. - * - * Therefore, it is necessary to check the contents of the labels, - * not just the pointer values. Of course, in most cases the labels - * will be on the list, so checking the pointers may be a worthwhile - * optimization. + * Smack labels are shared on smack_list   */  int smk_access(char *subject_label, char *object_label, int request,  	       struct smk_audit_info *a)  { +	struct smack_known *skp;  	int may = MAY_NOT;  	int rc = 0; @@ -137,8 +133,7 @@ int smk_access(char *subject_label, char *object_label, int request,  	 *  	 * A star subject can't access any object.  	 */ -	if (subject_label == smack_known_star.smk_known || -	    strcmp(subject_label, smack_known_star.smk_known) == 0) { +	if (subject_label == smack_known_star.smk_known) {  		rc = -EACCES;  		goto out_audit;  	} @@ -148,33 +143,27 @@ int smk_access(char *subject_label, char *object_label, int request,  	 * An internet subject can access any object.  	 */  	if (object_label == smack_known_web.smk_known || -	    subject_label == smack_known_web.smk_known || -	    strcmp(object_label, smack_known_web.smk_known) == 0 || -	    strcmp(subject_label, smack_known_web.smk_known) == 0) +	    subject_label == smack_known_web.smk_known)  		goto out_audit;  	/*  	 * A star object can be accessed by any subject.  	 */ -	if (object_label == smack_known_star.smk_known || -	    strcmp(object_label, smack_known_star.smk_known) == 0) +	if (object_label == smack_known_star.smk_known)  		goto out_audit;  	/*  	 * An object can be accessed in any way by a subject  	 * with the same label.  	 */ -	if (subject_label == object_label || -	    strcmp(subject_label, object_label) == 0) +	if (subject_label == object_label)  		goto out_audit;  	/*  	 * A hat subject can read any object.  	 * A floor object can be read by any subject.  	 */  	if ((request & MAY_ANYREAD) == request) { -		if (object_label == smack_known_floor.smk_known || -		    strcmp(object_label, smack_known_floor.smk_known) == 0) +		if (object_label == smack_known_floor.smk_known)  			goto out_audit; -		if (subject_label == smack_known_hat.smk_known || -		    strcmp(subject_label, smack_known_hat.smk_known) == 0) +		if (subject_label == smack_known_hat.smk_known)  			goto out_audit;  	}  	/* @@ -184,8 +173,9 @@ int smk_access(char *subject_label, char *object_label, int request,  	 * good. A negative response from smk_access_entry()  	 * indicates there is no entry for this pair.  	 */ +	skp = smk_find_entry(subject_label);  	rcu_read_lock(); -	may = smk_access_entry(subject_label, object_label, &smack_rule_list); +	may = smk_access_entry(subject_label, object_label, &skp->smk_rules);  	rcu_read_unlock();  	if (may > 0 && (request & may) == request) @@ -344,17 +334,32 @@ void smack_log(char *subject_label, char *object_label, int request,  static DEFINE_MUTEX(smack_known_lock);  /** - * smk_import_entry - import a label, return the list entry + * smk_find_entry - find a label on the list, return the list entry   * @string: a text string that might be a Smack label - * @len: the maximum size, or zero if it is NULL terminated.   *   * Returns a pointer to the entry in the label list that - * matches the passed string, adding it if necessary. + * matches the passed string.   */ -struct smack_known *smk_import_entry(const char *string, int len) +struct smack_known *smk_find_entry(const char *string)  {  	struct smack_known *skp; -	char smack[SMK_LABELLEN]; + +	list_for_each_entry_rcu(skp, &smack_known_list, list) { +		if (strncmp(skp->smk_known, string, SMK_MAXLEN) == 0) +			return skp; +	} + +	return NULL; +} + +/** + * smk_parse_smack - parse smack label from a text string + * @string: a text string that might contain a Smack label + * @len: the maximum size, or zero if it is NULL terminated. + * @smack: parsed smack label, or NULL if parse error + */ +void smk_parse_smack(const char *string, int len, char *smack) +{  	int found;  	int i; @@ -372,27 +377,38 @@ struct smack_known *smk_import_entry(const char *string, int len)  		} else  			smack[i] = string[i];  	} +} + +/** + * smk_import_entry - import a label, return the list entry + * @string: a text string that might be a Smack label + * @len: the maximum size, or zero if it is NULL terminated. + * + * Returns a pointer to the entry in the label list that + * matches the passed string, adding it if necessary. + */ +struct smack_known *smk_import_entry(const char *string, int len) +{ +	struct smack_known *skp; +	char smack[SMK_LABELLEN]; +	smk_parse_smack(string, len, smack);  	if (smack[0] == '\0')  		return NULL;  	mutex_lock(&smack_known_lock); -	found = 0; -	list_for_each_entry_rcu(skp, &smack_known_list, list) { -		if (strncmp(skp->smk_known, smack, SMK_MAXLEN) == 0) { -			found = 1; -			break; -		} -	} +	skp = smk_find_entry(smack); -	if (found == 0) { +	if (skp == NULL) {  		skp = kzalloc(sizeof(struct smack_known), GFP_KERNEL);  		if (skp != NULL) {  			strncpy(skp->smk_known, smack, SMK_MAXLEN);  			skp->smk_secid = smack_next_secid++;  			skp->smk_cipso = NULL; +			INIT_LIST_HEAD(&skp->smk_rules);  			spin_lock_init(&skp->smk_cipsolock); +			mutex_init(&skp->smk_rules_lock);  			/*  			 * Make sure that the entry is actually  			 * filled before putting it on the list. @@ -480,19 +496,12 @@ u32 smack_to_secid(const char *smack)   * smack_from_cipso - find the Smack label associated with a CIPSO option   * @level: Bell & LaPadula level from the network   * @cp: Bell & LaPadula categories from the network - * @result: where to put the Smack value   *   * This is a simple lookup in the label table.   * - * This is an odd duck as far as smack handling goes in that - * it sends back a copy of the smack label rather than a pointer - * to the master list. This is done because it is possible for - * a foreign host to send a smack label that is new to this - * machine and hence not on the list. That would not be an - * issue except that adding an entry to the master list can't - * be done at that point. + * Return the matching label from the label list or NULL.   */ -void smack_from_cipso(u32 level, char *cp, char *result) +char *smack_from_cipso(u32 level, char *cp)  {  	struct smack_known *kp;  	char *final = NULL; @@ -509,12 +518,13 @@ void smack_from_cipso(u32 level, char *cp, char *result)  			final = kp->smk_known;  		spin_unlock_bh(&kp->smk_cipsolock); + +		if (final != NULL) +			break;  	}  	rcu_read_unlock(); -	if (final == NULL) -		final = smack_known_huh.smk_known; -	strncpy(result, final, SMK_MAXLEN); -	return; + +	return final;  }  /** diff --git a/security/smack/smack_lsm.c b/security/smack/smack_lsm.c index b9c5e149903..7db62b48eb4 100644 --- a/security/smack/smack_lsm.c +++ b/security/smack/smack_lsm.c @@ -5,12 +5,13 @@   *   *  Authors:   *	Casey Schaufler <casey@schaufler-ca.com> - *	Jarkko Sakkinen <ext-jarkko.2.sakkinen@nokia.com> + *	Jarkko Sakkinen <jarkko.sakkinen@intel.com>   *   *  Copyright (C) 2007 Casey Schaufler <casey@schaufler-ca.com>   *  Copyright (C) 2009 Hewlett-Packard Development Company, L.P.   *                Paul Moore <paul@paul-moore.com>   *  Copyright (C) 2010 Nokia Corporation + *  Copyright (C) 2011 Intel Corporation.   *   *	This program is free software; you can redistribute it and/or modify   *	it under the terms of the GNU General Public License version 2, @@ -34,6 +35,7 @@  #include <linux/audit.h>  #include <linux/magic.h>  #include <linux/dcache.h> +#include <linux/personality.h>  #include "smack.h"  #define task_security(task)	(task_cred_xxx((task), security)) @@ -441,11 +443,17 @@ static int smack_sb_umount(struct vfsmount *mnt, int flags)   * BPRM hooks   */ +/** + * smack_bprm_set_creds - set creds for exec + * @bprm: the exec information + * + * Returns 0 if it gets a blob, -ENOMEM otherwise + */  static int smack_bprm_set_creds(struct linux_binprm *bprm)  { -	struct task_smack *tsp = bprm->cred->security; +	struct inode *inode = bprm->file->f_path.dentry->d_inode; +	struct task_smack *bsp = bprm->cred->security;  	struct inode_smack *isp; -	struct dentry *dp;  	int rc;  	rc = cap_bprm_set_creds(bprm); @@ -455,20 +463,48 @@ static int smack_bprm_set_creds(struct linux_binprm *bprm)  	if (bprm->cred_prepared)  		return 0; -	if (bprm->file == NULL || bprm->file->f_dentry == NULL) +	isp = inode->i_security; +	if (isp->smk_task == NULL || isp->smk_task == bsp->smk_task)  		return 0; -	dp = bprm->file->f_dentry; +	if (bprm->unsafe) +		return -EPERM; -	if (dp->d_inode == NULL) -		return 0; +	bsp->smk_task = isp->smk_task; +	bprm->per_clear |= PER_CLEAR_ON_SETID; -	isp = dp->d_inode->i_security; +	return 0; +} -	if (isp->smk_task != NULL) -		tsp->smk_task = isp->smk_task; +/** + * smack_bprm_committing_creds - Prepare to install the new credentials + * from bprm. + * + * @bprm: binprm for exec + */ +static void smack_bprm_committing_creds(struct linux_binprm *bprm) +{ +	struct task_smack *bsp = bprm->cred->security; -	return 0; +	if (bsp->smk_task != bsp->smk_forked) +		current->pdeath_signal = 0; +} + +/** + * smack_bprm_secureexec - Return the decision to use secureexec. + * @bprm: binprm for exec + * + * Returns 0 on success. + */ +static int smack_bprm_secureexec(struct linux_binprm *bprm) +{ +	struct task_smack *tsp = current_security(); +	int ret = cap_bprm_secureexec(bprm); + +	if (!ret && (tsp->smk_task != tsp->smk_forked)) +		ret = 1; + +	return ret;  }  /* @@ -516,6 +552,8 @@ static int smack_inode_init_security(struct inode *inode, struct inode *dir,  				     const struct qstr *qstr, char **name,  				     void **value, size_t *len)  { +	struct smack_known *skp; +	char *csp = smk_of_current();  	char *isp = smk_of_inode(inode);  	char *dsp = smk_of_inode(dir);  	int may; @@ -527,8 +565,9 @@ static int smack_inode_init_security(struct inode *inode, struct inode *dir,  	}  	if (value) { +		skp = smk_find_entry(csp);  		rcu_read_lock(); -		may = smk_access_entry(smk_of_current(), dsp, &smack_rule_list); +		may = smk_access_entry(csp, dsp, &skp->smk_rules);  		rcu_read_unlock();  		/* @@ -841,7 +880,7 @@ static void smack_inode_post_setxattr(struct dentry *dentry, const char *name,  	return;  } -/* +/**   * smack_inode_getxattr - Smack check on getxattr   * @dentry: the object   * @name: unused @@ -858,7 +897,7 @@ static int smack_inode_getxattr(struct dentry *dentry, const char *name)  	return smk_curacc(smk_of_inode(dentry->d_inode), MAY_READ, &ad);  } -/* +/**   * smack_inode_removexattr - Smack check on removexattr   * @dentry: the object   * @name: name of the attribute @@ -1088,36 +1127,31 @@ static int smack_file_lock(struct file *file, unsigned int cmd)   * @cmd: what action to check   * @arg: unused   * + * Generally these operations are harmless. + * File locking operations present an obvious mechanism + * for passing information, so they require write access. + *   * Returns 0 if current has access, error code otherwise   */  static int smack_file_fcntl(struct file *file, unsigned int cmd,  			    unsigned long arg)  {  	struct smk_audit_info ad; -	int rc; +	int rc = 0; -	smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_PATH); -	smk_ad_setfield_u_fs_path(&ad, file->f_path);  	switch (cmd) { -	case F_DUPFD: -	case F_GETFD: -	case F_GETFL:  	case F_GETLK: -	case F_GETOWN: -	case F_GETSIG: -		rc = smk_curacc(file->f_security, MAY_READ, &ad); -		break; -	case F_SETFD: -	case F_SETFL:  	case F_SETLK:  	case F_SETLKW:  	case F_SETOWN:  	case F_SETSIG: +		smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_PATH); +		smk_ad_setfield_u_fs_path(&ad, file->f_path);  		rc = smk_curacc(file->f_security, MAY_WRITE, &ad);  		break;  	default: -		rc = smk_curacc(file->f_security, MAY_READWRITE, &ad); +		break;  	}  	return rc; @@ -1138,6 +1172,7 @@ static int smack_file_mmap(struct file *file,  			   unsigned long flags, unsigned long addr,  			   unsigned long addr_only)  { +	struct smack_known *skp;  	struct smack_rule *srp;  	struct task_smack *tsp;  	char *sp; @@ -1170,6 +1205,7 @@ static int smack_file_mmap(struct file *file,  	tsp = current_security();  	sp = smk_of_current(); +	skp = smk_find_entry(sp);  	rc = 0;  	rcu_read_lock(); @@ -1177,15 +1213,8 @@ static int smack_file_mmap(struct file *file,  	 * For each Smack rule associated with the subject  	 * label verify that the SMACK64MMAP also has access  	 * to that rule's object label. -	 * -	 * Because neither of the labels comes -	 * from the networking code it is sufficient -	 * to compare pointers.  	 */ -	list_for_each_entry_rcu(srp, &smack_rule_list, list) { -		if (srp->smk_subject != sp) -			continue; - +	list_for_each_entry_rcu(srp, &skp->smk_rules, list) {  		osmack = srp->smk_object;  		/*  		 * Matching labels always allows access. @@ -1214,7 +1243,8 @@ static int smack_file_mmap(struct file *file,  		 * If there isn't one a SMACK64MMAP subject  		 * can't have as much access as current.  		 */ -		mmay = smk_access_entry(msmack, osmack, &smack_rule_list); +		skp = smk_find_entry(msmack); +		mmay = smk_access_entry(msmack, osmack, &skp->smk_rules);  		if (mmay == -ENOENT) {  			rc = -EACCES;  			break; @@ -1315,6 +1345,24 @@ static int smack_file_receive(struct file *file)  	return smk_curacc(file->f_security, may, &ad);  } +/** + * smack_dentry_open - Smack dentry open processing + * @file: the object + * @cred: unused + * + * Set the security blob in the file structure. + * + * Returns 0 + */ +static int smack_dentry_open(struct file *file, const struct cred *cred) +{ +	struct inode_smack *isp = file->f_path.dentry->d_inode->i_security; + +	file->f_security = isp->smk_inode; + +	return 0; +} +  /*   * Task hooks   */ @@ -1455,15 +1503,17 @@ static int smack_kernel_create_files_as(struct cred *new,  /**   * smk_curacc_on_task - helper to log task related access   * @p: the task object - * @access : the access requested + * @access: the access requested + * @caller: name of the calling function for audit   *   * Return 0 if access is permitted   */ -static int smk_curacc_on_task(struct task_struct *p, int access) +static int smk_curacc_on_task(struct task_struct *p, int access, +				const char *caller)  {  	struct smk_audit_info ad; -	smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_TASK); +	smk_ad_init(&ad, caller, LSM_AUDIT_DATA_TASK);  	smk_ad_setfield_u_tsk(&ad, p);  	return smk_curacc(smk_of_task(task_security(p)), access, &ad);  } @@ -1477,7 +1527,7 @@ static int smk_curacc_on_task(struct task_struct *p, int access)   */  static int smack_task_setpgid(struct task_struct *p, pid_t pgid)  { -	return smk_curacc_on_task(p, MAY_WRITE); +	return smk_curacc_on_task(p, MAY_WRITE, __func__);  }  /** @@ -1488,7 +1538,7 @@ static int smack_task_setpgid(struct task_struct *p, pid_t pgid)   */  static int smack_task_getpgid(struct task_struct *p)  { -	return smk_curacc_on_task(p, MAY_READ); +	return smk_curacc_on_task(p, MAY_READ, __func__);  }  /** @@ -1499,7 +1549,7 @@ static int smack_task_getpgid(struct task_struct *p)   */  static int smack_task_getsid(struct task_struct *p)  { -	return smk_curacc_on_task(p, MAY_READ); +	return smk_curacc_on_task(p, MAY_READ, __func__);  }  /** @@ -1527,7 +1577,7 @@ static int smack_task_setnice(struct task_struct *p, int nice)  	rc = cap_task_setnice(p, nice);  	if (rc == 0) -		rc = smk_curacc_on_task(p, MAY_WRITE); +		rc = smk_curacc_on_task(p, MAY_WRITE, __func__);  	return rc;  } @@ -1544,7 +1594,7 @@ static int smack_task_setioprio(struct task_struct *p, int ioprio)  	rc = cap_task_setioprio(p, ioprio);  	if (rc == 0) -		rc = smk_curacc_on_task(p, MAY_WRITE); +		rc = smk_curacc_on_task(p, MAY_WRITE, __func__);  	return rc;  } @@ -1556,7 +1606,7 @@ static int smack_task_setioprio(struct task_struct *p, int ioprio)   */  static int smack_task_getioprio(struct task_struct *p)  { -	return smk_curacc_on_task(p, MAY_READ); +	return smk_curacc_on_task(p, MAY_READ, __func__);  }  /** @@ -1573,7 +1623,7 @@ static int smack_task_setscheduler(struct task_struct *p)  	rc = cap_task_setscheduler(p);  	if (rc == 0) -		rc = smk_curacc_on_task(p, MAY_WRITE); +		rc = smk_curacc_on_task(p, MAY_WRITE, __func__);  	return rc;  } @@ -1585,7 +1635,7 @@ static int smack_task_setscheduler(struct task_struct *p)   */  static int smack_task_getscheduler(struct task_struct *p)  { -	return smk_curacc_on_task(p, MAY_READ); +	return smk_curacc_on_task(p, MAY_READ, __func__);  }  /** @@ -1596,7 +1646,7 @@ static int smack_task_getscheduler(struct task_struct *p)   */  static int smack_task_movememory(struct task_struct *p)  { -	return smk_curacc_on_task(p, MAY_WRITE); +	return smk_curacc_on_task(p, MAY_WRITE, __func__);  }  /** @@ -1711,7 +1761,7 @@ static int smack_sk_alloc_security(struct sock *sk, int family, gfp_t gfp_flags)  	ssp->smk_in = csp;  	ssp->smk_out = csp; -	ssp->smk_packet[0] = '\0'; +	ssp->smk_packet = NULL;  	sk->sk_security = ssp; @@ -2753,6 +2803,7 @@ static int smack_unix_stream_connect(struct sock *sock,  {  	struct socket_smack *ssp = sock->sk_security;  	struct socket_smack *osp = other->sk_security; +	struct socket_smack *nsp = newsk->sk_security;  	struct smk_audit_info ad;  	int rc = 0; @@ -2762,6 +2813,14 @@ static int smack_unix_stream_connect(struct sock *sock,  	if (!capable(CAP_MAC_OVERRIDE))  		rc = smk_access(ssp->smk_out, osp->smk_in, MAY_WRITE, &ad); +	/* +	 * Cross reference the peer labels for SO_PEERSEC. +	 */ +	if (rc == 0) { +		nsp->smk_packet = ssp->smk_out; +		ssp->smk_packet = osp->smk_out; +	} +  	return rc;  } @@ -2813,16 +2872,17 @@ static int smack_socket_sendmsg(struct socket *sock, struct msghdr *msg,  	return smack_netlabel_send(sock->sk, sip);  } -  /**   * smack_from_secattr - Convert a netlabel attr.mls.lvl/attr.mls.cat pair to smack   * @sap: netlabel secattr - * @sip: where to put the result + * @ssp: socket security information   * - * Copies a smack label into sip + * Returns a pointer to a Smack label found on the label list.   */ -static void smack_from_secattr(struct netlbl_lsm_secattr *sap, char *sip) +static char *smack_from_secattr(struct netlbl_lsm_secattr *sap, +				struct socket_smack *ssp)  { +	struct smack_known *skp;  	char smack[SMK_LABELLEN];  	char *sp;  	int pcat; @@ -2852,15 +2912,43 @@ static void smack_from_secattr(struct netlbl_lsm_secattr *sap, char *sip)  		 * we are already done. WeeHee.  		 */  		if (sap->attr.mls.lvl == smack_cipso_direct) { -			memcpy(sip, smack, SMK_MAXLEN); -			return; +			/* +			 * The label sent is usually on the label list. +			 * +			 * If it is not we may still want to allow the +			 * delivery. +			 * +			 * If the recipient is accepting all packets +			 * because it is using the star ("*") label +			 * for SMACK64IPIN provide the web ("@") label +			 * so that a directed response will succeed. +			 * This is not very correct from a MAC point +			 * of view, but gets around the problem that +			 * locking prevents adding the newly discovered +			 * label to the list. +			 * The case where the recipient is not using +			 * the star label should obviously fail. +			 * The easy way to do this is to provide the +			 * star label as the subject label. +			 */ +			skp = smk_find_entry(smack); +			if (skp != NULL) +				return skp->smk_known; +			if (ssp != NULL && +			    ssp->smk_in == smack_known_star.smk_known) +				return smack_known_web.smk_known; +			return smack_known_star.smk_known;  		}  		/*  		 * Look it up in the supplied table if it is not  		 * a direct mapping.  		 */ -		smack_from_cipso(sap->attr.mls.lvl, smack, sip); -		return; +		sp = smack_from_cipso(sap->attr.mls.lvl, smack); +		if (sp != NULL) +			return sp; +		if (ssp != NULL && ssp->smk_in == smack_known_star.smk_known) +			return smack_known_web.smk_known; +		return smack_known_star.smk_known;  	}  	if ((sap->flags & NETLBL_SECATTR_SECID) != 0) {  		/* @@ -2875,16 +2963,14 @@ static void smack_from_secattr(struct netlbl_lsm_secattr *sap, char *sip)  		 * secid is from a fallback.  		 */  		BUG_ON(sp == NULL); -		strncpy(sip, sp, SMK_MAXLEN); -		return; +		return sp;  	}  	/*  	 * Without guidance regarding the smack value  	 * for the packet fall back on the network  	 * ambient value.  	 */ -	strncpy(sip, smack_net_ambient, SMK_MAXLEN); -	return; +	return smack_net_ambient;  }  /** @@ -2898,7 +2984,6 @@ static int smack_socket_sock_rcv_skb(struct sock *sk, struct sk_buff *skb)  {  	struct netlbl_lsm_secattr secattr;  	struct socket_smack *ssp = sk->sk_security; -	char smack[SMK_LABELLEN];  	char *csp;  	int rc;  	struct smk_audit_info ad; @@ -2911,10 +2996,9 @@ static int smack_socket_sock_rcv_skb(struct sock *sk, struct sk_buff *skb)  	netlbl_secattr_init(&secattr);  	rc = netlbl_skbuff_getattr(skb, sk->sk_family, &secattr); -	if (rc == 0) { -		smack_from_secattr(&secattr, smack); -		csp = smack; -	} else +	if (rc == 0) +		csp = smack_from_secattr(&secattr, ssp); +	else  		csp = smack_net_ambient;  	netlbl_secattr_destroy(&secattr); @@ -2951,15 +3035,19 @@ static int smack_socket_getpeersec_stream(struct socket *sock,  					  int __user *optlen, unsigned len)  {  	struct socket_smack *ssp; -	int slen; +	char *rcp = ""; +	int slen = 1;  	int rc = 0;  	ssp = sock->sk->sk_security; -	slen = strlen(ssp->smk_packet) + 1; +	if (ssp->smk_packet != NULL) { +		rcp = ssp->smk_packet; +		slen = strlen(rcp) + 1; +	}  	if (slen > len)  		rc = -ERANGE; -	else if (copy_to_user(optval, ssp->smk_packet, slen) != 0) +	else if (copy_to_user(optval, rcp, slen) != 0)  		rc = -EFAULT;  	if (put_user(slen, optlen) != 0) @@ -2982,8 +3070,8 @@ static int smack_socket_getpeersec_dgram(struct socket *sock,  {  	struct netlbl_lsm_secattr secattr; -	struct socket_smack *sp; -	char smack[SMK_LABELLEN]; +	struct socket_smack *ssp = NULL; +	char *sp;  	int family = PF_UNSPEC;  	u32 s = 0;	/* 0 is the invalid secid */  	int rc; @@ -2998,17 +3086,19 @@ static int smack_socket_getpeersec_dgram(struct socket *sock,  		family = sock->sk->sk_family;  	if (family == PF_UNIX) { -		sp = sock->sk->sk_security; -		s = smack_to_secid(sp->smk_out); +		ssp = sock->sk->sk_security; +		s = smack_to_secid(ssp->smk_out);  	} else if (family == PF_INET || family == PF_INET6) {  		/*  		 * Translate what netlabel gave us.  		 */ +		if (sock != NULL && sock->sk != NULL) +			ssp = sock->sk->sk_security;  		netlbl_secattr_init(&secattr);  		rc = netlbl_skbuff_getattr(skb, family, &secattr);  		if (rc == 0) { -			smack_from_secattr(&secattr, smack); -			s = smack_to_secid(smack); +			sp = smack_from_secattr(&secattr, ssp); +			s = smack_to_secid(sp);  		}  		netlbl_secattr_destroy(&secattr);  	} @@ -3056,7 +3146,7 @@ static int smack_inet_conn_request(struct sock *sk, struct sk_buff *skb,  	struct netlbl_lsm_secattr secattr;  	struct sockaddr_in addr;  	struct iphdr *hdr; -	char smack[SMK_LABELLEN]; +	char *sp;  	int rc;  	struct smk_audit_info ad; @@ -3067,9 +3157,9 @@ static int smack_inet_conn_request(struct sock *sk, struct sk_buff *skb,  	netlbl_secattr_init(&secattr);  	rc = netlbl_skbuff_getattr(skb, family, &secattr);  	if (rc == 0) -		smack_from_secattr(&secattr, smack); +		sp = smack_from_secattr(&secattr, ssp);  	else -		strncpy(smack, smack_known_huh.smk_known, SMK_MAXLEN); +		sp = smack_known_huh.smk_known;  	netlbl_secattr_destroy(&secattr);  #ifdef CONFIG_AUDIT @@ -3082,7 +3172,7 @@ static int smack_inet_conn_request(struct sock *sk, struct sk_buff *skb,  	 * Receiving a packet requires that the other end be able to write  	 * here. Read access is not required.  	 */ -	rc = smk_access(smack, ssp->smk_in, MAY_WRITE, &ad); +	rc = smk_access(sp, ssp->smk_in, MAY_WRITE, &ad);  	if (rc != 0)  		return rc; @@ -3090,7 +3180,7 @@ static int smack_inet_conn_request(struct sock *sk, struct sk_buff *skb,  	 * Save the peer's label in the request_sock so we can later setup  	 * smk_packet in the child socket so that SO_PEERCRED can report it.  	 */ -	req->peer_secid = smack_to_secid(smack); +	req->peer_secid = smack_to_secid(sp);  	/*  	 * We need to decide if we want to label the incoming connection here @@ -3103,7 +3193,7 @@ static int smack_inet_conn_request(struct sock *sk, struct sk_buff *skb,  	if (smack_host_label(&addr) == NULL) {  		rcu_read_unlock();  		netlbl_secattr_init(&secattr); -		smack_to_secattr(smack, &secattr); +		smack_to_secattr(sp, &secattr);  		rc = netlbl_req_setattr(req, &secattr);  		netlbl_secattr_destroy(&secattr);  	} else { @@ -3125,13 +3215,11 @@ static void smack_inet_csk_clone(struct sock *sk,  				 const struct request_sock *req)  {  	struct socket_smack *ssp = sk->sk_security; -	char *smack; -	if (req->peer_secid != 0) { -		smack = smack_from_secid(req->peer_secid); -		strncpy(ssp->smk_packet, smack, SMK_MAXLEN); -	} else -		ssp->smk_packet[0] = '\0'; +	if (req->peer_secid != 0) +		ssp->smk_packet = smack_from_secid(req->peer_secid); +	else +		ssp->smk_packet = NULL;  }  /* @@ -3409,6 +3497,8 @@ struct security_operations smack_ops = {  	.sb_umount = 			smack_sb_umount,  	.bprm_set_creds =		smack_bprm_set_creds, +	.bprm_committing_creds =	smack_bprm_committing_creds, +	.bprm_secureexec =		smack_bprm_secureexec,  	.inode_alloc_security = 	smack_inode_alloc_security,  	.inode_free_security = 		smack_inode_free_security, @@ -3440,6 +3530,8 @@ struct security_operations smack_ops = {  	.file_send_sigiotask = 		smack_file_send_sigiotask,  	.file_receive = 		smack_file_receive, +	.dentry_open =			smack_dentry_open, +  	.cred_alloc_blank =		smack_cred_alloc_blank,  	.cred_free =			smack_cred_free,  	.cred_prepare =			smack_cred_prepare, diff --git a/security/smack/smackfs.c b/security/smack/smackfs.c index f93460156dc..6aceef518a4 100644 --- a/security/smack/smackfs.c +++ b/security/smack/smackfs.c @@ -44,6 +44,7 @@ enum smk_inos {  	SMK_ONLYCAP	= 9,	/* the only "capable" label */  	SMK_LOGGING	= 10,	/* logging */  	SMK_LOAD_SELF	= 11,	/* task specific rules */ +	SMK_ACCESSES	= 12,	/* access policy */  };  /* @@ -85,6 +86,16 @@ char *smack_onlycap;   */  LIST_HEAD(smk_netlbladdr_list); + +/* + * Rule lists are maintained for each label. + * This master list is just for reading /smack/load. + */ +struct smack_master_list { +	struct list_head	list; +	struct smack_rule	*smk_rule; +}; +  LIST_HEAD(smack_rule_list);  static int smk_cipso_doi_value = SMACK_CIPSO_DOI_DEFAULT; @@ -92,7 +103,7 @@ static int smk_cipso_doi_value = SMACK_CIPSO_DOI_DEFAULT;  const char *smack_cipso_option = SMACK_CIPSO_OPTION; -#define	SEQ_READ_FINISHED	1 +#define	SEQ_READ_FINISHED	((loff_t)-1)  /*   * Values for parsing cipso rules @@ -159,9 +170,13 @@ static int smk_set_access(struct smack_rule *srp, struct list_head *rule_list,  	mutex_lock(rule_lock); +	/* +	 * Because the object label is less likely to match +	 * than the subject label check it first +	 */  	list_for_each_entry_rcu(sp, rule_list, list) { -		if (sp->smk_subject == srp->smk_subject && -		    sp->smk_object == srp->smk_object) { +		if (sp->smk_object == srp->smk_object && +		    sp->smk_subject == srp->smk_subject) {  			found = 1;  			sp->smk_access = srp->smk_access;  			break; @@ -176,6 +191,99 @@ static int smk_set_access(struct smack_rule *srp, struct list_head *rule_list,  }  /** + * smk_parse_rule - parse Smack rule from load string + * @data: string to be parsed whose size is SMK_LOADLEN + * @rule: Smack rule + * @import: if non-zero, import labels + */ +static int smk_parse_rule(const char *data, struct smack_rule *rule, int import) +{ +	char smack[SMK_LABELLEN]; +	struct smack_known *skp; + +	if (import) { +		rule->smk_subject = smk_import(data, 0); +		if (rule->smk_subject == NULL) +			return -1; + +		rule->smk_object = smk_import(data + SMK_LABELLEN, 0); +		if (rule->smk_object == NULL) +			return -1; +	} else { +		smk_parse_smack(data, 0, smack); +		skp = smk_find_entry(smack); +		if (skp == NULL) +			return -1; +		rule->smk_subject = skp->smk_known; + +		smk_parse_smack(data + SMK_LABELLEN, 0, smack); +		skp = smk_find_entry(smack); +		if (skp == NULL) +			return -1; +		rule->smk_object = skp->smk_known; +	} + +	rule->smk_access = 0; + +	switch (data[SMK_LABELLEN + SMK_LABELLEN]) { +	case '-': +		break; +	case 'r': +	case 'R': +		rule->smk_access |= MAY_READ; +		break; +	default: +		return -1; +	} + +	switch (data[SMK_LABELLEN + SMK_LABELLEN + 1]) { +	case '-': +		break; +	case 'w': +	case 'W': +		rule->smk_access |= MAY_WRITE; +		break; +	default: +		return -1; +	} + +	switch (data[SMK_LABELLEN + SMK_LABELLEN + 2]) { +	case '-': +		break; +	case 'x': +	case 'X': +		rule->smk_access |= MAY_EXEC; +		break; +	default: +		return -1; +	} + +	switch (data[SMK_LABELLEN + SMK_LABELLEN + 3]) { +	case '-': +		break; +	case 'a': +	case 'A': +		rule->smk_access |= MAY_APPEND; +		break; +	default: +		return -1; +	} + +	switch (data[SMK_LABELLEN + SMK_LABELLEN + 4]) { +	case '-': +		break; +	case 't': +	case 'T': +		rule->smk_access |= MAY_TRANSMUTE; +		break; +	default: +		return -1; +	} + +	return 0; +} + +/**   * smk_write_load_list - write() for any /smack/load   * @file: file pointer, not actually used   * @buf: where to get the data from @@ -197,9 +305,12 @@ static ssize_t smk_write_load_list(struct file *file, const char __user *buf,  				struct list_head *rule_list,  				struct mutex *rule_lock)  { +	struct smack_master_list *smlp; +	struct smack_known *skp;  	struct smack_rule *rule;  	char *data;  	int rc = -EINVAL; +	int load = 0;  	/*  	 * No partial writes. @@ -234,69 +345,14 @@ static ssize_t smk_write_load_list(struct file *file, const char __user *buf,  		goto out;  	} -	rule->smk_subject = smk_import(data, 0); -	if (rule->smk_subject == NULL) -		goto out_free_rule; - -	rule->smk_object = smk_import(data + SMK_LABELLEN, 0); -	if (rule->smk_object == NULL) -		goto out_free_rule; - -	rule->smk_access = 0; - -	switch (data[SMK_LABELLEN + SMK_LABELLEN]) { -	case '-': -		break; -	case 'r': -	case 'R': -		rule->smk_access |= MAY_READ; -		break; -	default: -		goto out_free_rule; -	} - -	switch (data[SMK_LABELLEN + SMK_LABELLEN + 1]) { -	case '-': -		break; -	case 'w': -	case 'W': -		rule->smk_access |= MAY_WRITE; -		break; -	default: -		goto out_free_rule; -	} - -	switch (data[SMK_LABELLEN + SMK_LABELLEN + 2]) { -	case '-': -		break; -	case 'x': -	case 'X': -		rule->smk_access |= MAY_EXEC; -		break; -	default: -		goto out_free_rule; -	} - -	switch (data[SMK_LABELLEN + SMK_LABELLEN + 3]) { -	case '-': -		break; -	case 'a': -	case 'A': -		rule->smk_access |= MAY_APPEND; -		break; -	default: +	if (smk_parse_rule(data, rule, 1))  		goto out_free_rule; -	} -	switch (data[SMK_LABELLEN + SMK_LABELLEN + 4]) { -	case '-': -		break; -	case 't': -	case 'T': -		rule->smk_access |= MAY_TRANSMUTE; -		break; -	default: -		goto out_free_rule; +	if (rule_list == NULL) { +		load = 1; +		skp = smk_find_entry(rule->smk_subject); +		rule_list = &skp->smk_rules; +		rule_lock = &skp->smk_rules_lock;  	}  	rc = count; @@ -304,8 +360,15 @@ static ssize_t smk_write_load_list(struct file *file, const char __user *buf,  	 * smk_set_access returns true if there was already a rule  	 * for the subject/object pair, and false if it was new.  	 */ -	if (!smk_set_access(rule, rule_list, rule_lock)) +	if (!smk_set_access(rule, rule_list, rule_lock)) { +		smlp = kzalloc(sizeof(*smlp), GFP_KERNEL); +		if (smlp != NULL) { +			smlp->smk_rule = rule; +			list_add_rcu(&smlp->list, &smack_rule_list); +		} else +			rc = -ENOMEM;  		goto out; +	}  out_free_rule:  	kfree(rule); @@ -321,11 +384,24 @@ out:  static void *load_seq_start(struct seq_file *s, loff_t *pos)  { -	if (*pos == SEQ_READ_FINISHED) +	struct list_head *list; + +	/* +	 * This is 0 the first time through. +	 */ +	if (s->index == 0) +		s->private = &smack_rule_list; + +	if (s->private == NULL)  		return NULL; -	if (list_empty(&smack_rule_list)) + +	list = s->private; +	if (list_empty(list))  		return NULL; -	return smack_rule_list.next; + +	if (s->index == 0) +		return list->next; +	return list;  }  static void *load_seq_next(struct seq_file *s, void *v, loff_t *pos) @@ -333,17 +409,19 @@ static void *load_seq_next(struct seq_file *s, void *v, loff_t *pos)  	struct list_head *list = v;  	if (list_is_last(list, &smack_rule_list)) { -		*pos = SEQ_READ_FINISHED; +		s->private = NULL;  		return NULL;  	} +	s->private = list->next;  	return list->next;  }  static int load_seq_show(struct seq_file *s, void *v)  {  	struct list_head *list = v; -	struct smack_rule *srp = -		 list_entry(list, struct smack_rule, list); +	struct smack_master_list *smlp = +		 list_entry(list, struct smack_master_list, list); +	struct smack_rule *srp = smlp->smk_rule;  	seq_printf(s, "%s %s", (char *)srp->smk_subject,  		   (char *)srp->smk_object); @@ -412,8 +490,7 @@ static ssize_t smk_write_load(struct file *file, const char __user *buf,  	if (!capable(CAP_MAC_ADMIN))  		return -EPERM; -	return smk_write_load_list(file, buf, count, ppos, &smack_rule_list, -					&smack_list_lock); +	return smk_write_load_list(file, buf, count, ppos, NULL, NULL);  }  static const struct file_operations smk_load_ops = { @@ -1425,6 +1502,44 @@ static const struct file_operations smk_load_self_ops = {  	.write		= smk_write_load_self,  	.release        = seq_release,  }; + +/** + * smk_write_access - handle access check transaction + * @file: file pointer + * @buf: data from user space + * @count: bytes sent + * @ppos: where to start - must be 0 + */ +static ssize_t smk_write_access(struct file *file, const char __user *buf, +				size_t count, loff_t *ppos) +{ +	struct smack_rule rule; +	char *data; +	int res; + +	data = simple_transaction_get(file, buf, count); +	if (IS_ERR(data)) +		return PTR_ERR(data); + +	if (count < SMK_LOADLEN || smk_parse_rule(data, &rule, 0)) +		return -EINVAL; + +	res = smk_access(rule.smk_subject, rule.smk_object, rule.smk_access, +			  NULL); +	data[0] = res == 0 ? '1' : '0'; +	data[1] = '\0'; + +	simple_transaction_set(file, 2); +	return SMK_LOADLEN; +} + +static const struct file_operations smk_access_ops = { +	.write		= smk_write_access, +	.read		= simple_transaction_read, +	.release	= simple_transaction_release, +	.llseek		= generic_file_llseek, +}; +  /**   * smk_fill_super - fill the /smackfs superblock   * @sb: the empty superblock @@ -1459,6 +1574,8 @@ static int smk_fill_super(struct super_block *sb, void *data, int silent)  			"logging", &smk_logging_ops, S_IRUGO|S_IWUSR},  		[SMK_LOAD_SELF] = {  			"load-self", &smk_load_self_ops, S_IRUGO|S_IWUGO}, +		[SMK_ACCESSES] = { +			"access", &smk_access_ops, S_IRUGO|S_IWUGO},  		/* last one */  			{""}  	}; @@ -1534,6 +1651,20 @@ static int __init init_smk_fs(void)  	smk_cipso_doi();  	smk_unlbl_ambient(NULL); +	mutex_init(&smack_known_floor.smk_rules_lock); +	mutex_init(&smack_known_hat.smk_rules_lock); +	mutex_init(&smack_known_huh.smk_rules_lock); +	mutex_init(&smack_known_invalid.smk_rules_lock); +	mutex_init(&smack_known_star.smk_rules_lock); +	mutex_init(&smack_known_web.smk_rules_lock); + +	INIT_LIST_HEAD(&smack_known_floor.smk_rules); +	INIT_LIST_HEAD(&smack_known_hat.smk_rules); +	INIT_LIST_HEAD(&smack_known_huh.smk_rules); +	INIT_LIST_HEAD(&smack_known_invalid.smk_rules); +	INIT_LIST_HEAD(&smack_known_star.smk_rules); +	INIT_LIST_HEAD(&smack_known_web.smk_rules); +  	return err;  } diff --git a/security/tomoyo/Kconfig b/security/tomoyo/Kconfig index 7c7f8c16c10..8eb779b9d77 100644 --- a/security/tomoyo/Kconfig +++ b/security/tomoyo/Kconfig @@ -1,8 +1,10 @@  config SECURITY_TOMOYO  	bool "TOMOYO Linux Support"  	depends on SECURITY +	depends on NET  	select SECURITYFS  	select SECURITY_PATH +	select SECURITY_NETWORK  	default n  	help  	  This selects TOMOYO Linux, pathname-based access control. diff --git a/security/tomoyo/Makefile b/security/tomoyo/Makefile index 95278b71fc2..56a0c7be409 100644 --- a/security/tomoyo/Makefile +++ b/security/tomoyo/Makefile @@ -1,4 +1,4 @@ -obj-y = audit.o common.o condition.o domain.o file.o gc.o group.o load_policy.o memory.o mount.o realpath.o securityfs_if.o tomoyo.o util.o +obj-y = audit.o common.o condition.o domain.o environ.o file.o gc.o group.o load_policy.o memory.o mount.o network.o realpath.o securityfs_if.o tomoyo.o util.o  $(obj)/policy/profile.conf:  	@mkdir -p $(obj)/policy/ @@ -27,7 +27,7 @@ $(obj)/policy/stat.conf:  	@touch $@  $(obj)/builtin-policy.h: $(obj)/policy/profile.conf $(obj)/policy/exception_policy.conf $(obj)/policy/domain_policy.conf $(obj)/policy/manager.conf $(obj)/policy/stat.conf -	@echo Generating built-in policy for TOMOYO 2.4.x. +	@echo Generating built-in policy for TOMOYO 2.5.x.  	@echo "static char tomoyo_builtin_profile[] __initdata =" > $@.tmp  	@sed -e 's/\\/\\\\/g' -e 's/\"/\\"/g' -e 's/\(.*\)/"\1\\n"/' < $(obj)/policy/profile.conf >> $@.tmp  	@echo "\"\";" >> $@.tmp diff --git a/security/tomoyo/audit.c b/security/tomoyo/audit.c index 5dbb1f7617c..075c3a6d164 100644 --- a/security/tomoyo/audit.c +++ b/security/tomoyo/audit.c @@ -313,6 +313,7 @@ static unsigned int tomoyo_log_count;   */  static bool tomoyo_get_audit(const struct tomoyo_policy_namespace *ns,  			     const u8 profile, const u8 index, +			     const struct tomoyo_acl_info *matched_acl,  			     const bool is_granted)  {  	u8 mode; @@ -324,6 +325,9 @@ static bool tomoyo_get_audit(const struct tomoyo_policy_namespace *ns,  	p = tomoyo_profile(ns, profile);  	if (tomoyo_log_count >= p->pref[TOMOYO_PREF_MAX_AUDIT_LOG])  		return false; +	if (is_granted && matched_acl && matched_acl->cond && +	    matched_acl->cond->grant_log != TOMOYO_GRANTLOG_AUTO) +		return matched_acl->cond->grant_log == TOMOYO_GRANTLOG_YES;  	mode = p->config[index];  	if (mode == TOMOYO_CONFIG_USE_DEFAULT)  		mode = p->config[category]; @@ -350,7 +354,8 @@ void tomoyo_write_log2(struct tomoyo_request_info *r, int len, const char *fmt,  	char *buf;  	struct tomoyo_log *entry;  	bool quota_exceeded = false; -	if (!tomoyo_get_audit(r->domain->ns, r->profile, r->type, r->granted)) +	if (!tomoyo_get_audit(r->domain->ns, r->profile, r->type, +			      r->matched_acl, r->granted))  		goto out;  	buf = tomoyo_init_log(r, len, fmt, args);  	if (!buf) diff --git a/security/tomoyo/common.c b/security/tomoyo/common.c index 2e43aec1c36..150911c7ff0 100644 --- a/security/tomoyo/common.c +++ b/security/tomoyo/common.c @@ -20,6 +20,7 @@ const char * const tomoyo_mode[TOMOYO_CONFIG_MAX_MODE] = {  /* String table for /sys/kernel/security/tomoyo/profile */  const char * const tomoyo_mac_keywords[TOMOYO_MAX_MAC_INDEX  				       + TOMOYO_MAX_MAC_CATEGORY_INDEX] = { +	/* CONFIG::file group */  	[TOMOYO_MAC_FILE_EXECUTE]    = "execute",  	[TOMOYO_MAC_FILE_OPEN]       = "open",  	[TOMOYO_MAC_FILE_CREATE]     = "create", @@ -43,7 +44,28 @@ const char * const tomoyo_mac_keywords[TOMOYO_MAX_MAC_INDEX  	[TOMOYO_MAC_FILE_MOUNT]      = "mount",  	[TOMOYO_MAC_FILE_UMOUNT]     = "unmount",  	[TOMOYO_MAC_FILE_PIVOT_ROOT] = "pivot_root", +	/* CONFIG::network group */ +	[TOMOYO_MAC_NETWORK_INET_STREAM_BIND]       = "inet_stream_bind", +	[TOMOYO_MAC_NETWORK_INET_STREAM_LISTEN]     = "inet_stream_listen", +	[TOMOYO_MAC_NETWORK_INET_STREAM_CONNECT]    = "inet_stream_connect", +	[TOMOYO_MAC_NETWORK_INET_DGRAM_BIND]        = "inet_dgram_bind", +	[TOMOYO_MAC_NETWORK_INET_DGRAM_SEND]        = "inet_dgram_send", +	[TOMOYO_MAC_NETWORK_INET_RAW_BIND]          = "inet_raw_bind", +	[TOMOYO_MAC_NETWORK_INET_RAW_SEND]          = "inet_raw_send", +	[TOMOYO_MAC_NETWORK_UNIX_STREAM_BIND]       = "unix_stream_bind", +	[TOMOYO_MAC_NETWORK_UNIX_STREAM_LISTEN]     = "unix_stream_listen", +	[TOMOYO_MAC_NETWORK_UNIX_STREAM_CONNECT]    = "unix_stream_connect", +	[TOMOYO_MAC_NETWORK_UNIX_DGRAM_BIND]        = "unix_dgram_bind", +	[TOMOYO_MAC_NETWORK_UNIX_DGRAM_SEND]        = "unix_dgram_send", +	[TOMOYO_MAC_NETWORK_UNIX_SEQPACKET_BIND]    = "unix_seqpacket_bind", +	[TOMOYO_MAC_NETWORK_UNIX_SEQPACKET_LISTEN]  = "unix_seqpacket_listen", +	[TOMOYO_MAC_NETWORK_UNIX_SEQPACKET_CONNECT] = "unix_seqpacket_connect", +	/* CONFIG::misc group */ +	[TOMOYO_MAC_ENVIRON] = "env", +	/* CONFIG group */  	[TOMOYO_MAX_MAC_INDEX + TOMOYO_MAC_CATEGORY_FILE] = "file", +	[TOMOYO_MAX_MAC_INDEX + TOMOYO_MAC_CATEGORY_NETWORK] = "network", +	[TOMOYO_MAX_MAC_INDEX + TOMOYO_MAC_CATEGORY_MISC] = "misc",  };  /* String table for conditions. */ @@ -130,10 +152,20 @@ const char * const tomoyo_path_keyword[TOMOYO_MAX_PATH_OPERATION] = {  	[TOMOYO_TYPE_UMOUNT]     = "unmount",  }; +/* String table for socket's operation. */ +const char * const tomoyo_socket_keyword[TOMOYO_MAX_NETWORK_OPERATION] = { +	[TOMOYO_NETWORK_BIND]    = "bind", +	[TOMOYO_NETWORK_LISTEN]  = "listen", +	[TOMOYO_NETWORK_CONNECT] = "connect", +	[TOMOYO_NETWORK_SEND]    = "send", +}; +  /* String table for categories. */  static const char * const tomoyo_category_keywords  [TOMOYO_MAX_MAC_CATEGORY_INDEX] = { -	[TOMOYO_MAC_CATEGORY_FILE]       = "file", +	[TOMOYO_MAC_CATEGORY_FILE]    = "file", +	[TOMOYO_MAC_CATEGORY_NETWORK] = "network", +	[TOMOYO_MAC_CATEGORY_MISC]    = "misc",  };  /* Permit policy management by non-root user? */ @@ -230,13 +262,17 @@ static void tomoyo_set_string(struct tomoyo_io_buffer *head, const char *string)  		WARN_ON(1);  } +static void tomoyo_io_printf(struct tomoyo_io_buffer *head, const char *fmt, +			     ...) __printf(2, 3); +  /**   * tomoyo_io_printf - printf() to "struct tomoyo_io_buffer" structure.   *   * @head: Pointer to "struct tomoyo_io_buffer".   * @fmt:  The printf()'s format string, followed by parameters.   */ -void tomoyo_io_printf(struct tomoyo_io_buffer *head, const char *fmt, ...) +static void tomoyo_io_printf(struct tomoyo_io_buffer *head, const char *fmt, +			     ...)  {  	va_list args;  	size_t len; @@ -313,7 +349,7 @@ void tomoyo_init_policy_namespace(struct tomoyo_policy_namespace *ns)  		INIT_LIST_HEAD(&ns->group_list[idx]);  	for (idx = 0; idx < TOMOYO_MAX_POLICY; idx++)  		INIT_LIST_HEAD(&ns->policy_list[idx]); -	ns->profile_version = 20100903; +	ns->profile_version = 20110903;  	tomoyo_namespace_enabled = !list_empty(&tomoyo_namespace_list);  	list_add_tail_rcu(&ns->namespace_list, &tomoyo_namespace_list);  } @@ -466,8 +502,10 @@ static struct tomoyo_profile *tomoyo_assign_profile  			TOMOYO_CONFIG_WANT_REJECT_LOG;  		memset(ptr->config, TOMOYO_CONFIG_USE_DEFAULT,  		       sizeof(ptr->config)); -		ptr->pref[TOMOYO_PREF_MAX_AUDIT_LOG] = 1024; -		ptr->pref[TOMOYO_PREF_MAX_LEARNING_ENTRY] = 2048; +		ptr->pref[TOMOYO_PREF_MAX_AUDIT_LOG] = +			CONFIG_SECURITY_TOMOYO_MAX_AUDIT_LOG; +		ptr->pref[TOMOYO_PREF_MAX_LEARNING_ENTRY] = +			CONFIG_SECURITY_TOMOYO_MAX_ACCEPT_ENTRY;  		mb(); /* Avoid out-of-order execution. */  		ns->profile_ptr[profile] = ptr;  		entry = NULL; @@ -951,14 +989,12 @@ static bool tomoyo_select_domain(struct tomoyo_io_buffer *head,  	    (global_pid = true, sscanf(data, "global-pid=%u", &pid) == 1)) {  		struct task_struct *p;  		rcu_read_lock(); -		read_lock(&tasklist_lock);  		if (global_pid)  			p = find_task_by_pid_ns(pid, &init_pid_ns);  		else  			p = find_task_by_vpid(pid);  		if (p)  			domain = tomoyo_real_domain(p); -		read_unlock(&tasklist_lock);  		rcu_read_unlock();  	} else if (!strncmp(data, "domain=", 7)) {  		if (tomoyo_domain_def(data + 7)) @@ -982,6 +1018,48 @@ static bool tomoyo_select_domain(struct tomoyo_io_buffer *head,  }  /** + * tomoyo_same_task_acl - Check for duplicated "struct tomoyo_task_acl" entry. + * + * @a: Pointer to "struct tomoyo_acl_info". + * @b: Pointer to "struct tomoyo_acl_info". + * + * Returns true if @a == @b, false otherwise. + */ +static bool tomoyo_same_task_acl(const struct tomoyo_acl_info *a, +			      const struct tomoyo_acl_info *b) +{ +	const struct tomoyo_task_acl *p1 = container_of(a, typeof(*p1), head); +	const struct tomoyo_task_acl *p2 = container_of(b, typeof(*p2), head); +	return p1->domainname == p2->domainname; +} + +/** + * tomoyo_write_task - Update task related list. + * + * @param: Pointer to "struct tomoyo_acl_param". + * + * Returns 0 on success, negative value otherwise. + * + * Caller holds tomoyo_read_lock(). + */ +static int tomoyo_write_task(struct tomoyo_acl_param *param) +{ +	int error = -EINVAL; +	if (tomoyo_str_starts(¶m->data, "manual_domain_transition ")) { +		struct tomoyo_task_acl e = { +			.head.type = TOMOYO_TYPE_MANUAL_TASK_ACL, +			.domainname = tomoyo_get_domainname(param), +		}; +		if (e.domainname) +			error = tomoyo_update_domain(&e.head, sizeof(e), param, +						     tomoyo_same_task_acl, +						     NULL); +		tomoyo_put_name(e.domainname); +	} +	return error; +} + +/**   * tomoyo_delete_domain - Delete a domain.   *   * @domainname: The name of domain. @@ -1039,11 +1117,16 @@ static int tomoyo_write_domain2(struct tomoyo_policy_namespace *ns,  	static const struct {  		const char *keyword;  		int (*write) (struct tomoyo_acl_param *); -	} tomoyo_callback[1] = { +	} tomoyo_callback[5] = {  		{ "file ", tomoyo_write_file }, +		{ "network inet ", tomoyo_write_inet_network }, +		{ "network unix ", tomoyo_write_unix_network }, +		{ "misc ", tomoyo_write_misc }, +		{ "task ", tomoyo_write_task },  	};  	u8 i; -	for (i = 0; i < 1; i++) { + +	for (i = 0; i < ARRAY_SIZE(tomoyo_callback); i++) {  		if (!tomoyo_str_starts(¶m.data,  				       tomoyo_callback[i].keyword))  			continue; @@ -1127,6 +1210,10 @@ static bool tomoyo_print_condition(struct tomoyo_io_buffer *head,  	case 0:  		head->r.cond_index = 0;  		head->r.cond_step++; +		if (cond->transit) { +			tomoyo_set_space(head); +			tomoyo_set_string(head, cond->transit->name); +		}  		/* fall through */  	case 1:  		{ @@ -1239,6 +1326,10 @@ static bool tomoyo_print_condition(struct tomoyo_io_buffer *head,  		head->r.cond_step++;  		/* fall through */  	case 3: +		if (cond->grant_log != TOMOYO_GRANTLOG_AUTO) +			tomoyo_io_printf(head, " grant_log=%s", +					 tomoyo_yesno(cond->grant_log == +						      TOMOYO_GRANTLOG_YES));  		tomoyo_set_lf(head);  		return true;  	} @@ -1306,6 +1397,12 @@ static bool tomoyo_print_entry(struct tomoyo_io_buffer *head,  		if (first)  			return true;  		tomoyo_print_name_union(head, &ptr->name); +	} else if (acl_type == TOMOYO_TYPE_MANUAL_TASK_ACL) { +		struct tomoyo_task_acl *ptr = +			container_of(acl, typeof(*ptr), head); +		tomoyo_set_group(head, "task "); +		tomoyo_set_string(head, "manual_domain_transition "); +		tomoyo_set_string(head, ptr->domainname->name);  	} else if (head->r.print_transition_related_only) {  		return true;  	} else if (acl_type == TOMOYO_TYPE_PATH2_ACL) { @@ -1370,6 +1467,60 @@ static bool tomoyo_print_entry(struct tomoyo_io_buffer *head,  		tomoyo_print_number_union(head, &ptr->mode);  		tomoyo_print_number_union(head, &ptr->major);  		tomoyo_print_number_union(head, &ptr->minor); +	} else if (acl_type == TOMOYO_TYPE_INET_ACL) { +		struct tomoyo_inet_acl *ptr = +			container_of(acl, typeof(*ptr), head); +		const u8 perm = ptr->perm; + +		for (bit = 0; bit < TOMOYO_MAX_NETWORK_OPERATION; bit++) { +			if (!(perm & (1 << bit))) +				continue; +			if (first) { +				tomoyo_set_group(head, "network inet "); +				tomoyo_set_string(head, tomoyo_proto_keyword +						  [ptr->protocol]); +				tomoyo_set_space(head); +				first = false; +			} else { +				tomoyo_set_slash(head); +			} +			tomoyo_set_string(head, tomoyo_socket_keyword[bit]); +		} +		if (first) +			return true; +		tomoyo_set_space(head); +		if (ptr->address.group) { +			tomoyo_set_string(head, "@"); +			tomoyo_set_string(head, ptr->address.group->group_name +					  ->name); +		} else { +			char buf[128]; +			tomoyo_print_ip(buf, sizeof(buf), &ptr->address); +			tomoyo_io_printf(head, "%s", buf); +		} +		tomoyo_print_number_union(head, &ptr->port); +	} else if (acl_type == TOMOYO_TYPE_UNIX_ACL) { +		struct tomoyo_unix_acl *ptr = +			container_of(acl, typeof(*ptr), head); +		const u8 perm = ptr->perm; + +		for (bit = 0; bit < TOMOYO_MAX_NETWORK_OPERATION; bit++) { +			if (!(perm & (1 << bit))) +				continue; +			if (first) { +				tomoyo_set_group(head, "network unix "); +				tomoyo_set_string(head, tomoyo_proto_keyword +						  [ptr->protocol]); +				tomoyo_set_space(head); +				first = false; +			} else { +				tomoyo_set_slash(head); +			} +			tomoyo_set_string(head, tomoyo_socket_keyword[bit]); +		} +		if (first) +			return true; +		tomoyo_print_name_union(head, &ptr->name);  	} else if (acl_type == TOMOYO_TYPE_MOUNT_ACL) {  		struct tomoyo_mount_acl *ptr =  			container_of(acl, typeof(*ptr), head); @@ -1378,6 +1529,12 @@ static bool tomoyo_print_entry(struct tomoyo_io_buffer *head,  		tomoyo_print_name_union(head, &ptr->dir_name);  		tomoyo_print_name_union(head, &ptr->fs_type);  		tomoyo_print_number_union(head, &ptr->flags); +	} else if (acl_type == TOMOYO_TYPE_ENV_ACL) { +		struct tomoyo_env_acl *ptr = +			container_of(acl, typeof(*ptr), head); + +		tomoyo_set_group(head, "misc env "); +		tomoyo_set_string(head, ptr->env->name);  	}  	if (acl->cond) {  		head->r.print_cond_part = true; @@ -1510,14 +1667,12 @@ static void tomoyo_read_pid(struct tomoyo_io_buffer *head)  		global_pid = true;  	pid = (unsigned int) simple_strtoul(buf, NULL, 10);  	rcu_read_lock(); -	read_lock(&tasklist_lock);  	if (global_pid)  		p = find_task_by_pid_ns(pid, &init_pid_ns);  	else  		p = find_task_by_vpid(pid);  	if (p)  		domain = tomoyo_real_domain(p); -	read_unlock(&tasklist_lock);  	rcu_read_unlock();  	if (!domain)  		return; @@ -1537,8 +1692,9 @@ static const char *tomoyo_transition_type[TOMOYO_MAX_TRANSITION_TYPE] = {  /* String table for grouping keywords. */  static const char *tomoyo_group_name[TOMOYO_MAX_GROUP] = { -	[TOMOYO_PATH_GROUP]   = "path_group ", -	[TOMOYO_NUMBER_GROUP] = "number_group ", +	[TOMOYO_PATH_GROUP]    = "path_group ", +	[TOMOYO_NUMBER_GROUP]  = "number_group ", +	[TOMOYO_ADDRESS_GROUP] = "address_group ",  };  /** @@ -1580,7 +1736,7 @@ static int tomoyo_write_exception(struct tomoyo_io_buffer *head)  }  /** - * tomoyo_read_group - Read "struct tomoyo_path_group"/"struct tomoyo_number_group" list. + * tomoyo_read_group - Read "struct tomoyo_path_group"/"struct tomoyo_number_group"/"struct tomoyo_address_group" list.   *   * @head: Pointer to "struct tomoyo_io_buffer".   * @idx:  Index number. @@ -1617,6 +1773,15 @@ static bool tomoyo_read_group(struct tomoyo_io_buffer *head, const int idx)  							  (ptr,  						   struct tomoyo_number_group,  							   head)->number); +			} else if (idx == TOMOYO_ADDRESS_GROUP) { +				char buffer[128]; + +				struct tomoyo_address_group *member = +					container_of(ptr, typeof(*member), +						     head); +				tomoyo_print_ip(buffer, sizeof(buffer), +						&member->address); +				tomoyo_io_printf(head, " %s", buffer);  			}  			tomoyo_set_lf(head);  		} @@ -2066,27 +2231,7 @@ static int tomoyo_write_answer(struct tomoyo_io_buffer *head)  static void tomoyo_read_version(struct tomoyo_io_buffer *head)  {  	if (!head->r.eof) { -		tomoyo_io_printf(head, "2.4.0"); -		head->r.eof = true; -	} -} - -/** - * tomoyo_read_self_domain - Get the current process's domainname. - * - * @head: Pointer to "struct tomoyo_io_buffer". - * - * Returns the current process's domainname. - */ -static void tomoyo_read_self_domain(struct tomoyo_io_buffer *head) -{ -	if (!head->r.eof) { -		/* -		 * tomoyo_domain()->domainname != NULL -		 * because every process belongs to a domain and -		 * the domain's name cannot be NULL. -		 */ -		tomoyo_io_printf(head, "%s", tomoyo_domain()->domainname->name); +		tomoyo_io_printf(head, "2.5.0");  		head->r.eof = true;  	}  } @@ -2221,10 +2366,6 @@ int tomoyo_open_control(const u8 type, struct file *file)  		head->poll = tomoyo_poll_log;  		head->read = tomoyo_read_log;  		break; -	case TOMOYO_SELFDOMAIN: -		/* /sys/kernel/security/tomoyo/self_domain */ -		head->read = tomoyo_read_self_domain; -		break;  	case TOMOYO_PROCESS_STATUS:  		/* /sys/kernel/security/tomoyo/.process_status */  		head->write = tomoyo_write_pid; @@ -2453,6 +2594,7 @@ ssize_t tomoyo_write_control(struct tomoyo_io_buffer *head,  		return -EFAULT;  	if (mutex_lock_interruptible(&head->io_sem))  		return -EINTR; +	head->read_user_buf_avail = 0;  	idx = tomoyo_read_lock();  	/* Read a line and dispatch it to the policy handler. */  	while (avail_len > 0) { @@ -2562,11 +2704,11 @@ void tomoyo_check_profile(void)  	struct tomoyo_domain_info *domain;  	const int idx = tomoyo_read_lock();  	tomoyo_policy_loaded = true; -	printk(KERN_INFO "TOMOYO: 2.4.0\n"); +	printk(KERN_INFO "TOMOYO: 2.5.0\n");  	list_for_each_entry_rcu(domain, &tomoyo_domain_list, list) {  		const u8 profile = domain->profile;  		const struct tomoyo_policy_namespace *ns = domain->ns; -		if (ns->profile_version != 20100903) +		if (ns->profile_version != 20110903)  			printk(KERN_ERR  			       "Profile version %u is not supported.\n",  			       ns->profile_version); @@ -2577,9 +2719,9 @@ void tomoyo_check_profile(void)  		else  			continue;  		printk(KERN_ERR -		       "Userland tools for TOMOYO 2.4 must be installed and " +		       "Userland tools for TOMOYO 2.5 must be installed and "  		       "policy must be initialized.\n"); -		printk(KERN_ERR "Please see http://tomoyo.sourceforge.jp/2.4/ " +		printk(KERN_ERR "Please see http://tomoyo.sourceforge.jp/2.5/ "  		       "for more information.\n");  		panic("STOP!");  	} diff --git a/security/tomoyo/common.h b/security/tomoyo/common.h index f7fbaa66e44..ed311d7a8ce 100644 --- a/security/tomoyo/common.h +++ b/security/tomoyo/common.h @@ -3,7 +3,7 @@   *   * Header file for TOMOYO.   * - * Copyright (C) 2005-2010  NTT DATA CORPORATION + * Copyright (C) 2005-2011  NTT DATA CORPORATION   */  #ifndef _SECURITY_TOMOYO_COMMON_H @@ -23,6 +23,16 @@  #include <linux/poll.h>  #include <linux/binfmts.h>  #include <linux/highmem.h> +#include <linux/net.h> +#include <linux/inet.h> +#include <linux/in.h> +#include <linux/in6.h> +#include <linux/un.h> +#include <net/sock.h> +#include <net/af_unix.h> +#include <net/ip.h> +#include <net/ipv6.h> +#include <net/udp.h>  /********** Constants definitions. **********/ @@ -34,8 +44,17 @@  #define TOMOYO_HASH_BITS  8  #define TOMOYO_MAX_HASH (1u<<TOMOYO_HASH_BITS) +/* + * TOMOYO checks only SOCK_STREAM, SOCK_DGRAM, SOCK_RAW, SOCK_SEQPACKET. + * Therefore, we don't need SOCK_MAX. + */ +#define TOMOYO_SOCK_MAX 6 +  #define TOMOYO_EXEC_TMPSIZE     4096 +/* Garbage collector is trying to kfree() this element. */ +#define TOMOYO_GC_IN_PROGRESS -1 +  /* Profile number is an integer between 0 and 255. */  #define TOMOYO_MAX_PROFILES 256 @@ -136,6 +155,7 @@ enum tomoyo_mode_index {  /* Index numbers for entry type. */  enum tomoyo_policy_id {  	TOMOYO_ID_GROUP, +	TOMOYO_ID_ADDRESS_GROUP,  	TOMOYO_ID_PATH_GROUP,  	TOMOYO_ID_NUMBER_GROUP,  	TOMOYO_ID_TRANSITION_CONTROL, @@ -162,10 +182,21 @@ enum tomoyo_domain_info_flags_index {  	TOMOYO_MAX_DOMAIN_INFO_FLAGS  }; +/* Index numbers for audit type. */ +enum tomoyo_grant_log { +	/* Follow profile's configuration. */ +	TOMOYO_GRANTLOG_AUTO, +	/* Do not generate grant log. */ +	TOMOYO_GRANTLOG_NO, +	/* Generate grant_log. */ +	TOMOYO_GRANTLOG_YES, +}; +  /* Index numbers for group entries. */  enum tomoyo_group_id {  	TOMOYO_PATH_GROUP,  	TOMOYO_NUMBER_GROUP, +	TOMOYO_ADDRESS_GROUP,  	TOMOYO_MAX_GROUP  }; @@ -196,6 +227,10 @@ enum tomoyo_acl_entry_type_index {  	TOMOYO_TYPE_PATH_NUMBER_ACL,  	TOMOYO_TYPE_MKDEV_ACL,  	TOMOYO_TYPE_MOUNT_ACL, +	TOMOYO_TYPE_INET_ACL, +	TOMOYO_TYPE_UNIX_ACL, +	TOMOYO_TYPE_ENV_ACL, +	TOMOYO_TYPE_MANUAL_TASK_ACL,  };  /* Index numbers for access controls with one pathname. */ @@ -228,6 +263,15 @@ enum tomoyo_mkdev_acl_index {  	TOMOYO_MAX_MKDEV_OPERATION  }; +/* Index numbers for socket operations. */ +enum tomoyo_network_acl_index { +	TOMOYO_NETWORK_BIND,    /* bind() operation. */ +	TOMOYO_NETWORK_LISTEN,  /* listen() operation. */ +	TOMOYO_NETWORK_CONNECT, /* connect() operation. */ +	TOMOYO_NETWORK_SEND,    /* send() operation. */ +	TOMOYO_MAX_NETWORK_OPERATION +}; +  /* Index numbers for access controls with two pathnames. */  enum tomoyo_path2_acl_index {  	TOMOYO_TYPE_LINK, @@ -255,7 +299,6 @@ enum tomoyo_securityfs_interface_index {  	TOMOYO_EXCEPTIONPOLICY,  	TOMOYO_PROCESS_STATUS,  	TOMOYO_STAT, -	TOMOYO_SELFDOMAIN,  	TOMOYO_AUDIT,  	TOMOYO_VERSION,  	TOMOYO_PROFILE, @@ -300,12 +343,30 @@ enum tomoyo_mac_index {  	TOMOYO_MAC_FILE_MOUNT,  	TOMOYO_MAC_FILE_UMOUNT,  	TOMOYO_MAC_FILE_PIVOT_ROOT, +	TOMOYO_MAC_NETWORK_INET_STREAM_BIND, +	TOMOYO_MAC_NETWORK_INET_STREAM_LISTEN, +	TOMOYO_MAC_NETWORK_INET_STREAM_CONNECT, +	TOMOYO_MAC_NETWORK_INET_DGRAM_BIND, +	TOMOYO_MAC_NETWORK_INET_DGRAM_SEND, +	TOMOYO_MAC_NETWORK_INET_RAW_BIND, +	TOMOYO_MAC_NETWORK_INET_RAW_SEND, +	TOMOYO_MAC_NETWORK_UNIX_STREAM_BIND, +	TOMOYO_MAC_NETWORK_UNIX_STREAM_LISTEN, +	TOMOYO_MAC_NETWORK_UNIX_STREAM_CONNECT, +	TOMOYO_MAC_NETWORK_UNIX_DGRAM_BIND, +	TOMOYO_MAC_NETWORK_UNIX_DGRAM_SEND, +	TOMOYO_MAC_NETWORK_UNIX_SEQPACKET_BIND, +	TOMOYO_MAC_NETWORK_UNIX_SEQPACKET_LISTEN, +	TOMOYO_MAC_NETWORK_UNIX_SEQPACKET_CONNECT, +	TOMOYO_MAC_ENVIRON,  	TOMOYO_MAX_MAC_INDEX  };  /* Index numbers for category of functionality. */  enum tomoyo_mac_category_index {  	TOMOYO_MAC_CATEGORY_FILE, +	TOMOYO_MAC_CATEGORY_NETWORK, +	TOMOYO_MAC_CATEGORY_MISC,  	TOMOYO_MAX_MAC_CATEGORY_INDEX  }; @@ -340,7 +401,7 @@ enum tomoyo_pref_index {  /* Common header for holding ACL entries. */  struct tomoyo_acl_head {  	struct list_head list; -	bool is_deleted; +	s8 is_deleted; /* true or false or TOMOYO_GC_IN_PROGRESS */  } __packed;  /* Common header for shared entries. */ @@ -397,13 +458,36 @@ struct tomoyo_request_info {  			u8 operation;  		} path_number;  		struct { +			const struct tomoyo_path_info *name; +		} environ; +		struct { +			const __be32 *address; +			u16 port; +			/* One of values smaller than TOMOYO_SOCK_MAX. */ +			u8 protocol; +			/* One of values in "enum tomoyo_network_acl_index". */ +			u8 operation; +			bool is_ipv6; +		} inet_network; +		struct { +			const struct tomoyo_path_info *address; +			/* One of values smaller than TOMOYO_SOCK_MAX. */ +			u8 protocol; +			/* One of values in "enum tomoyo_network_acl_index". */ +			u8 operation; +		} unix_network; +		struct {  			const struct tomoyo_path_info *type;  			const struct tomoyo_path_info *dir;  			const struct tomoyo_path_info *dev;  			unsigned long flags;  			int need_dev;  		} mount; +		struct { +			const struct tomoyo_path_info *domainname; +		} task;  	} param; +	struct tomoyo_acl_info *matched_acl;  	u8 param_type;  	bool granted;  	u8 retry; @@ -442,7 +526,14 @@ struct tomoyo_number_union {  	u8 value_type[2];  }; -/* Structure for "path_group"/"number_group" directive. */ +/* Structure for holding an IP address. */ +struct tomoyo_ipaddr_union { +	struct in6_addr ip[2]; /* Big endian. */ +	struct tomoyo_group *group; /* Pointer to address group. */ +	bool is_ipv6; /* Valid only if @group == NULL. */ +}; + +/* Structure for "path_group"/"number_group"/"address_group" directive. */  struct tomoyo_group {  	struct tomoyo_shared_acl_head head;  	const struct tomoyo_path_info *group_name; @@ -461,6 +552,13 @@ struct tomoyo_number_group {  	struct tomoyo_number_union number;  }; +/* Structure for "address_group" directive. */ +struct tomoyo_address_group { +	struct tomoyo_acl_head head; +	/* Structure for holding an IP address. */ +	struct tomoyo_ipaddr_union address; +}; +  /* Subset of "struct stat". Used by conditional ACL and audit logs. */  struct tomoyo_mini_stat {  	uid_t uid; @@ -520,6 +618,7 @@ struct tomoyo_execve {  	struct tomoyo_request_info r;  	struct tomoyo_obj_info obj;  	struct linux_binprm *bprm; +	const struct tomoyo_path_info *transition;  	/* For dumping argv[] and envp[]. */  	struct tomoyo_page_dump dump;  	/* For temporary use. */ @@ -554,6 +653,8 @@ struct tomoyo_condition {  	u16 names_count; /* Number of "struct tomoyo_name_union names". */  	u16 argc; /* Number of "struct tomoyo_argv". */  	u16 envc; /* Number of "struct tomoyo_envp". */ +	u8 grant_log; /* One of values in "enum tomoyo_grant_log". */ +	const struct tomoyo_path_info *transit; /* Maybe NULL. */  	/*  	 * struct tomoyo_condition_element condition[condc];  	 * struct tomoyo_number_union values[numbers_count]; @@ -567,7 +668,7 @@ struct tomoyo_condition {  struct tomoyo_acl_info {  	struct list_head list;  	struct tomoyo_condition *cond; /* Maybe NULL. */ -	bool is_deleted; +	s8 is_deleted; /* true or false or TOMOYO_GC_IN_PROGRESS */  	u8 type; /* One of values in "enum tomoyo_acl_entry_type_index". */  } __packed; @@ -587,6 +688,15 @@ struct tomoyo_domain_info {  };  /* + * Structure for "task manual_domain_transition" directive. + */ +struct tomoyo_task_acl { +	struct tomoyo_acl_info head; /* type = TOMOYO_TYPE_MANUAL_TASK_ACL */ +	/* Pointer to domainname. */ +	const struct tomoyo_path_info *domainname; +}; + +/*   * Structure for "file execute", "file read", "file write", "file append",   * "file unlink", "file getattr", "file rmdir", "file truncate",   * "file symlink", "file chroot" and "file unmount" directive. @@ -638,6 +748,29 @@ struct tomoyo_mount_acl {  	struct tomoyo_number_union flags;  }; +/* Structure for "misc env" directive in domain policy. */ +struct tomoyo_env_acl { +	struct tomoyo_acl_info head;        /* type = TOMOYO_TYPE_ENV_ACL  */ +	const struct tomoyo_path_info *env; /* environment variable */ +}; + +/* Structure for "network inet" directive. */ +struct tomoyo_inet_acl { +	struct tomoyo_acl_info head; /* type = TOMOYO_TYPE_INET_ACL */ +	u8 protocol; +	u8 perm; /* Bitmask of values in "enum tomoyo_network_acl_index" */ +	struct tomoyo_ipaddr_union address; +	struct tomoyo_number_union port; +}; + +/* Structure for "network unix" directive. */ +struct tomoyo_unix_acl { +	struct tomoyo_acl_info head; /* type = TOMOYO_TYPE_UNIX_ACL */ +	u8 protocol; +	u8 perm; /* Bitmask of values in "enum tomoyo_network_acl_index" */ +	struct tomoyo_name_union name; +}; +  /* Structure for holding a line from /sys/kernel/security/tomoyo/ interface. */  struct tomoyo_acl_param {  	char *data; @@ -773,7 +906,7 @@ struct tomoyo_policy_namespace {  	struct list_head acl_group[TOMOYO_MAX_ACL_GROUPS];  	/* List for connecting to tomoyo_namespace_list list. */  	struct list_head namespace_list; -	/* Profile version. Currently only 20100903 is defined. */ +	/* Profile version. Currently only 20110903 is defined. */  	unsigned int profile_version;  	/* Name of this namespace (e.g. "<kernel>", "</usr/sbin/httpd>" ). */  	const char *name; @@ -781,6 +914,8 @@ struct tomoyo_policy_namespace {  /********** Function prototypes. **********/ +bool tomoyo_address_matches_group(const bool is_ipv6, const __be32 *address, +				  const struct tomoyo_group *group);  bool tomoyo_compare_number_union(const unsigned long value,  				 const struct tomoyo_number_union *ptr);  bool tomoyo_condition(struct tomoyo_request_info *r, @@ -796,6 +931,8 @@ bool tomoyo_memory_ok(void *ptr);  bool tomoyo_number_matches_group(const unsigned long min,  				 const unsigned long max,  				 const struct tomoyo_group *group); +bool tomoyo_parse_ipaddr_union(struct tomoyo_acl_param *param, +			       struct tomoyo_ipaddr_union *ptr);  bool tomoyo_parse_name_union(struct tomoyo_acl_param *param,  			     struct tomoyo_name_union *ptr);  bool tomoyo_parse_number_union(struct tomoyo_acl_param *param, @@ -805,6 +942,7 @@ bool tomoyo_path_matches_pattern(const struct tomoyo_path_info *filename,  bool tomoyo_permstr(const char *string, const char *keyword);  bool tomoyo_str_starts(char **src, const char *find);  char *tomoyo_encode(const char *str); +char *tomoyo_encode2(const char *str, int str_len);  char *tomoyo_init_log(struct tomoyo_request_info *r, int len, const char *fmt,  		      va_list args);  char *tomoyo_read_token(struct tomoyo_acl_param *param); @@ -814,12 +952,17 @@ const char *tomoyo_get_exe(void);  const char *tomoyo_yesno(const unsigned int value);  const struct tomoyo_path_info *tomoyo_compare_name_union  (const struct tomoyo_path_info *name, const struct tomoyo_name_union *ptr); +const struct tomoyo_path_info *tomoyo_get_domainname +(struct tomoyo_acl_param *param);  const struct tomoyo_path_info *tomoyo_get_name(const char *name);  const struct tomoyo_path_info *tomoyo_path_matches_group  (const struct tomoyo_path_info *pathname, const struct tomoyo_group *group);  int tomoyo_check_open_permission(struct tomoyo_domain_info *domain,  				 struct path *path, const int flag);  int tomoyo_close_control(struct tomoyo_io_buffer *head); +int tomoyo_env_perm(struct tomoyo_request_info *r, const char *env); +int tomoyo_execute_permission(struct tomoyo_request_info *r, +			      const struct tomoyo_path_info *filename);  int tomoyo_find_next_domain(struct linux_binprm *bprm);  int tomoyo_get_mode(const struct tomoyo_policy_namespace *ns, const u8 profile,  		    const u8 index); @@ -838,10 +981,15 @@ int tomoyo_path_number_perm(const u8 operation, struct path *path,  			    unsigned long number);  int tomoyo_path_perm(const u8 operation, struct path *path,  		     const char *target); -int tomoyo_path_permission(struct tomoyo_request_info *r, u8 operation, -			   const struct tomoyo_path_info *filename);  int tomoyo_poll_control(struct file *file, poll_table *wait);  int tomoyo_poll_log(struct file *file, poll_table *wait); +int tomoyo_socket_bind_permission(struct socket *sock, struct sockaddr *addr, +				  int addr_len); +int tomoyo_socket_connect_permission(struct socket *sock, +				     struct sockaddr *addr, int addr_len); +int tomoyo_socket_listen_permission(struct socket *sock); +int tomoyo_socket_sendmsg_permission(struct socket *sock, struct msghdr *msg, +				     int size);  int tomoyo_supervisor(struct tomoyo_request_info *r, const char *fmt, ...)  	__printf(2, 3);  int tomoyo_update_domain(struct tomoyo_acl_info *new_entry, const int size, @@ -860,8 +1008,11 @@ int tomoyo_update_policy(struct tomoyo_acl_head *new_entry, const int size,  int tomoyo_write_aggregator(struct tomoyo_acl_param *param);  int tomoyo_write_file(struct tomoyo_acl_param *param);  int tomoyo_write_group(struct tomoyo_acl_param *param, const u8 type); +int tomoyo_write_misc(struct tomoyo_acl_param *param); +int tomoyo_write_inet_network(struct tomoyo_acl_param *param);  int tomoyo_write_transition_control(struct tomoyo_acl_param *param,  				    const u8 type); +int tomoyo_write_unix_network(struct tomoyo_acl_param *param);  ssize_t tomoyo_read_control(struct tomoyo_io_buffer *head, char __user *buffer,  			    const int buffer_len);  ssize_t tomoyo_write_control(struct tomoyo_io_buffer *head, @@ -891,12 +1042,11 @@ void tomoyo_del_condition(struct list_head *element);  void tomoyo_fill_path_info(struct tomoyo_path_info *ptr);  void tomoyo_get_attributes(struct tomoyo_obj_info *obj);  void tomoyo_init_policy_namespace(struct tomoyo_policy_namespace *ns); -void tomoyo_io_printf(struct tomoyo_io_buffer *head, const char *fmt, ...) -	 __printf(2, 3);  void tomoyo_load_policy(const char *filename); -void tomoyo_memory_free(void *ptr);  void tomoyo_normalize_line(unsigned char *buffer);  void tomoyo_notify_gc(struct tomoyo_io_buffer *head, const bool is_register); +void tomoyo_print_ip(char *buf, const unsigned int size, +		     const struct tomoyo_ipaddr_union *ptr);  void tomoyo_print_ulong(char *buffer, const int buffer_len,  			const unsigned long value, const u8 type);  void tomoyo_put_name_union(struct tomoyo_name_union *ptr); @@ -919,6 +1069,8 @@ extern const char * const tomoyo_mac_keywords[TOMOYO_MAX_MAC_INDEX  					      + TOMOYO_MAX_MAC_CATEGORY_INDEX];  extern const char * const tomoyo_mode[TOMOYO_CONFIG_MAX_MODE];  extern const char * const tomoyo_path_keyword[TOMOYO_MAX_PATH_OPERATION]; +extern const char * const tomoyo_proto_keyword[TOMOYO_SOCK_MAX]; +extern const char * const tomoyo_socket_keyword[TOMOYO_MAX_NETWORK_OPERATION];  extern const u8 tomoyo_index2category[TOMOYO_MAX_MAC_INDEX];  extern const u8 tomoyo_pn2mac[TOMOYO_MAX_PATH_NUMBER_OPERATION];  extern const u8 tomoyo_pnnn2mac[TOMOYO_MAX_MKDEV_OPERATION]; @@ -1098,6 +1250,21 @@ static inline bool tomoyo_same_number_union  }  /** + * tomoyo_same_ipaddr_union - Check for duplicated "struct tomoyo_ipaddr_union" entry. + * + * @a: Pointer to "struct tomoyo_ipaddr_union". + * @b: Pointer to "struct tomoyo_ipaddr_union". + * + * Returns true if @a == @b, false otherwise. + */ +static inline bool tomoyo_same_ipaddr_union +(const struct tomoyo_ipaddr_union *a, const struct tomoyo_ipaddr_union *b) +{ +	return !memcmp(a->ip, b->ip, sizeof(a->ip)) && a->group == b->group && +		a->is_ipv6 == b->is_ipv6; +} + +/**   * tomoyo_current_namespace - Get "struct tomoyo_policy_namespace" for current thread.   *   * Returns pointer to "struct tomoyo_policy_namespace" for current thread. diff --git a/security/tomoyo/condition.c b/security/tomoyo/condition.c index 8a05f71eaf6..986330b8c73 100644 --- a/security/tomoyo/condition.c +++ b/security/tomoyo/condition.c @@ -348,6 +348,7 @@ static inline bool tomoyo_same_condition(const struct tomoyo_condition *a,  		a->numbers_count == b->numbers_count &&  		a->names_count == b->names_count &&  		a->argc == b->argc && a->envc == b->envc && +		a->grant_log == b->grant_log && a->transit == b->transit &&  		!memcmp(a + 1, b + 1, a->size - sizeof(*a));  } @@ -399,8 +400,9 @@ static struct tomoyo_condition *tomoyo_commit_condition  		found = true;  		goto out;  	} -	list_for_each_entry_rcu(ptr, &tomoyo_condition_list, head.list) { -		if (!tomoyo_same_condition(ptr, entry)) +	list_for_each_entry(ptr, &tomoyo_condition_list, head.list) { +		if (!tomoyo_same_condition(ptr, entry) || +		    atomic_read(&ptr->head.users) == TOMOYO_GC_IN_PROGRESS)  			continue;  		/* Same entry found. Share this entry. */  		atomic_inc(&ptr->head.users); @@ -410,8 +412,7 @@ static struct tomoyo_condition *tomoyo_commit_condition  	if (!found) {  		if (tomoyo_memory_ok(entry)) {  			atomic_set(&entry->head.users, 1); -			list_add_rcu(&entry->head.list, -				     &tomoyo_condition_list); +			list_add(&entry->head.list, &tomoyo_condition_list);  		} else {  			found = true;  			ptr = NULL; @@ -428,6 +429,46 @@ out:  }  /** + * tomoyo_get_transit_preference - Parse domain transition preference for execve(). + * + * @param: Pointer to "struct tomoyo_acl_param". + * @e:     Pointer to "struct tomoyo_condition". + * + * Returns the condition string part. + */ +static char *tomoyo_get_transit_preference(struct tomoyo_acl_param *param, +					   struct tomoyo_condition *e) +{ +	char * const pos = param->data; +	bool flag; +	if (*pos == '<') { +		e->transit = tomoyo_get_domainname(param); +		goto done; +	} +	{ +		char *cp = strchr(pos, ' '); +		if (cp) +			*cp = '\0'; +		flag = tomoyo_correct_path(pos) || !strcmp(pos, "keep") || +			!strcmp(pos, "initialize") || !strcmp(pos, "reset") || +			!strcmp(pos, "child") || !strcmp(pos, "parent"); +		if (cp) +			*cp = ' '; +	} +	if (!flag) +		return pos; +	e->transit = tomoyo_get_name(tomoyo_read_token(param)); +done: +	if (e->transit) +		return param->data; +	/* +	 * Return a bad read-only condition string that will let +	 * tomoyo_get_condition() return NULL. +	 */ +	return "/"; +} + +/**   * tomoyo_get_condition - Parse condition part.   *   * @param: Pointer to "struct tomoyo_acl_param". @@ -443,7 +484,8 @@ struct tomoyo_condition *tomoyo_get_condition(struct tomoyo_acl_param *param)  	struct tomoyo_argv *argv = NULL;  	struct tomoyo_envp *envp = NULL;  	struct tomoyo_condition e = { }; -	char * const start_of_string = param->data; +	char * const start_of_string = +		tomoyo_get_transit_preference(param, &e);  	char * const end_of_string = start_of_string + strlen(start_of_string);  	char *pos;  rerun: @@ -486,6 +528,20 @@ rerun:  			goto out;  		dprintk(KERN_WARNING "%u: <%s>%s=<%s>\n", __LINE__, left_word,  			is_not ? "!" : "", right_word); +		if (!strcmp(left_word, "grant_log")) { +			if (entry) { +				if (is_not || +				    entry->grant_log != TOMOYO_GRANTLOG_AUTO) +					goto out; +				else if (!strcmp(right_word, "yes")) +					entry->grant_log = TOMOYO_GRANTLOG_YES; +				else if (!strcmp(right_word, "no")) +					entry->grant_log = TOMOYO_GRANTLOG_NO; +				else +					goto out; +			} +			continue; +		}  		if (!strncmp(left_word, "exec.argv[", 10)) {  			if (!argv) {  				e.argc++; @@ -593,8 +649,9 @@ store_value:  		+ e.envc * sizeof(struct tomoyo_envp);  	entry = kzalloc(e.size, GFP_NOFS);  	if (!entry) -		return NULL; +		goto out2;  	*entry = e; +	e.transit = NULL;  	condp = (struct tomoyo_condition_element *) (entry + 1);  	numbers_p = (struct tomoyo_number_union *) (condp + e.condc);  	names_p = (struct tomoyo_name_union *) (numbers_p + e.numbers_count); @@ -621,6 +678,8 @@ out:  		tomoyo_del_condition(&entry->head.list);  		kfree(entry);  	} +out2: +	tomoyo_put_name(e.transit);  	return NULL;  } diff --git a/security/tomoyo/domain.c b/security/tomoyo/domain.c index cd0f92d88bb..9027ac1534a 100644 --- a/security/tomoyo/domain.c +++ b/security/tomoyo/domain.c @@ -39,6 +39,8 @@ int tomoyo_update_policy(struct tomoyo_acl_head *new_entry, const int size,  	if (mutex_lock_interruptible(&tomoyo_policy_lock))  		return -ENOMEM;  	list_for_each_entry_rcu(entry, list, list) { +		if (entry->is_deleted == TOMOYO_GC_IN_PROGRESS) +			continue;  		if (!check_duplicate(entry, new_entry))  			continue;  		entry->is_deleted = param->is_delete; @@ -102,10 +104,21 @@ int tomoyo_update_domain(struct tomoyo_acl_info *new_entry, const int size,  		new_entry->cond = tomoyo_get_condition(param);  		if (!new_entry->cond)  			return -EINVAL; +		/* +		 * Domain transition preference is allowed for only +		 * "file execute" entries. +		 */ +		if (new_entry->cond->transit && +		    !(new_entry->type == TOMOYO_TYPE_PATH_ACL && +		      container_of(new_entry, struct tomoyo_path_acl, head) +		      ->perm == 1 << TOMOYO_TYPE_EXECUTE)) +			goto out;  	}  	if (mutex_lock_interruptible(&tomoyo_policy_lock))  		goto out;  	list_for_each_entry_rcu(entry, list, list) { +		if (entry->is_deleted == TOMOYO_GC_IN_PROGRESS) +			continue;  		if (!tomoyo_same_acl_head(entry, new_entry) ||  		    !check_duplicate(entry, new_entry))  			continue; @@ -157,6 +170,7 @@ retry:  			continue;  		if (!tomoyo_condition(r, ptr->cond))  			continue; +		r->matched_acl = ptr;  		r->granted = true;  		return;  	} @@ -501,7 +515,8 @@ struct tomoyo_domain_info *tomoyo_assign_domain(const char *domainname,  			 * that domain. Do not perform domain transition if  			 * profile for that domain is not yet created.  			 */ -			if (!entry->ns->profile_ptr[entry->profile]) +			if (tomoyo_policy_loaded && +			    !entry->ns->profile_ptr[entry->profile])  				return NULL;  		}  		return entry; @@ -557,12 +572,99 @@ out:  			tomoyo_write_log(&r, "use_profile %u\n",  					 entry->profile);  			tomoyo_write_log(&r, "use_group %u\n", entry->group); +			tomoyo_update_stat(TOMOYO_STAT_POLICY_UPDATES);  		}  	}  	return entry;  }  /** + * tomoyo_environ - Check permission for environment variable names. + * + * @ee: Pointer to "struct tomoyo_execve". + * + * Returns 0 on success, negative value otherwise. + */ +static int tomoyo_environ(struct tomoyo_execve *ee) +{ +	struct tomoyo_request_info *r = &ee->r; +	struct linux_binprm *bprm = ee->bprm; +	/* env_page.data is allocated by tomoyo_dump_page(). */ +	struct tomoyo_page_dump env_page = { }; +	char *arg_ptr; /* Size is TOMOYO_EXEC_TMPSIZE bytes */ +	int arg_len = 0; +	unsigned long pos = bprm->p; +	int offset = pos % PAGE_SIZE; +	int argv_count = bprm->argc; +	int envp_count = bprm->envc; +	int error = -ENOMEM; + +	ee->r.type = TOMOYO_MAC_ENVIRON; +	ee->r.profile = r->domain->profile; +	ee->r.mode = tomoyo_get_mode(r->domain->ns, ee->r.profile, +				     TOMOYO_MAC_ENVIRON); +	if (!r->mode || !envp_count) +		return 0; +	arg_ptr = kzalloc(TOMOYO_EXEC_TMPSIZE, GFP_NOFS); +	if (!arg_ptr) +		goto out; +	while (error == -ENOMEM) { +		if (!tomoyo_dump_page(bprm, pos, &env_page)) +			goto out; +		pos += PAGE_SIZE - offset; +		/* Read. */ +		while (argv_count && offset < PAGE_SIZE) { +			if (!env_page.data[offset++]) +				argv_count--; +		} +		if (argv_count) { +			offset = 0; +			continue; +		} +		while (offset < PAGE_SIZE) { +			const unsigned char c = env_page.data[offset++]; + +			if (c && arg_len < TOMOYO_EXEC_TMPSIZE - 10) { +				if (c == '=') { +					arg_ptr[arg_len++] = '\0'; +				} else if (c == '\\') { +					arg_ptr[arg_len++] = '\\'; +					arg_ptr[arg_len++] = '\\'; +				} else if (c > ' ' && c < 127) { +					arg_ptr[arg_len++] = c; +				} else { +					arg_ptr[arg_len++] = '\\'; +					arg_ptr[arg_len++] = (c >> 6) + '0'; +					arg_ptr[arg_len++] +						= ((c >> 3) & 7) + '0'; +					arg_ptr[arg_len++] = (c & 7) + '0'; +				} +			} else { +				arg_ptr[arg_len] = '\0'; +			} +			if (c) +				continue; +			if (tomoyo_env_perm(r, arg_ptr)) { +				error = -EPERM; +				break; +			} +			if (!--envp_count) { +				error = 0; +				break; +			} +			arg_len = 0; +		} +		offset = 0; +	} +out: +	if (r->mode != TOMOYO_CONFIG_ENFORCING) +		error = 0; +	kfree(env_page.data); +	kfree(arg_ptr); +	return error; +} + +/**   * tomoyo_find_next_domain - Find a domain.   *   * @bprm: Pointer to "struct linux_binprm". @@ -577,10 +679,11 @@ int tomoyo_find_next_domain(struct linux_binprm *bprm)  	struct tomoyo_domain_info *domain = NULL;  	const char *original_name = bprm->filename;  	int retval = -ENOMEM; -	bool need_kfree = false;  	bool reject_on_transition_failure = false; -	struct tomoyo_path_info rn = { }; /* real name */ +	const struct tomoyo_path_info *candidate; +	struct tomoyo_path_info exename;  	struct tomoyo_execve *ee = kzalloc(sizeof(*ee), GFP_NOFS); +  	if (!ee)  		return -ENOMEM;  	ee->tmp = kzalloc(TOMOYO_EXEC_TMPSIZE, GFP_NOFS); @@ -594,40 +697,32 @@ int tomoyo_find_next_domain(struct linux_binprm *bprm)  	ee->bprm = bprm;  	ee->r.obj = &ee->obj;  	ee->obj.path1 = bprm->file->f_path; - retry: -	if (need_kfree) { -		kfree(rn.name); -		need_kfree = false; -	}  	/* Get symlink's pathname of program. */  	retval = -ENOENT; -	rn.name = tomoyo_realpath_nofollow(original_name); -	if (!rn.name) +	exename.name = tomoyo_realpath_nofollow(original_name); +	if (!exename.name)  		goto out; -	tomoyo_fill_path_info(&rn); -	need_kfree = true; - +	tomoyo_fill_path_info(&exename); +retry:  	/* Check 'aggregator' directive. */  	{  		struct tomoyo_aggregator *ptr;  		struct list_head *list =  			&old_domain->ns->policy_list[TOMOYO_ID_AGGREGATOR];  		/* Check 'aggregator' directive. */ +		candidate = &exename;  		list_for_each_entry_rcu(ptr, list, head.list) {  			if (ptr->head.is_deleted || -			    !tomoyo_path_matches_pattern(&rn, +			    !tomoyo_path_matches_pattern(&exename,  							 ptr->original_name))  				continue; -			kfree(rn.name); -			need_kfree = false; -			/* This is OK because it is read only. */ -			rn = *ptr->aggregated_name; +			candidate = ptr->aggregated_name;  			break;  		}  	}  	/* Check execute permission. */ -	retval = tomoyo_path_permission(&ee->r, TOMOYO_TYPE_EXECUTE, &rn); +	retval = tomoyo_execute_permission(&ee->r, candidate);  	if (retval == TOMOYO_RETRY_REQUEST)  		goto retry;  	if (retval < 0) @@ -638,20 +733,51 @@ int tomoyo_find_next_domain(struct linux_binprm *bprm)  	 * wildcard) rather than the pathname passed to execve()  	 * (which never contains wildcard).  	 */ -	if (ee->r.param.path.matched_path) { -		if (need_kfree) -			kfree(rn.name); -		need_kfree = false; -		/* This is OK because it is read only. */ -		rn = *ee->r.param.path.matched_path; -	} +	if (ee->r.param.path.matched_path) +		candidate = ee->r.param.path.matched_path; -	/* Calculate domain to transit to. */ +	/* +	 * Check for domain transition preference if "file execute" matched. +	 * If preference is given, make do_execve() fail if domain transition +	 * has failed, for domain transition preference should be used with +	 * destination domain defined. +	 */ +	if (ee->transition) { +		const char *domainname = ee->transition->name; +		reject_on_transition_failure = true; +		if (!strcmp(domainname, "keep")) +			goto force_keep_domain; +		if (!strcmp(domainname, "child")) +			goto force_child_domain; +		if (!strcmp(domainname, "reset")) +			goto force_reset_domain; +		if (!strcmp(domainname, "initialize")) +			goto force_initialize_domain; +		if (!strcmp(domainname, "parent")) { +			char *cp; +			strncpy(ee->tmp, old_domain->domainname->name, +				TOMOYO_EXEC_TMPSIZE - 1); +			cp = strrchr(ee->tmp, ' '); +			if (cp) +				*cp = '\0'; +		} else if (*domainname == '<') +			strncpy(ee->tmp, domainname, TOMOYO_EXEC_TMPSIZE - 1); +		else +			snprintf(ee->tmp, TOMOYO_EXEC_TMPSIZE - 1, "%s %s", +				 old_domain->domainname->name, domainname); +		goto force_jump_domain; +	} +	/* +	 * No domain transition preference specified. +	 * Calculate domain to transit to. +	 */  	switch (tomoyo_transition_type(old_domain->ns, old_domain->domainname, -				       &rn)) { +				       candidate)) {  	case TOMOYO_TRANSITION_CONTROL_RESET: +force_reset_domain:  		/* Transit to the root of specified namespace. */ -		snprintf(ee->tmp, TOMOYO_EXEC_TMPSIZE - 1, "<%s>", rn.name); +		snprintf(ee->tmp, TOMOYO_EXEC_TMPSIZE - 1, "<%s>", +			 candidate->name);  		/*  		 * Make do_execve() fail if domain transition across namespaces  		 * has failed. @@ -659,11 +785,13 @@ int tomoyo_find_next_domain(struct linux_binprm *bprm)  		reject_on_transition_failure = true;  		break;  	case TOMOYO_TRANSITION_CONTROL_INITIALIZE: +force_initialize_domain:  		/* Transit to the child of current namespace's root. */  		snprintf(ee->tmp, TOMOYO_EXEC_TMPSIZE - 1, "%s %s", -			 old_domain->ns->name, rn.name); +			 old_domain->ns->name, candidate->name);  		break;  	case TOMOYO_TRANSITION_CONTROL_KEEP: +force_keep_domain:  		/* Keep current domain. */  		domain = old_domain;  		break; @@ -677,13 +805,15 @@ int tomoyo_find_next_domain(struct linux_binprm *bprm)  			 * before /sbin/init.  			 */  			domain = old_domain; -		} else { -			/* Normal domain transition. */ -			snprintf(ee->tmp, TOMOYO_EXEC_TMPSIZE - 1, "%s %s", -				 old_domain->domainname->name, rn.name); +			break;  		} +force_child_domain: +		/* Normal domain transition. */ +		snprintf(ee->tmp, TOMOYO_EXEC_TMPSIZE - 1, "%s %s", +			 old_domain->domainname->name, candidate->name);  		break;  	} +force_jump_domain:  	if (!domain)  		domain = tomoyo_assign_domain(ee->tmp, true);  	if (domain) @@ -711,8 +841,11 @@ int tomoyo_find_next_domain(struct linux_binprm *bprm)  	/* Update reference count on "struct tomoyo_domain_info". */  	atomic_inc(&domain->users);  	bprm->cred->security = domain; -	if (need_kfree) -		kfree(rn.name); +	kfree(exename.name); +	if (!retval) { +		ee->r.domain = domain; +		retval = tomoyo_environ(ee); +	}  	kfree(ee->tmp);  	kfree(ee->dump.data);  	kfree(ee); @@ -732,7 +865,8 @@ bool tomoyo_dump_page(struct linux_binprm *bprm, unsigned long pos,  		      struct tomoyo_page_dump *dump)  {  	struct page *page; -	/* dump->data is released by tomoyo_finish_execve(). */ + +	/* dump->data is released by tomoyo_find_next_domain(). */  	if (!dump->data) {  		dump->data = kzalloc(PAGE_SIZE, GFP_NOFS);  		if (!dump->data) @@ -753,6 +887,7 @@ bool tomoyo_dump_page(struct linux_binprm *bprm, unsigned long pos,  		 * So do I.  		 */  		char *kaddr = kmap_atomic(page, KM_USER0); +  		dump->page = page;  		memcpy(dump->data + offset, kaddr + offset,  		       PAGE_SIZE - offset); diff --git a/security/tomoyo/environ.c b/security/tomoyo/environ.c new file mode 100644 index 00000000000..ad4c6e18a43 --- /dev/null +++ b/security/tomoyo/environ.c @@ -0,0 +1,122 @@ +/* + * security/tomoyo/environ.c + * + * Copyright (C) 2005-2011  NTT DATA CORPORATION + */ + +#include "common.h" + +/** + * tomoyo_check_env_acl - Check permission for environment variable's name. + * + * @r:   Pointer to "struct tomoyo_request_info". + * @ptr: Pointer to "struct tomoyo_acl_info". + * + * Returns true if granted, false otherwise. + */ +static bool tomoyo_check_env_acl(struct tomoyo_request_info *r, +				 const struct tomoyo_acl_info *ptr) +{ +	const struct tomoyo_env_acl *acl = +		container_of(ptr, typeof(*acl), head); + +	return tomoyo_path_matches_pattern(r->param.environ.name, acl->env); +} + +/** + * tomoyo_audit_env_log - Audit environment variable name log. + * + * @r: Pointer to "struct tomoyo_request_info". + * + * Returns 0 on success, negative value otherwise. + */ +static int tomoyo_audit_env_log(struct tomoyo_request_info *r) +{ +	return tomoyo_supervisor(r, "misc env %s\n", +				 r->param.environ.name->name); +} + +/** + * tomoyo_env_perm - Check permission for environment variable's name. + * + * @r:   Pointer to "struct tomoyo_request_info". + * @env: The name of environment variable. + * + * Returns 0 on success, negative value otherwise. + * + * Caller holds tomoyo_read_lock(). + */ +int tomoyo_env_perm(struct tomoyo_request_info *r, const char *env) +{ +	struct tomoyo_path_info environ; +	int error; + +	if (!env || !*env) +		return 0; +	environ.name = env; +	tomoyo_fill_path_info(&environ); +	r->param_type = TOMOYO_TYPE_ENV_ACL; +	r->param.environ.name = &environ; +	do { +		tomoyo_check_acl(r, tomoyo_check_env_acl); +		error = tomoyo_audit_env_log(r); +	} while (error == TOMOYO_RETRY_REQUEST); +	return error; +} + +/** + * tomoyo_same_env_acl - Check for duplicated "struct tomoyo_env_acl" entry. + * + * @a: Pointer to "struct tomoyo_acl_info". + * @b: Pointer to "struct tomoyo_acl_info". + * + * Returns true if @a == @b, false otherwise. + */ +static bool tomoyo_same_env_acl(const struct tomoyo_acl_info *a, +				const struct tomoyo_acl_info *b) +{ +	const struct tomoyo_env_acl *p1 = container_of(a, typeof(*p1), head); +	const struct tomoyo_env_acl *p2 = container_of(b, typeof(*p2), head); + +	return p1->env == p2->env; +} + +/** + * tomoyo_write_env - Write "struct tomoyo_env_acl" list. + * + * @param: Pointer to "struct tomoyo_acl_param". + * + * Returns 0 on success, negative value otherwise. + * + * Caller holds tomoyo_read_lock(). + */ +static int tomoyo_write_env(struct tomoyo_acl_param *param) +{ +	struct tomoyo_env_acl e = { .head.type = TOMOYO_TYPE_ENV_ACL }; +	int error = -ENOMEM; +	const char *data = tomoyo_read_token(param); + +	if (!tomoyo_correct_word(data) || strchr(data, '=')) +		return -EINVAL; +	e.env = tomoyo_get_name(data); +	if (!e.env) +		return error; +	error = tomoyo_update_domain(&e.head, sizeof(e), param, +				  tomoyo_same_env_acl, NULL); +	tomoyo_put_name(e.env); +	return error; +} + +/** + * tomoyo_write_misc - Update environment variable list. + * + * @param: Pointer to "struct tomoyo_acl_param". + * + * Returns 0 on success, negative value otherwise. + */ +int tomoyo_write_misc(struct tomoyo_acl_param *param) +{ +	if (tomoyo_str_starts(¶m->data, "env ")) +		return tomoyo_write_env(param); +	return -EINVAL; +} diff --git a/security/tomoyo/file.c b/security/tomoyo/file.c index 743c35f5084..40039079074 100644 --- a/security/tomoyo/file.c +++ b/security/tomoyo/file.c @@ -555,8 +555,8 @@ static int tomoyo_update_path2_acl(const u8 perm,   *   * Caller holds tomoyo_read_lock().   */ -int tomoyo_path_permission(struct tomoyo_request_info *r, u8 operation, -			   const struct tomoyo_path_info *filename) +static int tomoyo_path_permission(struct tomoyo_request_info *r, u8 operation, +				  const struct tomoyo_path_info *filename)  {  	int error; @@ -570,16 +570,42 @@ int tomoyo_path_permission(struct tomoyo_request_info *r, u8 operation,  	do {  		tomoyo_check_acl(r, tomoyo_check_path_acl);  		error = tomoyo_audit_path_log(r); -		/* -		 * Do not retry for execute request, for alias may have -		 * changed. -		 */ -	} while (error == TOMOYO_RETRY_REQUEST && -		 operation != TOMOYO_TYPE_EXECUTE); +	} while (error == TOMOYO_RETRY_REQUEST);  	return error;  }  /** + * tomoyo_execute_permission - Check permission for execute operation. + * + * @r:         Pointer to "struct tomoyo_request_info". + * @filename:  Filename to check. + * + * Returns 0 on success, negative value otherwise. + * + * Caller holds tomoyo_read_lock(). + */ +int tomoyo_execute_permission(struct tomoyo_request_info *r, +			      const struct tomoyo_path_info *filename) +{ +	/* +	 * Unlike other permission checks, this check is done regardless of +	 * profile mode settings in order to check for domain transition +	 * preference. +	 */ +	r->type = TOMOYO_MAC_FILE_EXECUTE; +	r->mode = tomoyo_get_mode(r->domain->ns, r->profile, r->type); +	r->param_type = TOMOYO_TYPE_PATH_ACL; +	r->param.path.filename = filename; +	r->param.path.operation = TOMOYO_TYPE_EXECUTE; +	tomoyo_check_acl(r, tomoyo_check_path_acl); +	r->ee->transition = r->matched_acl && r->matched_acl->cond ? +		r->matched_acl->cond->transit : NULL; +	if (r->mode != TOMOYO_CONFIG_DISABLED) +		return tomoyo_audit_path_log(r); +	return 0; +} + +/**   * tomoyo_same_path_number_acl - Check for duplicated "struct tomoyo_path_number_acl" entry.   *   * @a: Pointer to "struct tomoyo_acl_info". diff --git a/security/tomoyo/gc.c b/security/tomoyo/gc.c index ae135fbbbe9..986a6a75686 100644 --- a/security/tomoyo/gc.c +++ b/security/tomoyo/gc.c @@ -8,36 +8,26 @@  #include <linux/kthread.h>  #include <linux/slab.h> +/** + * tomoyo_memory_free - Free memory for elements. + * + * @ptr:  Pointer to allocated memory. + * + * Returns nothing. + * + * Caller holds tomoyo_policy_lock mutex. + */ +static inline void tomoyo_memory_free(void *ptr) +{ +	tomoyo_memory_used[TOMOYO_MEMORY_POLICY] -= ksize(ptr); +	kfree(ptr); +} +  /* The list for "struct tomoyo_io_buffer". */  static LIST_HEAD(tomoyo_io_buffer_list);  /* Lock for protecting tomoyo_io_buffer_list. */  static DEFINE_SPINLOCK(tomoyo_io_buffer_list_lock); -/* Size of an element. */ -static const u8 tomoyo_element_size[TOMOYO_MAX_POLICY] = { -	[TOMOYO_ID_GROUP] = sizeof(struct tomoyo_group), -	[TOMOYO_ID_PATH_GROUP] = sizeof(struct tomoyo_path_group), -	[TOMOYO_ID_NUMBER_GROUP] = sizeof(struct tomoyo_number_group), -	[TOMOYO_ID_AGGREGATOR] = sizeof(struct tomoyo_aggregator), -	[TOMOYO_ID_TRANSITION_CONTROL] = -	sizeof(struct tomoyo_transition_control), -	[TOMOYO_ID_MANAGER] = sizeof(struct tomoyo_manager), -	/* [TOMOYO_ID_CONDITION] = "struct tomoyo_condition"->size, */ -	/* [TOMOYO_ID_NAME] = "struct tomoyo_name"->size, */ -	/* [TOMOYO_ID_ACL] = -	   tomoyo_acl_size["struct tomoyo_acl_info"->type], */ -	[TOMOYO_ID_DOMAIN] = sizeof(struct tomoyo_domain_info), -}; - -/* Size of a domain ACL element. */ -static const u8 tomoyo_acl_size[] = { -	[TOMOYO_TYPE_PATH_ACL] = sizeof(struct tomoyo_path_acl), -	[TOMOYO_TYPE_PATH2_ACL] = sizeof(struct tomoyo_path2_acl), -	[TOMOYO_TYPE_PATH_NUMBER_ACL] = sizeof(struct tomoyo_path_number_acl), -	[TOMOYO_TYPE_MKDEV_ACL] = sizeof(struct tomoyo_mkdev_acl), -	[TOMOYO_TYPE_MOUNT_ACL] = sizeof(struct tomoyo_mount_acl), -}; -  /**   * tomoyo_struct_used_by_io_buffer - Check whether the list element is used by /sys/kernel/security/tomoyo/ users or not.   * @@ -55,15 +45,11 @@ static bool tomoyo_struct_used_by_io_buffer(const struct list_head *element)  	list_for_each_entry(head, &tomoyo_io_buffer_list, list) {  		head->users++;  		spin_unlock(&tomoyo_io_buffer_list_lock); -		if (mutex_lock_interruptible(&head->io_sem)) { -			in_use = true; -			goto out; -		} +		mutex_lock(&head->io_sem);  		if (head->r.domain == element || head->r.group == element ||  		    head->r.acl == element || &head->w.domain->list == element)  			in_use = true;  		mutex_unlock(&head->io_sem); -out:  		spin_lock(&tomoyo_io_buffer_list_lock);  		head->users--;  		if (in_use) @@ -77,15 +63,14 @@ out:   * tomoyo_name_used_by_io_buffer - Check whether the string is used by /sys/kernel/security/tomoyo/ users or not.   *   * @string: String to check. - * @size:   Memory allocated for @string .   *   * Returns true if @string is used by /sys/kernel/security/tomoyo/ users,   * false otherwise.   */ -static bool tomoyo_name_used_by_io_buffer(const char *string, -					  const size_t size) +static bool tomoyo_name_used_by_io_buffer(const char *string)  {  	struct tomoyo_io_buffer *head; +	const size_t size = strlen(string) + 1;  	bool in_use = false;  	spin_lock(&tomoyo_io_buffer_list_lock); @@ -93,10 +78,7 @@ static bool tomoyo_name_used_by_io_buffer(const char *string,  		int i;  		head->users++;  		spin_unlock(&tomoyo_io_buffer_list_lock); -		if (mutex_lock_interruptible(&head->io_sem)) { -			in_use = true; -			goto out; -		} +		mutex_lock(&head->io_sem);  		for (i = 0; i < TOMOYO_MAX_IO_READ_QUEUE; i++) {  			const char *w = head->r.w[i];  			if (w < string || w > string + size) @@ -105,7 +87,6 @@ static bool tomoyo_name_used_by_io_buffer(const char *string,  			break;  		}  		mutex_unlock(&head->io_sem); -out:  		spin_lock(&tomoyo_io_buffer_list_lock);  		head->users--;  		if (in_use) @@ -115,84 +96,6 @@ out:  	return in_use;  } -/* Structure for garbage collection. */ -struct tomoyo_gc { -	struct list_head list; -	enum tomoyo_policy_id type; -	size_t size; -	struct list_head *element; -}; -/* List of entries to be deleted. */ -static LIST_HEAD(tomoyo_gc_list); -/* Length of tomoyo_gc_list. */ -static int tomoyo_gc_list_len; - -/** - * tomoyo_add_to_gc - Add an entry to to be deleted list. - * - * @type:    One of values in "enum tomoyo_policy_id". - * @element: Pointer to "struct list_head". - * - * Returns true on success, false otherwise. - * - * Caller holds tomoyo_policy_lock mutex. - * - * Adding an entry needs kmalloc(). Thus, if we try to add thousands of - * entries at once, it will take too long time. Thus, do not add more than 128 - * entries per a scan. But to be able to handle worst case where all entries - * are in-use, we accept one more entry per a scan. - * - * If we use singly linked list using "struct list_head"->prev (which is - * LIST_POISON2), we can avoid kmalloc(). - */ -static bool tomoyo_add_to_gc(const int type, struct list_head *element) -{ -	struct tomoyo_gc *entry = kzalloc(sizeof(*entry), GFP_ATOMIC); -	if (!entry) -		return false; -	entry->type = type; -	if (type == TOMOYO_ID_ACL) -		entry->size = tomoyo_acl_size[ -			      container_of(element, -					   typeof(struct tomoyo_acl_info), -					   list)->type]; -	else if (type == TOMOYO_ID_NAME) -		entry->size = strlen(container_of(element, -						  typeof(struct tomoyo_name), -						  head.list)->entry.name) + 1; -	else if (type == TOMOYO_ID_CONDITION) -		entry->size = -			container_of(element, typeof(struct tomoyo_condition), -				     head.list)->size; -	else -		entry->size = tomoyo_element_size[type]; -	entry->element = element; -	list_add(&entry->list, &tomoyo_gc_list); -	list_del_rcu(element); -	return tomoyo_gc_list_len++ < 128; -} - -/** - * tomoyo_element_linked_by_gc - Validate next element of an entry. - * - * @element: Pointer to an element. - * @size:    Size of @element in byte. - * - * Returns true if @element is linked by other elements in the garbage - * collector's queue, false otherwise. - */ -static bool tomoyo_element_linked_by_gc(const u8 *element, const size_t size) -{ -	struct tomoyo_gc *p; -	list_for_each_entry(p, &tomoyo_gc_list, list) { -		const u8 *ptr = (const u8 *) p->element->next; -		if (ptr < element || element + size < ptr) -			continue; -		return true; -	} -	return false; -} -  /**   * tomoyo_del_transition_control - Delete members in "struct tomoyo_transition_control".   * @@ -200,7 +103,7 @@ static bool tomoyo_element_linked_by_gc(const u8 *element, const size_t size)   *   * Returns nothing.   */ -static void tomoyo_del_transition_control(struct list_head *element) +static inline void tomoyo_del_transition_control(struct list_head *element)  {  	struct tomoyo_transition_control *ptr =  		container_of(element, typeof(*ptr), head.list); @@ -215,7 +118,7 @@ static void tomoyo_del_transition_control(struct list_head *element)   *   * Returns nothing.   */ -static void tomoyo_del_aggregator(struct list_head *element) +static inline void tomoyo_del_aggregator(struct list_head *element)  {  	struct tomoyo_aggregator *ptr =  		container_of(element, typeof(*ptr), head.list); @@ -230,7 +133,7 @@ static void tomoyo_del_aggregator(struct list_head *element)   *   * Returns nothing.   */ -static void tomoyo_del_manager(struct list_head *element) +static inline void tomoyo_del_manager(struct list_head *element)  {  	struct tomoyo_manager *ptr =  		container_of(element, typeof(*ptr), head.list); @@ -293,6 +196,38 @@ static void tomoyo_del_acl(struct list_head *element)  			tomoyo_put_number_union(&entry->flags);  		}  		break; +	case TOMOYO_TYPE_ENV_ACL: +		{ +			struct tomoyo_env_acl *entry = +				container_of(acl, typeof(*entry), head); + +			tomoyo_put_name(entry->env); +		} +		break; +	case TOMOYO_TYPE_INET_ACL: +		{ +			struct tomoyo_inet_acl *entry = +				container_of(acl, typeof(*entry), head); + +			tomoyo_put_group(entry->address.group); +			tomoyo_put_number_union(&entry->port); +		} +		break; +	case TOMOYO_TYPE_UNIX_ACL: +		{ +			struct tomoyo_unix_acl *entry = +				container_of(acl, typeof(*entry), head); + +			tomoyo_put_name_union(&entry->name); +		} +		break; +	case TOMOYO_TYPE_MANUAL_TASK_ACL: +		{ +			struct tomoyo_task_acl *entry = +				container_of(acl, typeof(*entry), head); +			tomoyo_put_name(entry->domainname); +		} +		break;  	}  } @@ -301,44 +236,26 @@ static void tomoyo_del_acl(struct list_head *element)   *   * @element: Pointer to "struct list_head".   * - * Returns true if deleted, false otherwise. + * Returns nothing. + * + * Caller holds tomoyo_policy_lock mutex.   */ -static bool tomoyo_del_domain(struct list_head *element) +static inline void tomoyo_del_domain(struct list_head *element)  {  	struct tomoyo_domain_info *domain =  		container_of(element, typeof(*domain), list);  	struct tomoyo_acl_info *acl;  	struct tomoyo_acl_info *tmp;  	/* -	 * Since we don't protect whole execve() operation using SRCU, -	 * we need to recheck domain->users at this point. -	 * -	 * (1) Reader starts SRCU section upon execve(). -	 * (2) Reader traverses tomoyo_domain_list and finds this domain. -	 * (3) Writer marks this domain as deleted. -	 * (4) Garbage collector removes this domain from tomoyo_domain_list -	 *     because this domain is marked as deleted and used by nobody. -	 * (5) Reader saves reference to this domain into -	 *     "struct linux_binprm"->cred->security . -	 * (6) Reader finishes SRCU section, although execve() operation has -	 *     not finished yet. -	 * (7) Garbage collector waits for SRCU synchronization. -	 * (8) Garbage collector kfree() this domain because this domain is -	 *     used by nobody. -	 * (9) Reader finishes execve() operation and restores this domain from -	 *     "struct linux_binprm"->cred->security. -	 * -	 * By updating domain->users at (5), we can solve this race problem -	 * by rechecking domain->users at (8). +	 * Since this domain is referenced from neither +	 * "struct tomoyo_io_buffer" nor "struct cred"->security, we can delete +	 * elements without checking for is_deleted flag.  	 */ -	if (atomic_read(&domain->users)) -		return false;  	list_for_each_entry_safe(acl, tmp, &domain->acl_info_list, list) {  		tomoyo_del_acl(&acl->list);  		tomoyo_memory_free(acl);  	}  	tomoyo_put_name(domain->domainname); -	return true;  }  /** @@ -387,10 +304,9 @@ void tomoyo_del_condition(struct list_head *element)   *   * Returns nothing.   */ -static void tomoyo_del_name(struct list_head *element) +static inline void tomoyo_del_name(struct list_head *element)  { -	const struct tomoyo_name *ptr = -		container_of(element, typeof(*ptr), head.list); +	/* Nothing to do. */  }  /** @@ -400,7 +316,7 @@ static void tomoyo_del_name(struct list_head *element)   *   * Returns nothing.   */ -static void tomoyo_del_path_group(struct list_head *element) +static inline void tomoyo_del_path_group(struct list_head *element)  {  	struct tomoyo_path_group *member =  		container_of(element, typeof(*member), head.list); @@ -414,7 +330,7 @@ static void tomoyo_del_path_group(struct list_head *element)   *   * Returns nothing.   */ -static void tomoyo_del_group(struct list_head *element) +static inline void tomoyo_del_group(struct list_head *element)  {  	struct tomoyo_group *group =  		container_of(element, typeof(*group), head.list); @@ -422,16 +338,128 @@ static void tomoyo_del_group(struct list_head *element)  }  /** + * tomoyo_del_address_group - Delete members in "struct tomoyo_address_group". + * + * @element: Pointer to "struct list_head". + * + * Returns nothing. + */ +static inline void tomoyo_del_address_group(struct list_head *element) +{ +	/* Nothing to do. */ +} + +/**   * tomoyo_del_number_group - Delete members in "struct tomoyo_number_group".   *   * @element: Pointer to "struct list_head".   *   * Returns nothing.   */ -static void tomoyo_del_number_group(struct list_head *element) +static inline void tomoyo_del_number_group(struct list_head *element)  { -	struct tomoyo_number_group *member = -		container_of(element, typeof(*member), head.list); +	/* Nothing to do. */ +} + +/** + * tomoyo_try_to_gc - Try to kfree() an entry. + * + * @type:    One of values in "enum tomoyo_policy_id". + * @element: Pointer to "struct list_head". + * + * Returns nothing. + * + * Caller holds tomoyo_policy_lock mutex. + */ +static void tomoyo_try_to_gc(const enum tomoyo_policy_id type, +			     struct list_head *element) +{ +	/* +	 * __list_del_entry() guarantees that the list element became no longer +	 * reachable from the list which the element was originally on (e.g. +	 * tomoyo_domain_list). Also, synchronize_srcu() guarantees that the +	 * list element became no longer referenced by syscall users. +	 */ +	__list_del_entry(element); +	mutex_unlock(&tomoyo_policy_lock); +	synchronize_srcu(&tomoyo_ss); +	/* +	 * However, there are two users which may still be using the list +	 * element. We need to defer until both users forget this element. +	 * +	 * Don't kfree() until "struct tomoyo_io_buffer"->r.{domain,group,acl} +	 * and "struct tomoyo_io_buffer"->w.domain forget this element. +	 */ +	if (tomoyo_struct_used_by_io_buffer(element)) +		goto reinject; +	switch (type) { +	case TOMOYO_ID_TRANSITION_CONTROL: +		tomoyo_del_transition_control(element); +		break; +	case TOMOYO_ID_MANAGER: +		tomoyo_del_manager(element); +		break; +	case TOMOYO_ID_AGGREGATOR: +		tomoyo_del_aggregator(element); +		break; +	case TOMOYO_ID_GROUP: +		tomoyo_del_group(element); +		break; +	case TOMOYO_ID_PATH_GROUP: +		tomoyo_del_path_group(element); +		break; +	case TOMOYO_ID_ADDRESS_GROUP: +		tomoyo_del_address_group(element); +		break; +	case TOMOYO_ID_NUMBER_GROUP: +		tomoyo_del_number_group(element); +		break; +	case TOMOYO_ID_CONDITION: +		tomoyo_del_condition(element); +		break; +	case TOMOYO_ID_NAME: +		/* +		 * Don't kfree() until all "struct tomoyo_io_buffer"->r.w[] +		 * forget this element. +		 */ +		if (tomoyo_name_used_by_io_buffer +		    (container_of(element, typeof(struct tomoyo_name), +				  head.list)->entry.name)) +			goto reinject; +		tomoyo_del_name(element); +		break; +	case TOMOYO_ID_ACL: +		tomoyo_del_acl(element); +		break; +	case TOMOYO_ID_DOMAIN: +		/* +		 * Don't kfree() until all "struct cred"->security forget this +		 * element. +		 */ +		if (atomic_read(&container_of +				(element, typeof(struct tomoyo_domain_info), +				 list)->users)) +			goto reinject; +		break; +	case TOMOYO_MAX_POLICY: +		break; +	} +	mutex_lock(&tomoyo_policy_lock); +	if (type == TOMOYO_ID_DOMAIN) +		tomoyo_del_domain(element); +	tomoyo_memory_free(element); +	return; +reinject: +	/* +	 * We can safely reinject this element here bacause +	 * (1) Appending list elements and removing list elements are protected +	 *     by tomoyo_policy_lock mutex. +	 * (2) Only this function removes list elements and this function is +	 *     exclusively executed by tomoyo_gc_mutex mutex. +	 * are true. +	 */ +	mutex_lock(&tomoyo_policy_lock); +	list_add_rcu(element, element->prev);  }  /** @@ -440,19 +468,19 @@ static void tomoyo_del_number_group(struct list_head *element)   * @id:          One of values in "enum tomoyo_policy_id".   * @member_list: Pointer to "struct list_head".   * - * Returns true if some elements are deleted, false otherwise. + * Returns nothing.   */ -static bool tomoyo_collect_member(const enum tomoyo_policy_id id, +static void tomoyo_collect_member(const enum tomoyo_policy_id id,  				  struct list_head *member_list)  {  	struct tomoyo_acl_head *member; -	list_for_each_entry(member, member_list, list) { +	struct tomoyo_acl_head *tmp; +	list_for_each_entry_safe(member, tmp, member_list, list) {  		if (!member->is_deleted)  			continue; -		if (!tomoyo_add_to_gc(id, &member->list)) -			return false; +		member->is_deleted = TOMOYO_GC_IN_PROGRESS; +		tomoyo_try_to_gc(id, &member->list);  	} -	return true;  }  /** @@ -460,22 +488,22 @@ static bool tomoyo_collect_member(const enum tomoyo_policy_id id,   *   * @list: Pointer to "struct list_head".   * - * Returns true if some elements are deleted, false otherwise. + * Returns nothing.   */ -static bool tomoyo_collect_acl(struct list_head *list) +static void tomoyo_collect_acl(struct list_head *list)  {  	struct tomoyo_acl_info *acl; -	list_for_each_entry(acl, list, list) { +	struct tomoyo_acl_info *tmp; +	list_for_each_entry_safe(acl, tmp, list, list) {  		if (!acl->is_deleted)  			continue; -		if (!tomoyo_add_to_gc(TOMOYO_ID_ACL, &acl->list)) -			return false; +		acl->is_deleted = TOMOYO_GC_IN_PROGRESS; +		tomoyo_try_to_gc(TOMOYO_ID_ACL, &acl->list);  	} -	return true;  }  /** - * tomoyo_collect_entry - Scan lists for deleted elements. + * tomoyo_collect_entry - Try to kfree() deleted elements.   *   * Returns nothing.   */ @@ -484,174 +512,82 @@ static void tomoyo_collect_entry(void)  	int i;  	enum tomoyo_policy_id id;  	struct tomoyo_policy_namespace *ns; -	int idx; -	if (mutex_lock_interruptible(&tomoyo_policy_lock)) -		return; -	idx = tomoyo_read_lock(); +	mutex_lock(&tomoyo_policy_lock);  	{  		struct tomoyo_domain_info *domain; -		list_for_each_entry_rcu(domain, &tomoyo_domain_list, list) { -			if (!tomoyo_collect_acl(&domain->acl_info_list)) -				goto unlock; +		struct tomoyo_domain_info *tmp; +		list_for_each_entry_safe(domain, tmp, &tomoyo_domain_list, +					 list) { +			tomoyo_collect_acl(&domain->acl_info_list);  			if (!domain->is_deleted || atomic_read(&domain->users))  				continue; -			/* -			 * Nobody is referring this domain. But somebody may -			 * refer this domain after successful execve(). -			 * We recheck domain->users after SRCU synchronization. -			 */ -			if (!tomoyo_add_to_gc(TOMOYO_ID_DOMAIN, &domain->list)) -				goto unlock; +			tomoyo_try_to_gc(TOMOYO_ID_DOMAIN, &domain->list);  		}  	} -	list_for_each_entry_rcu(ns, &tomoyo_namespace_list, namespace_list) { +	list_for_each_entry(ns, &tomoyo_namespace_list, namespace_list) {  		for (id = 0; id < TOMOYO_MAX_POLICY; id++) -			if (!tomoyo_collect_member(id, &ns->policy_list[id])) -				goto unlock; +			tomoyo_collect_member(id, &ns->policy_list[id]);  		for (i = 0; i < TOMOYO_MAX_ACL_GROUPS; i++) -			if (!tomoyo_collect_acl(&ns->acl_group[i])) -				goto unlock; +			tomoyo_collect_acl(&ns->acl_group[i]); +	} +	{ +		struct tomoyo_shared_acl_head *ptr; +		struct tomoyo_shared_acl_head *tmp; +		list_for_each_entry_safe(ptr, tmp, &tomoyo_condition_list, +					 list) { +			if (atomic_read(&ptr->users) > 0) +				continue; +			atomic_set(&ptr->users, TOMOYO_GC_IN_PROGRESS); +			tomoyo_try_to_gc(TOMOYO_ID_CONDITION, &ptr->list); +		} +	} +	list_for_each_entry(ns, &tomoyo_namespace_list, namespace_list) {  		for (i = 0; i < TOMOYO_MAX_GROUP; i++) {  			struct list_head *list = &ns->group_list[i];  			struct tomoyo_group *group; +			struct tomoyo_group *tmp;  			switch (i) {  			case 0:  				id = TOMOYO_ID_PATH_GROUP;  				break; -			default: +			case 1:  				id = TOMOYO_ID_NUMBER_GROUP;  				break; +			default: +				id = TOMOYO_ID_ADDRESS_GROUP; +				break;  			} -			list_for_each_entry(group, list, head.list) { -				if (!tomoyo_collect_member -				    (id, &group->member_list)) -					goto unlock; +			list_for_each_entry_safe(group, tmp, list, head.list) { +				tomoyo_collect_member(id, &group->member_list);  				if (!list_empty(&group->member_list) || -				    atomic_read(&group->head.users)) +				    atomic_read(&group->head.users) > 0)  					continue; -				if (!tomoyo_add_to_gc(TOMOYO_ID_GROUP, -						      &group->head.list)) -					goto unlock; +				atomic_set(&group->head.users, +					   TOMOYO_GC_IN_PROGRESS); +				tomoyo_try_to_gc(TOMOYO_ID_GROUP, +						 &group->head.list);  			}  		}  	} -	id = TOMOYO_ID_CONDITION; -	for (i = 0; i < TOMOYO_MAX_HASH + 1; i++) { -		struct list_head *list = !i ? -			&tomoyo_condition_list : &tomoyo_name_list[i - 1]; +	for (i = 0; i < TOMOYO_MAX_HASH; i++) { +		struct list_head *list = &tomoyo_name_list[i];  		struct tomoyo_shared_acl_head *ptr; -		list_for_each_entry(ptr, list, list) { -			if (atomic_read(&ptr->users)) +		struct tomoyo_shared_acl_head *tmp; +		list_for_each_entry_safe(ptr, tmp, list, list) { +			if (atomic_read(&ptr->users) > 0)  				continue; -			if (!tomoyo_add_to_gc(id, &ptr->list)) -				goto unlock; +			atomic_set(&ptr->users, TOMOYO_GC_IN_PROGRESS); +			tomoyo_try_to_gc(TOMOYO_ID_NAME, &ptr->list);  		} -		id = TOMOYO_ID_NAME;  	} -unlock: -	tomoyo_read_unlock(idx);  	mutex_unlock(&tomoyo_policy_lock);  }  /** - * tomoyo_kfree_entry - Delete entries in tomoyo_gc_list. - * - * Returns true if some entries were kfree()d, false otherwise. - */ -static bool tomoyo_kfree_entry(void) -{ -	struct tomoyo_gc *p; -	struct tomoyo_gc *tmp; -	bool result = false; - -	list_for_each_entry_safe(p, tmp, &tomoyo_gc_list, list) { -		struct list_head *element = p->element; - -		/* -		 * list_del_rcu() in tomoyo_add_to_gc() guarantees that the -		 * list element became no longer reachable from the list which -		 * the element was originally on (e.g. tomoyo_domain_list). -		 * Also, synchronize_srcu() in tomoyo_gc_thread() guarantees -		 * that the list element became no longer referenced by syscall -		 * users. -		 * -		 * However, there are three users which may still be using the -		 * list element. We need to defer until all of these users -		 * forget the list element. -		 * -		 * Firstly, defer until "struct tomoyo_io_buffer"->r.{domain, -		 * group,acl} and "struct tomoyo_io_buffer"->w.domain forget -		 * the list element. -		 */ -		if (tomoyo_struct_used_by_io_buffer(element)) -			continue; -		/* -		 * Secondly, defer until all other elements in the -		 * tomoyo_gc_list list forget the list element. -		 */ -		if (tomoyo_element_linked_by_gc((const u8 *) element, p->size)) -			continue; -		switch (p->type) { -		case TOMOYO_ID_TRANSITION_CONTROL: -			tomoyo_del_transition_control(element); -			break; -		case TOMOYO_ID_AGGREGATOR: -			tomoyo_del_aggregator(element); -			break; -		case TOMOYO_ID_MANAGER: -			tomoyo_del_manager(element); -			break; -		case TOMOYO_ID_CONDITION: -			tomoyo_del_condition(element); -			break; -		case TOMOYO_ID_NAME: -			/* -			 * Thirdly, defer until all "struct tomoyo_io_buffer" -			 * ->r.w[] forget the list element. -			 */ -			if (tomoyo_name_used_by_io_buffer( -			    container_of(element, typeof(struct tomoyo_name), -					 head.list)->entry.name, p->size)) -				continue; -			tomoyo_del_name(element); -			break; -		case TOMOYO_ID_ACL: -			tomoyo_del_acl(element); -			break; -		case TOMOYO_ID_DOMAIN: -			if (!tomoyo_del_domain(element)) -				continue; -			break; -		case TOMOYO_ID_PATH_GROUP: -			tomoyo_del_path_group(element); -			break; -		case TOMOYO_ID_GROUP: -			tomoyo_del_group(element); -			break; -		case TOMOYO_ID_NUMBER_GROUP: -			tomoyo_del_number_group(element); -			break; -		case TOMOYO_MAX_POLICY: -			break; -		} -		tomoyo_memory_free(element); -		list_del(&p->list); -		kfree(p); -		tomoyo_gc_list_len--; -		result = true; -	} -	return result; -} - -/**   * tomoyo_gc_thread - Garbage collector thread function.   *   * @unused: Unused.   * - * In case OOM-killer choose this thread for termination, we create this thread - * as a short live thread whenever /sys/kernel/security/tomoyo/ interface was - * close()d. - *   * Returns 0.   */  static int tomoyo_gc_thread(void *unused) @@ -660,13 +596,7 @@ static int tomoyo_gc_thread(void *unused)  	static DEFINE_MUTEX(tomoyo_gc_mutex);  	if (!mutex_trylock(&tomoyo_gc_mutex))  		goto out; -	daemonize("GC for TOMOYO"); -	do { -		tomoyo_collect_entry(); -		if (list_empty(&tomoyo_gc_list)) -			break; -		synchronize_srcu(&tomoyo_ss); -	} while (tomoyo_kfree_entry()); +	tomoyo_collect_entry();  	{  		struct tomoyo_io_buffer *head;  		struct tomoyo_io_buffer *tmp; diff --git a/security/tomoyo/group.c b/security/tomoyo/group.c index 5fb0e129840..50092534ec5 100644 --- a/security/tomoyo/group.c +++ b/security/tomoyo/group.c @@ -42,7 +42,26 @@ static bool tomoyo_same_number_group(const struct tomoyo_acl_head *a,  }  /** - * tomoyo_write_group - Write "struct tomoyo_path_group"/"struct tomoyo_number_group" list. + * tomoyo_same_address_group - Check for duplicated "struct tomoyo_address_group" entry. + * + * @a: Pointer to "struct tomoyo_acl_head". + * @b: Pointer to "struct tomoyo_acl_head". + * + * Returns true if @a == @b, false otherwise. + */ +static bool tomoyo_same_address_group(const struct tomoyo_acl_head *a, +				      const struct tomoyo_acl_head *b) +{ +	const struct tomoyo_address_group *p1 = container_of(a, typeof(*p1), +							     head); +	const struct tomoyo_address_group *p2 = container_of(b, typeof(*p2), +							     head); + +	return tomoyo_same_ipaddr_union(&p1->address, &p2->address); +} + +/** + * tomoyo_write_group - Write "struct tomoyo_path_group"/"struct tomoyo_number_group"/"struct tomoyo_address_group" list.   *   * @param: Pointer to "struct tomoyo_acl_param".   * @type:  Type of this group. @@ -77,6 +96,14 @@ int tomoyo_write_group(struct tomoyo_acl_param *param, const u8 type)  		 * tomoyo_put_number_union() is not needed because  		 * param->data[0] != '@'.  		 */ +	} else { +		struct tomoyo_address_group e = { }; + +		if (param->data[0] == '@' || +		    !tomoyo_parse_ipaddr_union(param, &e.address)) +			goto out; +		error = tomoyo_update_policy(&e.head, sizeof(e), param, +					     tomoyo_same_address_group);  	}  out:  	tomoyo_put_group(group); @@ -137,3 +164,35 @@ bool tomoyo_number_matches_group(const unsigned long min,  	}  	return matched;  } + +/** + * tomoyo_address_matches_group - Check whether the given address matches members of the given address group. + * + * @is_ipv6: True if @address is an IPv6 address. + * @address: An IPv4 or IPv6 address. + * @group:   Pointer to "struct tomoyo_address_group". + * + * Returns true if @address matches addresses in @group group, false otherwise. + * + * Caller holds tomoyo_read_lock(). + */ +bool tomoyo_address_matches_group(const bool is_ipv6, const __be32 *address, +				  const struct tomoyo_group *group) +{ +	struct tomoyo_address_group *member; +	bool matched = false; +	const u8 size = is_ipv6 ? 16 : 4; + +	list_for_each_entry_rcu(member, &group->member_list, head.list) { +		if (member->head.is_deleted) +			continue; +		if (member->address.is_ipv6 != is_ipv6) +			continue; +		if (memcmp(&member->address.ip[0], address, size) > 0 || +		    memcmp(address, &member->address.ip[1], size) > 0) +			continue; +		matched = true; +		break; +	} +	return matched; +} diff --git a/security/tomoyo/memory.c b/security/tomoyo/memory.c index 7a56051146c..0e995716cc2 100644 --- a/security/tomoyo/memory.c +++ b/security/tomoyo/memory.c @@ -27,8 +27,6 @@ void tomoyo_warn_oom(const char *function)  		panic("MAC Initialization failed.\n");  } -/* Lock for protecting tomoyo_memory_used. */ -static DEFINE_SPINLOCK(tomoyo_policy_memory_lock);  /* Memoy currently used by policy/audit log/query. */  unsigned int tomoyo_memory_used[TOMOYO_MAX_MEMORY_STAT];  /* Memory quota for "policy"/"audit log"/"query". */ @@ -42,22 +40,19 @@ unsigned int tomoyo_memory_quota[TOMOYO_MAX_MEMORY_STAT];   * Returns true on success, false otherwise.   *   * Returns true if @ptr is not NULL and quota not exceeded, false otherwise. + * + * Caller holds tomoyo_policy_lock mutex.   */  bool tomoyo_memory_ok(void *ptr)  {  	if (ptr) {  		const size_t s = ksize(ptr); -		bool result; -		spin_lock(&tomoyo_policy_memory_lock);  		tomoyo_memory_used[TOMOYO_MEMORY_POLICY] += s; -		result = !tomoyo_memory_quota[TOMOYO_MEMORY_POLICY] || -			tomoyo_memory_used[TOMOYO_MEMORY_POLICY] <= -			tomoyo_memory_quota[TOMOYO_MEMORY_POLICY]; -		if (!result) -			tomoyo_memory_used[TOMOYO_MEMORY_POLICY] -= s; -		spin_unlock(&tomoyo_policy_memory_lock); -		if (result) +		if (!tomoyo_memory_quota[TOMOYO_MEMORY_POLICY] || +		    tomoyo_memory_used[TOMOYO_MEMORY_POLICY] <= +		    tomoyo_memory_quota[TOMOYO_MEMORY_POLICY])  			return true; +		tomoyo_memory_used[TOMOYO_MEMORY_POLICY] -= s;  	}  	tomoyo_warn_oom(__func__);  	return false; @@ -71,6 +66,8 @@ bool tomoyo_memory_ok(void *ptr)   *   * Returns pointer to allocated memory on success, NULL otherwise.   * @data is zero-cleared on success. + * + * Caller holds tomoyo_policy_lock mutex.   */  void *tomoyo_commit_ok(void *data, const unsigned int size)  { @@ -85,20 +82,6 @@ void *tomoyo_commit_ok(void *data, const unsigned int size)  }  /** - * tomoyo_memory_free - Free memory for elements. - * - * @ptr:  Pointer to allocated memory. - */ -void tomoyo_memory_free(void *ptr) -{ -	size_t s = ksize(ptr); -	spin_lock(&tomoyo_policy_memory_lock); -	tomoyo_memory_used[TOMOYO_MEMORY_POLICY] -= s; -	spin_unlock(&tomoyo_policy_memory_lock); -	kfree(ptr); -} - -/**   * tomoyo_get_group - Allocate memory for "struct tomoyo_path_group"/"struct tomoyo_number_group".   *   * @param: Pointer to "struct tomoyo_acl_param". @@ -123,7 +106,8 @@ struct tomoyo_group *tomoyo_get_group(struct tomoyo_acl_param *param,  		goto out;  	list = ¶m->ns->group_list[idx];  	list_for_each_entry(group, list, head.list) { -		if (e.group_name != group->group_name) +		if (e.group_name != group->group_name || +		    atomic_read(&group->head.users) == TOMOYO_GC_IN_PROGRESS)  			continue;  		atomic_inc(&group->head.users);  		found = true; @@ -175,7 +159,8 @@ const struct tomoyo_path_info *tomoyo_get_name(const char *name)  	if (mutex_lock_interruptible(&tomoyo_policy_lock))  		return NULL;  	list_for_each_entry(ptr, head, head.list) { -		if (hash != ptr->entry.hash || strcmp(name, ptr->entry.name)) +		if (hash != ptr->entry.hash || strcmp(name, ptr->entry.name) || +		    atomic_read(&ptr->head.users) == TOMOYO_GC_IN_PROGRESS)  			continue;  		atomic_inc(&ptr->head.users);  		goto out; diff --git a/security/tomoyo/network.c b/security/tomoyo/network.c new file mode 100644 index 00000000000..97527710a72 --- /dev/null +++ b/security/tomoyo/network.c @@ -0,0 +1,771 @@ +/* + * security/tomoyo/network.c + * + * Copyright (C) 2005-2011  NTT DATA CORPORATION + */ + +#include "common.h" +#include <linux/slab.h> + +/* Structure for holding inet domain socket's address. */ +struct tomoyo_inet_addr_info { +	__be16 port;           /* In network byte order. */ +	const __be32 *address; /* In network byte order. */ +	bool is_ipv6; +}; + +/* Structure for holding unix domain socket's address. */ +struct tomoyo_unix_addr_info { +	u8 *addr; /* This may not be '\0' terminated string. */ +	unsigned int addr_len; +}; + +/* Structure for holding socket address. */ +struct tomoyo_addr_info { +	u8 protocol; +	u8 operation; +	struct tomoyo_inet_addr_info inet; +	struct tomoyo_unix_addr_info unix0; +}; + +/* String table for socket's protocols. */ +const char * const tomoyo_proto_keyword[TOMOYO_SOCK_MAX] = { +	[SOCK_STREAM]    = "stream", +	[SOCK_DGRAM]     = "dgram", +	[SOCK_RAW]       = "raw", +	[SOCK_SEQPACKET] = "seqpacket", +	[0] = " ", /* Dummy for avoiding NULL pointer dereference. */ +	[4] = " ", /* Dummy for avoiding NULL pointer dereference. */ +}; + +/** + * tomoyo_parse_ipaddr_union - Parse an IP address. + * + * @param: Pointer to "struct tomoyo_acl_param". + * @ptr:   Pointer to "struct tomoyo_ipaddr_union". + * + * Returns true on success, false otherwise. + */ +bool tomoyo_parse_ipaddr_union(struct tomoyo_acl_param *param, +			       struct tomoyo_ipaddr_union *ptr) +{ +	u8 * const min = ptr->ip[0].in6_u.u6_addr8; +	u8 * const max = ptr->ip[1].in6_u.u6_addr8; +	char *address = tomoyo_read_token(param); +	const char *end; + +	if (!strchr(address, ':') && +	    in4_pton(address, -1, min, '-', &end) > 0) { +		ptr->is_ipv6 = false; +		if (!*end) +			ptr->ip[1].s6_addr32[0] = ptr->ip[0].s6_addr32[0]; +		else if (*end++ != '-' || +			 in4_pton(end, -1, max, '\0', &end) <= 0 || *end) +			return false; +		return true; +	} +	if (in6_pton(address, -1, min, '-', &end) > 0) { +		ptr->is_ipv6 = true; +		if (!*end) +			memmove(max, min, sizeof(u16) * 8); +		else if (*end++ != '-' || +			 in6_pton(end, -1, max, '\0', &end) <= 0 || *end) +			return false; +		return true; +	} +	return false; +} + +/** + * tomoyo_print_ipv4 - Print an IPv4 address. + * + * @buffer:     Buffer to write to. + * @buffer_len: Size of @buffer. + * @min_ip:     Pointer to __be32. + * @max_ip:     Pointer to __be32. + * + * Returns nothing. + */ +static void tomoyo_print_ipv4(char *buffer, const unsigned int buffer_len, +			      const __be32 *min_ip, const __be32 *max_ip) +{ +	snprintf(buffer, buffer_len, "%pI4%c%pI4", min_ip, +		 *min_ip == *max_ip ? '\0' : '-', max_ip); +} + +/** + * tomoyo_print_ipv6 - Print an IPv6 address. + * + * @buffer:     Buffer to write to. + * @buffer_len: Size of @buffer. + * @min_ip:     Pointer to "struct in6_addr". + * @max_ip:     Pointer to "struct in6_addr". + * + * Returns nothing. + */ +static void tomoyo_print_ipv6(char *buffer, const unsigned int buffer_len, +			      const struct in6_addr *min_ip, +			      const struct in6_addr *max_ip) +{ +	snprintf(buffer, buffer_len, "%pI6c%c%pI6c", min_ip, +		 !memcmp(min_ip, max_ip, 16) ? '\0' : '-', max_ip); +} + +/** + * tomoyo_print_ip - Print an IP address. + * + * @buf:  Buffer to write to. + * @size: Size of @buf. + * @ptr:  Pointer to "struct ipaddr_union". + * + * Returns nothing. + */ +void tomoyo_print_ip(char *buf, const unsigned int size, +		     const struct tomoyo_ipaddr_union *ptr) +{ +	if (ptr->is_ipv6) +		tomoyo_print_ipv6(buf, size, &ptr->ip[0], &ptr->ip[1]); +	else +		tomoyo_print_ipv4(buf, size, &ptr->ip[0].s6_addr32[0], +				  &ptr->ip[1].s6_addr32[0]); +} + +/* + * Mapping table from "enum tomoyo_network_acl_index" to + * "enum tomoyo_mac_index" for inet domain socket. + */ +static const u8 tomoyo_inet2mac +[TOMOYO_SOCK_MAX][TOMOYO_MAX_NETWORK_OPERATION] = { +	[SOCK_STREAM] = { +		[TOMOYO_NETWORK_BIND]    = TOMOYO_MAC_NETWORK_INET_STREAM_BIND, +		[TOMOYO_NETWORK_LISTEN]  = +		TOMOYO_MAC_NETWORK_INET_STREAM_LISTEN, +		[TOMOYO_NETWORK_CONNECT] = +		TOMOYO_MAC_NETWORK_INET_STREAM_CONNECT, +	}, +	[SOCK_DGRAM] = { +		[TOMOYO_NETWORK_BIND]    = TOMOYO_MAC_NETWORK_INET_DGRAM_BIND, +		[TOMOYO_NETWORK_SEND]    = TOMOYO_MAC_NETWORK_INET_DGRAM_SEND, +	}, +	[SOCK_RAW]    = { +		[TOMOYO_NETWORK_BIND]    = TOMOYO_MAC_NETWORK_INET_RAW_BIND, +		[TOMOYO_NETWORK_SEND]    = TOMOYO_MAC_NETWORK_INET_RAW_SEND, +	}, +}; + +/* + * Mapping table from "enum tomoyo_network_acl_index" to + * "enum tomoyo_mac_index" for unix domain socket. + */ +static const u8 tomoyo_unix2mac +[TOMOYO_SOCK_MAX][TOMOYO_MAX_NETWORK_OPERATION] = { +	[SOCK_STREAM] = { +		[TOMOYO_NETWORK_BIND]    = TOMOYO_MAC_NETWORK_UNIX_STREAM_BIND, +		[TOMOYO_NETWORK_LISTEN]  = +		TOMOYO_MAC_NETWORK_UNIX_STREAM_LISTEN, +		[TOMOYO_NETWORK_CONNECT] = +		TOMOYO_MAC_NETWORK_UNIX_STREAM_CONNECT, +	}, +	[SOCK_DGRAM] = { +		[TOMOYO_NETWORK_BIND]    = TOMOYO_MAC_NETWORK_UNIX_DGRAM_BIND, +		[TOMOYO_NETWORK_SEND]    = TOMOYO_MAC_NETWORK_UNIX_DGRAM_SEND, +	}, +	[SOCK_SEQPACKET] = { +		[TOMOYO_NETWORK_BIND]    = +		TOMOYO_MAC_NETWORK_UNIX_SEQPACKET_BIND, +		[TOMOYO_NETWORK_LISTEN]  = +		TOMOYO_MAC_NETWORK_UNIX_SEQPACKET_LISTEN, +		[TOMOYO_NETWORK_CONNECT] = +		TOMOYO_MAC_NETWORK_UNIX_SEQPACKET_CONNECT, +	}, +}; + +/** + * tomoyo_same_inet_acl - Check for duplicated "struct tomoyo_inet_acl" entry. + * + * @a: Pointer to "struct tomoyo_acl_info". + * @b: Pointer to "struct tomoyo_acl_info". + * + * Returns true if @a == @b except permission bits, false otherwise. + */ +static bool tomoyo_same_inet_acl(const struct tomoyo_acl_info *a, +				 const struct tomoyo_acl_info *b) +{ +	const struct tomoyo_inet_acl *p1 = container_of(a, typeof(*p1), head); +	const struct tomoyo_inet_acl *p2 = container_of(b, typeof(*p2), head); + +	return p1->protocol == p2->protocol && +		tomoyo_same_ipaddr_union(&p1->address, &p2->address) && +		tomoyo_same_number_union(&p1->port, &p2->port); +} + +/** + * tomoyo_same_unix_acl - Check for duplicated "struct tomoyo_unix_acl" entry. + * + * @a: Pointer to "struct tomoyo_acl_info". + * @b: Pointer to "struct tomoyo_acl_info". + * + * Returns true if @a == @b except permission bits, false otherwise. + */ +static bool tomoyo_same_unix_acl(const struct tomoyo_acl_info *a, +				 const struct tomoyo_acl_info *b) +{ +	const struct tomoyo_unix_acl *p1 = container_of(a, typeof(*p1), head); +	const struct tomoyo_unix_acl *p2 = container_of(b, typeof(*p2), head); + +	return p1->protocol == p2->protocol && +		tomoyo_same_name_union(&p1->name, &p2->name); +} + +/** + * tomoyo_merge_inet_acl - Merge duplicated "struct tomoyo_inet_acl" entry. + * + * @a:         Pointer to "struct tomoyo_acl_info". + * @b:         Pointer to "struct tomoyo_acl_info". + * @is_delete: True for @a &= ~@b, false for @a |= @b. + * + * Returns true if @a is empty, false otherwise. + */ +static bool tomoyo_merge_inet_acl(struct tomoyo_acl_info *a, +				  struct tomoyo_acl_info *b, +				  const bool is_delete) +{ +	u8 * const a_perm = +		&container_of(a, struct tomoyo_inet_acl, head)->perm; +	u8 perm = *a_perm; +	const u8 b_perm = container_of(b, struct tomoyo_inet_acl, head)->perm; + +	if (is_delete) +		perm &= ~b_perm; +	else +		perm |= b_perm; +	*a_perm = perm; +	return !perm; +} + +/** + * tomoyo_merge_unix_acl - Merge duplicated "struct tomoyo_unix_acl" entry. + * + * @a:         Pointer to "struct tomoyo_acl_info". + * @b:         Pointer to "struct tomoyo_acl_info". + * @is_delete: True for @a &= ~@b, false for @a |= @b. + * + * Returns true if @a is empty, false otherwise. + */ +static bool tomoyo_merge_unix_acl(struct tomoyo_acl_info *a, +				  struct tomoyo_acl_info *b, +				  const bool is_delete) +{ +	u8 * const a_perm = +		&container_of(a, struct tomoyo_unix_acl, head)->perm; +	u8 perm = *a_perm; +	const u8 b_perm = container_of(b, struct tomoyo_unix_acl, head)->perm; + +	if (is_delete) +		perm &= ~b_perm; +	else +		perm |= b_perm; +	*a_perm = perm; +	return !perm; +} + +/** + * tomoyo_write_inet_network - Write "struct tomoyo_inet_acl" list. + * + * @param: Pointer to "struct tomoyo_acl_param". + * + * Returns 0 on success, negative value otherwise. + * + * Caller holds tomoyo_read_lock(). + */ +int tomoyo_write_inet_network(struct tomoyo_acl_param *param) +{ +	struct tomoyo_inet_acl e = { .head.type = TOMOYO_TYPE_INET_ACL }; +	int error = -EINVAL; +	u8 type; +	const char *protocol = tomoyo_read_token(param); +	const char *operation = tomoyo_read_token(param); + +	for (e.protocol = 0; e.protocol < TOMOYO_SOCK_MAX; e.protocol++) +		if (!strcmp(protocol, tomoyo_proto_keyword[e.protocol])) +			break; +	for (type = 0; type < TOMOYO_MAX_NETWORK_OPERATION; type++) +		if (tomoyo_permstr(operation, tomoyo_socket_keyword[type])) +			e.perm |= 1 << type; +	if (e.protocol == TOMOYO_SOCK_MAX || !e.perm) +		return -EINVAL; +	if (param->data[0] == '@') { +		param->data++; +		e.address.group = +			tomoyo_get_group(param, TOMOYO_ADDRESS_GROUP); +		if (!e.address.group) +			return -ENOMEM; +	} else { +		if (!tomoyo_parse_ipaddr_union(param, &e.address)) +			goto out; +	} +	if (!tomoyo_parse_number_union(param, &e.port) || +	    e.port.values[1] > 65535) +		goto out; +	error = tomoyo_update_domain(&e.head, sizeof(e), param, +				     tomoyo_same_inet_acl, +				     tomoyo_merge_inet_acl); +out: +	tomoyo_put_group(e.address.group); +	tomoyo_put_number_union(&e.port); +	return error; +} + +/** + * tomoyo_write_unix_network - Write "struct tomoyo_unix_acl" list. + * + * @param: Pointer to "struct tomoyo_acl_param". + * + * Returns 0 on success, negative value otherwise. + */ +int tomoyo_write_unix_network(struct tomoyo_acl_param *param) +{ +	struct tomoyo_unix_acl e = { .head.type = TOMOYO_TYPE_UNIX_ACL }; +	int error; +	u8 type; +	const char *protocol = tomoyo_read_token(param); +	const char *operation = tomoyo_read_token(param); + +	for (e.protocol = 0; e.protocol < TOMOYO_SOCK_MAX; e.protocol++) +		if (!strcmp(protocol, tomoyo_proto_keyword[e.protocol])) +			break; +	for (type = 0; type < TOMOYO_MAX_NETWORK_OPERATION; type++) +		if (tomoyo_permstr(operation, tomoyo_socket_keyword[type])) +			e.perm |= 1 << type; +	if (e.protocol == TOMOYO_SOCK_MAX || !e.perm) +		return -EINVAL; +	if (!tomoyo_parse_name_union(param, &e.name)) +		return -EINVAL; +	error = tomoyo_update_domain(&e.head, sizeof(e), param, +				     tomoyo_same_unix_acl, +				     tomoyo_merge_unix_acl); +	tomoyo_put_name_union(&e.name); +	return error; +} + +/** + * tomoyo_audit_net_log - Audit network log. + * + * @r:         Pointer to "struct tomoyo_request_info". + * @family:    Name of socket family ("inet" or "unix"). + * @protocol:  Name of protocol in @family. + * @operation: Name of socket operation. + * @address:   Name of address. + * + * Returns 0 on success, negative value otherwise. + */ +static int tomoyo_audit_net_log(struct tomoyo_request_info *r, +				const char *family, const u8 protocol, +				const u8 operation, const char *address) +{ +	return tomoyo_supervisor(r, "network %s %s %s %s\n", family, +				 tomoyo_proto_keyword[protocol], +				 tomoyo_socket_keyword[operation], address); +} + +/** + * tomoyo_audit_inet_log - Audit INET network log. + * + * @r: Pointer to "struct tomoyo_request_info". + * + * Returns 0 on success, negative value otherwise. + */ +static int tomoyo_audit_inet_log(struct tomoyo_request_info *r) +{ +	char buf[128]; +	int len; +	const __be32 *address = r->param.inet_network.address; + +	if (r->param.inet_network.is_ipv6) +		tomoyo_print_ipv6(buf, sizeof(buf), (const struct in6_addr *) +				  address, (const struct in6_addr *) address); +	else +		tomoyo_print_ipv4(buf, sizeof(buf), address, address); +	len = strlen(buf); +	snprintf(buf + len, sizeof(buf) - len, " %u", +		 r->param.inet_network.port); +	return tomoyo_audit_net_log(r, "inet", r->param.inet_network.protocol, +				    r->param.inet_network.operation, buf); +} + +/** + * tomoyo_audit_unix_log - Audit UNIX network log. + * + * @r: Pointer to "struct tomoyo_request_info". + * + * Returns 0 on success, negative value otherwise. + */ +static int tomoyo_audit_unix_log(struct tomoyo_request_info *r) +{ +	return tomoyo_audit_net_log(r, "unix", r->param.unix_network.protocol, +				    r->param.unix_network.operation, +				    r->param.unix_network.address->name); +} + +/** + * tomoyo_check_inet_acl - Check permission for inet domain socket operation. + * + * @r:   Pointer to "struct tomoyo_request_info". + * @ptr: Pointer to "struct tomoyo_acl_info". + * + * Returns true if granted, false otherwise. + */ +static bool tomoyo_check_inet_acl(struct tomoyo_request_info *r, +				  const struct tomoyo_acl_info *ptr) +{ +	const struct tomoyo_inet_acl *acl = +		container_of(ptr, typeof(*acl), head); +	const u8 size = r->param.inet_network.is_ipv6 ? 16 : 4; + +	if (!(acl->perm & (1 << r->param.inet_network.operation)) || +	    !tomoyo_compare_number_union(r->param.inet_network.port, +					 &acl->port)) +		return false; +	if (acl->address.group) +		return tomoyo_address_matches_group +			(r->param.inet_network.is_ipv6, +			 r->param.inet_network.address, acl->address.group); +	return acl->address.is_ipv6 == r->param.inet_network.is_ipv6 && +		memcmp(&acl->address.ip[0], +		       r->param.inet_network.address, size) <= 0 && +		memcmp(r->param.inet_network.address, +		       &acl->address.ip[1], size) <= 0; +} + +/** + * tomoyo_check_unix_acl - Check permission for unix domain socket operation. + * + * @r:   Pointer to "struct tomoyo_request_info". + * @ptr: Pointer to "struct tomoyo_acl_info". + * + * Returns true if granted, false otherwise. + */ +static bool tomoyo_check_unix_acl(struct tomoyo_request_info *r, +				  const struct tomoyo_acl_info *ptr) +{ +	const struct tomoyo_unix_acl *acl = +		container_of(ptr, typeof(*acl), head); + +	return (acl->perm & (1 << r->param.unix_network.operation)) && +		tomoyo_compare_name_union(r->param.unix_network.address, +					  &acl->name); +} + +/** + * tomoyo_inet_entry - Check permission for INET network operation. + * + * @address: Pointer to "struct tomoyo_addr_info". + * + * Returns 0 on success, negative value otherwise. + */ +static int tomoyo_inet_entry(const struct tomoyo_addr_info *address) +{ +	const int idx = tomoyo_read_lock(); +	struct tomoyo_request_info r; +	int error = 0; +	const u8 type = tomoyo_inet2mac[address->protocol][address->operation]; + +	if (type && tomoyo_init_request_info(&r, NULL, type) +	    != TOMOYO_CONFIG_DISABLED) { +		r.param_type = TOMOYO_TYPE_INET_ACL; +		r.param.inet_network.protocol = address->protocol; +		r.param.inet_network.operation = address->operation; +		r.param.inet_network.is_ipv6 = address->inet.is_ipv6; +		r.param.inet_network.address = address->inet.address; +		r.param.inet_network.port = ntohs(address->inet.port); +		do { +			tomoyo_check_acl(&r, tomoyo_check_inet_acl); +			error = tomoyo_audit_inet_log(&r); +		} while (error == TOMOYO_RETRY_REQUEST); +	} +	tomoyo_read_unlock(idx); +	return error; +} + +/** + * tomoyo_check_inet_address - Check permission for inet domain socket's operation. + * + * @addr:     Pointer to "struct sockaddr". + * @addr_len: Size of @addr. + * @port:     Port number. + * @address:  Pointer to "struct tomoyo_addr_info". + * + * Returns 0 on success, negative value otherwise. + */ +static int tomoyo_check_inet_address(const struct sockaddr *addr, +				     const unsigned int addr_len, +				     const u16 port, +				     struct tomoyo_addr_info *address) +{ +	struct tomoyo_inet_addr_info *i = &address->inet; + +	switch (addr->sa_family) { +	case AF_INET6: +		if (addr_len < SIN6_LEN_RFC2133) +			goto skip; +		i->is_ipv6 = true; +		i->address = (__be32 *) +			((struct sockaddr_in6 *) addr)->sin6_addr.s6_addr; +		i->port = ((struct sockaddr_in6 *) addr)->sin6_port; +		break; +	case AF_INET: +		if (addr_len < sizeof(struct sockaddr_in)) +			goto skip; +		i->is_ipv6 = false; +		i->address = (__be32 *) +			&((struct sockaddr_in *) addr)->sin_addr; +		i->port = ((struct sockaddr_in *) addr)->sin_port; +		break; +	default: +		goto skip; +	} +	if (address->protocol == SOCK_RAW) +		i->port = htons(port); +	return tomoyo_inet_entry(address); +skip: +	return 0; +} + +/** + * tomoyo_unix_entry - Check permission for UNIX network operation. + * + * @address: Pointer to "struct tomoyo_addr_info". + * + * Returns 0 on success, negative value otherwise. + */ +static int tomoyo_unix_entry(const struct tomoyo_addr_info *address) +{ +	const int idx = tomoyo_read_lock(); +	struct tomoyo_request_info r; +	int error = 0; +	const u8 type = tomoyo_unix2mac[address->protocol][address->operation]; + +	if (type && tomoyo_init_request_info(&r, NULL, type) +	    != TOMOYO_CONFIG_DISABLED) { +		char *buf = address->unix0.addr; +		int len = address->unix0.addr_len - sizeof(sa_family_t); + +		if (len <= 0) { +			buf = "anonymous"; +			len = 9; +		} else if (buf[0]) { +			len = strnlen(buf, len); +		} +		buf = tomoyo_encode2(buf, len); +		if (buf) { +			struct tomoyo_path_info addr; + +			addr.name = buf; +			tomoyo_fill_path_info(&addr); +			r.param_type = TOMOYO_TYPE_UNIX_ACL; +			r.param.unix_network.protocol = address->protocol; +			r.param.unix_network.operation = address->operation; +			r.param.unix_network.address = &addr; +			do { +				tomoyo_check_acl(&r, tomoyo_check_unix_acl); +				error = tomoyo_audit_unix_log(&r); +			} while (error == TOMOYO_RETRY_REQUEST); +			kfree(buf); +		} else +			error = -ENOMEM; +	} +	tomoyo_read_unlock(idx); +	return error; +} + +/** + * tomoyo_check_unix_address - Check permission for unix domain socket's operation. + * + * @addr:     Pointer to "struct sockaddr". + * @addr_len: Size of @addr. + * @address:  Pointer to "struct tomoyo_addr_info". + * + * Returns 0 on success, negative value otherwise. + */ +static int tomoyo_check_unix_address(struct sockaddr *addr, +				     const unsigned int addr_len, +				     struct tomoyo_addr_info *address) +{ +	struct tomoyo_unix_addr_info *u = &address->unix0; + +	if (addr->sa_family != AF_UNIX) +		return 0; +	u->addr = ((struct sockaddr_un *) addr)->sun_path; +	u->addr_len = addr_len; +	return tomoyo_unix_entry(address); +} + +/** + * tomoyo_kernel_service - Check whether I'm kernel service or not. + * + * Returns true if I'm kernel service, false otherwise. + */ +static bool tomoyo_kernel_service(void) +{ +	/* Nothing to do if I am a kernel service. */ +	return segment_eq(get_fs(), KERNEL_DS); +} + +/** + * tomoyo_sock_family - Get socket's family. + * + * @sk: Pointer to "struct sock". + * + * Returns one of PF_INET, PF_INET6, PF_UNIX or 0. + */ +static u8 tomoyo_sock_family(struct sock *sk) +{ +	u8 family; + +	if (tomoyo_kernel_service()) +		return 0; +	family = sk->sk_family; +	switch (family) { +	case PF_INET: +	case PF_INET6: +	case PF_UNIX: +		return family; +	default: +		return 0; +	} +} + +/** + * tomoyo_socket_listen_permission - Check permission for listening a socket. + * + * @sock: Pointer to "struct socket". + * + * Returns 0 on success, negative value otherwise. + */ +int tomoyo_socket_listen_permission(struct socket *sock) +{ +	struct tomoyo_addr_info address; +	const u8 family = tomoyo_sock_family(sock->sk); +	const unsigned int type = sock->type; +	struct sockaddr_storage addr; +	int addr_len; + +	if (!family || (type != SOCK_STREAM && type != SOCK_SEQPACKET)) +		return 0; +	{ +		const int error = sock->ops->getname(sock, (struct sockaddr *) +						     &addr, &addr_len, 0); + +		if (error) +			return error; +	} +	address.protocol = type; +	address.operation = TOMOYO_NETWORK_LISTEN; +	if (family == PF_UNIX) +		return tomoyo_check_unix_address((struct sockaddr *) &addr, +						 addr_len, &address); +	return tomoyo_check_inet_address((struct sockaddr *) &addr, addr_len, +					 0, &address); +} + +/** + * tomoyo_socket_connect_permission - Check permission for setting the remote address of a socket. + * + * @sock:     Pointer to "struct socket". + * @addr:     Pointer to "struct sockaddr". + * @addr_len: Size of @addr. + * + * Returns 0 on success, negative value otherwise. + */ +int tomoyo_socket_connect_permission(struct socket *sock, +				     struct sockaddr *addr, int addr_len) +{ +	struct tomoyo_addr_info address; +	const u8 family = tomoyo_sock_family(sock->sk); +	const unsigned int type = sock->type; + +	if (!family) +		return 0; +	address.protocol = type; +	switch (type) { +	case SOCK_DGRAM: +	case SOCK_RAW: +		address.operation = TOMOYO_NETWORK_SEND; +		break; +	case SOCK_STREAM: +	case SOCK_SEQPACKET: +		address.operation = TOMOYO_NETWORK_CONNECT; +		break; +	default: +		return 0; +	} +	if (family == PF_UNIX) +		return tomoyo_check_unix_address(addr, addr_len, &address); +	return tomoyo_check_inet_address(addr, addr_len, sock->sk->sk_protocol, +					 &address); +} + +/** + * tomoyo_socket_bind_permission - Check permission for setting the local address of a socket. + * + * @sock:     Pointer to "struct socket". + * @addr:     Pointer to "struct sockaddr". + * @addr_len: Size of @addr. + * + * Returns 0 on success, negative value otherwise. + */ +int tomoyo_socket_bind_permission(struct socket *sock, struct sockaddr *addr, +				  int addr_len) +{ +	struct tomoyo_addr_info address; +	const u8 family = tomoyo_sock_family(sock->sk); +	const unsigned int type = sock->type; + +	if (!family) +		return 0; +	switch (type) { +	case SOCK_STREAM: +	case SOCK_DGRAM: +	case SOCK_RAW: +	case SOCK_SEQPACKET: +		address.protocol = type; +		address.operation = TOMOYO_NETWORK_BIND; +		break; +	default: +		return 0; +	} +	if (family == PF_UNIX) +		return tomoyo_check_unix_address(addr, addr_len, &address); +	return tomoyo_check_inet_address(addr, addr_len, sock->sk->sk_protocol, +					 &address); +} + +/** + * tomoyo_socket_sendmsg_permission - Check permission for sending a datagram. + * + * @sock: Pointer to "struct socket". + * @msg:  Pointer to "struct msghdr". + * @size: Unused. + * + * Returns 0 on success, negative value otherwise. + */ +int tomoyo_socket_sendmsg_permission(struct socket *sock, struct msghdr *msg, +				     int size) +{ +	struct tomoyo_addr_info address; +	const u8 family = tomoyo_sock_family(sock->sk); +	const unsigned int type = sock->type; + +	if (!msg->msg_name || !family || +	    (type != SOCK_DGRAM && type != SOCK_RAW)) +		return 0; +	address.protocol = type; +	address.operation = TOMOYO_NETWORK_SEND; +	if (family == PF_UNIX) +		return tomoyo_check_unix_address((struct sockaddr *) +						 msg->msg_name, +						 msg->msg_namelen, &address); +	return tomoyo_check_inet_address((struct sockaddr *) msg->msg_name, +					 msg->msg_namelen, +					 sock->sk->sk_protocol, &address); +} diff --git a/security/tomoyo/realpath.c b/security/tomoyo/realpath.c index 6c601bd300f..738bbdf8d4c 100644 --- a/security/tomoyo/realpath.c +++ b/security/tomoyo/realpath.c @@ -15,17 +15,19 @@  #include "../../fs/internal.h"  /** - * tomoyo_encode: Convert binary string to ascii string. + * tomoyo_encode2 - Encode binary string to ascii string.   * - * @str: String in binary format. + * @str:     String in binary format. + * @str_len: Size of @str in byte.   *   * Returns pointer to @str in ascii format on success, NULL otherwise.   *   * This function uses kzalloc(), so caller must kfree() if this function   * didn't return NULL.   */ -char *tomoyo_encode(const char *str) +char *tomoyo_encode2(const char *str, int str_len)  { +	int i;  	int len = 0;  	const char *p = str;  	char *cp; @@ -33,8 +35,9 @@ char *tomoyo_encode(const char *str)  	if (!p)  		return NULL; -	while (*p) { -		const unsigned char c = *p++; +	for (i = 0; i < str_len; i++) { +		const unsigned char c = p[i]; +  		if (c == '\\')  			len += 2;  		else if (c > ' ' && c < 127) @@ -49,8 +52,8 @@ char *tomoyo_encode(const char *str)  		return NULL;  	cp0 = cp;  	p = str; -	while (*p) { -		const unsigned char c = *p++; +	for (i = 0; i < str_len; i++) { +		const unsigned char c = p[i];  		if (c == '\\') {  			*cp++ = '\\'; @@ -68,6 +71,21 @@ char *tomoyo_encode(const char *str)  }  /** + * tomoyo_encode - Encode binary string to ascii string. + * + * @str: String in binary format. + * + * Returns pointer to @str in ascii format on success, NULL otherwise. + * + * This function uses kzalloc(), so caller must kfree() if this function + * didn't return NULL. + */ +char *tomoyo_encode(const char *str) +{ +	return str ? tomoyo_encode2(str, strlen(str)) : NULL; +} + +/**   * tomoyo_get_absolute_path - Get the path of a dentry but ignores chroot'ed root.   *   * @path:   Pointer to "struct path". diff --git a/security/tomoyo/securityfs_if.c b/security/tomoyo/securityfs_if.c index a49c3bfd4dd..2672ac4f3be 100644 --- a/security/tomoyo/securityfs_if.c +++ b/security/tomoyo/securityfs_if.c @@ -8,6 +8,124 @@  #include "common.h"  /** + * tomoyo_check_task_acl - Check permission for task operation. + * + * @r:   Pointer to "struct tomoyo_request_info". + * @ptr: Pointer to "struct tomoyo_acl_info". + * + * Returns true if granted, false otherwise. + */ +static bool tomoyo_check_task_acl(struct tomoyo_request_info *r, +				  const struct tomoyo_acl_info *ptr) +{ +	const struct tomoyo_task_acl *acl = container_of(ptr, typeof(*acl), +							 head); +	return !tomoyo_pathcmp(r->param.task.domainname, acl->domainname); +} + +/** + * tomoyo_write_self - write() for /sys/kernel/security/tomoyo/self_domain interface. + * + * @file:  Pointer to "struct file". + * @buf:   Domainname to transit to. + * @count: Size of @buf. + * @ppos:  Unused. + * + * Returns @count on success, negative value otherwise. + * + * If domain transition was permitted but the domain transition failed, this + * function returns error rather than terminating current thread with SIGKILL. + */ +static ssize_t tomoyo_write_self(struct file *file, const char __user *buf, +			      size_t count, loff_t *ppos) +{ +	char *data; +	int error; +	if (!count || count >= TOMOYO_EXEC_TMPSIZE - 10) +		return -ENOMEM; +	data = kzalloc(count + 1, GFP_NOFS); +	if (!data) +		return -ENOMEM; +	if (copy_from_user(data, buf, count)) { +		error = -EFAULT; +		goto out; +	} +	tomoyo_normalize_line(data); +	if (tomoyo_correct_domain(data)) { +		const int idx = tomoyo_read_lock(); +		struct tomoyo_path_info name; +		struct tomoyo_request_info r; +		name.name = data; +		tomoyo_fill_path_info(&name); +		/* Check "task manual_domain_transition" permission. */ +		tomoyo_init_request_info(&r, NULL, TOMOYO_MAC_FILE_EXECUTE); +		r.param_type = TOMOYO_TYPE_MANUAL_TASK_ACL; +		r.param.task.domainname = &name; +		tomoyo_check_acl(&r, tomoyo_check_task_acl); +		if (!r.granted) +			error = -EPERM; +		else { +			struct tomoyo_domain_info *new_domain = +				tomoyo_assign_domain(data, true); +			if (!new_domain) { +				error = -ENOENT; +			} else { +				struct cred *cred = prepare_creds(); +				if (!cred) { +					error = -ENOMEM; +				} else { +					struct tomoyo_domain_info *old_domain = +						cred->security; +					cred->security = new_domain; +					atomic_inc(&new_domain->users); +					atomic_dec(&old_domain->users); +					commit_creds(cred); +					error = 0; +				} +			} +		} +		tomoyo_read_unlock(idx); +	} else +		error = -EINVAL; +out: +	kfree(data); +	return error ? error : count; +} + +/** + * tomoyo_read_self - read() for /sys/kernel/security/tomoyo/self_domain interface. + * + * @file:  Pointer to "struct file". + * @buf:   Domainname which current thread belongs to. + * @count: Size of @buf. + * @ppos:  Bytes read by now. + * + * Returns read size on success, negative value otherwise. + */ +static ssize_t tomoyo_read_self(struct file *file, char __user *buf, +				size_t count, loff_t *ppos) +{ +	const char *domain = tomoyo_domain()->domainname->name; +	loff_t len = strlen(domain); +	loff_t pos = *ppos; +	if (pos >= len || !count) +		return 0; +	len -= pos; +	if (count < len) +		len = count; +	if (copy_to_user(buf, domain + pos, len)) +		return -EFAULT; +	*ppos += len; +	return len; +} + +/* Operations for /sys/kernel/security/tomoyo/self_domain interface. */ +static const struct file_operations tomoyo_self_operations = { +	.write = tomoyo_write_self, +	.read  = tomoyo_read_self, +}; + +/**   * tomoyo_open - open() for /sys/kernel/security/tomoyo/ interface.   *   * @inode: Pointer to "struct inode". @@ -135,8 +253,6 @@ static int __init tomoyo_initerface_init(void)  			    TOMOYO_EXCEPTIONPOLICY);  	tomoyo_create_entry("audit",            0400, tomoyo_dir,  			    TOMOYO_AUDIT); -	tomoyo_create_entry("self_domain",      0400, tomoyo_dir, -			    TOMOYO_SELFDOMAIN);  	tomoyo_create_entry(".process_status",  0600, tomoyo_dir,  			    TOMOYO_PROCESS_STATUS);  	tomoyo_create_entry("stat",             0644, tomoyo_dir, @@ -147,6 +263,9 @@ static int __init tomoyo_initerface_init(void)  			    TOMOYO_MANAGER);  	tomoyo_create_entry("version",          0400, tomoyo_dir,  			    TOMOYO_VERSION); +	securityfs_create_file("self_domain", 0666, tomoyo_dir, NULL, +			       &tomoyo_self_operations); +	tomoyo_load_builtin_policy();  	return 0;  } diff --git a/security/tomoyo/tomoyo.c b/security/tomoyo/tomoyo.c index f776400a8f3..4b327b69174 100644 --- a/security/tomoyo/tomoyo.c +++ b/security/tomoyo/tomoyo.c @@ -442,6 +442,64 @@ static int tomoyo_sb_pivotroot(struct path *old_path, struct path *new_path)  	return tomoyo_path2_perm(TOMOYO_TYPE_PIVOT_ROOT, new_path, old_path);  } +/** + * tomoyo_socket_listen - Check permission for listen(). + * + * @sock:    Pointer to "struct socket". + * @backlog: Backlog parameter. + * + * Returns 0 on success, negative value otherwise. + */ +static int tomoyo_socket_listen(struct socket *sock, int backlog) +{ +	return tomoyo_socket_listen_permission(sock); +} + +/** + * tomoyo_socket_connect - Check permission for connect(). + * + * @sock:     Pointer to "struct socket". + * @addr:     Pointer to "struct sockaddr". + * @addr_len: Size of @addr. + * + * Returns 0 on success, negative value otherwise. + */ +static int tomoyo_socket_connect(struct socket *sock, struct sockaddr *addr, +				 int addr_len) +{ +	return tomoyo_socket_connect_permission(sock, addr, addr_len); +} + +/** + * tomoyo_socket_bind - Check permission for bind(). + * + * @sock:     Pointer to "struct socket". + * @addr:     Pointer to "struct sockaddr". + * @addr_len: Size of @addr. + * + * Returns 0 on success, negative value otherwise. + */ +static int tomoyo_socket_bind(struct socket *sock, struct sockaddr *addr, +			      int addr_len) +{ +	return tomoyo_socket_bind_permission(sock, addr, addr_len); +} + +/** + * tomoyo_socket_sendmsg - Check permission for sendmsg(). + * + * @sock: Pointer to "struct socket". + * @msg:  Pointer to "struct msghdr". + * @size: Size of message. + * + * Returns 0 on success, negative value otherwise. + */ +static int tomoyo_socket_sendmsg(struct socket *sock, struct msghdr *msg, +				 int size) +{ +	return tomoyo_socket_sendmsg_permission(sock, msg, size); +} +  /*   * tomoyo_security_ops is a "struct security_operations" which is used for   * registering TOMOYO. @@ -472,6 +530,10 @@ static struct security_operations tomoyo_security_ops = {  	.sb_mount            = tomoyo_sb_mount,  	.sb_umount           = tomoyo_sb_umount,  	.sb_pivotroot        = tomoyo_sb_pivotroot, +	.socket_bind         = tomoyo_socket_bind, +	.socket_connect      = tomoyo_socket_connect, +	.socket_listen       = tomoyo_socket_listen, +	.socket_sendmsg      = tomoyo_socket_sendmsg,  };  /* Lock for GC. */ diff --git a/security/tomoyo/util.c b/security/tomoyo/util.c index c36bd1107fc..4a9b4b2eb75 100644 --- a/security/tomoyo/util.c +++ b/security/tomoyo/util.c @@ -42,6 +42,39 @@ const u8 tomoyo_index2category[TOMOYO_MAX_MAC_INDEX] = {  	[TOMOYO_MAC_FILE_MOUNT]      = TOMOYO_MAC_CATEGORY_FILE,  	[TOMOYO_MAC_FILE_UMOUNT]     = TOMOYO_MAC_CATEGORY_FILE,  	[TOMOYO_MAC_FILE_PIVOT_ROOT] = TOMOYO_MAC_CATEGORY_FILE, +	/* CONFIG::network group */ +	[TOMOYO_MAC_NETWORK_INET_STREAM_BIND]       = +	TOMOYO_MAC_CATEGORY_NETWORK, +	[TOMOYO_MAC_NETWORK_INET_STREAM_LISTEN]     = +	TOMOYO_MAC_CATEGORY_NETWORK, +	[TOMOYO_MAC_NETWORK_INET_STREAM_CONNECT]    = +	TOMOYO_MAC_CATEGORY_NETWORK, +	[TOMOYO_MAC_NETWORK_INET_DGRAM_BIND]        = +	TOMOYO_MAC_CATEGORY_NETWORK, +	[TOMOYO_MAC_NETWORK_INET_DGRAM_SEND]        = +	TOMOYO_MAC_CATEGORY_NETWORK, +	[TOMOYO_MAC_NETWORK_INET_RAW_BIND]          = +	TOMOYO_MAC_CATEGORY_NETWORK, +	[TOMOYO_MAC_NETWORK_INET_RAW_SEND]          = +	TOMOYO_MAC_CATEGORY_NETWORK, +	[TOMOYO_MAC_NETWORK_UNIX_STREAM_BIND]       = +	TOMOYO_MAC_CATEGORY_NETWORK, +	[TOMOYO_MAC_NETWORK_UNIX_STREAM_LISTEN]     = +	TOMOYO_MAC_CATEGORY_NETWORK, +	[TOMOYO_MAC_NETWORK_UNIX_STREAM_CONNECT]    = +	TOMOYO_MAC_CATEGORY_NETWORK, +	[TOMOYO_MAC_NETWORK_UNIX_DGRAM_BIND]        = +	TOMOYO_MAC_CATEGORY_NETWORK, +	[TOMOYO_MAC_NETWORK_UNIX_DGRAM_SEND]        = +	TOMOYO_MAC_CATEGORY_NETWORK, +	[TOMOYO_MAC_NETWORK_UNIX_SEQPACKET_BIND]    = +	TOMOYO_MAC_CATEGORY_NETWORK, +	[TOMOYO_MAC_NETWORK_UNIX_SEQPACKET_LISTEN]  = +	TOMOYO_MAC_CATEGORY_NETWORK, +	[TOMOYO_MAC_NETWORK_UNIX_SEQPACKET_CONNECT] = +	TOMOYO_MAC_CATEGORY_NETWORK, +	/* CONFIG::misc group */ +	[TOMOYO_MAC_ENVIRON]         = TOMOYO_MAC_CATEGORY_MISC,  };  /** @@ -126,6 +159,31 @@ char *tomoyo_read_token(struct tomoyo_acl_param *param)  }  /** + * tomoyo_get_domainname - Read a domainname from a line. + * + * @param: Pointer to "struct tomoyo_acl_param". + * + * Returns a domainname on success, NULL otherwise. + */ +const struct tomoyo_path_info *tomoyo_get_domainname +(struct tomoyo_acl_param *param) +{ +	char *start = param->data; +	char *pos = start; +	while (*pos) { +		if (*pos++ != ' ' || *pos++ == '/') +			continue; +		pos -= 2; +		*pos++ = '\0'; +		break; +	} +	param->data = pos; +	if (tomoyo_correct_domain(start)) +		return tomoyo_get_name(start); +	return NULL; +} + +/**   * tomoyo_parse_ulong - Parse an "unsigned long" value.   *   * @result: Pointer to "unsigned long". @@ -920,14 +978,17 @@ int tomoyo_get_mode(const struct tomoyo_policy_namespace *ns, const u8 profile,  		    const u8 index)  {  	u8 mode; -	const u8 category = TOMOYO_MAC_CATEGORY_FILE; +	struct tomoyo_profile *p; +  	if (!tomoyo_policy_loaded)  		return TOMOYO_CONFIG_DISABLED; -	mode = tomoyo_profile(ns, profile)->config[index]; +	p = tomoyo_profile(ns, profile); +	mode = p->config[index];  	if (mode == TOMOYO_CONFIG_USE_DEFAULT) -		mode = tomoyo_profile(ns, profile)->config[category]; +		mode = p->config[tomoyo_index2category[index] +				 + TOMOYO_MAX_MAC_INDEX];  	if (mode == TOMOYO_CONFIG_USE_DEFAULT) -		mode = tomoyo_profile(ns, profile)->default_config; +		mode = p->default_config;  	return mode & 3;  } @@ -996,6 +1057,17 @@ bool tomoyo_domain_quota_is_ok(struct tomoyo_request_info *r)  			perm = container_of(ptr, struct tomoyo_mkdev_acl,  					    head)->perm;  			break; +		case TOMOYO_TYPE_INET_ACL: +			perm = container_of(ptr, struct tomoyo_inet_acl, +					    head)->perm; +			break; +		case TOMOYO_TYPE_UNIX_ACL: +			perm = container_of(ptr, struct tomoyo_unix_acl, +					    head)->perm; +			break; +		case TOMOYO_TYPE_MANUAL_TASK_ACL: +			perm = 0; +			break;  		default:  			perm = 1;  		}  |