Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 15a9155f authored by Al Viro's avatar Al Viro
Browse files

fix race in audit_get_nd()



don't rely on pathname resolution ending up twice at the same point...

Signed-off-by: default avatarAl Viro <viro@zeniv.linux.org.uk>
parent 586ce098
Loading
Loading
Loading
Loading
+32 −53
Original line number Diff line number Diff line
@@ -144,9 +144,9 @@ int audit_watch_compare(struct audit_watch *watch, unsigned long ino, dev_t dev)
}

/* Initialize a parent watch entry. */
static struct audit_parent *audit_init_parent(struct nameidata *ndp)
static struct audit_parent *audit_init_parent(struct path *path)
{
	struct inode *inode = ndp->path.dentry->d_inode;
	struct inode *inode = path->dentry->d_inode;
	struct audit_parent *parent;
	int ret;

@@ -353,53 +353,40 @@ static void audit_remove_parent_watches(struct audit_parent *parent)
}

/* Get path information necessary for adding watches. */
static int audit_get_nd(char *path, struct nameidata **ndp, struct nameidata **ndw)
static int audit_get_nd(struct audit_watch *watch, struct path *parent)
{
	struct nameidata *ndparent, *ndwatch;
	struct nameidata nd;
	struct dentry *d;
	int err;

	ndparent = kmalloc(sizeof(*ndparent), GFP_KERNEL);
	if (unlikely(!ndparent))
		return -ENOMEM;
	err = path_lookup(watch->path, LOOKUP_PARENT, &nd);
	if (err)
		return err;

	ndwatch = kmalloc(sizeof(*ndwatch), GFP_KERNEL);
	if (unlikely(!ndwatch)) {
		kfree(ndparent);
		return -ENOMEM;
	if (nd.last_type != LAST_NORM) {
		path_put(&nd.path);
		return -EINVAL;
	}

	err = path_lookup(path, LOOKUP_PARENT, ndparent);
	if (err) {
		kfree(ndparent);
		kfree(ndwatch);
		return err;
	mutex_lock_nested(&nd.path.dentry->d_inode->i_mutex, I_MUTEX_PARENT);
	d = lookup_one_len(nd.last.name, nd.path.dentry, nd.last.len);
	if (IS_ERR(d)) {
		mutex_unlock(&nd.path.dentry->d_inode->i_mutex);
		path_put(&nd.path);
		return PTR_ERR(d);
	}

	err = path_lookup(path, 0, ndwatch);
	if (err) {
		kfree(ndwatch);
		ndwatch = NULL;
	if (d->d_inode) {
		/* update watch filter fields */
		watch->dev = d->d_inode->i_sb->s_dev;
		watch->ino = d->d_inode->i_ino;
	}
	mutex_unlock(&nd.path.dentry->d_inode->i_mutex);

	*ndp = ndparent;
	*ndw = ndwatch;

	*parent = nd.path;
	dput(d);
	return 0;
}

/* Release resources used for watch path information. */
static void audit_put_nd(struct nameidata *ndp, struct nameidata *ndw)
{
	if (ndp) {
		path_put(&ndp->path);
		kfree(ndp);
	}
	if (ndw) {
		path_put(&ndw->path);
		kfree(ndw);
	}
}

/* Associate the given rule with an existing parent.
 * Caller must hold audit_filter_mutex. */
static void audit_add_to_parent(struct audit_krule *krule,
@@ -440,31 +427,24 @@ int audit_add_watch(struct audit_krule *krule, struct list_head **list)
{
	struct audit_watch *watch = krule->watch;
	struct audit_parent *parent;
	struct nameidata *ndp = NULL, *ndw = NULL;
	struct path parent_path;
	int h, ret = 0;

	mutex_unlock(&audit_filter_mutex);

	/* Avoid calling path_lookup under audit_filter_mutex. */
	ret = audit_get_nd(watch->path, &ndp, &ndw);
	if (ret) {
		/* caller expects mutex locked */
		mutex_lock(&audit_filter_mutex);
		goto error;
	}
	ret = audit_get_nd(watch, &parent_path);

	/* caller expects mutex locked */
	mutex_lock(&audit_filter_mutex);

	/* update watch filter fields */
	if (ndw) {
		watch->dev = ndw->path.dentry->d_inode->i_sb->s_dev;
		watch->ino = ndw->path.dentry->d_inode->i_ino;
	}
	if (ret)
		return ret;

	/* either find an old parent or attach a new one */
	parent = audit_find_parent(ndp->path.dentry->d_inode);
	parent = audit_find_parent(parent_path.dentry->d_inode);
	if (!parent) {
		parent = audit_init_parent(ndp);
		parent = audit_init_parent(&parent_path);
		if (IS_ERR(parent)) {
			ret = PTR_ERR(parent);
			goto error;
@@ -479,9 +459,8 @@ int audit_add_watch(struct audit_krule *krule, struct list_head **list)
	h = audit_hash_ino((u32)watch->ino);
	*list = &audit_inode_hash[h];
error:
	audit_put_nd(ndp, ndw);		/* NULL args OK */
	path_put(&parent_path);
	return ret;

}

void audit_remove_watch_rule(struct audit_krule *krule)