linux-security-module.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [Patch V1] ima: avoid duplicate policy rules insertions
@ 2025-11-06 18:14 Tahera Fahimi
  2025-11-06 20:32 ` Anirudh Venkataramanan
                   ` (2 more replies)
  0 siblings, 3 replies; 7+ messages in thread
From: Tahera Fahimi @ 2025-11-06 18:14 UTC (permalink / raw)
  To: zohar, roberto.sassu, dmitry.kasatkin, eric.snowberg, paul,
	jmorris, serge, linux-integrity, linux-security-module,
	linux-kernel, code
  Cc: Tahera Fahimi

Prevent redundant IMA policy rules by checking for duplicates before insertion. This ensures that
rules are not re-added when userspace is restarted (using systemd-soft-reboot) without a full system
reboot. ima_rule_exists() detects duplicates in both temporary and active rule lists.

Signed-off-by: Tahera Fahimi <taherafahimi@linux.microsoft.com>
---
 security/integrity/ima/ima_policy.c | 157 +++++++++++++++++++++++++++-
 1 file changed, 156 insertions(+), 1 deletion(-)

diff --git a/security/integrity/ima/ima_policy.c b/security/integrity/ima/ima_policy.c
index 164d62832f8ec..3dd902101dbda 100644
--- a/security/integrity/ima/ima_policy.c
+++ b/security/integrity/ima/ima_policy.c
@@ -1953,6 +1953,153 @@ static int ima_parse_rule(char *rule, struct ima_rule_entry *entry)
 	return result;
 }
 
+static bool template_has_field(const char *field_id, const struct ima_template_desc *template2)
+{
+	int j;
+
+	for (int j = 0; j < template2->num_fields; j++)
+		if (strcmp(field_id, template2->fields[j]->field_id) == 0)
+			return true;
+
+	return false;
+}
+
+static bool keyring_has_item(const char *item, const struct ima_rule_opt_list *keyrings)
+{
+	int j;
+
+	for (j = 0; j < keyrings->count; j++) {
+		if (strcmp(item, keyrings->items[j]) == 0)
+			return true;
+	}
+	return false;
+}
+
+static bool labels_has_item(const char *item, const struct ima_rule_opt_list *labels)
+{
+	int j;
+
+	for (j = 0; j < labels->count; j++) {
+		if (strcmp(item, labels->items[j]) == 0)
+			return true;
+	}
+	return false;
+}
+
+static bool ima_rules_equal(const struct ima_rule_entry *rule1, const struct ima_rule_entry *rule2)
+{
+	int i;
+
+	if (rule1->flags != rule2->flags)
+		return false;
+
+	if (rule1->action != rule2->action)
+		return false;
+
+	if (((rule1->flags & IMA_FUNC) && rule1->func != rule2->func) ||
+	    ((rule1->flags & (IMA_MASK | IMA_INMASK)) && rule1->mask != rule2->mask) ||
+	    ((rule1->flags & IMA_FSMAGIC) && rule1->fsmagic != rule2->fsmagic) ||
+	    ((rule1->flags & IMA_FSUUID) && !uuid_equal(&rule1->fsuuid, &rule2->fsuuid)) ||
+	    ((rule1->flags & IMA_UID) && !uid_eq(rule1->uid, rule2->uid)) ||
+	    ((rule1->flags & IMA_GID) && !gid_eq(rule1->gid, rule2->gid)) ||
+	    ((rule1->flags & IMA_FOWNER) && !uid_eq(rule1->fowner, rule2->fowner)) ||
+	    ((rule1->flags & IMA_FGROUP) && !gid_eq(rule1->fgroup, rule2->fgroup)) ||
+	    ((rule1->flags & IMA_FSNAME) && (strcmp(rule1->fsname, rule2->fsname) != 0)) ||
+	    ((rule1->flags & IMA_PCR) && rule1->pcr != rule2->pcr) ||
+	    ((rule1->flags & IMA_VALIDATE_ALGOS) &&
+	      rule1->allowed_algos != rule2->allowed_algos) ||
+	    ((rule1->flags & IMA_EUID) && !uid_eq(rule1->uid, rule2->uid)) ||
+	    ((rule1->flags & IMA_EGID) && !gid_eq(rule1->gid, rule2->gid)))
+		return false;
+
+	if (!rule1->template && !rule2->template) {
+		;
+	} else if (!rule1->template || !rule2->template) {
+		return false;
+	} else if (rule1->template->num_fields != rule2->template->num_fields) {
+		return false;
+	} else if (rule1->template->num_fields != 0) {
+		for (i = 0; i < rule1->template->num_fields; i++) {
+			if (!template_has_field(rule1->template->fields[i]->field_id,
+						rule2->template))
+				return false;
+		}
+	}
+
+	if (rule1->flags & IMA_KEYRINGS) {
+		if (!rule1->keyrings && !rule2->keyrings) {
+			;
+		} else if (!rule1->keyrings || !rule2->keyrings) {
+			return false;
+		} else if (rule1->keyrings->count != rule2->keyrings->count) {
+			return false;
+		} else if (rule1->keyrings->count != 0) {
+			for (i = 0; i < rule1->keyrings->count; i++) {
+				if (!keyring_has_item(rule1->keyrings->items[i], rule2->keyrings))
+					return false;
+			}
+		}
+	}
+
+	if (rule1->flags & IMA_LABEL) {
+		if (!rule1->label && !rule2->label) {
+			;
+		} else if (!rule1->label || !rule2->label) {
+			return false;
+		} else if (rule1->label->count != rule2->label->count) {
+			return false;
+		} else if (rule1->label->count != 0) {
+			for (i = 0; i < rule1->label->count; i++) {
+				if (!labels_has_item(rule1->label->items[i], rule2->label))
+					return false;
+			}
+		}
+	}
+
+	for (i = 0; i < MAX_LSM_RULES; i++) {
+		if (!rule1->lsm[i].rule && !rule2->lsm[i].rule)
+			continue;
+
+		if (!rule1->lsm[i].rule || !rule2->lsm[i].rule)
+			return false;
+
+		if (strcmp(rule1->lsm[i].args_p, rule2->lsm[i].args_p) != 0)
+			return false;
+	}
+
+	return true;
+}
+
+/**
+ * ima_rule_exists - check if a rule already exists in the policy
+ *
+ * Checking both the active policy and the temporary rules list.
+ */
+static bool ima_rule_exists(struct ima_rule_entry *new_rule)
+{
+	struct ima_rule_entry *entry;
+	struct list_head *ima_rules_tmp;
+
+	if (!list_empty(&ima_temp_rules)) {
+		list_for_each_entry(entry, &ima_temp_rules, list) {
+			if (ima_rules_equal(entry, new_rule))
+				return true;
+		}
+	}
+
+	rcu_read_lock();
+	ima_rules_tmp = rcu_dereference(ima_rules);
+	list_for_each_entry_rcu(entry, ima_rules_tmp, list) {
+		if (ima_rules_equal(entry, new_rule)) {
+			rcu_read_unlock();
+			return true;
+		}
+	}
+	rcu_read_unlock();
+
+	return false;
+}
+
 /**
  * ima_parse_add_rule - add a rule to ima_policy_rules
  * @rule: ima measurement policy rule
@@ -1993,7 +2140,15 @@ ssize_t ima_parse_add_rule(char *rule)
 		return result;
 	}
 
-	list_add_tail(&entry->list, &ima_temp_rules);
+	if (!ima_rule_exists(entry)) {
+		list_add_tail(&entry->list, &ima_temp_rules);
+	} else {
+		result = -EEXIST;
+		ima_free_rule(entry);
+		integrity_audit_msg(AUDIT_INTEGRITY_STATUS, NULL,
+				    NULL, op, "duplicate-policy", result,
+				    audit_info);
+	}
 
 	return len;
 }
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 7+ messages in thread

* Re: [Patch V1] ima: avoid duplicate policy rules insertions
  2025-11-06 18:14 [Patch V1] ima: avoid duplicate policy rules insertions Tahera Fahimi
@ 2025-11-06 20:32 ` Anirudh Venkataramanan
  2025-11-10 19:06   ` Tahera Fahimi
  2025-11-07  6:54 ` kernel test robot
  2025-11-07  9:44 ` Roberto Sassu
  2 siblings, 1 reply; 7+ messages in thread
From: Anirudh Venkataramanan @ 2025-11-06 20:32 UTC (permalink / raw)
  To: Tahera Fahimi, zohar, roberto.sassu, dmitry.kasatkin,
	eric.snowberg, paul, jmorris, serge, linux-integrity,
	linux-security-module, linux-kernel, code

On 11/6/2025 10:14 AM, Tahera Fahimi wrote:
> Prevent redundant IMA policy rules by checking for duplicates before insertion. This ensures that
> rules are not re-added when userspace is restarted (using systemd-soft-reboot) without a full system
> reboot. ima_rule_exists() detects duplicates in both temporary and active rule lists.

I have run into this too. Thanks for proposing a patch!

FWIW - I am fairly new to the IMA subsystem, so feedback below is mostly 
structural, with some IMA specific comments.

> 
> Signed-off-by: Tahera Fahimi <taherafahimi@linux.microsoft.com>
> ---
>   security/integrity/ima/ima_policy.c | 157 +++++++++++++++++++++++++++-
>   1 file changed, 156 insertions(+), 1 deletion(-)
> 
> diff --git a/security/integrity/ima/ima_policy.c b/security/integrity/ima/ima_policy.c
> index 164d62832f8ec..3dd902101dbda 100644
> --- a/security/integrity/ima/ima_policy.c
> +++ b/security/integrity/ima/ima_policy.c
> @@ -1953,6 +1953,153 @@ static int ima_parse_rule(char *rule, struct ima_rule_entry *entry)
>   	return result;
>   }
>   
> +static bool template_has_field(const char *field_id, const struct ima_template_desc *template2)
> +{
> +	int j;

j is declared in the loop header below too, which is more correct 
because it keeps the scope of j to be within the loop. So I'd say get 
rid of the above declaration.

> +
> +	for (int j = 0; j < template2->num_fields; j++)
> +		if (strcmp(field_id, template2->fields[j]->field_id) == 0)
> +			return true;
I believe the preferred kernel style is to use if (!strcmp(...)).

> +
> +	return false;
> +}
> +
> +static bool keyring_has_item(const char *item, const struct ima_rule_opt_list *keyrings)
> +{
> +	int j;
> +
> +	for (j = 0; j < keyrings->count; j++) {
> +		if (strcmp(item, keyrings->items[j]) == 0)
> +			return true;
> +	}
> +	return false;
> +}
> +
> +static bool labels_has_item(const char *item, const struct ima_rule_opt_list *labels)
> +{
> +	int j;
> +
> +	for (j = 0; j < labels->count; j++) {
> +		if (strcmp(item, labels->items[j]) == 0)
> +			return true;
> +	}
> +	return false;
> +}
> +
> +static bool ima_rules_equal(const struct ima_rule_entry *rule1, const struct ima_rule_entry *rule2)
> +{
> +	int i;

i is used further down in this function, and even in all those cases, 
the scope of i can be limited to the loop body where it's used.

If you didn't know this already - you can use cppcheck to identify and 
reduce the scope of variables.

> +
> +	if (rule1->flags != rule2->flags)
> +		return false;
> +
> +	if (rule1->action != rule2->action)
> +		return false;
> +
> +	if (((rule1->flags & IMA_FUNC) && rule1->func != rule2->func) ||
> +	    ((rule1->flags & (IMA_MASK | IMA_INMASK)) && rule1->mask != rule2->mask) ||
> +	    ((rule1->flags & IMA_FSMAGIC) && rule1->fsmagic != rule2->fsmagic) ||
> +	    ((rule1->flags & IMA_FSUUID) && !uuid_equal(&rule1->fsuuid, &rule2->fsuuid)) ||
> +	    ((rule1->flags & IMA_UID) && !uid_eq(rule1->uid, rule2->uid)) ||
> +	    ((rule1->flags & IMA_GID) && !gid_eq(rule1->gid, rule2->gid)) ||
> +	    ((rule1->flags & IMA_FOWNER) && !uid_eq(rule1->fowner, rule2->fowner)) ||
> +	    ((rule1->flags & IMA_FGROUP) && !gid_eq(rule1->fgroup, rule2->fgroup)) ||
> +	    ((rule1->flags & IMA_FSNAME) && (strcmp(rule1->fsname, rule2->fsname) != 0)) ||
> +	    ((rule1->flags & IMA_PCR) && rule1->pcr != rule2->pcr) ||
> +	    ((rule1->flags & IMA_VALIDATE_ALGOS) &&
> +	      rule1->allowed_algos != rule2->allowed_algos) ||
> +	    ((rule1->flags & IMA_EUID) && !uid_eq(rule1->uid, rule2->uid)) ||
> +	    ((rule1->flags & IMA_EGID) && !gid_eq(rule1->gid, rule2->gid)))
> +		return false;

So the goal is to prevent the exact same policy rule from being added, 
not to update an existing rule, correct? IOW, you could end up with two 
very similar rules, because the new rule has one thing that's different 
compared to the existing rule?

I feel that a little bit of commentary around what makes two rules the 
same would be useful.

> +
> +	if (!rule1->template && !rule2->template) {
> +		;
You're trying to do nothing and continue on. A goto statement would 
communicate intent better. There are other places below with the same 
noop structure.

To be fair, I also don't completely understand what you're trying to 
achieve here, Regardless, this "do nothing inside a conditional" looks 
weird and I feel like there should be a way to structure your logic 
without resorting to this.

> +	} else if (!rule1->template || !rule2->template) {
> +		return false;
> +	} else if (rule1->template->num_fields != rule2->template->num_fields) {
> +		return false;
> +	} else if (rule1->template->num_fields != 0) {
> +		for (i = 0; i < rule1->template->num_fields; i++) {
> +			if (!template_has_field(rule1->template->fields[i]->field_id,
> +						rule2->template))
> +				return false;
> +		}
> +	}

if + return will achieve the same end goals as else if + return, with 
lesser clutter. I have seen some static analyzers flag this pattern, but 
I can't remember which one at the moment.

So something like this:

if (!rule1->template && !rule2->template)
     goto some_target;

if (!rule1->template || !rule2->template)
     return false;

if (rule1->template->num_fields != rule2->template->num_fields)
     return false;

if (rule1->template->num_fields != 0) {
     for (i = 0; i < rule1->template->num_fields; i++) {
         if (!template_has_field(rule1->template->fields[i]->field_id,
                                 rule2->template))
               return false;
         }
}

some_target:
...
...

> +
> +	if (rule1->flags & IMA_KEYRINGS) {
> +		if (!rule1->keyrings && !rule2->keyrings) {
> +			;

Another if block no-op

> +		} else if (!rule1->keyrings || !rule2->keyrings) {
> +			return false;
> +		} else if (rule1->keyrings->count != rule2->keyrings->count) {
> +			return false;
> +		} else if (rule1->keyrings->count != 0) {

if (rule1->keyrings->count)

> +			for (i = 0; i < rule1->keyrings->count; i++) {

for (int i,

> +				if (!keyring_has_item(rule1->keyrings->items[i], rule2->keyrings))
> +					return false;
> +			}
> +		}
> +	}
> +
> +	if (rule1->flags & IMA_LABEL) {
> +		if (!rule1->label && !rule2->label) {
> +			;

Another if block no-op

> +		} else if (!rule1->label || !rule2->label) {
> +			return false;
> +		} else if (rule1->label->count != rule2->label->count) {
> +			return false;
> +		} else if (rule1->label->count != 0) {
> +			for (i = 0; i < rule1->label->count; i++) {
> +				if (!labels_has_item(rule1->label->items[i], rule2->label))
> +					return false;
> +			}
> +		}
> +	}
> +
> +	for (i = 0; i < MAX_LSM_RULES; i++) {

for (int i,

> +		if (!rule1->lsm[i].rule && !rule2->lsm[i].rule)
> +			continue;
> +
> +		if (!rule1->lsm[i].rule || !rule2->lsm[i].rule)
> +			return false;
> +
> +		if (strcmp(rule1->lsm[i].args_p, rule2->lsm[i].args_p) != 0)
> +			return false;
> +	}
> +
> +	return true;
> +}
> +
> +/**
> + * ima_rule_exists - check if a rule already exists in the policy
> + *
> + * Checking both the active policy and the temporary rules list.
> + */
> +static bool ima_rule_exists(struct ima_rule_entry *new_rule)
> +{
> +	struct ima_rule_entry *entry;
> +	struct list_head *ima_rules_tmp;
> +
> +	if (!list_empty(&ima_temp_rules)) {
> +		list_for_each_entry(entry, &ima_temp_rules, list) {
> +			if (ima_rules_equal(entry, new_rule))
> +				return true;
> +		}
> +	}
> +
> +	rcu_read_lock();
> +	ima_rules_tmp = rcu_dereference(ima_rules);
> +	list_for_each_entry_rcu(entry, ima_rules_tmp, list) {
> +		if (ima_rules_equal(entry, new_rule)) {
> +			rcu_read_unlock();
> +			return true;
> +		}
> +	}
> +	rcu_read_unlock();
> +
> +	return false;
> +}
> +
>   /**
>    * ima_parse_add_rule - add a rule to ima_policy_rules
>    * @rule: ima measurement policy rule
> @@ -1993,7 +2140,15 @@ ssize_t ima_parse_add_rule(char *rule)
>   		return result;
>   	}
>   
> -	list_add_tail(&entry->list, &ima_temp_rules);
> +	if (!ima_rule_exists(entry)) {
> +		list_add_tail(&entry->list, &ima_temp_rules);
> +	} else {
> +		result = -EEXIST;
Is it necessary to set result? Or can you just pass -EEXIST to the audit 
call below?

> +		ima_free_rule(entry);
> +		integrity_audit_msg(AUDIT_INTEGRITY_STATUS, NULL,
> +				    NULL, op, "duplicate-policy", result,
> +				    audit_info);
> +	}
>   
>   	return len;
>   }


^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [Patch V1] ima: avoid duplicate policy rules insertions
  2025-11-06 18:14 [Patch V1] ima: avoid duplicate policy rules insertions Tahera Fahimi
  2025-11-06 20:32 ` Anirudh Venkataramanan
@ 2025-11-07  6:54 ` kernel test robot
  2025-11-07  9:44 ` Roberto Sassu
  2 siblings, 0 replies; 7+ messages in thread
From: kernel test robot @ 2025-11-07  6:54 UTC (permalink / raw)
  To: Tahera Fahimi, zohar, roberto.sassu, dmitry.kasatkin,
	eric.snowberg, paul, jmorris, serge, linux-integrity,
	linux-security-module, linux-kernel, code
  Cc: oe-kbuild-all, Tahera Fahimi

Hi Tahera,

kernel test robot noticed the following build warnings:

[auto build test WARNING on zohar-integrity/next-integrity]
[also build test WARNING on linus/master v6.18-rc4 next-20251107]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]

url:    https://github.com/intel-lab-lkp/linux/commits/Tahera-Fahimi/ima-avoid-duplicate-policy-rules-insertions/20251107-021615
base:   https://git.kernel.org/pub/scm/linux/kernel/git/zohar/linux-integrity.git next-integrity
patch link:    https://lore.kernel.org/r/20251106181404.3429710-1-taherafahimi%40linux.microsoft.com
patch subject: [Patch V1] ima: avoid duplicate policy rules insertions
config: alpha-allyesconfig (https://download.01.org/0day-ci/archive/20251107/202511071406.hU1UdCKh-lkp@intel.com/config)
compiler: alpha-linux-gcc (GCC) 15.1.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20251107/202511071406.hU1UdCKh-lkp@intel.com/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202511071406.hU1UdCKh-lkp@intel.com/

All warnings (new ones prefixed by >>):

   security/integrity/ima/ima_policy.c: In function 'template_has_field':
>> security/integrity/ima/ima_policy.c:1958:13: warning: unused variable 'j' [-Wunused-variable]
    1958 |         int j;
         |             ^
--
>> Warning: security/integrity/ima/ima_policy.c:2078 function parameter 'new_rule' not described in 'ima_rule_exists'


vim +/j +1958 security/integrity/ima/ima_policy.c

  1955	
  1956	static bool template_has_field(const char *field_id, const struct ima_template_desc *template2)
  1957	{
> 1958		int j;
  1959	
  1960		for (int j = 0; j < template2->num_fields; j++)
  1961			if (strcmp(field_id, template2->fields[j]->field_id) == 0)
  1962				return true;
  1963	
  1964		return false;
  1965	}
  1966	
  1967	static bool keyring_has_item(const char *item, const struct ima_rule_opt_list *keyrings)
  1968	{
  1969		int j;
  1970	
  1971		for (j = 0; j < keyrings->count; j++) {
  1972			if (strcmp(item, keyrings->items[j]) == 0)
  1973				return true;
  1974		}
  1975		return false;
  1976	}
  1977	
  1978	static bool labels_has_item(const char *item, const struct ima_rule_opt_list *labels)
  1979	{
  1980		int j;
  1981	
  1982		for (j = 0; j < labels->count; j++) {
  1983			if (strcmp(item, labels->items[j]) == 0)
  1984				return true;
  1985		}
  1986		return false;
  1987	}
  1988	
  1989	static bool ima_rules_equal(const struct ima_rule_entry *rule1, const struct ima_rule_entry *rule2)
  1990	{
  1991		int i;
  1992	
  1993		if (rule1->flags != rule2->flags)
  1994			return false;
  1995	
  1996		if (rule1->action != rule2->action)
  1997			return false;
  1998	
  1999		if (((rule1->flags & IMA_FUNC) && rule1->func != rule2->func) ||
  2000		    ((rule1->flags & (IMA_MASK | IMA_INMASK)) && rule1->mask != rule2->mask) ||
  2001		    ((rule1->flags & IMA_FSMAGIC) && rule1->fsmagic != rule2->fsmagic) ||
  2002		    ((rule1->flags & IMA_FSUUID) && !uuid_equal(&rule1->fsuuid, &rule2->fsuuid)) ||
  2003		    ((rule1->flags & IMA_UID) && !uid_eq(rule1->uid, rule2->uid)) ||
  2004		    ((rule1->flags & IMA_GID) && !gid_eq(rule1->gid, rule2->gid)) ||
  2005		    ((rule1->flags & IMA_FOWNER) && !uid_eq(rule1->fowner, rule2->fowner)) ||
  2006		    ((rule1->flags & IMA_FGROUP) && !gid_eq(rule1->fgroup, rule2->fgroup)) ||
  2007		    ((rule1->flags & IMA_FSNAME) && (strcmp(rule1->fsname, rule2->fsname) != 0)) ||
  2008		    ((rule1->flags & IMA_PCR) && rule1->pcr != rule2->pcr) ||
  2009		    ((rule1->flags & IMA_VALIDATE_ALGOS) &&
  2010		      rule1->allowed_algos != rule2->allowed_algos) ||
  2011		    ((rule1->flags & IMA_EUID) && !uid_eq(rule1->uid, rule2->uid)) ||
  2012		    ((rule1->flags & IMA_EGID) && !gid_eq(rule1->gid, rule2->gid)))
  2013			return false;
  2014	
  2015		if (!rule1->template && !rule2->template) {
  2016			;
  2017		} else if (!rule1->template || !rule2->template) {
  2018			return false;
  2019		} else if (rule1->template->num_fields != rule2->template->num_fields) {
  2020			return false;
  2021		} else if (rule1->template->num_fields != 0) {
  2022			for (i = 0; i < rule1->template->num_fields; i++) {
  2023				if (!template_has_field(rule1->template->fields[i]->field_id,
  2024							rule2->template))
  2025					return false;
  2026			}
  2027		}
  2028	
  2029		if (rule1->flags & IMA_KEYRINGS) {
  2030			if (!rule1->keyrings && !rule2->keyrings) {
  2031				;
  2032			} else if (!rule1->keyrings || !rule2->keyrings) {
  2033				return false;
  2034			} else if (rule1->keyrings->count != rule2->keyrings->count) {
  2035				return false;
  2036			} else if (rule1->keyrings->count != 0) {
  2037				for (i = 0; i < rule1->keyrings->count; i++) {
  2038					if (!keyring_has_item(rule1->keyrings->items[i], rule2->keyrings))
  2039						return false;
  2040				}
  2041			}
  2042		}
  2043	
  2044		if (rule1->flags & IMA_LABEL) {
  2045			if (!rule1->label && !rule2->label) {
  2046				;
  2047			} else if (!rule1->label || !rule2->label) {
  2048				return false;
  2049			} else if (rule1->label->count != rule2->label->count) {
  2050				return false;
  2051			} else if (rule1->label->count != 0) {
  2052				for (i = 0; i < rule1->label->count; i++) {
  2053					if (!labels_has_item(rule1->label->items[i], rule2->label))
  2054						return false;
  2055				}
  2056			}
  2057		}
  2058	
  2059		for (i = 0; i < MAX_LSM_RULES; i++) {
  2060			if (!rule1->lsm[i].rule && !rule2->lsm[i].rule)
  2061				continue;
  2062	
  2063			if (!rule1->lsm[i].rule || !rule2->lsm[i].rule)
  2064				return false;
  2065	
  2066			if (strcmp(rule1->lsm[i].args_p, rule2->lsm[i].args_p) != 0)
  2067				return false;
  2068		}
  2069	
  2070		return true;
  2071	}
  2072	
  2073	/**
  2074	 * ima_rule_exists - check if a rule already exists in the policy
  2075	 *
  2076	 * Checking both the active policy and the temporary rules list.
  2077	 */
> 2078	static bool ima_rule_exists(struct ima_rule_entry *new_rule)
  2079	{
  2080		struct ima_rule_entry *entry;
  2081		struct list_head *ima_rules_tmp;
  2082	
  2083		if (!list_empty(&ima_temp_rules)) {
  2084			list_for_each_entry(entry, &ima_temp_rules, list) {
  2085				if (ima_rules_equal(entry, new_rule))
  2086					return true;
  2087			}
  2088		}
  2089	
  2090		rcu_read_lock();
  2091		ima_rules_tmp = rcu_dereference(ima_rules);
  2092		list_for_each_entry_rcu(entry, ima_rules_tmp, list) {
  2093			if (ima_rules_equal(entry, new_rule)) {
  2094				rcu_read_unlock();
  2095				return true;
  2096			}
  2097		}
  2098		rcu_read_unlock();
  2099	
  2100		return false;
  2101	}
  2102	

-- 
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki

^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [Patch V1] ima: avoid duplicate policy rules insertions
  2025-11-06 18:14 [Patch V1] ima: avoid duplicate policy rules insertions Tahera Fahimi
  2025-11-06 20:32 ` Anirudh Venkataramanan
  2025-11-07  6:54 ` kernel test robot
@ 2025-11-07  9:44 ` Roberto Sassu
  2025-11-07 10:56   ` Lennart Poettering
  2 siblings, 1 reply; 7+ messages in thread
From: Roberto Sassu @ 2025-11-07  9:44 UTC (permalink / raw)
  To: Tahera Fahimi, zohar, roberto.sassu, dmitry.kasatkin,
	eric.snowberg, paul, jmorris, serge, linux-integrity,
	linux-security-module, linux-kernel, code
  Cc: Lennart Poettering

On Thu, 2025-11-06 at 18:14 +0000, Tahera Fahimi wrote:
> Prevent redundant IMA policy rules by checking for duplicates before insertion. This ensures that
> rules are not re-added when userspace is restarted (using systemd-soft-reboot) without a full system
> reboot. ima_rule_exists() detects duplicates in both temporary and active rule lists.

+ Lennart

Hi Tahera

thanks for the patch!


Wouldn't be better to enhance systemd-soft-reboot to not send the same
IMA policy again?

Thanks

Roberto

> Signed-off-by: Tahera Fahimi <taherafahimi@linux.microsoft.com>
> ---
>  security/integrity/ima/ima_policy.c | 157 +++++++++++++++++++++++++++-
>  1 file changed, 156 insertions(+), 1 deletion(-)
> 
> diff --git a/security/integrity/ima/ima_policy.c b/security/integrity/ima/ima_policy.c
> index 164d62832f8ec..3dd902101dbda 100644
> --- a/security/integrity/ima/ima_policy.c
> +++ b/security/integrity/ima/ima_policy.c
> @@ -1953,6 +1953,153 @@ static int ima_parse_rule(char *rule, struct ima_rule_entry *entry)
>  	return result;
>  }
>  
> +static bool template_has_field(const char *field_id, const struct ima_template_desc *template2)
> +{
> +	int j;
> +
> +	for (int j = 0; j < template2->num_fields; j++)
> +		if (strcmp(field_id, template2->fields[j]->field_id) == 0)
> +			return true;
> +
> +	return false;
> +}
> +
> +static bool keyring_has_item(const char *item, const struct ima_rule_opt_list *keyrings)
> +{
> +	int j;
> +
> +	for (j = 0; j < keyrings->count; j++) {
> +		if (strcmp(item, keyrings->items[j]) == 0)
> +			return true;
> +	}
> +	return false;
> +}
> +
> +static bool labels_has_item(const char *item, const struct ima_rule_opt_list *labels)
> +{
> +	int j;
> +
> +	for (j = 0; j < labels->count; j++) {
> +		if (strcmp(item, labels->items[j]) == 0)
> +			return true;
> +	}
> +	return false;
> +}
> +
> +static bool ima_rules_equal(const struct ima_rule_entry *rule1, const struct ima_rule_entry *rule2)
> +{
> +	int i;
> +
> +	if (rule1->flags != rule2->flags)
> +		return false;
> +
> +	if (rule1->action != rule2->action)
> +		return false;
> +
> +	if (((rule1->flags & IMA_FUNC) && rule1->func != rule2->func) ||
> +	    ((rule1->flags & (IMA_MASK | IMA_INMASK)) && rule1->mask != rule2->mask) ||
> +	    ((rule1->flags & IMA_FSMAGIC) && rule1->fsmagic != rule2->fsmagic) ||
> +	    ((rule1->flags & IMA_FSUUID) && !uuid_equal(&rule1->fsuuid, &rule2->fsuuid)) ||
> +	    ((rule1->flags & IMA_UID) && !uid_eq(rule1->uid, rule2->uid)) ||
> +	    ((rule1->flags & IMA_GID) && !gid_eq(rule1->gid, rule2->gid)) ||
> +	    ((rule1->flags & IMA_FOWNER) && !uid_eq(rule1->fowner, rule2->fowner)) ||
> +	    ((rule1->flags & IMA_FGROUP) && !gid_eq(rule1->fgroup, rule2->fgroup)) ||
> +	    ((rule1->flags & IMA_FSNAME) && (strcmp(rule1->fsname, rule2->fsname) != 0)) ||
> +	    ((rule1->flags & IMA_PCR) && rule1->pcr != rule2->pcr) ||
> +	    ((rule1->flags & IMA_VALIDATE_ALGOS) &&
> +	      rule1->allowed_algos != rule2->allowed_algos) ||
> +	    ((rule1->flags & IMA_EUID) && !uid_eq(rule1->uid, rule2->uid)) ||
> +	    ((rule1->flags & IMA_EGID) && !gid_eq(rule1->gid, rule2->gid)))
> +		return false;
> +
> +	if (!rule1->template && !rule2->template) {
> +		;
> +	} else if (!rule1->template || !rule2->template) {
> +		return false;
> +	} else if (rule1->template->num_fields != rule2->template->num_fields) {
> +		return false;
> +	} else if (rule1->template->num_fields != 0) {
> +		for (i = 0; i < rule1->template->num_fields; i++) {
> +			if (!template_has_field(rule1->template->fields[i]->field_id,
> +						rule2->template))
> +				return false;
> +		}
> +	}
> +
> +	if (rule1->flags & IMA_KEYRINGS) {
> +		if (!rule1->keyrings && !rule2->keyrings) {
> +			;
> +		} else if (!rule1->keyrings || !rule2->keyrings) {
> +			return false;
> +		} else if (rule1->keyrings->count != rule2->keyrings->count) {
> +			return false;
> +		} else if (rule1->keyrings->count != 0) {
> +			for (i = 0; i < rule1->keyrings->count; i++) {
> +				if (!keyring_has_item(rule1->keyrings->items[i], rule2->keyrings))
> +					return false;
> +			}
> +		}
> +	}
> +
> +	if (rule1->flags & IMA_LABEL) {
> +		if (!rule1->label && !rule2->label) {
> +			;
> +		} else if (!rule1->label || !rule2->label) {
> +			return false;
> +		} else if (rule1->label->count != rule2->label->count) {
> +			return false;
> +		} else if (rule1->label->count != 0) {
> +			for (i = 0; i < rule1->label->count; i++) {
> +				if (!labels_has_item(rule1->label->items[i], rule2->label))
> +					return false;
> +			}
> +		}
> +	}
> +
> +	for (i = 0; i < MAX_LSM_RULES; i++) {
> +		if (!rule1->lsm[i].rule && !rule2->lsm[i].rule)
> +			continue;
> +
> +		if (!rule1->lsm[i].rule || !rule2->lsm[i].rule)
> +			return false;
> +
> +		if (strcmp(rule1->lsm[i].args_p, rule2->lsm[i].args_p) != 0)
> +			return false;
> +	}
> +
> +	return true;
> +}
> +
> +/**
> + * ima_rule_exists - check if a rule already exists in the policy
> + *
> + * Checking both the active policy and the temporary rules list.
> + */
> +static bool ima_rule_exists(struct ima_rule_entry *new_rule)
> +{
> +	struct ima_rule_entry *entry;
> +	struct list_head *ima_rules_tmp;
> +
> +	if (!list_empty(&ima_temp_rules)) {
> +		list_for_each_entry(entry, &ima_temp_rules, list) {
> +			if (ima_rules_equal(entry, new_rule))
> +				return true;
> +		}
> +	}
> +
> +	rcu_read_lock();
> +	ima_rules_tmp = rcu_dereference(ima_rules);
> +	list_for_each_entry_rcu(entry, ima_rules_tmp, list) {
> +		if (ima_rules_equal(entry, new_rule)) {
> +			rcu_read_unlock();
> +			return true;
> +		}
> +	}
> +	rcu_read_unlock();
> +
> +	return false;
> +}
> +
>  /**
>   * ima_parse_add_rule - add a rule to ima_policy_rules
>   * @rule: ima measurement policy rule
> @@ -1993,7 +2140,15 @@ ssize_t ima_parse_add_rule(char *rule)
>  		return result;
>  	}
>  
> -	list_add_tail(&entry->list, &ima_temp_rules);
> +	if (!ima_rule_exists(entry)) {
> +		list_add_tail(&entry->list, &ima_temp_rules);
> +	} else {
> +		result = -EEXIST;
> +		ima_free_rule(entry);
> +		integrity_audit_msg(AUDIT_INTEGRITY_STATUS, NULL,
> +				    NULL, op, "duplicate-policy", result,
> +				    audit_info);
> +	}
>  
>  	return len;
>  }


^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [Patch V1] ima: avoid duplicate policy rules insertions
  2025-11-07  9:44 ` Roberto Sassu
@ 2025-11-07 10:56   ` Lennart Poettering
  2025-11-07 11:56     ` Roberto Sassu
  0 siblings, 1 reply; 7+ messages in thread
From: Lennart Poettering @ 2025-11-07 10:56 UTC (permalink / raw)
  To: Roberto Sassu
  Cc: Tahera Fahimi, zohar, roberto.sassu, dmitry.kasatkin,
	eric.snowberg, paul, jmorris, serge, linux-integrity,
	linux-security-module, linux-kernel, code

On Fr, 07.11.25 10:44, Roberto Sassu (roberto.sassu@huaweicloud.com) wrote:

> On Thu, 2025-11-06 at 18:14 +0000, Tahera Fahimi wrote:
> > Prevent redundant IMA policy rules by checking for duplicates before insertion. This ensures that
> > rules are not re-added when userspace is restarted (using systemd-soft-reboot) without a full system
> > reboot. ima_rule_exists() detects duplicates in both temporary and active rule lists.
>
> + Lennart
>
> Hi Tahera
>
> thanks for the patch!
>
> Wouldn't be better to enhance systemd-soft-reboot to not send the same
> IMA policy again?

the soft-reboot logic doesn't load the IMA policy. It's just that
soft-reboot means we reexec PID1: the old pid1 gets replaced by the
new one. And that new PID1 then initializes as it usually would, and
loads security policies again. It currently has support for selinux
policies, ima, ipe, smack.

These policies are supposed to *replace* whatever was loaded
before. Looking at our IMA logic, this doesn't happen right now
though, it just adds stuff:

https://github.com/systemd/systemd/blob/main/src/core/ima-setup.c

Is there a way to replace the old IMA policy with the new, with the
current IMA userspace interface? If so, we should probably make use of
that in systemd, and replace the policy that way. Or in other words:
under the assumption that one can flush out the old IMA policy and
replace it with a new one, I think this should be fixed in systemd,
not the kernel. (of there's no api for flushing out the old
policy/replacing it with the new, then of course we need something
like that in the kernel first).

My understanding of IMA is kinda limited though. I just know what we
do in our codebase.

Lennart

^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [Patch V1] ima: avoid duplicate policy rules insertions
  2025-11-07 10:56   ` Lennart Poettering
@ 2025-11-07 11:56     ` Roberto Sassu
  0 siblings, 0 replies; 7+ messages in thread
From: Roberto Sassu @ 2025-11-07 11:56 UTC (permalink / raw)
  To: Lennart Poettering
  Cc: Tahera Fahimi, zohar, roberto.sassu, dmitry.kasatkin,
	eric.snowberg, paul, jmorris, serge, linux-integrity,
	linux-security-module, linux-kernel, code

On Fri, 2025-11-07 at 11:56 +0100, Lennart Poettering wrote:
> On Fr, 07.11.25 10:44, Roberto Sassu (roberto.sassu@huaweicloud.com) wrote:
> 
> > On Thu, 2025-11-06 at 18:14 +0000, Tahera Fahimi wrote:
> > > Prevent redundant IMA policy rules by checking for duplicates before insertion. This ensures that
> > > rules are not re-added when userspace is restarted (using systemd-soft-reboot) without a full system
> > > reboot. ima_rule_exists() detects duplicates in both temporary and active rule lists.
> > 
> > + Lennart
> > 
> > Hi Tahera
> > 
> > thanks for the patch!
> > 
> > Wouldn't be better to enhance systemd-soft-reboot to not send the same
> > IMA policy again?
> 
> the soft-reboot logic doesn't load the IMA policy. It's just that
> soft-reboot means we reexec PID1: the old pid1 gets replaced by the
> new one. And that new PID1 then initializes as it usually would, and
> loads security policies again. It currently has support for selinux
> policies, ima, ipe, smack.
> 
> These policies are supposed to *replace* whatever was loaded
> before. Looking at our IMA logic, this doesn't happen right now
> though, it just adds stuff:

From a functional perspective. As far as integrity is concerned, you
would probably agree that just replacing PID 1 does not mean resetting
the integrity of the system to a known state (all the other processes
are still running). Due to that, I think the concept of soft-reset
should not be applied to the kernel.

> https://github.com/systemd/systemd/blob/main/src/core/ima-setup.c
> 
> Is there a way to replace the old IMA policy with the new, with the
> current IMA userspace interface? If so, we should probably make use of

No, only the IMA boot policies specified in the kernel command line can
be reset (only once, and not completely, secure boot rules still
persist despite user space loads a new policy). New rules are append-
only.

> that in systemd, and replace the policy that way. Or in other words:
> under the assumption that one can flush out the old IMA policy and
> replace it with a new one, I think this should be fixed in systemd,
> not the kernel. (of there's no api for flushing out the old
> policy/replacing it with the new, then of course we need something
> like that in the kernel first).

Assuming that technically it is feasible to flush the old IMA policy
(except for the permanent secure boot rules). What it would be the
additional value of changing the policy on the fly on the same system
as before, but with a different PID 1?

Regarding the duplicate IMA policy load, I guess you could probably
store the digest of the currently loaded policy in the systemd state
and not doing it again after soft-reboot, if the digest matches.

Roberto

> My understanding of IMA is kinda limited though. I just know what we
> do in our codebase.
> 
> Lennart


^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [Patch V1] ima: avoid duplicate policy rules insertions
  2025-11-06 20:32 ` Anirudh Venkataramanan
@ 2025-11-10 19:06   ` Tahera Fahimi
  0 siblings, 0 replies; 7+ messages in thread
From: Tahera Fahimi @ 2025-11-10 19:06 UTC (permalink / raw)
  To: Anirudh Venkataramanan, zohar, roberto.sassu, dmitry.kasatkin,
	eric.snowberg, paul, jmorris, serge, linux-integrity,
	linux-security-module, linux-kernel, code

On 11/6/2025 12:32 PM, Anirudh Venkataramanan wrote:
> On 11/6/2025 10:14 AM, Tahera Fahimi wrote:
>> Prevent redundant IMA policy rules by checking for duplicates before insertion. This ensures that
>> rules are not re-added when userspace is restarted (using systemd-soft-reboot) without a full system
>> reboot. ima_rule_exists() detects duplicates in both temporary and active rule lists.
> 
> I have run into this too. Thanks for proposing a patch!
> 
> FWIW - I am fairly new to the IMA subsystem, so feedback below is mostly structural, with some IMA specific comments.
Hi Ahirudh, Thanks for your feedback.
>>
>> Signed-off-by: Tahera Fahimi <taherafahimi@linux.microsoft.com>
>> ---
>>   security/integrity/ima/ima_policy.c | 157 +++++++++++++++++++++++++++-
>>   1 file changed, 156 insertions(+), 1 deletion(-)
>>
>> diff --git a/security/integrity/ima/ima_policy.c b/security/integrity/ima/ima_policy.c
>> index 164d62832f8ec..3dd902101dbda 100644
>> --- a/security/integrity/ima/ima_policy.c
>> +++ b/security/integrity/ima/ima_policy.c
>> @@ -1953,6 +1953,153 @@ static int ima_parse_rule(char *rule, struct ima_rule_entry *entry)
>>       return result;
>>   }
>>   +static bool template_has_field(const char *field_id, const struct ima_template_desc *template2)
>> +{
>> +    int j;
> 
> j is declared in the loop header below too, which is more correct because it keeps the scope of j to be within the loop. So I'd say get rid of the above declaration.
The declaration of j is at the beginning to adhere proper kernel style and ancient compile support. 
>> +
>> +    for (int j = 0; j < template2->num_fields; j++)
>> +        if (strcmp(field_id, template2->fields[j]->field_id) == 0)
>> +            return true;
> I believe the preferred kernel style is to use if (!strcmp(...)).
> 
>> +
>> +    return false;
>> +}
>> +
>> +static bool keyring_has_item(const char *item, const struct ima_rule_opt_list *keyrings)
>> +{
>> +    int j;
>> +
>> +    for (j = 0; j < keyrings->count; j++) {
>> +        if (strcmp(item, keyrings->items[j]) == 0)
>> +            return true;
>> +    }
>> +    return false;
>> +}
>> +
>> +static bool labels_has_item(const char *item, const struct ima_rule_opt_list *labels)
>> +{
>> +    int j;
>> +
>> +    for (j = 0; j < labels->count; j++) {
>> +        if (strcmp(item, labels->items[j]) == 0)
>> +            return true;
>> +    }
>> +    return false;
>> +}
>> +
>> +static bool ima_rules_equal(const struct ima_rule_entry *rule1, const struct ima_rule_entry *rule2)
>> +{
>> +    int i;
> 
> i is used further down in this function, and even in all those cases, the scope of i can be limited to the loop body where it's used.
> 
> If you didn't know this already - you can use cppcheck to identify and reduce the scope of variables.
> 
>> +
>> +    if (rule1->flags != rule2->flags)
>> +        return false;
>> +
>> +    if (rule1->action != rule2->action)
>> +        return false;
>> +
>> +    if (((rule1->flags & IMA_FUNC) && rule1->func != rule2->func) ||
>> +        ((rule1->flags & (IMA_MASK | IMA_INMASK)) && rule1->mask != rule2->mask) ||
>> +        ((rule1->flags & IMA_FSMAGIC) && rule1->fsmagic != rule2->fsmagic) ||
>> +        ((rule1->flags & IMA_FSUUID) && !uuid_equal(&rule1->fsuuid, &rule2->fsuuid)) ||
>> +        ((rule1->flags & IMA_UID) && !uid_eq(rule1->uid, rule2->uid)) ||
>> +        ((rule1->flags & IMA_GID) && !gid_eq(rule1->gid, rule2->gid)) ||
>> +        ((rule1->flags & IMA_FOWNER) && !uid_eq(rule1->fowner, rule2->fowner)) ||
>> +        ((rule1->flags & IMA_FGROUP) && !gid_eq(rule1->fgroup, rule2->fgroup)) ||
>> +        ((rule1->flags & IMA_FSNAME) && (strcmp(rule1->fsname, rule2->fsname) != 0)) ||
>> +        ((rule1->flags & IMA_PCR) && rule1->pcr != rule2->pcr) ||
>> +        ((rule1->flags & IMA_VALIDATE_ALGOS) &&
>> +          rule1->allowed_algos != rule2->allowed_algos) ||
>> +        ((rule1->flags & IMA_EUID) && !uid_eq(rule1->uid, rule2->uid)) ||
>> +        ((rule1->flags & IMA_EGID) && !gid_eq(rule1->gid, rule2->gid)))
>> +        return false;
> 
> So the goal is to prevent the exact same policy rule from being added, not to update an existing rule, correct? IOW, you could end up with two very similar rules, because the new rule has one thing that's different compared to the existing rule?

The purpose of this patch is to prohibit two exact same rule.
We can have other approaches like merging the new rule to the previously existing rule, ignore
new rule if a similar rule exists. However, this approaches would add more complexity to the code
and are not the purpose of this patch.

> I feel that a little bit of commentary around what makes two rules the same would be useful.
> 
>> +
>> +    if (!rule1->template && !rule2->template) {
>> +        ;
> You're trying to do nothing and continue on. A goto statement would communicate intent better. There are other places below with the same noop structure.
> 
> To be fair, I also don't completely understand what you're trying to achieve here, Regardless, this "do nothing inside a conditional" looks weird and I feel like there should be a way to structure your logic without resorting to this.
> 
>> +    } else if (!rule1->template || !rule2->template) {
>> +        return false;
>> +    } else if (rule1->template->num_fields != rule2->template->num_fields) {
>> +        return false;
>> +    } else if (rule1->template->num_fields != 0) {
>> +        for (i = 0; i < rule1->template->num_fields; i++) {
>> +            if (!template_has_field(rule1->template->fields[i]->field_id,
>> +                        rule2->template))
>> +                return false;
>> +        }
>> +    }
> 
> if + return will achieve the same end goals as else if + return, with lesser clutter. I have seen some static analyzers flag this pattern, but I can't remember which one at the moment.
> 
> So something like this:
> 
> if (!rule1->template && !rule2->template)
>     goto some_target;
> 
> if (!rule1->template || !rule2->template)
>     return false;
> 
> if (rule1->template->num_fields != rule2->template->num_fields)
>     return false;
> 
> if (rule1->template->num_fields != 0) {
>     for (i = 0; i < rule1->template->num_fields; i++) {
>         if (!template_has_field(rule1->template->fields[i]->field_id,
>                                 rule2->template))
>               return false;
>         }
> }> some_target:
> ...
> ...
I don't think having two goto in the code will improve its readability.

>> +
>> +    if (rule1->flags & IMA_KEYRINGS) {
>> +        if (!rule1->keyrings && !rule2->keyrings) {
>> +            ;
> 
> Another if block no-op
> 
>> +        } else if (!rule1->keyrings || !rule2->keyrings) {
>> +            return false;
>> +        } else if (rule1->keyrings->count != rule2->keyrings->count) {
>> +            return false;
>> +        } else if (rule1->keyrings->count != 0) {
> 
> if (rule1->keyrings->count)
> 
>> +            for (i = 0; i < rule1->keyrings->count; i++) {
> 
> for (int i,
> 
>> +                if (!keyring_has_item(rule1->keyrings->items[i], rule2->keyrings))
>> +                    return false;
>> +            }
>> +        }
>> +    }
>> +
>> +    if (rule1->flags & IMA_LABEL) {
>> +        if (!rule1->label && !rule2->label) {
>> +            ;
> 
> Another if block no-op
> 
>> +        } else if (!rule1->label || !rule2->label) {
>> +            return false;
>> +        } else if (rule1->label->count != rule2->label->count) {
>> +            return false;
>> +        } else if (rule1->label->count != 0) {
>> +            for (i = 0; i < rule1->label->count; i++) {
>> +                if (!labels_has_item(rule1->label->items[i], rule2->label))
>> +                    return false;
>> +            }
>> +        }
>> +    }
>> +
>> +    for (i = 0; i < MAX_LSM_RULES; i++) {
> 
> for (int i,
> 
>> +        if (!rule1->lsm[i].rule && !rule2->lsm[i].rule)
>> +            continue;
>> +
>> +        if (!rule1->lsm[i].rule || !rule2->lsm[i].rule)
>> +            return false;
>> +
>> +        if (strcmp(rule1->lsm[i].args_p, rule2->lsm[i].args_p) != 0)
>> +            return false;
>> +    }
>> +
>> +    return true;
>> +}
>> +
>> +/**
>> + * ima_rule_exists - check if a rule already exists in the policy
>> + *
>> + * Checking both the active policy and the temporary rules list.
>> + */
>> +static bool ima_rule_exists(struct ima_rule_entry *new_rule)
>> +{
>> +    struct ima_rule_entry *entry;
>> +    struct list_head *ima_rules_tmp;
>> +
>> +    if (!list_empty(&ima_temp_rules)) {
>> +        list_for_each_entry(entry, &ima_temp_rules, list) {
>> +            if (ima_rules_equal(entry, new_rule))
>> +                return true;
>> +        }
>> +    }
>> +
>> +    rcu_read_lock();
>> +    ima_rules_tmp = rcu_dereference(ima_rules);
>> +    list_for_each_entry_rcu(entry, ima_rules_tmp, list) {
>> +        if (ima_rules_equal(entry, new_rule)) {
>> +            rcu_read_unlock();
>> +            return true;
>> +        }
>> +    }
>> +    rcu_read_unlock();
>> +
>> +    return false;
>> +}
>> +
>>   /**
>>    * ima_parse_add_rule - add a rule to ima_policy_rules
>>    * @rule: ima measurement policy rule
>> @@ -1993,7 +2140,15 @@ ssize_t ima_parse_add_rule(char *rule)
>>           return result;
>>       }
>>   -    list_add_tail(&entry->list, &ima_temp_rules);
>> +    if (!ima_rule_exists(entry)) {
>> +        list_add_tail(&entry->list, &ima_temp_rules);
>> +    } else {
>> +        result = -EEXIST;
> Is it necessary to set result? Or can you just pass -EEXIST to the audit call below?
> 
>> +        ima_free_rule(entry);
>> +        integrity_audit_msg(AUDIT_INTEGRITY_STATUS, NULL,
>> +                    NULL, op, "duplicate-policy", result,
>> +                    audit_info);
>> +    }
>>         return len;
>>   }
I

^ permalink raw reply	[flat|nested] 7+ messages in thread

end of thread, other threads:[~2025-11-10 19:06 UTC | newest]

Thread overview: 7+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-11-06 18:14 [Patch V1] ima: avoid duplicate policy rules insertions Tahera Fahimi
2025-11-06 20:32 ` Anirudh Venkataramanan
2025-11-10 19:06   ` Tahera Fahimi
2025-11-07  6:54 ` kernel test robot
2025-11-07  9:44 ` Roberto Sassu
2025-11-07 10:56   ` Lennart Poettering
2025-11-07 11:56     ` Roberto Sassu

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).