fs: add kern_path_locked_negative()
The audit code relies on the fact that kern_path_locked() returned a
path even for a negative dentry. If it doesn't find a valid dentry it
immediately calls:
audit_find_parent(d_backing_inode(parent_path.dentry));
which assumes that parent_path.dentry is still valid. But it isn't since
kern_path_locked() has been changed to path_put() also for a negative
dentry.
Fix this by adding a helper that implements the required audit semantics
and allows us to fix the immediate bleeding. We can find a unified
solution for this afterwards.
Link: https://lore.kernel.org/20250414-rennt-wimmeln-f186c3a780f1@brauner
Fixes: 1c3cb50b58
("VFS: change kern_path_locked() and user_path_locked_at() to never return negative dentry")
Reported-and-tested-by: Vlastimil Babka <vbabka@suse.cz>
Signed-off-by: Christian Brauner <brauner@kernel.org>
This commit is contained in:
parent
ddee68c499
commit
c86b300b1e
65
fs/namei.c
65
fs/namei.c
|
@ -1665,27 +1665,20 @@ static struct dentry *lookup_dcache(const struct qstr *name,
|
|||
return dentry;
|
||||
}
|
||||
|
||||
/*
|
||||
* Parent directory has inode locked exclusive. This is one
|
||||
* and only case when ->lookup() gets called on non in-lookup
|
||||
* dentries - as the matter of fact, this only gets called
|
||||
* when directory is guaranteed to have no in-lookup children
|
||||
* at all.
|
||||
* Will return -ENOENT if name isn't found and LOOKUP_CREATE wasn't passed.
|
||||
* Will return -EEXIST if name is found and LOOKUP_EXCL was passed.
|
||||
*/
|
||||
struct dentry *lookup_one_qstr_excl(const struct qstr *name,
|
||||
struct dentry *base,
|
||||
unsigned int flags)
|
||||
static struct dentry *lookup_one_qstr_excl_raw(const struct qstr *name,
|
||||
struct dentry *base,
|
||||
unsigned int flags)
|
||||
{
|
||||
struct dentry *dentry = lookup_dcache(name, base, flags);
|
||||
struct dentry *dentry;
|
||||
struct dentry *old;
|
||||
struct inode *dir = base->d_inode;
|
||||
struct inode *dir;
|
||||
|
||||
dentry = lookup_dcache(name, base, flags);
|
||||
if (dentry)
|
||||
goto found;
|
||||
return dentry;
|
||||
|
||||
/* Don't create child dentry for a dead directory. */
|
||||
dir = base->d_inode;
|
||||
if (unlikely(IS_DEADDIR(dir)))
|
||||
return ERR_PTR(-ENOENT);
|
||||
|
||||
|
@ -1698,7 +1691,24 @@ struct dentry *lookup_one_qstr_excl(const struct qstr *name,
|
|||
dput(dentry);
|
||||
dentry = old;
|
||||
}
|
||||
found:
|
||||
return dentry;
|
||||
}
|
||||
|
||||
/*
|
||||
* Parent directory has inode locked exclusive. This is one
|
||||
* and only case when ->lookup() gets called on non in-lookup
|
||||
* dentries - as the matter of fact, this only gets called
|
||||
* when directory is guaranteed to have no in-lookup children
|
||||
* at all.
|
||||
* Will return -ENOENT if name isn't found and LOOKUP_CREATE wasn't passed.
|
||||
* Will return -EEXIST if name is found and LOOKUP_EXCL was passed.
|
||||
*/
|
||||
struct dentry *lookup_one_qstr_excl(const struct qstr *name,
|
||||
struct dentry *base, unsigned int flags)
|
||||
{
|
||||
struct dentry *dentry;
|
||||
|
||||
dentry = lookup_one_qstr_excl_raw(name, base, flags);
|
||||
if (IS_ERR(dentry))
|
||||
return dentry;
|
||||
if (d_is_negative(dentry) && !(flags & LOOKUP_CREATE)) {
|
||||
|
@ -2762,6 +2772,29 @@ static struct dentry *__kern_path_locked(int dfd, struct filename *name, struct
|
|||
return d;
|
||||
}
|
||||
|
||||
struct dentry *kern_path_locked_negative(const char *name, struct path *path)
|
||||
{
|
||||
struct filename *filename __free(putname) = getname_kernel(name);
|
||||
struct dentry *d;
|
||||
struct qstr last;
|
||||
int type, error;
|
||||
|
||||
error = filename_parentat(AT_FDCWD, filename, 0, path, &last, &type);
|
||||
if (error)
|
||||
return ERR_PTR(error);
|
||||
if (unlikely(type != LAST_NORM)) {
|
||||
path_put(path);
|
||||
return ERR_PTR(-EINVAL);
|
||||
}
|
||||
inode_lock_nested(path->dentry->d_inode, I_MUTEX_PARENT);
|
||||
d = lookup_one_qstr_excl_raw(&last, path->dentry, 0);
|
||||
if (IS_ERR(d)) {
|
||||
inode_unlock(path->dentry->d_inode);
|
||||
path_put(path);
|
||||
}
|
||||
return d;
|
||||
}
|
||||
|
||||
struct dentry *kern_path_locked(const char *name, struct path *path)
|
||||
{
|
||||
struct filename *filename = getname_kernel(name);
|
||||
|
|
|
@ -62,6 +62,7 @@ extern struct dentry *kern_path_create(int, const char *, struct path *, unsigne
|
|||
extern struct dentry *user_path_create(int, const char __user *, struct path *, unsigned int);
|
||||
extern void done_path_create(struct path *, struct dentry *);
|
||||
extern struct dentry *kern_path_locked(const char *, struct path *);
|
||||
extern struct dentry *kern_path_locked_negative(const char *, struct path *);
|
||||
extern struct dentry *user_path_locked_at(int , const char __user *, struct path *);
|
||||
int vfs_path_parent_lookup(struct filename *filename, unsigned int flags,
|
||||
struct path *parent, struct qstr *last, int *type,
|
||||
|
|
|
@ -347,12 +347,17 @@ static void audit_remove_parent_watches(struct audit_parent *parent)
|
|||
/* Get path information necessary for adding watches. */
|
||||
static int audit_get_nd(struct audit_watch *watch, struct path *parent)
|
||||
{
|
||||
struct dentry *d = kern_path_locked(watch->path, parent);
|
||||
struct dentry *d;
|
||||
|
||||
d = kern_path_locked_negative(watch->path, parent);
|
||||
if (IS_ERR(d))
|
||||
return PTR_ERR(d);
|
||||
/* update watch filter fields */
|
||||
watch->dev = d->d_sb->s_dev;
|
||||
watch->ino = d_backing_inode(d)->i_ino;
|
||||
|
||||
if (d_is_positive(d)) {
|
||||
/* update watch filter fields */
|
||||
watch->dev = d->d_sb->s_dev;
|
||||
watch->ino = d_backing_inode(d)->i_ino;
|
||||
}
|
||||
|
||||
inode_unlock(d_backing_inode(parent->dentry));
|
||||
dput(d);
|
||||
|
@ -418,11 +423,10 @@ int audit_add_watch(struct audit_krule *krule, struct list_head **list)
|
|||
/* caller expects mutex locked */
|
||||
mutex_lock(&audit_filter_mutex);
|
||||
|
||||
if (ret && ret != -ENOENT) {
|
||||
if (ret) {
|
||||
audit_put_watch(watch);
|
||||
return ret;
|
||||
}
|
||||
ret = 0;
|
||||
|
||||
/* either find an old parent or attach a new one */
|
||||
parent = audit_find_parent(d_backing_inode(parent_path.dentry));
|
||||
|
|
Loading…
Reference in New Issue