diff options
Diffstat (limited to 'security/integrity/ima')
| -rw-r--r-- | security/integrity/ima/Kconfig | 95 | ||||
| -rw-r--r-- | security/integrity/ima/Makefile | 3 | ||||
| -rw-r--r-- | security/integrity/ima/ima.h | 156 | ||||
| -rw-r--r-- | security/integrity/ima/ima_api.c | 269 | ||||
| -rw-r--r-- | security/integrity/ima/ima_appraise.c | 387 | ||||
| -rw-r--r-- | security/integrity/ima/ima_audit.c | 66 | ||||
| -rw-r--r-- | security/integrity/ima/ima_crypto.c | 253 | ||||
| -rw-r--r-- | security/integrity/ima/ima_fs.c | 96 | ||||
| -rw-r--r-- | security/integrity/ima/ima_init.c | 57 | ||||
| -rw-r--r-- | security/integrity/ima/ima_main.c | 264 | ||||
| -rw-r--r-- | security/integrity/ima/ima_policy.c | 395 | ||||
| -rw-r--r-- | security/integrity/ima/ima_queue.c | 25 | ||||
| -rw-r--r-- | security/integrity/ima/ima_template.c | 190 | ||||
| -rw-r--r-- | security/integrity/ima/ima_template_lib.c | 342 | ||||
| -rw-r--r-- | security/integrity/ima/ima_template_lib.h | 49 |
15 files changed, 2187 insertions, 460 deletions
diff --git a/security/integrity/ima/Kconfig b/security/integrity/ima/Kconfig index 4f554f20dc9..81a27971d88 100644 --- a/security/integrity/ima/Kconfig +++ b/security/integrity/ima/Kconfig @@ -9,8 +9,10 @@ config IMA select CRYPTO_HMAC select CRYPTO_MD5 select CRYPTO_SHA1 - select TCG_TPM if !S390 && !UML - select TCG_TIS if TCG_TPM + select CRYPTO_HASH_INFO + select TCG_TPM if HAS_IOMEM && !UML + select TCG_TIS if TCG_TPM && X86 + select TCG_IBMVTPM if TCG_TPM && PPC64 help The Trusted Computing Group(TCG) runtime Integrity Measurement Architecture(IMA) maintains a list of hash @@ -37,20 +39,87 @@ config IMA_MEASURE_PCR_IDX that IMA uses to maintain the integrity aggregate of the measurement list. If unsure, use the default 10. -config IMA_AUDIT - bool - depends on IMA - default y - help - This option adds a kernel parameter 'ima_audit', which - allows informational auditing messages to be enabled - at boot. If this option is selected, informational integrity - auditing messages can be enabled with 'ima_audit=1' on - the kernel command line. - config IMA_LSM_RULES bool depends on IMA && AUDIT && (SECURITY_SELINUX || SECURITY_SMACK) default y help Disabling this option will disregard LSM based policy rules. + +choice + prompt "Default template" + default IMA_NG_TEMPLATE + depends on IMA + help + Select the default IMA measurement template. + + The original 'ima' measurement list template contains a + hash, defined as 20 bytes, and a null terminated pathname, + limited to 255 characters. The 'ima-ng' measurement list + template permits both larger hash digests and longer + pathnames. + + config IMA_TEMPLATE + bool "ima" + config IMA_NG_TEMPLATE + bool "ima-ng (default)" + config IMA_SIG_TEMPLATE + bool "ima-sig" +endchoice + +config IMA_DEFAULT_TEMPLATE + string + depends on IMA + default "ima" if IMA_TEMPLATE + default "ima-ng" if IMA_NG_TEMPLATE + default "ima-sig" if IMA_SIG_TEMPLATE + +choice + prompt "Default integrity hash algorithm" + default IMA_DEFAULT_HASH_SHA1 + depends on IMA + help + Select the default hash algorithm used for the measurement + list, integrity appraisal and audit log. The compiled default + hash algorithm can be overwritten using the kernel command + line 'ima_hash=' option. + + config IMA_DEFAULT_HASH_SHA1 + bool "SHA1 (default)" + depends on CRYPTO_SHA1 + + config IMA_DEFAULT_HASH_SHA256 + bool "SHA256" + depends on CRYPTO_SHA256 && !IMA_TEMPLATE + + config IMA_DEFAULT_HASH_SHA512 + bool "SHA512" + depends on CRYPTO_SHA512 && !IMA_TEMPLATE + + config IMA_DEFAULT_HASH_WP512 + bool "WP512" + depends on CRYPTO_WP512 && !IMA_TEMPLATE +endchoice + +config IMA_DEFAULT_HASH + string + depends on IMA + default "sha1" if IMA_DEFAULT_HASH_SHA1 + default "sha256" if IMA_DEFAULT_HASH_SHA256 + default "sha512" if IMA_DEFAULT_HASH_SHA512 + default "wp512" if IMA_DEFAULT_HASH_WP512 + +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: + <http://linux-ima.sourceforge.net> + If unsure, say N. diff --git a/security/integrity/ima/Makefile b/security/integrity/ima/Makefile index 5690c021de8..d79263d2fdb 100644 --- a/security/integrity/ima/Makefile +++ b/security/integrity/ima/Makefile @@ -6,4 +6,5 @@ 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_audit.o + ima_policy.o ima_template.o ima_template_lib.o +ima-$(CONFIG_IMA_APPRAISE) += ima_appraise.o diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h index 3ccf7acac6d..f79fa8be203 100644 --- a/security/integrity/ima/ima.h +++ b/security/integrity/ima/ima.h @@ -26,7 +26,8 @@ #include "../integrity.h" -enum ima_show_type { IMA_SHOW_BINARY, IMA_SHOW_ASCII }; +enum ima_show_type { IMA_SHOW_BINARY, IMA_SHOW_BINARY_NO_FIELD_LEN, + IMA_SHOW_BINARY_OLD_STRING_FMT, IMA_SHOW_ASCII }; enum tpm_pcrs { TPM_PCR0 = 0, TPM_PCR8 = 8 }; /* digest size for IMA, fits SHA1 or MD5 */ @@ -36,22 +37,48 @@ enum tpm_pcrs { TPM_PCR0 = 0, TPM_PCR8 = 8 }; #define IMA_HASH_BITS 9 #define IMA_MEASURE_HTABLE_SIZE (1 << IMA_HASH_BITS) +#define IMA_TEMPLATE_FIELD_ID_MAX_LEN 16 +#define IMA_TEMPLATE_NUM_FIELDS_MAX 15 + +#define IMA_TEMPLATE_IMA_NAME "ima" +#define IMA_TEMPLATE_IMA_FMT "d|n" + /* set during initialization */ extern int ima_initialized; extern int ima_used_chip; -extern char *ima_hash; +extern int ima_hash_algo; +extern int ima_appraise; + +/* IMA template field data definition */ +struct ima_field_data { + u8 *data; + u32 len; +}; -/* IMA inode template definition */ -struct ima_template_data { - u8 digest[IMA_DIGEST_SIZE]; /* sha1/md5 measurement hash */ - char file_name[IMA_EVENT_NAME_LEN_MAX + 1]; /* name + \0 */ +/* IMA template field definition */ +struct ima_template_field { + const char field_id[IMA_TEMPLATE_FIELD_ID_MAX_LEN]; + int (*field_init) (struct integrity_iint_cache *iint, struct file *file, + const unsigned char *filename, + struct evm_ima_xattr_data *xattr_value, + int xattr_len, struct ima_field_data *field_data); + void (*field_show) (struct seq_file *m, enum ima_show_type show, + struct ima_field_data *field_data); +}; + +/* IMA template descriptor definition */ +struct ima_template_desc { + char *name; + char *fmt; + int num_fields; + struct ima_template_field **fields; }; struct ima_template_entry { - u8 digest[IMA_DIGEST_SIZE]; /* sha1 or md5 measurement hash */ - const char *template_name; - int template_len; - struct ima_template_data template; + u8 digest[TPM_DIGEST_SIZE]; /* sha1 or md5 measurement hash */ + struct ima_template_desc *template_desc; /* template descriptor */ + u32 template_data_len; + struct ima_field_data template_data[0]; /* template related data */ }; struct ima_queue_entry { @@ -61,11 +88,6 @@ struct ima_queue_entry { }; extern struct list_head ima_measurements; /* list of all measurements */ -/* declarations */ -void integrity_audit_msg(int audit_msgno, struct inode *inode, - const unsigned char *fname, const char *op, - const char *cause, int result, int info); - /* Internal IMA function definitions */ int ima_init(void); void ima_cleanup(void); @@ -73,12 +95,22 @@ int ima_fs_init(void); void ima_fs_cleanup(void); int ima_inode_alloc(struct inode *inode); int ima_add_template_entry(struct ima_template_entry *entry, int violation, - const char *op, struct inode *inode); -int ima_calc_hash(struct file *file, char *digest); -int ima_calc_template_hash(int template_len, void *template, char *digest); -int ima_calc_boot_aggregate(char *digest); -void ima_add_violation(struct inode *inode, const unsigned char *filename, + const char *op, struct inode *inode, + const unsigned char *filename); +int ima_calc_file_hash(struct file *file, struct ima_digest_data *hash); +int ima_calc_field_array_hash(struct ima_field_data *field_data, + struct ima_template_desc *desc, int num_fields, + struct ima_digest_data *hash); +int __init ima_calc_boot_aggregate(struct ima_digest_data *hash); +void ima_add_violation(struct file *file, const unsigned char *filename, const char *op, const char *cause); +int ima_init_crypto(void); +void ima_putc(struct seq_file *m, void *data, int datalen); +void ima_print_digest(struct seq_file *m, u8 *digest, int size); +struct ima_template_desc *ima_template_desc_current(void); +int ima_init_template(void); + +int ima_init_template(void); /* * used to protect h_table and sha_table @@ -98,14 +130,26 @@ static inline unsigned long ima_hash_key(u8 *digest) } /* LIM API function definitions */ +int ima_get_action(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); + struct file *file, + struct evm_ima_xattr_data **xattr_value, + int *xattr_len); void ima_store_measurement(struct integrity_iint_cache *iint, struct file *file, + const unsigned char *filename, + struct evm_ima_xattr_data *xattr_value, + int xattr_len); +void ima_audit_measurement(struct integrity_iint_cache *iint, const unsigned char *filename); +int ima_alloc_init_template(struct integrity_iint_cache *iint, + struct file *file, const unsigned char *filename, + struct evm_ima_xattr_data *xattr_value, + int xattr_len, struct ima_template_entry **entry); 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); + struct inode *inode, const unsigned char *filename); +void ima_free_template_entry(struct ima_template_entry *entry); +const char *ima_d_path(struct path *path, char **pathbuf); /* rbtree tree calls to lookup, insert, delete * integrity data associated with an inode. @@ -114,14 +158,76 @@ 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, MMAP_CHECK, BPRM_CHECK, MODULE_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 +#define IMA_APPRAISE_MODULES 0x04 + +#ifdef CONFIG_IMA_APPRAISE +int ima_appraise_measurement(int func, struct integrity_iint_cache *iint, + struct file *file, const unsigned char *filename, + struct evm_ima_xattr_data *xattr_value, + int xattr_len); +int ima_must_appraise(struct inode *inode, int mask, enum ima_hooks func); +void ima_update_xattr(struct integrity_iint_cache *iint, struct file *file); +enum integrity_status ima_get_cache_status(struct integrity_iint_cache *iint, + int func); +void ima_get_hash_algo(struct evm_ima_xattr_data *xattr_value, int xattr_len, + struct ima_digest_data *hash); +int ima_read_xattr(struct dentry *dentry, + struct evm_ima_xattr_data **xattr_value); + +#else +static inline int ima_appraise_measurement(int func, + struct integrity_iint_cache *iint, + struct file *file, + const unsigned char *filename, + struct evm_ima_xattr_data *xattr_value, + int xattr_len) +{ + return INTEGRITY_UNKNOWN; +} + +static inline int ima_must_appraise(struct inode *inode, int mask, + enum ima_hooks func) +{ + return 0; +} + +static inline void ima_update_xattr(struct integrity_iint_cache *iint, + struct file *file) +{ +} + +static inline enum integrity_status ima_get_cache_status(struct integrity_iint_cache + *iint, int func) +{ + return INTEGRITY_UNKNOWN; +} + +static inline void ima_get_hash_algo(struct evm_ima_xattr_data *xattr_value, + int xattr_len, + struct ima_digest_data *hash) +{ +} + +static inline int ima_read_xattr(struct dentry *dentry, + struct evm_ima_xattr_data **xattr_value) +{ + return 0; +} + +#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 88a2788b981..d9cd5ce14d2 100644 --- a/security/integrity/ima/ima_api.c +++ b/security/integrity/ima/ima_api.c @@ -9,14 +9,68 @@ * 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 <linux/module.h> #include <linux/slab.h> - +#include <linux/file.h> +#include <linux/fs.h> +#include <linux/xattr.h> +#include <linux/evm.h> +#include <crypto/hash_info.h> #include "ima.h" -static const char *IMA_TEMPLATE_NAME = "ima"; + +/* + * ima_free_template_entry - free an existing template entry + */ +void ima_free_template_entry(struct ima_template_entry *entry) +{ + int i; + + for (i = 0; i < entry->template_desc->num_fields; i++) + kfree(entry->template_data[i].data); + + kfree(entry); +} + +/* + * ima_alloc_init_template - create and initialize a new template entry + */ +int ima_alloc_init_template(struct integrity_iint_cache *iint, + struct file *file, const unsigned char *filename, + struct evm_ima_xattr_data *xattr_value, + int xattr_len, struct ima_template_entry **entry) +{ + struct ima_template_desc *template_desc = ima_template_desc_current(); + int i, result = 0; + + *entry = kzalloc(sizeof(**entry) + template_desc->num_fields * + sizeof(struct ima_field_data), GFP_NOFS); + if (!*entry) + return -ENOMEM; + + (*entry)->template_desc = template_desc; + for (i = 0; i < template_desc->num_fields; i++) { + struct ima_template_field *field = template_desc->fields[i]; + u32 len; + + result = field->field_init(iint, file, filename, + xattr_value, xattr_len, + &((*entry)->template_data[i])); + if (result != 0) + goto out; + + len = (*entry)->template_data[i].len; + (*entry)->template_data_len += sizeof(len); + (*entry)->template_data_len += len; + } + return 0; +out: + ima_free_template_entry(*entry); + *entry = NULL; + return result; +} /* * ima_store_template - store ima template measurements @@ -35,28 +89,35 @@ static const char *IMA_TEMPLATE_NAME = "ima"; * Returns 0 on success, error code otherwise */ int ima_store_template(struct ima_template_entry *entry, - int violation, struct inode *inode) + int violation, struct inode *inode, + const unsigned char *filename) { - const char *op = "add_template_measure"; - const char *audit_cause = "hashing_error"; + static const char op[] = "add_template_measure"; + static const char audit_cause[] = "hashing_error"; + char *template_name = entry->template_desc->name; int result; - - memset(entry->digest, 0, sizeof(entry->digest)); - entry->template_name = IMA_TEMPLATE_NAME; - entry->template_len = sizeof(entry->template); + struct { + struct ima_digest_data hdr; + char digest[TPM_DIGEST_SIZE]; + } hash; if (!violation) { - result = ima_calc_template_hash(entry->template_len, - &entry->template, - entry->digest); + int num_fields = entry->template_desc->num_fields; + + /* this function uses default algo */ + hash.hdr.algo = HASH_ALGO_SHA1; + result = ima_calc_field_array_hash(&entry->template_data[0], + entry->template_desc, + num_fields, &hash.hdr); if (result < 0) { integrity_audit_msg(AUDIT_INTEGRITY_PCR, inode, - entry->template_name, op, + template_name, op, audit_cause, result, 0); return result; } + memcpy(entry->digest, hash.hdr.digest, hash.hdr.length); } - result = ima_add_template_entry(entry, violation, op, inode); + result = ima_add_template_entry(entry, violation, op, inode, filename); return result; } @@ -67,53 +128,60 @@ int ima_store_template(struct ima_template_entry *entry, * By extending the PCR with 0xFF's instead of with zeroes, the PCR * value is invalidated. */ -void ima_add_violation(struct inode *inode, const unsigned char *filename, +void ima_add_violation(struct file *file, const unsigned char *filename, const char *op, const char *cause) { struct ima_template_entry *entry; + struct inode *inode = file_inode(file); int violation = 1; int result; /* can overflow, only indicator */ atomic_long_inc(&ima_htable.violations); - entry = kmalloc(sizeof(*entry), GFP_KERNEL); - if (!entry) { + result = ima_alloc_init_template(NULL, file, filename, + NULL, 0, &entry); + if (result < 0) { result = -ENOMEM; goto err_out; } - memset(&entry->template, 0, sizeof(entry->template)); - strncpy(entry->template.file_name, filename, IMA_EVENT_NAME_LEN_MAX); - result = ima_store_template(entry, violation, inode); + result = ima_store_template(entry, violation, inode, filename); if (result < 0) - kfree(entry); + ima_free_template_entry(entry); err_out: integrity_audit_msg(AUDIT_INTEGRITY_PCR, inode, filename, op, cause, result, 0); } /** - * ima_must_measure - measure decision based on policy. + * ima_get_action - 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) + * @function: calling function (FILE_CHECK, BPRM_CHECK, MMAP_CHECK, MODULE_CHECK) * * The policy is defined in terms of keypairs: - * subj=, obj=, type=, func=, mask=, fsmagic= + * subj=, obj=, type=, func=, mask=, fsmagic= * subj,obj, and type: are LSM specific. - * func: FILE_CHECK | BPRM_CHECK | FILE_MMAP - * mask: contains the permission mask + * func: FILE_CHECK | BPRM_CHECK | MMAP_CHECK | MODULE_CHECK + * 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. -*/ -int ima_must_measure(struct inode *inode, int mask, int function) + * Returns IMA_MEASURE, IMA_APPRAISE mask. + * + */ +int ima_get_action(struct inode *inode, int mask, int function) { - int must_measure; + int flags = IMA_MEASURE | IMA_AUDIT | IMA_APPRAISE; + + if (!ima_appraise) + flags &= ~IMA_APPRAISE; + + return ima_match_policy(inode, function, mask, flags); +} - must_measure = ima_match_policy(inode, function, mask); - return must_measure ? 0 : -EACCES; +int ima_must_measure(struct inode *inode, int mask, int function) +{ + return ima_match_policy(inode, function, mask, IMA_MEASURE); } /* @@ -127,18 +195,56 @@ int ima_must_measure(struct inode *inode, int mask, int function) * Return 0 on success, error code otherwise */ int ima_collect_measurement(struct integrity_iint_cache *iint, - struct file *file) + struct file *file, + struct evm_ima_xattr_data **xattr_value, + int *xattr_len) { - int result = -EEXIST; + const char *audit_cause = "failed"; + struct inode *inode = file_inode(file); + const char *filename = file->f_dentry->d_name.name; + int result = 0; + struct { + struct ima_digest_data hdr; + char digest[IMA_MAX_DIGEST_SIZE]; + } hash; + + if (xattr_value) + *xattr_len = ima_read_xattr(file->f_dentry, xattr_value); + + if (!(iint->flags & IMA_COLLECTED)) { + u64 i_version = file_inode(file)->i_version; + + if (file->f_flags & O_DIRECT) { + audit_cause = "failed(directio)"; + result = -EACCES; + goto out; + } + + /* use default hash algorithm */ + hash.hdr.algo = ima_hash_algo; - if (!(iint->flags & IMA_MEASURED)) { - u64 i_version = file->f_dentry->d_inode->i_version; + if (xattr_value) + ima_get_hash_algo(*xattr_value, *xattr_len, &hash.hdr); - memset(iint->digest, 0, IMA_DIGEST_SIZE); - result = ima_calc_hash(file, iint->digest); - if (!result) - iint->version = i_version; + result = ima_calc_file_hash(file, &hash.hdr); + if (!result) { + int length = sizeof(hash.hdr) + hash.hdr.length; + void *tmpbuf = krealloc(iint->ima_hash, length, + GFP_NOFS); + if (tmpbuf) { + iint->ima_hash = tmpbuf; + memcpy(iint->ima_hash, &hash, length); + iint->version = i_version; + iint->flags |= IMA_COLLECTED; + } else + result = -ENOMEM; + } } +out: + if (result) + integrity_audit_msg(AUDIT_INTEGRITY_DATA, inode, + filename, "collect_data", audit_cause, + result, 0); return result; } @@ -150,7 +256,7 @@ int ima_collect_measurement(struct integrity_iint_cache *iint, * * We only get here if the inode has not already been measured, * but the measurement could already exist: - * - multiple copies of the same file on either the same or + * - multiple copies of the same file on either the same or * different filesystems. * - the inode was previously flushed as well as the iint info, * containing the hashing info. @@ -158,28 +264,81 @@ int ima_collect_measurement(struct integrity_iint_cache *iint, * Must be called with iint->mutex held. */ void ima_store_measurement(struct integrity_iint_cache *iint, - struct file *file, const unsigned char *filename) + struct file *file, const unsigned char *filename, + struct evm_ima_xattr_data *xattr_value, + int xattr_len) { - const char *op = "add_template_measure"; - const char *audit_cause = "ENOMEM"; + static const char op[] = "add_template_measure"; + static const char audit_cause[] = "ENOMEM"; int result = -ENOMEM; - struct inode *inode = file->f_dentry->d_inode; + struct inode *inode = file_inode(file); struct ima_template_entry *entry; int violation = 0; - entry = kmalloc(sizeof(*entry), GFP_KERNEL); - if (!entry) { + if (iint->flags & IMA_MEASURED) + return; + + result = ima_alloc_init_template(iint, file, filename, + xattr_value, xattr_len, &entry); + if (result < 0) { integrity_audit_msg(AUDIT_INTEGRITY_PCR, inode, filename, op, audit_cause, result, 0); return; } - memset(&entry->template, 0, sizeof(entry->template)); - memcpy(entry->template.digest, iint->digest, IMA_DIGEST_SIZE); - strncpy(entry->template.file_name, filename, IMA_EVENT_NAME_LEN_MAX); - result = ima_store_template(entry, violation, inode); + result = ima_store_template(entry, violation, inode, filename); if (!result || result == -EEXIST) iint->flags |= IMA_MEASURED; if (result < 0) - kfree(entry); + ima_free_template_entry(entry); +} + +void ima_audit_measurement(struct integrity_iint_cache *iint, + const unsigned char *filename) +{ + struct audit_buffer *ab; + char hash[(iint->ima_hash->length * 2) + 1]; + const char *algo_name = hash_algo_name[iint->ima_hash->algo]; + char algo_hash[sizeof(hash) + strlen(algo_name) + 2]; + int i; + + if (iint->flags & IMA_AUDITED) + return; + + for (i = 0; i < iint->ima_hash->length; i++) + hex_byte_pack(hash + (i * 2), iint->ima_hash->digest[i]); + hash[i * 2] = '\0'; + + ab = audit_log_start(current->audit_context, GFP_KERNEL, + AUDIT_INTEGRITY_RULE); + if (!ab) + return; + + audit_log_format(ab, "file="); + audit_log_untrustedstring(ab, filename); + audit_log_format(ab, " hash="); + snprintf(algo_hash, sizeof(algo_hash), "%s:%s", algo_name, hash); + audit_log_untrustedstring(ab, algo_hash); + + audit_log_task_info(ab, current); + audit_log_end(ab); + + iint->flags |= IMA_AUDITED; +} + +const char *ima_d_path(struct path *path, char **pathbuf) +{ + char *pathname = NULL; + + /* We will allow 11 spaces for ' (deleted)' to be appended */ + *pathbuf = kmalloc(PATH_MAX + 11, GFP_KERNEL); + if (*pathbuf) { + pathname = d_path(path, *pathbuf, PATH_MAX + 11); + if (IS_ERR(pathname)) { + kfree(*pathbuf); + *pathbuf = NULL; + pathname = NULL; + } + } + return pathname ?: (const char *)path->dentry->d_name.name; } diff --git a/security/integrity/ima/ima_appraise.c b/security/integrity/ima/ima_appraise.c new file mode 100644 index 00000000000..d3113d4aaa3 --- /dev/null +++ b/security/integrity/ima/ima_appraise.c @@ -0,0 +1,387 @@ +/* + * 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/file.h> +#include <linux/fs.h> +#include <linux/xattr.h> +#include <linux/magic.h> +#include <linux/ima.h> +#include <linux/evm.h> +#include <crypto/hash_info.h> + +#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, int mask, enum ima_hooks func) +{ + if (!ima_appraise) + return 0; + + return ima_match_policy(inode, func, mask, IMA_APPRAISE); +} + +static int ima_fix_xattr(struct dentry *dentry, + struct integrity_iint_cache *iint) +{ + int rc, offset; + u8 algo = iint->ima_hash->algo; + + if (algo <= HASH_ALGO_SHA1) { + offset = 1; + iint->ima_hash->xattr.sha1.type = IMA_XATTR_DIGEST; + } else { + offset = 0; + iint->ima_hash->xattr.ng.type = IMA_XATTR_DIGEST_NG; + iint->ima_hash->xattr.ng.algo = algo; + } + rc = __vfs_setxattr_noperm(dentry, XATTR_NAME_IMA, + &iint->ima_hash->xattr.data[offset], + (sizeof(iint->ima_hash->xattr) - offset) + + iint->ima_hash->length, 0); + return rc; +} + +/* Return specific func appraised cached result */ +enum integrity_status ima_get_cache_status(struct integrity_iint_cache *iint, + int func) +{ + switch (func) { + case MMAP_CHECK: + return iint->ima_mmap_status; + case BPRM_CHECK: + return iint->ima_bprm_status; + case MODULE_CHECK: + return iint->ima_module_status; + case FILE_CHECK: + default: + return iint->ima_file_status; + } +} + +static void ima_set_cache_status(struct integrity_iint_cache *iint, + int func, enum integrity_status status) +{ + switch (func) { + case MMAP_CHECK: + iint->ima_mmap_status = status; + break; + case BPRM_CHECK: + iint->ima_bprm_status = status; + break; + case MODULE_CHECK: + iint->ima_module_status = status; + break; + case FILE_CHECK: + default: + iint->ima_file_status = status; + break; + } +} + +static void ima_cache_flags(struct integrity_iint_cache *iint, int func) +{ + switch (func) { + case MMAP_CHECK: + iint->flags |= (IMA_MMAP_APPRAISED | IMA_APPRAISED); + break; + case BPRM_CHECK: + iint->flags |= (IMA_BPRM_APPRAISED | IMA_APPRAISED); + break; + case MODULE_CHECK: + iint->flags |= (IMA_MODULE_APPRAISED | IMA_APPRAISED); + break; + case FILE_CHECK: + default: + iint->flags |= (IMA_FILE_APPRAISED | IMA_APPRAISED); + break; + } +} + +void ima_get_hash_algo(struct evm_ima_xattr_data *xattr_value, int xattr_len, + struct ima_digest_data *hash) +{ + struct signature_v2_hdr *sig; + + if (!xattr_value || xattr_len < 2) + return; + + switch (xattr_value->type) { + case EVM_IMA_XATTR_DIGSIG: + sig = (typeof(sig))xattr_value; + if (sig->version != 2 || xattr_len <= sizeof(*sig)) + return; + hash->algo = sig->hash_algo; + break; + case IMA_XATTR_DIGEST_NG: + hash->algo = xattr_value->digest[0]; + break; + case IMA_XATTR_DIGEST: + /* this is for backward compatibility */ + if (xattr_len == 21) { + unsigned int zero = 0; + if (!memcmp(&xattr_value->digest[16], &zero, 4)) + hash->algo = HASH_ALGO_MD5; + else + hash->algo = HASH_ALGO_SHA1; + } else if (xattr_len == 17) + hash->algo = HASH_ALGO_MD5; + break; + } +} + +int ima_read_xattr(struct dentry *dentry, + struct evm_ima_xattr_data **xattr_value) +{ + struct inode *inode = dentry->d_inode; + + if (!inode->i_op->getxattr) + return 0; + + return vfs_getxattr_alloc(dentry, XATTR_NAME_IMA, (char **)xattr_value, + 0, GFP_NOFS); +} + +/* + * 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(int func, struct integrity_iint_cache *iint, + struct file *file, const unsigned char *filename, + struct evm_ima_xattr_data *xattr_value, + int xattr_len) +{ + static const char op[] = "appraise_data"; + char *cause = "unknown"; + struct dentry *dentry = file->f_dentry; + struct inode *inode = dentry->d_inode; + enum integrity_status status = INTEGRITY_UNKNOWN; + int rc = xattr_len, hash_start = 0; + + if (!ima_appraise) + return 0; + if (!inode->i_op->getxattr) + return INTEGRITY_UNKNOWN; + + 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; + } + switch (xattr_value->type) { + case IMA_XATTR_DIGEST_NG: + /* first byte contains algorithm id */ + hash_start = 1; + case IMA_XATTR_DIGEST: + if (iint->flags & IMA_DIGSIG_REQUIRED) { + cause = "IMA signature required"; + status = INTEGRITY_FAIL; + break; + } + if (xattr_len - sizeof(xattr_value->type) - hash_start >= + iint->ima_hash->length) + /* xattr length may be longer. md5 hash in previous + version occupied 20 bytes in xattr, instead of 16 + */ + rc = memcmp(&xattr_value->digest[hash_start], + iint->ima_hash->digest, + iint->ima_hash->length); + else + rc = -EINVAL; + if (rc) { + cause = "invalid-hash"; + status = INTEGRITY_FAIL; + break; + } + status = INTEGRITY_PASS; + break; + case EVM_IMA_XATTR_DIGSIG: + iint->flags |= IMA_DIGSIG; + rc = integrity_digsig_verify(INTEGRITY_KEYRING_IMA, + (const char *)xattr_value, rc, + iint->ima_hash->digest, + iint->ima_hash->length); + if (rc == -EOPNOTSUPP) { + status = INTEGRITY_UNKNOWN; + } else if (rc) { + cause = "invalid-signature"; + status = INTEGRITY_FAIL; + } else { + status = INTEGRITY_PASS; + } + break; + default: + status = INTEGRITY_UNKNOWN; + cause = "unknown-ima-data"; + break; + } + +out: + if (status != INTEGRITY_PASS) { + if ((ima_appraise & IMA_APPRAISE_FIX) && + (!xattr_value || + xattr_value->type != EVM_IMA_XATTR_DIGSIG)) { + if (!ima_fix_xattr(dentry, iint)) + status = INTEGRITY_PASS; + } + integrity_audit_msg(AUDIT_INTEGRITY_DATA, inode, filename, + op, cause, rc, 0); + } else { + ima_cache_flags(iint, func); + } + ima_set_cache_status(iint, func, 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; + + /* do not collect and update hash for digital signatures */ + if (iint->flags & IMA_DIGSIG) + return; + + rc = ima_collect_measurement(iint, file, NULL, NULL); + 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) { + iint->flags &= ~(IMA_APPRAISE | IMA_APPRAISED | + IMA_APPRAISE_SUBMASK | IMA_APPRAISED_SUBMASK | + IMA_ACTION_FLAGS); + if (must_appraise) + iint->flags |= IMA_APPRAISE; + } + if (!must_appraise) + rc = inode->i_op->removexattr(dentry, XATTR_NAME_IMA); + return; +} + +/* + * ima_protect_xattr - protect 'security.ima' + * + * Ensure that not just anyone can modify or remove 'security.ima'. + */ +static int ima_protect_xattr(struct dentry *dentry, const char *xattr_name, + const void *xattr_value, size_t xattr_value_len) +{ + if (strcmp(xattr_name, XATTR_NAME_IMA) == 0) { + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + return 1; + } + return 0; +} + +static void ima_reset_appraise_flags(struct inode *inode, int digsig) +{ + struct integrity_iint_cache *iint; + + if (!ima_initialized || !ima_appraise || !S_ISREG(inode->i_mode)) + return; + + iint = integrity_iint_find(inode); + if (!iint) + return; + + iint->flags &= ~IMA_DONE_MASK; + if (digsig) + iint->flags |= IMA_DIGSIG; + return; +} + +int ima_inode_setxattr(struct dentry *dentry, const char *xattr_name, + const void *xattr_value, size_t xattr_value_len) +{ + const struct evm_ima_xattr_data *xvalue = xattr_value; + int result; + + result = ima_protect_xattr(dentry, xattr_name, xattr_value, + xattr_value_len); + if (result == 1) { + ima_reset_appraise_flags(dentry->d_inode, + (xvalue->type == EVM_IMA_XATTR_DIGSIG) ? 1 : 0); + result = 0; + } + return result; +} + +int ima_inode_removexattr(struct dentry *dentry, const char *xattr_name) +{ + int result; + + result = ima_protect_xattr(dentry, xattr_name, NULL, 0); + if (result == 1) { + ima_reset_appraise_flags(dentry->d_inode, 0); + result = 0; + } + return result; +} diff --git a/security/integrity/ima/ima_audit.c b/security/integrity/ima/ima_audit.c deleted file mode 100644 index 2ad942fb1e2..00000000000 --- a/security/integrity/ima/ima_audit.c +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (C) 2008 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. - * - * File: integrity_audit.c - * Audit calls for the integrity subsystem - */ - -#include <linux/fs.h> -#include <linux/gfp.h> -#include <linux/audit.h> -#include "ima.h" - -static int ima_audit; - -#ifdef CONFIG_IMA_AUDIT - -/* ima_audit_setup - enable informational auditing messages */ -static int __init ima_audit_setup(char *str) -{ - unsigned long audit; - - if (!strict_strtoul(str, 0, &audit)) - ima_audit = audit ? 1 : 0; - return 1; -} -__setup("ima_audit=", ima_audit_setup); -#endif - -void integrity_audit_msg(int audit_msgno, struct inode *inode, - const unsigned char *fname, const char *op, - const char *cause, int result, int audit_info) -{ - struct audit_buffer *ab; - - if (!ima_audit && audit_info == 1) /* Skip informational messages */ - return; - - ab = audit_log_start(current->audit_context, GFP_KERNEL, audit_msgno); - audit_log_format(ab, "pid=%d uid=%u auid=%u ses=%u", - current->pid, current_cred()->uid, - audit_get_loginuid(current), - audit_get_sessionid(current)); - audit_log_task_context(ab); - audit_log_format(ab, " op="); - audit_log_string(ab, op); - audit_log_format(ab, " cause="); - audit_log_string(ab, cause); - audit_log_format(ab, " comm="); - audit_log_untrustedstring(ab, current->comm); - if (fname) { - audit_log_format(ab, " name="); - audit_log_untrustedstring(ab, fname); - } - if (inode) { - audit_log_format(ab, " dev="); - audit_log_untrustedstring(ab, inode->i_sb->s_id); - audit_log_format(ab, " ino=%lu", inode->i_ino); - } - audit_log_format(ab, " res=%d", !result ? 0 : 1); - audit_log_end(ab); -} diff --git a/security/integrity/ima/ima_crypto.c b/security/integrity/ima/ima_crypto.c index 9b3ade7468b..ccd0ac8fa9a 100644 --- a/security/integrity/ima/ima_crypto.c +++ b/security/integrity/ima/ima_crypto.c @@ -10,60 +10,131 @@ * the Free Software Foundation, version 2 of the License. * * File: ima_crypto.c - * Calculates md5/sha1 file hash, template hash, boot-aggreate hash + * Calculates md5/sha1 file hash, template hash, boot-aggreate hash */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + #include <linux/kernel.h> #include <linux/file.h> #include <linux/crypto.h> #include <linux/scatterlist.h> #include <linux/err.h> #include <linux/slab.h> +#include <crypto/hash.h> +#include <crypto/hash_info.h> #include "ima.h" -static int init_desc(struct hash_desc *desc) +static struct crypto_shash *ima_shash_tfm; + +/** + * ima_kernel_read - read file content + * + * This is a function for reading file content instead of kernel_read(). + * It does not perform locking checks to ensure it cannot be blocked. + * It does not perform security checks because it is irrelevant for IMA. + * + */ +static int ima_kernel_read(struct file *file, loff_t offset, + char *addr, unsigned long count) { - int rc; + mm_segment_t old_fs; + char __user *buf = addr; + ssize_t ret; + + if (!(file->f_mode & FMODE_READ)) + return -EBADF; + if (!file->f_op->read && !file->f_op->aio_read) + return -EINVAL; + + old_fs = get_fs(); + set_fs(get_ds()); + if (file->f_op->read) + ret = file->f_op->read(file, buf, count, &offset); + else + ret = do_sync_read(file, buf, count, &offset); + set_fs(old_fs); + return ret; +} - desc->tfm = crypto_alloc_hash(ima_hash, 0, CRYPTO_ALG_ASYNC); - if (IS_ERR(desc->tfm)) { - pr_info("IMA: failed to load %s transform: %ld\n", - ima_hash, PTR_ERR(desc->tfm)); - rc = PTR_ERR(desc->tfm); +int ima_init_crypto(void) +{ + long rc; + + ima_shash_tfm = crypto_alloc_shash(hash_algo_name[ima_hash_algo], 0, 0); + if (IS_ERR(ima_shash_tfm)) { + rc = PTR_ERR(ima_shash_tfm); + pr_err("Can not allocate %s (reason: %ld)\n", + hash_algo_name[ima_hash_algo], rc); return rc; } - desc->flags = 0; - rc = crypto_hash_init(desc); - if (rc) - crypto_free_hash(desc->tfm); - return rc; + return 0; +} + +static struct crypto_shash *ima_alloc_tfm(enum hash_algo algo) +{ + struct crypto_shash *tfm = ima_shash_tfm; + int rc; + + if (algo != ima_hash_algo && algo < HASH_ALGO__LAST) { + tfm = crypto_alloc_shash(hash_algo_name[algo], 0, 0); + if (IS_ERR(tfm)) { + rc = PTR_ERR(tfm); + pr_err("Can not allocate %s (reason: %d)\n", + hash_algo_name[algo], rc); + } + } + return tfm; +} + +static void ima_free_tfm(struct crypto_shash *tfm) +{ + if (tfm != ima_shash_tfm) + crypto_free_shash(tfm); } /* * Calculate the MD5/SHA1 file digest */ -int ima_calc_hash(struct file *file, char *digest) +static int ima_calc_file_hash_tfm(struct file *file, + struct ima_digest_data *hash, + struct crypto_shash *tfm) { - struct hash_desc desc; - struct scatterlist sg[1]; loff_t i_size, offset = 0; char *rbuf; - int rc; + int rc, read = 0; + struct { + struct shash_desc shash; + char ctx[crypto_shash_descsize(tfm)]; + } desc; - rc = init_desc(&desc); + desc.shash.tfm = tfm; + desc.shash.flags = 0; + + hash->length = crypto_shash_digestsize(tfm); + + rc = crypto_shash_init(&desc.shash); if (rc != 0) return rc; - rbuf = kzalloc(PAGE_SIZE, GFP_KERNEL); - if (!rbuf) { - rc = -ENOMEM; + i_size = i_size_read(file_inode(file)); + + if (i_size == 0) goto out; + + rbuf = kzalloc(PAGE_SIZE, GFP_KERNEL); + if (!rbuf) + return -ENOMEM; + + 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; - rbuf_len = kernel_read(file, offset, rbuf, PAGE_SIZE); + rbuf_len = ima_kernel_read(file, offset, rbuf, PAGE_SIZE); if (rbuf_len < 0) { rc = rbuf_len; break; @@ -71,38 +142,103 @@ int ima_calc_hash(struct file *file, char *digest) if (rbuf_len == 0) break; offset += rbuf_len; - sg_init_one(sg, rbuf, rbuf_len); - rc = crypto_hash_update(&desc, sg, rbuf_len); + rc = crypto_shash_update(&desc.shash, rbuf, rbuf_len); if (rc) break; } + if (read) + file->f_mode &= ~FMODE_READ; kfree(rbuf); - if (!rc) - rc = crypto_hash_final(&desc, digest); out: - crypto_free_hash(desc.tfm); + if (!rc) + rc = crypto_shash_final(&desc.shash, hash->digest); + return rc; +} + +int ima_calc_file_hash(struct file *file, struct ima_digest_data *hash) +{ + struct crypto_shash *tfm; + int rc; + + tfm = ima_alloc_tfm(hash->algo); + if (IS_ERR(tfm)) + return PTR_ERR(tfm); + + rc = ima_calc_file_hash_tfm(file, hash, tfm); + + ima_free_tfm(tfm); + return rc; } /* - * Calculate the hash of a given template + * Calculate the hash of template data */ -int ima_calc_template_hash(int template_len, void *template, char *digest) +static int ima_calc_field_array_hash_tfm(struct ima_field_data *field_data, + struct ima_template_desc *td, + int num_fields, + struct ima_digest_data *hash, + struct crypto_shash *tfm) { - struct hash_desc desc; - struct scatterlist sg[1]; - int rc; + struct { + struct shash_desc shash; + char ctx[crypto_shash_descsize(tfm)]; + } desc; + int rc, i; + + desc.shash.tfm = tfm; + desc.shash.flags = 0; - rc = init_desc(&desc); + hash->length = crypto_shash_digestsize(tfm); + + rc = crypto_shash_init(&desc.shash); if (rc != 0) return rc; - sg_init_one(sg, template, template_len); - rc = crypto_hash_update(&desc, sg, template_len); + for (i = 0; i < num_fields; i++) { + u8 buffer[IMA_EVENT_NAME_LEN_MAX + 1] = { 0 }; + u8 *data_to_hash = field_data[i].data; + u32 datalen = field_data[i].len; + + if (strcmp(td->name, IMA_TEMPLATE_IMA_NAME) != 0) { + rc = crypto_shash_update(&desc.shash, + (const u8 *) &field_data[i].len, + sizeof(field_data[i].len)); + if (rc) + break; + } else if (strcmp(td->fields[i]->field_id, "n") == 0) { + memcpy(buffer, data_to_hash, datalen); + data_to_hash = buffer; + datalen = IMA_EVENT_NAME_LEN_MAX + 1; + } + rc = crypto_shash_update(&desc.shash, data_to_hash, datalen); + if (rc) + break; + } + if (!rc) - rc = crypto_hash_final(&desc, digest); - crypto_free_hash(desc.tfm); + rc = crypto_shash_final(&desc.shash, hash->digest); + + return rc; +} + +int ima_calc_field_array_hash(struct ima_field_data *field_data, + struct ima_template_desc *desc, int num_fields, + struct ima_digest_data *hash) +{ + struct crypto_shash *tfm; + int rc; + + tfm = ima_alloc_tfm(hash->algo); + if (IS_ERR(tfm)) + return PTR_ERR(tfm); + + rc = ima_calc_field_array_hash_tfm(field_data, desc, num_fields, + hash, tfm); + + ima_free_tfm(tfm); + return rc; } @@ -112,20 +248,26 @@ static void __init ima_pcrread(int idx, u8 *pcr) return; if (tpm_pcr_read(TPM_ANY_NUM, idx, pcr) != 0) - pr_err("IMA: Error Communicating to TPM chip\n"); + pr_err("Error Communicating to TPM chip\n"); } /* * Calculate the boot aggregate hash */ -int __init ima_calc_boot_aggregate(char *digest) +static int __init ima_calc_boot_aggregate_tfm(char *digest, + struct crypto_shash *tfm) { - struct hash_desc desc; - struct scatterlist sg; - u8 pcr_i[IMA_DIGEST_SIZE]; + u8 pcr_i[TPM_DIGEST_SIZE]; int rc, i; + struct { + struct shash_desc shash; + char ctx[crypto_shash_descsize(tfm)]; + } desc; + + desc.shash.tfm = tfm; + desc.shash.flags = 0; - rc = init_desc(&desc); + rc = crypto_shash_init(&desc.shash); if (rc != 0) return rc; @@ -133,11 +275,26 @@ int __init ima_calc_boot_aggregate(char *digest) for (i = TPM_PCR0; i < TPM_PCR8; i++) { ima_pcrread(i, pcr_i); /* now accumulate with current aggregate */ - sg_init_one(&sg, pcr_i, IMA_DIGEST_SIZE); - rc = crypto_hash_update(&desc, &sg, IMA_DIGEST_SIZE); + rc = crypto_shash_update(&desc.shash, pcr_i, TPM_DIGEST_SIZE); } if (!rc) - crypto_hash_final(&desc, digest); - crypto_free_hash(desc.tfm); + crypto_shash_final(&desc.shash, digest); + return rc; +} + +int __init ima_calc_boot_aggregate(struct ima_digest_data *hash) +{ + struct crypto_shash *tfm; + int rc; + + tfm = ima_alloc_tfm(hash->algo); + if (IS_ERR(tfm)) + return PTR_ERR(tfm); + + hash->length = crypto_shash_digestsize(tfm); + rc = ima_calc_boot_aggregate_tfm(hash->digest, tfm); + + ima_free_tfm(tfm); + return rc; } diff --git a/security/integrity/ima/ima_fs.c b/security/integrity/ima/ima_fs.c index e1aa2b482dd..da92fcc08d1 100644 --- a/security/integrity/ima/ima_fs.c +++ b/security/integrity/ima/ima_fs.c @@ -88,8 +88,7 @@ static void *ima_measurements_next(struct seq_file *m, void *v, loff_t *pos) * against concurrent list-extension */ rcu_read_lock(); - qe = list_entry_rcu(qe->later.next, - struct ima_queue_entry, later); + qe = list_entry_rcu(qe->later.next, struct ima_queue_entry, later); rcu_read_unlock(); (*pos)++; @@ -100,7 +99,7 @@ static void ima_measurements_stop(struct seq_file *m, void *v) { } -static void ima_putc(struct seq_file *m, void *data, int datalen) +void ima_putc(struct seq_file *m, void *data, int datalen) { while (datalen--) seq_putc(m, *(char *)data++); @@ -111,6 +110,7 @@ static void ima_putc(struct seq_file *m, void *data, int datalen) * char[20]=template digest * 32bit-le=template name size * char[n]=template name + * [eventdata length] * eventdata[n]=template specific data */ static int ima_measurements_show(struct seq_file *m, void *v) @@ -120,6 +120,8 @@ static int ima_measurements_show(struct seq_file *m, void *v) struct ima_template_entry *e; int namelen; u32 pcr = CONFIG_IMA_MEASURE_PCR_IDX; + bool is_ima_template = false; + int i; /* get entry */ e = qe->entry; @@ -131,21 +133,37 @@ static int ima_measurements_show(struct seq_file *m, void *v) * PCR used is always the same (config option) in * little-endian format */ - ima_putc(m, &pcr, sizeof pcr); + ima_putc(m, &pcr, sizeof(pcr)); /* 2nd: template digest */ - ima_putc(m, e->digest, IMA_DIGEST_SIZE); + ima_putc(m, e->digest, TPM_DIGEST_SIZE); /* 3rd: template name size */ - namelen = strlen(e->template_name); - ima_putc(m, &namelen, sizeof namelen); + namelen = strlen(e->template_desc->name); + ima_putc(m, &namelen, sizeof(namelen)); /* 4th: template name */ - ima_putc(m, (void *)e->template_name, namelen); - - /* 5th: template specific data */ - ima_template_show(m, (struct ima_template_data *)&e->template, - IMA_SHOW_BINARY); + ima_putc(m, e->template_desc->name, namelen); + + /* 5th: template length (except for 'ima' template) */ + if (strcmp(e->template_desc->name, IMA_TEMPLATE_IMA_NAME) == 0) + is_ima_template = true; + + if (!is_ima_template) + ima_putc(m, &e->template_data_len, + sizeof(e->template_data_len)); + + /* 6th: template specific data */ + for (i = 0; i < e->template_desc->num_fields; i++) { + enum ima_show_type show = IMA_SHOW_BINARY; + struct ima_template_field *field = e->template_desc->fields[i]; + + if (is_ima_template && strcmp(field->field_id, "d") == 0) + show = IMA_SHOW_BINARY_NO_FIELD_LEN; + if (is_ima_template && strcmp(field->field_id, "n") == 0) + show = IMA_SHOW_BINARY_OLD_STRING_FMT; + field->field_show(m, show, &e->template_data[i]); + } return 0; } @@ -168,41 +186,21 @@ static const struct file_operations ima_measurements_ops = { .release = seq_release, }; -static void ima_print_digest(struct seq_file *m, u8 *digest) +void ima_print_digest(struct seq_file *m, u8 *digest, int size) { int i; - for (i = 0; i < IMA_DIGEST_SIZE; i++) + for (i = 0; i < size; i++) seq_printf(m, "%02x", *(digest + i)); } -void ima_template_show(struct seq_file *m, void *e, enum ima_show_type show) -{ - struct ima_template_data *entry = e; - int namelen; - - switch (show) { - case IMA_SHOW_ASCII: - ima_print_digest(m, entry->digest); - seq_printf(m, " %s\n", entry->file_name); - break; - case IMA_SHOW_BINARY: - ima_putc(m, entry->digest, IMA_DIGEST_SIZE); - - namelen = strlen(entry->file_name); - ima_putc(m, &namelen, sizeof namelen); - ima_putc(m, entry->file_name, namelen); - default: - break; - } -} - /* print in ascii */ static int ima_ascii_measurements_show(struct seq_file *m, void *v) { /* the list never shrinks, so we don't need a lock here */ struct ima_queue_entry *qe = v; struct ima_template_entry *e; + int i; /* get entry */ e = qe->entry; @@ -213,14 +211,21 @@ static int ima_ascii_measurements_show(struct seq_file *m, void *v) seq_printf(m, "%2d ", CONFIG_IMA_MEASURE_PCR_IDX); /* 2nd: SHA1 template hash */ - ima_print_digest(m, e->digest); + ima_print_digest(m, e->digest, TPM_DIGEST_SIZE); /* 3th: template name */ - seq_printf(m, " %s ", e->template_name); + seq_printf(m, " %s", e->template_desc->name); /* 4th: template specific data */ - ima_template_show(m, (struct ima_template_data *)&e->template, - IMA_SHOW_ASCII); + for (i = 0; i < e->template_desc->num_fields; i++) { + seq_puts(m, " "); + if (e->template_data[i].len == 0) + continue; + + e->template_desc->fields[i]->field_show(m, IMA_SHOW_ASCII, + &e->template_data[i]); + } + seq_puts(m, "\n"); return 0; } @@ -287,7 +292,7 @@ static atomic_t policy_opencount = ATOMIC_INIT(1); /* * ima_open_policy: sequentialize access to the policy file */ -static 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)) @@ -367,20 +372,11 @@ int __init ima_fs_init(void) return 0; out: - securityfs_remove(runtime_measurements_count); - securityfs_remove(ascii_runtime_measurements); - securityfs_remove(binary_runtime_measurements); - securityfs_remove(ima_dir); - securityfs_remove(ima_policy); - return -1; -} - -void __exit ima_fs_cleanup(void) -{ securityfs_remove(violations); securityfs_remove(runtime_measurements_count); securityfs_remove(ascii_runtime_measurements); securityfs_remove(binary_runtime_measurements); securityfs_remove(ima_dir); securityfs_remove(ima_policy); + return -1; } diff --git a/security/integrity/ima/ima_init.c b/security/integrity/ima/ima_init.c index 17f1f060306..e8f9d70a465 100644 --- a/security/integrity/ima/ima_init.c +++ b/security/integrity/ima/ima_init.c @@ -14,10 +14,14 @@ * File: ima_init.c * initialization and cleanup functions */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + #include <linux/module.h> #include <linux/scatterlist.h> #include <linux/slab.h> #include <linux/err.h> +#include <crypto/hash_info.h> #include "ima.h" /* name for boot aggregate entry */ @@ -41,31 +45,40 @@ int ima_used_chip; */ static void __init ima_add_boot_aggregate(void) { - struct ima_template_entry *entry; - const char *op = "add_boot_aggregate"; + static const char op[] = "add_boot_aggregate"; const char *audit_cause = "ENOMEM"; + struct ima_template_entry *entry; + struct integrity_iint_cache tmp_iint, *iint = &tmp_iint; int result = -ENOMEM; - int violation = 1; + int violation = 0; + struct { + struct ima_digest_data hdr; + char digest[TPM_DIGEST_SIZE]; + } hash; - entry = kmalloc(sizeof(*entry), GFP_KERNEL); - if (!entry) - goto err_out; + memset(iint, 0, sizeof(*iint)); + memset(&hash, 0, sizeof(hash)); + iint->ima_hash = &hash.hdr; + iint->ima_hash->algo = HASH_ALGO_SHA1; + iint->ima_hash->length = SHA1_DIGEST_SIZE; - memset(&entry->template, 0, sizeof(entry->template)); - strncpy(entry->template.file_name, boot_aggregate_name, - IMA_EVENT_NAME_LEN_MAX); if (ima_used_chip) { - violation = 0; - result = ima_calc_boot_aggregate(entry->template.digest); + result = ima_calc_boot_aggregate(&hash.hdr); if (result < 0) { audit_cause = "hashing_error"; - kfree(entry); goto err_out; } } - result = ima_store_template(entry, violation, NULL); + + result = ima_alloc_init_template(iint, NULL, boot_aggregate_name, + NULL, 0, &entry); if (result < 0) - kfree(entry); + return; + + result = ima_store_template(entry, violation, NULL, + boot_aggregate_name); + if (result < 0) + ima_free_template_entry(entry); return; err_out: integrity_audit_msg(AUDIT_INTEGRITY_PCR, NULL, boot_aggregate_name, op, @@ -74,7 +87,7 @@ err_out: int __init ima_init(void) { - u8 pcr_i[IMA_DIGEST_SIZE]; + u8 pcr_i[TPM_DIGEST_SIZE]; int rc; ima_used_chip = 0; @@ -83,15 +96,17 @@ int __init ima_init(void) ima_used_chip = 1; if (!ima_used_chip) - pr_info("IMA: No TPM chip found, activating TPM-bypass!\n"); + pr_info("No TPM chip found, activating TPM-bypass!\n"); + + rc = ima_init_crypto(); + if (rc) + return rc; + rc = ima_init_template(); + if (rc != 0) + return rc; ima_add_boot_aggregate(); /* boot aggregate must be first entry */ ima_init_policy(); return ima_fs_init(); } - -void __exit ima_cleanup(void) -{ - ima_fs_cleanup(); -} diff --git a/security/integrity/ima/ima_main.c b/security/integrity/ima/ima_main.c index 1eff5cb001e..09baa335ebc 100644 --- a/security/integrity/ima/ima_main.c +++ b/security/integrity/ima/ima_main.c @@ -22,17 +22,47 @@ #include <linux/mount.h> #include <linux/mman.h> #include <linux/slab.h> +#include <linux/xattr.h> #include <linux/ima.h> +#include <crypto/hash_info.h> #include "ima.h" int ima_initialized; -char *ima_hash = "sha1"; +#ifdef CONFIG_IMA_APPRAISE +int ima_appraise = IMA_APPRAISE_ENFORCE; +#else +int ima_appraise; +#endif + +int ima_hash_algo = HASH_ALGO_SHA1; +static int hash_setup_done; + static int __init hash_setup(char *str) { - if (strncmp(str, "md5", 3) == 0) - ima_hash = "md5"; + struct ima_template_desc *template_desc = ima_template_desc_current(); + int i; + + if (hash_setup_done) + return 1; + + if (strcmp(template_desc->name, IMA_TEMPLATE_IMA_NAME) == 0) { + if (strncmp(str, "sha1", 4) == 0) + ima_hash_algo = HASH_ALGO_SHA1; + else if (strncmp(str, "md5", 3) == 0) + ima_hash_algo = HASH_ALGO_MD5; + goto out; + } + + for (i = 0; i < HASH_ALGO__LAST; i++) { + if (strcmp(str, hash_algo_name[i]) == 0) { + ima_hash_algo = i; + break; + } + } +out: + hash_setup_done = 1; return 1; } __setup("ima_hash=", hash_setup); @@ -41,19 +71,19 @@ __setup("ima_hash=", hash_setup); * ima_rdwr_violation_check * * Only invalidate the PCR for measured files: - * - Opening a file for write when already open for read, + * - Opening a file for write when already open for read, * results in a time of measure, time of use (ToMToU) error. * - Opening a file for read when already open for write, - * could result in a file measurement error. + * could result in a file measurement error. * */ static void ima_rdwr_violation_check(struct file *file) { - struct dentry *dentry = file->f_path.dentry; - struct inode *inode = dentry->d_inode; + struct inode *inode = file_inode(file); fmode_t mode = file->f_mode; - int rc; bool send_tomtou = false, send_writers = false; + char *pathbuf = NULL; + const char *pathname; if (!S_ISREG(inode->i_mode) || !ima_initialized) return; @@ -61,40 +91,50 @@ static void ima_rdwr_violation_check(struct file *file) mutex_lock(&inode->i_mutex); /* file metadata: permissions, xattr */ if (mode & FMODE_WRITE) { - if (atomic_read(&inode->i_readcount) && IS_IMA(inode)) - send_tomtou = true; - goto out; + if (atomic_read(&inode->i_readcount) && IS_IMA(inode)) { + struct integrity_iint_cache *iint; + iint = integrity_iint_find(inode); + /* IMA_MEASURE is set from reader side */ + if (iint && (iint->flags & IMA_MEASURE)) + send_tomtou = true; + } + } else { + if ((atomic_read(&inode->i_writecount) > 0) && + ima_must_measure(inode, MAY_READ, FILE_CHECK)) + send_writers = true; } - rc = ima_must_measure(inode, MAY_READ, FILE_CHECK); - if (rc < 0) - goto out; - - if (atomic_read(&inode->i_writecount) > 0) - send_writers = true; -out: mutex_unlock(&inode->i_mutex); + if (!send_tomtou && !send_writers) + return; + + pathname = ima_d_path(&file->f_path, &pathbuf); + if (send_tomtou) - ima_add_violation(inode, dentry->d_name.name, "invalid_pcr", - "ToMToU"); + ima_add_violation(file, pathname, "invalid_pcr", "ToMToU"); if (send_writers) - ima_add_violation(inode, dentry->d_name.name, "invalid_pcr", - "open_writers"); + ima_add_violation(file, pathname, + "invalid_pcr", "open_writers"); + kfree(pathbuf); } 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_DONE_MASK; + if (iint->flags & IMA_APPRAISE) + ima_update_xattr(iint, file); + } + mutex_unlock(&inode->i_mutex); } /** @@ -105,7 +145,7 @@ static void ima_check_last_writer(struct integrity_iint_cache *iint, */ void ima_file_free(struct file *file) { - struct inode *inode = file->f_dentry->d_inode; + struct inode *inode = file_inode(file); struct integrity_iint_cache *iint; if (!iint_initialized || !S_ISREG(inode->i_mode)) @@ -118,40 +158,88 @@ void ima_file_free(struct file *file) ima_check_last_writer(iint, inode, file); } -static int process_measurement(struct file *file, const unsigned char *filename, +static int process_measurement(struct file *file, const char *filename, int mask, int function) { - struct inode *inode = file->f_dentry->d_inode; + struct inode *inode = file_inode(file); struct integrity_iint_cache *iint; - int rc = 0; + struct ima_template_desc *template_desc = ima_template_desc_current(); + char *pathbuf = NULL; + const char *pathname = NULL; + int rc = -ENOMEM, action, must_appraise, _func; + struct evm_ima_xattr_data *xattr_value = NULL, **xattr_ptr = NULL; + int xattr_len = 0; if (!ima_initialized || !S_ISREG(inode->i_mode)) return 0; - rc = ima_must_measure(inode, mask, function); - if (rc != 0) - return rc; -retry: - iint = integrity_iint_find(inode); - if (!iint) { - rc = integrity_inode_alloc(inode); - if (!rc || rc == -EEXIST) - goto retry; - return rc; - } + /* Return an IMA_MEASURE, IMA_APPRAISE, IMA_AUDIT action + * bitmask based on the appraise/audit/measurement policy. + * Included is the appraise submask. + */ + action = ima_get_action(inode, mask, function); + if (!action) + return 0; + + must_appraise = action & IMA_APPRAISE; + + /* Is the appraise rule hook specific? */ + _func = (action & IMA_FILE_APPRAISE) ? FILE_CHECK : function; - mutex_lock(&iint->mutex); + mutex_lock(&inode->i_mutex); - rc = iint->flags & IMA_MEASURED ? 1 : 0; - if (rc != 0) + iint = integrity_inode_get(inode); + if (!iint) goto out; - rc = ima_collect_measurement(iint, file); - if (!rc) - ima_store_measurement(iint, file, filename); + /* Determine if already appraised/measured based on bitmask + * (IMA_MEASURE, IMA_MEASURED, IMA_XXXX_APPRAISE, IMA_XXXX_APPRAISED, + * IMA_AUDIT, IMA_AUDITED) + */ + iint->flags |= action; + action &= IMA_DO_MASK; + action &= ~((iint->flags & IMA_DONE_MASK) >> 1); + + /* Nothing to do, just return existing appraised status */ + if (!action) { + if (must_appraise) + rc = ima_get_cache_status(iint, _func); + goto out_digsig; + } + + if (strcmp(template_desc->name, IMA_TEMPLATE_IMA_NAME) == 0) { + if (action & IMA_APPRAISE_SUBMASK) + xattr_ptr = &xattr_value; + } else + xattr_ptr = &xattr_value; + + rc = ima_collect_measurement(iint, file, xattr_ptr, &xattr_len); + if (rc != 0) { + if (file->f_flags & O_DIRECT) + rc = (iint->flags & IMA_PERMIT_DIRECTIO) ? 0 : -EACCES; + goto out_digsig; + } + + pathname = filename ?: ima_d_path(&file->f_path, &pathbuf); + + if (action & IMA_MEASURE) + ima_store_measurement(iint, file, pathname, + xattr_value, xattr_len); + if (action & IMA_APPRAISE_SUBMASK) + rc = ima_appraise_measurement(_func, iint, file, pathname, + xattr_value, xattr_len); + if (action & IMA_AUDIT) + ima_audit_measurement(iint, pathname); + kfree(pathbuf); +out_digsig: + if ((mask & MAY_WRITE) && (iint->flags & IMA_DIGSIG)) + rc = -EACCES; out: - mutex_unlock(&iint->mutex); - return rc; + mutex_unlock(&inode->i_mutex); + kfree(xattr_value); + if ((rc && must_appraise) && (ima_appraise & IMA_APPRAISE_ENFORCE)) + return -EACCES; + return 0; } /** @@ -162,18 +250,13 @@ out: * Measure files being mmapped executable based on the ima_must_measure() * policy decision. * - * Return 0 on success, an error code on failure. - * (Based on the results of appraise_measurement().) + * On success return 0. On integrity appraisal error, assuming the file + * is in policy and IMA-appraisal is in enforcing mode, return -EACCES. */ int ima_file_mmap(struct file *file, unsigned long prot) { - int rc; - - if (!file) - return 0; - if (prot & PROT_EXEC) - rc = process_measurement(file, file->f_dentry->d_name.name, - MAY_EXEC, FILE_MMAP); + if (file && (prot & PROT_EXEC)) + return process_measurement(file, NULL, MAY_EXEC, MMAP_CHECK); return 0; } @@ -187,16 +270,15 @@ int ima_file_mmap(struct file *file, unsigned long prot) * So we can be certain that what we verify and measure here is actually * what is being executed. * - * Return 0 on success, an error code on failure. - * (Based on the results of appraise_measurement().) + * On success return 0. On integrity appraisal error, assuming the file + * is in policy and IMA-appraisal is in enforcing mode, return -EACCES. */ int ima_bprm_check(struct linux_binprm *bprm) { - int rc; - - rc = process_measurement(bprm->file, bprm->filename, - MAY_EXEC, BPRM_CHECK); - return 0; + return process_measurement(bprm->file, + (strcmp(bprm->filename, bprm->interp) == 0) ? + bprm->filename : bprm->interp, + MAY_EXEC, BPRM_CHECK); } /** @@ -206,35 +288,51 @@ int ima_bprm_check(struct linux_binprm *bprm) * * Measure files based on the ima_must_measure() policy decision. * - * Always return 0 and audit dentry_open failures. - * (Return code will be based upon measurement appraisal.) + * On success return 0. On integrity appraisal error, assuming the file + * is in policy and IMA-appraisal is in enforcing mode, return -EACCES. */ int ima_file_check(struct file *file, int mask) { - int rc; - ima_rdwr_violation_check(file); - rc = process_measurement(file, file->f_dentry->d_name.name, - mask & (MAY_READ | MAY_WRITE | MAY_EXEC), - FILE_CHECK); - return 0; + return process_measurement(file, NULL, + mask & (MAY_READ | MAY_WRITE | MAY_EXEC), + FILE_CHECK); } EXPORT_SYMBOL_GPL(ima_file_check); +/** + * ima_module_check - based on policy, collect/store/appraise measurement. + * @file: pointer to the file to be measured/appraised + * + * Measure/appraise kernel modules based on policy. + * + * On success return 0. On integrity appraisal error, assuming the file + * is in policy and IMA-appraisal is in enforcing mode, return -EACCES. + */ +int ima_module_check(struct file *file) +{ + if (!file) { +#ifndef CONFIG_MODULE_SIG_FORCE + if ((ima_appraise & IMA_APPRAISE_MODULES) && + (ima_appraise & IMA_APPRAISE_ENFORCE)) + return -EACCES; /* INTEGRITY_UNKNOWN */ +#endif + return 0; /* We rely on module signature checking */ + } + return process_measurement(file, NULL, MAY_EXEC, MODULE_CHECK); +} + static int __init init_ima(void) { int error; + hash_setup(CONFIG_IMA_DEFAULT_HASH); error = ima_init(); - ima_initialized = 1; + if (!error) + ima_initialized = 1; return error; } -static void __exit cleanup_ima(void) -{ - ima_cleanup(); -} - late_initcall(init_ima); /* Start IMA after the TPM is available */ MODULE_DESCRIPTION("Integrity Measurement Architecture"); diff --git a/security/integrity/ima/ima_policy.c b/security/integrity/ima/ima_policy.c index d45061d02fe..40a7488f672 100644 --- a/security/integrity/ima/ima_policy.c +++ b/security/integrity/ima/ima_policy.c @@ -7,7 +7,7 @@ * the Free Software Foundation, version 2 of the License. * * ima_policy.c - * - initialize default measure policy rules + * - initialize default measure policy rules * */ #include <linux/module.h> @@ -16,39 +16,50 @@ #include <linux/magic.h> #include <linux/parser.h> #include <linux/slab.h> +#include <linux/genhd.h> #include "ima.h" /* flags definitions */ -#define IMA_FUNC 0x0001 -#define IMA_MASK 0x0002 +#define IMA_FUNC 0x0001 +#define IMA_MASK 0x0002 #define IMA_FSMAGIC 0x0004 #define IMA_UID 0x0008 +#define IMA_FOWNER 0x0010 +#define IMA_FSUUID 0x0020 -enum ima_action { UNKNOWN = -1, DONT_MEASURE = 0, MEASURE }; +#define UNKNOWN 0 +#define MEASURE 0x0001 /* same as IMA_MEASURE */ +#define DONT_MEASURE 0x0002 +#define APPRAISE 0x0004 /* same as IMA_APPRAISE */ +#define DONT_APPRAISE 0x0008 +#define AUDIT 0x0040 #define MAX_LSM_RULES 6 enum lsm_rule_types { LSM_OBJ_USER, LSM_OBJ_ROLE, LSM_OBJ_TYPE, LSM_SUBJ_USER, LSM_SUBJ_ROLE, LSM_SUBJ_TYPE }; -struct ima_measure_rule_entry { +struct ima_rule_entry { struct list_head list; - enum ima_action action; + int action; unsigned int flags; enum ima_hooks func; int mask; unsigned long fsmagic; - uid_t uid; + u8 fsuuid[16]; + kuid_t uid; + kuid_t fowner; struct { void *rule; /* LSM file metadata specific */ + void *args_p; /* audit value */ int type; /* audit type */ } lsm[MAX_LSM_RULES]; }; /* * Without LSM specific knowledge, the default policy can only be - * written in terms of .action, .func, .mask, .fsmagic, and .uid + * written in terms of .action, .func, .mask, .fsmagic, .uid, and .fowner */ /* @@ -57,34 +68,88 @@ struct ima_measure_rule_entry { * normal users can easily run the machine out of memory simply building * and running executables. */ -static struct ima_measure_rule_entry default_rules[] = { - {.action = DONT_MEASURE,.fsmagic = PROC_SUPER_MAGIC,.flags = IMA_FSMAGIC}, - {.action = DONT_MEASURE,.fsmagic = SYSFS_MAGIC,.flags = IMA_FSMAGIC}, - {.action = DONT_MEASURE,.fsmagic = DEBUGFS_MAGIC,.flags = IMA_FSMAGIC}, - {.action = DONT_MEASURE,.fsmagic = TMPFS_MAGIC,.flags = IMA_FSMAGIC}, - {.action = DONT_MEASURE,.fsmagic = SECURITYFS_MAGIC,.flags = IMA_FSMAGIC}, - {.action = DONT_MEASURE,.fsmagic = SELINUX_MAGIC,.flags = IMA_FSMAGIC}, - {.action = MEASURE,.func = FILE_MMAP,.mask = MAY_EXEC, +static struct ima_rule_entry default_rules[] = { + {.action = DONT_MEASURE, .fsmagic = PROC_SUPER_MAGIC, .flags = IMA_FSMAGIC}, + {.action = DONT_MEASURE, .fsmagic = SYSFS_MAGIC, .flags = IMA_FSMAGIC}, + {.action = DONT_MEASURE, .fsmagic = DEBUGFS_MAGIC, .flags = IMA_FSMAGIC}, + {.action = DONT_MEASURE, .fsmagic = TMPFS_MAGIC, .flags = IMA_FSMAGIC}, + {.action = DONT_MEASURE, .fsmagic = DEVPTS_SUPER_MAGIC, .flags = IMA_FSMAGIC}, + {.action = DONT_MEASURE, .fsmagic = BINFMTFS_MAGIC, .flags = IMA_FSMAGIC}, + {.action = DONT_MEASURE, .fsmagic = SECURITYFS_MAGIC, .flags = IMA_FSMAGIC}, + {.action = DONT_MEASURE, .fsmagic = SELINUX_MAGIC, .flags = IMA_FSMAGIC}, + {.action = MEASURE, .func = MMAP_CHECK, .mask = MAY_EXEC, .flags = IMA_FUNC | IMA_MASK}, - {.action = MEASURE,.func = BPRM_CHECK,.mask = MAY_EXEC, + {.action = MEASURE, .func = BPRM_CHECK, .mask = MAY_EXEC, .flags = IMA_FUNC | IMA_MASK}, - {.action = MEASURE,.func = FILE_CHECK,.mask = MAY_READ,.uid = 0, + {.action = MEASURE, .func = FILE_CHECK, .mask = MAY_READ, .uid = GLOBAL_ROOT_UID, .flags = IMA_FUNC | IMA_MASK | IMA_UID}, + {.action = MEASURE, .func = MODULE_CHECK, .flags = IMA_FUNC}, }; -static LIST_HEAD(measure_default_rules); -static LIST_HEAD(measure_policy_rules); -static struct list_head *ima_measure; +static struct ima_rule_entry default_appraise_rules[] = { + {.action = DONT_APPRAISE, .fsmagic = PROC_SUPER_MAGIC, .flags = IMA_FSMAGIC}, + {.action = DONT_APPRAISE, .fsmagic = SYSFS_MAGIC, .flags = IMA_FSMAGIC}, + {.action = DONT_APPRAISE, .fsmagic = DEBUGFS_MAGIC, .flags = IMA_FSMAGIC}, + {.action = DONT_APPRAISE, .fsmagic = TMPFS_MAGIC, .flags = IMA_FSMAGIC}, + {.action = DONT_APPRAISE, .fsmagic = RAMFS_MAGIC, .flags = IMA_FSMAGIC}, + {.action = DONT_APPRAISE, .fsmagic = DEVPTS_SUPER_MAGIC, .flags = IMA_FSMAGIC}, + {.action = DONT_APPRAISE, .fsmagic = BINFMTFS_MAGIC, .flags = IMA_FSMAGIC}, + {.action = DONT_APPRAISE, .fsmagic = SECURITYFS_MAGIC, .flags = IMA_FSMAGIC}, + {.action = DONT_APPRAISE, .fsmagic = SELINUX_MAGIC, .flags = IMA_FSMAGIC}, + {.action = DONT_APPRAISE, .fsmagic = CGROUP_SUPER_MAGIC, .flags = IMA_FSMAGIC}, + {.action = APPRAISE, .fowner = GLOBAL_ROOT_UID, .flags = IMA_FOWNER}, +}; + +static LIST_HEAD(ima_default_rules); +static LIST_HEAD(ima_policy_rules); +static struct list_head *ima_rules; -static DEFINE_MUTEX(ima_measure_mutex); +static DEFINE_MUTEX(ima_rules_mutex); static bool ima_use_tcb __initdata; -static int __init default_policy_setup(char *str) +static int __init default_measure_policy_setup(char *str) { ima_use_tcb = 1; return 1; } -__setup("ima_tcb", default_policy_setup); +__setup("ima_tcb", default_measure_policy_setup); + +static bool ima_use_appraise_tcb __initdata; +static int __init default_appraise_policy_setup(char *str) +{ + ima_use_appraise_tcb = 1; + return 1; +} +__setup("ima_appraise_tcb", default_appraise_policy_setup); + +/* + * Although the IMA policy does not change, the LSM policy can be + * reloaded, leaving the IMA LSM based rules referring to the old, + * stale LSM policy. + * + * Update the IMA LSM based rules to reflect the reloaded LSM policy. + * We assume the rules still exist; and BUG_ON() if they don't. + */ +static void ima_lsm_update_rules(void) +{ + struct ima_rule_entry *entry, *tmp; + int result; + int i; + + mutex_lock(&ima_rules_mutex); + list_for_each_entry_safe(entry, tmp, &ima_policy_rules, list) { + for (i = 0; i < MAX_LSM_RULES; i++) { + if (!entry->lsm[i].rule) + continue; + result = security_filter_rule_init(entry->lsm[i].type, + Audit_equal, + entry->lsm[i].args_p, + &entry->lsm[i].rule); + BUG_ON(!entry->lsm[i].rule); + } + } + mutex_unlock(&ima_rules_mutex); +} /** * ima_match_rules - determine whether an inode matches the measure rule. @@ -95,29 +160,37 @@ __setup("ima_tcb", default_policy_setup); * * Returns true on rule match, false on failure. */ -static bool ima_match_rules(struct ima_measure_rule_entry *rule, +static bool ima_match_rules(struct ima_rule_entry *rule, struct inode *inode, enum ima_hooks func, int mask) { struct task_struct *tsk = current; const struct cred *cred = current_cred(); int i; - if ((rule->flags & IMA_FUNC) && rule->func != func) + if ((rule->flags & IMA_FUNC) && + (rule->func != func && func != POST_SETATTR)) return false; - if ((rule->flags & IMA_MASK) && rule->mask != mask) + if ((rule->flags & IMA_MASK) && + (rule->mask != mask && func != POST_SETATTR)) return false; if ((rule->flags & IMA_FSMAGIC) && rule->fsmagic != inode->i_sb->s_magic) return false; - if ((rule->flags & IMA_UID) && rule->uid != cred->uid) + if ((rule->flags & IMA_FSUUID) && + memcmp(rule->fsuuid, inode->i_sb->s_uuid, sizeof(rule->fsuuid))) + return false; + if ((rule->flags & IMA_UID) && !uid_eq(rule->uid, cred->uid)) + return false; + if ((rule->flags & IMA_FOWNER) && !uid_eq(rule->fowner, inode->i_uid)) return false; for (i = 0; i < MAX_LSM_RULES; i++) { int rc = 0; u32 osid, sid; + int retried = 0; if (!rule->lsm[i].rule) continue; - +retry: switch (i) { case LSM_OBJ_USER: case LSM_OBJ_ROLE: @@ -141,12 +214,39 @@ static bool ima_match_rules(struct ima_measure_rule_entry *rule, default: break; } + if ((rc < 0) && (!retried)) { + retried = 1; + ima_lsm_update_rules(); + goto retry; + } if (!rc) return false; } return true; } +/* + * In addition to knowing that we need to appraise the file in general, + * we need to differentiate between calling hooks, for hook specific rules. + */ +static int get_subaction(struct ima_rule_entry *rule, int func) +{ + if (!(rule->flags & IMA_FUNC)) + return IMA_FILE_APPRAISE; + + switch (func) { + case MMAP_CHECK: + return IMA_MMAP_APPRAISE; + case BPRM_CHECK: + return IMA_BPRM_APPRAISE; + case MODULE_CHECK: + return IMA_MODULE_APPRAISE; + case FILE_CHECK: + default: + return IMA_FILE_APPRAISE; + } +} + /** * ima_match_policy - decision based on LSM and other conditions * @inode: pointer to an inode for which the policy decision is being made @@ -160,39 +260,66 @@ 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; + struct ima_rule_entry *entry; + int action = 0, actmask = flags | (flags << 1); + + list_for_each_entry(entry, ima_rules, list) { + + if (!(entry->action & actmask)) + continue; + + if (!ima_match_rules(entry, inode, func, mask)) + continue; + + action |= entry->flags & IMA_ACTION_FLAGS; - list_for_each_entry(entry, ima_measure, list) { - bool rc; + action |= entry->action & IMA_DO_MASK; + if (entry->action & IMA_APPRAISE) + action |= get_subaction(entry, func); - rc = ima_match_rules(entry, inode, func, mask); - if (rc) - return entry->action; + if (entry->action & IMA_DO_MASK) + actmask &= ~(entry->action | entry->action << 1); + else + actmask &= ~(entry->action | entry->action >> 1); + + if (!actmask) + break; } - return 0; + + return action; } /** * ima_init_policy - initialize the default measure rules. * - * ima_measure points to either the measure_default_rules or the - * the new measure_policy_rules. + * ima_rules points to either the ima_default_rules or the + * the new ima_policy_rules. */ void __init ima_init_policy(void) { - int i, entries; + int i, measure_entries, appraise_entries; /* if !ima_use_tcb set entries = 0 so we load NO default rules */ - if (ima_use_tcb) - entries = ARRAY_SIZE(default_rules); - else - entries = 0; - - for (i = 0; i < entries; i++) - list_add_tail(&default_rules[i].list, &measure_default_rules); - ima_measure = &measure_default_rules; + measure_entries = ima_use_tcb ? ARRAY_SIZE(default_rules) : 0; + appraise_entries = ima_use_appraise_tcb ? + ARRAY_SIZE(default_appraise_rules) : 0; + + for (i = 0; i < measure_entries + appraise_entries; i++) { + if (i < measure_entries) + list_add_tail(&default_rules[i].list, + &ima_default_rules); + else { + int j = i - measure_entries; + + list_add_tail(&default_appraise_rules[j].list, + &ima_default_rules); + } + } + + ima_rules = &ima_default_rules; } /** @@ -204,13 +331,13 @@ void __init ima_init_policy(void) */ void ima_update_policy(void) { - const char *op = "policy_update"; + static const char op[] = "policy_update"; const char *cause = "already exists"; int result = 1; int audit_info = 0; - if (ima_measure == &measure_default_rules) { - ima_measure = &measure_policy_rules; + if (ima_rules == &ima_default_rules) { + ima_rules = &ima_policy_rules; cause = "complete"; result = 0; } @@ -221,14 +348,20 @@ void ima_update_policy(void) enum { Opt_err = -1, Opt_measure = 1, Opt_dont_measure, + Opt_appraise, Opt_dont_appraise, + Opt_audit, Opt_obj_user, Opt_obj_role, Opt_obj_type, Opt_subj_user, Opt_subj_role, Opt_subj_type, - Opt_func, Opt_mask, Opt_fsmagic, Opt_uid + Opt_func, Opt_mask, Opt_fsmagic, Opt_uid, Opt_fowner, + Opt_appraise_type, Opt_fsuuid, Opt_permit_directio }; static match_table_t policy_tokens = { {Opt_measure, "measure"}, {Opt_dont_measure, "dont_measure"}, + {Opt_appraise, "appraise"}, + {Opt_dont_appraise, "dont_appraise"}, + {Opt_audit, "audit"}, {Opt_obj_user, "obj_user=%s"}, {Opt_obj_role, "obj_role=%s"}, {Opt_obj_type, "obj_type=%s"}, @@ -238,24 +371,36 @@ static match_table_t policy_tokens = { {Opt_func, "func=%s"}, {Opt_mask, "mask=%s"}, {Opt_fsmagic, "fsmagic=%s"}, + {Opt_fsuuid, "fsuuid=%s"}, {Opt_uid, "uid=%s"}, + {Opt_fowner, "fowner=%s"}, + {Opt_appraise_type, "appraise_type=%s"}, + {Opt_permit_directio, "permit_directio"}, {Opt_err, NULL} }; -static int ima_lsm_rule_init(struct ima_measure_rule_entry *entry, - char *args, int lsm_rule, int audit_type) +static int ima_lsm_rule_init(struct ima_rule_entry *entry, + substring_t *args, int lsm_rule, int audit_type) { int result; if (entry->lsm[lsm_rule].rule) return -EINVAL; + entry->lsm[lsm_rule].args_p = match_strdup(args); + if (!entry->lsm[lsm_rule].args_p) + return -ENOMEM; + entry->lsm[lsm_rule].type = audit_type; result = security_filter_rule_init(entry->lsm[lsm_rule].type, - Audit_equal, args, + Audit_equal, + entry->lsm[lsm_rule].args_p, &entry->lsm[lsm_rule].rule); - if (!entry->lsm[lsm_rule].rule) + if (!entry->lsm[lsm_rule].rule) { + kfree(entry->lsm[lsm_rule].args_p); return -EINVAL; + } + return result; } @@ -266,7 +411,7 @@ static void ima_log_string(struct audit_buffer *ab, char *key, char *value) audit_log_format(ab, " "); } -static int ima_parse_rule(char *rule, struct ima_measure_rule_entry *entry) +static int ima_parse_rule(char *rule, struct ima_rule_entry *entry) { struct audit_buffer *ab; char *p; @@ -274,7 +419,8 @@ static int ima_parse_rule(char *rule, struct ima_measure_rule_entry *entry) ab = audit_log_start(NULL, GFP_KERNEL, AUDIT_INTEGRITY_RULE); - entry->uid = -1; + entry->uid = INVALID_UID; + entry->fowner = INVALID_UID; entry->action = UNKNOWN; while ((p = strsep(&rule, " \t")) != NULL) { substring_t args[MAX_OPT_ARGS]; @@ -303,19 +449,46 @@ static int ima_parse_rule(char *rule, struct ima_measure_rule_entry *entry) entry->action = DONT_MEASURE; break; + case Opt_appraise: + ima_log_string(ab, "action", "appraise"); + + if (entry->action != UNKNOWN) + result = -EINVAL; + + entry->action = APPRAISE; + break; + case Opt_dont_appraise: + ima_log_string(ab, "action", "dont_appraise"); + + if (entry->action != UNKNOWN) + result = -EINVAL; + + entry->action = DONT_APPRAISE; + break; + case Opt_audit: + ima_log_string(ab, "action", "audit"); + + if (entry->action != UNKNOWN) + result = -EINVAL; + + entry->action = AUDIT; + break; case Opt_func: ima_log_string(ab, "func", args[0].from); if (entry->func) - result = -EINVAL; + result = -EINVAL; if (strcmp(args[0].from, "FILE_CHECK") == 0) entry->func = FILE_CHECK; /* PATH_CHECK is for backwards compat */ else if (strcmp(args[0].from, "PATH_CHECK") == 0) entry->func = FILE_CHECK; - else if (strcmp(args[0].from, "FILE_MMAP") == 0) - entry->func = FILE_MMAP; + else if (strcmp(args[0].from, "MODULE_CHECK") == 0) + entry->func = MODULE_CHECK; + else if ((strcmp(args[0].from, "FILE_MMAP") == 0) + || (strcmp(args[0].from, "MMAP_CHECK") == 0)) + entry->func = MMAP_CHECK; else if (strcmp(args[0].from, "BPRM_CHECK") == 0) entry->func = BPRM_CHECK; else @@ -350,64 +523,109 @@ static int ima_parse_rule(char *rule, struct ima_measure_rule_entry *entry) break; } - result = strict_strtoul(args[0].from, 16, - &entry->fsmagic); + result = kstrtoul(args[0].from, 16, &entry->fsmagic); if (!result) entry->flags |= IMA_FSMAGIC; break; + case Opt_fsuuid: + ima_log_string(ab, "fsuuid", args[0].from); + + if (memchr_inv(entry->fsuuid, 0x00, + sizeof(entry->fsuuid))) { + result = -EINVAL; + break; + } + + result = blk_part_pack_uuid(args[0].from, + entry->fsuuid); + if (!result) + entry->flags |= IMA_FSUUID; + break; case Opt_uid: ima_log_string(ab, "uid", args[0].from); - if (entry->uid != -1) { + if (uid_valid(entry->uid)) { result = -EINVAL; break; } - result = strict_strtoul(args[0].from, 10, &lnum); + result = kstrtoul(args[0].from, 10, &lnum); if (!result) { - entry->uid = (uid_t) lnum; - if (entry->uid != lnum) + entry->uid = make_kuid(current_user_ns(), (uid_t)lnum); + if (!uid_valid(entry->uid) || (((uid_t)lnum) != lnum)) result = -EINVAL; else entry->flags |= IMA_UID; } break; + case Opt_fowner: + ima_log_string(ab, "fowner", args[0].from); + + if (uid_valid(entry->fowner)) { + result = -EINVAL; + break; + } + + result = kstrtoul(args[0].from, 10, &lnum); + if (!result) { + entry->fowner = make_kuid(current_user_ns(), (uid_t)lnum); + if (!uid_valid(entry->fowner) || (((uid_t)lnum) != lnum)) + result = -EINVAL; + else + entry->flags |= IMA_FOWNER; + } + break; case Opt_obj_user: ima_log_string(ab, "obj_user", args[0].from); - result = ima_lsm_rule_init(entry, args[0].from, + result = ima_lsm_rule_init(entry, args, LSM_OBJ_USER, AUDIT_OBJ_USER); break; case Opt_obj_role: ima_log_string(ab, "obj_role", args[0].from); - result = ima_lsm_rule_init(entry, args[0].from, + result = ima_lsm_rule_init(entry, args, LSM_OBJ_ROLE, AUDIT_OBJ_ROLE); break; case Opt_obj_type: ima_log_string(ab, "obj_type", args[0].from); - result = ima_lsm_rule_init(entry, args[0].from, + result = ima_lsm_rule_init(entry, args, LSM_OBJ_TYPE, AUDIT_OBJ_TYPE); break; case Opt_subj_user: ima_log_string(ab, "subj_user", args[0].from); - result = ima_lsm_rule_init(entry, args[0].from, + result = ima_lsm_rule_init(entry, args, LSM_SUBJ_USER, AUDIT_SUBJ_USER); break; case Opt_subj_role: ima_log_string(ab, "subj_role", args[0].from); - result = ima_lsm_rule_init(entry, args[0].from, + result = ima_lsm_rule_init(entry, args, LSM_SUBJ_ROLE, AUDIT_SUBJ_ROLE); break; case Opt_subj_type: ima_log_string(ab, "subj_type", args[0].from); - result = ima_lsm_rule_init(entry, args[0].from, + result = ima_lsm_rule_init(entry, args, LSM_SUBJ_TYPE, AUDIT_SUBJ_TYPE); break; + case Opt_appraise_type: + if (entry->action != APPRAISE) { + result = -EINVAL; + break; + } + + ima_log_string(ab, "appraise_type", args[0].from); + if ((strcmp(args[0].from, "imasig")) == 0) + entry->flags |= IMA_DIGSIG_REQUIRED; + else + result = -EINVAL; + break; + case Opt_permit_directio: + entry->flags |= IMA_PERMIT_DIRECTIO; + break; case Opt_err: ima_log_string(ab, "UNKNOWN", p); result = -EINVAL; @@ -416,14 +634,15 @@ static int ima_parse_rule(char *rule, struct ima_measure_rule_entry *entry) } if (!result && (entry->action == UNKNOWN)) result = -EINVAL; - - audit_log_format(ab, "res=%d", !!result); + else if (entry->func == MODULE_CHECK) + ima_appraise |= IMA_APPRAISE_MODULES; + audit_log_format(ab, "res=%d", !result); audit_log_end(ab); return result; } /** - * ima_parse_add_rule - add a rule to measure_policy_rules + * ima_parse_add_rule - add a rule to ima_policy_rules * @rule - ima measurement policy rule * * Uses a mutex to protect the policy list from multiple concurrent writers. @@ -431,14 +650,14 @@ static int ima_parse_rule(char *rule, struct ima_measure_rule_entry *entry) */ ssize_t ima_parse_add_rule(char *rule) { - const char *op = "update_policy"; + static const char op[] = "update_policy"; char *p; - struct ima_measure_rule_entry *entry; + struct ima_rule_entry *entry; ssize_t result, len; int audit_info = 0; /* Prevent installed policy from changing */ - if (ima_measure != &measure_default_rules) { + if (ima_rules != &ima_default_rules) { integrity_audit_msg(AUDIT_INTEGRITY_STATUS, NULL, NULL, op, "already exists", -EACCES, audit_info); @@ -471,9 +690,9 @@ ssize_t ima_parse_add_rule(char *rule) return result; } - mutex_lock(&ima_measure_mutex); - list_add_tail(&entry->list, &measure_policy_rules); - mutex_unlock(&ima_measure_mutex); + mutex_lock(&ima_rules_mutex); + list_add_tail(&entry->list, &ima_policy_rules); + mutex_unlock(&ima_rules_mutex); return len; } @@ -481,12 +700,16 @@ ssize_t ima_parse_add_rule(char *rule) /* ima_delete_rules called to cleanup invalid policy */ void ima_delete_rules(void) { - struct ima_measure_rule_entry *entry, *tmp; + struct ima_rule_entry *entry, *tmp; + int i; + + mutex_lock(&ima_rules_mutex); + list_for_each_entry_safe(entry, tmp, &ima_policy_rules, list) { + for (i = 0; i < MAX_LSM_RULES; i++) + kfree(entry->lsm[i].args_p); - mutex_lock(&ima_measure_mutex); - list_for_each_entry_safe(entry, tmp, &measure_policy_rules, list) { list_del(&entry->list); kfree(entry); } - mutex_unlock(&ima_measure_mutex); + mutex_unlock(&ima_rules_mutex); } diff --git a/security/integrity/ima/ima_queue.c b/security/integrity/ima/ima_queue.c index 55a6271bce7..552705d5a78 100644 --- a/security/integrity/ima/ima_queue.c +++ b/security/integrity/ima/ima_queue.c @@ -18,6 +18,9 @@ * The measurement list is append-only. No entry is * ever removed or changed during the boot-cycle. */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + #include <linux/module.h> #include <linux/rculist.h> #include <linux/slab.h> @@ -45,13 +48,12 @@ static struct ima_queue_entry *ima_lookup_digest_entry(u8 *digest_value) { struct ima_queue_entry *qe, *ret = NULL; unsigned int key; - struct hlist_node *pos; int rc; key = ima_hash_key(digest_value); rcu_read_lock(); - hlist_for_each_entry_rcu(qe, pos, &ima_htable.queue[key], hnext) { - rc = memcmp(qe->entry->digest, digest_value, IMA_DIGEST_SIZE); + hlist_for_each_entry_rcu(qe, &ima_htable.queue[key], hnext) { + rc = memcmp(qe->entry->digest, digest_value, TPM_DIGEST_SIZE); if (rc == 0) { ret = qe; break; @@ -73,7 +75,7 @@ static int ima_add_digest_entry(struct ima_template_entry *entry) qe = kmalloc(sizeof(*qe), GFP_KERNEL); if (qe == NULL) { - pr_err("IMA: OUT OF MEMORY ERROR creating queue entry.\n"); + pr_err("OUT OF MEMORY ERROR creating queue entry\n"); return -ENOMEM; } qe->entry = entry; @@ -96,8 +98,7 @@ static int ima_pcr_extend(const u8 *hash) result = tpm_pcr_extend(TPM_ANY_NUM, CONFIG_IMA_MEASURE_PCR_IDX, hash); if (result != 0) - pr_err("IMA: Error Communicating to TPM chip, result: %d\n", - result); + pr_err("Error Communicating to TPM chip, result: %d\n", result); return result; } @@ -105,9 +106,10 @@ static int ima_pcr_extend(const u8 *hash) * and extend the pcr. */ int ima_add_template_entry(struct ima_template_entry *entry, int violation, - const char *op, struct inode *inode) + const char *op, struct inode *inode, + const unsigned char *filename) { - u8 digest[IMA_DIGEST_SIZE]; + u8 digest[TPM_DIGEST_SIZE]; const char *audit_cause = "hash_added"; char tpm_audit_cause[AUDIT_CAUSE_LEN_MAX]; int audit_info = 1; @@ -115,7 +117,7 @@ int ima_add_template_entry(struct ima_template_entry *entry, int violation, mutex_lock(&ima_extend_list_mutex); if (!violation) { - memcpy(digest, entry->digest, sizeof digest); + memcpy(digest, entry->digest, sizeof(digest)); if (ima_lookup_digest_entry(digest)) { audit_cause = "hash_exists"; result = -EEXIST; @@ -131,7 +133,7 @@ int ima_add_template_entry(struct ima_template_entry *entry, int violation, } if (violation) /* invalidate pcr */ - memset(digest, 0xff, sizeof digest); + memset(digest, 0xff, sizeof(digest)); tpmresult = ima_pcr_extend(digest); if (tpmresult != 0) { @@ -142,8 +144,7 @@ int ima_add_template_entry(struct ima_template_entry *entry, int violation, } out: mutex_unlock(&ima_extend_list_mutex); - integrity_audit_msg(AUDIT_INTEGRITY_PCR, inode, - entry->template.file_name, + integrity_audit_msg(AUDIT_INTEGRITY_PCR, inode, filename, op, audit_cause, result, audit_info); return result; } diff --git a/security/integrity/ima/ima_template.c b/security/integrity/ima/ima_template.c new file mode 100644 index 00000000000..a076a967ec4 --- /dev/null +++ b/security/integrity/ima/ima_template.c @@ -0,0 +1,190 @@ +/* + * Copyright (C) 2013 Politecnico di Torino, Italy + * TORSEC group -- http://security.polito.it + * + * Author: 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. + * + * File: ima_template.c + * Helpers to manage template descriptors. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <crypto/hash_info.h> + +#include "ima.h" +#include "ima_template_lib.h" + +static struct ima_template_desc defined_templates[] = { + {.name = IMA_TEMPLATE_IMA_NAME, .fmt = IMA_TEMPLATE_IMA_FMT}, + {.name = "ima-ng", .fmt = "d-ng|n-ng"}, + {.name = "ima-sig", .fmt = "d-ng|n-ng|sig"}, +}; + +static struct ima_template_field supported_fields[] = { + {.field_id = "d", .field_init = ima_eventdigest_init, + .field_show = ima_show_template_digest}, + {.field_id = "n", .field_init = ima_eventname_init, + .field_show = ima_show_template_string}, + {.field_id = "d-ng", .field_init = ima_eventdigest_ng_init, + .field_show = ima_show_template_digest_ng}, + {.field_id = "n-ng", .field_init = ima_eventname_ng_init, + .field_show = ima_show_template_string}, + {.field_id = "sig", .field_init = ima_eventsig_init, + .field_show = ima_show_template_sig}, +}; + +static struct ima_template_desc *ima_template; +static struct ima_template_desc *lookup_template_desc(const char *name); + +static int __init ima_template_setup(char *str) +{ + struct ima_template_desc *template_desc; + int template_len = strlen(str); + + /* + * Verify that a template with the supplied name exists. + * If not, use CONFIG_IMA_DEFAULT_TEMPLATE. + */ + template_desc = lookup_template_desc(str); + if (!template_desc) + return 1; + + /* + * Verify whether the current hash algorithm is supported + * by the 'ima' template. + */ + if (template_len == 3 && strcmp(str, IMA_TEMPLATE_IMA_NAME) == 0 && + ima_hash_algo != HASH_ALGO_SHA1 && ima_hash_algo != HASH_ALGO_MD5) { + pr_err("template does not support hash alg\n"); + return 1; + } + + ima_template = template_desc; + return 1; +} +__setup("ima_template=", ima_template_setup); + +static struct ima_template_desc *lookup_template_desc(const char *name) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(defined_templates); i++) { + if (strcmp(defined_templates[i].name, name) == 0) + return defined_templates + i; + } + + return NULL; +} + +static struct ima_template_field *lookup_template_field(const char *field_id) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(supported_fields); i++) + if (strncmp(supported_fields[i].field_id, field_id, + IMA_TEMPLATE_FIELD_ID_MAX_LEN) == 0) + return &supported_fields[i]; + return NULL; +} + +static int template_fmt_size(const char *template_fmt) +{ + char c; + int template_fmt_len = strlen(template_fmt); + int i = 0, j = 0; + + while (i < template_fmt_len) { + c = template_fmt[i]; + if (c == '|') + j++; + i++; + } + + return j + 1; +} + +static int template_desc_init_fields(const char *template_fmt, + struct ima_template_field ***fields, + int *num_fields) +{ + char *c, *template_fmt_copy, *template_fmt_ptr; + int template_num_fields = template_fmt_size(template_fmt); + int i, result = 0; + + if (template_num_fields > IMA_TEMPLATE_NUM_FIELDS_MAX) + return -EINVAL; + + /* copying is needed as strsep() modifies the original buffer */ + template_fmt_copy = kstrdup(template_fmt, GFP_KERNEL); + if (template_fmt_copy == NULL) + return -ENOMEM; + + *fields = kzalloc(template_num_fields * sizeof(*fields), GFP_KERNEL); + if (*fields == NULL) { + result = -ENOMEM; + goto out; + } + + template_fmt_ptr = template_fmt_copy; + for (i = 0; (c = strsep(&template_fmt_ptr, "|")) != NULL && + i < template_num_fields; i++) { + struct ima_template_field *f = lookup_template_field(c); + + if (!f) { + result = -ENOENT; + goto out; + } + (*fields)[i] = f; + } + *num_fields = i; +out: + if (result < 0) { + kfree(*fields); + *fields = NULL; + } + kfree(template_fmt_copy); + return result; +} + +static int init_defined_templates(void) +{ + int i = 0; + int result = 0; + + /* Init defined templates. */ + for (i = 0; i < ARRAY_SIZE(defined_templates); i++) { + struct ima_template_desc *template = &defined_templates[i]; + + result = template_desc_init_fields(template->fmt, + &(template->fields), + &(template->num_fields)); + if (result < 0) + return result; + } + return result; +} + +struct ima_template_desc *ima_template_desc_current(void) +{ + if (!ima_template) + ima_template = + lookup_template_desc(CONFIG_IMA_DEFAULT_TEMPLATE); + return ima_template; +} + +int ima_init_template(void) +{ + int result; + + result = init_defined_templates(); + if (result < 0) + return result; + + return 0; +} diff --git a/security/integrity/ima/ima_template_lib.c b/security/integrity/ima/ima_template_lib.c new file mode 100644 index 00000000000..1506f024857 --- /dev/null +++ b/security/integrity/ima/ima_template_lib.c @@ -0,0 +1,342 @@ +/* + * Copyright (C) 2013 Politecnico di Torino, Italy + * TORSEC group -- http://security.polito.it + * + * Author: 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. + * + * File: ima_template_lib.c + * Library of supported template fields. + */ +#include <crypto/hash_info.h> + +#include "ima_template_lib.h" + +static bool ima_template_hash_algo_allowed(u8 algo) +{ + if (algo == HASH_ALGO_SHA1 || algo == HASH_ALGO_MD5) + return true; + + return false; +} + +enum data_formats { + DATA_FMT_DIGEST = 0, + DATA_FMT_DIGEST_WITH_ALGO, + DATA_FMT_STRING, + DATA_FMT_HEX +}; + +static int ima_write_template_field_data(const void *data, const u32 datalen, + enum data_formats datafmt, + struct ima_field_data *field_data) +{ + u8 *buf, *buf_ptr; + u32 buflen = datalen; + + if (datafmt == DATA_FMT_STRING) + buflen = datalen + 1; + + buf = kzalloc(buflen, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + memcpy(buf, data, datalen); + + /* + * Replace all space characters with underscore for event names and + * strings. This avoid that, during the parsing of a measurements list, + * filenames with spaces or that end with the suffix ' (deleted)' are + * split into multiple template fields (the space is the delimitator + * character for measurements lists in ASCII format). + */ + if (datafmt == DATA_FMT_STRING) { + for (buf_ptr = buf; buf_ptr - buf < datalen; buf_ptr++) + if (*buf_ptr == ' ') + *buf_ptr = '_'; + } + + field_data->data = buf; + field_data->len = buflen; + return 0; +} + +static void ima_show_template_data_ascii(struct seq_file *m, + enum ima_show_type show, + enum data_formats datafmt, + struct ima_field_data *field_data) +{ + u8 *buf_ptr = field_data->data, buflen = field_data->len; + + switch (datafmt) { + case DATA_FMT_DIGEST_WITH_ALGO: + buf_ptr = strnchr(field_data->data, buflen, ':'); + if (buf_ptr != field_data->data) + seq_printf(m, "%s", field_data->data); + + /* skip ':' and '\0' */ + buf_ptr += 2; + buflen -= buf_ptr - field_data->data; + case DATA_FMT_DIGEST: + case DATA_FMT_HEX: + if (!buflen) + break; + ima_print_digest(m, buf_ptr, buflen); + break; + case DATA_FMT_STRING: + seq_printf(m, "%s", buf_ptr); + break; + default: + break; + } +} + +static void ima_show_template_data_binary(struct seq_file *m, + enum ima_show_type show, + enum data_formats datafmt, + struct ima_field_data *field_data) +{ + u32 len = (show == IMA_SHOW_BINARY_OLD_STRING_FMT) ? + strlen(field_data->data) : field_data->len; + + if (show != IMA_SHOW_BINARY_NO_FIELD_LEN) + ima_putc(m, &len, sizeof(len)); + + if (!len) + return; + + ima_putc(m, field_data->data, len); +} + +static void ima_show_template_field_data(struct seq_file *m, + enum ima_show_type show, + enum data_formats datafmt, + struct ima_field_data *field_data) +{ + switch (show) { + case IMA_SHOW_ASCII: + ima_show_template_data_ascii(m, show, datafmt, field_data); + break; + case IMA_SHOW_BINARY: + case IMA_SHOW_BINARY_NO_FIELD_LEN: + case IMA_SHOW_BINARY_OLD_STRING_FMT: + ima_show_template_data_binary(m, show, datafmt, field_data); + break; + default: + break; + } +} + +void ima_show_template_digest(struct seq_file *m, enum ima_show_type show, + struct ima_field_data *field_data) +{ + ima_show_template_field_data(m, show, DATA_FMT_DIGEST, field_data); +} + +void ima_show_template_digest_ng(struct seq_file *m, enum ima_show_type show, + struct ima_field_data *field_data) +{ + ima_show_template_field_data(m, show, DATA_FMT_DIGEST_WITH_ALGO, + field_data); +} + +void ima_show_template_string(struct seq_file *m, enum ima_show_type show, + struct ima_field_data *field_data) +{ + ima_show_template_field_data(m, show, DATA_FMT_STRING, field_data); +} + +void ima_show_template_sig(struct seq_file *m, enum ima_show_type show, + struct ima_field_data *field_data) +{ + ima_show_template_field_data(m, show, DATA_FMT_HEX, field_data); +} + +static int ima_eventdigest_init_common(u8 *digest, u32 digestsize, u8 hash_algo, + struct ima_field_data *field_data) +{ + /* + * digest formats: + * - DATA_FMT_DIGEST: digest + * - DATA_FMT_DIGEST_WITH_ALGO: [<hash algo>] + ':' + '\0' + digest, + * where <hash algo> is provided if the hash algoritm is not + * SHA1 or MD5 + */ + u8 buffer[CRYPTO_MAX_ALG_NAME + 2 + IMA_MAX_DIGEST_SIZE] = { 0 }; + enum data_formats fmt = DATA_FMT_DIGEST; + u32 offset = 0; + + if (hash_algo < HASH_ALGO__LAST) { + fmt = DATA_FMT_DIGEST_WITH_ALGO; + offset += snprintf(buffer, CRYPTO_MAX_ALG_NAME + 1, "%s", + hash_algo_name[hash_algo]); + buffer[offset] = ':'; + offset += 2; + } + + if (digest) + memcpy(buffer + offset, digest, digestsize); + else + /* + * If digest is NULL, the event being recorded is a violation. + * Make room for the digest by increasing the offset of + * IMA_DIGEST_SIZE. + */ + offset += IMA_DIGEST_SIZE; + + return ima_write_template_field_data(buffer, offset + digestsize, + fmt, field_data); +} + +/* + * This function writes the digest of an event (with size limit). + */ +int ima_eventdigest_init(struct integrity_iint_cache *iint, struct file *file, + const unsigned char *filename, + struct evm_ima_xattr_data *xattr_value, int xattr_len, + struct ima_field_data *field_data) +{ + struct { + struct ima_digest_data hdr; + char digest[IMA_MAX_DIGEST_SIZE]; + } hash; + u8 *cur_digest = NULL; + u32 cur_digestsize = 0; + struct inode *inode; + int result; + + memset(&hash, 0, sizeof(hash)); + + if (!iint) /* recording a violation. */ + goto out; + + if (ima_template_hash_algo_allowed(iint->ima_hash->algo)) { + cur_digest = iint->ima_hash->digest; + cur_digestsize = iint->ima_hash->length; + goto out; + } + + if (!file) /* missing info to re-calculate the digest */ + return -EINVAL; + + inode = file_inode(file); + hash.hdr.algo = ima_template_hash_algo_allowed(ima_hash_algo) ? + ima_hash_algo : HASH_ALGO_SHA1; + result = ima_calc_file_hash(file, &hash.hdr); + if (result) { + integrity_audit_msg(AUDIT_INTEGRITY_DATA, inode, + filename, "collect_data", + "failed", result, 0); + return result; + } + cur_digest = hash.hdr.digest; + cur_digestsize = hash.hdr.length; +out: + return ima_eventdigest_init_common(cur_digest, cur_digestsize, + HASH_ALGO__LAST, field_data); +} + +/* + * This function writes the digest of an event (without size limit). + */ +int ima_eventdigest_ng_init(struct integrity_iint_cache *iint, + struct file *file, const unsigned char *filename, + struct evm_ima_xattr_data *xattr_value, + int xattr_len, struct ima_field_data *field_data) +{ + u8 *cur_digest = NULL, hash_algo = HASH_ALGO_SHA1; + u32 cur_digestsize = 0; + + /* If iint is NULL, we are recording a violation. */ + if (!iint) + goto out; + + cur_digest = iint->ima_hash->digest; + cur_digestsize = iint->ima_hash->length; + + hash_algo = iint->ima_hash->algo; +out: + return ima_eventdigest_init_common(cur_digest, cur_digestsize, + hash_algo, field_data); +} + +static int ima_eventname_init_common(struct integrity_iint_cache *iint, + struct file *file, + const unsigned char *filename, + struct ima_field_data *field_data, + bool size_limit) +{ + const char *cur_filename = NULL; + u32 cur_filename_len = 0; + + BUG_ON(filename == NULL && file == NULL); + + if (filename) { + cur_filename = filename; + cur_filename_len = strlen(filename); + + if (!size_limit || cur_filename_len <= IMA_EVENT_NAME_LEN_MAX) + goto out; + } + + if (file) { + cur_filename = file->f_dentry->d_name.name; + cur_filename_len = strlen(cur_filename); + } else + /* + * Truncate filename if the latter is too long and + * the file descriptor is not available. + */ + cur_filename_len = IMA_EVENT_NAME_LEN_MAX; +out: + return ima_write_template_field_data(cur_filename, cur_filename_len, + DATA_FMT_STRING, field_data); +} + +/* + * This function writes the name of an event (with size limit). + */ +int ima_eventname_init(struct integrity_iint_cache *iint, struct file *file, + const unsigned char *filename, + struct evm_ima_xattr_data *xattr_value, int xattr_len, + struct ima_field_data *field_data) +{ + return ima_eventname_init_common(iint, file, filename, + field_data, true); +} + +/* + * This function writes the name of an event (without size limit). + */ +int ima_eventname_ng_init(struct integrity_iint_cache *iint, struct file *file, + const unsigned char *filename, + struct evm_ima_xattr_data *xattr_value, int xattr_len, + struct ima_field_data *field_data) +{ + return ima_eventname_init_common(iint, file, filename, + field_data, false); +} + +/* + * ima_eventsig_init - include the file signature as part of the template data + */ +int ima_eventsig_init(struct integrity_iint_cache *iint, struct file *file, + const unsigned char *filename, + struct evm_ima_xattr_data *xattr_value, int xattr_len, + struct ima_field_data *field_data) +{ + enum data_formats fmt = DATA_FMT_HEX; + int rc = 0; + + if ((!xattr_value) || (xattr_value->type != EVM_IMA_XATTR_DIGSIG)) + goto out; + + rc = ima_write_template_field_data(xattr_value, xattr_len, fmt, + field_data); +out: + return rc; +} diff --git a/security/integrity/ima/ima_template_lib.h b/security/integrity/ima/ima_template_lib.h new file mode 100644 index 00000000000..63f6b52cb1c --- /dev/null +++ b/security/integrity/ima/ima_template_lib.h @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2013 Politecnico di Torino, Italy + * TORSEC group -- http://security.polito.it + * + * Author: 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. + * + * File: ima_template_lib.h + * Header for the library of supported template fields. + */ +#ifndef __LINUX_IMA_TEMPLATE_LIB_H +#define __LINUX_IMA_TEMPLATE_LIB_H + +#include <linux/seq_file.h> +#include "ima.h" + +void ima_show_template_digest(struct seq_file *m, enum ima_show_type show, + struct ima_field_data *field_data); +void ima_show_template_digest_ng(struct seq_file *m, enum ima_show_type show, + struct ima_field_data *field_data); +void ima_show_template_string(struct seq_file *m, enum ima_show_type show, + struct ima_field_data *field_data); +void ima_show_template_sig(struct seq_file *m, enum ima_show_type show, + struct ima_field_data *field_data); +int ima_eventdigest_init(struct integrity_iint_cache *iint, struct file *file, + const unsigned char *filename, + struct evm_ima_xattr_data *xattr_value, int xattr_len, + struct ima_field_data *field_data); +int ima_eventname_init(struct integrity_iint_cache *iint, struct file *file, + const unsigned char *filename, + struct evm_ima_xattr_data *xattr_value, int xattr_len, + struct ima_field_data *field_data); +int ima_eventdigest_ng_init(struct integrity_iint_cache *iint, + struct file *file, const unsigned char *filename, + struct evm_ima_xattr_data *xattr_value, + int xattr_len, struct ima_field_data *field_data); +int ima_eventname_ng_init(struct integrity_iint_cache *iint, struct file *file, + const unsigned char *filename, + struct evm_ima_xattr_data *xattr_value, int xattr_len, + struct ima_field_data *field_data); +int ima_eventsig_init(struct integrity_iint_cache *iint, struct file *file, + const unsigned char *filename, + struct evm_ima_xattr_data *xattr_value, int xattr_len, + struct ima_field_data *field_data); +#endif /* __LINUX_IMA_TEMPLATE_LIB_H */ |
