This patch allows audit to operate as an inotify client.
It adds a list of parents, which represent the dentry parents of the
filesystem locations to be watched. When created, a parent registers
an inotify watch on itself. If all the audit rules corresponding to a
parent are removed by the admin, the parent removes its inotify watch
before it is destroyed.
As you will see in audit's inotify event callback
audit_handle_fs_event(), I have not completed the code for updating
the audit rules in the exit filter list based on the information
received from the callback. I am still thinking through some aspects
of the locking model, so I decided to leave the code as printks to
illustrate what should happen. I've played around with some various
filesystem operations, and I believe the printks indicate that audit
is getting the information it needs from inotify in order to make the
right changes to the audit rules.
Again, comments would be appreciated.
diff --git a/kernel/audit.c b/kernel/audit.c
index bdda766..5b1539d 100644
--- a/kernel/audit.c
+++ b/kernel/audit.c
@@ -55,6 +55,9 @@
#include <net/netlink.h>
#include <linux/skbuff.h>
#include <linux/netlink.h>
+#include <linux/inotify.h>
+
+#include "audit.h"
/* No auditing will take place until audit_initialized != 0.
* (Initialization happens after skb_init is called.) */
@@ -99,6 +102,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). */
@@ -564,6 +570,11 @@ static int __init audit_init(void)
audit_initialized = 1;
audit_enabled = audit_default;
audit_log(NULL, GFP_KERNEL, AUDIT_KERNEL, "initialized");
+
+ audit_idev = inotify_init(audit_handle_fs_event);
+ if (IS_ERR(audit_idev))
+ audit_panic("cannot initialize inotify device");
+
return 0;
}
__initcall(audit_init);
diff --git a/kernel/audit.h b/kernel/audit.h
index 5033e1f..26f08d5 100644
--- a/kernel/audit.h
+++ b/kernel/audit.h
@@ -22,6 +22,8 @@
#include <linux/fs.h>
#include <linux/audit.h>
+struct inotify_event;
+
/* 0 = no checking
1 = put_count checking
2 = verbose put_count checking
@@ -52,10 +54,19 @@ enum audit_state {
};
/* Rule lists */
+struct audit_parent {
+ unsigned long ino; /* associated inode number */
+ u32 wd; /* inotify watch descriptor */
+ struct list_head mlist; /* entry in master_parents */
+ struct list_head watches; /* associated watches */
+};
+
struct audit_watch {
char *path; /* watch insertion path */
struct list_head mlist; /* entry in master_watchlist */
struct list_head rules; /* associated rules */
+ struct list_head wlist; /* entry in audit_parent.watches list*/
+ struct audit_parent *parent; /* associated parent */
};
struct audit_field {
@@ -86,7 +97,9 @@ struct audit_entry {
extern int audit_pid;
extern int audit_comparator(const u32 left, const u32 op, const u32 right);
-
+extern void audit_handle_fs_event(struct inotify_event *event,
+ const char *name, struct inode * inode,
+ void *ptr);
extern void audit_send_reply(int pid, int seq, int type,
int done, int multi,
void *payload, int size);
diff --git a/kernel/auditfilter.c b/kernel/auditfilter.c
index 6506084..f2d60a8 100644
--- a/kernel/auditfilter.c
+++ b/kernel/auditfilter.c
@@ -25,6 +25,7 @@
#include <linux/fs.h>
#include <linux/namei.h>
#include <linux/netlink.h>
+#include <linux/inotify.h>
#include "audit.h"
/* There are three lists of rules -- one to search at task creation
@@ -43,6 +44,13 @@ struct list_head audit_filter_list[AUDIT
};
static LIST_HEAD(master_watchlist);
+static LIST_HEAD(master_parents);
+
+/* Inotify device. */
+extern struct inotify_device *audit_idev;
+
+/* Inotify events we care about. */
+#define AUDIT_FSEVENTS IN_MOVE|IN_CREATE|IN_DELETE|IN_DELETE_SELF|IN_MOVE_SELF
/* Unpack a filter field's string representation from user-space
* buffer. */
@@ -75,7 +83,6 @@ static char *audit_unpack_string(void **
static int audit_to_watch(char *path, struct audit_krule *krule, int fidx)
{
struct audit_field *f = &krule->fields[fidx];
- struct nameidata nd;
struct audit_watch *watch;
if (path[0] != '/' || path[f->val-1] == '/' ||
@@ -83,17 +90,12 @@ static int audit_to_watch(char *path, st
f->op & ~AUDIT_EQUAL)
return -EINVAL;
- if (path_lookup(path, 0, &nd) == 0)
- f->val = nd.dentry->d_inode->i_ino;
- else
- f->val = (unsigned int)-1;
- path_release(&nd);
-
watch = kmalloc(sizeof(*watch), GFP_KERNEL);
if (unlikely(!watch))
return -ENOMEM;
watch->path = path;
krule->watch = watch;
+ f->val = (unsigned int)-1;
return 0;
}
@@ -368,11 +370,94 @@ static inline void audit_free_rule(struc
kfree(e);
}
+static void audit_update_field(struct audit_krule *krule, u32 type, u32 val)
+{
+ int i;
+
+ for (i = 0; i < AUDIT_MAX_FIELDS; i++)
+ if (krule->fields[i].type == type) {
+ krule->fields[i].val = val;
+ return;
+ }
+}
+
+void audit_handle_fs_event(struct inotify_event *event, const char *name,
+ struct inode *inode, void *ptr)
+{
+ struct audit_parent *parent = (struct audit_parent *)ptr;
+
+ if (event->mask & (IN_CREATE)) {
+ printk(KERN_ERR "%s:%d: check inode %lu for watch %s\n",
+ __FILE__, __LINE__, parent->ino, name);
+ printk(KERN_ERR "%s:%d: if found update rules with inode %lu\n",
+ __FILE__, __LINE__, inode->i_ino);
+ } else if (event->mask & (IN_DELETE)) {
+ printk(KERN_ERR "%s:%d: check inode %lu for watch %s\n",
+ __FILE__, __LINE__, parent->ino, name);
+ printk(KERN_ERR "%s:%d: if found update rules with inode -1\n",
+ __FILE__, __LINE__);
+ } else if (event->mask & (IN_MOVED_TO) && inode) {
+ printk(KERN_ERR "%s:%d: check inode %lu for watch %s\n",
+ __FILE__, __LINE__, parent->ino, name);
+ printk(KERN_ERR "%s:%d: if found update rules with inode %lu\n",
+ __FILE__, __LINE__, inode->i_ino);
+ } else if (event->mask & (IN_MOVED_FROM)) {
+ printk(KERN_ERR "%s:%d: check inode %lu for watch %s\n",
+ __FILE__, __LINE__, parent->ino, name);
+ printk(KERN_ERR "%s:%d: if found update rules with inode -1\n",
+ __FILE__, __LINE__);
+ } else if (event->mask & (IN_DELETE_SELF|IN_MOVE_SELF))
+ printk(KERN_ERR
+ "%s:%d: remove all rules w/watches under inode %lu\n",
+ __FILE__, __LINE__, parent->ino);
+}
+
+/* Create a parent entry for this watch, using an existing parent when
+ * possible. */
+static inline int audit_add_parent(struct audit_watch *watch,
+ struct inode *inode)
+{
+ struct audit_parent *p, *parent;
+ int ret = 0;
+
+ list_for_each_entry(p, &master_parents, mlist) {
+ if (p->ino != inode->i_ino)
+ continue;
+
+ list_add(&watch->wlist, &p->watches);
+ watch->parent = p;
+ goto out;
+ }
+
+ parent = kmalloc(sizeof(*parent), GFP_KERNEL);
+ if (unlikely(!parent)) {
+ ret = -ENOMEM;
+ goto out;
+ }
+ INIT_LIST_HEAD(&parent->watches);
+ parent->wd = inotify_add_watch(audit_idev, inode, AUDIT_FSEVENTS,
+ parent);
+ if (parent->wd < 0) {
+ kfree(parent);
+ ret = parent->wd;
+ goto out;
+ }
+ parent->ino = inode->i_ino;
+ list_add(&watch->wlist, &parent->watches);
+ list_add(&parent->mlist, &master_parents);
+ watch->parent = parent;
+
+out:
+ return ret;
+}
+
/* Attach krule's watch to master_watchlist, using existing watches
* when possible. */
-static inline void audit_add_watch(struct audit_krule *krule)
+static inline int audit_add_watch(struct audit_krule *krule)
{
struct audit_watch *w;
+ struct nameidata nd;
+ int ret = 0;
list_for_each_entry(w, &master_watchlist, mlist) {
if (audit_compare_watch(w, krule->watch))
@@ -381,11 +466,28 @@ static inline void audit_add_watch(struc
audit_free_watch(krule->watch);
krule->watch = w;
list_add(&krule->rlist, &w->rules);
- return;
+ goto inode_num;
}
+
+ ret = path_lookup(krule->watch->path, LOOKUP_PARENT, &nd);
+ if (ret)
+ goto out;
INIT_LIST_HEAD(&krule->watch->rules);
list_add(&krule->rlist, &krule->watch->rules);
list_add(&krule->watch->mlist, &master_watchlist);
+
+ ret = audit_add_parent(krule->watch, nd.dentry->d_inode);
+ if (ret)
+ goto out;
+ path_release(&nd);
+
+inode_num:
+ if (path_lookup(krule->watch->path, 0, &nd) == 0)
+ audit_update_field(krule, AUDIT_WATCH,
+ nd.dentry->d_inode->i_ino);
+out:
+ path_release(&nd);
+ return ret;
}
/* Add rule to given filterlist if not a duplicate. Protected by
@@ -394,6 +496,7 @@ static inline int audit_add_rule(struct
struct list_head *list)
{
struct audit_entry *e;
+ int err;
/* Do not use the _rcu iterator here, since this is the only
* addition routine. */
@@ -402,8 +505,11 @@ static inline int audit_add_rule(struct
return -EEXIST;
}
- if (entry->rule.watch)
- audit_add_watch(&entry->rule);
+ if (entry->rule.watch) {
+ err = audit_add_watch(&entry->rule);
+ if (err)
+ return err;
+ }
if (entry->rule.flags & AUDIT_FILTER_PREPEND) {
list_add_rcu(&entry->list, list);
} else {
@@ -413,6 +519,21 @@ static inline int audit_add_rule(struct
return 0;
}
+/* Detach parent from watch, freeing if it has no associated watches. */
+static inline void audit_detach_parent(struct audit_watch *watch)
+{
+ struct audit_parent *parent = watch->parent;
+
+ list_del(&watch->wlist);
+ watch->parent = NULL;
+
+ if (list_empty(&parent->watches)) {
+ list_del(&parent->mlist);
+ inotify_ignore(audit_idev, parent->wd);
+ kfree(parent);
+ }
+}
+
/* Detach watch from krule, freeing if it has no associated rules. */
static inline void audit_detach_watch(struct audit_krule *krule)
{
@@ -423,6 +544,7 @@ static inline void audit_detach_watch(st
if (list_empty(&watch->rules)) {
list_del(&watch->mlist);
+ audit_detach_parent(watch);
audit_free_watch(watch);
}
}