From: Jonathan Flynn <jonathan.flynn@hammerspace.com>
To: NeilBrown <neil@brown.name>, Trond Myklebust <trondmy@kernel.org>,
Anna Schumaker <anna@kernel.org>
Cc: Alexander Viro <viro@zeniv.linux.org.uk>,
Christian Brauner <brauner@kernel.org>,
Jeff Layton <jlayton@kernel.org>, Chuck Lever <cel@kernel.org>,
Jan Kara <jack@suse.cz>,
linux-nfs@vger.kernel.org, linux-fsdevel@vger.kernel.org,
linux-kernel@vger.kernel.org
Subject: [PATCH 2/3] nfs: answer a handed-off silly-delete on a removal target with -EBUSY
Date: Thu, 29 Oct 2026 12:09:49 -0600 [thread overview]
Message-ID: <20261029180950.387072-3-jonathan.flynn@hammerspace.com> (raw)
In-Reply-To: <20261029180950.387072-1-jonathan.flynn@hammerspace.com>
When the last user of a silly-renamed file goes away, nfs_call_unlink()
can hand the delete it still owes to another cached entry for the same
name: it marks that entry DCACHE_NFSFS_RENAMED and stores its struct
nfs_unlinkdata in ->d_fsdata, under the entry's ->d_lock. NeilBrown's
"nfs: fix nfs_call_unlink()" makes that hand-off reachable; before it,
the lookup for the other entry was given a name with no hash and never
found one.
The VFS refuses to remove an entry so marked, or to rename over one,
with -EBUSY, but may_delete_dentry() tests the flag without ->d_lock,
and a hand-off can land after it. nfs_unlink() repeats the test under
->d_lock and treats a marked entry as a bug: it warns and returns
-ETXTBSY. nfs_rename() makes the same test before it takes ->d_lock, so
a hand-off can also land between the test and block_revalidate(), which
then kfree()s the nfs_unlinkdata as if it were a device name. Its
credential and name are leaked, DCACHE_NFSFS_RENAMED stays set with no
data behind it, and the final dput() of the target reaches
nfs_call_unlink() with a NULL pointer, which it dereferences.
Take the target's ->d_lock before the tests in nfs_rename(), as
nfs_unlink() already does, and answer a marked entry in both with
-EBUSY and no warning, as may_delete_dentry() does. No hand-off can
land between the test and the store any more, and one that landed
before the test makes the entry the busy file the VFS already refuses.
This was found by an AI coding assistant (Claude Code) reading the
code. The hand-off itself has been reproduced with "nfs: fix
nfs_call_unlink()" applied; its race with a removal or rename has not.
Fixes: 3c59366c207e ("NFS: don't unhash dentry during unlink/rename")
Signed-off-by: Jonathan Flynn <jonathan.flynn@hammerspace.com>
---
Notes:
For reviewers and review tools; not part of the changelog.
A hand-off that lands while nfs_rename() is in its silly-rename
branch (the target's count above 2, with ->d_lock dropped before
d_duplicate()) still gets -ETXTBSY: nfs_sillyrename() fails with
-EBUSY, and that branch returns -ETXTBSY for every
nfs_sillyrename() failure. This patch leaves the branch as it
was, and the handed-off data is neither freed nor leaked there,
since nfs_async_unlink() tests and stores under ->d_lock. An
automated review (Claude Code running Sashiko's review prompts)
raised this; it was checked and dropped as not introduced here.
fs/nfs/dir.c | 32 ++++++++++++++++++++++++++------
1 file changed, 26 insertions(+), 6 deletions(-)
diff --git a/fs/nfs/dir.c b/fs/nfs/dir.c
index 36cb99f746..dc7784b666 100644
--- a/fs/nfs/dir.c
+++ b/fs/nfs/dir.c
@@ -2605,10 +2605,18 @@ int nfs_unlink(struct inode *dir, struct dentry *dentry)
* completes. ->d_revalidate will wait for ->d_fsdata
* to clear. We set it here to ensure no lookup succeeds until
* the unlink is complete on the server.
+ *
+ * may_delete_dentry() refused an entry carrying a silly-delete,
+ * but without ->d_lock, and nfs_call_unlink() can have handed
+ * this one a silly-delete since. It is busy, as the VFS says.
*/
+ error = -EBUSY;
+ if (dentry->d_flags & DCACHE_NFSFS_RENAMED) {
+ spin_unlock(&dentry->d_lock);
+ goto out;
+ }
error = -ETXTBSY;
- if (WARN_ON(dentry->d_flags & DCACHE_NFSFS_RENAMED) ||
- WARN_ON(dentry->d_fsdata == NFS_FSDATA_BLOCKED)) {
+ if (WARN_ON(dentry->d_fsdata == NFS_FSDATA_BLOCKED)) {
spin_unlock(&dentry->d_lock);
goto out;
}
@@ -2801,13 +2809,25 @@ int nfs_rename(const struct mnt_idmap *idmap, struct inode *old_dir,
* completes. ->d_revalidate will wait for ->d_fsdata
* to clear. We set it here to ensure no lookup succeeds until
* the unlink is complete on the server.
+ *
+ * Test under ->d_lock, which nfs_call_unlink() hands a
+ * silly-delete over under and block_revalidate() below
+ * stores under, so that no hand-off lands between the test
+ * and the store, which would kfree() it. A target carrying
+ * one is busy, as may_delete_dentry() says when it sees the
+ * flag first.
*/
+ spin_lock(&new_dentry->d_lock);
+ error = -EBUSY;
+ if (new_dentry->d_flags & DCACHE_NFSFS_RENAMED) {
+ spin_unlock(&new_dentry->d_lock);
+ goto out;
+ }
error = -ETXTBSY;
- if (WARN_ON(new_dentry->d_flags & DCACHE_NFSFS_RENAMED) ||
- WARN_ON(new_dentry->d_fsdata == NFS_FSDATA_BLOCKED))
+ if (WARN_ON(new_dentry->d_fsdata == NFS_FSDATA_BLOCKED)) {
+ spin_unlock(&new_dentry->d_lock);
goto out;
-
- spin_lock(&new_dentry->d_lock);
+ }
if (d_count(new_dentry) > 2) {
int err;
--
2.43.0
next prev parent reply other threads:[~2026-10-29 18:09 UTC|newest]
Thread overview: 31+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-28 4:06 [PATCH v4 0/9] nfs: prepare for locking changes NeilBrown
2026-10-28 4:06 ` [PATCH v4 1/9] nfs: fix open-blocking with d_fsdata NeilBrown
2026-10-28 4:06 ` [PATCH v4 2/9] nfs: fix nfs_call_unlink() NeilBrown
2026-10-29 18:09 ` [PATCH 0/3] nfs: fix the silly-delete hand-off that 2/9 makes reachable Jonathan Flynn
2026-10-29 18:09 ` [PATCH 1/3] nfs: do not transfer a sillydelete onto a blocked dentry Jonathan Flynn
2026-10-29 18:09 ` Jonathan Flynn [this message]
2026-10-29 18:09 ` [PATCH 3/3] nfs: take rmdir_sem for a silly-delete only when its REMOVE is sent Jonathan Flynn
2026-10-29 21:58 ` [PATCH 0/3] nfs: fix the silly-delete hand-off that 2/9 makes reachable NeilBrown
2026-10-28 4:06 ` [PATCH v4 3/9] nfs: remove d_drop()/d_alloc_parallel() from nfs_atomic_open() NeilBrown
2026-10-28 4:06 ` [PATCH v4 4/9] nfs: use d_splice_alias() in nfs_link() NeilBrown
2026-10-28 4:06 ` [PATCH v4 5/9] nfs: don't d_drop() before d_splice_alias() NeilBrown
2026-10-28 4:06 ` [PATCH v4 6/9] nfs: don't d_drop() before d_splice_alias() in atomic_create NeilBrown
2026-10-28 4:06 ` [PATCH v4 7/9] nfs: Use d_alloc_trylock() in nfs_prime_dcache() NeilBrown
2026-10-28 4:06 ` [PATCH v4 8/9] nfs: use d_alloc_trylock() in silly-rename NeilBrown
2026-10-28 4:06 ` [PATCH v4 9/9] nfs: use d_duplicate() NeilBrown
2026-10-28 17:21 ` [PATCH v4 0/9] nfs: prepare for locking changes Anna Schumaker
2026-10-29 18:09 ` [PATCH RFC 00/12] vfs, nfs: concurrent create and unlink in one directory Jonathan Flynn
2026-10-29 18:09 ` [PATCH RFC 01/12] nfs: update a server's capabilities in one step Jonathan Flynn
2026-10-29 18:09 ` [PATCH RFC 02/12] vfs: allow the parent lock to be shared for O_CREAT Jonathan Flynn
2026-10-29 18:09 ` [PATCH RFC 03/12] nfs: tell the VFS when two names can be one entry on the server Jonathan Flynn
2026-10-29 22:23 ` NeilBrown
2026-10-29 18:09 ` [PATCH RFC 04/12] nfs: concurrent O_CREAT in one NFSv4 directory Jonathan Flynn
2026-10-29 18:09 ` [PATCH RFC 05/12] vfs: factor file removal into a helper, no functional change Jonathan Flynn
2026-10-29 18:09 ` [PATCH RFC 06/12] vfs: let a filesystem remove files with the parent directory locked shared Jonathan Flynn
2026-10-29 18:09 ` [PATCH RFC 07/12] nfs: add a second mark for a removal made with the parent " Jonathan Flynn
2026-10-29 18:09 ` [PATCH RFC 08/12] nfs: keep the dentry of a shared-parent removal hashed Jonathan Flynn
2026-10-29 18:09 ` [PATCH RFC 09/12] nfs: do not cache a name from a listing that a removal overlapped Jonathan Flynn
2026-10-29 18:09 ` [PATCH RFC 10/12] nfs: add the removal method for a parent directory locked shared Jonathan Flynn
2026-10-29 18:09 ` [PATCH RFC 11/12] nfs: look a name up again once its removal has unhashed it Jonathan Flynn
2026-10-29 18:09 ` [PATCH RFC 12/12] nfs: remove files with the parent directory locked shared on NFSv4 Jonathan Flynn
2026-10-29 22:18 ` [PATCH RFC 00/12] vfs, nfs: concurrent create and unlink in one directory NeilBrown
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=20261029180950.387072-3-jonathan.flynn@hammerspace.com \
--to=jonathan.flynn@hammerspace.com \
--cc=anna@kernel.org \
--cc=brauner@kernel.org \
--cc=cel@kernel.org \
--cc=jack@suse.cz \
--cc=jlayton@kernel.org \
--cc=linux-fsdevel@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-nfs@vger.kernel.org \
--cc=neil@brown.name \
--cc=trondmy@kernel.org \
--cc=viro@zeniv.linux.org.uk \
/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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®