diff options
Diffstat (limited to 'security')
-rw-r--r-- | security/integrity/Kconfig | 3 | ||||
-rw-r--r-- | security/integrity/Makefile | 2 | ||||
-rw-r--r-- | security/integrity/evm/Kconfig | 12 | ||||
-rw-r--r-- | security/integrity/evm/Makefile | 6 | ||||
-rw-r--r-- | security/integrity/evm/evm.h | 33 | ||||
-rw-r--r-- | security/integrity/evm/evm_crypto.c | 183 | ||||
-rw-r--r-- | security/integrity/evm/evm_main.c | 284 | ||||
-rw-r--r-- | security/integrity/evm/evm_secfs.c | 108 | ||||
-rw-r--r-- | security/integrity/iint.c | 1 | ||||
-rw-r--r-- | security/integrity/integrity.h | 1 |
10 files changed, 632 insertions, 1 deletions
diff --git a/security/integrity/Kconfig b/security/integrity/Kconfig index 27046915568..4bf00acf793 100644 --- a/security/integrity/Kconfig +++ b/security/integrity/Kconfig @@ -1,6 +1,7 @@ # config INTEGRITY def_bool y - depends on IMA + depends on IMA || EVM source security/integrity/ima/Kconfig +source security/integrity/evm/Kconfig diff --git a/security/integrity/Makefile b/security/integrity/Makefile index 6eddd61b84e..0ae44aea651 100644 --- a/security/integrity/Makefile +++ b/security/integrity/Makefile @@ -8,3 +8,5 @@ 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..73f654099a4 --- /dev/null +++ b/security/integrity/evm/Kconfig @@ -0,0 +1,12 @@ +config EVM + boolean "EVM support" + depends on SECURITY && KEYS && ENCRYPTED_KEYS + select CRYPTO_HMAC + select CRYPTO_MD5 + select CRYPTO_SHA1 + 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..0787d262b9e --- /dev/null +++ b/security/integrity/evm/Makefile @@ -0,0 +1,6 @@ +# +# Makefile for building the Extended Verification Module(EVM) +# +obj-$(CONFIG_EVM) += evm.o + +evm-y := evm_main.o evm_crypto.o evm_secfs.o diff --git a/security/integrity/evm/evm.h b/security/integrity/evm/evm.h new file mode 100644 index 00000000000..375dc3e6015 --- /dev/null +++ b/security/integrity/evm/evm.h @@ -0,0 +1,33 @@ +/* + * 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/security.h> +#include "../integrity.h" + +extern int evm_initialized; +extern char *evm_hmac; + +/* 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_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..d49bb002f3d --- /dev/null +++ b/security/integrity/evm/evm_crypto.c @@ -0,0 +1,183 @@ +/* + * 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 <linux/scatterlist.h> +#include <keys/encrypted-type.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; + +static int init_desc(struct hash_desc *desc) +{ + int rc; + + desc->tfm = crypto_alloc_hash(evm_hmac, 0, CRYPTO_ALG_ASYNC); + if (IS_ERR(desc->tfm)) { + pr_info("Can not allocate %s (reason: %ld)\n", + evm_hmac, PTR_ERR(desc->tfm)); + rc = PTR_ERR(desc->tfm); + return rc; + } + desc->flags = 0; + rc = crypto_hash_setkey(desc->tfm, evmkey, evmkey_len); + if (rc) + goto out; + rc = crypto_hash_init(desc); +out: + if (rc) + crypto_free_hash(desc->tfm); + return rc; +} + +/* 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 hash_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; + struct scatterlist sg[1]; + + 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; + sg_init_one(sg, &hmac_misc, sizeof hmac_misc); + crypto_hash_update(desc, sg, sizeof hmac_misc); + crypto_hash_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 hash_desc desc; + struct scatterlist sg[1]; + 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; + error = init_desc(&desc); + if (error) + return error; + + error = -ENODATA; + for (xattrname = evm_config_xattrnames; *xattrname != NULL; xattrname++) { + if ((req_xattr_name && req_xattr_value) + && !strcmp(*xattrname, req_xattr_name)) { + error = 0; + sg_init_one(sg, req_xattr_value, req_xattr_value_len); + crypto_hash_update(&desc, sg, 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; + sg_init_one(sg, xattr_value, xattr_size); + crypto_hash_update(&desc, sg, xattr_size); + } + hmac_add_misc(&desc, inode, digest); + kfree(xattr_value); +out: + crypto_free_hash(desc.tfm); + 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; + u8 hmac[SHA1_DIGEST_SIZE]; + int rc = 0; + + rc = evm_calc_hmac(dentry, xattr_name, xattr_value, + xattr_value_len, hmac); + if (rc == 0) + rc = __vfs_setxattr_noperm(dentry, XATTR_NAME_EVM, + hmac, SHA1_DIGEST_SIZE, 0); + else if (rc == -ENODATA) + rc = inode->i_op->removexattr(dentry, XATTR_NAME_EVM); + return rc; +} + +/* + * 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..a8fa45fef8f --- /dev/null +++ b/security/integrity/evm/evm_main.c @@ -0,0 +1,284 @@ +/* + * 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 "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 +}; + +/* + * 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.) + * + * 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) +{ + char hmac_val[SHA1_DIGEST_SIZE]; + int rc; + + if (iint->hmac_status != INTEGRITY_UNKNOWN) + return iint->hmac_status; + + memset(hmac_val, 0, sizeof hmac_val); + rc = evm_calc_hmac(dentry, xattr_name, xattr_value, + xattr_value_len, hmac_val); + if (rc < 0) + return INTEGRITY_UNKNOWN; + + rc = vfs_xattr_cmp(dentry, XATTR_NAME_EVM, hmac_val, sizeof hmac_val, + GFP_NOFS); + if (rc < 0) + goto err_out; + iint->hmac_status = INTEGRITY_PASS; + return iint->hmac_status; + +err_out: + switch (rc) { + case -ENODATA: /* file not labelled */ + iint->hmac_status = INTEGRITY_NOLABEL; + break; + case -EINVAL: + iint->hmac_status = INTEGRITY_FAIL; + break; + default: + iint->hmac_status = INTEGRITY_UNKNOWN; + } + return iint->hmac_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; + } + } + 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 inode *inode = dentry->d_inode; + struct integrity_iint_cache *iint; + enum integrity_status status; + + if (!evm_initialized || !evm_protected_xattr(xattr_name)) + return INTEGRITY_UNKNOWN; + + iint = integrity_iint_find(inode); + if (!iint) + return INTEGRITY_UNKNOWN; + status = evm_verify_hmac(dentry, xattr_name, xattr_value, + xattr_value_len, iint); + return status; +} +EXPORT_SYMBOL_GPL(evm_verifyxattr); + +/* + * evm_protect_xattr - protect the EVM extended attribute + * + * Prevent security.evm from being modified or removed. + */ +static int evm_protect_xattr(struct dentry *dentry, const char *xattr_name, + const void *xattr_value, size_t xattr_value_len) +{ + if (strcmp(xattr_name, XATTR_NAME_EVM) == 0) { + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + } + return 0; +} + +/** + * 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 + * + * Prevent 'security.evm' from being modified + */ +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 + * + * Prevent 'security.evm' from being removed. + */ +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)) + 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_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; +} + +static struct crypto_hash *tfm_hmac; /* preload crypto alg */ +static int __init init_evm(void) +{ + int error; + + tfm_hmac = crypto_alloc_hash(evm_hmac, 0, CRYPTO_ALG_ASYNC); + 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(); + crypto_free_hash(tfm_hmac); +} + +/* + * 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_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 index d17de48bd6c..991df20709b 100644 --- a/security/integrity/iint.c +++ b/security/integrity/iint.c @@ -157,6 +157,7 @@ static void init_once(void *foo) iint->version = 0; iint->flags = 0UL; mutex_init(&iint->mutex); + iint->hmac_status = INTEGRITY_UNKNOWN; } static int __init integrity_iintcache_init(void) diff --git a/security/integrity/integrity.h b/security/integrity/integrity.h index 7351836325a..397a46b3992 100644 --- a/security/integrity/integrity.h +++ b/security/integrity/integrity.h @@ -26,6 +26,7 @@ struct integrity_iint_cache { unsigned char flags; u8 digest[SHA1_DIGEST_SIZE]; struct mutex mutex; /* protects: version, flags, digest */ + enum integrity_status hmac_status; }; /* rbtree tree calls to lookup, insert, delete |