From: Roberto Sassu <[email protected]>

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 <[email protected]>
---
Changelog:
v2:
 - Not present in this version

v1:
 - Not present in this version
---
 security/integrity/ima/ima.h       |  3 +-
 security/integrity/ima/ima_init.c  |  5 +++
 security/integrity/ima/ima_queue.c | 49 +++++++++++++++++++++++++++---
 3 files changed, 51 insertions(+), 6 deletions(-)

diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h
index 1f2c81ec0fba..ccd037d49de7 100644
--- a/security/integrity/ima/ima.h
+++ b/security/integrity/ima/ima.h
@@ -285,6 +285,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);
@@ -300,7 +301,7 @@ extern spinlock_t ima_queue_lock;
 
 extern atomic_long_t ima_num_entries;
 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 4837fc6d9ada..2050b9d21e70 100644
--- a/security/integrity/ima/ima_queue.c
+++ b/security/integrity/ima/ima_queue.c
@@ -38,9 +38,7 @@ atomic_long_t ima_num_entries = 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
@@ -54,17 +52,54 @@ 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);
+
+       /* Synchronize_rcu() and kfree() not necessary, only for robustness. */
+       synchronize_rcu();
+
+       if (IS_ERR(old_htable))
+               return PTR_ERR(old_htable);
+
+       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)) {
@@ -104,6 +139,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);
@@ -116,10 +152,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_entries);
        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) {
-- 
2.43.0


Reply via email to