Thread (29 messages) 29 messages, 3 authors, 2026-08-07

[PATCH net 5/9] net/tls: Consume empty data records in tls_sw_recvmsg()

flat view
STALE75d SUPERSEDED

From: Chuck Lever <cel@kernel.org>
Date: 2026-07-27 00:33:52
Also in: linux-kselftest
Subsystem: networking [general], networking [tls], the rest · Maintainers: "David S. Miller", Eric Dumazet, Jakub Kicinski, Paolo Abeni, John Fastabend, Sabrina Dubroca, Linus Torvalds

Revision v1 of 6 in this series.

Revisions (6)
  1. v1 current
  2. v2 [diff vs current]
  3. v3 [diff vs current]
  4. v4 [diff vs current]
  5. v5 [diff vs current]
  6. v6 [diff vs current]
TLS 1.2 and TLS 1.3 both permit zero-length application_data
records as a traffic-analysis countermeasure (RFC 5246, Section
6.2.1; RFC 8446, Section 5.1). Such a record decrypts to
full_len == 0, so every arm of the receive loop reaches
"decrypted += chunk" and "len -= chunk" with chunk == 0: len
never reaches zero, and tls_strp_msg_ready() holds the second
loop term true while the peer keeps records arriving. The peek
arm and the async arm also queue each record on rx_list, which
then grows without bound. tls_rx_rec_wait() returns without
waiting whenever a record is already parsed, so its signal check
never runs, and no other test in the loop consults
signal_pending(). A peer streaming empty records therefore holds
the caller in recvmsg(), unresponsive to SIGKILL, until it stops.

Consume an empty data record as soon as the receive loop has it,
before the paths diverge on darg.zc, and test for a pending
signal there. Freeing the record's skb requires its decryption to
have completed, so an empty record is no longer decrypted
asynchronously, and the new branch sets MSG_EOR itself because
the record no longer reaches the assignment at the bottom of the
loop. Bytes already received take precedence over the signal:
they are returned, and the signal is handled when the caller next
enters the kernel.

Fixes: c46234ebb4d1 ("tls: RX path for ktls")
Signed-off-by: Chuck Lever <cel@kernel.org>
---
 net/tls/tls_sw.c | 28 ++++++++++++++++++++++++++--
 1 file changed, 26 insertions(+), 2 deletions(-)
diff --git a/net/tls/tls_sw.c b/net/tls/tls_sw.c
index f85d8a639731..0e76b31b1291 100644
--- a/net/tls/tls_sw.c
+++ b/net/tls/tls_sw.c
@@ -1877,9 +1877,12 @@ int tls_sw_recvmsg(struct sock *sk,
 		    tlm->control == TLS_RECORD_TYPE_DATA)
 			darg.zc = true;
 
-		/* Do not use async mode if record is non-data */
+		/* Do not use async mode if record is non-data, or if it
+		 * is empty: the receive loop frees an empty record's skb,
+		 * so its decryption must have completed.
+		 */
 		if (tlm->control == TLS_RECORD_TYPE_DATA)
-			darg.async = ctx->async_capable;
+			darg.async = ctx->async_capable && to_decrypt;
 		else
 			darg.async = false;
 
@@ -1915,6 +1918,27 @@ int tls_sw_recvmsg(struct sock *sk,
 		chunk = rxm->full_len;
 		tls_rx_rec_done(ctx);
 
+		/* An empty record advances neither loop bound, so a flood
+		 * of them can be interrupted only here. On the zero-copy
+		 * path darg.skb is the strparser anchor, already released
+		 * by tls_rx_rec_done().
+		 */
+		if (tls_rx_empty_data_rec(chunk, control)) {
+			long timeo = sock_rcvtimeo(sk, flags & MSG_DONTWAIT);
+
+			if (!darg.zc)
+				consume_skb(darg.skb);
+
+			/* An empty record still marks a boundary. */
+			msg->msg_flags |= MSG_EOR;
+
+			if (signal_pending(current)) {
+				err = tls_rx_intr_errno(timeo);
+				goto recv_end;
+			}
+			continue;
+		}
+
 		if (!darg.zc) {
 			bool partially_consumed = chunk > len;
 			struct sk_buff *skb = darg.skb;
-- 
2.54.0
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help