[PATCH 2/2] audit string fields interface + consumer

Amy Griffis amy.griffis at hp.com
Thu Jan 19 22:19:39 UTC 2006


Another update with the following changes:
    - explicitly disallow use of AUDIT_WATCH field with struct audit_rule
    - remove unnecessary err variable from audit_to_watch()
    - add unlikely() for ENOMEM conditional
    - cosmetic name change (audit_detach_watch)

On Wed, Jan 11, 2006 at 02:04:53PM -0500, Amy Griffis wrote:
> Add AUDIT_WATCH field type and associated helpers.
> 
> Signed-off-by: Amy Griffis <amy.griffis at hp.com>

---

 include/linux/audit.h |    1 
 kernel/audit.h        |    8 +++
 kernel/auditfilter.c  |  136 +++++++++++++++++++++++++++++++++++++++++++------
 kernel/auditsc.c      |    3 +
 4 files changed, 131 insertions(+), 17 deletions(-)

ec717eea41bdbe52454401364a43bc33ec2668bd
diff --git a/include/linux/audit.h b/include/linux/audit.h
index c208554..d76fa58 100644
--- a/include/linux/audit.h
+++ b/include/linux/audit.h
@@ -148,6 +148,7 @@
 #define AUDIT_INODE	102
 #define AUDIT_EXIT	103
 #define AUDIT_SUCCESS   104	/* exit >= 0; value ignored */
+#define AUDIT_WATCH	105
 
 #define AUDIT_ARG0      200
 #define AUDIT_ARG1      (AUDIT_ARG0+1)
diff --git a/kernel/audit.h b/kernel/audit.h
index f3b2a00..5033e1f 100644
--- a/kernel/audit.h
+++ b/kernel/audit.h
@@ -52,6 +52,12 @@ enum audit_state {
 };
 
 /* Rule lists */
+struct audit_watch {
+	char			*path;	 /* watch insertion path */
+	struct list_head	mlist;	 /* entry in master_watchlist */
+	struct list_head	rules;	 /* associated rules */
+};
+
 struct audit_field {
 	u32			type;
 	u32			val;
@@ -67,6 +73,8 @@ struct audit_krule {
 	u32			buflen; /* for data alloc on list rules */
 	u32			field_count;
 	struct audit_field	fields[AUDIT_MAX_FIELDS];
+	struct audit_watch	*watch;	 /* associated watch */
+	struct list_head	rlist;	 /* entry in audit_watch.rules list */
 };
 
 struct audit_entry {
diff --git a/kernel/auditfilter.c b/kernel/auditfilter.c
index 296bda9..6506084 100644
--- a/kernel/auditfilter.c
+++ b/kernel/auditfilter.c
@@ -22,6 +22,8 @@
 #include <linux/kernel.h>
 #include <linux/audit.h>
 #include <linux/kthread.h>
+#include <linux/fs.h>
+#include <linux/namei.h>
 #include <linux/netlink.h>
 #include "audit.h"
 
@@ -40,6 +42,8 @@ struct list_head audit_filter_list[AUDIT
 #endif
 };
 
+static LIST_HEAD(master_watchlist);
+
 /* Unpack a filter field's string representation from user-space
  * buffer. */
 static char *audit_unpack_string(void **bufp, size_t *remain, size_t len)
@@ -67,6 +71,33 @@ static char *audit_unpack_string(void **
 	return str;
 }
 
+/* Translate a watch string to kernel respresentation. */
+static int audit_to_watch(char *path, struct audit_krule *krule, int fidx)
+{
+	struct audit_field *f = &krule->fields[fidx];
+	struct nameidata nd;
+	struct audit_watch *watch;
+
+	if (path[0] != '/' || path[f->val-1] == '/' ||
+	    krule->listnr != AUDIT_FILTER_EXIT ||
+	    f->op & ~AUDIT_EQUAL)
+		return -EINVAL;
+
+	if (path_lookup(path, 0, &nd) == 0)
+		f->val = nd.dentry->d_inode->i_ino;
+	else
+		f->val = (unsigned int)-1;
+	path_release(&nd);
+
+	watch = kmalloc(sizeof(*watch), GFP_KERNEL);
+	if (unlikely(!watch))
+		return -ENOMEM;
+	watch->path = path;
+	krule->watch = watch;
+
+	return 0;
+}
+
 /* Common user-space to kernel rule translation. */
 static inline struct audit_entry *audit_to_entry_common(struct audit_rule *rule)
 {
@@ -129,15 +160,16 @@ static struct audit_entry *audit_rule_to
 	for (i = 0; i < rule->field_count; i++) {
 		struct audit_field *f = &entry->rule.fields[i];
 
-		if (rule->fields[i] & AUDIT_UNUSED_BITS) {
-			err = -EINVAL;
-			goto exit_free;
-		}
-
 		f->op = rule->fields[i] & (AUDIT_NEGATE|AUDIT_OPERATORS);
 		f->type = rule->fields[i] & ~(AUDIT_NEGATE|AUDIT_OPERATORS);
 		f->val = rule->values[i];
 
+		if (f->type & AUDIT_UNUSED_BITS ||
+		    f->type == AUDIT_WATCH) {
+			err = -EINVAL;
+			goto exit_free;
+		}
+
 		entry->rule.vers_ops = (f->op & AUDIT_OPERATORS) ? 2 : 1;
 		if (f->op & AUDIT_NEGATE)
 			f->op |= AUDIT_NOT_EQUAL;
@@ -161,8 +193,9 @@ static struct audit_entry *audit_data_to
 	int err = 0;
 	struct audit_entry *entry;
 	void *bufp;
-	/* size_t remain = datasz - sizeof(struct audit_rule_data); */
+	size_t remain = datasz - sizeof(struct audit_rule_data);
 	int i;
+	char *path;
 
 	entry = audit_to_entry_common((struct audit_rule *)data);
 	if (IS_ERR(entry))
@@ -180,10 +213,20 @@ static struct audit_entry *audit_data_to
 
 		f->op = data->fieldflags[i] & AUDIT_OPERATORS;
 		f->type = data->fields[i];
+		f->val = data->values[i];
 		switch(f->type) {
-		/* call type-specific conversion routines here */
-		default:
-			f->val = data->values[i];
+		case AUDIT_WATCH:
+			path = audit_unpack_string(&bufp, &remain, f->val);
+			if (IS_ERR(path))
+				goto exit_free;
+			entry->rule.buflen += f->val;
+
+			err = audit_to_watch(path, &entry->rule, i);
+			if (err) {
+				kfree(path);
+				goto exit_free;
+			}
+			break;
 		}
 	}
 
@@ -259,7 +302,10 @@ static struct audit_rule_data *audit_kru
 		data->fields[i] = f->type;
 		data->fieldflags[i] = f->op;
 		switch(f->type) {
-		/* call type-specific conversion routines here */
+		case AUDIT_WATCH:
+			data->buflen += data->values[i] =
+				audit_pack_string(&bufp, krule->watch->path);
+			break;
 		default:
 			data->values[i] = f->val;
 		}
@@ -269,6 +315,12 @@ static struct audit_rule_data *audit_kru
 	return data;
 }
 
+/* Compare two watches.  Considered success if rules don't match. */
+static inline int audit_compare_watch(struct audit_watch *a, struct audit_watch *b)
+{
+	return strcmp(a->path, b->path);
+}
+
 /* Compare two rules in kernel format.  Considered success if rules
  * don't match. */
 static int audit_compare_rule(struct audit_krule *a, struct audit_krule *b)
@@ -287,7 +339,10 @@ static int audit_compare_rule(struct aud
 			return 1;
 
 		switch(a->fields[i].type) {
-		/* call type-specific comparison routines here */
+		case AUDIT_WATCH:
+			if (audit_compare_watch(a->watch, b->watch))
+				return 1;
+			break;
 		default:
 			if (a->fields[i].val != b->fields[i].val)
 				return 1;
@@ -301,6 +356,38 @@ static int audit_compare_rule(struct aud
 	return 0;
 }
 
+static inline void audit_free_watch(struct audit_watch *watch)
+{
+	kfree(watch->path);
+	kfree(watch);
+}
+
+static inline void audit_free_rule(struct rcu_head *head)
+{
+	struct audit_entry *e = container_of(head, struct audit_entry, rcu);
+	kfree(e);
+}
+
+/* Attach krule's watch to master_watchlist, using existing watches
+ * when possible. */
+static inline void audit_add_watch(struct audit_krule *krule)
+{
+	struct audit_watch *w;
+
+	list_for_each_entry(w, &master_watchlist, mlist) {
+		if (audit_compare_watch(w, krule->watch))
+			continue;
+
+		audit_free_watch(krule->watch);
+		krule->watch = w;
+		list_add(&krule->rlist, &w->rules);
+		return;
+	}
+	INIT_LIST_HEAD(&krule->watch->rules);
+	list_add(&krule->rlist, &krule->watch->rules);
+	list_add(&krule->watch->mlist, &master_watchlist);
+}
+
 /* Add rule to given filterlist if not a duplicate.  Protected by
  * audit_netlink_sem. */
 static inline int audit_add_rule(struct audit_entry *entry,
@@ -315,6 +402,8 @@ static inline int audit_add_rule(struct 
 			return -EEXIST;
 	}
 
+	if (entry->rule.watch)
+		audit_add_watch(&entry->rule);
 	if (entry->rule.flags & AUDIT_FILTER_PREPEND) {
 		list_add_rcu(&entry->list, list);
 	} else {
@@ -324,10 +413,18 @@ static inline int audit_add_rule(struct 
 	return 0;
 }
 
-static inline void audit_free_rule(struct rcu_head *head)
+/* Detach watch from krule, freeing if it has no associated rules. */
+static inline void audit_detach_watch(struct audit_krule *krule)
 {
-	struct audit_entry *e = container_of(head, struct audit_entry, rcu);
-	kfree(e);
+	struct audit_watch *watch = krule->watch;
+
+	list_del(&krule->rlist);
+	krule->watch = NULL;
+
+	if (list_empty(&watch->rules)) {
+		list_del(&watch->mlist);
+		audit_free_watch(watch);
+	}
 }
 
 /* Remove an existing rule from filterlist.  Protected by
@@ -342,6 +439,8 @@ static inline int audit_del_rule(struct 
 	list_for_each_entry(e, list, list) {
 		if (!audit_compare_rule(&entry->rule, &e->rule)) {
 			list_del_rcu(&e->list);
+			if (e->rule.watch)
+				audit_detach_watch(&e->rule);
 			call_rcu(&e->rcu, audit_free_rule);
 			return 0;
 		}
@@ -408,7 +507,7 @@ static int audit_list_rules(void *_dest)
 			if (!data)
 				break;
 			audit_send_reply(pid, seq, AUDIT_LIST_RULES, 0, 1,
-					 data, sizeof(*data));
+					 data, sizeof(*data) + data->buflen);
 			kfree(data);
 		}
 	}
@@ -475,8 +574,11 @@ int audit_receive_filter(int type, int p
 		if (!err)
 			audit_log(NULL, GFP_KERNEL, AUDIT_CONFIG_CHANGE,
 				  "auid=%u added an audit rule\n", loginuid);
-		else
+		else {
+			if (entry->rule.watch)
+				audit_free_watch(entry->rule.watch);
 			kfree(entry);
+		}
 		break;
 	case AUDIT_DEL:
 	case AUDIT_DEL_RULE:
@@ -492,6 +594,8 @@ int audit_receive_filter(int type, int p
 		if (!err)
 			audit_log(NULL, GFP_KERNEL, AUDIT_CONFIG_CHANGE,
 				  "auid=%u removed an audit rule\n", loginuid);
+		if (entry->rule.watch)
+			audit_free_watch(entry->rule.watch);
 		kfree(entry);
 		break;
 	default:
diff --git a/kernel/auditsc.c b/kernel/auditsc.c
index e4f7096..8e98b65 100644
--- a/kernel/auditsc.c
+++ b/kernel/auditsc.c
@@ -240,7 +240,8 @@ static int audit_filter_rules(struct tas
 			}
 			break;
 		case AUDIT_INODE:
-			if (ctx) {
+		case AUDIT_WATCH:
+			if (ctx && f->val != (unsigned int)-1) {
 				for (j = 0; j < ctx->name_count; j++) {
 					if (audit_comparator(ctx->names[j].ino, f->op, f->val) ||
 					    audit_comparator(ctx->names[j].pino, f->op, f->val)) {
-- 
0.99.9n




More information about the Linux-audit mailing list