* [PATCH v2 1/8] SUNRPC: do not credit control-record octets to the RPC stream
2026-08-06 20:20 [PATCH v2 0/8] SUNRPC: Fix TLS control record handling Chuck Lever
@ 2026-08-06 20:20 ` Chuck Lever
2026-08-06 20:20 ` [PATCH v2 2/8] SUNRPC: reject a TLS alert record that is not two octets Chuck Lever
` (6 subsequent siblings)
7 siblings, 0 replies; 9+ messages in thread
From: Chuck Lever @ 2026-08-06 20:20 UTC (permalink / raw)
To: Trond Myklebust, Anna Schumaker, Chuck Lever, Jeff Layton,
NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey
Cc: linux-nfs, netdev
svc_tcp_sock_recv_cmsg() receives up to two octets into a local
buffer, and returns that count for any record type other than
TLS_RECORD_TYPE_ALERT. Nothing reached the caller's buffer, but
svc_tcp_read_marker() adds the count to sk_tcplen and
svc_tcp_read_msg()'s caller adds it to sk_datalen. The RPC stream
advances over octets it never received. The fragment marker is
assembled from stale sk_marker octets. The message body comes from
pages nothing wrote.
A conforming client reaches this. RFC 8446 Section 4.6.3 lets either
peer send KeyUpdate once it has sent its Finished, and svcsock has no
rekey path. kTLS leaves the partially consumed record on ctx->rx_list,
so the body drains two octets per svc_tcp_recvfrom() call. Each pair
is credited the same way.
Return -EAGAIN for a record that is not an alert. That is what
svc_tcp_sock_process_cmsg()'s default arm returned before the receive
moved into a local buffer.
Fixes: bee47cb026e7 ("sunrpc: fix handling of server side tls alerts")
Signed-off-by: Chuck Lever <cel@kernel.org>
---
net/sunrpc/svcsock.c | 9 +++++++--
1 file changed, 7 insertions(+), 2 deletions(-)
diff --git a/net/sunrpc/svcsock.c b/net/sunrpc/svcsock.c
index 50e5e7f5b762..8e1009302e3b 100644
--- a/net/sunrpc/svcsock.c
+++ b/net/sunrpc/svcsock.c
@@ -289,8 +289,13 @@ svc_tcp_sock_recv_cmsg(struct socket *sock, unsigned int *msg_flags)
iov_iter_kvec(&msg.msg_iter, ITER_DEST, &alert_kvec, 1,
alert_kvec.iov_len);
ret = sock_recvmsg(sock, &msg, MSG_DONTWAIT);
- if (ret > 0 &&
- tls_get_record_type(sock->sk, &u.cmsg) == TLS_RECORD_TYPE_ALERT) {
+ if (ret > 0) {
+ /* Returning the count would credit the RPC stream with
+ * octets that never reached the caller's buffer.
+ */
+ if (tls_get_record_type(sock->sk, &u.cmsg) !=
+ TLS_RECORD_TYPE_ALERT)
+ return -EAGAIN;
iov_iter_revert(&msg.msg_iter, ret);
ret = svc_tcp_sock_process_cmsg(sock, &msg, &u.cmsg, -EAGAIN);
}
--
2.54.0
^ permalink raw reply related [flat|nested] 9+ messages in thread* [PATCH v2 2/8] SUNRPC: reject a TLS alert record that is not two octets
2026-08-06 20:20 [PATCH v2 0/8] SUNRPC: Fix TLS control record handling Chuck Lever
2026-08-06 20:20 ` [PATCH v2 1/8] SUNRPC: do not credit control-record octets to the RPC stream Chuck Lever
@ 2026-08-06 20:20 ` Chuck Lever
2026-08-06 20:20 ` [PATCH v2 3/8] SUNRPC: treat every TLS error alert as fatal Chuck Lever
` (5 subsequent siblings)
7 siblings, 0 replies; 9+ messages in thread
From: Chuck Lever @ 2026-08-06 20:20 UTC (permalink / raw)
To: Trond Myklebust, Anna Schumaker, Chuck Lever, Jeff Layton,
NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey
Cc: linux-nfs, netdev
tls_alert_recv() reads two octets from the kvec it is handed and does
not check the length (net/handshake/alert.c). svc_tcp_sock_recv_cmsg()
calls it for any positive receive, and the alert[] buffer it supplies
carries no initializer. A one-octet alert body leaves the description
read from uninitialized stack and reported through
trace_tls_alert_recv().
The peer controls that length. Neither tls_rx_msg_size() nor
tls_rx_one_record() enforces the two-octet Alert payload. A TLS 1.3
record carrying only the inner content-type octet decrypts to a
zero-length payload. RFC 8446 Section 5.1 requires a record with an
Alert type to carry exactly one message, so any other length is
malformed.
Require exactly two octets before parsing and return -EBADMSG
otherwise. That closes the transport rather than acting on a partly
uninitialized alert. Gate the path on a control message rather than a
positive count so that a zero-length record reaches the check.
Fixes: bee47cb026e7 ("sunrpc: fix handling of server side tls alerts")
Signed-off-by: Chuck Lever <cel@kernel.org>
---
net/sunrpc/svcsock.c | 12 +++++++++++-
1 file changed, 11 insertions(+), 1 deletion(-)
diff --git a/net/sunrpc/svcsock.c b/net/sunrpc/svcsock.c
index 8e1009302e3b..2e5107a1fb89 100644
--- a/net/sunrpc/svcsock.c
+++ b/net/sunrpc/svcsock.c
@@ -289,13 +289,23 @@ svc_tcp_sock_recv_cmsg(struct socket *sock, unsigned int *msg_flags)
iov_iter_kvec(&msg.msg_iter, ITER_DEST, &alert_kvec, 1,
alert_kvec.iov_len);
ret = sock_recvmsg(sock, &msg, MSG_DONTWAIT);
- if (ret > 0) {
+ /* put_cmsg() shrinks msg_controllen, so a short one means
+ * kTLS filled in u.cmsg.
+ */
+ if (ret >= 0 && msg.msg_controllen < sizeof(u)) {
/* Returning the count would credit the RPC stream with
* octets that never reached the caller's buffer.
*/
if (tls_get_record_type(sock->sk, &u.cmsg) !=
TLS_RECORD_TYPE_ALERT)
return -EAGAIN;
+ /* An Alert record carries exactly one two-octet message
+ * (RFC 8446 Section 5.1). alert_kvec caps the receive at two,
+ * so a longer record produces the same count. MSG_EOR appears
+ * only once kTLS has drained the whole record.
+ */
+ if (ret != sizeof(alert) || !(msg.msg_flags & MSG_EOR))
+ return -EBADMSG;
iov_iter_revert(&msg.msg_iter, ret);
ret = svc_tcp_sock_process_cmsg(sock, &msg, &u.cmsg, -EAGAIN);
}
--
2.54.0
^ permalink raw reply related [flat|nested] 9+ messages in thread* [PATCH v2 3/8] SUNRPC: treat every TLS error alert as fatal
2026-08-06 20:20 [PATCH v2 0/8] SUNRPC: Fix TLS control record handling Chuck Lever
2026-08-06 20:20 ` [PATCH v2 1/8] SUNRPC: do not credit control-record octets to the RPC stream Chuck Lever
2026-08-06 20:20 ` [PATCH v2 2/8] SUNRPC: reject a TLS alert record that is not two octets Chuck Lever
@ 2026-08-06 20:20 ` Chuck Lever
2026-08-06 20:20 ` [PATCH v2 4/8] SUNRPC: resume receiving after a TLS control record Chuck Lever
` (4 subsequent siblings)
7 siblings, 0 replies; 9+ messages in thread
From: Chuck Lever @ 2026-08-06 20:20 UTC (permalink / raw)
To: Trond Myklebust, Anna Schumaker, Chuck Lever, Jeff Layton,
NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey
Cc: linux-nfs, netdev
svc_tcp_sock_process_cmsg() decides whether an alert ends the session
by reading the alert's level octet. RFC 8446 Section 6 retired that
field. The severity is implicit in the description, and a receiver
treats every alert listed in Section 6.2 as an error alert
"regardless of the AlertLevel in the message". A peer that aborts
with unexpected_message but leaves the legacy octet set to warning
makes the server return -EAGAIN. svc_tcp_recvfrom() then leaves a
dead TLS session attached to an open transport. NFSD keeps polling
it.
Decide from the alert description instead. close_notify and
user_canceled are the closure alerts (RFC 8446 Section 6.1). Every
other description ends the session, including one this kernel does
not recognize.
Fixes: 39067dda1d86 ("SUNRPC: Use new helpers to handle TLS Alerts")
Signed-off-by: Chuck Lever <cel@kernel.org>
---
net/sunrpc/svcsock.c | 14 ++++++++++++--
1 file changed, 12 insertions(+), 2 deletions(-)
diff --git a/net/sunrpc/svcsock.c b/net/sunrpc/svcsock.c
index 2e5107a1fb89..756db84e4aec 100644
--- a/net/sunrpc/svcsock.c
+++ b/net/sunrpc/svcsock.c
@@ -257,8 +257,18 @@ svc_tcp_sock_process_cmsg(struct socket *sock, struct msghdr *msg,
break;
case TLS_RECORD_TYPE_ALERT:
tls_alert_recv(sock->sk, msg, &level, &description);
- ret = (level == TLS_ALERT_LEVEL_FATAL) ?
- -ENOTCONN : -EAGAIN;
+ /* RFC 8446 Section 6: every alert but a closure alert is
+ * an error alert, whatever the legacy AlertLevel octet
+ * says.
+ */
+ switch (description) {
+ case TLS_ALERT_DESC_CLOSE_NOTIFY:
+ case TLS_ALERT_DESC_USER_CANCELED:
+ ret = -EAGAIN;
+ break;
+ default:
+ ret = -ENOTCONN;
+ }
break;
default:
/* discard this record type */
--
2.54.0
^ permalink raw reply related [flat|nested] 9+ messages in thread* [PATCH v2 4/8] SUNRPC: resume receiving after a TLS control record
2026-08-06 20:20 [PATCH v2 0/8] SUNRPC: Fix TLS control record handling Chuck Lever
` (2 preceding siblings ...)
2026-08-06 20:20 ` [PATCH v2 3/8] SUNRPC: treat every TLS error alert as fatal Chuck Lever
@ 2026-08-06 20:20 ` Chuck Lever
2026-08-06 20:20 ` [PATCH v2 5/8] SUNRPC: fold svc_tcp_sock_process_cmsg() into its only caller Chuck Lever
` (3 subsequent siblings)
7 siblings, 0 replies; 9+ messages in thread
From: Chuck Lever @ 2026-08-06 20:20 UTC (permalink / raw)
To: Trond Myklebust, Anna Schumaker, Chuck Lever, Jeff Layton,
NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey
Cc: linux-nfs, netdev
A TLS control record delivers no payload to the RPC layer.
svc_tcp_recvfrom() clears XPT_DATA before the receive, and
svc_tcp_sock_recv_cmsg() returns -EAGAIN for the record it consumed.
Nothing marks the transport ready again. kTLS raises data_ready for
arriving TCP segments, not for records it has already decrypted. An
RPC Call queued behind an alert or a KeyUpdate waits until the client
sends more. The client blocks until its RPC timeout expires.
The receive takes only the first two octets of the record. kTLS holds
the remainder on its receive list, where each later receive takes two
octets more.
Drain a record that is not an alert, then mark the transport ready
once a control record has been consumed.
Fixes: 5e052dda121e ("SUNRPC: Recognize control messages in server-side TCP socket code")
Signed-off-by: Chuck Lever <cel@kernel.org>
---
net/sunrpc/svcsock.c | 55 +++++++++++++++++++++++++++++++++++++++++++++++++---
1 file changed, 52 insertions(+), 3 deletions(-)
diff --git a/net/sunrpc/svcsock.c b/net/sunrpc/svcsock.c
index 756db84e4aec..6fee54f4290c 100644
--- a/net/sunrpc/svcsock.c
+++ b/net/sunrpc/svcsock.c
@@ -238,6 +238,39 @@ static int svc_one_sock_name(struct svc_sock *svsk, char *buf, int remaining)
return len;
}
+/*
+ * kTLS delivers a record only up to the caller's buffer and keeps
+ * the remainder on its receive list, where no further data_ready
+ * announces it. Consume the whole record.
+ */
+static void
+svc_tcp_sock_drain_record(struct socket *sock)
+{
+ union {
+ struct cmsghdr cmsg;
+ u8 buf[CMSG_SPACE(sizeof(u8))];
+ } u;
+ u8 discard[64];
+ struct kvec discard_kvec = {
+ .iov_base = discard,
+ .iov_len = sizeof(discard),
+ };
+
+ for (;;) {
+ struct msghdr msg = {
+ .msg_control = &u,
+ .msg_controllen = sizeof(u),
+ };
+
+ iov_iter_kvec(&msg.msg_iter, ITER_DEST, &discard_kvec, 1,
+ discard_kvec.iov_len);
+ if (sock_recvmsg(sock, &msg, MSG_DONTWAIT) <= 0)
+ break;
+ if (msg.msg_flags & MSG_EOR)
+ break;
+ }
+}
+
static int
svc_tcp_sock_process_cmsg(struct socket *sock, struct msghdr *msg,
struct cmsghdr *cmsg, int ret)
@@ -303,12 +336,20 @@ svc_tcp_sock_recv_cmsg(struct socket *sock, unsigned int *msg_flags)
* kTLS filled in u.cmsg.
*/
if (ret >= 0 && msg.msg_controllen < sizeof(u)) {
+ u8 content_type = tls_get_record_type(sock->sk, &u.cmsg);
+
/* Returning the count would credit the RPC stream with
* octets that never reached the caller's buffer.
*/
- if (tls_get_record_type(sock->sk, &u.cmsg) !=
- TLS_RECORD_TYPE_ALERT)
+ if (content_type != TLS_RECORD_TYPE_ALERT) {
+ /* Draining an application data record would
+ * discard the RPC stream.
+ */
+ if (content_type != TLS_RECORD_TYPE_DATA &&
+ !(msg.msg_flags & MSG_EOR))
+ svc_tcp_sock_drain_record(sock);
return -EAGAIN;
+ }
/* An Alert record carries exactly one two-octet message
* (RFC 8446 Section 5.1). alert_kvec caps the receive at two,
* so a longer record produces the same count. MSG_EOR appears
@@ -331,8 +372,16 @@ svc_tcp_sock_recvmsg(struct svc_sock *svsk, struct msghdr *msg)
ret = sock_recvmsg(sock, msg, MSG_DONTWAIT);
if (msg->msg_flags & MSG_CTRUNC) {
msg->msg_flags &= ~(MSG_CTRUNC | MSG_EOR);
- if (ret == 0 || ret == -EIO)
+ if (ret == 0 || ret == -EIO) {
ret = svc_tcp_sock_recv_cmsg(sock, &msg->msg_flags);
+ /* A control record delivers nothing to the caller,
+ * and kTLS announces no data_ready for records it
+ * already holds. Mark the transport ready so that
+ * the records behind this one can be received.
+ */
+ if (ret == -EAGAIN)
+ set_bit(XPT_DATA, &svsk->sk_xprt.xpt_flags);
+ }
}
return ret;
}
--
2.54.0
^ permalink raw reply related [flat|nested] 9+ messages in thread* [PATCH v2 5/8] SUNRPC: fold svc_tcp_sock_process_cmsg() into its only caller
2026-08-06 20:20 [PATCH v2 0/8] SUNRPC: Fix TLS control record handling Chuck Lever
` (3 preceding siblings ...)
2026-08-06 20:20 ` [PATCH v2 4/8] SUNRPC: resume receiving after a TLS control record Chuck Lever
@ 2026-08-06 20:20 ` Chuck Lever
2026-08-06 20:20 ` [PATCH v2 6/8] SUNRPC: reject a client-side TLS alert record that is not two octets Chuck Lever
` (2 subsequent siblings)
7 siblings, 0 replies; 9+ messages in thread
From: Chuck Lever @ 2026-08-06 20:20 UTC (permalink / raw)
To: Trond Myklebust, Anna Schumaker, Chuck Lever, Jeff Layton,
NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey
Cc: linux-nfs, netdev
svc_tcp_sock_process_cmsg() switches on the TLS record type.
svc_tcp_sock_recv_cmsg() now returns -EAGAIN for every record type
except an alert before it calls the helper. The case 0,
TLS_RECORD_TYPE_DATA, and default arms are unreachable. The DATA arm
is inert twice over. It clears MSG_EOR in the msghdr local to
svc_tcp_sock_recv_cmsg(), and that msghdr is discarded on return.
svc_tcp_sock_recvmsg() has already cleared the flag in the caller's
msghdr. Deriving the record type a second time inside the helper also
fires trace_tls_contenttype() twice for every alert.
Move the alert handling into svc_tcp_sock_recv_cmsg() and delete the
helper. The DATA arm's account of MSG_EOR moves to
svc_tcp_sock_recvmsg(), where the flag is now cleared.
Signed-off-by: Chuck Lever <cel@kernel.org>
---
net/sunrpc/svcsock.c | 58 ++++++++++++++++------------------------------------
1 file changed, 18 insertions(+), 40 deletions(-)
diff --git a/net/sunrpc/svcsock.c b/net/sunrpc/svcsock.c
index 6fee54f4290c..99a159afd3df 100644
--- a/net/sunrpc/svcsock.c
+++ b/net/sunrpc/svcsock.c
@@ -271,45 +271,6 @@ svc_tcp_sock_drain_record(struct socket *sock)
}
}
-static int
-svc_tcp_sock_process_cmsg(struct socket *sock, struct msghdr *msg,
- struct cmsghdr *cmsg, int ret)
-{
- u8 content_type = tls_get_record_type(sock->sk, cmsg);
- u8 level, description;
-
- switch (content_type) {
- case 0:
- break;
- case TLS_RECORD_TYPE_DATA:
- /* TLS sets EOR at the end of each application data
- * record, even though there might be more frames
- * waiting to be decrypted.
- */
- msg->msg_flags &= ~MSG_EOR;
- break;
- case TLS_RECORD_TYPE_ALERT:
- tls_alert_recv(sock->sk, msg, &level, &description);
- /* RFC 8446 Section 6: every alert but a closure alert is
- * an error alert, whatever the legacy AlertLevel octet
- * says.
- */
- switch (description) {
- case TLS_ALERT_DESC_CLOSE_NOTIFY:
- case TLS_ALERT_DESC_USER_CANCELED:
- ret = -EAGAIN;
- break;
- default:
- ret = -ENOTCONN;
- }
- break;
- default:
- /* discard this record type */
- ret = -EAGAIN;
- }
- return ret;
-}
-
static int
svc_tcp_sock_recv_cmsg(struct socket *sock, unsigned int *msg_flags)
{
@@ -327,6 +288,7 @@ svc_tcp_sock_recv_cmsg(struct socket *sock, unsigned int *msg_flags)
.msg_control = &u,
.msg_controllen = sizeof(u),
};
+ u8 level, description;
int ret;
iov_iter_kvec(&msg.msg_iter, ITER_DEST, &alert_kvec, 1,
@@ -358,7 +320,19 @@ svc_tcp_sock_recv_cmsg(struct socket *sock, unsigned int *msg_flags)
if (ret != sizeof(alert) || !(msg.msg_flags & MSG_EOR))
return -EBADMSG;
iov_iter_revert(&msg.msg_iter, ret);
- ret = svc_tcp_sock_process_cmsg(sock, &msg, &u.cmsg, -EAGAIN);
+ tls_alert_recv(sock->sk, &msg, &level, &description);
+ /* RFC 8446 Section 6: every alert but a closure alert is
+ * an error alert, whatever the legacy AlertLevel octet
+ * says.
+ */
+ switch (description) {
+ case TLS_ALERT_DESC_CLOSE_NOTIFY:
+ case TLS_ALERT_DESC_USER_CANCELED:
+ ret = -EAGAIN;
+ break;
+ default:
+ ret = -ENOTCONN;
+ }
}
return ret;
}
@@ -371,6 +345,10 @@ svc_tcp_sock_recvmsg(struct svc_sock *svsk, struct msghdr *msg)
ret = sock_recvmsg(sock, msg, MSG_DONTWAIT);
if (msg->msg_flags & MSG_CTRUNC) {
+ /* TLS sets EOR at the end of each application data
+ * record, even though there might be more frames
+ * waiting to be decrypted.
+ */
msg->msg_flags &= ~(MSG_CTRUNC | MSG_EOR);
if (ret == 0 || ret == -EIO) {
ret = svc_tcp_sock_recv_cmsg(sock, &msg->msg_flags);
--
2.54.0
^ permalink raw reply related [flat|nested] 9+ messages in thread* [PATCH v2 6/8] SUNRPC: reject a client-side TLS alert record that is not two octets
2026-08-06 20:20 [PATCH v2 0/8] SUNRPC: Fix TLS control record handling Chuck Lever
` (4 preceding siblings ...)
2026-08-06 20:20 ` [PATCH v2 5/8] SUNRPC: fold svc_tcp_sock_process_cmsg() into its only caller Chuck Lever
@ 2026-08-06 20:20 ` Chuck Lever
2026-08-06 20:20 ` [PATCH v2 7/8] SUNRPC: treat every client-side TLS error alert as fatal Chuck Lever
2026-08-06 20:20 ` [PATCH v2 8/8] SUNRPC: fold xs_sock_process_cmsg() into its only caller Chuck Lever
7 siblings, 0 replies; 9+ messages in thread
From: Chuck Lever @ 2026-08-06 20:20 UTC (permalink / raw)
To: Trond Myklebust, Anna Schumaker, Chuck Lever, Jeff Layton,
NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey
Cc: linux-nfs, netdev
tls_alert_recv() reads two octets from the kvec it is handed and does
not check the length (net/handshake/alert.c). xs_sock_process_cmsg()
calls it for any alert record, and the alert[] buffer that
xs_sock_recv_cmsg() supplies carries no initializer. A one-octet alert
body leaves the description read from uninitialized stack and reported
through trace_tls_alert_recv().
The peer controls that length. Neither tls_rx_msg_size() nor
tls_rx_one_record() enforces the two-octet Alert payload. A TLS 1.3
record carrying only the inner content-type octet decrypts to a
zero-length payload. RFC 8446 Section 5.1 requires a record with an
Alert type to carry exactly one message, so any other length is
malformed. RFC 9289 Section 5 bars RPC-with-TLS from negotiating a
version below TLS 1.3, so no other alert framing applies.
Require exactly two octets before parsing and return -EACCES
otherwise. xs_stream_data_receive() already treats -EACCES as a fatal
alert and reports it to the pending tasks. Gate the path on a control
message rather than a positive count so that a zero-length record
reaches the check.
Fixes: cc5d59081fa2 ("sunrpc: fix client side handling of tls alerts")
Signed-off-by: Chuck Lever <cel@kernel.org>
---
net/sunrpc/xprtsock.c | 20 ++++++++++++++++++--
1 file changed, 18 insertions(+), 2 deletions(-)
diff --git a/net/sunrpc/xprtsock.c b/net/sunrpc/xprtsock.c
index 359407aae03e..8e9d47d77e5b 100644
--- a/net/sunrpc/xprtsock.c
+++ b/net/sunrpc/xprtsock.c
@@ -407,9 +407,25 @@ xs_sock_recv_cmsg(struct socket *sock, unsigned int *msg_flags, int flags)
iov_iter_kvec(&msg.msg_iter, ITER_DEST, &alert_kvec, 1,
alert_kvec.iov_len);
ret = sock_recvmsg(sock, &msg, flags);
- if (ret > 0) {
- if (tls_get_record_type(sock->sk, &u.cmsg) == TLS_RECORD_TYPE_ALERT)
+ /* put_cmsg() shrinks msg_controllen, so a short one means
+ * kTLS filled in u.cmsg.
+ */
+ if (ret >= 0 && msg.msg_controllen < sizeof(u)) {
+ if (tls_get_record_type(sock->sk, &u.cmsg) ==
+ TLS_RECORD_TYPE_ALERT) {
+ /* RFC 8446 Section 5.1 requires a record with an
+ * Alert type to carry exactly one message. An alert
+ * is two octets. tls_alert_recv() reads both without
+ * checking the length. alert_kvec caps the count at
+ * two, so a longer record fills it as well. kTLS
+ * sets MSG_EOR only once the record has been
+ * drained.
+ */
+ if (ret != sizeof(alert) ||
+ !(msg.msg_flags & MSG_EOR))
+ return -EACCES;
iov_iter_revert(&msg.msg_iter, ret);
+ }
ret = xs_sock_process_cmsg(sock, &msg, msg_flags, &u.cmsg,
-EAGAIN);
}
--
2.54.0
^ permalink raw reply related [flat|nested] 9+ messages in thread* [PATCH v2 7/8] SUNRPC: treat every client-side TLS error alert as fatal
2026-08-06 20:20 [PATCH v2 0/8] SUNRPC: Fix TLS control record handling Chuck Lever
` (5 preceding siblings ...)
2026-08-06 20:20 ` [PATCH v2 6/8] SUNRPC: reject a client-side TLS alert record that is not two octets Chuck Lever
@ 2026-08-06 20:20 ` Chuck Lever
2026-08-06 20:20 ` [PATCH v2 8/8] SUNRPC: fold xs_sock_process_cmsg() into its only caller Chuck Lever
7 siblings, 0 replies; 9+ messages in thread
From: Chuck Lever @ 2026-08-06 20:20 UTC (permalink / raw)
To: Trond Myklebust, Anna Schumaker, Chuck Lever, Jeff Layton,
NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey
Cc: linux-nfs, netdev
xs_sock_process_cmsg() decides whether an alert ends the session by
reading the alert's level octet. RFC 8446 Section 6 retired that
field. The severity is implicit in the description, and a receiver
treats every alert listed in Section 6.2 as an error alert
"regardless of the AlertLevel in the message". A peer that aborts
with unexpected_message but leaves the legacy octet set to warning
makes the client return -EAGAIN. xs_stream_data_receive() wakes no
pending task for that error, so RPC Calls queued on a dead TLS
session wait for their timeouts to expire.
Decide from the alert description instead. close_notify and
user_canceled are the closure alerts (RFC 8446 Section 6.1). Every
other description ends the session, including one this kernel does
not recognize.
Fixes: 39067dda1d86 ("SUNRPC: Use new helpers to handle TLS Alerts")
Signed-off-by: Chuck Lever <cel@kernel.org>
---
net/sunrpc/xprtsock.c | 14 ++++++++++++--
1 file changed, 12 insertions(+), 2 deletions(-)
diff --git a/net/sunrpc/xprtsock.c b/net/sunrpc/xprtsock.c
index 8e9d47d77e5b..5527f7f8a185 100644
--- a/net/sunrpc/xprtsock.c
+++ b/net/sunrpc/xprtsock.c
@@ -375,8 +375,18 @@ xs_sock_process_cmsg(struct socket *sock, struct msghdr *msg,
break;
case TLS_RECORD_TYPE_ALERT:
tls_alert_recv(sock->sk, msg, &level, &description);
- ret = (level == TLS_ALERT_LEVEL_FATAL) ?
- -EACCES : -EAGAIN;
+ /* RFC 8446 Section 6: every alert but a closure alert is
+ * an error alert, whatever the legacy AlertLevel octet
+ * says.
+ */
+ switch (description) {
+ case TLS_ALERT_DESC_CLOSE_NOTIFY:
+ case TLS_ALERT_DESC_USER_CANCELED:
+ ret = -EAGAIN;
+ break;
+ default:
+ ret = -EACCES;
+ }
break;
default:
/* discard this record type */
--
2.54.0
^ permalink raw reply related [flat|nested] 9+ messages in thread* [PATCH v2 8/8] SUNRPC: fold xs_sock_process_cmsg() into its only caller
2026-08-06 20:20 [PATCH v2 0/8] SUNRPC: Fix TLS control record handling Chuck Lever
` (6 preceding siblings ...)
2026-08-06 20:20 ` [PATCH v2 7/8] SUNRPC: treat every client-side TLS error alert as fatal Chuck Lever
@ 2026-08-06 20:20 ` Chuck Lever
7 siblings, 0 replies; 9+ messages in thread
From: Chuck Lever @ 2026-08-06 20:20 UTC (permalink / raw)
To: Trond Myklebust, Anna Schumaker, Chuck Lever, Jeff Layton,
NeilBrown, Olga Kornievskaia, Dai Ngo, Tom Talpey
Cc: linux-nfs, netdev
xs_sock_process_cmsg() switches on the TLS record type, and every arm
but TLS_RECORD_TYPE_ALERT returns the -EAGAIN its caller passed in.
The DATA arm clears MSG_EOR in the caller's msghdr, but
xs_sock_recvmsg() has already cleared that flag before the call.
Deriving the record type a second time inside the helper also fires
trace_tls_contenttype() twice for every alert.
Move the alert handling into xs_sock_recv_cmsg() and delete the
helper. Every other record type still returns -EAGAIN. The DATA arm's
account of MSG_EOR moves to xs_sock_recvmsg(), where the flag is
cleared.
Signed-off-by: Chuck Lever <cel@kernel.org>
---
net/sunrpc/xprtsock.c | 85 ++++++++++++++++++---------------------------------
1 file changed, 30 insertions(+), 55 deletions(-)
diff --git a/net/sunrpc/xprtsock.c b/net/sunrpc/xprtsock.c
index 5527f7f8a185..38dd75a23af7 100644
--- a/net/sunrpc/xprtsock.c
+++ b/net/sunrpc/xprtsock.c
@@ -356,45 +356,6 @@ xs_alloc_sparse_pages(struct xdr_buf *buf, size_t want, gfp_t gfp)
return want;
}
-static int
-xs_sock_process_cmsg(struct socket *sock, struct msghdr *msg,
- unsigned int *msg_flags, struct cmsghdr *cmsg, int ret)
-{
- u8 content_type = tls_get_record_type(sock->sk, cmsg);
- u8 level, description;
-
- switch (content_type) {
- case 0:
- break;
- case TLS_RECORD_TYPE_DATA:
- /* TLS sets EOR at the end of each application data
- * record, even though there might be more frames
- * waiting to be decrypted.
- */
- *msg_flags &= ~MSG_EOR;
- break;
- case TLS_RECORD_TYPE_ALERT:
- tls_alert_recv(sock->sk, msg, &level, &description);
- /* RFC 8446 Section 6: every alert but a closure alert is
- * an error alert, whatever the legacy AlertLevel octet
- * says.
- */
- switch (description) {
- case TLS_ALERT_DESC_CLOSE_NOTIFY:
- case TLS_ALERT_DESC_USER_CANCELED:
- ret = -EAGAIN;
- break;
- default:
- ret = -EACCES;
- }
- break;
- default:
- /* discard this record type */
- ret = -EAGAIN;
- }
- return ret;
-}
-
static int
xs_sock_recv_cmsg(struct socket *sock, unsigned int *msg_flags, int flags)
{
@@ -412,6 +373,7 @@ xs_sock_recv_cmsg(struct socket *sock, unsigned int *msg_flags, int flags)
.msg_control = &u,
.msg_controllen = sizeof(u),
};
+ u8 level, description;
int ret;
iov_iter_kvec(&msg.msg_iter, ITER_DEST, &alert_kvec, 1,
@@ -421,23 +383,32 @@ xs_sock_recv_cmsg(struct socket *sock, unsigned int *msg_flags, int flags)
* kTLS filled in u.cmsg.
*/
if (ret >= 0 && msg.msg_controllen < sizeof(u)) {
- if (tls_get_record_type(sock->sk, &u.cmsg) ==
- TLS_RECORD_TYPE_ALERT) {
- /* RFC 8446 Section 5.1 requires a record with an
- * Alert type to carry exactly one message. An alert
- * is two octets. tls_alert_recv() reads both without
- * checking the length. alert_kvec caps the count at
- * two, so a longer record fills it as well. kTLS
- * sets MSG_EOR only once the record has been
- * drained.
- */
- if (ret != sizeof(alert) ||
- !(msg.msg_flags & MSG_EOR))
- return -EACCES;
- iov_iter_revert(&msg.msg_iter, ret);
+ if (tls_get_record_type(sock->sk, &u.cmsg) !=
+ TLS_RECORD_TYPE_ALERT)
+ return -EAGAIN;
+ /* RFC 8446 Section 5.1: a record with an Alert type carries
+ * exactly one message, and an alert is two octets.
+ * tls_alert_recv() reads both without checking the length.
+ * alert_kvec caps the count at two, so a longer record
+ * fills it as well. kTLS sets MSG_EOR only once the
+ * record has been drained.
+ */
+ if (ret != sizeof(alert) || !(msg.msg_flags & MSG_EOR))
+ return -EACCES;
+ iov_iter_revert(&msg.msg_iter, ret);
+ tls_alert_recv(sock->sk, &msg, &level, &description);
+ /* RFC 8446 Section 6: every alert but a closure alert is
+ * an error alert, whatever the legacy AlertLevel octet
+ * says.
+ */
+ switch (description) {
+ case TLS_ALERT_DESC_CLOSE_NOTIFY:
+ case TLS_ALERT_DESC_USER_CANCELED:
+ ret = -EAGAIN;
+ break;
+ default:
+ ret = -EACCES;
}
- ret = xs_sock_process_cmsg(sock, &msg, msg_flags, &u.cmsg,
- -EAGAIN);
}
return ret;
}
@@ -451,6 +422,10 @@ xs_sock_recvmsg(struct socket *sock, struct msghdr *msg, int flags, size_t seek)
ret = sock_recvmsg(sock, msg, flags);
/* Handle TLS inband control message lazily */
if (msg->msg_flags & MSG_CTRUNC) {
+ /* TLS sets EOR at the end of each application data
+ * record, even though there might be more frames
+ * waiting to be decrypted.
+ */
msg->msg_flags &= ~(MSG_CTRUNC | MSG_EOR);
if (ret == 0 || ret == -EIO)
ret = xs_sock_recv_cmsg(sock, &msg->msg_flags, flags);
--
2.54.0
^ permalink raw reply related [flat|nested] 9+ messages in thread