Here is another iteration of the filesystem location based auditing patch,
previous version was posted here:
https://www.redhat.com/archives/linux-audit/2006-February/msg00152.html
The patch is based off of the current audit git tree plus the latest versions
of the context based audit filtering work, posted here:
https://www.redhat.com/archives/linux-audit/2006-February/msg00160.html
https://www.redhat.com/archives/linux-audit/2006-March/msg00071.html
I believe I've addressed all of the feedback I received last round. Here is a
laundry list:
- protect against uninitialized audit_idev (similar fix for audit_sock in
separate patch)
- add INOTIFY dependency to AUDITSYSCALL in Kconfig
- use atomic_set 1 for struct initialization
- move path_lookup and path_release out from under spinlock
- don't call path_release on path_lookup failure
- post-process inotify registration/de-registration instead of using
kthread_run
- remove no longer needed AUDIT_PARENT_DEL and AUDIT_ENTRY_ADD flags
- use a combination of superblock dev and inode # to distinguish watches and
parents; this results in replacing the watch in addition to the watch's rules
in audit_update_watch()
- fix memory corruption due to list_add while traversing same list
- incorporate selinux field copy into audit_dupe_rule()
- modify selinux_audit_rule_update() to use audit_dupe_rule() and do
necessary watch.rules list manipulation
- modify selinux_audit_rule_update() to use filterlist locks instead of
audit_netlink_mutex
- remove the replacement pointer from audit_entry; it doesn't seem to be needed
since we always search for list elements under lock
I'm a bit uncertain about the per-filterlist spinlocks. It may suffice to just use
the audit_netlink_mutex as Darrel has been doing. Any advice?
If ok, please include this patch in the next version of the lspp test kernel,
along with the latest inotify kernel API patch. Please don't include either of these
patches in -mm yet.
Thanks,
Amy
---
include/linux/audit.h | 1
init/Kconfig | 2
kernel/audit.c | 24 +
kernel/audit.h | 37 +-
kernel/auditfilter.c | 886 ++++++++++++++++++++++++++++++++++++++++++--------
kernel/auditsc.c | 76 ++--
6 files changed, 848 insertions(+), 178 deletions(-)
diff --git a/include/linux/audit.h b/include/linux/audit.h
index 76feae3..4fb7fbd 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 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 6a44e0a..b0d7fb7 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,12 @@ static atomic_t audit_lost = ATOMIC_I
/* The netlink socket. */
static struct sock *audit_sock;
+/* Inotify device. */
+struct inotify_device *audit_idev;
+
+/* Audit filter lists, initialized in audit_init() */
+extern struct audit_flist audit_filter_list[];
+
/* 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). */
@@ -552,6 +561,14 @@ static void audit_receive(struct sock *s
/* Initialize audit support at boot time. */
static int __init audit_init(void)
{
+ int i;
+
+ /* must be initialized before any audit_log calls */
+ for (i = 0; i < AUDIT_NR_FILTERS; i++) {
+ INIT_LIST_HEAD(&audit_filter_list[i].head);
+ spin_lock_init(&audit_filter_list[i].lock);
+ }
+
printk(KERN_INFO "audit: initializing netlink socket (%s)\n",
audit_default ? "enabled" : "disabled");
audit_sock = netlink_kernel_create(NETLINK_AUDIT, 0, audit_receive,
@@ -564,6 +581,13 @@ static int __init audit_init(void)
audit_initialized = 1;
audit_enabled = audit_default;
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 051ac2a..795fb72 100644
--- a/kernel/audit.h
+++ b/kernel/audit.h
@@ -23,6 +23,8 @@
#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 +55,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,18 +93,28 @@ 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 {
struct list_head list;
struct rcu_head rcu;
- struct audit_krule rule;
+ unsigned int flags; /* flag list manips in progress */
+ struct audit_krule rule; /* audit rule data */
};
+struct audit_flist {
+ struct list_head head;
+ spinlock_t lock; /* syncs filter data manipulation */
+};
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_handle_ievent(struct inotify_event *event,
+ const char *dname, 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 b115f51..9ae8be9 100644
--- a/kernel/auditfilter.c
+++ b/kernel/auditfilter.c
@@ -22,28 +22,66 @@
#include <linux/kernel.h>
#include <linux/audit.h>
#include <linux/kthread.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. */
-struct list_head audit_filter_list[AUDIT_NR_FILTERS] = {
- LIST_HEAD_INIT(audit_filter_list[0]),
- LIST_HEAD_INIT(audit_filter_list[1]),
- LIST_HEAD_INIT(audit_filter_list[2]),
- LIST_HEAD_INIT(audit_filter_list[3]),
- LIST_HEAD_INIT(audit_filter_list[4]),
- LIST_HEAD_INIT(audit_filter_list[5]),
-#if AUDIT_NR_FILTERS != 6
-#error Fix audit_filter_list initialiser
-#endif
-};
+/* Audit filter lists */
+struct audit_flist audit_filter_list[AUDIT_NR_FILTERS];
+
+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
+
+/* Flags for stale filterlist data */
+#define AUDIT_ENTRY_DEL 0x01 /* Rule entry deletion in progress. */
+
+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)) {
+ BUG_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)) {
+ BUG_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 +98,65 @@ 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_ATOMIC);
+ 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,
+ gfp_t gfp_mask)
+{
+ struct audit_watch *watch;
+
+ watch = kzalloc(sizeof(*watch), gfp_mask);
+ 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,
+ gfp_t gfp_mask)
+{
+ struct audit_entry *entry;
+ struct audit_field *fields;
+
+ entry = kzalloc(sizeof(*entry), gfp_mask);
+ if (unlikely(!entry))
+ return NULL;
+
+ fields = kzalloc(sizeof(*fields) * field_count, gfp_mask);
+ if (unlikely(!fields)) {
+ kfree(entry);
+ return NULL;
+ }
+ entry->rule.fields = fields;
+
+ return entry;
+}
+
/* Unpack a filter field's string representation from user-space
* buffer. */
static char *audit_unpack_string(void **bufp, size_t *remain, size_t len)
@@ -87,12 +184,32 @@ static char *audit_unpack_string(void **
return str;
}
+/* Translate a watch string to kernel respresentation. */
+static int audit_to_watch(char *path, struct audit_krule *krule, int fidx)
+{
+ struct audit_field *f = &krule->fields[fidx];
+ struct audit_watch *watch;
+
+ if (path[0] != '/' || path[f->val-1] == '/' ||
+ krule->listnr != AUDIT_FILTER_EXIT ||
+ f->op & ~AUDIT_EQUAL)
+ return -EINVAL;
+
+ watch = audit_init_watch(path, GFP_KERNEL);
+ 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)
{
unsigned listnr;
struct audit_entry *entry;
- struct audit_field *fields;
int i, err;
err = -EINVAL;
@@ -116,23 +233,14 @@ static inline struct audit_entry *audit_
goto exit_err;
err = -ENOMEM;
- entry = kmalloc(sizeof(*entry), GFP_KERNEL);
- if (unlikely(!entry))
+ entry = audit_init_entry(rule->field_count, GFP_KERNEL);
+ if (!entry)
goto exit_err;
- fields = kmalloc(sizeof(*fields) * rule->field_count, GFP_KERNEL);
- if (unlikely(!fields)) {
- kfree(entry);
- goto exit_err;
- }
-
- memset(&entry->rule, 0, sizeof(struct audit_krule));
- memset(fields, 0, sizeof(struct audit_field));
entry->rule.flags = rule->flags & AUDIT_FILTER_PREPEND;
entry->rule.listnr = listnr;
entry->rule.action = rule->action;
entry->rule.field_count = rule->field_count;
- entry->rule.fields = fields;
for (i = 0; i < AUDIT_BITMASK_SIZE; i++)
entry->rule.mask[i] = rule->mask[i];
@@ -167,7 +275,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;
}
@@ -249,6 +358,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(str, &entry->rule, i);
+ if (err) {
+ kfree(str);
+ goto exit_free;
+ }
+ break;
}
}
@@ -278,7 +399,8 @@ static struct audit_rule *audit_krule_to
struct audit_rule *rule;
int i;
- rule = kmalloc(sizeof(*rule), GFP_KERNEL);
+ /* use GFP_ATOMIC because we're under rcu_read_lock() */
+ rule = kmalloc(sizeof(*rule), GFP_ATOMIC);
if (unlikely(!rule))
return ERR_PTR(-ENOMEM);
memset(rule, 0, sizeof(*rule));
@@ -309,7 +431,8 @@ static struct audit_rule_data *audit_kru
void *bufp;
int i;
- data = kmalloc(sizeof(*data) + krule->buflen, GFP_KERNEL);
+ /* use GFP_ATOMIC because we're under rcu_read_lock() */
+ data = kmalloc(sizeof(*data) + krule->buflen, GFP_ATOMIC);
if (unlikely(!data))
return ERR_PTR(-ENOMEM);
memset(data, 0, sizeof(*data));
@@ -332,6 +455,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;
}
@@ -341,6 +468,12 @@ static struct audit_rule_data *audit_kru
return data;
}
+/* Compare two watches. Considered success if rules don't match. */
+static inline int audit_compare_watch(struct audit_watch *a, struct audit_watch *b)
+{
+ return strcmp(a->path, b->path);
+}
+
/* Compare two rules in kernel format. Considered success if rules
* don't match. */
static int audit_compare_rule(struct audit_krule *a, struct audit_krule *b)
@@ -367,6 +500,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 (audit_compare_watch(a->watch, b->watch))
+ return 1;
+ break;
default:
if (a->fields[i].val != b->fields[i].val)
return 1;
@@ -380,22 +517,416 @@ 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_ATOMIC);
+ if (unlikely(!path))
+ return ERR_PTR(-ENOMEM);
+
+ new = audit_init_watch(path, GFP_ATOMIC);
+ 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,
+ struct audit_field *sf)
+{
+ int ret = 0;
+ char *se_str;
+
+ /* our own copy of se_str */
+ se_str = kstrdup(sf->se_str, GFP_ATOMIC);
+ if (unlikely(IS_ERR(se_str)))
+ return -ENOMEM;
+ df->se_str = se_str;
+
+ /* our own (refreshed) copy of se_rule */
+ ret = selinux_audit_rule_init(df->type, df->op, df->se_str,
+ &df->se_rule);
+ /* Keep currently invalid fields around in case they
+ * become valid after a policy reload. */
+ if (ret == -EINVAL) {
+ printk(KERN_WARNING "audit rule for selinux \'%s\' is invalid\n",
+ df->se_str);
+ ret = 0;
+ }
+
+ return ret;
+}
+
+/* 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. The rlist
+ * element is undefined; list manipulations should happen elsewhere. */
+static struct audit_entry *audit_dupe_rule(struct audit_krule *old,
+ struct audit_watch *watch)
+{
+ u32 fcount = old->field_count;
+ struct audit_entry *entry;
+ struct audit_krule *new;
+ int i, err = 0;
+
+ entry = audit_init_entry(fcount, GFP_ATOMIC);
+ if (unlikely(!entry))
+ return ERR_PTR(-ENOMEM);
+
+ new = &entry->rule;
+ new->vers_ops = old->vers_ops;
+ new->flags = old->flags;
+ new->listnr = old->listnr;
+ new->action = old->action;
+ 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);
+
+ /* deep copy this information, updating the se_rule fields, because
+ the originals will all be freed when the old rule is freed. */
+ for (i = 0; i < fcount; i++) {
+ switch (new->fields[i].type) {
+ case AUDIT_SE_USER:
+ case AUDIT_SE_ROLE:
+ case AUDIT_SE_TYPE:
+ case AUDIT_SE_SEN:
+ case AUDIT_SE_CLR:
+ err = audit_dupe_selinux_field(&new->fields[i],
+ &old->fields[i]);
+ }
+ if (err) {
+ audit_free_rule(entry);
+ return ERR_PTR(err);
+ }
+ }
+
+ if (watch) {
+ audit_get_watch(watch);
+ new->watch = watch;
+ }
+
+ return entry;
+}
+
+/* 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_flist *flist = &audit_filter_list[AUDIT_FILTER_EXIT];
+ struct audit_watch *owatch, *nwatch, *nextw;
+ struct audit_krule *r, *nextr;
+ struct audit_entry *oentry, *nentry;
+ struct audit_buffer *ab;
+
+ spin_lock(&flist->lock);
+ 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))) {
+ audit_panic("error updating watch");
+ ab = audit_log_start(NULL, GFP_ATOMIC,
+ AUDIT_CONFIG_CHANGE);
+ audit_log_format(ab,
+ "audit skipped update for rules specifying watch=");
+ audit_log_untrustedstring(ab, owatch->path);
+ audit_log_format(ab, " with ino=%lu\n", ino);
+ audit_log_end(ab);
+ 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);
+ if (oentry->flags & AUDIT_ENTRY_DEL)
+ continue;
+
+ nentry = audit_dupe_rule(&oentry->rule, nwatch);
+ if (unlikely(IS_ERR(nentry))) {
+ audit_panic("error updating watch");
+ audit_log(NULL, GFP_ATOMIC, AUDIT_CONFIG_CHANGE,
+ "audit removed rule to be updated\n");
+ 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);
+ }
+ oentry->flags |= AUDIT_ENTRY_DEL;
+ call_rcu(&oentry->rcu, audit_free_rule_rcu);
+ }
+
+ ab = audit_log_start(NULL, GFP_ATOMIC, AUDIT_CONFIG_CHANGE);
+ audit_log_format(ab, "audit updated rules specifying watch=");
+ audit_log_untrustedstring(ab, owatch->path);
+ audit_log_format(ab, " with ino=%lu\n", ino);
+ audit_log_end(ab);
+
+ list_del(&owatch->wlist);
+ audit_put_watch(owatch);
+ goto add_watch_to_parent; /* event applies to a single watch */
+ }
+ spin_unlock(&flist->lock);
+ return;
+
+add_watch_to_parent:
+ list_add(&nwatch->wlist, &parent->watches);
+ spin_unlock(&flist->lock);
+ 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;
+ struct audit_flist *flist = &audit_filter_list[AUDIT_FILTER_EXIT];
+
+ spin_lock(&flist->lock);
+ 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);
+ if (e->flags & AUDIT_ENTRY_DEL)
+ continue;
+
+ list_del(&r->rlist);
+ list_del_rcu(&e->list);
+ e->flags |= AUDIT_ENTRY_DEL;
+ call_rcu(&e->rcu, audit_free_rule_rcu);
+ audit_log(NULL, GFP_ATOMIC, AUDIT_CONFIG_CHANGE,
+ "audit implicitly removed rule from list=%d\n",
+ AUDIT_FILTER_EXIT);
+ }
+ list_del(&w->wlist);
+ audit_put_watch(w);
+ }
+ spin_unlock(&flist->lock);
+}
+
+/* Actually remove the parent; inotify has acknowleged the removal. */
+static inline void audit_remove_parent(struct audit_parent *parent)
+{
+ BUG_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) {
+ 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);
+ audit_put_parent(p);
+ ret = wd;
+ } else {
+ /* These values only read during operations which are
+ * currently prevented by audit_netlink_sem. */
+ struct inode *inode = nd->dentry->d_inode;
+ p->wd = wd;
+ p->dev = inode->i_sb->s_dev;
+ p->ino = inode->i_ino;
+ }
+
+ 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);
+ 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 exit filterlist lock. */
+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 exit filterlist lock. */
+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 (audit_compare_watch(watch, w))
+ continue;
+
+ audit_put_watch(watch); /* krule's ref */
+ audit_put_watch(watch); /* destroy */
+
+ 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. Protected by
* audit_netlink_mutex. */
static inline int audit_add_rule(struct audit_entry *entry,
- struct list_head *list)
+ struct audit_flist *flist)
{
struct audit_entry *e;
+ struct audit_watch *watch = entry->rule.watch;
+ struct nameidata *ndp, *ndw;
+ LIST_HEAD(inotify_list);
+ int err;
#ifdef CONFIG_AUDITSYSCALL
int dont_count = 0;
#endif
- /* Do not use the _rcu iterator here, since this is the only
- * addition routine. */
- list_for_each_entry(e, list, list) {
- if (!audit_compare_rule(&entry->rule, &e->rule))
- return -EEXIST;
+ /* The *_rcu iterator is needed to protect from automatic filterlist
+ * updates or removals. */
+ rcu_read_lock();
+ list_for_each_entry_rcu(e, &flist->head, list) {
+ if (e->flags & AUDIT_ENTRY_DEL)
+ continue;
+ if (!audit_compare_rule(&entry->rule, &e->rule)) {
+ err = -EEXIST;
+ rcu_read_unlock();
+ goto error;
+ }
}
+ rcu_read_unlock();
/* If either of these, don't count towards total */
#ifdef CONFIG_AUDITSYSCALL
@@ -403,43 +934,111 @@ static inline int audit_add_rule(struct
entry->rule.listnr == AUDIT_FILTER_TYPE)
dont_count = 1;
#endif
+ /* Get watch nameidata before taking spinlock */
+ if (watch) {
+ err = audit_get_nd(watch->path, &ndp, &ndw);
+ if (err)
+ goto error;
+ }
+
+ spin_lock(&flist->lock);
+ if (watch) {
+ err = audit_add_watch(&entry->rule, ndp, ndw, &inotify_list);
+ if (err) {
+ audit_put_nd(ndp, ndw);
+ goto error;
+ }
+ }
if (entry->rule.flags & AUDIT_FILTER_PREPEND) {
- list_add_rcu(&entry->list, list);
+ list_add_rcu(&entry->list, &flist->head);
} else {
- list_add_tail_rcu(&entry->list, list);
+ list_add_tail_rcu(&entry->list, &flist->head);
}
+ spin_unlock(&flist->lock);
+
#ifdef CONFIG_AUDITSYSCALL
if (!dont_count)
audit_n_rules++;
#endif
+ 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);
+ 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 exit filterlist lock */
+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);
+
+ if (list_empty(&parent->watches)) {
+ /* put parent on the inotify un-registration list */
+ list_add(&parent->ilist, in_list);
+ audit_get_parent(parent);
+ }
+ }
}
/* Remove an existing rule from filterlist. Protected by
* audit_netlink_mutex. */
static inline int audit_del_rule(struct audit_entry *entry,
- struct list_head *list)
+ struct audit_flist *flist)
{
struct audit_entry *e;
+ LIST_HEAD(inotify_list);
- /* Do not use the _rcu iterator here, since this is the only
- * deletion routine. */
- list_for_each_entry(e, list, list) {
- if (!audit_compare_rule(&entry->rule, &e->rule)) {
- list_del_rcu(&e->list);
+ spin_lock(&flist->lock);
+ list_for_each_entry(e, &flist->head, list) {
+ if (e->flags & AUDIT_ENTRY_DEL ||
+ audit_compare_rule(&entry->rule, &e->rule))
+ continue;
+
+ if (e->rule.watch) {
+ audit_remove_watch(&e->rule, &inotify_list);
+ audit_put_watch(entry->rule.watch);
+ }
+
+ list_del_rcu(&e->list);
+ e->flags |= AUDIT_ENTRY_DEL;
#ifdef CONFIG_AUDITSYSCALL
- if (entry->rule.listnr == AUDIT_FILTER_USER ||
- entry->rule.listnr == AUDIT_FILTER_TYPE)
- audit_n_rules++;
+ if (entry->rule.listnr == AUDIT_FILTER_USER ||
+ entry->rule.listnr == AUDIT_FILTER_TYPE)
+ audit_n_rules++;
#endif
- call_rcu(&e->rcu, audit_free_rule_rcu);
+ call_rcu(&e->rcu, audit_free_rule_rcu);
#ifdef CONFIG_AUDITSYSCALL
- audit_n_rules--;
+ audit_n_rules--;
#endif
- return 0;
- }
+ spin_unlock(&flist->lock);
+ audit_n_rules--;
+
+ if (e->rule.watch)
+ audit_inotify_unregister(&inotify_list);
+
+ return 0;
}
+ spin_unlock(&flist->lock);
+ if (entry->rule.watch)
+ audit_put_watch(entry->rule.watch);
return -ENOENT; /* No matching rule */
}
@@ -458,10 +1057,12 @@ static int audit_list(void *_dest)
mutex_lock(&audit_netlink_mutex);
- /* The *_rcu iterators not needed here because we are
- always called with audit_netlink_mutex held. */
+ /* The *_rcu iterator is needed to protect from filesystem
+ * updates or removals. */
for (i=0; i<AUDIT_NR_FILTERS; i++) {
- list_for_each_entry(entry, &audit_filter_list[i], list) {
+ rcu_read_lock();
+ list_for_each_entry_rcu(entry, &audit_filter_list[i].head,
+ list) {
struct audit_rule *rule;
rule = audit_krule_to_rule(&entry->rule);
@@ -471,6 +1072,7 @@ static int audit_list(void *_dest)
rule, sizeof(*rule));
kfree(rule);
}
+ rcu_read_unlock();
}
audit_send_reply(pid, seq, AUDIT_LIST, 1, 1, NULL, 0);
@@ -492,19 +1094,21 @@ static int audit_list_rules(void *_dest)
mutex_lock(&audit_netlink_mutex);
- /* The *_rcu iterators not needed here because we are
- always called with audit_netlink_mutex held. */
+ /* The *_rcu iterator is needed to protect from filesystem
+ * updates or removals. */
for (i=0; i<AUDIT_NR_FILTERS; i++) {
- list_for_each_entry(e, &audit_filter_list[i], list) {
+ rcu_read_lock();
+ list_for_each_entry_rcu(e, &audit_filter_list[i].head, list) {
struct audit_rule_data *data;
data = audit_krule_to_data(&e->rule);
if (unlikely(!data))
break;
audit_send_reply(pid, seq, AUDIT_LIST_RULES, 0, 1,
- data, sizeof(*data));
+ data, sizeof(*data) + data->buflen);
kfree(data);
}
+ rcu_read_unlock();
}
audit_send_reply(pid, seq, AUDIT_LIST_RULES, 1, 1, NULL, 0);
@@ -597,6 +1201,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) {
@@ -617,7 +1247,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,
@@ -662,7 +1324,8 @@ int audit_filter_user(struct netlink_skb
int ret = 1;
rcu_read_lock();
- list_for_each_entry_rcu(e, &audit_filter_list[AUDIT_FILTER_USER], list) {
+ list_for_each_entry_rcu(e, &audit_filter_list[AUDIT_FILTER_USER].head,
+ list) {
if (audit_filter_user_rules(cb, &e->rule, &state)) {
if (state == AUDIT_DISABLED)
ret = 0;
@@ -680,10 +1343,10 @@ int audit_filter_type(int type)
int result = 0;
rcu_read_lock();
- if (list_empty(&audit_filter_list[AUDIT_FILTER_TYPE]))
+ if (list_empty(&audit_filter_list[AUDIT_FILTER_TYPE].head))
goto unlock_and_return;
- list_for_each_entry_rcu(e, &audit_filter_list[AUDIT_FILTER_TYPE],
+ list_for_each_entry_rcu(e, &audit_filter_list[AUDIT_FILTER_TYPE].head,
list) {
int i;
for (i = 0; i < e->rule.field_count; i++) {
@@ -723,62 +1386,6 @@ static inline int audit_rule_has_selinux
return 0;
}
-/* Make a copy of src in dest. 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 src
- rule with the dest rule in the list, then free the dest rule. */
-static inline int selinux_audit_rule_update_helper(struct audit_krule *dest,
- struct audit_krule *src)
-{
- int i, err = 0;
-
- dest->vers_ops = src->vers_ops;
- dest->flags = src->flags;
- dest->listnr = src->listnr;
- dest->action = src->action;
- for (i = 0; i < AUDIT_BITMASK_SIZE; i++)
- dest->mask[i] = src->mask[i];
- dest->buflen = src->buflen;
- dest->field_count = src->field_count;
-
- /* deep copy this information, updating the se_rule fields, because
- the originals will all be freed when the old rule is freed. */
- dest->fields = kzalloc(sizeof(struct audit_field) * dest->field_count,
- GFP_ATOMIC);
- if (!dest->fields)
- return -ENOMEM;
- memcpy(dest->fields, src->fields,
- sizeof(struct audit_field) * dest->field_count);
- for (i = 0; i < dest->field_count; i++) {
- struct audit_field *df = &dest->fields[i];
- struct audit_field *sf = &src->fields[i];
- switch (df->type) {
- case AUDIT_SE_USER:
- case AUDIT_SE_ROLE:
- case AUDIT_SE_TYPE:
- case AUDIT_SE_SEN:
- case AUDIT_SE_CLR:
- /* our own copy of se_str */
- df->se_str = kstrdup(sf->se_str, GFP_ATOMIC);
- if (!df->se_str)
- return -ENOMEM;
- /* our own (refreshed) copy of se_rule */
- err = selinux_audit_rule_init(df->type, df->op,
- df->se_str, &df->se_rule);
- /* Keep currently invalid fields around in case they
- become valid after a policy reload. */
- if (err == -EINVAL) {
- printk(KERN_WARNING "selinux audit rule for item %s is invalid\n",
df->se_str);
- err = 0;
- }
- if (err)
- return err;
- }
- }
-
- return 0;
-}
-
/* This function will re-initialize the se_rule field of all applicable rules.
It will traverse the filter lists serarching for rules that contain selinux
specific filter fields. When such a rule is found, it is copied, the
@@ -787,44 +1394,41 @@ static inline int selinux_audit_rule_upd
static int selinux_audit_rule_update(void)
{
struct audit_entry *entry, *nentry;
- int i, err = 0, tmperr;
-
- /* audit_netlink_mutex synchronizes the writers */
- mutex_lock(&audit_netlink_mutex);
+ struct audit_watch *watch;
+ int i, err = 0;
for (i = 0; i < AUDIT_NR_FILTERS; i++) {
- list_for_each_entry(entry, &audit_filter_list[i], list) {
+ /* filterlist lock synchronizes the writers */
+ spin_lock(&audit_filter_list[i].lock);
+ list_for_each_entry(entry, &audit_filter_list[i].head, list) {
if (!audit_rule_has_selinux(&entry->rule))
continue;
- nentry = kmalloc(sizeof(*entry), GFP_ATOMIC);
- if (!nentry) {
+ 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 */
+ * return value */
if (!err)
- err = -ENOMEM;
+ err = PTR_ERR(nentry);
audit_panic("error updating selinux filters");
- continue;
+ 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);
}
-
- tmperr = selinux_audit_rule_update_helper(&nentry->rule,
- &entry->rule);
- if (tmperr) {
- /* save the first error encountered for the
- return value */
- if (!err)
- err = tmperr;
- audit_free_rule(nentry);
- audit_panic("error updating selinux filters");
- continue;
- }
- list_replace_rcu(&entry->list, &nentry->list);
+ entry->flags |= AUDIT_ENTRY_DEL;
call_rcu(&entry->rcu, audit_free_rule_rcu);
}
+ spin_unlock(&audit_filter_list[i].lock);
}
- mutex_unlock(&audit_netlink_mutex);
-
return err;
}
diff --git a/kernel/auditsc.c b/kernel/auditsc.c
index 05a2dc1..921e79b 100644
--- a/kernel/auditsc.c
+++ b/kernel/auditsc.c
@@ -62,7 +62,7 @@
#include "audit.h"
-extern struct list_head audit_filter_list[];
+extern struct audit_flist audit_filter_list[];
/* No syscall auditing will take place unless audit_enabled != 0. */
extern int audit_enabled;
@@ -166,6 +166,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. */
@@ -252,7 +273,7 @@ static int audit_filter_rules(struct tas
}
break;
case AUDIT_INODE:
- if (ctx) {
+ if (ctx && f->val != (unsigned int)-1) {
for (j = 0; j < ctx->name_count; j++) {
if (audit_comparator(ctx->names[j].ino, f->op, f->val) ||
audit_comparator(ctx->names[j].pino, f->op, f->val)) {
@@ -262,6 +283,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)
@@ -313,7 +337,8 @@ static enum audit_state audit_filter_tas
enum audit_state state;
rcu_read_lock();
- list_for_each_entry_rcu(e, &audit_filter_list[AUDIT_FILTER_TASK], list) {
+ list_for_each_entry_rcu(e, &audit_filter_list[AUDIT_FILTER_TASK].head,
+ list) {
if (audit_filter_rules(tsk, &e->rule, NULL, &state)) {
rcu_read_unlock();
return state;
@@ -369,7 +394,7 @@ static inline struct audit_context *audi
if (context->in_syscall && !context->auditable) {
enum audit_state state;
- state = audit_filter_syscall(tsk, context, &audit_filter_list[AUDIT_FILTER_EXIT]);
+ state = audit_filter_syscall(tsk, context,
&audit_filter_list[AUDIT_FILTER_EXIT].head);
if (state == AUDIT_RECORD_CONTEXT)
context->auditable = 1;
}
@@ -847,7 +872,7 @@ void audit_syscall_entry(struct task_str
state = context->state;
if (state == AUDIT_SETUP_CONTEXT || state == AUDIT_BUILD_CONTEXT)
- state = audit_filter_syscall(tsk, context,
&audit_filter_list[AUDIT_FILTER_ENTRY]);
+ state = audit_filter_syscall(tsk, context,
&audit_filter_list[AUDIT_FILTER_ENTRY].head);
if (likely(state == AUDIT_DISABLED))
return;
@@ -1091,37 +1116,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;