[PATCH] filesystem location based auditing

Amy Griffis amy.griffis at hp.com
Thu Mar 9 23:05:18 UTC 2006


On Fri, Feb 24, 2006 at 03:07:30PM -0600, Timothy R. Chavez wrote:
> > +/* 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_ADD  0x01	/* Rule entry addition in progress. */
> > +#define AUDIT_ENTRY_DEL  0x02	/* Rule entry deletion in progress. */
> > +#define AUDIT_PARENT_DEL 0x01	/* Parent deletion in progress. */
> 
> AUDIT_ENTRY_ADD and AUDIT_PARENT_DEL are the same?  I'm assuming since
> this is a mask, that it probably makes sense to make 0x04, no?

AUDIT_ENTRY_* and AUDIT_PARENT_* were two different masks in the
audit_entry and audit_parent structs.  I've re-written the code
though, so neither of them is needed now.

> > +/* 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);
> > +
> > +			/* make sure we have a valid copy */
> > +			while (e->replacement != NULL)
> > +				e = e->replacement;
> > +			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);
> > +}
> 
> Why not rcu locking here?  You can iterate over the list w/
> rcu_read_lock/unlock and then removals from the list via admin
> action can be defferred using list_del_rcu?  Doesn't the same apply
> for additions to the flist as well.. 

I am using rcu locking, but you need an additional lock to protect
against concurrent list writes.

> > +/* Register this parent's inotify watch. */
> > +static int audit_inotify_register(void *_data)
> > +{
> > +	void **data = _data;
> > +	struct audit_parent *parent;
> > +	char *path;
> > +	struct nameidata nd;
> > +	int err;
> > +	u32 wd;
> > +
> > +	parent = data[0];
> > +	path = data[1];
> > +	kfree(data);
> > +
> > +	err = path_lookup(path, LOOKUP_PARENT, &nd);
> > +	if (err)
> > +		goto handle_error;
> > +
> > +	wd = inotify_add_watch(audit_idev, nd.dentry->d_inode, AUDIT_IN_WATCH,
> > +			       parent);
> > +	if (wd < 0)
> > +		goto handle_error;
> > +
> > +	spin_lock(&master_parents_lock);
> > +	parent->wd = wd;
> > +	parent->ino = nd.dentry->d_inode->i_ino;
> > +	spin_unlock(&master_parents_lock);
> > +
> > +	path_release(&nd);
> > +	audit_put_parent(parent);
> > +	return 0;
> > +
> > +handle_error:
> > +	path_release(&nd);
> > +	audit_remove_parent_watches(parent);
> > +	audit_remove_parent(parent);
> > +
> > +	audit_put_parent(parent);
> > +	return 0;
> 
> Hm... on error you return 0?  That's what you return on success too.

Yeah, the function was supposed to contain the error by cleaning up
after itself.  It's a little different in the latest iteration...

> > +/* Unregister this parent's inotify watch.  Generates an IN_IGNORED event. */
> > +static int audit_inotify_unregister(void *data)
> > +{
> > +	struct audit_parent *parent = data;
> > +	s32 wd;
> > +
> > +	spin_lock(&master_parents_lock);
> > +	wd = parent->wd;
> > +	spin_unlock(&master_parents_lock);
> > +
> > +	if (inotify_ignore(audit_idev, wd))
> > +		printk(KERN_ERR
> > +		       "%s:%d: unable to remove inotify watch for inode %lu\n",
> > +		       __FILE__, __LINE__, parent->ino);
> 
> This is benign?

Not really benign, but a way to signal an un-recoverable error.  This
situation has been removed by not using kthread_run.

> > +/* Initialize a parent watch entry. */
> > +static inline struct audit_parent *audit_init_parent(char *path,
> > +						     unsigned long ino)
> > +{
> > +	struct audit_parent *parent;
> > +	void **data;
> > +	struct task_struct *task;
> > +
> > +	parent = kmalloc(sizeof(*parent), GFP_ATOMIC);
> > +	if (unlikely(!parent))
> > +		return ERR_PTR(-ENOMEM);
> > +
> > +	memset(parent, 0, sizeof(*parent));
> > +	INIT_LIST_HEAD(&parent->watches);
> > +	atomic_set(&parent->count, 0);
> > +	parent->ino = ino;
> > +	audit_get_parent(parent);
> > +
> > +	/* Spawn a thread to register the parent's inotify watch without
> > +	 * the filterlist spinlock. */
> > +	data = kmalloc(2 * sizeof(void *), GFP_ATOMIC);
> > +	if (!data) {
> > +		audit_put_parent(parent);
> > +		return ERR_PTR(-ENOMEM);
> > +	}
> > +	data[0] = parent;
> > +	data[1] = path;
> > +	audit_get_parent(parent);
> > +	task = kthread_run(audit_inotify_register, data,
> > +			   "audit_inotify_register");
> > +	if (IS_ERR(task)) {
> > +		audit_put_parent(parent);
> > +		return ERR_PTR(PTR_ERR(task));
> 
> Redundant?  return ERR_PTR(task), right?

The function returns an audit_parent struct, and this is a task
struct.  Anyway, it's been removed as a result of other changes. :-)

> > +/* Initialize a watch entry. */
> > +static inline struct audit_watch *audit_init_watch(char *path)
> > +{
> > +	struct audit_watch *watch;
> > +
> > +	watch = kmalloc(sizeof(*watch), GFP_KERNEL);
> > +	if (unlikely(!watch))
> > +		return ERR_PTR(-ENOMEM);
> > +
> > +	memset(watch, 0, sizeof(*watch));
> > +	INIT_LIST_HEAD(&watch->rules);
> > +	atomic_set(&watch->count, 0);
> > +	watch->path = path;
> > +	audit_get_watch(watch);
> > +
> 
> Why not just atomic_set(&watch->count, 1), rather than making two
> atomic calls?

Done.

> > +/* 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 */
> 
> Why not path_lookup with LOOKUP_PARENT, then d_lookup?

I was hoping to avoid doing path_lookup here.

> > +	p = p - dlen + 1;
> > +	if (p < path)
> > +		return 1;
> > +	else if (p > path) {
> > +		if (*--p != '/')
> > +			return 1;
> > +		else
> > +			p++;
> > +	}
> > +
> > +	return strncmp(p, dname, dlen);
> > +}




More information about the Linux-audit mailing list