[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