summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristian Brauner <brauner@kernel.org>2026-01-16 19:15:20 +0100
committerChristian Brauner <brauner@kernel.org>2026-01-16 19:15:20 +0100
commit6358461178ca29a87c66495f1ce854388b0107c3 (patch)
treef7aae1c101378e0e3c0f34aab5f879493659a179
parent4973d95679fb4f8bb4413dcb3bce435ef848285d (diff)
parent79d11311f64d3e9fbc20ac95b7df6f917221329f (diff)
Merge patch series "fuse: fixes and cleanups for expired dentry eviction"
Miklos Szeredi <mszeredi@redhat.com> says: This mini series fixes issues with the stale dentry cleanup patches added in this cycle. In particular commit ab84ad597386 ("fuse: new work queue to periodically invalidate expired dentries") allowed a race resulting in UAF. * patches from https://patch.msgid.link/20260114145344.468856-1-mszeredi@redhat.com: vfs: document d_dispose_if_unused() fuse: shrink once after all buckets have been scanned fuse: clean up fuse_dentry_tree_work() fuse: add need_resched() before unlocking bucket fuse: make sure dentry is evicted if stale fuse: fix race when disposing stale dentries Link: https://patch.msgid.link/20260114145344.468856-1-mszeredi@redhat.com Signed-off-by: Christian Brauner <brauner@kernel.org>
-rw-r--r--fs/dcache.c10
-rw-r--r--fs/fuse/dir.c29
2 files changed, 24 insertions, 15 deletions
diff --git a/fs/dcache.c b/fs/dcache.c
index dc2fff4811d1..66dd1bb830d1 100644
--- a/fs/dcache.c
+++ b/fs/dcache.c
@@ -1104,6 +1104,16 @@ struct dentry *d_find_alias_rcu(struct inode *inode)
return de;
}
+/**
+ * d_dispose_if_unused - move unreferenced dentries to shrink list
+ * @dentry: dentry in question
+ * @dispose: head of shrink list
+ *
+ * If dentry has no external references, move it to shrink list.
+ *
+ * NOTE!!! The caller is responsible for preventing eviction of the dentry by
+ * holding dentry->d_inode->i_lock or equivalent.
+ */
void d_dispose_if_unused(struct dentry *dentry, struct list_head *dispose)
{
spin_lock(&dentry->d_lock);
diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c
index dbb55bad5476..3927cb069236 100644
--- a/fs/fuse/dir.c
+++ b/fs/fuse/dir.c
@@ -169,20 +169,26 @@ static void fuse_dentry_tree_work(struct work_struct *work)
node = rb_first(&dentry_hash[i].tree);
while (node) {
fd = rb_entry(node, struct fuse_dentry, node);
- if (time_after64(get_jiffies_64(), fd->time)) {
- rb_erase(&fd->node, &dentry_hash[i].tree);
- RB_CLEAR_NODE(&fd->node);
+ if (!time_before64(fd->time, get_jiffies_64()))
+ break;
+
+ rb_erase(&fd->node, &dentry_hash[i].tree);
+ RB_CLEAR_NODE(&fd->node);
+ spin_lock(&fd->dentry->d_lock);
+ /* If dentry is still referenced, let next dput release it */
+ fd->dentry->d_flags |= DCACHE_OP_DELETE;
+ spin_unlock(&fd->dentry->d_lock);
+ d_dispose_if_unused(fd->dentry, &dispose);
+ if (need_resched()) {
spin_unlock(&dentry_hash[i].lock);
- d_dispose_if_unused(fd->dentry, &dispose);
cond_resched();
spin_lock(&dentry_hash[i].lock);
- } else
- break;
+ }
node = rb_first(&dentry_hash[i].tree);
}
spin_unlock(&dentry_hash[i].lock);
- shrink_dentry_list(&dispose);
}
+ shrink_dentry_list(&dispose);
if (inval_wq)
schedule_delayed_work(&dentry_tree_work,
@@ -479,18 +485,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 +527,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,
};