From: Roberto Sassu Date: Fri, 5 Jun 2026 17:22:26 +0000 (+0200) Subject: ima: Replace static htable queue with dynamically allocated array X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=7bc01800a7739972626e366766f54c3e76cc3e69;p=thirdparty%2Fkernel%2Flinux.git ima: Replace static htable queue with dynamically allocated array The IMA hash table is a fixed-size array of hlist_head buckets: struct hlist_head ima_htable[IMA_MEASURE_HTABLE_SIZE]; IMA_MEASURE_HTABLE_SIZE is (1 << IMA_HASH_BITS) = 1024 buckets, each a struct hlist_head (one pointer, 8 bytes on 64-bit). That is 8 KiB allocated in BSS for every kernel, regardless of whether IMA is ever used, and regardless of how many measurements are actually made. Replace the fixed-size array with a RCU-protected pointer to a dynamically allocated array that is initialized in ima_init_htable(), which is called from ima_init() during early boot. ima_init_htable() calls the static function ima_alloc_replace_htable() which, other than initializing the hash table the first time, can also hot-swap the existing hash table with a blank one. The allocation in ima_alloc_replace_htable() uses kcalloc() so the buckets are zero-initialised (equivalent to HLIST_HEAD_INIT { .first = NULL }). Callers of ima_alloc_replace_htable() must call synchronize_rcu() and free the returned hash table. Finally, access the hash table with rcu_dereference() in ima_lookup_digest_entry() (reader side) and with rcu_dereference_protected() in ima_add_digest_entry() (writer side). No functional change: bucket count, hash function, and all locking remain identical. Link: https://github.com/linux-integrity/linux/issues/1 Signed-off-by: Roberto Sassu Signed-off-by: Mimi Zohar --- diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h index b3ad7eac6a1e..0e41c2113efd 100644 --- a/security/integrity/ima/ima.h +++ b/security/integrity/ima/ima.h @@ -311,6 +311,7 @@ bool ima_template_has_modsig(const struct ima_template_desc *ima_template); int ima_restore_measurement_entry(struct ima_template_entry *entry); int ima_restore_measurement_list(loff_t bufsize, void *buf); int ima_measurements_show(struct seq_file *m, void *v); +int __init ima_init_htable(void); unsigned long ima_get_binary_runtime_size(void); int ima_init_template(void); void ima_init_template_list(void); @@ -328,7 +329,7 @@ extern spinlock_t ima_queue_lock; extern atomic_long_t ima_num_records; /* Total number of violations since hard boot. */ extern atomic_long_t ima_num_violations; -extern struct hlist_head ima_htable[IMA_MEASURE_HTABLE_SIZE]; +extern struct hlist_head __rcu *ima_htable; static inline unsigned int ima_hash_key(u8 *digest) { diff --git a/security/integrity/ima/ima_init.c b/security/integrity/ima/ima_init.c index a2f34f2d8ad7..7e0aa09a12e6 100644 --- a/security/integrity/ima/ima_init.c +++ b/security/integrity/ima/ima_init.c @@ -140,6 +140,11 @@ int __init ima_init(void) rc = ima_init_digests(); if (rc != 0) return rc; + + rc = ima_init_htable(); + if (rc != 0) + return rc; + rc = ima_add_boot_aggregate(); /* boot aggregate must be first entry */ if (rc != 0) return rc; diff --git a/security/integrity/ima/ima_queue.c b/security/integrity/ima/ima_queue.c index 6bdaefc790c3..a31b75d9302b 100644 --- a/security/integrity/ima/ima_queue.c +++ b/security/integrity/ima/ima_queue.c @@ -36,9 +36,7 @@ atomic_long_t ima_num_records = ATOMIC_LONG_INIT(0); atomic_long_t ima_num_violations = ATOMIC_LONG_INIT(0); /* key: inode (before secure-hashing a file) */ -struct hlist_head ima_htable[IMA_MEASURE_HTABLE_SIZE] = { - [0 ... IMA_MEASURE_HTABLE_SIZE - 1] = HLIST_HEAD_INIT -}; +struct hlist_head __rcu *ima_htable; /* mutex protects atomicity of extending measurement list * and extending the TPM PCR aggregate. Since tpm_extend can take @@ -52,17 +50,53 @@ static DEFINE_MUTEX(ima_extend_list_mutex); */ static bool ima_measurements_suspended; +/* Callers must call synchronize_rcu() and free the hash table. */ +static struct hlist_head *ima_alloc_replace_htable(void) +{ + struct hlist_head *old_htable, *new_htable; + + /* Initializing to zeros is equivalent to call HLIST_HEAD_INIT. */ + new_htable = kcalloc(IMA_MEASURE_HTABLE_SIZE, sizeof(struct hlist_head), + GFP_KERNEL); + if (!new_htable) + return ERR_PTR(-ENOMEM); + + old_htable = rcu_replace_pointer(ima_htable, new_htable, + lockdep_is_held(&ima_extend_list_mutex)); + + return old_htable; +} + +int __init ima_init_htable(void) +{ + struct hlist_head *old_htable; + + mutex_lock(&ima_extend_list_mutex); + old_htable = ima_alloc_replace_htable(); + mutex_unlock(&ima_extend_list_mutex); + + if (IS_ERR(old_htable)) + return PTR_ERR(old_htable); + + /* Synchronize_rcu() and kfree() not necessary, only for robustness. */ + synchronize_rcu(); + kfree(old_htable); + return 0; +} + /* lookup up the digest value in the hash table, and return the entry */ static struct ima_queue_entry *ima_lookup_digest_entry(u8 *digest_value, int pcr) { struct ima_queue_entry *qe, *ret = NULL; + struct hlist_head *htable; unsigned int key; int rc; key = ima_hash_key(digest_value); rcu_read_lock(); - hlist_for_each_entry_rcu(qe, &ima_htable[key], hnext) { + htable = rcu_dereference(ima_htable); + hlist_for_each_entry_rcu(qe, &htable[key], hnext) { rc = memcmp(qe->entry->digests[ima_hash_algo_idx].digest, digest_value, hash_digest_size[ima_hash_algo]); if ((rc == 0) && (qe->entry->pcr == pcr)) { @@ -102,6 +136,7 @@ static int ima_add_digest_entry(struct ima_template_entry *entry, bool update_htable) { struct ima_queue_entry *qe; + struct hlist_head *htable; unsigned int key; qe = kmalloc_obj(*qe); @@ -114,10 +149,13 @@ static int ima_add_digest_entry(struct ima_template_entry *entry, INIT_LIST_HEAD(&qe->later); list_add_tail_rcu(&qe->later, &ima_measurements); + htable = rcu_dereference_protected(ima_htable, + lockdep_is_held(&ima_extend_list_mutex)); + atomic_long_inc(&ima_num_records); if (update_htable) { key = ima_hash_key(entry->digests[ima_hash_algo_idx].digest); - hlist_add_head_rcu(&qe->hnext, &ima_htable[key]); + hlist_add_head_rcu(&qe->hnext, &htable[key]); } if (binary_runtime_size != ULONG_MAX) {