From: Steven Chen <[email protected]>

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 <[email protected]>
Signed-off-by: Anirudh Venkataramanan <[email protected]>
---
 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 <[email protected]>
+ *
+ * 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 <[email protected]>
+ *
+ * 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


Reply via email to