diff options
Diffstat (limited to 'security/integrity/ima/ima_appraise.c')
-rw-r--r-- | security/integrity/ima/ima_appraise.c | 181 |
1 files changed, 143 insertions, 38 deletions
diff --git a/security/integrity/ima/ima_appraise.c b/security/integrity/ima/ima_appraise.c index 300c8d2943c5..dbba51583e7c 100644 --- a/security/integrity/ima/ima_appraise.c +++ b/security/integrity/ima/ima_appraise.c @@ -5,6 +5,7 @@ * Author: * Mimi Zohar <zohar@us.ibm.com> */ +#include <linux/module.h> #include <linux/init.h> #include <linux/file.h> #include <linux/fs.h> @@ -16,20 +17,41 @@ #include "ima.h" -static int __init default_appraise_setup(char *str) -{ #ifdef CONFIG_IMA_APPRAISE_BOOTPARAM +static char *ima_appraise_cmdline_default __initdata; +core_param(ima_appraise, ima_appraise_cmdline_default, charp, 0); + +void __init ima_appraise_parse_cmdline(void) +{ + const char *str = ima_appraise_cmdline_default; + bool sb_state = arch_ima_get_secureboot(); + int appraisal_state = ima_appraise; + + if (!str) + return; + if (strncmp(str, "off", 3) == 0) - ima_appraise = 0; + appraisal_state = 0; else if (strncmp(str, "log", 3) == 0) - ima_appraise = IMA_APPRAISE_LOG; + appraisal_state = IMA_APPRAISE_LOG; else if (strncmp(str, "fix", 3) == 0) - ima_appraise = IMA_APPRAISE_FIX; -#endif - return 1; + appraisal_state = IMA_APPRAISE_FIX; + else if (strncmp(str, "enforce", 7) == 0) + appraisal_state = IMA_APPRAISE_ENFORCE; + else + pr_err("invalid \"%s\" appraise option", str); + + /* If appraisal state was changed, but secure boot is enabled, + * keep its default */ + if (sb_state) { + if (!(appraisal_state & IMA_APPRAISE_ENFORCE)) + pr_info("Secure boot enabled: ignoring ima_appraise=%s option", + str); + } else { + ima_appraise = appraisal_state; + } } - -__setup("ima_appraise=", default_appraise_setup); +#endif /* * is_ima_appraise_enabled - return appraise status @@ -46,16 +68,18 @@ bool is_ima_appraise_enabled(void) * * Return 1 to appraise or hash */ -int ima_must_appraise(struct inode *inode, int mask, enum ima_hooks func) +int ima_must_appraise(struct user_namespace *mnt_userns, struct inode *inode, + int mask, enum ima_hooks func) { u32 secid; if (!ima_appraise) return 0; - security_task_getsecid(current, &secid); - return ima_match_policy(inode, current_cred(), secid, func, mask, - IMA_APPRAISE | IMA_HASH, NULL, NULL); + security_task_getsecid_subj(current, &secid); + return ima_match_policy(mnt_userns, inode, current_cred(), secid, + func, mask, IMA_APPRAISE | IMA_HASH, NULL, + NULL, NULL, NULL); } static int ima_fix_xattr(struct dentry *dentry, @@ -72,7 +96,7 @@ static int ima_fix_xattr(struct dentry *dentry, 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, + rc = __vfs_setxattr_noperm(&init_user_ns, dentry, XATTR_NAME_IMA, &iint->ima_hash->xattr.data[offset], (sizeof(iint->ima_hash->xattr) - offset) + iint->ima_hash->length, 0); @@ -148,7 +172,7 @@ static void ima_cache_flags(struct integrity_iint_cache *iint, } } -enum hash_algo ima_get_hash_algo(struct evm_ima_xattr_data *xattr_value, +enum hash_algo ima_get_hash_algo(const struct evm_ima_xattr_data *xattr_value, int xattr_len) { struct signature_v2_hdr *sig; @@ -161,7 +185,8 @@ enum hash_algo ima_get_hash_algo(struct evm_ima_xattr_data *xattr_value, switch (xattr_value->type) { case EVM_IMA_XATTR_DIGSIG: sig = (typeof(sig))xattr_value; - if (sig->version != 2 || xattr_len <= sizeof(*sig)) + if (sig->version != 2 || xattr_len <= sizeof(*sig) + || sig->hash_algo >= HASH_ALGO__LAST) return ima_hash_algo; return sig->hash_algo; break; @@ -193,8 +218,8 @@ int ima_read_xattr(struct dentry *dentry, { ssize_t ret; - ret = vfs_getxattr_alloc(dentry, XATTR_NAME_IMA, (char **)xattr_value, - 0, GFP_NOFS); + ret = vfs_getxattr_alloc(&init_user_ns, dentry, XATTR_NAME_IMA, + (char **)xattr_value, 0, GFP_NOFS); if (ret == -EOPNOTSUPP) ret = 0; return ret; @@ -217,14 +242,18 @@ static int xattr_verify(enum ima_hooks func, struct integrity_iint_cache *iint, case IMA_XATTR_DIGEST_NG: /* first byte contains algorithm id */ hash_start = 1; - /* fall through */ + fallthrough; case IMA_XATTR_DIGEST: - if (iint->flags & IMA_DIGSIG_REQUIRED) { - *cause = "IMA-signature-required"; - *status = INTEGRITY_FAIL; - break; + if (*status != INTEGRITY_PASS_IMMUTABLE) { + if (iint->flags & IMA_DIGSIG_REQUIRED) { + *cause = "IMA-signature-required"; + *status = INTEGRITY_FAIL; + break; + } + clear_bit(IMA_DIGSIG, &iint->atomic_flags); + } else { + set_bit(IMA_DIGSIG, &iint->atomic_flags); } - clear_bit(IMA_DIGSIG, &iint->atomic_flags); if (xattr_len - sizeof(xattr_value->type) - hash_start >= iint->ima_hash->length) /* @@ -328,9 +357,9 @@ int ima_check_blacklist(struct integrity_iint_cache *iint, rc = is_binary_blacklisted(digest, digestsize); if ((rc == -EPERM) && (iint->flags & IMA_MEASURE)) - process_buffer_measurement(digest, digestsize, + process_buffer_measurement(&init_user_ns, NULL, digest, digestsize, "blacklisted-hash", NONE, - pcr); + pcr, NULL, false, NULL, 0); } return rc; @@ -389,10 +418,14 @@ int ima_appraise_measurement(enum ima_hooks func, /* It's fine not to have xattrs when using a modsig. */ if (try_modsig) break; - /* fall through */ + fallthrough; case INTEGRITY_NOLABEL: /* No security.evm xattr. */ cause = "missing-HMAC"; goto out; + case INTEGRITY_FAIL_IMMUTABLE: + set_bit(IMA_DIGSIG, &iint->atomic_flags); + cause = "invalid-fail-immutable"; + goto out; case INTEGRITY_FAIL: /* Invalid HMAC/signature. */ cause = "invalid-HMAC"; goto out; @@ -436,9 +469,12 @@ out: status = INTEGRITY_PASS; } - /* Permit new files with file signatures, but without data. */ + /* + * Permit new files with file/EVM portable signatures, but + * without data. + */ if (inode->i_size == 0 && iint->flags & IMA_NEW_FILE && - xattr_value && xattr_value->type == EVM_IMA_XATTR_DIGSIG) { + test_bit(IMA_DIGSIG, &iint->atomic_flags)) { status = INTEGRITY_PASS; } @@ -479,6 +515,7 @@ void ima_update_xattr(struct integrity_iint_cache *iint, struct file *file) /** * ima_inode_post_setattr - reflect file metadata changes + * @mnt_userns: user namespace of the mount the inode was found from * @dentry: pointer to the affected dentry * * Changes to a dentry's metadata might result in needing to appraise. @@ -486,7 +523,8 @@ void ima_update_xattr(struct integrity_iint_cache *iint, struct file *file) * 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) +void ima_inode_post_setattr(struct user_namespace *mnt_userns, + struct dentry *dentry) { struct inode *inode = d_backing_inode(dentry); struct integrity_iint_cache *iint; @@ -496,9 +534,7 @@ void ima_inode_post_setattr(struct dentry *dentry) || !(inode->i_opflags & IOP_XATTR)) return; - action = ima_must_appraise(inode, MAY_ACCESS, POST_SETATTR); - if (!action) - __vfs_removexattr(dentry, XATTR_NAME_IMA); + action = ima_must_appraise(mnt_userns, inode, MAY_ACCESS, POST_SETATTR); iint = integrity_iint_find(inode); if (iint) { set_bit(IMA_CHANGE_ATTR, &iint->atomic_flags); @@ -541,10 +577,71 @@ static void ima_reset_appraise_flags(struct inode *inode, int digsig) clear_bit(IMA_DIGSIG, &iint->atomic_flags); } +/** + * validate_hash_algo() - Block setxattr with unsupported hash algorithms + * @dentry: object of the setxattr() + * @xattr_value: userland supplied xattr value + * @xattr_value_len: length of xattr_value + * + * The xattr value is mapped to its hash algorithm, and this algorithm + * must be built in the kernel for the setxattr to be allowed. + * + * Emit an audit message when the algorithm is invalid. + * + * Return: 0 on success, else an error. + */ +static int validate_hash_algo(struct dentry *dentry, + const struct evm_ima_xattr_data *xattr_value, + size_t xattr_value_len) +{ + char *path = NULL, *pathbuf = NULL; + enum hash_algo xattr_hash_algo; + const char *errmsg = "unavailable-hash-algorithm"; + unsigned int allowed_hashes; + + xattr_hash_algo = ima_get_hash_algo(xattr_value, xattr_value_len); + + allowed_hashes = atomic_read(&ima_setxattr_allowed_hash_algorithms); + + if (allowed_hashes) { + /* success if the algorithm is allowed in the ima policy */ + if (allowed_hashes & (1U << xattr_hash_algo)) + return 0; + + /* + * We use a different audit message when the hash algorithm + * is denied by a policy rule, instead of not being built + * in the kernel image + */ + errmsg = "denied-hash-algorithm"; + } else { + if (likely(xattr_hash_algo == ima_hash_algo)) + return 0; + + /* allow any xattr using an algorithm built in the kernel */ + if (crypto_has_alg(hash_algo_name[xattr_hash_algo], 0, 0)) + return 0; + } + + pathbuf = kmalloc(PATH_MAX, GFP_KERNEL); + if (!pathbuf) + return -EACCES; + + path = dentry_path(dentry, pathbuf, PATH_MAX); + + integrity_audit_msg(AUDIT_INTEGRITY_DATA, d_inode(dentry), path, + "set_data", errmsg, -EACCES, 0); + + kfree(pathbuf); + + return -EACCES; +} + 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 digsig = 0; int result; result = ima_protect_xattr(dentry, xattr_name, xattr_value, @@ -552,9 +649,16 @@ int ima_inode_setxattr(struct dentry *dentry, const char *xattr_name, if (result == 1) { if (!xattr_value_len || (xvalue->type >= IMA_XATTR_LAST)) return -EINVAL; - ima_reset_appraise_flags(d_backing_inode(dentry), - xvalue->type == EVM_IMA_XATTR_DIGSIG); - result = 0; + digsig = (xvalue->type == EVM_IMA_XATTR_DIGSIG); + } else if (!strcmp(xattr_name, XATTR_NAME_EVM) && xattr_value_len > 0) { + digsig = (xvalue->type == EVM_XATTR_PORTABLE_DIGSIG); + } + if (result == 1 || evm_revalidate_status(xattr_name)) { + result = validate_hash_algo(dentry, xvalue, xattr_value_len); + if (result) + return result; + + ima_reset_appraise_flags(d_backing_inode(dentry), digsig); } return result; } @@ -564,9 +668,10 @@ 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) { + if (result == 1 || evm_revalidate_status(xattr_name)) { ima_reset_appraise_flags(d_backing_inode(dentry), 0); - result = 0; + if (result == 1) + result = 0; } return result; } |