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 and RFC 8446 Section 5.1). A record that decrypts to
full_len == 0 advances neither len nor decrypted, so
tls_sw_recvmsg() keeps reading empty records for as long as they
arrive. The peek arm and the async arm queue each empty record on
rx_list, and a peer that streams empty records grows rx_list
without bound. The growth has not been reproduced.
Consume an empty data record as soon as the receive loop has it,
before the paths diverge on darg.zc. Freeing the skb requires its
decryption to have completed, and an empty record is already
decrypted synchronously. The new branch sets MSG_EOR itself,
because the record no longer reaches the assignment at the bottom
of the loop.
After this change, tls_sw_recvmsg() still reads empty records for
as long as they arrive, until a signal is pending.
Fixes: 692d7b5d1f91 ("tls: Fix recvmsg() to be able to peek across multiple records")
Reported-by: sashiko-bot <sashiko-bot@kernel.org>
Closes: https://sashiko.dev/#/patchset/20260630191551.875664-1-cel@kernel.org?part=1
Signed-off-by: Chuck Lever <cel@kernel.org>
---
net/tls/tls_sw.c | 17 +++++++++++++++--
1 file changed, 15 insertions(+), 2 deletions(-)
diff --git a/net/tls/tls_sw.c b/net/tls/tls_sw.c
index 53a4af9eeeef..153ff135a395 100644
--- a/net/tls/tls_sw.c
+++ b/net/tls/tls_sw.c
@@ -1888,8 +1888,8 @@ int tls_sw_recvmsg(struct sock *sk,
darg.zc = true;
/* Do not use async mode if record is non-data, or if it
- * is empty: once a record has gone async, recv_end
- * discards the error that ends a run of empty records.
+ * is empty: the receive loop frees an empty record's skb,
+ * which an async decrypt would still be using.
*/
if (tlm->control == TLS_RECORD_TYPE_DATA)
darg.async = ctx->async_capable && to_decrypt;
@@ -1929,6 +1929,19 @@ int tls_sw_recvmsg(struct sock *sk,
nodata = !chunk;
tls_rx_rec_done(ctx);
+ /* Keep an empty record off rx_list. On the zero-copy path
+ * the strparser owns darg.skb, and tls_rx_rec_done() has
+ * released it.
+ */
+ if (!chunk && control == TLS_RECORD_TYPE_DATA) {
+ if (!darg.zc)
+ consume_skb(darg.skb);
+
+ /* An empty record still marks a boundary. */
+ msg->msg_flags |= MSG_EOR;
+ continue;
+ }
+
if (!darg.zc) {
bool partially_consumed = chunk > len;
struct sk_buff *skb = darg.skb;
--
2.55.0