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
- Previous message (by thread): rpms/policycoreutils/devel policycoreutils-rhat.patch, 1.176, 1.177 policycoreutils.spec, 1.250, 1.251
- Next message (by thread): rpms/k3b/FC-4 .cvsignore, 1.8, 1.9 k3b.spec, 1.15, 1.16 sources, 1.8, 1.9
- Messages sorted by:
[ date ]
[ thread ]
[ subject ]
[ author ]
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 ---
- Previous message (by thread): rpms/policycoreutils/devel policycoreutils-rhat.patch, 1.176, 1.177 policycoreutils.spec, 1.250, 1.251
- Next message (by thread): rpms/k3b/FC-4 .cvsignore, 1.8, 1.9 k3b.spec, 1.15, 1.16 sources, 1.8, 1.9
- Messages sorted by:
[ date ]
[ thread ]
[ subject ]
[ author ]
More information about the fedora-cvs-commits
mailing list