diff --git a/Documentation/kernel-parameters.txt b/Documentation/kernel-parameters.txt index ad7e2e5088c1..fa09e64d9877 100644 --- a/Documentation/kernel-parameters.txt +++ b/Documentation/kernel-parameters.txt @@ -1051,6 +1051,10 @@ bytes respectively. Such letter suffixes can also be entirely omitted. ihash_entries= [KNL] Set number of hash buckets for inode cache. + ima_appraise= [IMA] appraise integrity measurements + Format: { "off" | "enforce" | "fix" } + default: "enforce" + ima_audit= [IMA] Format: { "0" | "1" } 0 -- integrity auditing messages. (Default) diff --git a/include/linux/xattr.h b/include/linux/xattr.h index e5d122031542..77a3e686d566 100644 --- a/include/linux/xattr.h +++ b/include/linux/xattr.h @@ -33,6 +33,9 @@ #define XATTR_EVM_SUFFIX "evm" #define XATTR_NAME_EVM XATTR_SECURITY_PREFIX XATTR_EVM_SUFFIX +#define XATTR_IMA_SUFFIX "ima" +#define XATTR_NAME_IMA XATTR_SECURITY_PREFIX XATTR_IMA_SUFFIX + #define XATTR_SELINUX_SUFFIX "selinux" #define XATTR_NAME_SELINUX XATTR_SECURITY_PREFIX XATTR_SELINUX_SUFFIX diff --git a/security/integrity/evm/evm_main.c b/security/integrity/evm/evm_main.c index 8901501425f4..eb5484504f50 100644 --- a/security/integrity/evm/evm_main.c +++ b/security/integrity/evm/evm_main.c @@ -33,6 +33,9 @@ char *evm_config_xattrnames[] = { #endif #ifdef CONFIG_SECURITY_SMACK XATTR_NAME_SMACK, +#endif +#ifdef CONFIG_IMA_APPRAISE + XATTR_NAME_IMA, #endif XATTR_NAME_CAPS, NULL diff --git a/security/integrity/iint.c b/security/integrity/iint.c index 399641c3e846..e600986aa49f 100644 --- a/security/integrity/iint.c +++ b/security/integrity/iint.c @@ -74,6 +74,7 @@ static void iint_free(struct integrity_iint_cache *iint) { iint->version = 0; iint->flags = 0UL; + iint->ima_status = INTEGRITY_UNKNOWN; iint->evm_status = INTEGRITY_UNKNOWN; kmem_cache_free(iint_cache, iint); } @@ -157,7 +158,7 @@ static void init_once(void *foo) memset(iint, 0, sizeof *iint); iint->version = 0; iint->flags = 0UL; - mutex_init(&iint->mutex); + iint->ima_status = INTEGRITY_UNKNOWN; iint->evm_status = INTEGRITY_UNKNOWN; } diff --git a/security/integrity/ima/Kconfig b/security/integrity/ima/Kconfig index 809ccf19d09c..d232c73647ae 100644 --- a/security/integrity/ima/Kconfig +++ b/security/integrity/ima/Kconfig @@ -56,3 +56,18 @@ config IMA_LSM_RULES default y help Disabling this option will disregard LSM based policy rules. + +config IMA_APPRAISE + bool "Appraise integrity measurements" + depends on IMA + default n + help + This option enables local measurement integrity appraisal. + It requires the system to be labeled with a security extended + attribute containing the file hash measurement. To protect + the security extended attributes from offline attack, enable + and configure EVM. + + For more information on integrity appraisal refer to: + + If unsure, say N. diff --git a/security/integrity/ima/Makefile b/security/integrity/ima/Makefile index 5f740f6971e1..3f2ca6bdc384 100644 --- a/security/integrity/ima/Makefile +++ b/security/integrity/ima/Makefile @@ -8,3 +8,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-$(CONFIG_IMA_AUDIT) += ima_audit.o +ima-$(CONFIG_IMA_APPRAISE) += ima_appraise.o diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h index e7c99fd0d223..069a4aa63e95 100644 --- a/security/integrity/ima/ima.h +++ b/security/integrity/ima/ima.h @@ -40,6 +40,7 @@ enum tpm_pcrs { TPM_PCR0 = 0, TPM_PCR8 = 8 }; extern int ima_initialized; extern int ima_used_chip; extern char *ima_hash; +extern int ima_appraise; /* IMA inode template definition */ struct ima_template_data { @@ -107,6 +108,7 @@ static inline unsigned long ima_hash_key(u8 *digest) } /* LIM API function definitions */ +int ima_must_appraise_or_measure(struct inode *inode, int mask, int function); int ima_must_measure(struct inode *inode, int mask, int function); int ima_collect_measurement(struct integrity_iint_cache *iint, struct file *file); @@ -123,14 +125,45 @@ 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 }; +enum ima_hooks { FILE_CHECK = 1, FILE_MMAP, BPRM_CHECK, POST_SETATTR }; -int ima_match_policy(struct inode *inode, enum ima_hooks func, int mask); +int ima_match_policy(struct inode *inode, enum ima_hooks func, int mask, + int flags); void ima_init_policy(void); void ima_update_policy(void); ssize_t ima_parse_add_rule(char *); void ima_delete_rules(void); +/* Appraise integrity measurements */ +#define IMA_APPRAISE_ENFORCE 0x01 +#define IMA_APPRAISE_FIX 0x02 + +#ifdef CONFIG_IMA_APPRAISE +int ima_appraise_measurement(struct integrity_iint_cache *iint, + struct file *file, const unsigned char *filename); +int ima_must_appraise(struct inode *inode, enum ima_hooks func, int mask); +void ima_update_xattr(struct integrity_iint_cache *iint, struct file *file); + +#else +static inline int ima_appraise_measurement(struct integrity_iint_cache *iint, + struct file *file, + const unsigned char *filename) +{ + return INTEGRITY_UNKNOWN; +} + +static inline int ima_must_appraise(struct inode *inode, + enum ima_hooks func, int mask) +{ + return 0; +} + +static inline void ima_update_xattr(struct integrity_iint_cache *iint, + struct file *file) +{ +} +#endif + /* LSM based policy rules require audit */ #ifdef CONFIG_IMA_LSM_RULES diff --git a/security/integrity/ima/ima_api.c b/security/integrity/ima/ima_api.c index 032ff03ad907..41cce84416c5 100644 --- a/security/integrity/ima/ima_api.c +++ b/security/integrity/ima/ima_api.c @@ -9,13 +9,17 @@ * License. * * File: ima_api.c - * Implements must_measure, collect_measurement, store_measurement, - * and store_template. + * Implements must_appraise_or_measure, collect_measurement, + * appraise_measurement, store_measurement and store_template. */ #include #include - +#include +#include +#include +#include #include "ima.h" + static const char *IMA_TEMPLATE_NAME = "ima"; /* @@ -93,7 +97,7 @@ err_out: } /** - * ima_must_measure - measure decision based on policy. + * ima_must_appraise_or_measure - appraise & measure decision based on policy. * @inode: pointer to inode to measure * @mask: contains the permission mask (MAY_READ, MAY_WRITE, MAY_EXECUTE) * @function: calling function (FILE_CHECK, BPRM_CHECK, FILE_MMAP) @@ -105,15 +109,22 @@ err_out: * mask: contains the permission mask * fsmagic: hex value * - * Return 0 to measure. For matching a DONT_MEASURE policy, no policy, - * or other error, return an error code. -*/ + * Returns IMA_MEASURE, IMA_APPRAISE mask. + * + */ +int ima_must_appraise_or_measure(struct inode *inode, int mask, int function) +{ + int flags = IMA_MEASURE | IMA_APPRAISE; + + if (!ima_appraise) + flags &= ~IMA_APPRAISE; + + return ima_match_policy(inode, function, mask, flags); +} + int ima_must_measure(struct inode *inode, int mask, int function) { - int must_measure; - - must_measure = ima_match_policy(inode, function, mask); - return must_measure ? 0 : -EACCES; + return ima_match_policy(inode, function, mask, IMA_MEASURE); } /* @@ -129,16 +140,24 @@ int ima_must_measure(struct inode *inode, int mask, int function) int ima_collect_measurement(struct integrity_iint_cache *iint, struct file *file) { - int result = -EEXIST; + struct inode *inode = file->f_dentry->d_inode; + const char *filename = file->f_dentry->d_name.name; + int result = 0; - if (!(iint->flags & IMA_MEASURED)) { + if (!(iint->flags & IMA_COLLECTED)) { u64 i_version = file->f_dentry->d_inode->i_version; memset(iint->digest, 0, IMA_DIGEST_SIZE); result = ima_calc_hash(file, iint->digest); - if (!result) + if (!result) { iint->version = i_version; + iint->flags |= IMA_COLLECTED; + } } + if (result) + integrity_audit_msg(AUDIT_INTEGRITY_DATA, inode, + filename, "collect_data", "failed", + result, 0); return result; } @@ -167,6 +186,9 @@ void ima_store_measurement(struct integrity_iint_cache *iint, struct ima_template_entry *entry; int violation = 0; + if (iint->flags & IMA_MEASURED) + return; + entry = kmalloc(sizeof(*entry), GFP_KERNEL); if (!entry) { integrity_audit_msg(AUDIT_INTEGRITY_PCR, inode, filename, diff --git a/security/integrity/ima/ima_appraise.c b/security/integrity/ima/ima_appraise.c new file mode 100644 index 000000000000..4865f61f9044 --- /dev/null +++ b/security/integrity/ima/ima_appraise.c @@ -0,0 +1,168 @@ +/* + * Copyright (C) 2011 IBM Corporation + * + * Author: + * Mimi Zohar + * + * 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 +#include +#include +#include +#include +#include +#include + +#include "ima.h" + +static int __init default_appraise_setup(char *str) +{ + if (strncmp(str, "off", 3) == 0) + ima_appraise = 0; + else if (strncmp(str, "fix", 3) == 0) + ima_appraise = IMA_APPRAISE_FIX; + return 1; +} + +__setup("ima_appraise=", default_appraise_setup); + +/* + * ima_must_appraise - set appraise flag + * + * Return 1 to appraise + */ +int ima_must_appraise(struct inode *inode, enum ima_hooks func, int mask) +{ + return 0; +} + +static void ima_fix_xattr(struct dentry *dentry, + struct integrity_iint_cache *iint) +{ + iint->digest[0] = IMA_XATTR_DIGEST; + __vfs_setxattr_noperm(dentry, XATTR_NAME_IMA, + iint->digest, IMA_DIGEST_SIZE + 1, 0); +} + +/* + * ima_appraise_measurement - appraise file measurement + * + * Call evm_verifyxattr() to verify the integrity of 'security.ima'. + * Assuming success, compare the xattr hash with the collected measurement. + * + * Return 0 on success, error code otherwise + */ +int ima_appraise_measurement(struct integrity_iint_cache *iint, + struct file *file, const unsigned char *filename) +{ + struct dentry *dentry = file->f_dentry; + struct inode *inode = dentry->d_inode; + u8 xattr_value[IMA_DIGEST_SIZE]; + enum integrity_status status = INTEGRITY_UNKNOWN; + const char *op = "appraise_data"; + char *cause = "unknown"; + int rc; + + if (!ima_appraise) + return 0; + if (!inode->i_op->getxattr) + return INTEGRITY_UNKNOWN; + + if (iint->flags & IMA_APPRAISED) + return iint->ima_status; + + rc = inode->i_op->getxattr(dentry, XATTR_NAME_IMA, xattr_value, + IMA_DIGEST_SIZE); + if (rc <= 0) { + if (rc && rc != -ENODATA) + goto out; + + cause = "missing-hash"; + status = + (inode->i_size == 0) ? INTEGRITY_PASS : INTEGRITY_NOLABEL; + goto out; + } + + status = evm_verifyxattr(dentry, XATTR_NAME_IMA, xattr_value, rc, iint); + if ((status != INTEGRITY_PASS) && (status != INTEGRITY_UNKNOWN)) { + if ((status == INTEGRITY_NOLABEL) + || (status == INTEGRITY_NOXATTRS)) + cause = "missing-HMAC"; + else if (status == INTEGRITY_FAIL) + cause = "invalid-HMAC"; + goto out; + } + + rc = memcmp(xattr_value, iint->digest, IMA_DIGEST_SIZE); + if (rc) { + status = INTEGRITY_FAIL; + cause = "invalid-hash"; + print_hex_dump_bytes("security.ima: ", DUMP_PREFIX_NONE, + xattr_value, IMA_DIGEST_SIZE); + print_hex_dump_bytes("collected: ", DUMP_PREFIX_NONE, + iint->digest, IMA_DIGEST_SIZE); + goto out; + } + status = INTEGRITY_PASS; + iint->flags |= IMA_APPRAISED; +out: + if (status != INTEGRITY_PASS) { + if (ima_appraise & IMA_APPRAISE_FIX) { + ima_fix_xattr(dentry, iint); + status = INTEGRITY_PASS; + } + integrity_audit_msg(AUDIT_INTEGRITY_DATA, inode, filename, + op, cause, rc, 0); + } + iint->ima_status = status; + return status; +} + +/* + * ima_update_xattr - update 'security.ima' hash value + */ +void ima_update_xattr(struct integrity_iint_cache *iint, struct file *file) +{ + struct dentry *dentry = file->f_dentry; + int rc = 0; + + rc = ima_collect_measurement(iint, file); + if (rc < 0) + return; + ima_fix_xattr(dentry, iint); +} + +/** + * ima_inode_post_setattr - reflect file metadata changes + * @dentry: pointer to the affected dentry + * + * Changes to a dentry's metadata might result in needing to appraise. + * + * This function is called from notify_change(), which expects the caller + * to lock the inode's i_mutex. + */ +void ima_inode_post_setattr(struct dentry *dentry) +{ + struct inode *inode = dentry->d_inode; + struct integrity_iint_cache *iint; + int must_appraise, rc; + + if (!ima_initialized || !ima_appraise || !S_ISREG(inode->i_mode) + || !inode->i_op->removexattr) + return; + + must_appraise = ima_must_appraise(inode, MAY_ACCESS, POST_SETATTR); + iint = integrity_iint_find(inode); + if (iint) { + if (must_appraise) + iint->flags |= IMA_APPRAISE; + else + iint->flags &= ~(IMA_APPRAISE | IMA_APPRAISED); + } + if (!must_appraise) + rc = inode->i_op->removexattr(dentry, XATTR_NAME_IMA); + return; +} diff --git a/security/integrity/ima/ima_crypto.c b/security/integrity/ima/ima_crypto.c index 9b3ade7468b2..b21ee5b5495a 100644 --- a/security/integrity/ima/ima_crypto.c +++ b/security/integrity/ima/ima_crypto.c @@ -48,7 +48,7 @@ int ima_calc_hash(struct file *file, char *digest) struct scatterlist sg[1]; loff_t i_size, offset = 0; char *rbuf; - int rc; + int rc, read = 0; rc = init_desc(&desc); if (rc != 0) @@ -59,6 +59,10 @@ int ima_calc_hash(struct file *file, char *digest) rc = -ENOMEM; goto out; } + if (!(file->f_mode & FMODE_READ)) { + file->f_mode |= FMODE_READ; + read = 1; + } i_size = i_size_read(file->f_dentry->d_inode); while (offset < i_size) { int rbuf_len; @@ -80,6 +84,8 @@ int ima_calc_hash(struct file *file, char *digest) kfree(rbuf); if (!rc) rc = crypto_hash_final(&desc, digest); + if (read) + file->f_mode &= ~FMODE_READ; out: crypto_free_hash(desc.tfm); return rc; diff --git a/security/integrity/ima/ima_main.c b/security/integrity/ima/ima_main.c index be8294915cf7..6eb28d47e74b 100644 --- a/security/integrity/ima/ima_main.c +++ b/security/integrity/ima/ima_main.c @@ -22,12 +22,19 @@ #include #include #include +#include #include #include "ima.h" int ima_initialized; +#ifdef CONFIG_IMA_APPRAISE +int ima_appraise = IMA_APPRAISE_ENFORCE; +#else +int ima_appraise; +#endif + char *ima_hash = "sha1"; static int __init hash_setup(char *str) { @@ -52,7 +59,7 @@ static void ima_rdwr_violation_check(struct file *file) struct dentry *dentry = file->f_path.dentry; struct inode *inode = dentry->d_inode; fmode_t mode = file->f_mode; - int rc; + int must_measure; bool send_tomtou = false, send_writers = false; unsigned char *pathname = NULL, *pathbuf = NULL; @@ -67,8 +74,8 @@ static void ima_rdwr_violation_check(struct file *file) goto out; } - rc = ima_must_measure(inode, MAY_READ, FILE_CHECK); - if (rc < 0) + must_measure = ima_must_measure(inode, MAY_READ, FILE_CHECK); + if (!must_measure) goto out; if (atomic_read(&inode->i_writecount) > 0) @@ -100,17 +107,21 @@ out: } static void ima_check_last_writer(struct integrity_iint_cache *iint, - struct inode *inode, - struct file *file) + struct inode *inode, struct file *file) { fmode_t mode = file->f_mode; - mutex_lock(&iint->mutex); - if (mode & FMODE_WRITE && - atomic_read(&inode->i_writecount) == 1 && - iint->version != inode->i_version) - iint->flags &= ~IMA_MEASURED; - mutex_unlock(&iint->mutex); + if (!(mode & FMODE_WRITE)) + return; + + mutex_lock(&inode->i_mutex); + if (atomic_read(&inode->i_writecount) == 1 && + iint->version != inode->i_version) { + iint->flags &= ~(IMA_COLLECTED | IMA_APPRAISED | IMA_MEASURED); + if (iint->flags & IMA_APPRAISE) + ima_update_xattr(iint, file); + } + mutex_unlock(&inode->i_mutex); } /** @@ -140,14 +151,17 @@ static int process_measurement(struct file *file, const unsigned char *filename, struct inode *inode = file->f_dentry->d_inode; struct integrity_iint_cache *iint; unsigned char *pathname = NULL, *pathbuf = NULL; - int rc = 0; + int rc = -ENOMEM, action, must_appraise; if (!ima_initialized || !S_ISREG(inode->i_mode)) return 0; - rc = ima_must_measure(inode, mask, function); - if (rc != 0) - return rc; + /* Determine if in appraise/measurement policy, + * returns IMA_MEASURE, IMA_APPRAISE bitmask. */ + action = ima_must_appraise_or_measure(inode, mask, function); + if (!action) + return 0; + retry: iint = integrity_iint_find(inode); if (!iint) { @@ -157,11 +171,21 @@ retry: return rc; } - mutex_lock(&iint->mutex); + must_appraise = action & IMA_APPRAISE; - rc = iint->flags & IMA_MEASURED ? 1 : 0; - if (rc != 0) + mutex_lock(&inode->i_mutex); + + /* Determine if already appraised/measured based on bitmask + * (IMA_MEASURE, IMA_MEASURED, IMA_APPRAISE, IMA_APPRAISED) */ + iint->flags |= action; + action &= ~((iint->flags & (IMA_MEASURED | IMA_APPRAISED)) >> 1); + + /* Nothing to do, just return existing appraised status */ + if (!action) { + if (iint->flags & IMA_APPRAISED) + rc = iint->ima_status; goto out; + } rc = ima_collect_measurement(iint, file); if (rc != 0) @@ -177,11 +201,16 @@ retry: pathname = NULL; } } - ima_store_measurement(iint, file, !pathname ? filename : pathname); + if (action & IMA_MEASURE) + ima_store_measurement(iint, file, + !pathname ? filename : pathname); + if (action & IMA_APPRAISE) + rc = ima_appraise_measurement(iint, file, + !pathname ? filename : pathname); kfree(pathbuf); out: - mutex_unlock(&iint->mutex); - return rc; + mutex_unlock(&inode->i_mutex); + return (rc && must_appraise) ? -EACCES : 0; } /** @@ -197,14 +226,14 @@ out: */ int ima_file_mmap(struct file *file, unsigned long prot) { - int rc; + int rc = 0; if (!file) return 0; if (prot & PROT_EXEC) rc = process_measurement(file, file->f_dentry->d_name.name, MAY_EXEC, FILE_MMAP); - return 0; + return (ima_appraise & IMA_APPRAISE_ENFORCE) ? rc : 0; } /** @@ -228,7 +257,7 @@ int ima_bprm_check(struct linux_binprm *bprm) (strcmp(bprm->filename, bprm->interp) == 0) ? bprm->filename : bprm->interp, MAY_EXEC, BPRM_CHECK); - return 0; + return (ima_appraise & IMA_APPRAISE_ENFORCE) ? rc : 0; } /** @@ -249,7 +278,7 @@ int ima_file_check(struct file *file, int mask) rc = process_measurement(file, file->f_dentry->d_name.name, mask & (MAY_READ | MAY_WRITE | MAY_EXEC), FILE_CHECK); - return 0; + return (ima_appraise & IMA_APPRAISE_ENFORCE) ? rc : 0; } EXPORT_SYMBOL_GPL(ima_file_check); diff --git a/security/integrity/ima/ima_policy.c b/security/integrity/ima/ima_policy.c index 1a9583008aae..3e22e17da295 100644 --- a/security/integrity/ima/ima_policy.c +++ b/security/integrity/ima/ima_policy.c @@ -25,7 +25,13 @@ #define IMA_FSMAGIC 0x0004 #define IMA_UID 0x0008 -enum ima_action { UNKNOWN = -1, DONT_MEASURE = 0, MEASURE }; +#define UNKNOWN 0 +#define MEASURE 1 /* same as IMA_MEASURE */ +#define DONT_MEASURE 2 +#define MEASURE_MASK 3 +#define APPRAISE 4 /* same as IMA_APPRAISE */ +#define DONT_APPRAISE 8 +#define APPRAISE_MASK 12 #define MAX_LSM_RULES 6 enum lsm_rule_types { LSM_OBJ_USER, LSM_OBJ_ROLE, LSM_OBJ_TYPE, @@ -34,7 +40,7 @@ enum lsm_rule_types { LSM_OBJ_USER, LSM_OBJ_ROLE, LSM_OBJ_TYPE, struct ima_measure_rule_entry { struct list_head list; - enum ima_action action; + int action; unsigned int flags; enum ima_hooks func; int mask; @@ -163,18 +169,28 @@ static bool ima_match_rules(struct ima_measure_rule_entry *rule, * as elements in the list are never deleted, nor does the list * change.) */ -int ima_match_policy(struct inode *inode, enum ima_hooks func, int mask) +int ima_match_policy(struct inode *inode, enum ima_hooks func, int mask, + int flags) { struct ima_measure_rule_entry *entry; + int action = 0, actmask = flags | (flags << 1); list_for_each_entry(entry, ima_measure, list) { - bool rc; - rc = ima_match_rules(entry, inode, func, mask); - if (rc) - return entry->action; + if (!(entry->action & actmask)) + continue; + + if (!ima_match_rules(entry, inode, func, mask)) + continue; + + action |= (entry->action & (IMA_APPRAISE | IMA_MEASURE)); + actmask &= (entry->action & APPRAISE_MASK) ? + ~APPRAISE_MASK : ~MEASURE_MASK; + if (!actmask) + break; } - return 0; + + return action; } /** diff --git a/security/integrity/integrity.h b/security/integrity/integrity.h index 7a25ecec5aaa..dac6b68e945a 100644 --- a/security/integrity/integrity.h +++ b/security/integrity/integrity.h @@ -16,7 +16,11 @@ #include /* iint cache flags */ -#define IMA_MEASURED 0x01 +#define IMA_MEASURE 0x01 +#define IMA_MEASURED 0x02 +#define IMA_APPRAISE 0x04 +#define IMA_APPRAISED 0x08 +#define IMA_COLLECTED 0x10 enum evm_ima_xattr_type { IMA_XATTR_DIGEST = 0x01, @@ -36,7 +40,7 @@ struct integrity_iint_cache { u64 version; /* track inode changes */ unsigned char flags; u8 digest[SHA1_DIGEST_SIZE]; - struct mutex mutex; /* protects: version, flags, digest */ + enum integrity_status ima_status; enum integrity_status evm_status; };