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() keeps the second loop term
true while records keep arriving. The peek arm and the async arm
also queue each record on rx_list, which then grows without bound.
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, so an empty record is no longer
decrypted asynchronously. The new branch sets MSG_EOR itself,
because the record no longer reaches the assignment at the bottom
of the loop.
Fixes: 692d7b5d1f91 ("tls: Fix recvmsg() to be able to peek across multiple
records")
Signed-off-by: Chuck Lever <[email protected]>
---
net/tls/tls_sw.c | 20 ++++++++++++++++++--
1 file changed, 18 insertions(+), 2 deletions(-)
diff --git a/net/tls/tls_sw.c b/net/tls/tls_sw.c
index 6ca1e9f4e504..4fecac8a0b0d 100644
--- a/net/tls/tls_sw.c
+++ b/net/tls/tls_sw.c
@@ -1885,9 +1885,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;
@@ -1924,6 +1927,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