fuse: fix race when disposing stale dentries

In fuse_dentry_tree_work() just before d_dispose_if_unused() the dentry
could get evicted, resulting in UAF.

Move unlocking dentry_hash[i].lock to after the dispose.  To do this,
fuse_dentry_tree_del_node() needs to be moved from fuse_dentry_prune() to
fuse_dentry_release() to prevent an ABBA deadlock.

The lock ordering becomes:

 -> dentry_bucket.lock
    -> dentry.d_lock

Reported-by: Al Viro <viro@zeniv.linux.org.uk>
Closes: https://lore.kernel.org/all/20251206014242.GO1712166@ZenIV/
Fixes: ab84ad5973 ("fuse: new work queue to periodically invalidate expired dentries")
Signed-off-by: Miklos Szeredi <mszeredi@redhat.com>
Link: https://patch.msgid.link/20260114145344.468856-2-mszeredi@redhat.com
Signed-off-by: Christian Brauner <brauner@kernel.org>
This commit is contained in:
Miklos Szeredi
2026-01-14 15:53:38 +01:00
committed by Christian Brauner
parent 4973d95679
commit cb8d2bdcb8

View File

@@ -172,8 +172,8 @@ static void fuse_dentry_tree_work(struct work_struct *work)
if (time_after64(get_jiffies_64(), fd->time)) {
rb_erase(&fd->node, &dentry_hash[i].tree);
RB_CLEAR_NODE(&fd->node);
spin_unlock(&dentry_hash[i].lock);
d_dispose_if_unused(fd->dentry, &dispose);
spin_unlock(&dentry_hash[i].lock);
cond_resched();
spin_lock(&dentry_hash[i].lock);
} else
@@ -479,18 +479,12 @@ static int fuse_dentry_init(struct dentry *dentry)
return 0;
}
static void fuse_dentry_prune(struct dentry *dentry)
static void fuse_dentry_release(struct dentry *dentry)
{
struct fuse_dentry *fd = dentry->d_fsdata;
if (!RB_EMPTY_NODE(&fd->node))
fuse_dentry_tree_del_node(dentry);
}
static void fuse_dentry_release(struct dentry *dentry)
{
struct fuse_dentry *fd = dentry->d_fsdata;
kfree_rcu(fd, rcu);
}
@@ -527,7 +521,6 @@ const struct dentry_operations fuse_dentry_operations = {
.d_revalidate = fuse_dentry_revalidate,
.d_delete = fuse_dentry_delete,
.d_init = fuse_dentry_init,
.d_prune = fuse_dentry_prune,
.d_release = fuse_dentry_release,
.d_automount = fuse_dentry_automount,
};