This patch contains fixes for the audit watch implementation in the
git tree.
- deadlock fix: release mutex on error from audit_add_watch
- remove "moved" inotify watches from event handler
- use inotify refcounting for audit_parent data
- register inotify watches before adding rule instead of after;
using inotify refcounting, this ensures our audit_watch->parent
is valid
- some code consolidation based on above changes
Please take a look and make sure I haven't bungled anything.
Signed-off-by: Amy Griffis <amy.griffis(a)hp.com>
--
audit.c | 8 ++
audit.h | 1
auditfilter.c | 175 ++++++++++++++++++++++------------------------------------
3 files changed, 75 insertions(+), 109 deletions(-)
diff --git a/kernel/audit.c b/kernel/audit.c
index b834b2e..db8bce4 100644
--- a/kernel/audit.c
+++ b/kernel/audit.c
@@ -680,6 +680,12 @@ static void audit_receive(struct sock *s
mutex_unlock(&audit_cmd_mutex);
}
+#ifdef CONFIG_AUDITSYSCALL
+static const struct inotify_operations audit_inotify_ops = {
+ .handle_event = audit_handle_ievent,
+ .destroy_watch = audit_free_parent,
+};
+#endif
/* Initialize audit support at boot time. */
static int __init audit_init(void)
@@ -706,7 +712,7 @@ static int __init audit_init(void)
audit_log(NULL, GFP_KERNEL, AUDIT_KERNEL, "initialized");
#ifdef CONFIG_AUDITSYSCALL
- audit_ih = inotify_init(audit_handle_ievent);
+ audit_ih = inotify_init(&audit_inotify_ops);
if (IS_ERR(audit_ih))
audit_panic("cannot initialize inotify handle");
diff --git a/kernel/audit.h b/kernel/audit.h
index 8506287..125aebe 100644
--- a/kernel/audit.h
+++ b/kernel/audit.h
@@ -122,6 +122,7 @@ struct audit_netlink_list {
int audit_send_list(void *);
struct inotify_watch;
+extern void audit_free_parent(struct inotify_watch *);
extern void audit_handle_ievent(struct inotify_watch *, u32, u32, u32,
const char *, struct inode *);
extern int selinux_audit_rule_update(void);
diff --git a/kernel/auditfilter.c b/kernel/auditfilter.c
index e116322..6caa46e 100644
--- a/kernel/auditfilter.c
+++ b/kernel/auditfilter.c
@@ -58,7 +58,6 @@ #include "audit.h"
*/
struct audit_parent {
- atomic_t count; /* reference count */
struct list_head ilist; /* entry in inotify registration list */
struct list_head watches; /* associated watches */
struct inotify_watch wdata; /* inotify watch data */
@@ -98,19 +97,14 @@ extern struct inotify_handle *audit_ih;
/* 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)
+void audit_free_parent(struct inotify_watch *i_watch)
{
- atomic_inc(&parent->count);
-}
+ struct audit_parent *parent;
-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);
- }
+ parent = container_of(i_watch, struct audit_parent, wdata);
+ WARN_ON(!list_empty(&parent->watches));
+ kfree(parent);
}
static inline void audit_get_watch(struct audit_watch *watch)
@@ -124,7 +118,7 @@ static inline void audit_put_watch(struc
WARN_ON(!list_empty(&watch->rules));
/* watches that were never added don't have a parent */
if (watch->parent)
- audit_put_parent(watch->parent);
+ put_inotify_watch(&watch->parent->wdata);
kfree(watch->path);
kfree(watch);
}
@@ -154,18 +148,28 @@ static inline void audit_free_rule_rcu(s
}
/* Initialize a parent watch entry. */
-static inline struct audit_parent *audit_init_parent(void)
+static inline struct audit_parent *audit_init_parent(struct nameidata *ndp)
{
struct audit_parent *parent;
+ s32 wd;
parent = kzalloc(sizeof(*parent), GFP_KERNEL);
if (unlikely(!parent))
return ERR_PTR(-ENOMEM);
INIT_LIST_HEAD(&parent->watches);
- atomic_set(&parent->count, 1);
parent->flags = 0;
+ inotify_init_watch(&parent->wdata);
+ /* grab a ref so inotify watch hangs around until we take audit_filter_mutex */
+ get_inotify_watch(&parent->wdata);
+ wd = inotify_add_watch(audit_ih, &parent->wdata, ndp->dentry->d_inode,
+ AUDIT_IN_WATCH);
+ if (wd < 0) {
+ audit_free_parent(&parent->wdata);
+ return ERR_PTR(wd);
+ }
+
return parent;
}
@@ -632,7 +636,7 @@ static inline struct audit_watch *audit_
new->dev = old->dev;
new->ino = old->ino;
- audit_get_parent(old->parent);
+ get_inotify_watch(&old->parent->wdata);
new->parent = old->parent;
out:
@@ -811,30 +815,6 @@ static inline void audit_remove_parent_w
mutex_unlock(&audit_filter_mutex);
}
-/* 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) {
- wd = inotify_add_watch(audit_ih, &p->wdata, nd->dentry->d_inode,
- AUDIT_IN_WATCH);
- if (wd < 0) {
- audit_remove_parent_watches(p);
- /* the put matching the get in audit_init_parent() */
- audit_put_parent(p);
- /* save the first error for return value */
- if (!ret)
- ret = wd;
- }
- }
-
- 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)
@@ -844,7 +824,7 @@ static void audit_inotify_unregister(str
list_for_each_entry(p, in_list, ilist) {
inotify_rm_watch(audit_ih, &p->wdata);
/* the put matching the get in audit_remove_watch() */
- audit_put_parent(p);
+ put_inotify_watch(&p->wdata);
}
}
@@ -897,46 +877,14 @@ static inline void audit_put_nd(struct n
}
}
-/* Add a parent inotify_watch for the given rule. */
-static int audit_add_parent(struct audit_krule *krule,
- struct list_head *inotify_list)
-{
- struct audit_parent *parent;
- struct audit_watch *watch = krule->watch;
-
- parent = audit_init_parent();
- if (IS_ERR(parent))
- return PTR_ERR(parent);
-
- audit_get_parent(parent);
- watch->parent = parent;
-
- /* krule, watch and parent have not been added to any global
- * lists, so we don't need to take audit_filter_mutex. */
- list_add(&watch->wlist, &parent->watches);
- list_add(&krule->rlist, &watch->rules);
-
- /* add parent to inotify registration list */
- list_add(&parent->ilist, inotify_list);
-
- return 0;
-}
-
/* Associate the given rule with an existing parent inotify_watch.
* Caller must hold audit_filter_mutex. */
-static int audit_add_to_parent(struct audit_krule *krule,
- struct inotify_watch *iwatch)
+static void audit_add_to_parent(struct audit_krule *krule,
+ struct audit_parent *parent)
{
- struct audit_parent *parent;
struct audit_watch *w, *watch = krule->watch;
int watch_found = 0;
- parent = container_of(iwatch, struct audit_parent, wdata);
-
- /* parent was moved before we took audit_filter_mutex */
- if (parent->flags & AUDIT_PARENT_INVALID)
- return -ENOENT;
-
list_for_each_entry(w, &parent->watches, wlist) {
if (strcmp(watch->path, w->path))
continue;
@@ -953,26 +901,23 @@ static int audit_add_to_parent(struct au
}
if (!watch_found) {
- audit_get_parent(parent);
+ get_inotify_watch(&parent->wdata);
watch->parent = parent;
list_add(&watch->wlist, &parent->watches);
}
-
list_add(&krule->rlist, &watch->rules);
-
- return 0;
}
/* Find a matching watch entry, or add this one.
* Caller must hold audit_filter_mutex. */
static int audit_add_watch(struct audit_krule *krule, struct nameidata *ndp,
- struct nameidata *ndw,
- struct list_head *inotify_list)
+ struct nameidata *ndw)
{
struct audit_watch *watch = krule->watch;
- struct inotify_watch *iwatch;
- int ret;
+ struct inotify_watch *i_watch;
+ struct audit_parent *parent;
+ int ret = 0;
/* update watch filter fields */
if (ndw) {
@@ -981,18 +926,32 @@ static int audit_add_watch(struct audit_
}
/* The audit_filter_mutex must not be held during inotify calls because
- * we hold it during inotify event callback processing.
- * We can trust iwatch to stick around because we hold nameidata (ndp). */
+ * we hold it during inotify event callback processing. If an existing
+ * inotify watch is found, inotify_find_watch() grabs a reference before
+ * returning.
+ */
mutex_unlock(&audit_filter_mutex);
- if (inotify_find_watch(audit_ih, ndp->dentry->d_inode, &iwatch) < 0) {
- ret = audit_add_parent(krule, inotify_list);
- mutex_lock(&audit_filter_mutex);
- } else {
- mutex_lock(&audit_filter_mutex);
- ret = audit_add_to_parent(krule, iwatch);
- }
+ if (inotify_find_watch(audit_ih, ndp->dentry->d_inode, &i_watch) < 0) {
+ parent = audit_init_parent(ndp);
+ if (IS_ERR(parent)) {
+ /* caller expects mutex locked */
+ mutex_lock(&audit_filter_mutex);
+ return PTR_ERR(parent);
+ }
+ } else
+ parent = container_of(i_watch, struct audit_parent, wdata);
+ mutex_lock(&audit_filter_mutex);
+
+ /* parent was moved before we took audit_filter_mutex */
+ if (parent->flags & AUDIT_PARENT_INVALID)
+ ret = -ENOENT;
+ else
+ audit_add_to_parent(krule, parent);
+
+ /* put ref grabbed by inotify_find_watch or before inotify_add_watch */
+ put_inotify_watch(&parent->wdata);
return ret;
}
@@ -1004,7 +963,6 @@ static inline int audit_add_rule(struct
struct audit_field *inode_f = entry->rule.inode_f;
struct audit_watch *watch = entry->rule.watch;
struct nameidata *ndp, *ndw;
- LIST_HEAD(inotify_list);
int h, err, putnd_needed = 0;
/* Taking audit_filter_mutex protects from stale rule data. */
@@ -1029,9 +987,11 @@ static inline int audit_add_rule(struct
mutex_lock(&audit_filter_mutex);
if (watch) {
/* audit_filter_mutex is dropped and re-taken during this call */
- err = audit_add_watch(&entry->rule, ndp, ndw, &inotify_list);
- if (err)
+ err = audit_add_watch(&entry->rule, ndp, ndw);
+ if (err) {
+ mutex_unlock(&audit_filter_mutex);
goto error;
+ }
h = audit_hash_ino((u32)watch->ino);
list = &audit_inode_hash[h];
} else if (inode_f) {
@@ -1046,11 +1006,6 @@ static inline int audit_add_rule(struct
}
mutex_unlock(&audit_filter_mutex);
- if (!list_empty(&inotify_list)) {
- err = audit_inotify_register(ndp, &inotify_list);
- if (err)
- goto error;
- }
if (putnd_needed)
audit_put_nd(ndp, ndw);
@@ -1083,7 +1038,7 @@ static inline void audit_remove_watch(st
* 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);
+ get_inotify_watch(&parent->wdata);
}
}
}
@@ -1561,21 +1516,25 @@ int selinux_audit_rule_update(void)
}
/* Update watch data in audit rules based on inotify events. */
-void audit_handle_ievent(struct inotify_watch *iwatch, u32 wd, u32 mask,
+void audit_handle_ievent(struct inotify_watch *i_watch, u32 wd, u32 mask,
u32 cookie, const char *dname, struct inode *inode)
{
- struct audit_parent *parent = container_of(iwatch, struct audit_parent, wdata);
+ struct audit_parent *parent;
+
+ parent = container_of(i_watch, struct audit_parent, wdata);
if (mask & (IN_CREATE|IN_MOVED_TO) && inode)
audit_update_watch(parent, dname, inode->i_sb->s_dev,
inode->i_ino);
else if (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 (mask & (AUDIT_IN_SELF))
+ /* inotify automatically removes the watch and sends IN_IGNORED */
+ else if (mask & (IN_DELETE_SELF|IN_UNMOUNT))
+ audit_remove_parent_watches(parent);
+ /* inotify does not remove the watch, so remove it manually */
+ else if(mask & IN_MOVE_SELF) {
audit_remove_parent_watches(parent);
- else if (mask & IN_IGNORED)
- audit_put_parent(parent); /* match get in audit_init_parent() */
+ inotify_remove_watch_locked(audit_ih, i_watch);
+ } else if (mask & IN_IGNORED)
+ put_inotify_watch(i_watch);
}