[PATCH 7/9] SUNRPC: Copy the TLS session tags when they are available

Chuck Lever cel at kernel.org
Fri Jun 5 10:34:41 PDT 2026


From: Chuck Lever <chuck.lever at oracle.com>

When a server handshake completes successfully, tlshd might provide
a set of TLS session tags. SUNRPC can save these within the svc_xprt;
NFSD can later use them to authorize or reject operations that target
NFS exports that have a similar set of tags associated with them.
A second handshake on the same transport would destroy the saved
tags while other workers read them, so svcauth_tls_accept() now
refuses AUTH_TLS on a transport that already carries a TLS session,
and svc_tcp_handshake() rechecks the session flag under XPT_BUSY to
close the race with a handshake that completes concurrently.

Signed-off-by: Chuck Lever <chuck.lever at oracle.com>
---
 include/linux/sunrpc/svc_xprt.h |  2 ++
 net/sunrpc/svc_xprt.c           | 11 ++++++++---
 net/sunrpc/svcauth_unix.c       | 12 ++++++++++++
 net/sunrpc/svcsock.c            | 33 ++++++++++++++++++++++++++++++++-
 4 files changed, 54 insertions(+), 4 deletions(-)

diff --git a/include/linux/sunrpc/svc_xprt.h b/include/linux/sunrpc/svc_xprt.h
index da2a2531e110..15f678d00876 100644
--- a/include/linux/sunrpc/svc_xprt.h
+++ b/include/linux/sunrpc/svc_xprt.h
@@ -9,6 +9,7 @@
 #define SUNRPC_SVC_XPRT_H
 
 #include <linux/sunrpc/svc.h>
+#include <linux/tagset.h>
 
 struct module;
 
@@ -79,6 +80,7 @@ struct svc_xprt {
 	const struct cred	*xpt_cred;
 	struct rpc_xprt		*xpt_bc_xprt;	/* NFSv4.1 backchannel */
 	struct rpc_xprt_switch	*xpt_bc_xps;	/* NFSv4.1 backchannel */
+	struct tagset		xpt_handshake_tags;	/* TLS session tags */
 };
 
 /* flag bits for xpt_flags */
diff --git a/net/sunrpc/svc_xprt.c b/net/sunrpc/svc_xprt.c
index 63d1002e63e7..1638fc09db8b 100644
--- a/net/sunrpc/svc_xprt.c
+++ b/net/sunrpc/svc_xprt.c
@@ -168,6 +168,8 @@ static void svc_xprt_free(struct kref *kref)
 	struct svc_xprt *xprt =
 		container_of(kref, struct svc_xprt, xpt_ref);
 	struct module *owner = xprt->xpt_class->xcl_owner;
+
+	tagset_destroy(&xprt->xpt_handshake_tags);
 	if (test_bit(XPT_CACHE_AUTH, &xprt->xpt_flags))
 		svcauth_unix_info_release(xprt);
 	put_cred(xprt->xpt_cred);
@@ -188,9 +190,12 @@ void svc_xprt_put(struct svc_xprt *xprt)
 }
 EXPORT_SYMBOL_GPL(svc_xprt_put);
 
-/*
- * Called by transport drivers to initialize the transport independent
- * portion of the transport instance.
+/**
+ * svc_xprt_init - initialize transport-independent fields of an xprt
+ * @net: Network namespace
+ * @xcl: Transport class
+ * @xprt: Transport to be initialized
+ * @serv: RPC service
  */
 void svc_xprt_init(struct net *net, struct svc_xprt_class *xcl,
 		   struct svc_xprt *xprt, struct svc_serv *serv)
diff --git a/net/sunrpc/svcauth_unix.c b/net/sunrpc/svcauth_unix.c
index 64a2658faddb..7a779e773107 100644
--- a/net/sunrpc/svcauth_unix.c
+++ b/net/sunrpc/svcauth_unix.c
@@ -1129,6 +1129,18 @@ svcauth_tls_accept(struct svc_rqst *rqstp)
 		return SVC_DENIED;
 	}
 
+	/*
+	 * AUTH_TLS initiates a handshake. Refuse it on a transport
+	 * that already has a TLS session: a second handshake would
+	 * destroy xpt_handshake_tags. This test can pass before a
+	 * concurrent handshake completes; svc_tcp_handshake()
+	 * rechecks under XPT_BUSY before destroying the tags.
+	 */
+	if (test_bit(XPT_TLS_SESSION, &xprt->xpt_flags)) {
+		rqstp->rq_auth_stat = rpc_autherr_badcred;
+		return SVC_DENIED;
+	}
+
 	/* Signal that mapping to nobody uid/gid is required */
 	cred->cr_uid = INVALID_UID;
 	cred->cr_gid = INVALID_GID;
diff --git a/net/sunrpc/svcsock.c b/net/sunrpc/svcsock.c
index b4ad84910687..cc06ed3075db 100644
--- a/net/sunrpc/svcsock.c
+++ b/net/sunrpc/svcsock.c
@@ -470,7 +470,18 @@ static void svc_tcp_handshake_done(void *data, int status, key_serial_t peerid,
 	if (!status) {
 		if (peerid != TLS_NO_PEERID)
 			set_bit(XPT_PEER_AUTH, &xprt->xpt_flags);
-		set_bit(XPT_TLS_SESSION, &xprt->xpt_flags);
+		/*
+		 * Leaving XPT_TLS_SESSION clear on copy failure makes
+		 * svc_tcp_handshake() close the connection. The tags
+		 * cannot be recovered later on this transport because
+		 * a second handshake is refused once a session is
+		 * established; a reconnect retries both the handshake
+		 * and the copy.
+		 */
+		if (tagset_copy(&xprt->xpt_handshake_tags, tags, GFP_KERNEL))
+			set_bit(XPT_TLS_SESSION, &xprt->xpt_flags);
+		else
+			pr_warn_ratelimited("svc: failed to copy TLS session tags\n");
 	}
 	clear_bit(XPT_HANDSHAKE, &xprt->xpt_flags);
 	complete_all(&svsk->sk_handshake_done);
@@ -481,6 +492,9 @@ static void svc_tcp_handshake_done(void *data, int status, key_serial_t peerid,
  * svc_tcp_handshake - Perform a transport-layer security handshake
  * @xprt: connected transport endpoint
  *
+ * If the transport already has a TLS session, the handshake request
+ * is declined: a fresh handshake would destroy the saved session
+ * tags.
  */
 static void svc_tcp_handshake(struct svc_xprt *xprt)
 {
@@ -493,8 +507,25 @@ static void svc_tcp_handshake(struct svc_xprt *xprt)
 	};
 	int ret;
 
+	/*
+	 * The XPT_TLS_SESSION test in svcauth_tls_accept() is not
+	 * race-free: a worker can pass it before a concurrent
+	 * handshake completes and raise XPT_HANDSHAKE afterwards.
+	 * XPT_BUSY serializes handshake starts, so this test cannot
+	 * go stale: a set bit here means an established session
+	 * whose tags other workers may be reading. Decline to start
+	 * a handshake that would destroy them.
+	 */
+	if (test_bit(XPT_TLS_SESSION, &xprt->xpt_flags)) {
+		clear_bit(XPT_HANDSHAKE, &xprt->xpt_flags);
+		set_bit(XPT_DATA, &xprt->xpt_flags);
+		svc_xprt_enqueue(xprt);
+		return;
+	}
+
 	trace_svc_tls_upcall(xprt);
 
+	tagset_destroy(&xprt->xpt_handshake_tags);
 	clear_bit(XPT_TLS_SESSION, &xprt->xpt_flags);
 	init_completion(&svsk->sk_handshake_done);
 

-- 
2.54.0




More information about the Linux-nvme mailing list