[RFC v1 1/1] ima: Implement IMA event log trimming

Anirudh Venkataramanan anirudhve at linux.microsoft.com
Wed Nov 19 21:33:18 UTC 2025


From: Steven Chen <chenste at linux.microsoft.com>

Implement a PCR value based method for IMA event log trimming by
exposing a new pseudo file /sys/kernel/config/ima/pcrs. This
pseudo file allows userspace to:

 - read IMA starting PCR values.
 - write trim-to PCR values to trigger IMA log trimming.

If a trimming operation is successful, one or more entries will be
purged from the IMA measurements list in the kernel.

Signed-off-by: Steven Chen <chenste at linux.microsoft.com>
Signed-off-by: Anirudh Venkataramanan <anirudhve at linux.microsoft.com>
---
 drivers/Kconfig                       |   2 +
 drivers/Makefile                      |   1 +
 drivers/ima/Kconfig                   |  13 +
 drivers/ima/Makefile                  |   2 +
 drivers/ima/ima_config_pcrs.c         | 291 ++++++++++++++++++
 include/linux/ima.h                   |  27 ++
 security/integrity/ima/Makefile       |   4 +
 security/integrity/ima/ima.h          |   8 +
 security/integrity/ima/ima_init.c     |  44 +++
 security/integrity/ima/ima_log_trim.c | 421 ++++++++++++++++++++++++++
 security/integrity/ima/ima_policy.c   |   7 +-
 security/integrity/ima/ima_queue.c    |   5 +-
 12 files changed, 821 insertions(+), 4 deletions(-)
 create mode 100644 drivers/ima/Kconfig
 create mode 100644 drivers/ima/Makefile
 create mode 100644 drivers/ima/ima_config_pcrs.c
 create mode 100644 security/integrity/ima/ima_log_trim.c

diff --git a/drivers/Kconfig b/drivers/Kconfig
index 4915a63866b0..35b83be86c42 100644
--- a/drivers/Kconfig
+++ b/drivers/Kconfig
@@ -251,4 +251,6 @@ source "drivers/hte/Kconfig"
 
 source "drivers/cdx/Kconfig"
 
+source "drivers/ima/Kconfig"
+
 endmenu
diff --git a/drivers/Makefile b/drivers/Makefile
index 8e1ffa4358d5..3aad6096d416 100644
--- a/drivers/Makefile
+++ b/drivers/Makefile
@@ -197,3 +197,4 @@ obj-$(CONFIG_DPLL)		+= dpll/
 
 obj-$(CONFIG_DIBS)		+= dibs/
 obj-$(CONFIG_S390)		+= s390/
+obj-$(CONFIG_IMA_PCRS)		+= ima/
diff --git a/drivers/ima/Kconfig b/drivers/ima/Kconfig
new file mode 100644
index 000000000000..9e465fbe3adb
--- /dev/null
+++ b/drivers/ima/Kconfig
@@ -0,0 +1,13 @@
+# SPDX-License-Identifier: GPL-2.0-only
+config IMA_PCRS
+	bool "IMA Event Log Trimming"
+	default n
+	help
+	  Say Y here if you want support for IMA Event Log Trimming.
+
+	  This creates the configfs file /sys/kernel/config/ima/pcrs.
+	  Userspace
+	  - writes to this file to trigger IMA event log trimming
+	  - reads this file to get IMA starting PCR values
+
+	  If unsure, say N.
diff --git a/drivers/ima/Makefile b/drivers/ima/Makefile
new file mode 100644
index 000000000000..ac9b9b96b5cb
--- /dev/null
+++ b/drivers/ima/Makefile
@@ -0,0 +1,2 @@
+# SPDX-License-Identifier: GPL-2.0-only
+obj-$(CONFIG_IMA_PCRS)	+= ima_config_pcrs.o
diff --git a/drivers/ima/ima_config_pcrs.c b/drivers/ima/ima_config_pcrs.c
new file mode 100644
index 000000000000..f329665f2b90
--- /dev/null
+++ b/drivers/ima/ima_config_pcrs.c
@@ -0,0 +1,291 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2025 Microsoft Corporation
+ *
+ * Authors:
+ * Steven Chen <chenste at linux.microsoft.com>
+ *
+ * File: ima_config_pcrs.c
+ *
+ * Implements IMA interface to allow userspace to read IMA starting PCR
+ * values and trigger IMA event log trimming.
+ */
+
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/configfs.h>
+#include <linux/printk.h>
+#include <linux/tpm.h>
+#include <linux/ima.h>
+/*
+ * The IMA PCR configfs is a childless subsystem.  It cannot create
+ * any config_items.  It just has attribute pcrs for read and write.
+ */
+
+#define MAX_HASH_ALGO_NAME_LENGTH 16 /*sha1, sha256, ...*/
+#define PCR_NAME_LEN 6  /*pcrxx:*/
+#define MIN_PCR_RECORD_LENGTH 20
+#define OFFSET_IDX_1 3
+#define OFFSET_IDX_2 4
+#define OFFSET_COLON 5
+
+/*
+ * mutex protects atomicity of trimming measurement list
+ * and updating ima_start_point_pcr_values
+ * protects concurrent writes to the IMA PCR values
+ * This also not allow memory allocation while waiting the mutex
+ */
+static DEFINE_MUTEX(ima_pcr_write_mutex);
+
+/* show current IMA Start Point PCR values in ascii format */
+static ssize_t ima_config_pcrs_ascii_show(struct config_item *item, char *page)
+{
+	int len = 0;
+
+	for (int i = 0; i < num_tpm_banks; i++) {
+		for (int j = 0; j < num_pcr_configured; j++) {
+			int algorithm_id = ima_start_point_pcr_values[i].alg_id;
+
+			/*
+			 * Write "pcrXX:AAA:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
+			 * where XX is the PCR index (00-23),
+			 * where AAA is the algorithm name, such as sha1, sha256, sha384..
+			 * and x...x is the digest data in hex format
+			 */
+			len += sprintf(page + len, "pcr%02d:", digests_pcr_map[j]);
+			len += sprintf(page + len, "%s:", hash_algo_name[algorithm_id]);
+			for (int k = 0; k < hash_digest_size[algorithm_id]; k++)
+				len += sprintf(page + len, "%02x",
+					ima_start_point_pcr_values[i].digests[j][k]);
+			len += sprintf(page + len, "\n");
+		}
+	}
+	return len;
+}
+
+/* show current IMA Start Point PCR values */
+static ssize_t ima_config_pcrs_show(struct config_item *item, char *page)
+{
+	int len = 0;
+
+	for (int i = 0; i < num_tpm_banks; i++) {
+		int algorithm_id = ima_start_point_pcr_values[i].alg_id;
+
+		for (int j = 0; j < TPM2_PLATFORM_PCR; ++j) {
+			int digest_index = pcr_digests_map[j];
+
+			if (digest_index == -1)
+				continue;
+			/*
+			 * Write "pcrXX:AAA:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
+			 * where XX is the PCR index (00-23),
+			 * where AAA is the algorithm name, such as sha1, sha256, sha384..
+			 * and x...x is the digest data in raw format
+			 */
+			len += sprintf(page + len, "pcr%02d:", j);
+			len += sprintf(page + len, "%s:", hash_algo_name[algorithm_id]);
+
+			memcpy(page + len, ima_start_point_pcr_values[i].digests[digest_index],
+			       hash_digest_size[algorithm_id]);
+
+			len += hash_digest_size[algorithm_id];
+		}
+	}
+
+	return len;
+}
+
+/* get PCR index from input string */
+static int get_pcr_idx(char *p, int offset)
+{
+	/* validate digits first */
+	if (!isdigit((unsigned char)p[offset + OFFSET_IDX_1]) ||
+	    !isdigit((unsigned char)p[offset + OFFSET_IDX_2]))
+		return -EINVAL;
+
+	return (p[offset + OFFSET_IDX_1] - '0') * 10 + (p[offset + OFFSET_IDX_2] - '0');
+}
+
+/*
+ * Parse the input data
+ * Get new PCR values and trim IMA event log
+ */
+static ssize_t ima_config_pcrs_store(struct config_item *item, const char *page, size_t count)
+{
+	struct ima_pcr_value *ima_pcr_values;
+	unsigned long pcr_found = 0;
+	char *p = (char *)page;
+	int pcr_count;
+	int offset = 0;
+	int ret = -EINVAL;
+
+	if (count <= 0)
+		return ret;
+
+	if (num_pcr_configured <= 0)
+		return -ENOMEM;
+
+	/*
+	 * Only one thread can write to the PCRs at a time
+	 * Not allocate memory while waiting the mutex
+	 */
+
+	mutex_lock(&ima_pcr_write_mutex);
+
+	pcr_count = num_pcr_configured;
+
+	ima_pcr_values = kcalloc(num_pcr_configured, sizeof(*ima_pcr_values), GFP_KERNEL);
+
+	if (!ima_pcr_values) {
+		ret = -ENOMEM;
+		goto out_notrim_unlock;
+	}
+
+	/*
+	 * parse the input data
+	 * each entry is like: pcrX:AAA:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+	 * where XX is the PCR index (00-23),
+	 * where AAA is the algorithm name, e.g. sha1, sha256, sha384..
+	 * and x...x is the digest data in binary format
+	 * multiple entries are concatenated together
+	 */
+	while (offset < count) {
+		int index, mapped_index, algo_id, algo_name_len = 0;
+		char algo_name[MAX_HASH_ALGO_NAME_LENGTH];
+		u8 digest_len;
+
+		/* Need at least "pcrXX:AAA:" */
+		if (offset + MIN_PCR_RECORD_LENGTH > count)
+			goto out_nottrim;
+
+		if (memcmp(p + offset, "pcr", 3) != 0 || p[offset + OFFSET_COLON] != ':') {
+			pr_alert("invalid input format\n");
+			goto out_nottrim;
+		}
+
+		/* Get the PCR index*/
+		index = get_pcr_idx(p, offset);
+		if (index < 0 || index >= TPM2_PLATFORM_PCR) {
+			pr_alert("invalid PCR index: %d\n", index);
+			goto out_nottrim;
+		}
+
+		offset += PCR_NAME_LEN;
+
+		while (offset + algo_name_len < count && p[offset + algo_name_len] != ':')
+			++algo_name_len;
+
+		/* Check actual algo name length */
+		if (algo_name_len == 0 || algo_name_len >= MAX_HASH_ALGO_NAME_LENGTH) {
+			pr_err("ima pcr configuration: invalid algorithm name length\n");
+			goto out_nottrim;
+		}
+
+		memcpy(algo_name, p + offset, algo_name_len);
+		algo_name[algo_name_len] = '\0';
+
+		algo_id = match_string(hash_algo_name, HASH_ALGO__LAST, algo_name);
+		/* validate algo_id */
+		if (algo_id < HASH_ALGO_SHA1 || algo_id >= HASH_ALGO__LAST) {
+			pr_err("ima pcr configuration: invalid algorithm ID\n");
+			goto out_nottrim;
+		}
+		digest_len = hash_digest_size[algo_id];
+		offset += algo_name_len + 1;
+		/* validate we have enough data for the digest */
+		if (offset + digest_len > count)
+			goto out_nottrim;
+
+		mapped_index = pcr_digests_map[index];
+
+		if (pcr_digests_map[index] != -1 && !test_bit(index, &pcr_found)) {
+			ima_pcr_values[mapped_index].algo_id = algo_id;
+			set_bit(index, &pcr_found);
+			memcpy(ima_pcr_values[mapped_index].digest, p + offset, digest_len);
+			--pcr_count;
+		} else {
+			pr_alert("invalid PCR index or duplicate PCR set index: %d\n", index);
+			goto out_nottrim;
+		}
+		offset += digest_len;
+	}
+
+	if (pcr_count != 0) {
+		/* not all PCRs are provided */
+		pr_alert("not all PCRs are provided\n");
+		goto out_nottrim;
+	}
+
+	/*
+	 * all configured PCRs values are provided, now recalculate the IMA PCRs
+	 * and check if they can match the these PCR values
+	 * Trim the IMA event logs if the given PCR values match
+	 */
+	ret = ima_trim_event_log((const void *)ima_pcr_values);
+	if (ret >= 0)
+		ret = count;
+
+out_nottrim:
+	kfree(ima_pcr_values);
+out_notrim_unlock:
+	mutex_unlock(&ima_pcr_write_mutex);
+	return ret;
+}
+
+CONFIGFS_ATTR(ima_config_, pcrs);
+CONFIGFS_ATTR_RO(ima_config_, pcrs_ascii);
+
+static struct configfs_attribute *ima_config_pcr_attrs[] = {
+	&ima_config_attr_pcrs,
+	&ima_config_attr_pcrs_ascii,
+	NULL,
+};
+
+static const struct config_item_type ima_config_pcr_type = {
+	.ct_attrs	= ima_config_pcr_attrs,
+	.ct_owner	= THIS_MODULE,
+};
+
+static struct configfs_subsystem config_ima_pcrs = {
+	.su_group = {
+		.cg_item = {
+			.ci_namebuf = "ima",
+			.ci_type = &ima_config_pcr_type,
+		},
+	},
+};
+
+static int __init configfs_ima_pcrs_init(void)
+{
+	struct tpm_chip *tpm_chip;
+	int ret;
+
+	tpm_chip = tpm_default_chip();
+	if (!tpm_chip) {
+		pr_err("No TPM chip found, IMA PCR configfs disabled\n");
+		return -ENODEV;
+	}
+
+	config_group_init(&config_ima_pcrs.su_group);
+	mutex_init(&config_ima_pcrs.su_mutex);
+	ret = configfs_register_subsystem(&config_ima_pcrs);
+	if (ret) {
+		pr_err("Error %d while registering subsystem %s\n",
+		       ret, config_ima_pcrs.su_group.cg_item.ci_namebuf);
+		return ret;
+	}
+
+	return 0;
+}
+
+static void __exit configfs_ima_pcrs_exit(void)
+{
+	configfs_unregister_subsystem(&config_ima_pcrs);
+}
+
+module_init(configfs_ima_pcrs_init);
+module_exit(configfs_ima_pcrs_exit);
+MODULE_DESCRIPTION("IMA PCRS configfs module");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/ima.h b/include/linux/ima.h
index 8e29cb4e6a01..f8a6209b9423 100644
--- a/include/linux/ima.h
+++ b/include/linux/ima.h
@@ -12,6 +12,8 @@
 #include <linux/security.h>
 #include <linux/kexec.h>
 #include <crypto/hash_info.h>
+#include <linux/tpm.h>
+
 struct linux_binprm;
 
 #ifdef CONFIG_IMA
@@ -24,6 +26,31 @@ extern int ima_measure_critical_data(const char *event_label,
 				     const void *buf, size_t buf_len,
 				     bool hash, u8 *digest, size_t digest_len);
 
+#ifdef CONFIG_IMA_PCRS
+struct ima_pcr_value {
+	u8 digest[HASH_MAX_DIGESTSIZE];	/* PCR value */
+	u8 algo_id;                     /* Hash algorithm ID */
+};
+
+struct tpm_bank_pcr_values {
+	u16 alg_id;
+	u8 **digests; /* Array of pointers, one per configured PCR */
+};
+
+extern u8 num_tpm_banks;
+extern u8 num_pcr_configured;
+
+extern signed char algo_pcr_bank_map[HASH_ALGO__LAST];
+extern signed char pcr_digests_map[TPM2_PLATFORM_PCR];
+extern signed char digests_pcr_map[TPM2_PLATFORM_PCR];
+
+extern struct tpm_bank_pcr_values *ima_start_point_pcr_values;
+
+extern int ima_trim_event_log(const void *bin);
+#else
+static inline int ima_trim_event_log(const void *bin) { return 0; }
+#endif /* CONFIG_IMA_PCRS */
+
 #ifdef CONFIG_IMA_APPRAISE_BOOTPARAM
 extern void ima_appraise_parse_cmdline(void);
 #else
diff --git a/security/integrity/ima/Makefile b/security/integrity/ima/Makefile
index b376d38b4ee6..ae9ce210638d 100644
--- a/security/integrity/ima/Makefile
+++ b/security/integrity/ima/Makefile
@@ -18,3 +18,7 @@ ima-$(CONFIG_IMA_QUEUE_EARLY_BOOT_KEYS) += ima_queue_keys.o
 ifeq ($(CONFIG_EFI),y)
 ima-$(CONFIG_IMA_SECURE_AND_OR_TRUSTED_BOOT) += ima_efi.o
 endif
+
+ifeq ($(CONFIG_IMA_PCRS),y)
+ima-y += ima_log_trim.o
+endif
diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h
index e3d71d8d56e3..d9accf504e42 100644
--- a/security/integrity/ima/ima.h
+++ b/security/integrity/ima/ima.h
@@ -55,6 +55,8 @@ struct ima_algo_desc {
 	enum hash_algo algo;
 };
 
+extern struct mutex ima_extend_list_mutex; /* protects extending measurement list */
+
 /* set during initialization */
 extern int ima_hash_algo __ro_after_init;
 extern int ima_sha1_idx __ro_after_init;
@@ -250,6 +252,12 @@ void ima_measure_kexec_event(const char *event_name);
 static inline void ima_measure_kexec_event(const char *event_name) {}
 #endif
 
+#ifdef CONFIG_IMA_PCRS
+extern int ima_add_configured_pcr(int new_pcr_index);
+#else
+static inline int ima_add_configured_pcr(int new_pcr_index) { return 0; }
+#endif /* CONFIG_IMA_PCRS */
+
 /*
  * The default binary_runtime_measurements list format is defined as the
  * platform native format.  The canonical format is defined as little-endian.
diff --git a/security/integrity/ima/ima_init.c b/security/integrity/ima/ima_init.c
index a2f34f2d8ad7..1102d2073336 100644
--- a/security/integrity/ima/ima_init.c
+++ b/security/integrity/ima/ima_init.c
@@ -24,6 +24,16 @@
 const char boot_aggregate_name[] = "boot_aggregate";
 struct tpm_chip *ima_tpm_chip;
 
+#ifdef CONFIG_IMA_PCRS
+u8 num_tpm_banks;
+u8 num_pcr_configured;
+signed char algo_pcr_bank_map[HASH_ALGO__LAST];
+signed char digests_pcr_map[TPM2_PLATFORM_PCR];
+signed char pcr_digests_map[TPM2_PLATFORM_PCR];
+
+struct tpm_bank_pcr_values *ima_start_point_pcr_values;
+#endif
+
 /* Add the boot aggregate to the IMA measurement list and extend
  * the PCR register.
  *
@@ -134,6 +144,40 @@ int __init ima_init(void)
 	if (rc != 0)
 		return rc;
 
+#ifdef CONFIG_IMA_PCRS
+
+	num_tpm_banks = 0;
+	num_pcr_configured = 0;
+
+	for (int i = 0; i < TPM2_PLATFORM_PCR; i++) {
+		pcr_digests_map[i] = -1;
+		digests_pcr_map[i] = -1;
+	}
+
+	for (int i = 0; i < HASH_ALGO__LAST; i++)
+		algo_pcr_bank_map[i] = -1;
+
+	if (ima_tpm_chip) {
+		num_tpm_banks = ima_tpm_chip->nr_allocated_banks;
+		/* Allocate memory for the ima start point PCR values */
+		ima_start_point_pcr_values = kcalloc(num_tpm_banks,
+						     sizeof(*ima_start_point_pcr_values),
+						     GFP_KERNEL);
+
+		if (!ima_start_point_pcr_values)
+			return -ENOMEM;
+
+		for (int i = 0; i < num_tpm_banks; i++) {
+			int algo_id = ima_tpm_chip->allocated_banks[i].crypto_id;
+
+			ima_start_point_pcr_values[i].alg_id = algo_id;
+			algo_pcr_bank_map[algo_id] = i;
+		}
+
+		ima_add_configured_pcr(CONFIG_IMA_MEASURE_PCR_IDX);
+	}
+#endif
+
 	/* It can be called before ima_init_digests(), it does not use TPM. */
 	ima_load_kexec_buffer();
 
diff --git a/security/integrity/ima/ima_log_trim.c b/security/integrity/ima/ima_log_trim.c
new file mode 100644
index 000000000000..85025d502db2
--- /dev/null
+++ b/security/integrity/ima/ima_log_trim.c
@@ -0,0 +1,421 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2025 Microsoft Corporation
+ *
+ * Authors:
+ * Steven Chen <chenste at linux.microsoft.com>
+ *
+ * File: ima_log_trim.c
+ *	Implements PCR value based IMA Event Log Trimming
+ */
+
+#include <linux/fcntl.h>
+#include <linux/kernel_read_file.h>
+#include <linux/slab.h>
+#include <linux/seq_file.h>
+#include <linux/rculist.h>
+#include <linux/rcupdate.h>
+#include <linux/vmalloc.h>
+#include <linux/ima.h>
+#include <crypto/hash_info.h>
+
+#include "ima.h"
+
+struct digest_value {
+	u8 digest[HASH_MAX_DIGESTSIZE];	/* digest value */
+};
+
+/* Average IMA event log data length */
+#define EXTEND_BUF_LEN 300
+
+static struct digest_value *ima_extended_pcr;
+static struct ima_pcr_value *target_pcr_values;
+static unsigned long pcr_matched;
+static int pcr_match_needed;
+
+/* Calculate the hash digest for the given data using the specified algorithm */
+static int ima_calculate_pcr(const void *data, size_t len, u8 *md, int algo)
+{
+	struct crypto_shash *tfm;
+	struct shash_desc *desc;
+	int ret = 0, size;
+
+	tfm = crypto_alloc_shash(hash_algo_name[algo], 0, 0);
+
+	if (IS_ERR(tfm)) {
+		ret = -ENOMEM;
+		goto out_nocleanup;
+	}
+
+	/* Allocate memory for the shash_desc structure and initialize it */
+	size = sizeof(struct shash_desc) + crypto_shash_descsize(tfm);
+
+	desc = kzalloc(size, GFP_KERNEL);
+	if (!desc) {
+		crypto_free_shash(tfm);
+		ret = -ENOMEM;
+		goto out_nocleanup;
+	}
+
+	desc->tfm = tfm;
+
+	if (crypto_shash_init(desc)) {
+		ret = -EINVAL;
+		goto out_cleanup;
+	}
+
+	/* Update the hash with the input data */
+	if (crypto_shash_update(desc, data, len)) {
+		ret = -EINVAL;
+		goto out_cleanup;
+	}
+
+	/* Finalize the hash and retrieve the digest */
+	ret = crypto_shash_final(desc, md);
+	if (ret)
+		goto out_cleanup;
+
+	ret = crypto_shash_digestsize(tfm);
+
+out_cleanup:
+	kfree(desc);
+	crypto_free_shash(tfm);
+out_nocleanup:
+	return ret;
+}
+
+/* Add a new configured PCR */
+int ima_add_configured_pcr(int new_pcr_index)
+{
+	if (!ima_tpm_chip)
+		return -1;
+
+	if (new_pcr_index < 0 || new_pcr_index >= TPM2_PLATFORM_PCR)
+		return -1;
+
+	if (pcr_digests_map[new_pcr_index] != -1)
+		return 0;
+
+	/*
+	 * For each TPM bank, expand one more entry for the new PCR
+	 * Allocate new PCR digest buffers for the new PCR
+	 */
+	for (int i = 0; i < num_tpm_banks; ++i) {
+		int length = hash_digest_size[ima_start_point_pcr_values[i].alg_id];
+		u8 *new_pcr = kmalloc(sizeof(u8) * length, GFP_KERNEL);
+		u8 **new_buf;
+
+		if (!new_pcr)
+			return -ENOMEM;
+		memset(new_pcr, 0, sizeof(u8) * length);
+		new_buf = (u8 **)krealloc(ima_start_point_pcr_values[i].digests,
+					(num_pcr_configured + 1) * sizeof(u8 *), GFP_KERNEL);
+		if (!new_buf) {
+			kfree(new_pcr);
+			return -ENOMEM;
+		}
+		new_buf[num_pcr_configured] = new_pcr;
+		ima_start_point_pcr_values[i].digests = new_buf;
+	}
+
+	pcr_digests_map[new_pcr_index] = num_pcr_configured;
+	digests_pcr_map[num_pcr_configured] = new_pcr_index;
+
+	++num_pcr_configured;
+	return 0;
+}
+
+/* Delete the IMA event logs */
+static int ima_purge_event_log(int number_logs)
+{
+	struct ima_queue_entry *qe;
+	int cur = 0;
+
+	mutex_lock(&ima_extend_list_mutex);
+	rcu_read_lock();
+
+	/*
+	 * Remove this entry from both hash table and the measurement list
+	 * When removing from hash table, decrease the length counter
+	 * so that the hash table re-sizing logic works correctly
+	 */
+	list_for_each_entry_rcu(qe, &ima_measurements, later) {
+		/* if CONFIG_IMA_DISABLE_HTABLE is set, the hash table is not used */
+		if (!IS_ENABLED(CONFIG_IMA_DISABLE_HTABLE))
+			hlist_del_rcu(&qe->hnext);
+
+		atomic_long_dec(&ima_htable.len);
+		list_del_rcu(&qe->later);
+		++cur;
+		if (cur >= number_logs)
+			break;
+	}
+
+	rcu_read_unlock();
+	mutex_unlock(&ima_extend_list_mutex);
+	return cur;
+}
+
+/* compare the target PCR values with IMA Start Point PCR Values */
+static int ima_compare_pcr_values(struct ima_pcr_value *target_pcr_val)
+{
+	int count_not_matched = 0;
+
+	for (int i = 0; i < num_pcr_configured; ++i) {
+		int algo_id_tmp;
+		int bank_id_tmp;
+		int hash_size;
+
+		algo_id_tmp = target_pcr_val[i].algo_id;
+		bank_id_tmp = algo_pcr_bank_map[algo_id_tmp];
+		hash_size = hash_digest_size[algo_id_tmp];
+
+		if (bank_id_tmp < 0 || bank_id_tmp >= num_tpm_banks)
+			return -EINVAL;
+
+		/* already matched this PCR, skip it */
+		if (memcmp(ima_start_point_pcr_values[bank_id_tmp].digests[i],
+			   target_pcr_val[i].digest, hash_size) == 0) {
+			set_bit(i, &pcr_matched);
+			continue;
+		}
+		count_not_matched++;
+	}
+	return count_not_matched;
+}
+
+static int ima_recalculate_check_pcrs(int pcr_map_index, char *org_pointer, char *digest_pointer,
+				      int total_length, bool digest_zero)
+{
+	int ret = 0;
+
+	/* Recalculate the PCR for each bank */
+	for (int i = 0; i < num_tpm_banks; i++) {
+		int digest_len, crypto_id, hash_size, idx, len;
+		u8 digest[HASH_MAX_DIGESTSIZE];
+
+		crypto_id = ima_start_point_pcr_values[i].alg_id;
+		hash_size = hash_digest_size[crypto_id];
+		digest_len = 0;
+		/* Prepare digest buffer */
+		if (digest_zero) {
+			/* all zero digest, use 0xFF to extend */
+			memset(digest, 0xFF, HASH_MAX_DIGESTSIZE);
+			digest_len = hash_size;
+		} else {
+			memcpy(digest_pointer, org_pointer, total_length);
+			digest_len = ima_calculate_pcr(digest_pointer, total_length, digest,
+						       crypto_id);
+			if (digest_len < 0) {
+				ret = digest_len;
+				break;
+			}
+		}
+
+		len = digest_len + hash_size;
+
+		idx = i * num_pcr_configured + pcr_map_index;
+		memcpy(digest_pointer, ima_extended_pcr[idx].digest, hash_size);
+		memcpy(digest_pointer + hash_size, digest, digest_len);
+
+		/* Recalculate the PCR starting with the IMA Start Point PCR value */
+		digest_len = ima_calculate_pcr(digest_pointer, len, ima_extended_pcr[idx].digest,
+					       crypto_id);
+
+		if (digest_len < 0) {
+			ret = digest_len;
+			break;
+		}
+
+		/*
+		 * Check if the extended PCR value matches the target PCR value
+		 * if matched, mark this PCR as matched
+		 * if all PCRs matched, set the entry_found flag
+		 */
+		if (crypto_id == target_pcr_values[pcr_map_index].algo_id) {
+			if (memcmp(ima_extended_pcr[idx].digest,
+				   target_pcr_values[pcr_map_index].digest, hash_size) == 0) {
+				set_bit(pcr_map_index, &pcr_matched);
+				--pcr_match_needed;
+			}
+		}
+	}
+
+	return ret;
+}
+
+static int ima_get_log_count(void)
+{
+	u8 algo_digest_buffer[EXTEND_BUF_LEN];
+	u8 digest_buffer[EXTEND_BUF_LEN];
+	struct ima_queue_entry *qe;
+	int count = 0;
+	int ret = 0;
+	unsigned int hash_digest_length;
+
+	/* Event log digests algorithm is SHA1 */
+	hash_digest_length = hash_digest_size[HASH_ALGO_SHA1];
+	list_for_each_entry_rcu(qe, &ima_measurements, later) {
+		char *org_digest_pointer, *digest_pointer;
+		int pcr_idx, pcr_map_index, total_length;
+		struct ima_template_entry *e = qe->entry;
+		bool digest_zero;
+
+		if (!qe->entry) {
+			ret = -EINVAL;
+			break;
+		}
+		pcr_idx = e->pcr;
+		pcr_map_index = pcr_digests_map[pcr_idx];
+
+		if (test_bit(pcr_map_index, &pcr_matched) || pcr_digests_map[pcr_idx] == -1) {
+			/* already matched this PCR, something wrong */
+			ret = -EINVAL;
+			break;
+		}
+		/* The original digest buffer is used to save data for multiple banks/algorithms */
+		org_digest_pointer = digest_buffer;
+		digest_pointer = algo_digest_buffer;
+
+		total_length = e->template_data_len;
+
+		/* Allocate large memory for the original and digest buffers if needed */
+		if (total_length > EXTEND_BUF_LEN) {
+			org_digest_pointer = kzalloc(total_length, GFP_KERNEL);
+			if (!org_digest_pointer) {
+				ret = -ENOMEM;
+				break;
+			}
+			digest_pointer = kzalloc(total_length, GFP_KERNEL);
+			if (!digest_pointer) {
+				kfree(org_digest_pointer);
+				ret = -ENOMEM;
+				break;
+			}
+		}
+
+		digest_zero = true;
+		/*
+		 * Check if the original digest is all zeros or not
+		 * if not all zero, use template data to recalculate PCR
+		 */
+		if (memchr_inv(e->digests->digest, 0, hash_digest_length) != NULL) {
+			int offset = 0;
+
+			for (int i = 0; i < e->template_desc->num_fields; i++) {
+				memcpy(org_digest_pointer + offset, &e->template_data[i].len,
+				       sizeof(e->template_data[i].len));
+				offset += sizeof(e->template_data[i].len);
+				memcpy(org_digest_pointer + offset, e->template_data[i].data,
+				       e->template_data[i].len);
+				offset += e->template_data[i].len;
+			}
+			digest_zero = false;
+		}
+
+		count++;
+
+		/* Check if this log entry can match the target PCRs */
+		ret = ima_recalculate_check_pcrs(pcr_map_index, org_digest_pointer,
+						 digest_pointer, total_length, digest_zero);
+
+		if (total_length > EXTEND_BUF_LEN) {
+			kfree(org_digest_pointer);
+			kfree(digest_pointer);
+		}
+
+		/* If entry found or error occurred, break the loop */
+		if (ret < 0 || pcr_match_needed <= 0)
+			break;
+	}
+
+	if (ret < 0)
+		return ret;
+
+	if (pcr_match_needed <= 0)
+		return count;
+	else
+		return 0;
+}
+
+/*
+ * Trim the IMA event log to match the given PCR values
+ * Return:
+ *  >0: number of log entries removed
+ *   0: no log entries removed
+ *  -1: error
+ *  -ENOENT: no matching log entry found
+ *  -EIO: I/O error when removing log entries
+ *  -EINVAL: invalid parameter
+ *  -ENOMEM: memory allocation failure
+ */
+int ima_trim_event_log(const void *bin)
+{
+	int count, ret = -1;
+
+	count = 0;
+	target_pcr_values = (struct ima_pcr_value *)bin;
+	pcr_matched = 0;
+
+	pcr_match_needed = ima_compare_pcr_values(target_pcr_values);
+
+	/* No need to trim */
+	if (pcr_match_needed <= 0) {
+		ret = 0;
+		goto out_nofree;
+	}
+
+	ima_extended_pcr = kcalloc(num_tpm_banks * num_pcr_configured,
+				   sizeof(*ima_extended_pcr), GFP_KERNEL);
+
+	if (!ima_extended_pcr) {
+		ret = -ENOMEM;
+		goto out_nofree;
+	}
+
+	/* Initialize ima_extended_pcr with ima_start_point_pcr_values */
+	for (int i = 0; i < num_tpm_banks; i++) {
+		int length = hash_digest_size[ima_start_point_pcr_values[i].alg_id];
+
+		for (int j = 0; j < num_pcr_configured; ++j) {
+			int record_index = i * num_pcr_configured + j;
+
+			memcpy(ima_extended_pcr[record_index].digest,
+			       ima_start_point_pcr_values[i].digests[j], length);
+		}
+	}
+
+	ret = ima_get_log_count();
+
+	if (ret <= 0)
+		goto out_notrim;
+
+	count = ret;
+
+	/* Remove logs from the IMA log list */
+	ret = ima_purge_event_log(count);
+
+	if (ret == count) {
+		/* Update the IMA Start Point PCR values */
+		for (int i = 0; i < num_tpm_banks; i++) {
+			int algorithm_id = ima_start_point_pcr_values[i].alg_id;
+			int hash_size = hash_digest_size[algorithm_id];
+
+			for (int j = 0; j < num_pcr_configured; j++) {
+				int ext_idx = i * num_pcr_configured + j;
+
+				memcpy(ima_start_point_pcr_values[i].digests[j],
+				       ima_extended_pcr[ext_idx].digest, hash_size);
+			}
+		}
+	} else {
+		/* something wrong, should not happen */
+		ret = -EIO;
+	}
+
+out_notrim:
+	kfree(ima_extended_pcr);
+
+out_nofree:
+	return ret;
+}
diff --git a/security/integrity/ima/ima_policy.c b/security/integrity/ima/ima_policy.c
index 7468afaab686..fe537827ac1f 100644
--- a/security/integrity/ima/ima_policy.c
+++ b/security/integrity/ima/ima_policy.c
@@ -1895,10 +1895,13 @@ static int ima_parse_rule(char *rule, struct ima_rule_entry *entry)
 			ima_log_string(ab, "pcr", args[0].from);
 
 			result = kstrtoint(args[0].from, 10, &entry->pcr);
-			if (result || INVALID_PCR(entry->pcr))
+			if (result || INVALID_PCR(entry->pcr)) {
 				result = -EINVAL;
-			else
+			} else {
 				entry->flags |= IMA_PCR;
+				if (ima_add_configured_pcr(entry->pcr) < 0)
+					result = -EINVAL;
+			}
 
 			break;
 		case Opt_template:
diff --git a/security/integrity/ima/ima_queue.c b/security/integrity/ima/ima_queue.c
index 590637e81ad1..7bbfdd2ce3b0 100644
--- a/security/integrity/ima/ima_queue.c
+++ b/security/integrity/ima/ima_queue.c
@@ -39,11 +39,12 @@ struct ima_h_table ima_htable = {
 	.queue[0 ... IMA_MEASURE_HTABLE_SIZE - 1] = HLIST_HEAD_INIT
 };
 
-/* mutex protects atomicity of extending measurement list
+/*
+ * This mutex protects atomicity of extending measurement list
  * and extending the TPM PCR aggregate. Since tpm_extend can take
  * long (and the tpm driver uses a mutex), we can't use the spinlock.
  */
-static DEFINE_MUTEX(ima_extend_list_mutex);
+DEFINE_MUTEX(ima_extend_list_mutex);
 
 /*
  * Used internally by the kernel to suspend measurements.
-- 
2.43.0




More information about the Linux-security-module-archive mailing list