From: Roberto Sassu <roberto.sassu@xxxxxxxxxx> Add a digest cache pointer to the ima_iint_cache structure and introduce ima_digest_cache_get_check() to retrieve a fresh digest cache and compare with the pointer stored in the previous calls (if digest cache was enabled in the IMA policy). If the pointers don't match, reset the integrity status since the digest cache used for the previous verification might have changed. Also, initialize and put the digest cache respectively in ima_iint_init_always() and ima_iint_free(). Call ima_digest_cache_get_check() with the iint->mutex held, to protect the assignment of the digest cache pointer in the inode integrity metadata. Change mutex_lock() to mutex_lock_nested() to avoid a lockdep warning due to a possible deadlock (recursive iint->mutex lock, during a kernel read). Nesting is safe for files opened by the Integrity Digest Cache, because the latter guarantees that it will never cause IMA to be invoked with the same inode. Call digest_cache_opened_fd() to know when nesting is safe, and pass the boolean to mutex_lock_nested(). Signed-off-by: Roberto Sassu <roberto.sassu@xxxxxxxxxx> --- security/integrity/ima/Makefile | 1 + security/integrity/ima/ima.h | 1 + security/integrity/ima/ima_digest_cache.c | 49 +++++++++++++++++++++++ security/integrity/ima/ima_digest_cache.h | 22 ++++++++++ security/integrity/ima/ima_iint.c | 4 ++ security/integrity/ima/ima_main.c | 13 +++++- 6 files changed, 88 insertions(+), 2 deletions(-) create mode 100644 security/integrity/ima/ima_digest_cache.c create mode 100644 security/integrity/ima/ima_digest_cache.h diff --git a/security/integrity/ima/Makefile b/security/integrity/ima/Makefile index b376d38b4ee6..b4a284634a07 100644 --- a/security/integrity/ima/Makefile +++ b/security/integrity/ima/Makefile @@ -14,6 +14,7 @@ ima-$(CONFIG_HAVE_IMA_KEXEC) += ima_kexec.o ima-$(CONFIG_IMA_BLACKLIST_KEYRING) += ima_mok.o ima-$(CONFIG_IMA_MEASURE_ASYMMETRIC_KEYS) += ima_asymmetric_keys.o ima-$(CONFIG_IMA_QUEUE_EARLY_BOOT_KEYS) += ima_queue_keys.o +ima-$(CONFIG_INTEGRITY_DIGEST_CACHE) += ima_digest_cache.o ifeq ($(CONFIG_EFI),y) ima-$(CONFIG_IMA_SECURE_AND_OR_TRUSTED_BOOT) += ima_efi.o diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h index 64667f16a30f..f3e6dcd9defd 100644 --- a/security/integrity/ima/ima.h +++ b/security/integrity/ima/ima.h @@ -198,6 +198,7 @@ struct ima_iint_cache { enum integrity_status ima_read_status:4; enum integrity_status ima_creds_status:4; struct ima_digest_data *ima_hash; + struct digest_cache *digest_cache; }; extern struct lsm_blob_sizes ima_blob_sizes; diff --git a/security/integrity/ima/ima_digest_cache.c b/security/integrity/ima/ima_digest_cache.c new file mode 100644 index 000000000000..ad47772a05bd --- /dev/null +++ b/security/integrity/ima/ima_digest_cache.c @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2024 Huawei Technologies Duesseldorf GmbH + * + * Author: Roberto Sassu <roberto.sassu@xxxxxxxxxx> + * + * Integrate with the Integrity Digest Cache. + */ + +#include <linux/digest_cache.h> + +#include "ima_digest_cache.h" + +/** + * ima_digest_cache_get_check - Get digest cache and check if changed + * @file: File descriptor of the inode for which the digest cache will be used + * @iint: Inode integrity metadata + * + * Get a digest cache for the file descriptor parameter and compare with the + * digest cache stored in the inode integrity metadata. + * + * It must be called with the iint->mutex held. + * + * Return: True if the digest cache pointer changed, false otherwise. + */ +bool ima_digest_cache_get_check(struct file *file, + struct ima_iint_cache *iint) +{ + struct digest_cache *digest_cache; + + digest_cache = digest_cache_get(file); + + /* There was no digest cache before, not changed. */ + if (!iint->digest_cache) { + iint->digest_cache = digest_cache; + return false; + } + + /* New digest cache not available, or digest cache changed. */ + if (!digest_cache || iint->digest_cache != digest_cache) { + digest_cache_put(iint->digest_cache); + iint->digest_cache = digest_cache; + return true; + } + + /* Digest cache not changed. */ + digest_cache_put(digest_cache); + return false; +} diff --git a/security/integrity/ima/ima_digest_cache.h b/security/integrity/ima/ima_digest_cache.h new file mode 100644 index 000000000000..8126ae1e2f4f --- /dev/null +++ b/security/integrity/ima/ima_digest_cache.h @@ -0,0 +1,22 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2024 Huawei Technologies Duesseldorf GmbH + * + * Author: Roberto Sassu <roberto.sassu@xxxxxxxxxx> + * + * Header file of ima_digest_cache.c. + */ + +#include "ima.h" + +#ifdef CONFIG_INTEGRITY_DIGEST_CACHE +bool ima_digest_cache_get_check(struct file *file, + struct ima_iint_cache *iint); +#else +static inline bool ima_digest_cache_get_check(struct file *file, + struct ima_iint_cache *iint) +{ + return false; +} + +#endif /* CONFIG_INTEGRITY_DIGEST_CACHE */ diff --git a/security/integrity/ima/ima_iint.c b/security/integrity/ima/ima_iint.c index 00b249101f98..7ed7e857d738 100644 --- a/security/integrity/ima/ima_iint.c +++ b/security/integrity/ima/ima_iint.c @@ -68,12 +68,16 @@ static void ima_iint_init_always(struct ima_iint_cache *iint, iint->ima_read_status = INTEGRITY_UNKNOWN; iint->ima_creds_status = INTEGRITY_UNKNOWN; iint->measured_pcrs = 0; + iint->digest_cache = NULL; mutex_init(&iint->mutex); ima_iint_lockdep_annotate(iint, inode); } static void ima_iint_free(struct ima_iint_cache *iint) { + if (iint->digest_cache) + digest_cache_put(iint->digest_cache); + kfree(iint->ima_hash); mutex_destroy(&iint->mutex); kmem_cache_free(ima_iint_cache, iint); diff --git a/security/integrity/ima/ima_main.c b/security/integrity/ima/ima_main.c index bcbf64bb03c2..aaff8cd8d5c6 100644 --- a/security/integrity/ima/ima_main.c +++ b/security/integrity/ima/ima_main.c @@ -29,6 +29,7 @@ #include <linux/evm.h> #include "ima.h" +#include "ima_digest_cache.h" #ifdef CONFIG_IMA_APPRAISE int ima_appraise = IMA_APPRAISE_ENFORCE; @@ -224,6 +225,7 @@ static int process_measurement(struct file *file, const struct cred *cred, bool violation_check; enum hash_algo hash_algo; unsigned int allowed_algos = 0; + u64 policy_usage = 0ULL; if (!ima_policy_flag || !S_ISREG(inode->i_mode)) return 0; @@ -234,7 +236,7 @@ static int process_measurement(struct file *file, const struct cred *cred, */ action = ima_get_action(file_mnt_idmap(file), inode, cred, secid, mask, func, &pcr, &template_desc, NULL, - &allowed_algos, NULL); + &allowed_algos, &policy_usage); violation_check = ((func == FILE_CHECK || func == MMAP_CHECK || func == MMAP_CHECK_REQPROT) && (ima_policy_flag & IMA_MEASURE)); @@ -266,7 +268,7 @@ static int process_measurement(struct file *file, const struct cred *cred, if (!action) goto out; - mutex_lock(&iint->mutex); + mutex_lock_nested(&iint->mutex, digest_cache_opened_fd(file)); if (test_and_clear_bit(IMA_CHANGE_ATTR, &iint->atomic_flags)) /* reset appraisal flags if ima_inode_post_setattr was called */ @@ -287,6 +289,13 @@ static int process_measurement(struct file *file, const struct cred *cred, iint->measured_pcrs = 0; } + /* Digest cache changed, reset integrity status. */ + if (policy_usage && + ima_digest_cache_get_check(file, iint)) { + iint->flags &= ~IMA_DONE_MASK; + iint->measured_pcrs = 0; + } + /* * On stacked filesystems, detect and re-evaluate file data and * metadata changes. -- 2.47.0.118.gfd3785337b