On Fri, 2015-10-16 at 22:31 +0300, Petko Manolov wrote:
> IMA policy can now be updated multiple times.  The new rules get appended
> to the original policy.  Have in mind that the rules are scanned in FIFO
> order so be careful when you add new ones.
> 
> The mutex locks are replaced with RCU, which should lead to faster policy
> traversals.  The new rules are first appended to a temporary list, which
> on error gets released without disturbing the normal IMA operations.

There was no need for locking in the original version.  This comment
should be included in a change log to reflect different versions of the
patch.

(Still reviewing the code ...)

Mimi

> Signed-off-by: Petko Manolov <pet...@mip-labs.com>
> ---
>  security/integrity/ima/Kconfig      | 14 ++++++++
>  security/integrity/ima/ima_fs.c     | 13 +++++++
>  security/integrity/ima/ima_policy.c | 70 
> +++++++++++++++++++++++++------------
>  3 files changed, 74 insertions(+), 23 deletions(-)
> 
> diff --git a/security/integrity/ima/Kconfig b/security/integrity/ima/Kconfig
> index df30334..15264b7 100644
> --- a/security/integrity/ima/Kconfig
> +++ b/security/integrity/ima/Kconfig
> @@ -107,6 +107,20 @@ config IMA_DEFAULT_HASH
>       default "sha512" if IMA_DEFAULT_HASH_SHA512
>       default "wp512" if IMA_DEFAULT_HASH_WP512
> 
> +config IMA_WRITE_POLICY
> +     bool "Enable multiple writes to the IMA policy"
> +     depends on IMA
> +     default n
> +     help
> +       IMA policy can now be updated multiple times.  The new rules get
> +       appended to the original policy.  Have in mind that the rules are
> +       scanned in FIFO order so be careful when you add new ones.
> +
> +       WARNING: Potential security hole - should be used with care in
> +       production-grade kernels!
> +
> +       If unsure, say N.
> +
>  config IMA_APPRAISE
>       bool "Appraise integrity measurements"
>       depends on IMA
> diff --git a/security/integrity/ima/ima_fs.c b/security/integrity/ima/ima_fs.c
> index 816d175..a3cf5c0 100644
> --- a/security/integrity/ima/ima_fs.c
> +++ b/security/integrity/ima/ima_fs.c
> @@ -25,6 +25,8 @@
> 
>  #include "ima.h"
> 
> +static DEFINE_MUTEX(ima_write_mutex);
> +
>  static int valid_policy = 1;
>  #define TMPBUFLEN 12
>  static ssize_t ima_show_htable_value(char __user *buf, size_t count,
> @@ -261,6 +263,11 @@ static ssize_t ima_write_policy(struct file *file, const 
> char __user *buf,
>  {
>       char *data = NULL;
>       ssize_t result;
> +     int res;
> +
> +     res = mutex_lock_interruptible(&ima_write_mutex);
> +     if (res)
> +             return res;
> 
>       if (datalen >= PAGE_SIZE)
>               datalen = PAGE_SIZE - 1;
> @@ -286,6 +293,8 @@ out:
>       if (result < 0)
>               valid_policy = 0;
>       kfree(data);
> +     mutex_unlock(&ima_write_mutex);
> +
>       return result;
>  }
> 
> @@ -337,8 +346,12 @@ static int ima_release_policy(struct inode *inode, 
> struct file *file)
>               return 0;
>       }
>       ima_update_policy();
> +#ifndef      CONFIG_IMA_WRITE_POLICY
>       securityfs_remove(ima_policy);
>       ima_policy = NULL;
> +#else
> +     clear_bit(IMA_FS_BUSY, &ima_fs_flags);
> +#endif
>       return 0;
>  }
> 
> diff --git a/security/integrity/ima/ima_policy.c 
> b/security/integrity/ima/ima_policy.c
> index 3997e20..7ace4e4 100644
> --- a/security/integrity/ima/ima_policy.c
> +++ b/security/integrity/ima/ima_policy.c
> @@ -16,6 +16,7 @@
>  #include <linux/magic.h>
>  #include <linux/parser.h>
>  #include <linux/slab.h>
> +#include <linux/rculist.h>
>  #include <linux/genhd.h>
> 
>  #include "ima.h"
> @@ -135,11 +136,11 @@ static struct ima_rule_entry default_appraise_rules[] = 
> {
> 
>  static LIST_HEAD(ima_default_rules);
>  static LIST_HEAD(ima_policy_rules);
> +static LIST_HEAD(ima_temp_rules);
>  static struct list_head *ima_rules;
> 
> -static DEFINE_MUTEX(ima_rules_mutex);
> -
>  static int ima_policy __initdata;
> +
>  static int __init default_measure_policy_setup(char *str)
>  {
>       if (ima_policy)
> @@ -171,9 +172,8 @@ static int __init default_appraise_policy_setup(char *str)
>  __setup("ima_appraise_tcb", default_appraise_policy_setup);
> 
>  /*
> - * Although the IMA policy does not change, the LSM policy can be
> - * reloaded, leaving the IMA LSM based rules referring to the old,
> - * stale LSM policy.
> + * Blocking here is not legal as we hold an RCU lock.  ima_update_policy()
> + * should make it safe to walk the list at any time.
>   *
>   * Update the IMA LSM based rules to reflect the reloaded LSM policy.
>   * We assume the rules still exist; and BUG_ON() if they don't.
> @@ -184,7 +184,6 @@ static void ima_lsm_update_rules(void)
>       int result;
>       int i;
> 
> -     mutex_lock(&ima_rules_mutex);
>       list_for_each_entry_safe(entry, tmp, &ima_policy_rules, list) {
>               for (i = 0; i < MAX_LSM_RULES; i++) {
>                       if (!entry->lsm[i].rule)
> @@ -196,7 +195,6 @@ static void ima_lsm_update_rules(void)
>                       BUG_ON(!entry->lsm[i].rule);
>               }
>       }
> -     mutex_unlock(&ima_rules_mutex);
>  }
> 
>  /**
> @@ -319,9 +317,9 @@ static int get_subaction(struct ima_rule_entry *rule, int 
> func)
>   * Measure decision based on func/mask/fsmagic and LSM(subj/obj/type)
>   * conditions.
>   *
> - * (There is no need for locking when walking the policy list,
> - * as elements in the list are never deleted, nor does the list
> - * change.)
> + * Since the IMA policy may be updated multiple times we need to lock the
> + * list when walking it.  Reads are many orders of magnitude more numerous
> + * than writes so ima_match_policy() is classical RCU candidate.
>   */
>  int ima_match_policy(struct inode *inode, enum ima_hooks func, int mask,
>                    int flags)
> @@ -329,7 +327,8 @@ int ima_match_policy(struct inode *inode, enum ima_hooks 
> func, int mask,
>       struct ima_rule_entry *entry;
>       int action = 0, actmask = flags | (flags << 1);
> 
> -     list_for_each_entry(entry, ima_rules, list) {
> +     rcu_read_lock();
> +     list_for_each_entry_rcu(entry, ima_rules, list) {
> 
>               if (!(entry->action & actmask))
>                       continue;
> @@ -351,6 +350,7 @@ int ima_match_policy(struct inode *inode, enum ima_hooks 
> func, int mask,
>               if (!actmask)
>                       break;
>       }
> +     rcu_read_unlock();
> 
>       return action;
>  }
> @@ -365,7 +365,6 @@ void ima_update_policy_flag(void)
>  {
>       struct ima_rule_entry *entry;
> 
> -     ima_policy_flag = 0;
>       list_for_each_entry(entry, ima_rules, list) {
>               if (entry->action & IMA_DO_MASK)
>                       ima_policy_flag |= entry->action;
> @@ -419,12 +418,36 @@ void __init ima_init_policy(void)
>   * ima_update_policy - update default_rules with new measure rules
>   *
>   * Called on file .release to update the default rules with a complete new
> - * policy.  Once updated, the policy is locked, no additional rules can be
> - * added to the policy.
> + * policy.  What we do here is to splice ima_policy_rules and ima_temp_rules 
> so
> + * they make a queue.  The policy may be updated multiple times and this is 
> the
> + * RCU updater.
> + *
> + * Policy rules are never deleted so ima_policy_flag gets zeroed only once 
> when
> + * we switch from the default policy to user defined.
>   */
>  void ima_update_policy(void)
>  {
> -     ima_rules = &ima_policy_rules;
> +     struct list_head *first, *last, *policy;
> +
> +     /* append current policy with the new rules */
> +     first = (&ima_temp_rules)->next;
> +     last = (&ima_temp_rules)->prev;
> +     policy = &ima_policy_rules;
> +
> +     synchronize_rcu();
> +
> +     last->next = policy;
> +     rcu_assign_pointer(list_next_rcu(policy->prev), first);
> +     first->prev = policy->prev;
> +     policy->prev = last;
> +
> +     /* prepare for the next policy rules addition */
> +     INIT_LIST_HEAD(&ima_temp_rules);
> +
> +     if (ima_rules != policy) {
> +             ima_policy_flag = 0;
> +             ima_rules = policy;
> +     }
>       ima_update_policy_flag();
>  }
> 
> @@ -746,7 +769,7 @@ static int ima_parse_rule(char *rule, struct 
> ima_rule_entry *entry)
>   * ima_parse_add_rule - add a rule to ima_policy_rules
>   * @rule - ima measurement policy rule
>   *
> - * Uses a mutex to protect the policy list from multiple concurrent writers.
> + * Avoid locking by allowing just one writer at a time in ima_write_policy()
>   * Returns the length of the rule parsed, an error code on failure
>   */
>  ssize_t ima_parse_add_rule(char *rule)
> @@ -782,26 +805,27 @@ ssize_t ima_parse_add_rule(char *rule)
>               return result;
>       }
> 
> -     mutex_lock(&ima_rules_mutex);
> -     list_add_tail(&entry->list, &ima_policy_rules);
> -     mutex_unlock(&ima_rules_mutex);
> +     list_add_tail(&entry->list, &ima_temp_rules);
> 
>       return len;
>  }
> 
> -/* ima_delete_rules called to cleanup invalid policy */
> +/**
> + * ima_delete_rules() called to cleanup invalid in-flight policy.
> + * We don't need locking as we operate on the temp list, which is
> + * different from the active one.  There is also only one user of
> + * ima_delete_rules() at a time.
> + */
>  void ima_delete_rules(void)
>  {
>       struct ima_rule_entry *entry, *tmp;
>       int i;
> 
> -     mutex_lock(&ima_rules_mutex);
> -     list_for_each_entry_safe(entry, tmp, &ima_policy_rules, list) {
> +     list_for_each_entry_safe(entry, tmp, &ima_temp_rules, list) {
>               for (i = 0; i < MAX_LSM_RULES; i++)
>                       kfree(entry->lsm[i].args_p);
> 
>               list_del(&entry->list);
>               kfree(entry);
>       }
> -     mutex_unlock(&ima_rules_mutex);
>  }


--
To unsubscribe from this list: send the line "unsubscribe 
linux-security-module" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to