* [PATCH v2] nfs/localio: pin clients during global invalidation
@ 2026-09-30 5:50 Jinpyo Lee
2026-10-05 15:56 ` Chuck Lever
0 siblings, 1 reply; 5+ messages in thread
From: Jinpyo Lee @ 2026-09-30 5:50 UTC (permalink / raw)
To: linux-nfs
Cc: Trond Myklebust, Anna Schumaker, Chuck Lever, Jeff Layton,
NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey, bobtobabz,
Jinpyo Lee
nfs_localio_invalidate_clients() moves UUID nodes embedded in nfs_client
objects to a private list, drops the namespace list lock, and walks the
private list without holding references on the clients that own the nodes.
Moving an embedded list node does not retain its containing object.
Concurrent client teardown can therefore drop the final cl_count reference
after the splice. List iteration can preload the next UUID node before
processing the current entry, allowing that node's nfs_client to be freed
before the iterator advances to it. Generic KASAN reported an eight-byte
use-after-free read from a freed nfs_client allocation in
nfs_localio_invalidate_clients().
Process one UUID at a time under the namespace list lock. Acquire a
cl_count reference before releasing the lock, disable LOCALIO, and then
drop the reference with nfs_put_client(). If the reference cannot be
acquired, yield and retry so final teardown can remove the dying client
from the list.
Expose the existing nfs_put_client() declaration in a public NFS header and
remove its duplicate declaration from the private NFS header.
The reproducer creates 16 clients, populates their LOCALIO state, and races
administrator-driven global file-cache invalidation with normal client
teardown. It demonstrates the lifetime error, but does not establish
controlled reuse, privilege escalation, or an unprivileged end-to-end
trigger.
On the current nfsd-testing head, the unpatched KASAN kernel reproduced
the use-after-free in nfs_localio_invalidate_clients(). With only this
patch applied, the same race completed without a KASAN report. A separate
run that kept all 16 clients live recorded one LOCALIO disable event for
each client.
Basic NFSv4.2 and NFSv3 read, write, and unmount smoke tests also passed. A
full x86_64 kernel and modules build with GCC 13.3 and CONFIG_WERROR=y
completed without warnings. A source reproducer and complete logs are
available privately on request.
The vulnerability research and validation were conducted by members of the
Tobabz team as part of the Best of the Best 15th program.
Fixes: 085804110aa1 ("nfs_common: track all open nfsd_files per LOCALIO nfs_client")
Assisted-by: LLM
Signed-off-by: Jinpyo Lee <bint4b13@gmail.com>
---
Changes in v2:
- Move the nfs_put_client() declaration to a public NFS header instead of
duplicating it.
- Make the commit message self-contained and document current-tree runtime
validation.
- Correct the author identity and DCO sign-off.
fs/nfs/internal.h | 1 -
fs/nfs_common/nfslocalio.c | 27 ++++++++++++++++++++-------
include/linux/nfs_fs.h | 2 ++
3 files changed, 22 insertions(+), 8 deletions(-)
diff --git a/fs/nfs/internal.h b/fs/nfs/internal.h
index abc81f5ae5780..c377075a8b5b5 100644
--- a/fs/nfs/internal.h
+++ b/fs/nfs/internal.h
@@ -223,7 +223,6 @@ int nfs_init_server_rpcclient(struct nfs_server *, const struct rpc_timeout *t,
struct nfs_server *nfs_alloc_server(void);
void nfs_server_copy_userdata(struct nfs_server *, struct nfs_server *);
-extern void nfs_put_client(struct nfs_client *);
extern void nfs_free_client(struct nfs_client *);
void nfs_cb_idr_remove(struct nfs_client *clp);
extern struct nfs_client *nfs4_find_client_ident(struct net *, int);
diff --git a/fs/nfs_common/nfslocalio.c b/fs/nfs_common/nfslocalio.c
index 85aa03a7b020c..00fcba467ee8d 100644
--- a/fs/nfs_common/nfslocalio.c
+++ b/fs/nfs_common/nfslocalio.c
@@ -226,18 +226,31 @@ EXPORT_SYMBOL_GPL(nfs_localio_disable_client);
void nfs_localio_invalidate_clients(struct list_head *nn_local_clients,
spinlock_t *nn_local_clients_lock)
{
- LIST_HEAD(local_clients);
- nfs_uuid_t *nfs_uuid, *tmp;
+ nfs_uuid_t *nfs_uuid;
struct nfs_client *clp;
- spin_lock(nn_local_clients_lock);
- list_splice_init(nn_local_clients, &local_clients);
- spin_unlock(nn_local_clients_lock);
- list_for_each_entry_safe(nfs_uuid, tmp, &local_clients, list) {
- if (WARN_ON(nfs_uuid->list_lock != nn_local_clients_lock))
+ for (;;) {
+ spin_lock(nn_local_clients_lock);
+ nfs_uuid = list_first_entry_or_null(nn_local_clients,
+ nfs_uuid_t, list);
+ if (!nfs_uuid) {
+ spin_unlock(nn_local_clients_lock);
+ break;
+ }
+ if (WARN_ON(nfs_uuid->list_lock != nn_local_clients_lock)) {
+ spin_unlock(nn_local_clients_lock);
break;
+ }
clp = container_of(nfs_uuid, struct nfs_client, cl_uuid);
+ if (!refcount_inc_not_zero(&clp->cl_count)) {
+ spin_unlock(nn_local_clients_lock);
+ cond_resched();
+ continue;
+ }
+ spin_unlock(nn_local_clients_lock);
+
nfs_localio_disable_client(clp);
+ nfs_put_client(clp);
}
}
EXPORT_SYMBOL_GPL(nfs_localio_invalidate_clients);
diff --git a/include/linux/nfs_fs.h b/include/linux/nfs_fs.h
index b85a73ae7919f..8c1e2d2027c62 100644
--- a/include/linux/nfs_fs.h
+++ b/include/linux/nfs_fs.h
@@ -84,6 +84,8 @@ struct nfs_file_localio {
void __rcu *nfs_uuid; /* opaque pointer to 'nfs_uuid_t' */
};
+void nfs_put_client(struct nfs_client *clp);
+
static inline void nfs_localio_file_init(struct nfs_file_localio *nfl)
{
#if IS_ENABLED(CONFIG_NFS_LOCALIO)
--
2.43.0
^ permalink raw reply related [flat|nested] 5+ messages in thread
* Re: [PATCH v2] nfs/localio: pin clients during global invalidation
2026-09-30 5:50 [PATCH v2] nfs/localio: pin clients during global invalidation Jinpyo Lee
@ 2026-10-05 15:56 ` Chuck Lever
2026-10-12 11:27 ` [PATCH v3] nfs/localio: pin UUIDs " Jinpyo Lee
0 siblings, 1 reply; 5+ messages in thread
From: Chuck Lever @ 2026-10-05 15:56 UTC (permalink / raw)
To: Jinpyo Lee
Cc: linux-nfs, Trond Myklebust, Anna Schumaker, Jeff Layton,
NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey, bobtobabz
On Wed, Sep 30, 2026 at 02:50:53PM +0900, Jinpyo Lee wrote:
> Process one UUID at a time under the namespace list lock. Acquire a
> cl_count reference before releasing the lock, disable LOCALIO, and then
> drop the reference with nfs_put_client(). If the reference cannot be
> acquired, yield and retry so final teardown can remove the dying client
> from the list.
The reported use-after-free is confirmed.
> A full x86_64 kernel and modules build with GCC 13.3 and
> CONFIG_WERROR=y completed without warnings.
How was NFS_FS set for that build?
> + spin_unlock(nn_local_clients_lock);
> +
> nfs_localio_disable_client(clp);
> + nfs_put_client(clp);
nfs_put_client() is exported from nfs.ko. This call makes
nfs_localio depend on nfs.ko, and nfs.ko already depends on
nfs_localio for nfs_uuid_init(), nfs_uuid_begin(), nfs_uuid_end(),
and nfs_open_local_fh().
With NFS_FS=m and NFSD=m, NFS_COMMON_LOCALIO_SUPPORT is also "m", so
the two modules now require each other and neither can be loaded
first. With NFS_FS=m and NFSD=y, nfs_localio is built in and
references a symbol that lives in a module, so vmlinux cannot link.
Only NFS_FS=y avoids both.
nfs_common cannot call into nfs.ko. The pin has to be something
nfs_common owns, or the put has to go through a callback that the
NFS client provides.
Another issue: If client teardown drops its reference while this
loop holds the temporary one, the put above is the final one, and
nfs_free_client() or nfs4_free_client() then runs in the caller's
context. The callers here are the export flush path, NFSD file
cache shutdown, and nfsd_net_pre_exit(). For NFSv4 that means
nfs4_shutdown_client() and the callback teardown run while NFSD
is flushing or shutting down.
A pin that does not hold cl_count would avoid this as well as the
module dependency.
> + for (;;) {
> + spin_lock(nn_local_clients_lock);
> + nfs_uuid = list_first_entry_or_null(nn_local_clients,
> + nfs_uuid_t, list);
This assumes nfs_localio_disable_client() always removes the head
entry from the list. It does not when another thread is already in
nfs_uuid_put() for that client.
nfs_uuid_put() clears ->net first, then drops nfs_uuid->lock around
each nfs_to_nfsd_file_put_local() call, and unlinks the uuid from
nn->local_clients only after the last file is closed. While that
thread is closing files, this loop finds the same uuid at the head
of the list, takes a reference, gets "false" back from
nfs_uuid_put() because ->net is already NULL, drops the reference,
and goes around again. That path has no cond_resched(), so it spins
until the other thread finishes. The other thread can sleep in
nfs_to_nfsd_file_put_local() or in wait_var_event_spinlock().
The splice in the old code moved such an entry off the list, so
the walk visited it once and went on. The new loop needs a way to
get past an entry that someone else is already disabling.
--
Chuck Lever (Come to NFS bake-a-thon! https://nfsv4bat.org)
^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH v3] nfs/localio: pin UUIDs during global invalidation
2026-10-05 15:56 ` Chuck Lever
@ 2026-10-12 11:27 ` Jinpyo Lee
2026-10-12 17:14 ` Chuck Lever
0 siblings, 1 reply; 5+ messages in thread
From: Jinpyo Lee @ 2026-10-12 11:27 UTC (permalink / raw)
To: linux-nfs
Cc: Trond Myklebust, Anna Schumaker, Chuck Lever, Jeff Layton,
NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey, bobtobabz,
Jinpyo Lee
nfs_localio_invalidate_clients() moves UUID nodes embedded in nfs_client
objects to a private list, then walks that list without retaining the
containing clients. Concurrent client teardown can free an entry that
the iterator has already selected, causing a use-after-free.
Take a reference to each selected UUID under the namespace list lock.
Keep this reference count in nfs_common, independently of cl_count, and
have nfs_free_client() drain temporary UUID users before freeing the
client. Releasing a UUID reference wakes waiting probes and signals
completion when the last reference is dropped. It neither calls into
nfs.ko nor performs final NFS client teardown in the NFSD invalidation
context.
Keep the UUID on the namespace list until its files have been closed.
When another thread is already disabling it, wait for that thread to
finish unlinking it instead of repeatedly selecting the same head entry.
The wait releases the UUID spinlock so file cleanup can make progress.
Append registrations in sequence order and capture the last sequence
under the namespace list lock at the start of invalidation. Stop at that
boundary so new registrations cannot indefinitely extend the sweep,
even if its original last entry is removed concurrently.
Publish the LOCALIO association under the UUID and namespace list locks
before making the entry visible to invalidation. A temporary UUID
reference allows the handshake to drop the global lookup lock before
taking the UUID lock in the documented order; recheck the lookup in case
the probe was withdrawn. A new probe waits for temporary UUID users to
drain, then rechecks the association before reusing the UUID. This keeps
a transient invalidation reference from discarding the client's LOCALIO
revalidation attempt. Withdraw pending probes before dropping the owner's
UUID reference.
A deterministic same-base KASAN reproducer triggered the reported
use-after-free on the unpatched tree. With this patch, the same controlled
invalidation and teardown race completed without the target KASAN report
or another selected kernel diagnostic. A separate all-live run recorded
disable events for all 16 unique clients.
Test-only synchronization hooks verified that a second disabler waits for
unlink after ->net is cleared and that client teardown waits for a
temporary UUID reference to drain. NFSv4.2 and NFSv3 read, write, and
unmount smoke tests, export-flush and I/O stress, NFSD restarts, network
namespace lifecycle tests, and module unloads passed with KASAN and
lockdep enabled.
A full x86_64 kernel and modules build with GCC 13.3, CONFIG_WERROR=y,
NFS_FS=m, NFSD=m, and LOCALIO enabled completed without diagnostics.
Module metadata showed that nfs_localio.ko depends only on sunrpc, while
nfs.ko depends on lockd, nfs_localio, and sunrpc.
A separate NFS_FS=m, NFSD=y build also linked vmlinux successfully.
The vulnerability research and validation were conducted by members of
the Tobabz team as part of the Best of the Best 15th program.
Fixes: 085804110aa1 ("nfs_common: track all open nfsd_files per LOCALIO nfs_client")
Assisted-by: LLM
Signed-off-by: Jinpyo Lee <bint4b13@gmail.com>
---
Changes in v3:
- Replace cl_count pinning with UUID references owned by nfs_common and
drained by the original NFS client teardown path.
- Wait for concurrent disable to finish unlinking the UUID, and bound
each sweep to registrations present when invalidation begins.
- Serialize association publication and preserve reprobes by waiting
for temporary UUID references before rechecking association state.
- Add same-base KASAN, runtime, lifecycle, and module dependency
validation on the latest nfsd-testing revision.
fs/nfs/client.c | 2 +-
fs/nfs_common/nfslocalio.c | 101 +++++++++++++++++++++++++++++++------
include/linux/nfslocalio.h | 12 +++++
3 files changed, 98 insertions(+), 17 deletions(-)
diff --git a/fs/nfs/client.c b/fs/nfs/client.c
index 60386330aeec..d85711edecbf 100644
--- a/fs/nfs/client.c
+++ b/fs/nfs/client.c
@@ -256,7 +256,7 @@ static void pnfs_init_server(struct nfs_server *server)
*/
void nfs_free_client(struct nfs_client *clp)
{
- nfs_localio_disable_client(clp);
+ nfs_localio_free_client(clp);
/* -EIO all pending I/O */
if (!IS_ERR(clp->cl_rpcclient))
diff --git a/fs/nfs_common/nfslocalio.c b/fs/nfs_common/nfslocalio.c
index e0a4273ae341..b2b8d7c3d93e 100644
--- a/fs/nfs_common/nfslocalio.c
+++ b/fs/nfs_common/nfslocalio.c
@@ -24,6 +24,26 @@ static DEFINE_SPINLOCK(nfs_uuids_lock);
* that is protected by nfs_uuids_lock.
*/
static LIST_HEAD(nfs_uuids);
+/* Namespace registration order, protected by nfs_uuids_lock. */
+static u64 nfs_uuid_seq;
+
+/* The owner waits for temporary users before freeing its nfs_client. */
+static void nfs_uuid_get(nfs_uuid_t *nfs_uuid)
+{
+ refcount_inc(&nfs_uuid->refs);
+}
+
+static void nfs_uuid_release(nfs_uuid_t *nfs_uuid)
+{
+ bool released;
+
+ spin_lock(&nfs_uuid->lock);
+ released = refcount_dec_and_test(&nfs_uuid->refs);
+ wake_up_var_locked(&nfs_uuid->refs, &nfs_uuid->lock);
+ spin_unlock(&nfs_uuid->lock);
+ if (released)
+ complete(&nfs_uuid->released);
+}
/*
* Lock ordering:
@@ -43,6 +63,8 @@ void nfs_uuid_init(nfs_uuid_t *nfs_uuid)
INIT_LIST_HEAD(&nfs_uuid->list);
INIT_LIST_HEAD(&nfs_uuid->files);
spin_lock_init(&nfs_uuid->lock);
+ refcount_set(&nfs_uuid->refs, 1);
+ init_completion(&nfs_uuid->released);
nfs_uuid->nfs3_localio_probe_count = 0;
}
EXPORT_SYMBOL_GPL(nfs_uuid_init);
@@ -50,7 +72,12 @@ EXPORT_SYMBOL_GPL(nfs_uuid_init);
bool nfs_uuid_begin(nfs_uuid_t *nfs_uuid)
{
spin_lock(&nfs_uuid->lock);
- if (rcu_access_pointer(nfs_uuid->net)) {
+ /* The caller retains the client while temporary UUID users drain. */
+ wait_var_event_spinlock(&nfs_uuid->refs,
+ refcount_read(&nfs_uuid->refs) <= 1,
+ &nfs_uuid->lock);
+ if (rcu_access_pointer(nfs_uuid->net) || nfs_uuid->list_lock ||
+ refcount_read(&nfs_uuid->refs) != 1) {
/* This nfs_uuid is already in use */
spin_unlock(&nfs_uuid->lock);
return false;
@@ -77,7 +104,7 @@ void nfs_uuid_end(nfs_uuid_t *nfs_uuid)
{
if (!rcu_access_pointer(nfs_uuid->net)) {
spin_lock(&nfs_uuid->lock);
- if (!rcu_access_pointer(nfs_uuid->net)) {
+ if (!rcu_access_pointer(nfs_uuid->net) && !nfs_uuid->list_lock) {
/* Not local, remove from nfs_uuids */
spin_lock(&nfs_uuids_lock);
list_del_init(&nfs_uuid->list);
@@ -113,28 +140,37 @@ void nfs_uuid_is_local(const uuid_t *uuid, struct list_head *list,
spin_unlock(&nfs_uuids_lock);
return;
}
+ /* Drop the lookup lock before taking nfs_uuid->lock. */
+ nfs_uuid_get(nfs_uuid);
+ spin_unlock(&nfs_uuids_lock);
+
+ spin_lock(&nfs_uuid->lock);
+ spin_lock(&nfs_uuids_lock);
+ /* nfs_uuid_end() may have withdrawn the probe in the meantime. */
+ if (nfs_uuid_lookup_locked(uuid) != nfs_uuid)
+ goto out_unlock;
/*
* We don't hold a ref on the net, but instead put
* ourselves on @list (nn->local_clients) so the net
* pointer can be invalidated.
*/
- spin_lock(list_lock); /* list_lock is nn->local_clients_lock */
- list_move(&nfs_uuid->list, list);
- spin_unlock(list_lock);
-
- spin_unlock(&nfs_uuids_lock);
- /* Once nfs_uuid is parented to @list, avoid global nfs_uuids_lock */
- spin_lock(&nfs_uuid->lock);
-
__module_get(mod);
nfsd_mod = mod;
+ spin_lock(list_lock); /* list_lock is nn->local_clients_lock */
nfs_uuid->list_lock = list_lock;
kref_get(&dom->ref);
nfs_uuid->dom = dom;
rcu_assign_pointer(nfs_uuid->net, net);
+ nfs_uuid->seq = ++nfs_uuid_seq;
+ list_move_tail(&nfs_uuid->list, list);
+ spin_unlock(list_lock);
+
+out_unlock:
+ spin_unlock(&nfs_uuids_lock);
spin_unlock(&nfs_uuid->lock);
+ nfs_uuid_release(nfs_uuid);
}
EXPORT_SYMBOL_GPL(nfs_uuid_is_local);
@@ -154,6 +190,10 @@ static bool nfs_uuid_put(nfs_uuid_t *nfs_uuid)
struct nfs_file_localio *nfl;
spin_lock(&nfs_uuid->lock);
+ /* A concurrent disabler can drop the lock while closing files. */
+ wait_var_event_spinlock(&nfs_uuid->list_lock,
+ rcu_access_pointer(nfs_uuid->net) ||
+ !nfs_uuid->list_lock, &nfs_uuid->lock);
if (unlikely(!rcu_access_pointer(nfs_uuid->net))) {
spin_unlock(&nfs_uuid->lock);
return false;
@@ -211,6 +251,7 @@ static bool nfs_uuid_put(nfs_uuid_t *nfs_uuid)
}
module_put(nfsd_mod);
+ wake_up_var_locked(&nfs_uuid->list_lock, &nfs_uuid->lock);
spin_unlock(&nfs_uuid->lock);
return true;
@@ -223,22 +264,50 @@ void nfs_localio_disable_client(struct nfs_client *clp)
}
EXPORT_SYMBOL_GPL(nfs_localio_disable_client);
+void nfs_localio_free_client(struct nfs_client *clp)
+{
+ nfs_uuid_t *nfs_uuid = &clp->cl_uuid;
+
+ nfs_uuid_end(nfs_uuid);
+ nfs_localio_disable_client(clp);
+ /* Unlink before dropping the owner reference, then drain all users. */
+ nfs_uuid_release(nfs_uuid);
+ wait_for_completion(&nfs_uuid->released);
+}
+EXPORT_SYMBOL_GPL(nfs_localio_free_client);
+
void nfs_localio_invalidate_clients(struct list_head *nn_local_clients,
spinlock_t *nn_local_clients_lock)
{
- LIST_HEAD(local_clients);
- nfs_uuid_t *nfs_uuid, *tmp;
+ nfs_uuid_t *nfs_uuid;
struct nfs_client *clp;
+ u64 last_seq;
spin_lock(nn_local_clients_lock);
- list_splice_init(nn_local_clients, &local_clients);
- spin_unlock(nn_local_clients_lock);
- list_for_each_entry_safe(nfs_uuid, tmp, &local_clients, list) {
- if (WARN_ON(nfs_uuid->list_lock != nn_local_clients_lock))
+ if (list_empty(nn_local_clients))
+ goto out_unlock;
+ /* New registrations go to the tail and belong to a later sweep. */
+ last_seq = list_last_entry(nn_local_clients, nfs_uuid_t, list)->seq;
+ for (;;) {
+ nfs_uuid = list_first_entry_or_null(nn_local_clients,
+ nfs_uuid_t, list);
+ /* Use a signed difference to allow the sequence to wrap. */
+ if (!nfs_uuid || (s64)(nfs_uuid->seq - last_seq) > 0)
break;
+ if (WARN_ON_ONCE(nfs_uuid->list_lock != nn_local_clients_lock))
+ break;
+ /* Teardown unlinks under this lock before dropping its reference. */
+ nfs_uuid_get(nfs_uuid);
+ spin_unlock(nn_local_clients_lock);
+
clp = container_of(nfs_uuid, struct nfs_client, cl_uuid);
nfs_localio_disable_client(clp);
+ nfs_uuid_release(nfs_uuid);
+ cond_resched();
+ spin_lock(nn_local_clients_lock);
}
+out_unlock:
+ spin_unlock(nn_local_clients_lock);
}
EXPORT_SYMBOL_GPL(nfs_localio_invalidate_clients);
diff --git a/include/linux/nfslocalio.h b/include/linux/nfslocalio.h
index 8ce4d978a636..914246e3f342 100644
--- a/include/linux/nfslocalio.h
+++ b/include/linux/nfslocalio.h
@@ -8,8 +8,10 @@
#if IS_ENABLED(CONFIG_NFS_LOCALIO)
+#include <linux/completion.h>
#include <linux/module.h>
#include <linux/list.h>
+#include <linux/refcount.h>
#include <linux/uuid.h>
#include <linux/sunrpc/clnt.h>
#include <linux/sunrpc/svcauth.h>
@@ -37,6 +39,11 @@ struct nfs_file_localio;
typedef struct {
uuid_t uuid;
unsigned nfs3_localio_probe_count;
+ /* Owner reference plus temporary references held by nfs_common */
+ refcount_t refs;
+ struct completion released;
+ /* Namespace registration order, protected by list_lock */
+ u64 seq;
/* this struct is over a cacheline, avoid bouncing */
spinlock_t ____cacheline_aligned lock;
struct list_head list;
@@ -55,6 +62,7 @@ void nfs_uuid_is_local(const uuid_t *, struct list_head *, spinlock_t *,
void nfs_localio_enable_client(struct nfs_client *clp);
void nfs_localio_disable_client(struct nfs_client *clp);
+void nfs_localio_free_client(struct nfs_client *clp);
void nfs_localio_invalidate_clients(struct list_head *nn_local_clients,
spinlock_t *nn_local_clients_lock);
@@ -127,6 +135,10 @@ static inline void nfs_localio_disable_client(struct nfs_client *clp)
{
}
+static inline void nfs_localio_free_client(struct nfs_client *clp)
+{
+}
+
#endif /* CONFIG_NFS_LOCALIO */
#endif /* __LINUX_NFSLOCALIO_H */
base-commit: f27ad2d5441154f0178599c8ab2b1166acc543b2
--
2.39.5
^ permalink raw reply related [flat|nested] 5+ messages in thread
* Re: [PATCH v3] nfs/localio: pin UUIDs during global invalidation
2026-10-12 11:27 ` [PATCH v3] nfs/localio: pin UUIDs " Jinpyo Lee
@ 2026-10-12 17:14 ` Chuck Lever
2026-10-12 18:23 ` Anna Schumaker
0 siblings, 1 reply; 5+ messages in thread
From: Chuck Lever @ 2026-10-12 17:14 UTC (permalink / raw)
To: Jinpyo Lee, Trond Myklebust, Anna Schumaker
Cc: Jeff Layton, NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey,
BoB_Tobabz, linux-nfs
On Mon, Oct 12, 2026, at 7:27 AM, Jinpyo Lee wrote:
> nfs_localio_invalidate_clients() moves UUID nodes embedded in nfs_client
> objects to a private list, then walks that list without retaining the
> containing clients. Concurrent client teardown can free an entry that
> the iterator has already selected, causing a use-after-free.
>
> Take a reference to each selected UUID under the namespace list lock.
> Keep this reference count in nfs_common, independently of cl_count, and
> have nfs_free_client() drain temporary UUID users before freeing the
> client. Releasing a UUID reference wakes waiting probes and signals
> completion when the last reference is dropped. It neither calls into
> nfs.ko nor performs final NFS client teardown in the NFSD invalidation
> context.
Until someone tells me otherwise, I will treat this patch as going through
the NFS client tree(s).
--
Chuck Lever (Come to NFS bake-a-thon! https://nfsv4bat.org)
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH v3] nfs/localio: pin UUIDs during global invalidation
2026-10-12 17:14 ` Chuck Lever
@ 2026-10-12 18:23 ` Anna Schumaker
0 siblings, 0 replies; 5+ messages in thread
From: Anna Schumaker @ 2026-10-12 18:23 UTC (permalink / raw)
To: Chuck Lever, Jinpyo Lee, Trond Myklebust
Cc: Jeff Layton, NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey,
BoB_Tobabz, linux-nfs
On Mon, Oct 12, 2026, at 1:14 PM, Chuck Lever wrote:
> On Mon, Oct 12, 2026, at 7:27 AM, Jinpyo Lee wrote:
>> nfs_localio_invalidate_clients() moves UUID nodes embedded in nfs_client
>> objects to a private list, then walks that list without retaining the
>> containing clients. Concurrent client teardown can free an entry that
>> the iterator has already selected, causing a use-after-free.
>>
>> Take a reference to each selected UUID under the namespace list lock.
>> Keep this reference count in nfs_common, independently of cl_count, and
>> have nfs_free_client() drain temporary UUID users before freeing the
>> client. Releasing a UUID reference wakes waiting probes and signals
>> completion when the last reference is dropped. It neither calls into
>> nfs.ko nor performs final NFS client teardown in the NFSD invalidation
>> context.
>
> Until someone tells me otherwise, I will treat this patch as going through
> the NFS client tree(s).
Yes, most likely. I still need to look over this version of the patch.
Anna
>
>
> --
> Chuck Lever (Come to NFS bake-a-thon! https://nfsv4bat.org)
^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2026-10-12 18:23 UTC | newest]
Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-30 5:50 [PATCH v2] nfs/localio: pin clients during global invalidation Jinpyo Lee
2026-10-05 15:56 ` Chuck Lever
2026-10-12 11:27 ` [PATCH v3] nfs/localio: pin UUIDs " Jinpyo Lee
2026-10-12 17:14 ` Chuck Lever
2026-10-12 18:23 ` Anna Schumaker
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.