[PATCH 3/8] nfs/localio: fix ref leak on nfs_uuid_add_file failure

From: Jeff Layton

Date: Mon Jun 01 2026 - 13:37:55 EST


From: Chris Mason <clm@xxxxxxxx>

When nfs_uuid_add_file() races with nfs_uuid_put() tearing down
uuid->net, it returns -ENXIO without publishing nfl->nfs_uuid via
rcu_assign_pointer(). nfs_open_local_fh() then enters its error
branch and only releases the slot pair plus its own entry-time net
ref, while the close path is a no-op:

nfs_close_local_fh()
nfs_uuid = rcu_dereference(nfl->nfs_uuid);
if (!nfs_uuid) { rcu_read_unlock(); return; } /* always */

nfsd_open_local_fh() returns localio holding a caller-owned +1
nfsd_file reference (from nfsd_file_get() after
nfsd_file_acquire_local()) and an entry-time nfsd_net reference
(from its first nfsd_net_try_get()) embedded as nf->nf_net. Both
are leaked on the failure path, pinning one nfsd_file (and the
underlying struct file, dentry, inode) and one nfsd_net_ref per
occurrence, which blocks nfsd_net and netns teardown.

Fix by releasing the caller-owned file ref and its net ref through
the existing helper, using a stack-local RCU pointer so the helper
can xchg it out, then returning -ENXIO so callers do not
dereference a localio whose slot has been cleared:

struct nfsd_file __rcu *tmp = RCU_INITIALIZER(localio);

nfs_to_nfsd_file_put_local(pnf);
nfs_to_nfsd_file_put_local(&tmp);
localio = ERR_PTR(-ENXIO);

The trailing nfs_to_nfsd_net_put(net) continues to release the
outer net ref, so all three nfsd_net_try_get() increments are
balanced on the error branch.

Fixes: fdd015de7679 ("NFS/localio: nfs_uuid_put() fix races with nfs_open/close_local_fh()")
Assisted-by: kres:claude-opus-4-7
Signed-off-by: Chris Mason <clm@xxxxxxxx>
---
fs/nfs_common/nfslocalio.c | 14 +++++++++++++-
1 file changed, 13 insertions(+), 1 deletion(-)

diff --git a/fs/nfs_common/nfslocalio.c b/fs/nfs_common/nfslocalio.c
index dd715cdb6c04..a3c1c5c2764a 100644
--- a/fs/nfs_common/nfslocalio.c
+++ b/fs/nfs_common/nfslocalio.c
@@ -292,8 +292,20 @@ struct nfsd_file *nfs_open_local_fh(nfs_uuid_t *uuid,
localio = nfs_to->nfsd_open_local_fh(net, uuid->dom, rpc_clnt, cred,
nfs_fh, pnf, fmode);
if (!IS_ERR(localio) && nfs_uuid_add_file(uuid, nfl) < 0) {
- /* Delete the cached file when racing with nfs_uuid_put() */
+ /*
+ * Delete the cached file when racing with nfs_uuid_put().
+ * Since nfl->nfs_uuid was never published via
+ * rcu_assign_pointer(), nfs_close_local_fh() will early-return
+ * and cannot clean up after us. Drop the slot pair, then drop
+ * the caller-owned nfsd_file ref (+1) and the entry-time
+ * nfsd_net ref carried via nf->nf_net, and return -ENXIO so
+ * the caller never dereferences the now-cleared localio.
+ */
+ struct nfsd_file __rcu *tmp = RCU_INITIALIZER(localio);
+
nfs_to_nfsd_file_put_local(pnf);
+ nfs_to_nfsd_file_put_local(&tmp);
+ localio = ERR_PTR(-ENXIO);
}
nfs_to_nfsd_net_put(net);


--
2.54.0