Updated filesystem auditing patch based on lspp.b6 branch.
The following patch updates the previous filesystem auditing patch
based on changes to the inotify kernel API.
Other changes include:
- use inotify_find_watch() instead of maintaining our own list of
audit_parent structures
- add audit_add_rm_mutex to prevent add/remove rule races
- check for list_empty before calling inotify registration or
de-registration routines
TODO
----
1. postpone some rule updates until exit filtering completes
2. add watch insertion path to audit record
3. remove inotify watches for moved parents
Please include this patch in the next lspp test kernel.
include/linux/audit.h | 1
init/Kconfig | 3
kernel/audit.c | 19 -
kernel/audit.h | 22 +
kernel/auditfilter.c | 673 +++++++++++++++++++++++++++++++++++++++++++++++---
kernel/auditsc.c | 65 ++--
6 files changed, 706 insertions(+), 77 deletions(-)
diff --git a/include/linux/audit.h b/include/linux/audit.h
index b74c148..e552ac4 100644
--- a/include/linux/audit.h
+++ b/include/linux/audit.h
@@ -159,6 +159,7 @@
#define AUDIT_INODE 102
#define AUDIT_EXIT 103
#define AUDIT_SUCCESS 104 /* exit >= 0; value ignored */
+#define AUDIT_WATCH 105
#define AUDIT_ARG0 200
#define AUDIT_ARG1 (AUDIT_ARG0+1)
diff --git a/init/Kconfig b/init/Kconfig
index 3b36a1d..c4d0fa6 100644
--- a/init/Kconfig
+++ b/init/Kconfig
@@ -182,7 +182,8 @@ config AUDITSYSCALL
help
Enable low-overhead system-call auditing infrastructure that
can be used independently or with another kernel subsystem,
- such as SELinux.
+ such as SELinux. To use audit's filesystem watch feature, please
+ ensure that INOTIFY is configured.
config IKCONFIG
bool "Kernel .config support"
diff --git a/kernel/audit.c b/kernel/audit.c
index df57b49..35b2c6f 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 handle. */
+struct inotify_handle *audit_ih;
+
/* 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. */
@@ -629,14 +628,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);
}
@@ -661,6 +657,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_ih = inotify_init(audit_handle_ievent);
+ if (IS_ERR(audit_ih))
+ audit_panic("cannot initialize inotify handle");
+#endif
+
return 0;
}
__initcall(audit_init);
diff --git a/kernel/audit.h b/kernel/audit.h
index 6f73392..1fe45e8 100644
--- a/kernel/audit.h
+++ b/kernel/audit.h
@@ -19,7 +19,6 @@
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
-#include <linux/mutex.h>
#include <linux/fs.h>
#include <linux/audit.h>
@@ -53,6 +52,18 @@ enum audit_state {
};
/* Rule lists */
+struct audit_parent;
+
+struct audit_watch {
+ atomic_t count; /* reference count */
+ char *path; /* 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 parent->watches list */
+ struct list_head rules; /* associated rules */
+};
+
struct audit_field {
u32 type;
u32 val;
@@ -70,6 +81,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 +94,15 @@ 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;
+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 63aa039..7b91a1e 100644
--- a/kernel/auditfilter.c
+++ b/kernel/auditfilter.c
@@ -22,13 +22,70 @@
#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.
+ *
+ * audit_add_rm_mutex:
+ * Prevents a removal request for a rule that is currently being
+ * added. The audit_filter_mutex must be dropped to do some parts
+ * of add/remove processing, so may not be used for this purpose.
+ * This situation could be mitigated by referencing rules by id
+ * numbers.
+ */
+
+/*
+ * Reference counting:
+ *
+ * audit_parent: lifetime is from audit_init_parent() to receipt of an IN_IGNORED
+ * event. 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.
+ */
+
+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 */
+ unsigned flags; /* status flags */
+};
+
+/*
+ * audit_parent status flags:
+ *
+ * AUDIT_PARENT_INVALID - set anytime rules/watches are auto-removed due to
+ * a filesystem event. Technically not needed for IN_DELETE_SELF or IN_UNMOUNT
+ * events, as we cannot receive them while we have nameidata (during rule add)
+ * and the audit_parent is immediately removed when processing the following
+ * IN_IGNORED event. The IN_MOVE_SELF event is different. We can receive it
+ * while holding nameidata, and inotify will not send us the IN_IGNORED so we
+ * must later remove the inotify watch on audit_parent ourselves.
+ */
+#define AUDIT_PARENT_INVALID 0x001
+
+/* 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 +98,53 @@ struct list_head audit_filter_list[AUDIT
#endif
};
+DEFINE_MUTEX(audit_filter_mutex);
+DEFINE_MUTEX(audit_add_rm_mutex);
+
+/* Inotify handle */
+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)
+{
+ 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 +161,40 @@ 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);
+ parent->flags = 0;
+
+ 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 +242,32 @@ 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;
+
+ /* ensure inotify handle was initialized */
+ if (!audit_ih)
+ return -EOPNOTSUPP;
+
+ 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 +338,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 +422,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 +517,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 +556,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 +573,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 +629,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 +652,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 +674,372 @@ 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. */
+/* Update inode info in audit rules based on filesystem event. */
+static inline void audit_update_watch(struct audit_parent *parent,
+ const char *dname, dev_t dev,
+ unsigned long ino)
+{
+ struct audit_watch *owatch, *nwatch, *nextw;
+ struct audit_krule *r, *nextr;
+ struct audit_entry *oentry, *nentry;
+ struct audit_buffer *ab;
+
+ mutex_lock(&audit_filter_mutex);
+ list_for_each_entry_safe(owatch, nextw, &parent->watches, wlist) {
+ if (audit_compare_dname_path(dname, owatch->path))
+ continue;
+
+ nwatch = audit_dupe_watch(owatch);
+ if (unlikely(IS_ERR(nwatch))) {
+ mutex_unlock(&audit_filter_mutex);
+ audit_panic("error updating watch, skipping");
+ return;
+ }
+ nwatch->dev = dev;
+ nwatch->ino = ino;
+
+ list_for_each_entry_safe(r, nextr, &owatch->rules, rlist) {
+ oentry = container_of(r, struct audit_entry, rule);
+
+ nentry = audit_dupe_rule(&oentry->rule, nwatch);
+ if (unlikely(IS_ERR(nentry))) {
+ audit_panic("error updating watch, removing");
+ list_del(&oentry->rule.rlist);
+ list_del_rcu(&oentry->list);
+ } else {
+ list_add(&nentry->rule.rlist, &nwatch->rules);
+ list_del(&oentry->rule.rlist);
+ list_replace_rcu(&oentry->list, &nentry->list);
+ }
+ call_rcu(&oentry->rcu, audit_free_rule_rcu);
+ }
+
+ ab = audit_log_start(NULL, GFP_KERNEL, AUDIT_CONFIG_CHANGE);
+ audit_log_format(ab, "audit updated rules specifying watch=");
+ audit_log_untrustedstring(ab, owatch->path);
+ audit_log_format(ab, " with dev=%u ino=%lu\n", dev, ino);
+ audit_log_end(ab);
+
+ list_del(&owatch->wlist);
+ audit_put_watch(owatch); /* matches initial get */
+ goto add_watch_to_parent; /* event applies to a single watch */
+ }
+ mutex_unlock(&audit_filter_mutex);
+ return;
+
+add_watch_to_parent:
+ list_add(&nwatch->wlist, &parent->watches);
+ mutex_unlock(&audit_filter_mutex);
+ return;
+}
+
+/* Remove all watches & rules associated with a parent that is going away. */
+static inline void audit_remove_parent_watches(struct audit_parent *parent)
+{
+ struct audit_watch *w, *nextw;
+ struct audit_krule *r, *nextr;
+ struct audit_entry *e;
+
+ mutex_lock(&audit_filter_mutex);
+ parent->flags |= AUDIT_PARENT_INVALID;
+ 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);
+}
+
+/* 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)
+{
+ struct audit_parent *p;
+
+ 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);
+ }
+}
+
+/* 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 a matching watch entry, or add this one. */
+static inline int audit_add_watch(struct audit_krule *krule,
+ struct nameidata *ndp, struct nameidata *ndw,
+ struct list_head *inotify_list)
+{
+ int ret;
+ struct inotify_watch *iwatch;
+ struct audit_parent *parent;
+ struct audit_watch *w, *watch = krule->watch;
+ int watch_found = 0;
+
+ ret = inotify_find_watch(audit_ih, ndp->dentry->d_inode, &iwatch);
+ if (ret < 0) {
+ 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);
+
+ /* update watch filter fields */
+ if (ndw) {
+ watch->dev = ndw->dentry->d_inode->i_sb->s_dev;
+ watch->ino = ndw->dentry->d_inode->i_ino;
+ }
+
+ /* add parent to inotify registration list */
+ list_add(&parent->ilist, inotify_list);
+ } else {
+ parent = container_of(iwatch, struct audit_parent, wdata);
+
+ mutex_lock(&audit_filter_mutex);
+ if (parent->flags & AUDIT_PARENT_INVALID) {
+ /* parent was moved before we took the lock */
+ mutex_unlock(&audit_filter_mutex);
+ return -ENOENT;
+ }
+
+ list_for_each_entry(w, &parent->watches, wlist) {
+ if (strcmp(watch->path, w->path))
+ continue;
+
+ watch_found = 1;
+
+ /* put krule's and initial refs to temporary watch */
+ audit_put_watch(watch);
+ audit_put_watch(watch);
+
+ audit_get_watch(w);
+ krule->watch = watch = w;
+ break;
+ }
+
+ if (!watch_found) {
+ audit_get_parent(parent);
+ watch->parent = parent;
+
+ list_add(&watch->wlist, &parent->watches);
+ }
+
+ list_add(&krule->rlist, &watch->rules);
+ mutex_unlock(&audit_filter_mutex);
+ }
+
+ 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 list_head *list)
{
struct audit_entry *e;
+ struct audit_watch *watch = entry->rule.watch;
+ struct nameidata *ndp, *ndw;
+ LIST_HEAD(inotify_list);
+ int err;
- /* Do not use the _rcu iterator here, since this is the only
- * addition routine. */
+ /* Taking audit_filter_mutex protects from stale rule data. */
+ 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;
}
+ if (watch) {
+ err = audit_add_watch(&entry->rule, ndp, ndw, &inotify_list);
+ if (err) {
+ audit_put_nd(ndp, ndw);
+ goto error;
+ }
+ }
+
+ mutex_lock(&audit_filter_mutex);
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);
- return 0;
+ if (watch && !list_empty(&inotify_list)) {
+ 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 && !list_empty(&inotify_list))
+ 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 +1056,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 +1074,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 +1090,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 +1102,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;
}
@@ -630,8 +1165,11 @@ int audit_receive_filter(int type, int p
if (IS_ERR(entry))
return PTR_ERR(entry);
+ mutex_lock(&audit_add_rm_mutex);
err = audit_add_rule(entry,
&audit_filter_list[entry->rule.listnr]);
+ mutex_unlock(&audit_add_rm_mutex);
+
if (sid) {
char *ctx = NULL;
u32 len;
@@ -662,8 +1200,10 @@ int audit_receive_filter(int type, int p
if (IS_ERR(entry))
return PTR_ERR(entry);
+ mutex_lock(&audit_add_rm_mutex);
err = audit_del_rule(entry,
&audit_filter_list[entry->rule.listnr]);
+ mutex_unlock(&audit_add_rm_mutex);
if (sid) {
char *ctx = NULL;
@@ -712,7 +1252,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,
@@ -826,32 +1398,61 @@ 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;
}
+
+/* Update watch data in audit rules based on inotify events. */
+void audit_handle_ievent(struct inotify_watch *iwatch, u32 wd, u32 mask,
+ u32 cookie, const char *dname, struct inode *inode)
+{
+ struct audit_parent *parent = container_of(iwatch, 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))
+ audit_remove_parent_watches(parent);
+ else if (mask & IN_IGNORED)
+ audit_put_parent(parent); /* match get in audit_init_parent() */
+}
diff --git a/kernel/auditsc.c b/kernel/auditsc.c
index a300736..80375fa 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)
@@ -1058,37 +1082,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;