[PATCH] filesystem location based auditing

Amy Griffis amy.griffis at hp.com
Tue Mar 14 00:18:05 UTC 2006


Here is another iteration based off of audit-current.git plus the following
pre-requisites:

selinux support for context based audit filtering:
https://www.redhat.com/archives/linux-audit/2006-February/msg00160.html

context based audit filtering:
https://www.redhat.com/archives/linux-audit/2006-March/msg00107.html

inotify kernel api:
https://www.redhat.com/archives/linux-audit/2006-January/msg00084.html

This version fixes the following:

- remove extra parent put in audit_inotify_register()
- add missing unlock in audit_add_rule() error path
- replace per-filterlist spinlocks with use of audit_netlink_mutex (see below)
- remove now un-needed GFP_ATOMIC allocations
- remove now unused AUDIT_ENTRY_DEL flag - all code paths either avoid stale
  data by taking the mutex, or don't care
- take mutex to update parent data in audit_inotify_register()
- kernel enforces 1 watch per rule to avoid potential memleak
- add comments describing locking and refcounts
- miscellaneous code cleanup

The audit_netlink_mutex was previously taken/released in audit_receive() with
the following comment:

/* The netlink socket is only to be read by 1 CPU, which lets us assume
 * that list additions and deletions never happen simultaneously in
 * auditsc.c */

audit_receive() is three calls up the stack from where we need to release the
mutex for some operations in audit_add_rule() and audit_del_rule().  However,
from what I could see, it didn't seem to be protecting anything specific to the
netlink socket itself, but rather the operations on filterlists.  For that
reason I renamed it to audit_filter_mutex and modified the code to use it
explicitly around filterlist manipulations.

Please verify my analysis on this matter.  If incorrect we will need two
mutexes: audit_netlink_mutex and audit_filter_mutex.

Thanks,
Amy


---

 include/linux/audit.h |    1 
 init/Kconfig          |    2 
 kernel/audit.c        |   19 -
 kernel/audit.h        |   32 ++
 kernel/auditfilter.c  |  679 +++++++++++++++++++++++++++++++++++++++++++++++---
 kernel/auditsc.c      |   65 ++--
 6 files changed, 718 insertions(+), 80 deletions(-)

diff --git a/include/linux/audit.h b/include/linux/audit.h
index 76feae3..4fb7fbd 100644
--- a/include/linux/audit.h
+++ b/include/linux/audit.h
@@ -159,6 +159,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/init/Kconfig b/init/Kconfig
index 38416a1..7fc7b20 100644
--- a/init/Kconfig
+++ b/init/Kconfig
@@ -177,7 +177,7 @@ config AUDIT
 
 config AUDITSYSCALL
 	bool "Enable system-call auditing support"
-	depends on AUDIT && (X86 || PPC || PPC64 || S390 || IA64 || UML || SPARC64)
+	depends on AUDIT && INOTIFY && (X86 || PPC || PPC64 || S390 || IA64 || UML || SPARC64)
 	default y if SECURITY_SELINUX
 	help
 	  Enable low-overhead system-call auditing infrastructure that
diff --git a/kernel/audit.c b/kernel/audit.c
index 65e1d03..6eff223 100644
--- a/kernel/audit.c
+++ b/kernel/audit.c
@@ -56,6 +56,7 @@
 #include <linux/skbuff.h>
 #include <linux/netlink.h>
 #include <linux/selinux.h>
+#include <linux/inotify.h>
 
 #include "audit.h"
 
@@ -102,6 +103,9 @@ static atomic_t    audit_lost = ATOMIC_I
 /* The netlink socket. */
 static struct sock *audit_sock;
 
+/* Inotify device. */
+struct inotify_device *audit_idev;
+
 /* The audit_freelist is a list of pre-allocated audit buffers (if more
  * than AUDIT_MAXFREE are in use, the audit buffer is freed instead of
  * being placed on the freelist). */
@@ -114,11 +118,6 @@ static struct task_struct *kauditd_task;
 static DECLARE_WAIT_QUEUE_HEAD(kauditd_wait);
 static DECLARE_WAIT_QUEUE_HEAD(audit_backlog_wait);
 
-/* The netlink socket is only to be read by 1 CPU, which lets us assume
- * that list additions and deletions never happen simultaneously in
- * auditsc.c */
-DEFINE_MUTEX(audit_netlink_mutex);
-
 /* AUDIT_BUFSIZ is the size of the temporary buffer used for formatting
  * audit records.  Since printk uses a 1024 byte buffer, this buffer
  * should be at least that large. */
@@ -541,14 +540,11 @@ static void audit_receive(struct sock *s
 	struct sk_buff  *skb;
 	unsigned int qlen;
 
-	mutex_lock(&audit_netlink_mutex);
-
 	for (qlen = skb_queue_len(&sk->sk_receive_queue); qlen; qlen--) {
 		skb = skb_dequeue(&sk->sk_receive_queue);
 		audit_receive_skb(skb);
 		kfree_skb(skb);
 	}
-	mutex_unlock(&audit_netlink_mutex);
 }
 
 
@@ -573,6 +569,13 @@ static int __init audit_init(void)
 	selinux_audit_set_callback(&selinux_audit_rule_update);
 
 	audit_log(NULL, GFP_KERNEL, AUDIT_KERNEL, "initialized");
+
+#ifdef CONFIG_AUDITSYSCALL
+	audit_idev = inotify_init(audit_handle_ievent);
+	if (IS_ERR(audit_idev))
+		audit_panic("cannot initialize inotify device");
+#endif
+
 	return 0;
 }
 __initcall(audit_init);
diff --git a/kernel/audit.h b/kernel/audit.h
index 6f73392..423e826 100644
--- a/kernel/audit.h
+++ b/kernel/audit.h
@@ -19,10 +19,11 @@
  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  */
 
-#include <linux/mutex.h>
 #include <linux/fs.h>
 #include <linux/audit.h>
 
+struct inotify_event;
+
 /* 0 = no checking
    1 = put_count checking
    2 = verbose put_count checking
@@ -53,6 +54,27 @@ enum audit_state {
 };
 
 /* Rule lists */
+struct audit_parent {
+	atomic_t		count;	 /* reference count */
+	unsigned int		flags;	 /* flag in-process removals */
+	u32			wd;	 /* inotify watch descriptor */
+	dev_t			dev;	 /* associated superblock device */
+	unsigned long		ino;	 /* associated inode number */
+	struct list_head	mlist;	 /* entry in master_parents */
+	struct list_head	ilist;	 /* entry in inotify registration list*/
+	struct list_head	watches; /* associated watches */
+};
+
+struct audit_watch {
+	atomic_t		count;	 /* reference count */
+	char			*path;	 /* watch insertion path */
+	dev_t			dev;	 /* associated superblock device */
+	unsigned long		ino;	 /* associated inode number */
+	struct audit_parent	*parent; /* associated parent */
+	struct list_head	wlist;	 /* entry in audit_parent.watches list*/
+	struct list_head	rules;	 /* associated rules */
+};
+
 struct audit_field {
 	u32				type;
 	u32				val;
@@ -70,6 +92,8 @@ struct audit_krule {
 	u32			buflen; /* for data alloc on list rules */
 	u32			field_count;
 	struct audit_field	*fields;
+	struct audit_watch	*watch;	 /* associated watch */
+	struct list_head	rlist;	 /* entry in audit_watch.rules list */
 };
 
 struct audit_entry {
@@ -81,12 +105,14 @@ struct audit_entry {
 
 extern int audit_pid;
 extern int audit_comparator(const u32 left, const u32 op, const u32 right);
-
+extern int audit_compare_dname_path(const char *dname, const char *path);
 extern void		    audit_send_reply(int pid, int seq, int type,
 					     int done, int multi,
 					     void *payload, int size);
 extern void		    audit_log_lost(const char *message);
 extern void		    audit_panic(const char *message);
-extern struct mutex audit_netlink_mutex;
 
 extern int selinux_audit_rule_update(void);
+extern void audit_handle_ievent(struct inotify_event *event,
+				const char *dname, struct inode * inode,
+				void *ptr);
diff --git a/kernel/auditfilter.c b/kernel/auditfilter.c
index c895de7..0a4c99d 100644
--- a/kernel/auditfilter.c
+++ b/kernel/auditfilter.c
@@ -22,13 +22,45 @@
 #include <linux/kernel.h>
 #include <linux/audit.h>
 #include <linux/kthread.h>
+#include <linux/mutex.h>
+#include <linux/fs.h>
+#include <linux/namei.h>
 #include <linux/netlink.h>
+#include <linux/inotify.h>
 #include <linux/selinux.h>
 #include "audit.h"
 
-/* There are three lists of rules -- one to search at task creation
- * time, one to search at syscall entry time, and another to search at
- * syscall exit time. */
+/*
+ * Locking model:
+ *
+ * audit_filter_mutex:
+ * 		Synchronizes writes and blocking reads of audit's filterlist
+ * 		data.  Rcu is used to traverse the filterlist and access
+ * 		contents of structs audit_entry, audit_watch and opaque
+ * 		selinux rules during filtering.  If modified, these structures
+ * 		must be copied and replace their counterparts in the filterlist.
+ * 		An audit_parent struct is not accessed during filtering, so may
+ * 		be written directly provided audit_filter_mutex is held.
+ *
+ * 	master_parents_lock: (spinlock)
+ * 		Protects master_parents list.
+ */
+
+/*
+ * Reference counting:
+ *
+ * audit_parent: lifetime is from audit_init_parent() to audit_remove_parent().
+ * 	Each audit_watch holds a reference to its associated parent.
+ *
+ * audit_watch: if added to lists, lifetime is from audit_init_watch() to one
+ * 	of: audit_remove_watch() [user removes], audit_update_watch() [kernel
+ * 	replaces], or audit_remove_parent_watches() [kernel removes].
+ * 	Additionally, an audit_watch may exist temporarily to assist in
+ * 	searching existing filter data.  Each audit_krule holds a reference to
+ * 	its associated watch.
+ */
+
+/* Audit filter lists, defined in <linux/audit.h> */
 struct list_head audit_filter_list[AUDIT_NR_FILTERS] = {
 	LIST_HEAD_INIT(audit_filter_list[0]),
 	LIST_HEAD_INIT(audit_filter_list[1]),
@@ -41,9 +73,55 @@ struct list_head audit_filter_list[AUDIT
 #endif
 };
 
+DEFINE_MUTEX(audit_filter_mutex);
+
+static LIST_HEAD(master_parents);
+static DEFINE_SPINLOCK(master_parents_lock);
+
+/* Inotify device. */
+extern struct inotify_device *audit_idev;
+
+/* Inotify events we care about. */
+#define AUDIT_IN_WATCH IN_MOVE|IN_CREATE|IN_DELETE|IN_DELETE_SELF|IN_MOVE_SELF
+#define AUDIT_IN_SELF  IN_DELETE_SELF|IN_MOVE_SELF|IN_UNMOUNT
+
+static inline void audit_get_parent(struct audit_parent *parent)
+{
+	atomic_inc(&parent->count);
+}
+
+static inline void audit_put_parent(struct audit_parent *parent)
+{
+	if (atomic_dec_and_test(&parent->count)) {
+		WARN_ON(!list_empty(&parent->watches));
+		kfree(parent);
+	}
+}
+
+static inline void audit_get_watch(struct audit_watch *watch)
+{
+	atomic_inc(&watch->count);
+}
+
+static inline void audit_put_watch(struct audit_watch *watch)
+{
+	if (atomic_dec_and_test(&watch->count)) {
+		WARN_ON(!list_empty(&watch->rules));
+		/* watches that were never added don't have a parent */
+		if (watch->parent)
+			audit_put_parent(watch->parent);
+		kfree(watch->path);
+		kfree(watch);
+	}
+}
+
 static inline void audit_free_rule(struct audit_entry *e)
 {
 	int i;
+
+	/* some rules don't have associated watches */
+	if (e->rule.watch)
+		audit_put_watch(e->rule.watch);
 	if (e->rule.fields)
 		for (i = 0; i < e->rule.field_count; i++) {
 			struct audit_field *f = &e->rule.fields[i];
@@ -60,6 +138,43 @@ static inline void audit_free_rule_rcu(s
 	audit_free_rule(e);
 }
 
+/* Initialize a parent watch entry. */
+static inline struct audit_parent *audit_init_parent(void)
+{
+	struct audit_parent *parent;
+
+	parent = kzalloc(sizeof(*parent), GFP_KERNEL);
+	if (unlikely(!parent))
+		return ERR_PTR(-ENOMEM);
+
+	INIT_LIST_HEAD(&parent->watches);
+	atomic_set(&parent->count, 1);
+
+	spin_lock(&master_parents_lock);
+	list_add(&parent->mlist, &master_parents);
+	spin_unlock(&master_parents_lock);
+
+	return parent;
+}
+
+/* Initialize a watch entry. */
+static inline struct audit_watch *audit_init_watch(char *path)
+{
+	struct audit_watch *watch;
+
+	watch = kzalloc(sizeof(*watch), GFP_KERNEL);
+	if (unlikely(!watch))
+		return ERR_PTR(-ENOMEM);
+
+	INIT_LIST_HEAD(&watch->rules);
+	atomic_set(&watch->count, 1);
+	watch->path = path;
+	watch->dev = (dev_t)-1;
+	watch->ino = (unsigned long)-1;
+
+	return watch;
+}
+
 /* Initialize an audit filterlist entry. */
 static inline struct audit_entry *audit_init_entry(u32 field_count)
 {
@@ -107,6 +222,28 @@ static char *audit_unpack_string(void **
 	return str;
 }
 
+/* Translate a watch string to kernel respresentation. */
+static int audit_to_watch(struct audit_krule *krule, char *path, int len,
+			  u32 op)
+{
+	struct audit_watch *watch;
+
+	if (path[0] != '/' || path[len-1] == '/' ||
+	    krule->listnr != AUDIT_FILTER_EXIT ||
+	    op & ~AUDIT_EQUAL ||
+	    krule->watch) /* allow only 1 watch per rule */
+		return -EINVAL;
+
+	watch = audit_init_watch(path);
+	if (unlikely(IS_ERR(watch)))
+		return PTR_ERR(watch);
+
+	audit_get_watch(watch);
+	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)
 {
@@ -177,7 +314,8 @@ static struct audit_entry *audit_rule_to
 		    f->type == AUDIT_SE_ROLE ||
 		    f->type == AUDIT_SE_TYPE ||
 		    f->type == AUDIT_SE_SEN ||
-		    f->type == AUDIT_SE_CLR) {
+		    f->type == AUDIT_SE_CLR ||
+		    f->type == AUDIT_WATCH) {
 			err = -EINVAL;
 			goto exit_free;
 		}
@@ -260,6 +398,18 @@ static struct audit_entry *audit_data_to
 			} else
 				f->se_str = str;
 			break;
+		case AUDIT_WATCH:
+			str = audit_unpack_string(&bufp, &remain, f->val);
+			if (IS_ERR(str))
+				goto exit_free;
+			entry->rule.buflen += f->val;
+
+			err = audit_to_watch(&entry->rule, str, f->val, f->op);
+			if (err) {
+				kfree(str);
+				goto exit_free;
+			}
+			break;
 		}
 	}
 
@@ -343,6 +493,10 @@ static struct audit_rule_data *audit_kru
 			data->buflen += data->values[i] =
 				audit_pack_string(&bufp, f->se_str);
 			break;
+		case AUDIT_WATCH:
+			data->buflen += data->values[i] =
+				audit_pack_string(&bufp, krule->watch->path);
+			break;
 		default:
 			data->values[i] = f->val;
 		}
@@ -378,6 +532,10 @@ static int audit_compare_rule(struct aud
 			if (strcmp(a->fields[i].se_str, b->fields[i].se_str))
 				return 1;
 			break;
+		case AUDIT_WATCH:
+			if (strcmp(a->watch->path, b->watch->path))
+				return 1;
+			break;
 		default:
 			if (a->fields[i].val != b->fields[i].val)
 				return 1;
@@ -391,6 +549,31 @@ static int audit_compare_rule(struct aud
 	return 0;
 }
 
+/* Duplicate the given audit watch.  The new watch's rules list is initialized
+ * to an empty list and wlist is undefined. */
+static inline struct audit_watch *audit_dupe_watch(struct audit_watch *old)
+{
+	char *path;
+	struct audit_watch *new;
+
+	path = kstrdup(old->path, GFP_KERNEL);
+	if (unlikely(!path))
+		return ERR_PTR(-ENOMEM);
+
+	new = audit_init_watch(path);
+	if (unlikely(!new)) {
+		kfree(path);
+		return ERR_PTR(-ENOMEM);
+	}
+
+	new->dev = old->dev;
+	new->ino = old->ino;
+	audit_get_parent(old->parent);
+	new->parent = old->parent;
+
+	return new;
+}
+
 /* Duplicate selinux field information.  The se_rule is opaque, so must be
  * re-initialized. */
 static inline int audit_dupe_selinux_field(struct audit_field *df,
@@ -422,8 +605,11 @@ static inline int audit_dupe_selinux_fie
 /* Duplicate an audit rule.  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 old
- * rule with the new rule in the filterlist, then free the old rule. */
-static struct audit_entry *audit_dupe_rule(struct audit_krule *old)
+ * rule with the new rule in the filterlist, then free the old rule.
+ * The rlist element is undefined; list manipulations are handled apart from
+ * the initial copy. */
+static struct audit_entry *audit_dupe_rule(struct audit_krule *old,
+					   struct audit_watch *watch)
 {
 	u32 fcount = old->field_count;
 	struct audit_entry *entry;
@@ -442,6 +628,7 @@ static struct audit_entry *audit_dupe_ru
 	for (i = 0; i < AUDIT_BITMASK_SIZE; i++)
 		new->mask[i] = old->mask[i];
 	new->buflen = old->buflen;
+	new->watch = NULL;
 	new->field_count = old->field_count;
 	memcpy(new->fields, old->fields, sizeof(struct audit_field) * fcount);
 
@@ -463,25 +650,302 @@ static struct audit_entry *audit_dupe_ru
 		}
 	}
 
+	if (watch) {
+		audit_get_watch(watch);
+		new->watch = watch;
+	}
+
 	return entry;
 }
 
-/* Add rule to given filterlist if not a duplicate.  Protected by
- * audit_netlink_mutex. */
-static inline int audit_add_rule(struct audit_entry *entry,
+/* Update inode numbers in audit rules based on filesystem event. */
+static inline void audit_update_watch(struct audit_parent *parent,
+				      const char *dname, dev_t dev,
+				      unsigned long ino)
+{
+	struct audit_watch *owatch, *nwatch, *nextw;
+	struct audit_krule *r, *nextr;
+	struct audit_entry *oentry, *nentry;
+	struct audit_buffer *ab;
+
+	mutex_lock(&audit_filter_mutex);
+	list_for_each_entry_safe(owatch, nextw, &parent->watches, wlist) {
+		if (audit_compare_dname_path(dname, owatch->path))
+			continue;
+
+		nwatch = audit_dupe_watch(owatch);
+		if (unlikely(IS_ERR(nwatch))) {
+			mutex_unlock(&audit_filter_mutex);
+			audit_panic("error updating watch, skipping");
+			return;
+		}
+		nwatch->dev = dev;
+		nwatch->ino = ino;
+
+		list_for_each_entry_safe(r, nextr, &owatch->rules, rlist) {
+			oentry = container_of(r, struct audit_entry, rule);
+
+			nentry = audit_dupe_rule(&oentry->rule, nwatch);
+			if (unlikely(IS_ERR(nentry))) {
+				audit_panic("error updating watch, removing");
+				list_del(&oentry->rule.rlist);
+				list_del_rcu(&oentry->list);
+			} else {
+				list_add(&nentry->rule.rlist, &nwatch->rules);
+				list_del(&oentry->rule.rlist);
+				list_replace_rcu(&oentry->list, &nentry->list);
+			}
+			call_rcu(&oentry->rcu, audit_free_rule_rcu);
+		}
+
+		ab = audit_log_start(NULL, GFP_KERNEL, AUDIT_CONFIG_CHANGE);
+		audit_log_format(ab, "audit updated rules specifying watch=");
+		audit_log_untrustedstring(ab, owatch->path);
+		audit_log_format(ab, " with dev=%u ino=%lu\n", dev, ino);
+		audit_log_end(ab);
+
+		list_del(&owatch->wlist);
+		audit_put_watch(owatch); /* matches initial get */
+		goto add_watch_to_parent; /* event applies to a single watch */
+	}
+	mutex_unlock(&audit_filter_mutex);
+	return;
+
+add_watch_to_parent:
+	list_add(&nwatch->wlist, &parent->watches);
+	mutex_unlock(&audit_filter_mutex);
+	return;
+}
+
+/* Remove all watches & rules associated with a parent that is going away. */
+static inline void audit_remove_parent_watches(struct audit_parent *parent)
+{
+	struct audit_watch *w, *nextw;
+	struct audit_krule *r, *nextr;
+	struct audit_entry *e;
+
+	mutex_lock(&audit_filter_mutex);
+	list_for_each_entry_safe(w, nextw, &parent->watches, wlist) {
+		list_for_each_entry_safe(r, nextr, &w->rules, rlist) {
+			e = container_of(r, struct audit_entry, rule);
+			list_del(&r->rlist);
+			list_del_rcu(&e->list);
+			call_rcu(&e->rcu, audit_free_rule_rcu);
+
+			audit_log(NULL, GFP_KERNEL, AUDIT_CONFIG_CHANGE,
+				 "audit implicitly removed rule from list=%d\n",
+				  AUDIT_FILTER_EXIT);
+		}
+		list_del(&w->wlist);
+		audit_put_watch(w); /* matches initial get */
+	}
+	mutex_unlock(&audit_filter_mutex);
+}
+
+/* Actually remove the parent; inotify has acknowleged the removal. */
+static inline void audit_remove_parent(struct audit_parent *parent)
+{
+	WARN_ON(!list_empty(&parent->watches));
+	spin_lock(&master_parents_lock);
+	list_del(&parent->mlist);
+	audit_put_parent(parent);
+	spin_unlock(&master_parents_lock);
+}
+
+/* Register inotify watches for parents on in_list. */
+static int audit_inotify_register(struct nameidata *nd,
+				  struct list_head *in_list)
+{
+	struct audit_parent *p;
+	s32 wd;
+	int ret = 0;
+
+	list_for_each_entry(p, in_list, ilist) {
+		/* Grab a ref while calling inotify_add_watch(), so parent
+		 * can't be removed until we've updated its data. */
+		audit_get_parent(p);
+
+		if (!audit_idev)
+			wd = -EOPNOTSUPP;
+		else
+			wd = inotify_add_watch(audit_idev, nd->dentry->d_inode,
+					       AUDIT_IN_WATCH, p);
+		if (wd < 0) {
+			audit_remove_parent_watches(p);
+			audit_remove_parent(p);
+			/* save the first error for return value */
+			if (!ret)
+				ret = wd;
+		} else {
+			struct inode *inode = nd->dentry->d_inode;
+
+			mutex_lock(&audit_filter_mutex);
+			p->wd = wd;
+			p->dev = inode->i_sb->s_dev;
+			p->ino = inode->i_ino;
+			mutex_unlock(&audit_filter_mutex);
+		}
+
+		audit_put_parent(p);
+	}
+
+	return ret;
+}
+
+/* Unregister inotify watches for parents on in_list.
+ * Generates an IN_IGNORED event. */
+static void audit_inotify_unregister(struct list_head *in_list)
+{
+	struct audit_parent *p;
+
+	list_for_each_entry(p, in_list, ilist) {
+		if (audit_idev)
+			inotify_ignore(audit_idev, p->wd);
+		/* matches get in audit_remove_watch() */
+		audit_put_parent(p);
+	}
+}
+
+/* Get path information necessary for adding watches. */
+static int audit_get_nd(char *path, struct nameidata **ndp,
+			struct nameidata **ndw)
+{
+	struct nameidata *ndparent, *ndwatch;
+	int err;
+
+	ndparent = kmalloc(sizeof(*ndparent), GFP_KERNEL);
+	if (unlikely(!ndparent))
+		return -ENOMEM;
+
+	ndwatch = kmalloc(sizeof(*ndwatch), GFP_KERNEL);
+	if (unlikely(!ndwatch)) {
+		kfree(ndparent);
+		return -ENOMEM;
+	}
+
+	err = path_lookup(path, LOOKUP_PARENT, ndparent);
+	if (err) {
+		kfree(ndparent);
+		kfree(ndwatch);
+		return err;
+	}
+
+	err = path_lookup(path, 0, ndwatch);
+	if (err) {
+		kfree(ndwatch);
+		ndwatch = NULL;
+	}
+
+	*ndp = ndparent;
+	*ndw = ndwatch;
+
+	return 0;
+}
+
+/* Release resources used for watch path information. */
+static inline void audit_put_nd(struct nameidata *ndp, struct nameidata *ndw)
+{
+	if (ndp) {
+		path_release(ndp);
+		kfree(ndp);
+	}
+	if (ndw) {
+		path_release(ndw);
+		kfree(ndw);
+	}
+}
+
+/* Find an existing parent entry for this watch, or create a new one.
+ * Caller must hold audit_filter_mutex. */
+static inline struct audit_parent *audit_find_parent(struct nameidata *nd,
+						     struct list_head *in_list)
+{
+	struct audit_parent *p, *parent, *next;
+	struct inode *inode = nd->dentry->d_inode;
+
+	list_for_each_entry_safe(p, next, &master_parents, mlist) {
+		if (p->ino != inode->i_ino ||
+		    p->dev != inode->i_sb->s_dev)
+			continue;
+
+		parent = p;
+		goto out;
+	}
+
+	parent = audit_init_parent();
+	if (IS_ERR(parent))
+		goto out;
+	/* add new parent to inotify registration list */
+	list_add(&parent->ilist, in_list);
+
+out:
+	return parent;
+}
+
+/* Find a matching watch entry, or add this one.
+ * Caller must hold audit_filter_mutex. */
+static inline int audit_add_watch(struct audit_krule *krule,
+				  struct nameidata *ndp, struct nameidata *ndw,
 				  struct list_head *list)
 {
+	struct audit_parent *parent;
+	struct audit_watch *w, *watch = krule->watch;
+
+	parent = audit_find_parent(ndp, list);
+	if (IS_ERR(parent))
+		return PTR_ERR(parent);
+
+	list_for_each_entry(w, &parent->watches, wlist) {
+		if (strcmp(watch->path, w->path))
+			continue;
+
+		audit_put_watch(watch); /* tmp watch, krule's ref */
+		audit_put_watch(watch); /* tmp watch, matches initial get */
+
+		audit_get_watch(w);
+		krule->watch = watch = w;
+		goto add_rule;
+	}
+
+	audit_get_parent(parent);
+	watch->parent = parent;
+	list_add(&watch->wlist, &parent->watches);
+
+add_rule:
+	list_add(&krule->rlist, &watch->rules);
+
+	if (ndw) {
+		watch->dev = ndw->dentry->d_inode->i_sb->s_dev;
+		watch->ino = ndw->dentry->d_inode->i_ino;
+	}
+
+	return 0;
+}
+
+/* Add rule to given filterlist if not a duplicate. */
+static inline int audit_add_rule(struct audit_entry *entry,
+				 struct list_head *list)
+{
 	struct audit_entry *e;
+	struct audit_watch *watch = entry->rule.watch;
+	struct nameidata *ndp, *ndw;
+	LIST_HEAD(inotify_list);
+	int err;
 #ifdef CONFIG_AUDITSYSCALL
 	int dont_count = 0;
 #endif
 
-	/* Do not use the _rcu iterator here, since this is the only
-	 * addition routine. */
+	/* Taking audit_filter_mutex protects from stale rule data and
+	 * writes to an audit_parent. */
+	mutex_lock(&audit_filter_mutex);
 	list_for_each_entry(e, list, list) {
-		if (!audit_compare_rule(&entry->rule, &e->rule))
-			return -EEXIST;
+		if (!audit_compare_rule(&entry->rule, &e->rule)) {
+			err = -EEXIST;
+			mutex_unlock(&audit_filter_mutex);
+			goto error;
+		}
 	}
+	mutex_unlock(&audit_filter_mutex);
 
 	/* If either of these, don't count towards total */
 #ifdef CONFIG_AUDITSYSCALL
@@ -489,43 +953,113 @@ static inline int audit_add_rule(struct 
 		entry->rule.listnr == AUDIT_FILTER_TYPE)
 		dont_count = 1;
 #endif
+	/* Avoid calling path_lookup under audit_filter_mutex. */
+	if (watch) {
+		err = audit_get_nd(watch->path, &ndp, &ndw);
+		if (err)
+			goto error;
+	}
+
+	mutex_lock(&audit_filter_mutex);
+	if (watch) {
+		err = audit_add_watch(&entry->rule, ndp, ndw, &inotify_list);
+		if (err) {
+			mutex_unlock(&audit_filter_mutex);
+			audit_put_nd(ndp, ndw);
+			goto error;
+		}
+	}
 	if (entry->rule.flags & AUDIT_FILTER_PREPEND) {
 		list_add_rcu(&entry->list, list);
 	} else {
 		list_add_tail_rcu(&entry->list, list);
 	}
+	mutex_unlock(&audit_filter_mutex);
+
 #ifdef CONFIG_AUDITSYSCALL
 	if (!dont_count)
 		audit_n_rules++;
 #endif
 
+	if (watch) {
+		err = audit_inotify_register(ndp, &inotify_list);
+		if (err)
+			goto error;
+		audit_put_nd(ndp, ndw);
+	}
+
 	return 0;
+
+error:
+	if (watch)
+		audit_put_watch(watch); /* tmp watch, matches initial get */
+	return err;
 }
 
-/* Remove an existing rule from filterlist.  Protected by
- * audit_netlink_mutex. */
+/* Remove given krule from its associated watch's rules list and clean up any
+ * last instances of associated watch and parent.
+ * Caller must hold audit_filter_mutex. */
+static inline void audit_remove_watch(struct audit_krule *krule,
+				      struct list_head *in_list)
+{
+	struct audit_watch *watch = krule->watch;
+	struct audit_parent *parent = watch->parent;
+
+	list_del(&krule->rlist);
+	if (list_empty(&watch->rules)) {
+		list_del(&watch->wlist);
+		audit_put_watch(watch); /* matches initial get */
+
+		if (list_empty(&parent->watches)) {
+			/* Put parent on the inotify un-registration list.
+			 * Grab a reference before releasing audit_filter_mutex,
+			 * to be released in audit_inotify_unregister(). */
+			list_add(&parent->ilist, in_list);
+			audit_get_parent(parent);
+		}
+	}
+}
+
+/* Remove an existing rule from filterlist. */
 static inline int audit_del_rule(struct audit_entry *entry,
 				 struct list_head *list)
 {
 	struct audit_entry  *e;
+	LIST_HEAD(inotify_list);
 
-	/* Do not use the _rcu iterator here, since this is the only
-	 * deletion routine. */
+	mutex_lock(&audit_filter_mutex);
 	list_for_each_entry(e, list, list) {
-		if (!audit_compare_rule(&entry->rule, &e->rule)) {
-			list_del_rcu(&e->list);
+		if (audit_compare_rule(&entry->rule, &e->rule))
+			continue;
+
+		if (e->rule.watch) {
+			audit_remove_watch(&e->rule, &inotify_list);
+			/* match initial get for tmp watch */
+			audit_put_watch(entry->rule.watch);
+		}
+
+		list_del_rcu(&e->list);
 #ifdef CONFIG_AUDITSYSCALL
-			if (entry->rule.listnr == AUDIT_FILTER_USER ||
-				entry->rule.listnr == AUDIT_FILTER_TYPE)
-				audit_n_rules++;	
+		if (entry->rule.listnr == AUDIT_FILTER_USER ||
+		    entry->rule.listnr == AUDIT_FILTER_TYPE)
+			audit_n_rules++;
 #endif
-			call_rcu(&e->rcu, audit_free_rule_rcu);
+		call_rcu(&e->rcu, audit_free_rule_rcu);
 #ifdef CONFIG_AUDITSYSCALL
-			audit_n_rules--;
+		audit_n_rules--;
 #endif
-			return 0;
-		}
+		mutex_unlock(&audit_filter_mutex);
+		audit_n_rules--;
+
+		if (e->rule.watch)
+			audit_inotify_unregister(&inotify_list);
+
+		return 0;
 	}
+	mutex_unlock(&audit_filter_mutex);
+	/* match initial get for tmp watch */
+	if (entry->rule.watch)
+		audit_put_watch(entry->rule.watch);
 	return -ENOENT;		/* No matching rule */
 }
 
@@ -542,10 +1076,10 @@ static int audit_list(void *_dest)
 	seq = dest[1];
 	kfree(dest);
 
-	mutex_lock(&audit_netlink_mutex);
+	mutex_lock(&audit_filter_mutex);
 
-	/* The *_rcu iterators not needed here because we are
-	   always called with audit_netlink_mutex held. */
+	/* This is a blocking read, so use audit_filter_mutex instead of rcu
+	 * iterator to sync with list writers. */
 	for (i=0; i<AUDIT_NR_FILTERS; i++) {
 		list_for_each_entry(entry, &audit_filter_list[i], list) {
 			struct audit_rule *rule;
@@ -560,7 +1094,7 @@ static int audit_list(void *_dest)
 	}
 	audit_send_reply(pid, seq, AUDIT_LIST, 1, 1, NULL, 0);
 	
-	mutex_unlock(&audit_netlink_mutex);
+	mutex_unlock(&audit_filter_mutex);
 	return 0;
 }
 
@@ -576,10 +1110,10 @@ static int audit_list_rules(void *_dest)
 	seq = dest[1];
 	kfree(dest);
 
-	mutex_lock(&audit_netlink_mutex);
+	mutex_lock(&audit_filter_mutex);
 
-	/* The *_rcu iterators not needed here because we are
-	   always called with audit_netlink_mutex held. */
+	/* This is a blocking read, so use audit_filter_mutex instead of rcu
+	 * iterator to sync with list writers. */
 	for (i=0; i<AUDIT_NR_FILTERS; i++) {
 		list_for_each_entry(e, &audit_filter_list[i], list) {
 			struct audit_rule_data *data;
@@ -588,13 +1122,13 @@ static int audit_list_rules(void *_dest)
 			if (unlikely(!data))
 				break;
 			audit_send_reply(pid, seq, AUDIT_LIST_RULES, 0, 1,
-					 data, sizeof(*data));
+					 data, sizeof(*data) + data->buflen);
 			kfree(data);
 		}
 	}
 	audit_send_reply(pid, seq, AUDIT_LIST_RULES, 1, 1, NULL, 0);
 
-	mutex_unlock(&audit_netlink_mutex);
+	mutex_unlock(&audit_filter_mutex);
 	return 0;
 }
 
@@ -683,6 +1217,32 @@ int audit_receive_filter(int type, int p
 	return err;
 }
 
+/**
+ * audit_handle_ievent - handler for Inotify events
+ * @event: information about the event
+ * @dname: dentry name associated with event
+ * @inode: inode associated with event
+ * @ptr: kernel's version of a watch descriptor
+ */
+void audit_handle_ievent(struct inotify_event *event, const char *dname,
+			  struct inode *inode, void *ptr)
+{
+	struct audit_parent *parent = (struct audit_parent *)ptr;
+
+	if (event->mask & (IN_CREATE|IN_MOVED_TO) && inode)
+		audit_update_watch(parent, dname, inode->i_sb->s_dev,
+				   inode->i_ino);
+	else if (event->mask & (IN_DELETE|IN_MOVED_FROM))
+		audit_update_watch(parent, dname, (dev_t)-1, (unsigned long)-1);
+	/* Note: Inotify doesn't remove the watch for the IN_MOVE_SELF event.
+	 * Work around this by leaving the parent around with an empty
+	 * watchlist.  It will be re-used if new watches are added. */
+	else if (event->mask & (AUDIT_IN_SELF))
+		audit_remove_parent_watches(parent);
+	else if (event->mask & IN_IGNORED)
+		audit_remove_parent(parent);
+}
+
 int audit_comparator(const u32 left, const u32 op, const u32 right)
 {
 	switch (op) {
@@ -703,7 +1263,39 @@ int audit_comparator(const u32 left, con
 	return 0;
 }
 
+/* Compare given dentry name with last component in given path,
+ * return of 0 indicates a match. */
+int audit_compare_dname_path(const char *dname, const char *path)
+{
+	int dlen, plen;
+	const char *p;
+
+	if (!dname || !path)
+		return 1;
+
+	dlen = strlen(dname);
+	plen = strlen(path);
+	if (plen < dlen)
+		return 1;
+
+	/* disregard trailing slashes */
+	p = path + plen - 1;
+	while ((*p == '/') && (p > path))
+		p--;
+
+	/* find last path component */
+	p = p - dlen + 1;
+	if (p < path)
+		return 1;
+	else if (p > path) {
+		if (*--p != '/')
+			return 1;
+		else
+			p++;
+	}
 
+	return strncmp(p, dname, dlen);
+}
 
 static int audit_filter_user_rules(struct netlink_skb_parms *cb,
 				   struct audit_krule *rule,
@@ -817,32 +1409,41 @@ static inline int audit_rule_has_selinux
 int selinux_audit_rule_update(void)
 {
 	struct audit_entry *entry, *nentry;
+	struct audit_watch *watch;
 	int i, err = 0;
 
-	/* audit_netlink_mutex synchronizes the writers */
-	mutex_lock(&audit_netlink_mutex);
+	/* audit_filter_mutex synchronizes the writers */
+	mutex_lock(&audit_filter_mutex);
 
 	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 = audit_dupe_rule(&entry->rule);
+			watch = entry->rule.watch;
+			nentry = audit_dupe_rule(&entry->rule, watch);
 			if (unlikely(IS_ERR(nentry))) {
 				/* save the first error encountered for the
 				 * return value */
 				if (!err)
 					err = PTR_ERR(nentry);
 				audit_panic("error updating selinux filters");
+				if (watch)
+					list_del(&entry->rule.rlist);
 				list_del_rcu(&entry->list);
 			} else {
+				if (watch) {
+					list_add(&nentry->rule.rlist,
+						 &watch->rules);
+					list_del(&entry->rule.rlist);
+				}
 				list_replace_rcu(&entry->list, &nentry->list);
 			}
 			call_rcu(&entry->rcu, audit_free_rule_rcu);
 		}
 	}
 
-	mutex_unlock(&audit_netlink_mutex);
+	mutex_unlock(&audit_filter_mutex);
 
 	return err;
 }
diff --git a/kernel/auditsc.c b/kernel/auditsc.c
index 3aea29b..64f8489 100644
--- a/kernel/auditsc.c
+++ b/kernel/auditsc.c
@@ -166,6 +166,27 @@ struct audit_context {
 #endif
 };
 
+/* Determine if any context name data matches a rule's watch data */
+static inline int audit_match_watch(struct audit_context *ctx,
+				    struct audit_watch *watch)
+{
+	int i;
+
+	if (!ctx)
+		return  0;
+
+	if (watch->ino == (unsigned long)-1)
+		return  0;
+
+	for (i = 0; i < ctx->name_count; i++) {
+		if (ctx->names[i].dev == watch->dev &&
+		    (ctx->names[i].ino == watch->ino ||
+		     ctx->names[i].pino == watch->ino))
+			return 1;
+	}
+
+	return 0;
+}
 
 /* Compare a task_struct with an audit_rule.  Return 1 on match, 0
  * otherwise. */
@@ -262,6 +283,9 @@ static int audit_filter_rules(struct tas
 				}
 			}
 			break;
+		case AUDIT_WATCH:
+			result = audit_match_watch(ctx, rule->watch);
+			break;
 		case AUDIT_LOGINUID:
 			result = 0;
 			if (ctx)
@@ -1092,37 +1116,20 @@ void __audit_inode_child(const char *dna
 		return;
 
 	/* determine matching parent */
-	if (dname)
-		for (idx = 0; idx < context->name_count; idx++)
-			if (context->names[idx].pino == pino) {
-				const char *n;
-				const char *name = context->names[idx].name;
-				int dlen = strlen(dname);
-				int nlen = name ? strlen(name) : 0;
-
-				if (nlen < dlen)
-					continue;
-				
-				/* disregard trailing slashes */
-				n = name + nlen - 1;
-				while ((*n == '/') && (n > name))
-					n--;
-
-				/* find last path component */
-				n = n - dlen + 1;
-				if (n < name)
-					continue;
-				else if (n > name) {
-					if (*--n != '/')
-						continue;
-					else
-						n++;
-				}
+	if (!dname)
+		goto no_match;
+	for (idx = 0; idx < context->name_count; idx++)
+		if (context->names[idx].pino == pino) {
+			const char *name = context->names[idx].name;
 
-				if (strncmp(n, dname, dlen) == 0)
-					goto update_context;
-			}
+			if (!name)
+				continue;
+
+			if (audit_compare_dname_path(dname, name) == 0)
+				goto update_context;
+		}
 
+no_match:
 	/* catch-all in case match not found */
 	idx = context->name_count++;
 	context->names[idx].name  = NULL;




More information about the Linux-audit mailing list