On 15-10-19 14:21:03, Mimi Zohar wrote:
> 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.
Yep, a message change is in order.
Petko
> > Signed-off-by: Petko Manolov <[email protected]> ---
> > 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 [email protected]
> More majordomo info at http://vger.kernel.org/majordomo-info.html
--
To unsubscribe from this list: send the line "unsubscribe
linux-security-module" in
the body of a message to [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html