* [PATCH 1/6] fuse: fix race when disposing stale dentries
2026-01-14 14:53 [PATCH 0/6] fuse: fixes and cleanups for expired dentry eviction Miklos Szeredi
@ 2026-01-14 14:53 ` Miklos Szeredi
2026-01-14 14:53 ` [PATCH 2/6] fuse: make sure dentry is evicted if stale Miklos Szeredi
` (6 subsequent siblings)
7 siblings, 0 replies; 11+ messages in thread
From: Miklos Szeredi @ 2026-01-14 14:53 UTC (permalink / raw)
To: linux-fsdevel; +Cc: Luis Henriques, Al Viro
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>
---
fs/fuse/dir.c | 11 ++---------
1 file changed, 2 insertions(+), 9 deletions(-)
diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c
index 4b6b3d2758ff..2f89804b9ff3 100644
--- a/fs/fuse/dir.c
+++ b/fs/fuse/dir.c
@@ -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,
};
--
2.52.0
^ permalink raw reply related [flat|nested] 11+ messages in thread* [PATCH 2/6] fuse: make sure dentry is evicted if stale
2026-01-14 14:53 [PATCH 0/6] fuse: fixes and cleanups for expired dentry eviction Miklos Szeredi
2026-01-14 14:53 ` [PATCH 1/6] fuse: fix race when disposing stale dentries Miklos Szeredi
@ 2026-01-14 14:53 ` Miklos Szeredi
2026-01-14 14:53 ` [PATCH 3/6] fuse: add need_resched() before unlocking bucket Miklos Szeredi
` (5 subsequent siblings)
7 siblings, 0 replies; 11+ messages in thread
From: Miklos Szeredi @ 2026-01-14 14:53 UTC (permalink / raw)
To: linux-fsdevel; +Cc: Luis Henriques, Al Viro
d_dispose_if_unused() may find the dentry with a positive refcount, in
which case it won't be put on the dispose list even though it has already
timed out.
"Reinstall" the d_delete() callback, which was optimized out in
fuse_dentry_settime(). This will result in the dentry being evicted as
soon as the refcount hits zero.
Fixes: ab84ad597386 ("fuse: new work queue to periodically invalidate expired dentries")
Signed-off-by: Miklos Szeredi <mszeredi@redhat.com>
---
fs/fuse/dir.c | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c
index 2f89804b9ff3..cd6c92be7a2c 100644
--- a/fs/fuse/dir.c
+++ b/fs/fuse/dir.c
@@ -172,6 +172,10 @@ 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_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);
spin_unlock(&dentry_hash[i].lock);
cond_resched();
--
2.52.0
^ permalink raw reply related [flat|nested] 11+ messages in thread* [PATCH 3/6] fuse: add need_resched() before unlocking bucket
2026-01-14 14:53 [PATCH 0/6] fuse: fixes and cleanups for expired dentry eviction Miklos Szeredi
2026-01-14 14:53 ` [PATCH 1/6] fuse: fix race when disposing stale dentries Miklos Szeredi
2026-01-14 14:53 ` [PATCH 2/6] fuse: make sure dentry is evicted if stale Miklos Szeredi
@ 2026-01-14 14:53 ` Miklos Szeredi
2026-01-14 14:53 ` [PATCH 4/6] fuse: clean up fuse_dentry_tree_work() Miklos Szeredi
` (4 subsequent siblings)
7 siblings, 0 replies; 11+ messages in thread
From: Miklos Szeredi @ 2026-01-14 14:53 UTC (permalink / raw)
To: linux-fsdevel; +Cc: Luis Henriques, Al Viro
In fuse_dentry_tree_work() no need to unlock/lock dentry_hash[i].lock on
each iteration.
Suggested-by: Al Viro <viro@zeniv.linux.org.uk>
Signed-off-by: Miklos Szeredi <mszeredi@redhat.com>
---
fs/fuse/dir.c | 8 +++++---
1 file changed, 5 insertions(+), 3 deletions(-)
diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c
index cd6c92be7a2c..3910c5a53835 100644
--- a/fs/fuse/dir.c
+++ b/fs/fuse/dir.c
@@ -177,9 +177,11 @@ static void fuse_dentry_tree_work(struct work_struct *work)
fd->dentry->d_flags |= DCACHE_OP_DELETE;
spin_unlock(&fd->dentry->d_lock);
d_dispose_if_unused(fd->dentry, &dispose);
- spin_unlock(&dentry_hash[i].lock);
- cond_resched();
- spin_lock(&dentry_hash[i].lock);
+ if (need_resched()) {
+ spin_unlock(&dentry_hash[i].lock);
+ cond_resched();
+ spin_lock(&dentry_hash[i].lock);
+ }
} else
break;
node = rb_first(&dentry_hash[i].tree);
--
2.52.0
^ permalink raw reply related [flat|nested] 11+ messages in thread* [PATCH 4/6] fuse: clean up fuse_dentry_tree_work()
2026-01-14 14:53 [PATCH 0/6] fuse: fixes and cleanups for expired dentry eviction Miklos Szeredi
` (2 preceding siblings ...)
2026-01-14 14:53 ` [PATCH 3/6] fuse: add need_resched() before unlocking bucket Miklos Szeredi
@ 2026-01-14 14:53 ` Miklos Szeredi
2026-01-14 14:53 ` [PATCH 5/6] fuse: shrink once after all buckets have been scanned Miklos Szeredi
` (3 subsequent siblings)
7 siblings, 0 replies; 11+ messages in thread
From: Miklos Szeredi @ 2026-01-14 14:53 UTC (permalink / raw)
To: linux-fsdevel; +Cc: Luis Henriques, Al Viro
- Change time_after64() time_before64(), since the latter is exclusively
used in this file to compare dentry/inode timeout with current time.
- Move the break statement from the else branch to the if branch, reducing
indentation.
Signed-off-by: Miklos Szeredi <mszeredi@redhat.com>
---
fs/fuse/dir.c | 28 ++++++++++++++--------------
1 file changed, 14 insertions(+), 14 deletions(-)
diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c
index 3910c5a53835..d8dd515d4bd6 100644
--- a/fs/fuse/dir.c
+++ b/fs/fuse/dir.c
@@ -169,21 +169,21 @@ 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);
- 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);
- cond_resched();
- spin_lock(&dentry_hash[i].lock);
- }
- } else
+ 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);
+ cond_resched();
+ spin_lock(&dentry_hash[i].lock);
+ }
node = rb_first(&dentry_hash[i].tree);
}
spin_unlock(&dentry_hash[i].lock);
--
2.52.0
^ permalink raw reply related [flat|nested] 11+ messages in thread* [PATCH 5/6] fuse: shrink once after all buckets have been scanned
2026-01-14 14:53 [PATCH 0/6] fuse: fixes and cleanups for expired dentry eviction Miklos Szeredi
` (3 preceding siblings ...)
2026-01-14 14:53 ` [PATCH 4/6] fuse: clean up fuse_dentry_tree_work() Miklos Szeredi
@ 2026-01-14 14:53 ` Miklos Szeredi
2026-01-14 14:53 ` [PATCH 6/6] vfs: document d_dispose_if_unused() Miklos Szeredi
` (2 subsequent siblings)
7 siblings, 0 replies; 11+ messages in thread
From: Miklos Szeredi @ 2026-01-14 14:53 UTC (permalink / raw)
To: linux-fsdevel; +Cc: Luis Henriques, Al Viro
In fuse_dentry_tree_work() move the shrink_dentry_list() out from the loop.
Suggested-by: Al Viro <viro@zeniv.linux.org.uk>
Signed-off-by: Miklos Szeredi <mszeredi@redhat.com>
---
fs/fuse/dir.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c
index d8dd515d4bd6..fc1734758c8a 100644
--- a/fs/fuse/dir.c
+++ b/fs/fuse/dir.c
@@ -187,8 +187,8 @@ static void fuse_dentry_tree_work(struct work_struct *work)
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,
--
2.52.0
^ permalink raw reply related [flat|nested] 11+ messages in thread* [PATCH 6/6] vfs: document d_dispose_if_unused()
2026-01-14 14:53 [PATCH 0/6] fuse: fixes and cleanups for expired dentry eviction Miklos Szeredi
` (4 preceding siblings ...)
2026-01-14 14:53 ` [PATCH 5/6] fuse: shrink once after all buckets have been scanned Miklos Szeredi
@ 2026-01-14 14:53 ` Miklos Szeredi
2026-01-14 15:29 ` [PATCH 0/6] fuse: fixes and cleanups for expired dentry eviction Christian Brauner
2026-01-16 18:15 ` Christian Brauner
7 siblings, 0 replies; 11+ messages in thread
From: Miklos Szeredi @ 2026-01-14 14:53 UTC (permalink / raw)
To: linux-fsdevel; +Cc: Luis Henriques, Al Viro
Add a warning about the danger of using this function without proper
locking preventing eviction.
Signed-off-by: Miklos Szeredi <mszeredi@redhat.com>
---
fs/dcache.c | 10 ++++++++++
1 file changed, 10 insertions(+)
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);
--
2.52.0
^ permalink raw reply related [flat|nested] 11+ messages in thread* Re: [PATCH 0/6] fuse: fixes and cleanups for expired dentry eviction
2026-01-14 14:53 [PATCH 0/6] fuse: fixes and cleanups for expired dentry eviction Miklos Szeredi
` (5 preceding siblings ...)
2026-01-14 14:53 ` [PATCH 6/6] vfs: document d_dispose_if_unused() Miklos Szeredi
@ 2026-01-14 15:29 ` Christian Brauner
2026-01-14 15:43 ` Miklos Szeredi
2026-01-16 18:15 ` Christian Brauner
7 siblings, 1 reply; 11+ messages in thread
From: Christian Brauner @ 2026-01-14 15:29 UTC (permalink / raw)
To: Miklos Szeredi; +Cc: linux-fsdevel, Luis Henriques, Al Viro
On Wed, Jan 14, 2026 at 03:53:37PM +0100, Miklos Szeredi wrote:
> 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.
>
> Signed-off-by: Miklos Szeredi <mszeredi@redhat.com>
> ---
Do you want me to route those via vfs.fixes?
Btw, the Link: you provided in the first patch points to nothing on lore.
^ permalink raw reply [flat|nested] 11+ messages in thread* Re: [PATCH 0/6] fuse: fixes and cleanups for expired dentry eviction
2026-01-14 15:29 ` [PATCH 0/6] fuse: fixes and cleanups for expired dentry eviction Christian Brauner
@ 2026-01-14 15:43 ` Miklos Szeredi
2026-01-14 16:59 ` Luis Henriques
0 siblings, 1 reply; 11+ messages in thread
From: Miklos Szeredi @ 2026-01-14 15:43 UTC (permalink / raw)
To: Christian Brauner; +Cc: Miklos Szeredi, linux-fsdevel, Luis Henriques, Al Viro
On Wed, 14 Jan 2026 at 16:37, Christian Brauner <brauner@kernel.org> wrote:
>
> On Wed, Jan 14, 2026 at 03:53:37PM +0100, Miklos Szeredi wrote:
> > 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.
> >
> > Signed-off-by: Miklos Szeredi <mszeredi@redhat.com>
> > ---
>
> Do you want me to route those via vfs.fixes?
Yes, please.
> Btw, the Link: you provided in the first patch points to nothing on lore.
It works for me. How can that happen?
Thanks,
Miklos
^ permalink raw reply [flat|nested] 11+ messages in thread* Re: [PATCH 0/6] fuse: fixes and cleanups for expired dentry eviction
2026-01-14 15:43 ` Miklos Szeredi
@ 2026-01-14 16:59 ` Luis Henriques
0 siblings, 0 replies; 11+ messages in thread
From: Luis Henriques @ 2026-01-14 16:59 UTC (permalink / raw)
To: Miklos Szeredi; +Cc: Christian Brauner, Miklos Szeredi, linux-fsdevel, Al Viro
On Wed, Jan 14 2026, Miklos Szeredi wrote:
> On Wed, 14 Jan 2026 at 16:37, Christian Brauner <brauner@kernel.org> wrote:
>>
>> On Wed, Jan 14, 2026 at 03:53:37PM +0100, Miklos Szeredi wrote:
>> > 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.
>> >
>> > Signed-off-by: Miklos Szeredi <mszeredi@redhat.com>
>> > ---
>>
>> Do you want me to route those via vfs.fixes?
>
> Yes, please.
>
>> Btw, the Link: you provided in the first patch points to nothing on lore.
>
> It works for me. How can that happen?
Yikes! I totally missed that discussion (the link works for me too btw).
I remember seeing the fuse pull request for 6.19 but not the discussion
that followed.
I'm still reading through that thread, but these patches look sensible.
Thanks a lot for fixing these issues, Miklos!
Cheers,
--
Luís
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH 0/6] fuse: fixes and cleanups for expired dentry eviction
2026-01-14 14:53 [PATCH 0/6] fuse: fixes and cleanups for expired dentry eviction Miklos Szeredi
` (6 preceding siblings ...)
2026-01-14 15:29 ` [PATCH 0/6] fuse: fixes and cleanups for expired dentry eviction Christian Brauner
@ 2026-01-16 18:15 ` Christian Brauner
7 siblings, 0 replies; 11+ messages in thread
From: Christian Brauner @ 2026-01-16 18:15 UTC (permalink / raw)
To: Miklos Szeredi; +Cc: Christian Brauner, Luis Henriques, Al Viro, linux-fsdevel
On Wed, 14 Jan 2026 15:53:37 +0100, Miklos Szeredi wrote:
> 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.
>
>
Applied to the vfs.fixes branch of the vfs/vfs.git tree.
Patches in the vfs.fixes branch should appear in linux-next soon.
Please report any outstanding bugs that were missed during review in a
new review to the original patch series allowing us to drop it.
It's encouraged to provide Acked-bys and Reviewed-bys even though the
patch has now been applied. If possible patch trailers will be updated.
Note that commit hashes shown below are subject to change due to rebase,
trailer updates or similar. If in doubt, please check the listed branch.
tree: https://git.kernel.org/pub/scm/linux/kernel/git/vfs/vfs.git
branch: vfs.fixes
[1/6] fuse: fix race when disposing stale dentries
https://git.kernel.org/vfs/vfs/c/cb8d2bdcb824
[2/6] fuse: make sure dentry is evicted if stale
https://git.kernel.org/vfs/vfs/c/1e2c1af1beb3
[3/6] fuse: add need_resched() before unlocking bucket
https://git.kernel.org/vfs/vfs/c/09f7a43ae501
[4/6] fuse: clean up fuse_dentry_tree_work()
https://git.kernel.org/vfs/vfs/c/3926746b5534
[5/6] fuse: shrink once after all buckets have been scanned
https://git.kernel.org/vfs/vfs/c/fa79401a9c35
[6/6] vfs: document d_dispose_if_unused()
https://git.kernel.org/vfs/vfs/c/79d11311f64d
^ permalink raw reply [flat|nested] 11+ messages in thread