Linux NFS development
 help / color / mirror / Atom feed
From: Jinpyo Lee <bint4b13@gmail.com>
To: linux-nfs@vger.kernel.org
Cc: Trond Myklebust <trondmy@kernel.org>,
	Anna Schumaker <anna@kernel.org>, Chuck Lever <cel@kernel.org>,
	Jeff Layton <jlayton@kernel.org>, NeilBrown <neil@brown.name>,
	Olga Kornievskaia <okorniev@redhat.com>,
	Dai Ngo <Dai.Ngo@oracle.com>, Tom Talpey <tom@talpey.com>,
	bobtobabz@gmail.com, Jinpyo Lee <bint4b13@gmail.com>
Subject: [PATCH v2] nfs/localio: pin clients during global invalidation
Date: Wed, 30 Sep 2026 14:50:53 +0900	[thread overview]
Message-ID: <20260930055053.134716-1-bint4b13@gmail.com> (raw)

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

             reply	other threads:[~2026-09-30  5:51 UTC|newest]

Thread overview: 2+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-09-30  5:50 Jinpyo Lee [this message]
2026-10-05 15:56 ` [PATCH v2] nfs/localio: pin clients during global invalidation Chuck Lever

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=20260930055053.134716-1-bint4b13@gmail.com \
    --to=bint4b13@gmail.com \
    --cc=Dai.Ngo@oracle.com \
    --cc=anna@kernel.org \
    --cc=bobtobabz@gmail.com \
    --cc=cel@kernel.org \
    --cc=jlayton@kernel.org \
    --cc=linux-nfs@vger.kernel.org \
    --cc=neil@brown.name \
    --cc=okorniev@redhat.com \
    --cc=tom@talpey.com \
    --cc=trondmy@kernel.org \
    /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