]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
fuse: fix race when disposing stale dentries
authorMiklos Szeredi <mszeredi@redhat.com>
Wed, 14 Jan 2026 14:53:38 +0000 (15:53 +0100)
committerChristian Brauner <brauner@kernel.org>
Fri, 16 Jan 2026 18:15:14 +0000 (19:15 +0100)
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: ab84ad597386 ("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>
fs/fuse/dir.c

index dbb55bad5476ef2f33922305c78580b9e2ff92a4..ea90dd682bc369934f103a9712385f5d8dfa76c2 100644 (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,
 };