[PATCH v2 8/9] nfsd: hold net namespace reference for delayed-dispose nfsd_files
From: Jeff Layton
Date: Tue Jun 02 2026 - 12:36:31 EST
Take a net-namespace reference in nfsd_file_dispose_list_delayed()
(get_net) and release it in nfsd_file_free() (put_net), so that
nf_net is always valid for files that the GC or shrinker has isolated
from the hash table and LRU -- which __nfsd_file_cache_purge() cannot
see.
Without this, nf_net can dangle for in-flight files whose net namespace
is torn down concurrently, causing a use-after-free when
nfsd_file_dispose_list_delayed() calls net_generic(nf->nf_net, ...).
Only files entering the delayed-dispose path need the net reference;
files freed synchronously (non-GC stateful opens, purge, direct put)
are always freed while the net namespace is still alive, so they skip
get_net()/put_net() entirely. A new NFSD_FILE_NET_HELD flag tracks
whether a given nfsd_file holds a net reference.
Because nfsd_file_free() may now call put_net(nf->nf_net), the old
nfsd_file_put_local() pattern of returning nf->nf_net after
nfsd_file_put() is unsafe -- put_net() could theoretically drop the
last net namespace reference, leaving the returned pointer stale.
Fix this by moving the nfsd_net_put() call into nfsd_file_put_local()
itself, before the nfsd_file_put() that may trigger nfsd_file_free().
The function now returns void and the caller no longer needs to handle
the net reference.
Fixes: 43fd953fa7e2 ("nfsd: simplify the delayed disposal list code")
Assisted-by: Claude:claude-opus-4-6
Signed-off-by: Jeff Layton <jlayton@xxxxxxxxxx>
---
fs/nfsd/filecache.c | 34 ++++++++++++++++++++++++++--------
fs/nfsd/filecache.h | 3 ++-
fs/nfsd/localio.c | 4 ++--
include/linux/nfslocalio.h | 9 ++-------
4 files changed, 32 insertions(+), 18 deletions(-)
diff --git a/fs/nfsd/filecache.c b/fs/nfsd/filecache.c
index 03f01a0beced..957fe57be063 100644
--- a/fs/nfsd/filecache.c
+++ b/fs/nfsd/filecache.c
@@ -295,6 +295,9 @@ nfsd_file_free(struct nfsd_file *nf)
if (WARN_ON_ONCE(!list_empty(&nf->nf_lru)))
return;
+ if (test_bit(NFSD_FILE_NET_HELD, &nf->nf_flags))
+ put_net(nf->nf_net);
+
call_rcu(&nf->nf_rcu, nfsd_file_slab_free);
}
@@ -375,24 +378,28 @@ nfsd_file_put(struct nfsd_file *nf)
}
/**
- * nfsd_file_put_local - put nfsd_file reference and arm nfsd_net_put in caller
+ * nfsd_file_put_local - put nfsd_file reference and release nfsd_net ref
* @pnf: nfsd_file of which to put the reference
*
- * First save the associated net to return to caller, then put
- * the reference of the nfsd_file.
+ * Drops both the nfsd_file reference and the associated nfsd_net
+ * reference. The nfsd_net ref is released before the file ref so
+ * that put_net() inside nfsd_file_free() cannot drop the last net
+ * namespace reference while the caller still needs it.
*/
-struct net *
+void
nfsd_file_put_local(struct nfsd_file __rcu **pnf)
{
struct nfsd_file *nf;
- struct net *net = NULL;
nf = unrcu_pointer(xchg(pnf, NULL));
if (nf) {
- net = nf->nf_net;
+ struct net *net = nf->nf_net;
+
+ rcu_read_lock();
+ nfsd_net_put(net);
+ rcu_read_unlock();
nfsd_file_put(nf);
}
- return net;
}
/**
@@ -433,9 +440,20 @@ nfsd_file_dispose_list_delayed(struct list_head *dispose)
while (!list_empty(dispose)) {
struct nfsd_file *nf = list_first_entry(dispose,
struct nfsd_file, nf_gc);
- struct nfsd_net *nn = net_generic(nf->nf_net, nfsd_net_id);
+ struct nfsd_net *nn;
struct svc_serv *serv;
+ /*
+ * Pin the net namespace so nf_net stays valid while the
+ * file sits on the per-net dispose list. Callers (the GC
+ * worker, shrinker, and fsnotify callbacks) always run
+ * before nfsd_net_exit(), so nf_net is still live here.
+ * The matching put_net() is in nfsd_file_free().
+ */
+ get_net(nf->nf_net);
+ set_bit(NFSD_FILE_NET_HELD, &nf->nf_flags);
+
+ nn = net_generic(nf->nf_net, nfsd_net_id);
spin_lock(&nn->fcache_dispose_lock);
list_move_tail(&nf->nf_gc, &nn->fcache_dispose_list);
spin_unlock(&nn->fcache_dispose_lock);
diff --git a/fs/nfsd/filecache.h b/fs/nfsd/filecache.h
index 683b6437cacc..7ae3c0ea0a2a 100644
--- a/fs/nfsd/filecache.h
+++ b/fs/nfsd/filecache.h
@@ -45,6 +45,7 @@ struct nfsd_file {
#define NFSD_FILE_REFERENCED (2)
#define NFSD_FILE_GC (3)
#define NFSD_FILE_RECENT (4)
+#define NFSD_FILE_NET_HELD (5)
unsigned long nf_flags;
refcount_t nf_ref;
unsigned char nf_may;
@@ -66,7 +67,7 @@ void nfsd_file_cache_shutdown(void);
int nfsd_file_cache_start_net(struct net *net);
void nfsd_file_cache_shutdown_net(struct net *net);
void nfsd_file_put(struct nfsd_file *nf);
-struct net *nfsd_file_put_local(struct nfsd_file __rcu **nf);
+void nfsd_file_put_local(struct nfsd_file __rcu **nf);
struct nfsd_file *nfsd_file_get(struct nfsd_file *nf);
struct file *nfsd_file_file(struct nfsd_file *nf);
void nfsd_file_close_inode_sync(struct inode *inode);
diff --git a/fs/nfsd/localio.c b/fs/nfsd/localio.c
index c3eb0557b3e1..e3295bae75a4 100644
--- a/fs/nfsd/localio.c
+++ b/fs/nfsd/localio.c
@@ -40,8 +40,8 @@
* avoid all the NFS overhead with reads, writes and commits.
*
* On successful return, returned nfsd_file will have its nf_net member
- * set. Caller (NFS client) is responsible for calling nfsd_net_put and
- * nfsd_file_put (via nfs_to_nfsd_file_put_local).
+ * set. Caller (NFS client) is responsible for calling nfsd_file_put
+ * (via nfs_to_nfsd_file_put_local), which also releases the nfsd_net ref.
*/
static struct nfsd_file *
nfsd_open_local_fh(struct net *net, struct auth_domain *dom,
diff --git a/include/linux/nfslocalio.h b/include/linux/nfslocalio.h
index 3d91043254e6..7267a69092d1 100644
--- a/include/linux/nfslocalio.h
+++ b/include/linux/nfslocalio.h
@@ -62,7 +62,7 @@ struct nfsd_localio_operations {
const struct nfs_fh *,
struct nfsd_file __rcu **pnf,
const fmode_t);
- struct net *(*nfsd_file_put_local)(struct nfsd_file __rcu **);
+ void (*nfsd_file_put_local)(struct nfsd_file __rcu **);
struct file *(*nfsd_file_file)(struct nfsd_file *);
void (*nfsd_file_dio_alignment)(struct nfsd_file *,
u32 *, u32 *, u32 *);
@@ -96,12 +96,7 @@ static inline void nfs_to_nfsd_file_put_local(struct nfsd_file __rcu **localio)
* must prevent nfsd shutdown from completing as nfs_close_local_fh()
* does by blocking the nfs_uuid from being finally put.
*/
- struct net *net;
-
- net = nfs_to->nfsd_file_put_local(localio);
-
- if (net)
- nfs_to_nfsd_net_put(net);
+ nfs_to->nfsd_file_put_local(localio);
}
#else /* CONFIG_NFS_LOCALIO */
--
2.54.0