diff mbox series

[v2,1/2] IMA: Define workqueue for early boot "key" measurements

Message ID 20191211185116.2740-2-nramas@linux.microsoft.com (mailing list archive)
State New, archived
Headers show
Series IMA: Deferred measurement of keys | expand

Commit Message

Lakshmi Ramasubramanian Dec. 11, 2019, 6:51 p.m. UTC
Measuring keys requires a custom IMA policy to be loaded.
Keys created or updated before a custom IMA policy is loaded should
be queued and the keys should be processed after a custom policy
is loaded.

This patch defines workqueue for queuing keys when a custom IMA policy
has not yet been loaded.

A flag namely ima_process_keys is used to check if the key should be
queued or should be processed immediately.

Signed-off-by: Lakshmi Ramasubramanian <nramas@linux.microsoft.com>
---
 security/integrity/ima/ima.h                 |  15 +++
 security/integrity/ima/ima_asymmetric_keys.c | 110 +++++++++++++++++++
 2 files changed, 125 insertions(+)

Comments

Mimi Zohar Dec. 12, 2019, 8:19 a.m. UTC | #1
On Wed, 2019-12-11 at 10:51 -0800, Lakshmi Ramasubramanian wrote:
> Measuring keys requires a custom IMA policy to be loaded.
> Keys created or updated before a custom IMA policy is loaded should
> be queued and the keys should be processed after a custom policy
> is loaded.
> 
> This patch defines workqueue for queuing keys when a custom IMA policy
> has not yet been loaded.
> 
> A flag namely ima_process_keys is used to check if the key should be
> queued or should be processed immediately.
> 
> Signed-off-by: Lakshmi Ramasubramanian <nramas@linux.microsoft.com>
> ---
>  security/integrity/ima/ima.h                 |  15 +++
>  security/integrity/ima/ima_asymmetric_keys.c | 110 +++++++++++++++++++
>  2 files changed, 125 insertions(+)
> 
> diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h
> index f06238e41a7c..97f8a4078483 100644
> --- a/security/integrity/ima/ima.h
> +++ b/security/integrity/ima/ima.h
> @@ -205,6 +205,21 @@ extern const char *const func_tokens[];
>  
>  struct modsig;
>  
> +#ifdef CONFIG_ASYMMETRIC_PUBLIC_KEY_SUBTYPE
> +/*
> + * To track keys that need to be measured.
> + */
> +struct ima_key_entry {
> +	struct list_head list;
> +	void *payload;
> +	size_t payload_len;
> +	char *keyring_name;
> +};
> +void ima_process_queued_keys(void);
> +#else
> +static inline void ima_process_queued_keys(void) {}
> +#endif /* CONFIG_ASYMMETRIC_PUBLIC_KEY_SUBTYPE */
> +
>  /* LIM API function definitions */
>  int ima_get_action(struct inode *inode, const struct cred *cred, u32 secid,
>  		   int mask, enum ima_hooks func, int *pcr,
> diff --git a/security/integrity/ima/ima_asymmetric_keys.c b/security/integrity/ima/ima_asymmetric_keys.c
> index fea2e7dd3b09..ba01e04ec025 100644
> --- a/security/integrity/ima/ima_asymmetric_keys.c
> +++ b/security/integrity/ima/ima_asymmetric_keys.c
> @@ -14,6 +14,116 @@
>  #include <keys/asymmetric-type.h>
>  #include "ima.h"
>  
> +/*
> + * Flag to indicate whether a key can be processed
> + * right away or should be queued for processing later.
> + */
> +bool ima_process_keys;
> +
> +/*
> + * To synchronize access to the list of keys that need to be measured
> + */
> +static DEFINE_MUTEX(ima_keys_mutex);
> +static LIST_HEAD(ima_keys);
> +
> +static void ima_free_key_entry(struct ima_key_entry *entry)
> +{
> +	if (entry) {
> +		kfree(entry->payload);
> +		kfree(entry->keyring_name);
> +		kfree(entry);
> +	}
> +}
> +
> +static struct ima_key_entry *ima_alloc_key_entry(
> +	struct key *keyring,
> +	const void *payload, size_t payload_len)
> +{
> +	int rc = 0;
> +	struct ima_key_entry *entry;
> +
> +	entry = kzalloc(sizeof(*entry), GFP_KERNEL);
> +	if (entry) {
> +		entry->payload = kmemdup(payload, payload_len, GFP_KERNEL);
> +		entry->keyring_name = kstrdup(keyring->description,
> +					      GFP_KERNEL);
> +		entry->payload_len = payload_len;
> +	}
> +
> +	if ((entry == NULL) || (entry->payload == NULL) ||
> +	    (entry->keyring_name == NULL)) {
> +		rc = -ENOMEM;
> +		goto out;
> +	}
> +
> +	INIT_LIST_HEAD(&entry->list);
> +
> +out:
> +	if (rc) {
> +		ima_free_key_entry(entry);
> +		entry = NULL;
> +	}
> +
> +	return entry;
> +}
> +
> +bool ima_queue_key(struct key *keyring, const void *payload,
> +		   size_t payload_len)
> +{
> +	bool queued = false;
> +	struct ima_key_entry *entry;
> +
> +	entry = ima_alloc_key_entry(keyring, payload, payload_len);
> +	if (!entry)
> +		return false;
> +
> +	mutex_lock(&ima_keys_mutex);
> +	if (!ima_process_keys) {
> +		list_add_tail(&entry->list, &ima_keys);
> +		queued = true;
> +	}
> +	mutex_unlock(&ima_keys_mutex);
> +
> +	if (!queued)
> +		ima_free_key_entry(entry);
> +
> +	return queued;
> +}
> +
> +/*
> + * ima_process_queued_keys() - process keys queued for measurement
> + *
> + * This function sets ima_process_keys to true and processes queued keys.
> + * From here on keys will be processed right away (not queued).
> + */
> +void ima_process_queued_keys(void)
> +{
> +	struct ima_key_entry *entry, *tmp;
> +	LIST_HEAD(temp_ima_keys);
> +
> +	if (ima_process_keys)
> +		return;
> +
> +	ima_process_keys = true;
> +
> +	INIT_LIST_HEAD(&temp_ima_keys);
> +
> +	mutex_lock(&ima_keys_mutex);
> +
> +	list_for_each_entry_safe(entry, tmp, &ima_keys, list)
> +		list_move_tail(&entry->list, &temp_ima_keys);
> +
> +	mutex_unlock(&ima_keys_mutex);


The v1 comment, which explained the need for using a temporary
keyring, is an example of an informative comment.  If you don't
object, instead of re-posting this patch, I can insert it.

Mimi

> +
> +	list_for_each_entry_safe(entry, tmp, &temp_ima_keys, list) {
> +		process_buffer_measurement(entry->payload, entry->payload_len,
> +					   entry->keyring_name, KEY_CHECK, 0,
> +					   entry->keyring_name);
> +		list_del(&entry->list);
> +		ima_free_key_entry(entry);
> +	}
> +}
> +
>  /**
>   * ima_post_key_create_or_update - measure asymmetric keys
>   * @keyring: keyring to which the key is linked to
Lakshmi Ramasubramanian Dec. 12, 2019, 4:57 p.m. UTC | #2
On 12/12/19 12:19 AM, Mimi Zohar wrote:

>>> +	ima_process_keys = true;
>> +
>> +	INIT_LIST_HEAD(&temp_ima_keys);
>> +
>> +	mutex_lock(&ima_keys_mutex);
>> +
>> +	list_for_each_entry_safe(entry, tmp, &ima_keys, list)
>> +		list_move_tail(&entry->list, &temp_ima_keys);
>> +
>> +	mutex_unlock(&ima_keys_mutex);
> 
> 
> The v1 comment, which explained the need for using a temporary
> keyring, is an example of an informative comment.  If you don't
> object, instead of re-posting this patch, I can insert it.
> 
> Mimi

Sure Mimi. Thanks for including the comment in the patch.

thanks,
  -lakshmi
Mimi Zohar Dec. 12, 2019, 9:13 p.m. UTC | #3
On Thu, 2019-12-12 at 08:57 -0800, Lakshmi Ramasubramanian wrote:
> On 12/12/19 12:19 AM, Mimi Zohar wrote:
> 
> >>> +	ima_process_keys = true;
> >> +
> >> +	INIT_LIST_HEAD(&temp_ima_keys);
> >> +
> >> +	mutex_lock(&ima_keys_mutex);
> >> +
> >> +	list_for_each_entry_safe(entry, tmp, &ima_keys, list)
> >> +		list_move_tail(&entry->list, &temp_ima_keys);
> >> +
> >> +	mutex_unlock(&ima_keys_mutex);
> > 
> > 
> > The v1 comment, which explained the need for using a temporary
> > keyring, is an example of an informative comment.  If you don't
> > object, instead of re-posting this patch, I can insert it.
> 
> Sure Mimi. Thanks for including the comment in the patch.

Looking at this again, something seems off or at least the comment 
doesn't match the code.

       /*
         * To avoid holding the mutex while processing queued keys,
         * transfer the queued keys with the mutex held to a temp list,
         * release the mutex, and then process the queued keys from
         * the temp list.
         *
         * Since ima_process_keys is set to true above, any new key will
         * be processed immediately and not queued.
         */

Setting ima_process_key before taking the lock won't prevent the race.
 I think you want to test ima_process_keys before taking the lock and
again immediately afterward taking the lock, before setting it.  Then
the comment would match the code.

Shouldn't ima_process_keys be defined as static to limit the scope to
this file?

Mimi
Lakshmi Ramasubramanian Dec. 12, 2019, 9:59 p.m. UTC | #4
On 12/12/19 1:13 PM, Mimi Zohar wrote:

> 
> Looking at this again, something seems off or at least the comment
> doesn't match the code.
> 
>         /*
>           * To avoid holding the mutex while processing queued keys,
>           * transfer the queued keys with the mutex held to a temp list,
>           * release the mutex, and then process the queued keys from
>           * the temp list.
>           *
>           * Since ima_process_keys is set to true above, any new key will
>           * be processed immediately and not queued.
>           */
> 
> Setting ima_process_key before taking the lock won't prevent the race.
>   I think you want to test ima_process_keys before taking the lock and
> again immediately afterward taking the lock, before setting it.  Then
> the comment would match the code.
> 
> Shouldn't ima_process_keys be defined as static to limit the scope to
> this file?
> 
> Mimi
> 

In IMA hook, ima_process_key is checked without lock. If it is false, 
ima_queue_key is called. If the key was queued (by ima_queue_key()) then 
the hook defers measurement. Else, it processes it immediately.

In ima_queue_key() function the check for ima_process_key is done after 
taking the lock and the key queued if the flag is false.

In ima_process_keys() ima_process_key is set without lock and then the 
queued keys are moved to a temp list after taking the lock.

I have reviewed the changes myself and also with a few of my colleagues. 
I don't think there is a race condition. Please let me know if you do 
see a problem.

I can move the setting of ima_process_key flag inside the lock. But 
honestly I don't think that is necessary.

I agree that ima_process_keys should be static since it is used in this 
file one. I'll make that change.

I can also move the setting of ima_process_key flag inside the lock 
along with the above change.

thanks,
  -lakshmi
Mimi Zohar Dec. 12, 2019, 10:54 p.m. UTC | #5
On Thu, 2019-12-12 at 13:59 -0800, Lakshmi Ramasubramanian wrote:
> On 12/12/19 1:13 PM, Mimi Zohar wrote:
> 
> > 
> > Looking at this again, something seems off or at least the comment
> > doesn't match the code.
> > 
> >         /*
> >           * To avoid holding the mutex while processing queued keys,
> >           * transfer the queued keys with the mutex held to a temp list,
> >           * release the mutex, and then process the queued keys from
> >           * the temp list.
> >           *
> >           * Since ima_process_keys is set to true above, any new key will
> >           * be processed immediately and not queued.
> >           */
> > 
> > Setting ima_process_key before taking the lock won't prevent the race.
> >   I think you want to test ima_process_keys before taking the lock and
> > again immediately afterward taking the lock, before setting it.  Then
> > the comment would match the code.
> > 
> > Shouldn't ima_process_keys be defined as static to limit the scope to
> > this file?
> > 
> > Mimi
> > 
> 
> In IMA hook, ima_process_key is checked without lock. If it is false, 
> ima_queue_key is called. If the key was queued (by ima_queue_key()) then 
> the hook defers measurement. Else, it processes it immediately.
> 
> In ima_queue_key() function the check for ima_process_key is done after 
> taking the lock and the key queued if the flag is false.
> 
> In ima_process_keys() ima_process_key is set without lock and then the 
> queued keys are moved to a temp list after taking the lock.
> 
> I have reviewed the changes myself and also with a few of my colleagues. 
> I don't think there is a race condition. Please let me know if you do 
> see a problem.
> 
> I can move the setting of ima_process_key flag inside the lock. But 
> honestly I don't think that is necessary.
> 
> I agree that ima_process_keys should be static since it is used in this 
> file one. I'll make that change.
> 
> I can also move the setting of ima_process_key flag inside the lock 
> along with the above change.

My concern is with the last sentence "Since ima_process_keys is set to
true above, any new key will be processed immediately and not queued."
  It's unlikely, but possible, that a second process will wait for the
ima_keys_mutex.  Either we remove this sentence or move setting
ima_process_keys to after taking the lock.

Mimi
Lakshmi Ramasubramanian Dec. 12, 2019, 10:58 p.m. UTC | #6
On 12/12/2019 2:54 PM, Mimi Zohar wrote:

>>
>> I can also move the setting of ima_process_key flag inside the lock
>> along with the above change.
> 
> My concern is with the last sentence "Since ima_process_keys is set to
> true above, any new key will be processed immediately and not queued."
>    It's unlikely, but possible, that a second process will wait for the
> ima_keys_mutex.  Either we remove this sentence or move setting
> ima_process_keys to after taking the lock.
> 
> Mimi

Sure - i'll move the setting of ima_process_keys flag inside the lock 
and define the flag as static. Will keep the comment as is.

thanks,
  -lakshmi
diff mbox series

Patch

diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h
index f06238e41a7c..97f8a4078483 100644
--- a/security/integrity/ima/ima.h
+++ b/security/integrity/ima/ima.h
@@ -205,6 +205,21 @@  extern const char *const func_tokens[];
 
 struct modsig;
 
+#ifdef CONFIG_ASYMMETRIC_PUBLIC_KEY_SUBTYPE
+/*
+ * To track keys that need to be measured.
+ */
+struct ima_key_entry {
+	struct list_head list;
+	void *payload;
+	size_t payload_len;
+	char *keyring_name;
+};
+void ima_process_queued_keys(void);
+#else
+static inline void ima_process_queued_keys(void) {}
+#endif /* CONFIG_ASYMMETRIC_PUBLIC_KEY_SUBTYPE */
+
 /* LIM API function definitions */
 int ima_get_action(struct inode *inode, const struct cred *cred, u32 secid,
 		   int mask, enum ima_hooks func, int *pcr,
diff --git a/security/integrity/ima/ima_asymmetric_keys.c b/security/integrity/ima/ima_asymmetric_keys.c
index fea2e7dd3b09..ba01e04ec025 100644
--- a/security/integrity/ima/ima_asymmetric_keys.c
+++ b/security/integrity/ima/ima_asymmetric_keys.c
@@ -14,6 +14,116 @@ 
 #include <keys/asymmetric-type.h>
 #include "ima.h"
 
+/*
+ * Flag to indicate whether a key can be processed
+ * right away or should be queued for processing later.
+ */
+bool ima_process_keys;
+
+/*
+ * To synchronize access to the list of keys that need to be measured
+ */
+static DEFINE_MUTEX(ima_keys_mutex);
+static LIST_HEAD(ima_keys);
+
+static void ima_free_key_entry(struct ima_key_entry *entry)
+{
+	if (entry) {
+		kfree(entry->payload);
+		kfree(entry->keyring_name);
+		kfree(entry);
+	}
+}
+
+static struct ima_key_entry *ima_alloc_key_entry(
+	struct key *keyring,
+	const void *payload, size_t payload_len)
+{
+	int rc = 0;
+	struct ima_key_entry *entry;
+
+	entry = kzalloc(sizeof(*entry), GFP_KERNEL);
+	if (entry) {
+		entry->payload = kmemdup(payload, payload_len, GFP_KERNEL);
+		entry->keyring_name = kstrdup(keyring->description,
+					      GFP_KERNEL);
+		entry->payload_len = payload_len;
+	}
+
+	if ((entry == NULL) || (entry->payload == NULL) ||
+	    (entry->keyring_name == NULL)) {
+		rc = -ENOMEM;
+		goto out;
+	}
+
+	INIT_LIST_HEAD(&entry->list);
+
+out:
+	if (rc) {
+		ima_free_key_entry(entry);
+		entry = NULL;
+	}
+
+	return entry;
+}
+
+bool ima_queue_key(struct key *keyring, const void *payload,
+		   size_t payload_len)
+{
+	bool queued = false;
+	struct ima_key_entry *entry;
+
+	entry = ima_alloc_key_entry(keyring, payload, payload_len);
+	if (!entry)
+		return false;
+
+	mutex_lock(&ima_keys_mutex);
+	if (!ima_process_keys) {
+		list_add_tail(&entry->list, &ima_keys);
+		queued = true;
+	}
+	mutex_unlock(&ima_keys_mutex);
+
+	if (!queued)
+		ima_free_key_entry(entry);
+
+	return queued;
+}
+
+/*
+ * ima_process_queued_keys() - process keys queued for measurement
+ *
+ * This function sets ima_process_keys to true and processes queued keys.
+ * From here on keys will be processed right away (not queued).
+ */
+void ima_process_queued_keys(void)
+{
+	struct ima_key_entry *entry, *tmp;
+	LIST_HEAD(temp_ima_keys);
+
+	if (ima_process_keys)
+		return;
+
+	ima_process_keys = true;
+
+	INIT_LIST_HEAD(&temp_ima_keys);
+
+	mutex_lock(&ima_keys_mutex);
+
+	list_for_each_entry_safe(entry, tmp, &ima_keys, list)
+		list_move_tail(&entry->list, &temp_ima_keys);
+
+	mutex_unlock(&ima_keys_mutex);
+
+	list_for_each_entry_safe(entry, tmp, &temp_ima_keys, list) {
+		process_buffer_measurement(entry->payload, entry->payload_len,
+					   entry->keyring_name, KEY_CHECK, 0,
+					   entry->keyring_name);
+		list_del(&entry->list);
+		ima_free_key_entry(entry);
+	}
+}
+
 /**
  * ima_post_key_create_or_update - measure asymmetric keys
  * @keyring: keyring to which the key is linked to