[PATCH] support for context based audit filtering

Darrel Goeddel dgoeddel at trustedcs.com
Wed Mar 8 18:46:51 UTC 2006


Amy Griffis wrote:
> Hi Darrel,
> 
> I didn't notice this patch in my inbox until just recently.  I've put
> a few comments inline.
> 
> On Fri, Feb 24, 2006 at 04:26:13PM -0600, Darrel Goeddel wrote:
> 
>>diff --git a/kernel/auditfilter.c b/kernel/auditfilter.c
>>index 3712295..752e2bb 100644
>>--- a/kernel/auditfilter.c
>>+++ b/kernel/auditfilter.c
 <snip>
>>@@ -726,3 +784,143 @@ unlock_and_return:
>>	rcu_read_unlock();
>>	return result;
>>}
>>+
>>+/* Check to see if the rule contains any selinux fields.  Returns 1 if 
>>there
>>+   are selinux fields specified in the rule, 0 otherwise. */
>>+static inline int audit_rule_has_selinux(struct audit_krule *rule)
>>+{
>>+	int i;
>>+
>>+	for (i = 0; i < rule->field_count; i++) {
>>+		struct audit_field *f = &rule->fields[i];
>>+		switch (f->type) {
>>+		case AUDIT_SE_USER:
>>+		case AUDIT_SE_ROLE:
>>+		case AUDIT_SE_TYPE:
>>+		case AUDIT_SE_SEN:
>>+		case AUDIT_SE_CLR:
>>+			return 1;
>>+		}
>>+	}
>>+
>>+	return 0;
>>+}
>>+
>>+/* Make a copy of src in dest.  This will be a deep copy with the exception
>>+   of the watch - that pointer is carried over.  The selinux specific 
>>fields
>>+   will be updated in the copy.  The point is to be able to replace the src
>>+   rule with the dest rule in the list, then free the dest rule. */
>>+static inline int selinux_audit_rule_update_helper(struct audit_krule 
>>*dest,
>>+                                                   struct audit_krule *src)
>>+{
>>+	int i, err = 0;
>>+
>>+	dest->vers_ops = src->vers_ops;
>>+	dest->flags = src->flags;
>>+	dest->listnr = src->listnr;
>>+	dest->action = src->action;
>>+	for (i = 0; i < AUDIT_BITMASK_SIZE; i++)
>>+		dest->mask[i] = src->mask[i];
>>+	dest->buflen = src->buflen;
>>+	dest->field_count = src->field_count;
>>+
>>+	/* deep copy this information, updating the se_rule fields, because
>>+	   the originals will all be freed when the old rule is freed. */
>>+	dest->fields = kzalloc(sizeof(struct audit_field) * 
>>dest->field_count,
>>+	                       GFP_ATOMIC);
>>+	if (!dest->fields)
>>+		return -ENOMEM;
> 
> 
> It seems like it would be cleaner to group the memory allocations for
> the rule entry and the fields into a single function, as any audit
> rule must always have both.  This would allow you to retain the
> assumption of the existence of fields in audit_free_rule.

I originally went with that approach, but later decided to break it out
to make a generic copy function (which I scrapped in favor of the
specialized helper because I didn't know anything about the watches...).
I'm assuming that this whole thing will need modification based on your
continued fs auditing work.  I'll keep this in mind for the next version.

> 
>>+	memcpy(dest->fields, src->fields,
>>+	       sizeof(struct audit_field) * dest->field_count);
>>+	for (i = 0; i < dest->field_count; i++) {
>>+		struct audit_field *df = &dest->fields[i];
>>+		struct audit_field *sf = &src->fields[i];
>>+		switch (df->type) {
>>+		case AUDIT_SE_USER:
>>+		case AUDIT_SE_ROLE:
>>+		case AUDIT_SE_TYPE:
>>+		case AUDIT_SE_SEN:
>>+		case AUDIT_SE_CLR:
>>+			/* our own copy of se_str */
>>+			df->se_str = kstrdup(sf->se_str, GFP_ATOMIC);
> 
> 
> I realize failure is unlikely here, but shouldn't you check the return
> value?  Later on you'll end up passing this to strcmp() which won't
> like it if it's NULL.  

Yep.  Thanks.

> 
>>+			/* our own (refreshed) copy of se_rule */
>>+			err = selinux_audit_rule_init(df->type, df->op,
>>+			                              df->se_str, 
>>&df->se_rule);
>>+			/* Keep currently invalid fields around in case they
>>+			   become valid after a policy reload. */
>>+			if (err == -EINVAL) {
>>+				printk(KERN_WARNING "selinux audit rule for 
>>item %s is invalid\n", df->se_str);
>>+				err = 0;
>>+			}
>>+			if (err)
>>+				return err;
>>+		}
>>+	}
>>+
>>+	/* we can shallow copy the watch because we will not be freeing it 
>>via
>>+	   selinux_audit_rule_update (and we do nto modify it) */
> 
> 
> On the flipside, when audit watches are updated, the se_str and
> se_rule will need to be deep copied since they are freed in
> audit_free_rule().  Is this what you intend?

As I mentioned before, I'm not really sure on the whole watch situation.
Sounds right though.  se_str will need to be copied and se_rule would need
to be re-initialized using selinux_audit_rule_init() because it is an
opaque item.

> 
>>+	dest->watch = src->watch;
>>+	dest->rlist = src->rlist;
> 
> 
> 
>>+
>>+	return 0;
>>+}
>>+
>>+/* This function will re-initialize the se_rule field of all applicable 
>>rules.
>>+   It will traverse the filter lists serarching for rules that contain 
>>selinux
>>+   specific filter fields.  When such a rule is found, it is copied, the
>>+   selinux field is re-initialized, and the old rule is replaced with the
>>+   updated rule. */
>>+/* XXX: is the error handling below appropriate */
>>+static int selinux_audit_rule_update(void)
>>+{
>>+	struct audit_entry *entry, *nentry;
>>+	int i, err = 0, tmperr;
>>+
>>+	/* audit_netlink_sem synchronizes the writers */
>>+	down(&audit_netlink_sem);
>>+
>>+	for (i = 0; i < AUDIT_NR_FILTERS; i++) {
>>+		list_for_each_entry(entry, &audit_filter_list[i], list) {
>>+			if (!audit_rule_has_selinux(&entry->rule))
>>+				continue;
>>+
>>+			nentry = kmalloc(sizeof(*entry), GFP_ATOMIC);
>>+			if (!nentry) {
>>+				/* save the first error encountered for the
>>+				   return value */
>>+				if (!err)
>>+					err = -ENOMEM;
>>+				audit_panic("error updating selinux 
>>filters");
>>+				continue;
>>+			}
>>+
>>+			tmperr = 
>>selinux_audit_rule_update_helper(&nentry->rule,
>>+			                                          
>>&entry->rule);
>>+			if (!nentry) {
> 
> 
> How would we end up with !nentry here?  Maybe you mean if (temperr) ?

Yep.

>>+				/* save the first error encountered for the
>>+				   return value */
>>+				if (!err)
>>+					err = -ENOMEM;
> 
> 
> You don't want to hardcode the -ENOMEM as selinux_audit_rule_init()
> can also return an error.

Yep.  Should be tmperr.

> 
>>+				audit_free_rule(nentry);
>>+				audit_panic("error updating selinux 
>>filters");
>>+				continue;
>>+			}
>>+			list_replace_rcu(&entry->list, &nentry->list);
>>+			call_rcu(&entry->rcu, audit_free_rule_rcu);
>>+		}
>>+	}
>>+
>>+	up(&audit_netlink_sem);
>>+
>>+	return err;
>>+}
>>+
>>+/* Register the callback with selinux.  This callback will be invoked when 
>>a
>>+   new policy is loaded. */
>>+static int __init register_selinux_update_callback(void)
>>+{
>>+	selinux_audit_set_callback(&selinux_audit_rule_update);
>>+	return 0;
>>+}
>>+__initcall(register_selinux_update_callback);
>>+
> 
> 
> I don't know about anyone else, but I would prefer to keep all of the
> initialization for the audit subsystem in audit.c:audit_init().  This
> makes the audit initialization path more easily synchronized and
> readable.

That can be done.  I liked keeping this all scoped together.  We could put
this part in audit_init (audit.c) and put the declaration for
selinux_audit_rule_update in kernel/audit.h.

Thanks for the feedback.  I'm sure this stuff will look much better when
integrated with your audit fs work.

-- 

Darrel




More information about the Linux-audit mailing list