mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-08-28 01:43:47 -04:00
NFS/localio: fix ref leak on nfs_uuid_add_file failure
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's file ref and its paired net
ref 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: fdd015de76 ("NFS/localio: nfs_uuid_put() fix races with nfs_open/close_local_fh()")
Cc: stable@vger.kernel.org
Assisted-by: kres:claude-opus-4-7
Signed-off-by: Chris Mason <clm@meta.com>
Link: https://patch.msgid.link/20260602-nfsd-testing-v2-3-e4ea62e3cd5c@kernel.org
Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
This commit is contained in:
@@ -292,8 +292,22 @@ 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's file ref and
|
||||
* its paired net ref, 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 =
|
||||
(struct nfsd_file __force __rcu *)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);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user