Subject: [RFC net-next 1/3] net/tls_sw: support randomized zero padding

From: Wilfred Mallawa <[email-masked]>
Date: Mon, 9 Mar 2026 15:48:36 +1000

From: Wilfred Mallawa <[email-masked]>

Currently, for TLS 1.3, ktls does not support record zero padding [1].
Record zero padding is used to allow the sender to hide the size of the
traffic patterns from an observer. TLS is susceptible to a variety of traffic
analysis attacks based on observing the length and timing of encrypted
packets [2]. Upcoming Western Digital NVMe-TCP hardware controllers
implement TLS 1.3. Which from a security perspective, can benefit from having
record zero padding enabled to mitigate against traffic analysis attacks [2].

Thus, for TX, add support to appending a randomized number of zero padding
bytes to end-of-record (EOR) records that are not full. The number of zero
padding bytes to append is determined by the remaining record room and the
user specified upper bound (minimum of the two). That is
rand([0, min(record_room, upper_bound)]).

For TLS 1.3, zero padding is added after the content type byte, as such,
if the record in context meets the above conditions for zero padding,
attach a zero padding buffer to the content type byte before a record is
encrypted. The padding buffer is freed when the record is freed.

By default, record zero padding is disabled, and userspace may enable it
by using the setsockopt TLS_TX_RANDOM_PAD option.

[1] https://datatracker.ietf.org/doc/html/rfc8446#section-5.4l
[2] https://datatracker.ietf.org/doc/html/rfc8446#appendix-E.3

Signed-off-by: Wilfred Mallawa <[email-masked]>
---
 include/net/tls.h  |  1 +
 net/tls/tls.h      |  6 ++++-
 net/tls/tls_main.c |  2 ++
 net/tls/tls_sw.c   | 58 ++++++++++++++++++++++++++++++++++++++--------
 4 files changed, 56 insertions(+), 11 deletions(-)

diff --git a/include/net/tls.h b/include/net/tls.h
index ebd2550280ae..1feef72cc339 100644
--- a/include/net/tls.h
+++ b/include/net/tls.h
@@ -229,6 +229,7 @@ struct tls_context {
 	u8 zerocopy_sendfile:1;
 	u8 rx_no_pad:1;
 	u16 tx_max_payload_len;
+	u16 tx_record_zero_pad;
 
 	int (*push_pending_record)(struct sock *sk, int flags);
 	void (*sk_write_space)(struct sock *sk);
diff --git a/net/tls/tls.h b/net/tls/tls.h
index e8f81a006520..3a86eb145332 100644
--- a/net/tls/tls.h
+++ b/net/tls/tls.h
@@ -121,8 +121,12 @@ struct tls_rec {
 	/* AAD | msg_encrypted.sg.data (data contains overhead for hdr & iv & tag) */
 	struct scatterlist sg_aead_out[2];
 
+	/* TLS 1.3 record zero padding */
+	char *zero_padding;
+	u16 zero_padding_len;
+
 	char content_type;
-	struct scatterlist sg_content_type;
+	struct scatterlist sg_content_trail[2];
 
 	struct sock *sk;
 
diff --git a/net/tls/tls_main.c b/net/tls/tls_main.c
index fd39acf41a61..b0702effbc26 100644
--- a/net/tls/tls_main.c
+++ b/net/tls/tls_main.c
@@ -1076,6 +1076,8 @@ static int tls_init(struct sock *sk)
 	ctx->tx_conf = TLS_BASE;
 	ctx->rx_conf = TLS_BASE;
 	ctx->tx_max_payload_len = TLS_MAX_PAYLOAD_SIZE;
+	/* TX record zero padding is disabled by default */
+	ctx->tx_record_zero_pad = 0;
 	update_sk_prot(sk, ctx);
 out:
 	write_unlock_bh(&sk->sk_callback_lock);
diff --git a/net/tls/tls_sw.c b/net/tls/tls_sw.c
index a656ce235758..84b167607e1f 100644
--- a/net/tls/tls_sw.c
+++ b/net/tls/tls_sw.c
@@ -389,6 +389,7 @@ static void tls_free_rec(struct sock *sk, struct tls_rec *rec)
 {
 	sk_msg_free(sk, &rec->msg_encrypted);
 	sk_msg_free(sk, &rec->msg_plaintext);
+	kfree(rec->zero_padding);
 	kfree(rec);
 }
 
@@ -430,6 +431,7 @@ int tls_tx_records(struct sock *sk, int flags)
 		 */
 		list_del(&rec->list);
 		sk_msg_free(sk, &rec->msg_plaintext);
+		kfree(rec->zero_padding);
 		kfree(rec);
 	}
 
@@ -450,6 +452,7 @@ int tls_tx_records(struct sock *sk, int flags)
 
 			list_del(&rec->list);
 			sk_msg_free(sk, &rec->msg_plaintext);
+			kfree(rec->zero_padding);
 			kfree(rec);
 		} else {
 			break;
@@ -779,12 +782,29 @@ static int tls_push_record(struct sock *sk, int flags,
 	sk_msg_iter_var_prev(i);
 
 	rec->content_type = record_type;
+
 	if (prot->version == TLS_1_3_VERSION) {
-		/* Add content type to end of message.  No padding added */
-		sg_set_buf(&rec->sg_content_type, &rec->content_type, 1);
-		sg_mark_end(&rec->sg_content_type);
+		/*
+		 * Add content type to end of message with zero padding
+		 * if available.
+		 */
+		sg_init_table(rec->sg_content_trail, 2);
+		sg_set_buf(&rec->sg_content_trail[0], &rec->content_type, 1);
+		if (rec->zero_padding_len) {
+			rec->zero_padding = kzalloc(rec->zero_padding_len,
+						    sk->sk_allocation);
+			if (!rec->zero_padding)
+				return -ENOMEM;
+
+			sg_set_buf(&rec->sg_content_trail[1],
+				   rec->zero_padding, rec->zero_padding_len);
+			sg_mark_end(&rec->sg_content_trail[1]);
+		} else {
+			sg_mark_end(&rec->sg_content_trail[0]);
+		}
+
 		sg_chain(msg_pl->sg.data, msg_pl->sg.end + 1,
-			 &rec->sg_content_type);
+			 rec->sg_content_trail);
 	} else {
 		sg_mark_end(sk_msg_elem(msg_pl, i));
 	}
@@ -805,19 +825,21 @@ static int tls_push_record(struct sock *sk, int flags,
 	i = msg_en->sg.start;
 	sg_chain(rec->sg_aead_out, 2, &msg_en->sg.data[i]);
 
-	tls_make_aad(rec->aad_space, msg_pl->sg.size + prot->tail_size,
-		     tls_ctx->tx.rec_seq, record_type, prot);
+	tls_make_aad(rec->aad_space, msg_pl->sg.size + prot->tail_size +
+		     rec->zero_padding_len, tls_ctx->tx.rec_seq,
+		     record_type, prot);
 
 	tls_fill_prepend(tls_ctx,
 			 page_address(sg_page(&msg_en->sg.data[i])) +
 			 msg_en->sg.data[i].offset,
-			 msg_pl->sg.size + prot->tail_size,
-			 record_type);
+			 msg_pl->sg.size + prot->tail_size +
+			 rec->zero_padding_len, record_type);
 
 	tls_ctx->pending_open_record_frags = false;
 
 	rc = tls_do_encryption(sk, tls_ctx, ctx, req,
-			       msg_pl->sg.size + prot->tail_size, i);
+			       msg_pl->sg.size + prot->tail_size +
+			       rec->zero_padding_len, i);
 	if (rc < 0) {
 		if (rc != -EINPROGRESS) {
 			tls_err_abort(sk, -EBADMSG);
@@ -1033,6 +1055,8 @@ static int tls_sw_sendmsg_locked(struct sock *sk, struct msghdr *msg,
 	unsigned char record_type = TLS_RECORD_TYPE_DATA;
 	bool is_kvec = iov_iter_is_kvec(&msg->msg_iter);
 	bool eor = !(msg->msg_flags & MSG_MORE);
+	bool tls_13 = (prot->version == TLS_1_3_VERSION);
+	bool rec_zero_pad = eor && tls_13 && tls_ctx->tx_record_zero_pad;
 	size_t try_to_copy;
 	ssize_t copied = 0;
 	struct sk_msg *msg_pl, *msg_en;
@@ -1043,6 +1067,7 @@ static int tls_sw_sendmsg_locked(struct sock *sk, struct msghdr *msg,
 	int record_room;
 	int num_zc = 0;
 	int orig_size;
+	int max_zero_pad_len, zero_pad_len = 0;
 	int ret = 0;
 
 	if (!eor && (msg->msg_flags & MSG_EOR))
@@ -1085,8 +1110,19 @@ static int tls_sw_sendmsg_locked(struct sock *sk, struct msghdr *msg,
 			full_record = true;
 		}
 
+		if (rec_zero_pad && !full_record)
+			zero_pad_len = record_room - try_to_copy;
+
+		if (zero_pad_len > prot->tail_size) {
+			max_zero_pad_len = min(zero_pad_len,
+					       tls_ctx->tx_record_zero_pad);
+			zero_pad_len =
+				get_random_u32_inclusive(0, max_zero_pad_len);
+			rec->zero_padding_len = zero_pad_len;
+		}
+
 		required_size = msg_pl->sg.size + try_to_copy +
-				prot->overhead_size;
+				prot->overhead_size + rec->zero_padding_len;
 
 		if (!sk_stream_memory_free(sk))
 			goto wait_for_sndbuf;
@@ -2555,6 +2591,7 @@ void tls_sw_release_resources_tx(struct sock *sk)
 				       struct tls_rec, list);
 		list_del(&rec->list);
 		sk_msg_free(sk, &rec->msg_plaintext);
+		kfree(rec->zero_padding);
 		kfree(rec);
 	}
 
@@ -2562,6 +2599,7 @@ void tls_sw_release_resources_tx(struct sock *sk)
 		list_del(&rec->list);
 		sk_msg_free(sk, &rec->msg_encrypted);
 		sk_msg_free(sk, &rec->msg_plaintext);
+		kfree(rec->zero_padding);
 		kfree(rec);
 	}
 
-- 
2.53.0