public inbox for linux-fsdevel@vger.kernel.org
 help / color / mirror / Atom feed
* [PATCH 0/6] fuse: fixes and cleanups for expired dentry eviction
@ 2026-01-14 14:53 Miklos Szeredi
  2026-01-14 14:53 ` [PATCH 1/6] fuse: fix race when disposing stale dentries Miklos Szeredi
                   ` (7 more replies)
  0 siblings, 8 replies; 11+ messages in thread
From: Miklos Szeredi @ 2026-01-14 14:53 UTC (permalink / raw)
  To: linux-fsdevel; +Cc: Luis Henriques, Al Viro

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>
---

Miklos Szeredi (6):
  fuse: fix race when disposing stale dentries
  fuse: make sure dentry is evicted if stale
  fuse: add need_resched() before unlocking bucket
  fuse: clean up fuse_dentry_tree_work()
  fuse: shrink once after all buckets have been scanned
  vfs: document d_dispose_if_unused()

 fs/dcache.c   | 10 ++++++++++
 fs/fuse/dir.c | 29 ++++++++++++++---------------
 2 files changed, 24 insertions(+), 15 deletions(-)

-- 
2.52.0


^ permalink raw reply	[flat|nested] 11+ messages in thread

* [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

end of thread, other threads:[~2026-01-16 18:16 UTC | newest]

Thread overview: 11+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
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 ` [PATCH 3/6] fuse: add need_resched() before unlocking bucket Miklos Szeredi
2026-01-14 14:53 ` [PATCH 4/6] fuse: clean up fuse_dentry_tree_work() Miklos Szeredi
2026-01-14 14:53 ` [PATCH 5/6] fuse: shrink once after all buckets have been scanned Miklos Szeredi
2026-01-14 14:53 ` [PATCH 6/6] vfs: document d_dispose_if_unused() Miklos Szeredi
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
2026-01-16 18:15 ` Christian Brauner

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox