rpms/kernel/FC-5 0003-Inotify-kernel-API.patch, NONE, 1.1.2.1 0004-filesystem-location-based-auditing.patch, NONE, 1.1.2.1 kernel-2.6.spec, 1.2032.2.4, 1.2032.2.5 0003-filesystem-location-based-auditing.patch, 1.1.2.1, NONE

fedora-cvs-commits at redhat.com fedora-cvs-commits at redhat.com
Tue Mar 21 16:18:25 UTC 2006


Author: sgrubb

Update of /cvs/dist/rpms/kernel/FC-5
In directory cvs.devel.redhat.com:/tmp/cvs-serv18593

Modified Files:
      Tag: private-lspp-11-branch
	kernel-2.6.spec 
Added Files:
      Tag: private-lspp-11-branch
	0003-Inotify-kernel-API.patch 
	0004-filesystem-location-based-auditing.patch 
Removed Files:
      Tag: private-lspp-11-branch
	0003-filesystem-location-based-auditing.patch 
Log Message:
Make some updates for fs auditing


0003-Inotify-kernel-API.patch:
 fs/inotify.c             |  395 +++++++++++++++++++++++++++++------------------
 include/linux/fsnotify.h |   29 +--
 include/linux/inotify.h  |   22 ++
 3 files changed, 281 insertions(+), 165 deletions(-)

--- NEW FILE 0003-Inotify-kernel-API.patch ---
Subject: [PATCH] Inotify kernel API
From: Amy Griffis <amy.griffis at hp.com>
Date: 1137855413 -0500

This is the first of two patches, which when complete should be the
last patches for the baseline filesystem audit functionality.

This patch provides a kernel api for inotify.  It was first posted as
an RFC last year:

https://www.redhat.com/archives/linux-audit/2005-August/msg00055.html

I have made some minor changes to address feedback I received and to
provide a little more information in the kernel's event callback.  I
found that adding or removing inotify watches from an event callback
is unnecessary for audit's purposes, so I did not make that change.

I also received some feedback regarding making a cleaner separation
between the core inotify code and a kernel and userspace api.  I
haven't addressed this yet as it would make for a much larger patch
against inotify, and I would like to discuss it with the inotify dev
before making a lot of changes.

I believe this patch represents the functionality audit requires in
terms of an inotify kernel api.  Please have a look and let me know
what you think.

[AV: rediffed]
[folded akpm's fix for CONFIG_INOTIFY=n build]

Signed-off-by: Al Viro <viro at zeniv.linux.org.uk>

---

 fs/inotify.c             |  395 +++++++++++++++++++++++++++++-----------------
 include/linux/fsnotify.h |   29 ++-
 include/linux/inotify.h  |   22 ++-
 3 files changed, 281 insertions(+), 165 deletions(-)

059170e790375f168bab670c2f9e0e04332430f4
diff --git a/fs/inotify.c b/fs/inotify.c
index 3041503..ab57df6 100644
--- a/fs/inotify.c
+++ b/fs/inotify.c
@@ -85,14 +85,18 @@ struct inotify_device {
 	wait_queue_head_t 	wq;		/* wait queue for i/o */
 	struct idr		idr;		/* idr mapping wd -> watch */
 	struct semaphore	sem;		/* protects this bad boy */
-	struct list_head 	events;		/* list of queued events */
 	struct list_head	watches;	/* list of watches */
 	atomic_t		count;		/* reference count */
+	u32			last_wd;	/* the last wd allocated */
+	/* userland consumer API */
+	struct list_head 	events;		/* list of queued events */
 	struct user_struct	*user;		/* user who opened this dev */
 	unsigned int		queue_size;	/* size of the queue (bytes) */
 	unsigned int		event_count;	/* number of pending events */
 	unsigned int		max_events;	/* maximum number of events */
-	u32			last_wd;	/* the last wd allocated */
+	/* kernel consumer API */
+	void (*callback)(struct inotify_event *, const char *, struct inode *,
+			 void *);		/* event callback */
 };
 
 /*
@@ -124,6 +128,7 @@ struct inotify_watch {
 	struct inode		*inode;	/* associated inode */
 	s32 			wd;	/* watch descriptor */
 	u32			mask;	/* event mask for this watch */
+	void		*callback_arg;	/* callback argument - kernel API */
 };
 
 #ifdef CONFIG_SYSCTL
@@ -175,8 +180,10 @@ static inline void get_inotify_dev(struc
 static inline void put_inotify_dev(struct inotify_device *dev)
 {
 	if (atomic_dec_and_test(&dev->count)) {
-		atomic_dec(&dev->user->inotify_devs);
-		free_uid(dev->user);
+		if (dev->user) {
+			atomic_dec(&dev->user->inotify_devs);
+			free_uid(dev->user);
+		}
 		idr_destroy(&dev->idr);
 		kfree(dev);
 	}
@@ -344,6 +351,24 @@ static void inotify_dev_event_dequeue(st
 }
 
 /*
+ * inotify_callback_event - notify kernel consumers of events
+ */
+static void inotify_callback_event(struct inotify_device *dev,
+				   struct inotify_watch *watch,
+				   u32 mask, u32 cookie, const char *name,
+				   struct inode *inode)
+{
+	struct inotify_event event;
+
+	event.wd = watch->wd;
+	event.mask = mask;
+	event.cookie = cookie;
+	event.len = 0; /* kernel consumers don't need length */
+
+	dev->callback(&event, name, inode, watch->callback_arg);
+}
+
+/*
  * inotify_dev_get_wd - returns the next WD for use by the given dev
  *
  * Callers must hold dev->sem.  This function can sleep.
@@ -387,12 +412,13 @@ static int find_inode(const char __user 
  * Both 'dev' and 'inode' (by way of nameidata) need to be pinned.
  */
 static struct inotify_watch *create_watch(struct inotify_device *dev,
-					  u32 mask, struct inode *inode)
+					  u32 mask, struct inode *inode,
+					  void *callback_arg)
 {
 	struct inotify_watch *watch;
 	int ret;
 
-	if (atomic_read(&dev->user->inotify_watches) >=
+	if (dev->user && atomic_read(&dev->user->inotify_watches) >=
 			inotify_max_user_watches)
 		return ERR_PTR(-ENOSPC);
 
@@ -408,6 +434,7 @@ static struct inotify_watch *create_watc
 
 	dev->last_wd = watch->wd;
 	watch->mask = mask;
+	watch->callback_arg = callback_arg;
 	atomic_set(&watch->count, 0);
 	INIT_LIST_HEAD(&watch->d_list);
 	INIT_LIST_HEAD(&watch->i_list);
@@ -425,7 +452,8 @@ static struct inotify_watch *create_watc
 	/* bump our own count, corresponding to our entry in dev->watches */
 	get_inotify_watch(watch);
 
-	atomic_inc(&dev->user->inotify_watches);
+	if (dev->user)
+		atomic_inc(&dev->user->inotify_watches);
 	atomic_inc(&inotify_watches);
 
 	return watch;
@@ -458,7 +486,8 @@ static void remove_watch_no_event(struct
 	list_del(&watch->i_list);
 	list_del(&watch->d_list);
 
-	atomic_dec(&dev->user->inotify_watches);
+	if (dev->user)
+		atomic_dec(&dev->user->inotify_watches);
 	atomic_dec(&inotify_watches);
 	idr_remove(&dev->idr, watch->wd);
 	put_inotify_watch(watch);
@@ -477,7 +506,10 @@ static void remove_watch_no_event(struct
  */
 static void remove_watch(struct inotify_watch *watch,struct inotify_device *dev)
 {
-	inotify_dev_queue_event(dev, watch, IN_IGNORED, 0, NULL);
+	if (dev->callback)
+		inotify_callback_event(dev, watch, IN_IGNORED, 0, NULL, NULL);
+	else
+		inotify_dev_queue_event(dev, watch, IN_IGNORED, 0, NULL);
 	remove_watch_no_event(watch, dev);
 }
 
@@ -490,7 +522,190 @@ static inline int inotify_inode_watched(
 	return !list_empty(&inode->inotify_watches);
 }
 
-/* Kernel API */
+/* Kernel consumer API */
+
+/**
+ * inotify_init - allocates and initializes an inotify device
+ * @callback: kernel consumer's event callback
+ */
+struct inotify_device *inotify_init(void (*callback)(struct inotify_event *,
+						     const char *,
+						     struct inode *, void *))
+{
+	struct inotify_device *dev;
+
+	dev = kmalloc(sizeof(struct inotify_device), GFP_KERNEL);
+	if (unlikely(!dev))
+		return NULL;
+
+	idr_init(&dev->idr);
+	INIT_LIST_HEAD(&dev->events);
+	INIT_LIST_HEAD(&dev->watches);
+	init_waitqueue_head(&dev->wq);
+	sema_init(&dev->sem, 1);
+	dev->event_count = 0;
+	dev->queue_size = 0;
+	dev->max_events = inotify_max_queued_events;
+	dev->user = NULL;	/* set in sys_inotify_init */
+	dev->last_wd = 0;
+	dev->callback = callback;
+	atomic_set(&dev->count, 0);
+	get_inotify_dev(dev);
+
+	return dev;
+}
+EXPORT_SYMBOL_GPL(inotify_init);
+
+/**
+ * inotify_free - clean up and free an inotify device
+ * @dev: inotify device to free
+ */
+int inotify_free(struct inotify_device *dev)
+{
+	/*
+	 * Destroy all of the watches on this device.  Unfortunately, not very
+	 * pretty.  We cannot do a simple iteration over the list, because we
+	 * do not know the inode until we iterate to the watch.  But we need to
+	 * hold inode->inotify_sem before dev->sem.  The following works.
+	 */
+	while (1) {
+		struct inotify_watch *watch;
+		struct list_head *watches;
+		struct inode *inode;
+
+		down(&dev->sem);
+		watches = &dev->watches;
+		if (list_empty(watches)) {
+			up(&dev->sem);
+			break;
+		}
+		watch = list_entry(watches->next, struct inotify_watch, d_list);
+		get_inotify_watch(watch);
+		up(&dev->sem);
+
+		inode = watch->inode;
+		down(&inode->inotify_sem);
+		down(&dev->sem);
+		remove_watch_no_event(watch, dev);
+		up(&dev->sem);
+		up(&inode->inotify_sem);
+		put_inotify_watch(watch);
+	}
+
+	/* destroy all of the events on this device */
+	down(&dev->sem);
+	while (!list_empty(&dev->events))
+		inotify_dev_event_dequeue(dev);
+	up(&dev->sem);
+
+	/* free this device: the put matching the get in inotify_init() */
+	put_inotify_dev(dev);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(inotify_free);
+
+/**
+ * inotify_inotify_add_watch - add a watch to this inotify device
+ * @dev: inotify device
+ * @inode: inode to watch for events
+ * @mask: filesystem event mask
+ * @callback_arg - ptr to data that kernel consumer associates with this watch
+ *
+ * Caller must pin the inode in question, e.g. by calling path_lookup.
+ */
+s32 inotify_add_watch(struct inotify_device *dev, struct inode *inode,
+		      u32 mask, void *callback_arg)
+{
+	int mask_add = 0;
+	struct inotify_watch *watch, *old;
+	int ret;
+
+	down(&inode->inotify_sem);
+	down(&dev->sem);
+
+	if (mask & IN_MASK_ADD)
+		mask_add = 1;
+
+	/* don't let user-space set invalid bits: we don't want flags set */
+	mask &= IN_ALL_EVENTS | IN_ONESHOT;
+	if (unlikely(!mask)) {
+		ret = -EINVAL;
+		goto out;
+	}
+
+	/*
+	 * Handle the case of re-adding a watch on an (inode,dev) pair that we
+	 * are already watching.  We just update the mask and callback_arg and
+	 * return its wd.
+	 */
+	old = inode_find_dev(inode, dev);
+	if (unlikely(old)) {
+		if (mask_add)
+			old->mask |= mask;
+		else
+			old->mask = mask;
+		old->callback_arg = callback_arg;
+		ret = old->wd;
+		goto out;
+	}
+
+	watch = create_watch(dev, mask, inode, callback_arg);
+	if (unlikely(IS_ERR(watch))) {
+		ret = PTR_ERR(watch);
+		goto out;
+	}
+
+	/* Add the watch to the device's and the inode's list */
+	list_add(&watch->d_list, &dev->watches);
+	list_add(&watch->i_list, &inode->inotify_watches);
+	ret = watch->wd;
+
+out:
+	up(&dev->sem);
+	up(&inode->inotify_sem);
+	return ret;
+}
+EXPORT_SYMBOL_GPL(inotify_add_watch);
+
+/**
+ * inotify_ignore - remove a given wd from this inotify device
+ * @dev: inotify device
+ * @wd: watch descriptor to remove
+ */
+int inotify_ignore(struct inotify_device *dev, s32 wd)
+{
+	struct inotify_watch *watch;
+	struct inode *inode;
+
+	down(&dev->sem);
+	watch = idr_find(&dev->idr, wd);
+	if (unlikely(!watch)) {
+		up(&dev->sem);
+		return -EINVAL;
+	}
+	get_inotify_watch(watch);
+	inode = watch->inode;
+	up(&dev->sem);
+
+	down(&inode->inotify_sem);
+	down(&dev->sem);
+
+	/* make sure that we did not race */
+	watch = idr_find(&dev->idr, wd);
+	if (likely(watch))
+		remove_watch(watch, dev);
+
+	up(&dev->sem);
+	up(&inode->inotify_sem);
+	put_inotify_watch(watch);
+
+	return 0;
+
+}
+EXPORT_SYMBOL_GPL(inotify_ignore);
+
+/* Kernel producer API */
 
 /**
  * inotify_inode_queue_event - queue an event to all watches on this inode
@@ -498,9 +713,10 @@ static inline int inotify_inode_watched(
  * @mask: event mask describing this event
  * @cookie: cookie for synchronization, or zero
  * @name: filename, if any
+ * @cinode: child inode, used for events on directories
  */
 void inotify_inode_queue_event(struct inode *inode, u32 mask, u32 cookie,
-			       const char *name)
+			       const char *name, struct inode *cinode)
 {
 	struct inotify_watch *watch, *next;
 
@@ -514,7 +730,12 @@ void inotify_inode_queue_event(struct in
 			struct inotify_device *dev = watch->dev;
 			get_inotify_watch(watch);
 			down(&dev->sem);
-			inotify_dev_queue_event(dev, watch, mask, cookie, name);
+			if (dev->callback)
+				inotify_callback_event(dev, watch, mask,
+						       cookie, name, cinode);
+			else
+				inotify_dev_queue_event(dev, watch, mask,
+							cookie, name);
 			if (watch_mask & IN_ONESHOT)
 				remove_watch_no_event(watch, dev);
 			up(&dev->sem);
@@ -548,7 +769,8 @@ void inotify_dentry_parent_queue_event(s
 	if (inotify_inode_watched(inode)) {
 		dget(parent);
 		spin_unlock(&dentry->d_lock);
-		inotify_inode_queue_event(inode, mask, cookie, name);
+		inotify_inode_queue_event(inode, mask, cookie, name,
+					  dentry->d_inode);
 		dput(parent);
 	} else
 		spin_unlock(&dentry->d_lock);
@@ -631,7 +853,12 @@ void inotify_unmount_inodes(struct list_
 		list_for_each_entry_safe(watch, next_w, watches, i_list) {
 			struct inotify_device *dev = watch->dev;
 			down(&dev->sem);
-			inotify_dev_queue_event(dev, watch, IN_UNMOUNT,0,NULL);
+			if (dev->callback)
+				inotify_callback_event(dev, watch, IN_UNMOUNT,
+						       0, NULL, NULL);
+			else
+				inotify_dev_queue_event(dev, watch, IN_UNMOUNT,
+							0, NULL);
 			remove_watch(watch, dev);
 			up(&dev->sem);
 		}
@@ -757,83 +984,7 @@ static ssize_t inotify_read(struct file 
 
 static int inotify_release(struct inode *ignored, struct file *file)
 {
-	struct inotify_device *dev = file->private_data;
-
-	/*
-	 * Destroy all of the watches on this device.  Unfortunately, not very
-	 * pretty.  We cannot do a simple iteration over the list, because we
-	 * do not know the inode until we iterate to the watch.  But we need to
-	 * hold inode->inotify_sem before dev->sem.  The following works.
-	 */
-	while (1) {
-		struct inotify_watch *watch;
-		struct list_head *watches;
-		struct inode *inode;
-
-		down(&dev->sem);
-		watches = &dev->watches;
-		if (list_empty(watches)) {
-			up(&dev->sem);
-			break;
-		}
-		watch = list_entry(watches->next, struct inotify_watch, d_list);
-		get_inotify_watch(watch);
-		up(&dev->sem);
-
-		inode = watch->inode;
-		down(&inode->inotify_sem);
-		down(&dev->sem);
-		remove_watch_no_event(watch, dev);
-		up(&dev->sem);
-		up(&inode->inotify_sem);
-		put_inotify_watch(watch);
-	}
-
-	/* destroy all of the events on this device */
-	down(&dev->sem);
-	while (!list_empty(&dev->events))
-		inotify_dev_event_dequeue(dev);
-	up(&dev->sem);
-
-	/* free this device: the put matching the get in inotify_init() */
-	put_inotify_dev(dev);
-
-	return 0;
-}
-
-/*
- * inotify_ignore - remove a given wd from this inotify instance.
- *
- * Can sleep.
- */
-static int inotify_ignore(struct inotify_device *dev, s32 wd)
-{
-	struct inotify_watch *watch;
-	struct inode *inode;
-
-	down(&dev->sem);
-	watch = idr_find(&dev->idr, wd);
-	if (unlikely(!watch)) {
-		up(&dev->sem);
-		return -EINVAL;
-	}
-	get_inotify_watch(watch);
-	inode = watch->inode;
-	up(&dev->sem);
-
-	down(&inode->inotify_sem);
-	down(&dev->sem);
-
-	/* make sure that we did not race */
-	watch = idr_find(&dev->idr, wd);
-	if (likely(watch))
-		remove_watch(watch, dev);
-
-	up(&dev->sem);
-	up(&inode->inotify_sem);
-	put_inotify_watch(watch);
-
-	return 0;
+	return inotify_free(file->private_data);
 }
 
 static long inotify_ioctl(struct file *file, unsigned int cmd,
@@ -887,12 +1038,15 @@ asmlinkage long sys_inotify_init(void)
 		goto out_free_uid;
 	}
 
-	dev = kmalloc(sizeof(struct inotify_device), GFP_KERNEL);
+	dev = inotify_init(NULL);
 	if (unlikely(!dev)) {
 		ret = -ENOMEM;
 		goto out_free_uid;
 	}
 
+	dev->user = user;
+	atomic_inc(&user->inotify_devs);
+
 	filp->f_op = &inotify_fops;
 	filp->f_vfsmnt = mntget(inotify_mnt);
 	filp->f_dentry = dget(inotify_mnt->mnt_root);
@@ -901,20 +1055,6 @@ asmlinkage long sys_inotify_init(void)
 	filp->f_flags = O_RDONLY;
 	filp->private_data = dev;
 
-	idr_init(&dev->idr);
-	INIT_LIST_HEAD(&dev->events);
-	INIT_LIST_HEAD(&dev->watches);
-	init_waitqueue_head(&dev->wq);
-	sema_init(&dev->sem, 1);
-	dev->event_count = 0;
-	dev->queue_size = 0;
-	dev->max_events = inotify_max_queued_events;
-	dev->user = user;
-	dev->last_wd = 0;
-	atomic_set(&dev->count, 0);
-
-	get_inotify_dev(dev);
-	atomic_inc(&user->inotify_devs);
 	fd_install(fd, filp);
 
 	return fd;
@@ -928,13 +1068,11 @@ out_put_fd:
 
 asmlinkage long sys_inotify_add_watch(int fd, const char __user *path, u32 mask)
 {
-	struct inotify_watch *watch, *old;
 	struct inode *inode;
 	struct inotify_device *dev;
 	struct nameidata nd;
 	struct file *filp;
 	int ret, fput_needed;
-	int mask_add = 0;
 	unsigned flags = 0;
 
 	filp = fget_light(fd, &fput_needed);
@@ -960,46 +1098,7 @@ asmlinkage long sys_inotify_add_watch(in
 	inode = nd.dentry->d_inode;
 	dev = filp->private_data;
 
-	down(&inode->inotify_sem);
-	down(&dev->sem);
-
-	if (mask & IN_MASK_ADD)
-		mask_add = 1;
-
-	/* don't let user-space set invalid bits: we don't want flags set */
-	mask &= IN_ALL_EVENTS | IN_ONESHOT;
-	if (unlikely(!mask)) {
-		ret = -EINVAL;
-		goto out;
-	}
-
-	/*
-	 * Handle the case of re-adding a watch on an (inode,dev) pair that we
-	 * are already watching.  We just update the mask and return its wd.
-	 */
-	old = inode_find_dev(inode, dev);
-	if (unlikely(old)) {
-		if (mask_add)
-			old->mask |= mask;
-		else
-			old->mask = mask;
-		ret = old->wd;
-		goto out;
-	}
-
-	watch = create_watch(dev, mask, inode);
-	if (unlikely(IS_ERR(watch))) {
-		ret = PTR_ERR(watch);
-		goto out;
-	}
-
-	/* Add the watch to the device's and the inode's list */
-	list_add(&watch->d_list, &dev->watches);
-	list_add(&watch->i_list, &inode->inotify_watches);
-	ret = watch->wd;
-out:
-	up(&dev->sem);
-	up(&inode->inotify_sem);
+	ret = inotify_add_watch(dev, inode, mask, NULL);
 	path_release(&nd);
 fput_and_out:
 	fput_light(filp, fput_needed);
diff --git a/include/linux/fsnotify.h b/include/linux/fsnotify.h
index 94919c3..606b875 100644
--- a/include/linux/fsnotify.h
+++ b/include/linux/fsnotify.h
@@ -35,16 +35,18 @@ static inline void fsnotify_move(struct 
 
 	if (isdir)
 		isdir = IN_ISDIR;
-	inotify_inode_queue_event(old_dir, IN_MOVED_FROM|isdir,cookie,old_name);
-	inotify_inode_queue_event(new_dir, IN_MOVED_TO|isdir, cookie, new_name);
+	inotify_inode_queue_event(old_dir, IN_MOVED_FROM|isdir, cookie,
+				  old_name, NULL);
+	inotify_inode_queue_event(new_dir, IN_MOVED_TO|isdir, cookie,
+				  new_name, source);
 
 	if (target) {
-		inotify_inode_queue_event(target, IN_DELETE_SELF, 0, NULL);
+		inotify_inode_queue_event(target, IN_DELETE_SELF, 0, NULL,NULL);
 		inotify_inode_is_dead(target);
 	}
 
 	if (source) {
-		inotify_inode_queue_event(source, IN_MOVE_SELF, 0, NULL);
+		inotify_inode_queue_event(source, IN_MOVE_SELF, 0, NULL, NULL);
 	}
 	audit_inode_child(old_name, source, old_dir->i_ino);
 	audit_inode_child(new_name, target, new_dir->i_ino);
@@ -66,7 +68,7 @@ static inline void fsnotify_nameremove(s
  */
 static inline void fsnotify_inoderemove(struct inode *inode)
 {
-	inotify_inode_queue_event(inode, IN_DELETE_SELF, 0, NULL);
+	inotify_inode_queue_event(inode, IN_DELETE_SELF, 0, NULL, NULL);
 	inotify_inode_is_dead(inode);
 }
 
@@ -76,7 +78,8 @@ static inline void fsnotify_inoderemove(
 static inline void fsnotify_create(struct inode *inode, struct dentry *dentry)
 {
 	inode_dir_notify(inode, DN_CREATE);
-	inotify_inode_queue_event(inode, IN_CREATE, 0, dentry->d_name.name);
+	inotify_inode_queue_event(inode, IN_CREATE, 0, dentry->d_name.name,
+				  dentry->d_inode);
 	audit_inode_child(dentry->d_name.name, dentry->d_inode, inode->i_ino);
 }
 
@@ -87,7 +90,7 @@ static inline void fsnotify_mkdir(struct
 {
 	inode_dir_notify(inode, DN_CREATE);
 	inotify_inode_queue_event(inode, IN_CREATE | IN_ISDIR, 0, 
-				  dentry->d_name.name);
+				  dentry->d_name.name, dentry->d_inode);
 	audit_inode_child(dentry->d_name.name, dentry->d_inode, inode->i_ino);
 }
 
@@ -104,7 +107,7 @@ static inline void fsnotify_access(struc
 
 	dnotify_parent(dentry, DN_ACCESS);
 	inotify_dentry_parent_queue_event(dentry, mask, 0, dentry->d_name.name);
-	inotify_inode_queue_event(inode, mask, 0, NULL);
+	inotify_inode_queue_event(inode, mask, 0, NULL, NULL);
 }
 
 /*
@@ -120,7 +123,7 @@ static inline void fsnotify_modify(struc
 
 	dnotify_parent(dentry, DN_MODIFY);
 	inotify_dentry_parent_queue_event(dentry, mask, 0, dentry->d_name.name);
-	inotify_inode_queue_event(inode, mask, 0, NULL);
+	inotify_inode_queue_event(inode, mask, 0, NULL, NULL);
 }
 
 /*
@@ -135,7 +138,7 @@ static inline void fsnotify_open(struct 
 		mask |= IN_ISDIR;
 
 	inotify_dentry_parent_queue_event(dentry, mask, 0, dentry->d_name.name);
-	inotify_inode_queue_event(inode, mask, 0, NULL);	
+	inotify_inode_queue_event(inode, mask, 0, NULL, NULL);
 }
 
 /*
@@ -153,7 +156,7 @@ static inline void fsnotify_close(struct
 		mask |= IN_ISDIR;
 
 	inotify_dentry_parent_queue_event(dentry, mask, 0, name);
-	inotify_inode_queue_event(inode, mask, 0, NULL);
+	inotify_inode_queue_event(inode, mask, 0, NULL, NULL);
 }
 
 /*
@@ -168,7 +171,7 @@ static inline void fsnotify_xattr(struct
 		mask |= IN_ISDIR;
 
 	inotify_dentry_parent_queue_event(dentry, mask, 0, dentry->d_name.name);
-	inotify_inode_queue_event(inode, mask, 0, NULL);
+	inotify_inode_queue_event(inode, mask, 0, NULL, NULL);
 }
 
 /*
@@ -215,7 +218,7 @@ static inline void fsnotify_change(struc
 	if (in_mask) {
 		if (S_ISDIR(inode->i_mode))
 			in_mask |= IN_ISDIR;
-		inotify_inode_queue_event(inode, in_mask, 0, NULL);
+		inotify_inode_queue_event(inode, in_mask, 0, NULL, NULL);
 		inotify_dentry_parent_queue_event(dentry, in_mask, 0,
 						  dentry->d_name.name);
 	}
diff --git a/include/linux/inotify.h b/include/linux/inotify.h
index 267c88b..f32c70e 100644
--- a/include/linux/inotify.h
+++ b/include/linux/inotify.h
@@ -14,6 +14,9 @@
  *
  * When you are watching a directory, you will receive the filename for events
  * such as IN_CREATE, IN_DELETE, IN_OPEN, IN_CLOSE, ..., relative to the wd.
+ *
+ * When using inotify from the kernel, len will always be zero.  Instead you
+ * should check the path for non-NULL in your callback.
  */
 struct inotify_event {
 	__s32		wd;		/* watch descriptor */
@@ -71,8 +74,19 @@ struct inotify_event {
 
 #ifdef CONFIG_INOTIFY
 
+/* Kernel consumer API */
+
+extern struct inotify_device *inotify_init(void (*)(struct inotify_event *,
+						    const char *,
+						    struct inode *, void *));
+extern int inotify_free(struct inotify_device *);
+extern __s32 inotify_add_watch(struct inotify_device *, struct inode *, __u32,
+			       void *);
+extern int inotify_ignore(struct inotify_device *, __s32);
+
+/* Kernel producer API */
 extern void inotify_inode_queue_event(struct inode *, __u32, __u32,
-				      const char *);
+				      const char *, struct inode *);
 extern void inotify_dentry_parent_queue_event(struct dentry *, __u32, __u32,
 					      const char *);
 extern void inotify_unmount_inodes(struct list_head *);
@@ -81,9 +95,9 @@ extern u32 inotify_get_cookie(void);
 
 #else
 
-static inline void inotify_inode_queue_event(struct inode *inode,
-					     __u32 mask, __u32 cookie,
-					     const char *filename)
+/* Kernel producer API stubs */
+static inline void inotify_inode_queue_event(struct inode *inode, u32 mask,
+			u32 cookie, const char *name, struct inode *cinode)
 {
 }
 
-- 
0.99.9.GIT

0004-filesystem-location-based-auditing.patch:
 include/linux/audit.h |    1 
 init/Kconfig          |    2 
 kernel/audit.c        |   19 -
 kernel/audit.h        |   32 ++
 kernel/auditfilter.c  |  671 +++++++++++++++++++++++++++++++++++++++++++++++---
 kernel/auditsc.c      |   65 ++--
 6 files changed, 713 insertions(+), 77 deletions(-)

--- NEW FILE 0004-filesystem-location-based-auditing.patch ---
Subject: [PATCH] filesystem location based auditing
From: Amy Griffis <amy.griffis at hp.com>
Date: 1142295485 -0500

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

Signed-off-by: Al Viro <viro at zeniv.linux.org.uk>

---

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

6ba23ad7c06151e53ec55c32e9f842c19ef08f5a
diff --git a/include/linux/audit.h b/include/linux/audit.h
index 41b0813..8350a55 100644
--- a/include/linux/audit.h
+++ b/include/linux/audit.h
@@ -158,6 +158,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 4d2023a..3966a49 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,48 +650,393 @@ 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,
-				  struct list_head *list)
+/* 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;
 
-	/* Do not use the _rcu iterator here, since this is the only
-	 * addition routine. */
+	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;
+
+	/* 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);
+
+	/* 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);
+
+	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 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.  Protected by
- * audit_netlink_mutex. */
+/* 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);
-			call_rcu(&e->rcu, audit_free_rule_rcu);
-			return 0;
+		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);
+		call_rcu(&e->rcu, audit_free_rule_rcu);
+		mutex_unlock(&audit_filter_mutex);
+
+		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 */
 }
 
@@ -521,10 +1053,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;
@@ -539,7 +1071,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;
 }
 
@@ -555,10 +1087,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;
@@ -567,13 +1099,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;
 }
 
@@ -662,6 +1194,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) {
@@ -682,7 +1240,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,
@@ -796,32 +1386,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 ba9e9a3..0f56c69 100644
--- a/kernel/auditsc.c
+++ b/kernel/auditsc.c
@@ -160,6 +160,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. */
@@ -256,6 +277,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)
@@ -1067,37 +1091,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;
-- 
0.99.9.GIT


Index: kernel-2.6.spec
===================================================================
RCS file: /cvs/dist/rpms/kernel/FC-5/kernel-2.6.spec,v
retrieving revision 1.2032.2.4
retrieving revision 1.2032.2.5
diff -u -r1.2032.2.4 -r1.2032.2.5
--- kernel-2.6.spec	21 Mar 2006 15:17:39 -0000	1.2032.2.4
+++ kernel-2.6.spec	21 Mar 2006 16:18:22 -0000	1.2032.2.5
@@ -427,7 +427,8 @@
 Patch20101: linux-2.6-audit-slab-leak-tracking.patch
 Patch20102: 0001-support-for-context-based-audit-filtering.patch
 Patch20103: 0002-support-for-context-based-audit-filtering.patch
-Patch20104: 0003-filesystem-location-based-auditing.patch
+Patch20104: 0003-Inotify-kernel-API.patch
+Patch20105: 0004-filesystem-location-based-auditing.patch
 
 # END OF PATCH DEFINITIONS
 
@@ -1076,6 +1077,7 @@
 %patch20102 -p1
 %patch20103 -p1
 %patch20104 -p1
+%patch20105 -p1
 
 # END OF PATCH APPLICATIONS
 


--- 0003-filesystem-location-based-auditing.patch DELETED ---




More information about the fedora-cvs-commits mailing list