All of lore.kernel.org
 help / color / mirror / Atom feed
From: Leon Ming <leonming@meta.com>
To: Miklos Szeredi <miklos@szeredi.hu>
Cc: <fuse-devel@lists.linux.dev>, <linux-fsdevel@vger.kernel.org>,
	<linux-kernel@vger.kernel.org>, Luis Henriques <luis@igalia.com>,
	Joanne Koong <joannelkoong@gmail.com>, <leonming@meta.com>,
	<stable@vger.kernel.org>
Subject: [PATCH v2] fuse: set dentry epoch before making the dentry visible
Date: Wed, 28 Oct 2026 20:37:00 -0700	[thread overview]
Message-ID: <20261029033700.273707-1-leonming@meta.com> (raw)

fuse_lookup(), create_new_entry(), fuse_create_open() and
fuse_direntplus_link() set the dentry epoch only after d_splice_alias()
or d_instantiate() has made the dentry visible.  A new dentry starts at
epoch 0 and the connection at epoch 1, so in that window a concurrent
lookup can find the dentry, fail fuse_dentry_revalidate() and
d_invalidate() it.  The original lookup then returns an unhashed dentry,
and readlink() of /proc/self/fd/N on an O_PATH fd for it says
"<path> (deleted)" for a file that exists.

Anything that resolves real paths with open(O_PATH) + readlink() can
pick up the bogus path; TypeScript's native compiler (tsgo)
intermittently fails to find installed packages this way.  It got much
more common after commit 5a6baf204610 ("fuse: fix uninit-value in
fuse_dentry_revalidate()") zeroed the initial epoch; before that, slab
garbage usually passed the check.

Set the epoch before the dentry is published, and keep setting it on
any alias that d_splice_alias() returns; the dentry that loses to an
alias is dropped without ever being visible, so setting it too is
harmless.

With 32 processes making concurrent first lookups of 6000 files on a
fresh squashfuse_ll mount (UML, 16 CPUs), 1652 of 10.75M lookups came
back "(deleted)" before this patch and none after.

Fixes: 2396356a945b ("fuse: add more control over cache invalidation behaviour")
Cc: stable@vger.kernel.org
Signed-off-by: Leon Ming <leonming@meta.com>
---
v2: in fuse_direntplus_link(), set the epoch in each branch instead of
    once after both (Luis)
v1: https://lore.kernel.org/r/20261026201101.3915217-1-leonming@meta.com

 fs/fuse/dir.c     | 12 ++++++++----
 fs/fuse/readdir.c |  5 ++++-
 2 files changed, 12 insertions(+), 5 deletions(-)

diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c
index 28516a1760..650bd13fd2 100644
--- a/fs/fuse/dir.c
+++ b/fs/fuse/dir.c
@@ -644,13 +644,17 @@ static struct dentry *fuse_lookup(struct inode *dir, struct dentry *entry,
 	if (inode && get_node_id(inode) == FUSE_ROOT_ID)
 		goto out_iput;
 
+	/* Set before the dentry is visible, or a racing lookup invalidates it */
+	fuse_dentry_set_epoch(entry, epoch);
 	newent = d_splice_alias(inode, entry);
 	err = PTR_ERR(newent);
 	if (IS_ERR(newent))
 		goto out_err;
 
-	entry = newent ? newent : entry;
-	fuse_dentry_set_epoch(entry, epoch);
+	if (newent) {
+		entry = newent;
+		fuse_dentry_set_epoch(entry, epoch);
+	}
 	if (outarg_valid)
 		fuse_change_entry_timeout(entry, &outarg);
 	else
@@ -907,8 +911,8 @@ static int fuse_create_open(struct mnt_idmap *idmap, struct inode *dir,
 		goto out_err;
 	}
 	kfree(forget);
-	d_instantiate(entry, inode);
 	fuse_dentry_set_epoch(entry, epoch);
+	d_instantiate(entry, inode);
 	fuse_change_entry_timeout(entry, &outentry);
 	fuse_dir_changed(dir);
 	err = generic_file_open(inode, file);
@@ -1030,6 +1034,7 @@ static struct dentry *create_new_entry(struct mnt_idmap *idmap, struct fuse_moun
 		fuse_chan_queue_forget(fm->fc->chan, forget, outarg.nodeid, 1);
 		return ERR_PTR(-ENOMEM);
 	}
+	fuse_dentry_set_epoch(entry, epoch);
 	kfree(forget);
 
 	d = d_splice_alias(inode, entry);
@@ -1040,7 +1045,6 @@ static struct dentry *create_new_entry(struct mnt_idmap *idmap, struct fuse_moun
 		fuse_dentry_set_epoch(d, epoch);
 		fuse_change_entry_timeout(d, &outarg);
 	} else {
-		fuse_dentry_set_epoch(entry, epoch);
 		fuse_change_entry_timeout(entry, &outarg);
 	}
 	fuse_dir_changed(dir);
diff --git a/fs/fuse/readdir.c b/fs/fuse/readdir.c
index 83a1cc46fe..a3af5ad82a 100644
--- a/fs/fuse/readdir.c
+++ b/fs/fuse/readdir.c
@@ -228,6 +228,7 @@ static int fuse_direntplus_link(struct file *file,
 		fuse_change_attributes(inode, &o->attr, NULL,
 				       ATTR_TIMEOUT(o),
 				       attr_version);
+		fuse_dentry_set_epoch(dentry, epoch);
 		/*
 		 * The other branch comes via fuse_iget()
 		 * which bumps nlookup inside
@@ -239,6 +240,7 @@ static int fuse_direntplus_link(struct file *file,
 		if (!inode)
 			inode = ERR_PTR(-ENOMEM);
 
+		fuse_dentry_set_epoch(dentry, epoch);
 		alias = d_splice_alias(inode, dentry);
 		d_lookup_done(dentry);
 		if (alias) {
@@ -255,10 +257,11 @@ static int fuse_direntplus_link(struct file *file,
 			}
 			return PTR_ERR(dentry);
 		}
+		if (alias)
+			fuse_dentry_set_epoch(dentry, epoch);
 	}
 	if (fc->readdirplus_auto)
 		set_bit(FUSE_I_INIT_RDPLUS, &get_fuse_inode(inode)->state);
-	fuse_dentry_set_epoch(dentry, epoch);
 	fuse_change_entry_timeout(dentry, o);
 
 	dput(dentry);
-- 
2.53.0-Meta


                 reply	other threads:[~2026-10-29  3:37 UTC|newest]

Thread overview: [no followups] expand[flat|nested]  mbox.gz  Atom feed

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20261029033700.273707-1-leonming@meta.com \
    --to=leonming@meta.com \
    --cc=fuse-devel@lists.linux.dev \
    --cc=joannelkoong@gmail.com \
    --cc=linux-fsdevel@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=luis@igalia.com \
    --cc=miklos@szeredi.hu \
    --cc=stable@vger.kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.