All of lore.kernel.org
 help / color / mirror / Atom feed
From: Martin Belanger via B4 Relay <devnull+martin.nvme.dev.gmail.com@kernel.org>
To: John Fastabend <john.fastabend@gmail.com>,
	 Jakub Kicinski <kuba@kernel.org>,
	Sabrina Dubroca <sd@queasysnail.net>,
	 "David S. Miller" <davem@davemloft.net>,
	Eric Dumazet <edumazet@kernel.org>,
	 Paolo Abeni <pabeni@redhat.com>, Simon Horman <horms@kernel.org>,
	 Jonathan Corbet <corbet@lwn.net>,
	Shuah Khan <skhan@linuxfoundation.org>,
	 Randy Dunlap <rdunlap@infradead.org>,
	Shuah Khan <shuah@kernel.org>
Cc: linux-kernel@vger.kernel.org, netdev@vger.kernel.org,
	 linux-doc@vger.kernel.org, linux-kselftest@vger.kernel.org,
	 Hannes Reinecke <hare@kernel.org>, Chuck Lever <cel@kernel.org>,
	 kernel-tls-handshake@lists.linux.dev,
	linux-nvme@lists.infradead.org,
	 Alistair Francis <alistair.francis@wdc.com>,
	 Martin Belanger <martin.belanger@dell.com>
Subject: [PATCH RFC net-next v2 4/5] tls: send a KeyUpdate at the record limit and on request
Date: Wed, 28 Oct 2026 09:52:54 -0400	[thread overview]
Message-ID: <20261028-keyupdate-rfc-v2-4-eb5143b4c487@gmail.com> (raw)
In-Reply-To: <20261028-keyupdate-rfc-v2-0-eb5143b4c487@gmail.com>

From: Martin Belanger <martin.belanger@dell.com>

RFC 9846 section 5.5 requires a key update before the key reaches its
usage limit, 2^24.5 full-size records for AES-GCM. The limit counts
records, and only the record layer knows where records start and end.
Section 4.7.3 also requires a reply to a KeyUpdate with
update_requested, sent before the next Application Data record. Both
need a KeyUpdate at an exact position in the record stream.

For a socket with a registered TX secret, send a KeyUpdate before the
next record when the key has protected 2^24 records, as GnuTLS does,
or when the peer asked for one. ChaCha20-Poly1305 has no record limit.
The KeyUpdate is sent as a handshake record, which closes the open data
record first. Pending encryptions are then completed, and only then is
the next key installed.

Add TLS_TX_KEY_UPDATE to send a KeyUpdate now, optionally asking the
peer to update too. It can only bring a KeyUpdate forward: the record
limit is enforced either way. Following section 4.7.3, a new request
to the peer waits for the answer to the previous one, and no more than
2^48-1 epochs are used.

Assisted-by: LLM
Signed-off-by: Martin Belanger <martin.belanger@dell.com>
---
 Documentation/networking/tls.rst |  17 +++++
 include/net/tls.h                |   5 +-
 include/uapi/linux/tls.h         |   1 +
 net/tls/tls.h                    |   1 +
 net/tls/tls_keyupdate.c          |   5 ++
 net/tls/tls_main.c               |  38 +++++++++-
 net/tls/tls_sw.c                 | 150 ++++++++++++++++++++++++++++++++++++++-
 7 files changed, 214 insertions(+), 3 deletions(-)

diff --git a/Documentation/networking/tls.rst b/Documentation/networking/tls.rst
index 2e011bd1ce4a..69752a6d67df 100644
--- a/Documentation/networking/tls.rst
+++ b/Documentation/networking/tls.rst
@@ -326,6 +326,23 @@ direction is then left to the application again.
 
 The secrets cannot be read back.
 
+With a registered TX secret, the kernel sends a KeyUpdate before the key
+reaches its record limit (RFC 9846, section 5.5): after 2^24 records, except
+for ChaCha20-Poly1305, which has no limit. It also sends one before the next
+record when a received KeyUpdate asks for it. With a registered RX secret,
+the kernel processes a received KeyUpdate and does not pass the record to
+the application. A peer that sends more than 8 KeyUpdates in one second ends
+the connection.
+
+TLS_TX_KEY_UPDATE
+~~~~~~~~~~~~~~~~~
+
+TLS 1.3 only. Send a KeyUpdate now and install the next TX key. Needs a
+registered TX secret. The value is an ``int``: 0 to send a KeyUpdate, 1 to
+also ask the peer to update its key. Asking the peer needs a registered RX
+secret, and fails with ``EBUSY`` until the peer has answered the previous
+request.
+
 Statistics
 ==========
 
diff --git a/include/net/tls.h b/include/net/tls.h
index a5bc28a7d1f5..d3123b17030c 100644
--- a/include/net/tls.h
+++ b/include/net/tls.h
@@ -282,9 +282,12 @@ struct tls_context {
 	union tls_crypto_context crypto_send;
 	union tls_crypto_context crypto_recv;
 
-	/* Registered for KeyUpdate, protected by the socket lock */
+	/* KeyUpdate state, protected by the socket lock */
 	struct tls_traffic_secret tx_secret;
 	struct tls_traffic_secret rx_secret;
+	u64 tx_key_updates;		/* KeyUpdates sent */
+	bool tx_key_update_reply;	/* peer set update_requested */
+	bool tx_key_update_asked;	/* we set it, no answer yet */
 
 	struct list_head list;
 	refcount_t refcount;
diff --git a/include/uapi/linux/tls.h b/include/uapi/linux/tls.h
index d1b136ecea96..c85408cb6ce7 100644
--- a/include/uapi/linux/tls.h
+++ b/include/uapi/linux/tls.h
@@ -44,6 +44,7 @@
 #define TLS_TX_MAX_PAYLOAD_LEN	5	/* Maximum plaintext size */
 #define TLS_TX_TRAFFIC_SECRET	6	/* TLS 1.3 TX traffic secret */
 #define TLS_RX_TRAFFIC_SECRET	7	/* TLS 1.3 RX traffic secret */
+#define TLS_TX_KEY_UPDATE	8	/* Send a TLS 1.3 KeyUpdate now */
 
 /* Supported versions */
 #define TLS_VERSION_MINOR(ver)	((ver) & 0xFF)
diff --git a/net/tls/tls.h b/net/tls/tls.h
index 63ece8074985..d12c9278c814 100644
--- a/net/tls/tls.h
+++ b/net/tls/tls.h
@@ -151,6 +151,7 @@ int tls_hkdf_expand_label(const u8 *secret, size_t secret_len,
 			  const char *label, u8 *out, size_t out_len);
 int tls_keyupdate_rekey(struct sock *sk, int tx);
 int tls_keyupdate_rx(struct sock *sk, struct sk_buff *skb);
+int tls_sw_key_update(struct sock *sk, int request);
 int tls_set_sw_offload(struct sock *sk, int tx,
 		       struct tls_crypto_info *new_crypto_info);
 void tls_update_rx_zc_capable(struct tls_context *tls_ctx);
diff --git a/net/tls/tls_keyupdate.c b/net/tls/tls_keyupdate.c
index 543bf57599c7..851d5b54b336 100644
--- a/net/tls/tls_keyupdate.c
+++ b/net/tls/tls_keyupdate.c
@@ -146,5 +146,10 @@ int tls_keyupdate_rx(struct sock *sk, struct sk_buff *skb)
 	}
 	TLS_INC_STATS(sock_net(sk), LINUX_MIB_TLSRXREKEYOK);
 
+	/* Any KeyUpdate from the peer answers our update_requested */
+	ctx->tx_key_update_asked = false;
+	if (msg[4] == TLS_KEY_UPDATE_REQUESTED)
+		ctx->tx_key_update_reply = true;
+
 	return 0;
 }
diff --git a/net/tls/tls_main.c b/net/tls/tls_main.c
index 8bb1b2773f3f..d71de99d8890 100644
--- a/net/tls/tls_main.c
+++ b/net/tls/tls_main.c
@@ -765,10 +765,14 @@ static int do_tls_setsockopt_conf(struct sock *sk, sockptr_t optval,
 		/* The registered secret no longer matches the key. The RX
 		 * secret also needs the TX one, see do_tls_setsockopt_secret().
 		 */
-		if (tx)
+		if (tx) {
 			memzero_explicit(&ctx->tx_secret,
 					 sizeof(ctx->tx_secret));
+			ctx->tx_key_update_reply = false;
+		}
 		memzero_explicit(&ctx->rx_secret, sizeof(ctx->rx_secret));
+		/* the answer now goes to user space */
+		ctx->tx_key_update_asked = false;
 		return 0;
 	}
 
@@ -987,6 +991,35 @@ static int do_tls_setsockopt_secret(struct sock *sk, sockptr_t optval,
 	return rc;
 }
 
+static int do_tls_setsockopt_key_update(struct sock *sk, sockptr_t optval,
+					unsigned int optlen)
+{
+	struct tls_context *ctx = tls_get_ctx(sk);
+	int value, rc;
+
+	if (sockptr_is_null(optval) || optlen != sizeof(value))
+		return -EINVAL;
+
+	if (copy_from_sockptr(&value, optval, sizeof(value)))
+		return -EFAULT;
+
+	if (value != 0 && value != 1)
+		return -EINVAL;
+
+	rc = mutex_lock_interruptible(&ctx->tx_lock);
+	if (rc)
+		return rc;
+	lock_sock(sk);
+	if (ctx->tx_conf == TLS_SW)
+		rc = tls_sw_key_update(sk, value);
+	else
+		rc = -EINVAL;
+	release_sock(sk);
+	mutex_unlock(&ctx->tx_lock);
+
+	return rc;
+}
+
 static int do_tls_setsockopt(struct sock *sk, int optname, sockptr_t optval,
 			     unsigned int optlen)
 {
@@ -1020,6 +1053,9 @@ static int do_tls_setsockopt(struct sock *sk, int optname, sockptr_t optval,
 					      optname == TLS_TX_TRAFFIC_SECRET);
 		release_sock(sk);
 		break;
+	case TLS_TX_KEY_UPDATE:
+		rc = do_tls_setsockopt_key_update(sk, optval, optlen);
+		break;
 	default:
 		rc = -ENOPROTOOPT;
 		break;
diff --git a/net/tls/tls_sw.c b/net/tls/tls_sw.c
index cc0177320e7e..4fe78624859d 100644
--- a/net/tls/tls_sw.c
+++ b/net/tls/tls_sw.c
@@ -40,6 +40,7 @@
 #include <linux/module.h>
 #include <linux/kernel.h>
 #include <linux/splice.h>
+#include <linux/unaligned.h>
 #include <crypto/aead.h>
 
 #include <net/strparser.h>
@@ -1005,6 +1006,151 @@ static int tls_sw_sendmsg_locked(struct sock *sk, struct msghdr *msg,
 	return copied > 0 ? copied : ret;
 }
 
+/* RFC 9846 section 5.5 allows 2^24.5 full-size records per AES-GCM key.
+ * Send the KeyUpdate a little earlier.
+ */
+#define TLS_KEYUPDATE_TX_RECORDS	BIT_ULL(24)
+
+/* Section 4.7.3: the epoch must not exceed 2^48-1. The application
+ * traffic secrets start at epoch 3.
+ */
+#define TLS_KEYUPDATE_MAX		(BIT_ULL(48) - 4)
+
+/* Send a KeyUpdate with the current key, then install the next key.
+ * Called with tx_lock held and the socket locked.
+ */
+static int tls_tx_key_update(struct sock *sk, u8 request, int flags)
+{
+	u8 body[TLS_KEYUPDATE_MSG_SIZE] = {
+		TLS_HANDSHAKE_KEYUPDATE, 0, 0, 1, request
+	};
+	struct tls_context *tls_ctx = tls_get_ctx(sk);
+	struct tls_sw_context_tx *ctx = tls_sw_ctx_tx(tls_ctx);
+	u8 record_type = TLS_RECORD_TYPE_HANDSHAKE;
+	u8 buf[CMSG_SPACE(sizeof(record_type))];
+	struct msghdr msg = {};
+	struct cmsghdr *cmsg;
+	struct kvec iov;
+	int ret;
+
+	memset(buf, 0, sizeof(buf));
+	msg.msg_control = buf;
+	msg.msg_controllen = sizeof(buf);
+	msg.msg_flags = flags & (MSG_DONTWAIT | MSG_NOSIGNAL);
+
+	cmsg = CMSG_FIRSTHDR(&msg);
+	cmsg->cmsg_level = SOL_TLS;
+	cmsg->cmsg_type = TLS_SET_RECORD_TYPE;
+	cmsg->cmsg_len = CMSG_LEN(sizeof(record_type));
+	memcpy(CMSG_DATA(cmsg), &record_type, sizeof(record_type));
+
+	iov.iov_base = body;
+	iov.iov_len = sizeof(body);
+	iov_iter_kvec(&msg.msg_iter, ITER_SOURCE, &iov, 1, sizeof(body));
+
+	/* A control record closes the open record first, so all data
+	 * written so far goes out with the current key, ahead of the
+	 * KeyUpdate.
+	 */
+	ret = tls_sw_sendmsg_locked(sk, &msg, sizeof(body));
+	if (ret < 0)
+		return ret;
+
+	/* Nothing may be encrypted with the next key before the KeyUpdate
+	 * is encrypted with the current one.
+	 */
+	ret = tls_encrypt_async_wait(ctx);
+	if (!ret)
+		ret = tls_keyupdate_rekey(sk, 1);
+	if (ret) {
+		/* The peer expects the next key: the connection is lost */
+		TLS_INC_STATS(sock_net(sk), LINUX_MIB_TLSTXREKEYERROR);
+		tls_err_abort(sk, ret);
+		return ret;
+	}
+
+	TLS_INC_STATS(sock_net(sk), LINUX_MIB_TLSTXREKEYOK);
+	tls_ctx->tx_key_updates++;
+	if (request == TLS_KEY_UPDATE_NOT_REQUESTED)
+		tls_ctx->tx_key_update_reply = false;
+
+	return 0;
+}
+
+/* Send a KeyUpdate if one is due: in reply to the peer's
+ * update_requested (RFC 9846 section 4.7.3), or before the key reaches
+ * its record limit (section 5.5). ChaCha20-Poly1305 has no record limit:
+ * the sequence number would wrap first.
+ */
+static int tls_tx_key_update_if_due(struct sock *sk, int flags)
+{
+	struct tls_context *ctx = tls_get_ctx(sk);
+	bool limit;
+
+	if (likely(!ctx->tx_secret.len))
+		return 0;
+
+	limit = ctx->prot_info.cipher_type != TLS_CIPHER_CHACHA20_POLY1305 &&
+		get_unaligned_be64(ctx->tx.rec_seq) >= TLS_KEYUPDATE_TX_RECORDS;
+	if (!limit && !ctx->tx_key_update_reply)
+		return 0;
+
+	if (ctx->tx_key_updates >= TLS_KEYUPDATE_MAX) {
+		/* Ignore update_requested. At the record limit the
+		 * connection must end.
+		 */
+		ctx->tx_key_update_reply = false;
+		return limit ? -EKEYEXPIRED : 0;
+	}
+
+	return tls_tx_key_update(sk, TLS_KEY_UPDATE_NOT_REQUESTED, flags);
+}
+
+/**
+ * tls_sw_key_update - send a KeyUpdate now, for TLS_TX_KEY_UPDATE
+ * @sk: socket
+ * @request: non-zero to also ask the peer to update its key
+ *
+ * Called with tx_lock held and the socket locked.
+ *
+ * Return: 0 on success, or a negative errno.
+ */
+int tls_sw_key_update(struct sock *sk, int request)
+{
+	struct tls_context *ctx = tls_get_ctx(sk);
+	int ret;
+
+	if (!ctx->tx_secret.len)
+		return -EINVAL;
+
+	if (request) {
+		/* Only the kernel sees the answer */
+		if (!ctx->rx_secret.len)
+			return -EINVAL;
+		/* Section 4.7.3: wait for the answer to the previous one */
+		if (ctx->tx_key_update_asked)
+			return -EBUSY;
+	}
+
+	if (ctx->tx_key_updates >= TLS_KEYUPDATE_MAX - 1)
+		return -EKEYEXPIRED;
+
+	/* A reply must carry update_not_requested */
+	if (request && ctx->tx_key_update_reply) {
+		ret = tls_tx_key_update(sk, TLS_KEY_UPDATE_NOT_REQUESTED,
+					MSG_NOSIGNAL);
+		if (ret)
+			return ret;
+	}
+
+	ret = tls_tx_key_update(sk, request ? TLS_KEY_UPDATE_REQUESTED :
+				TLS_KEY_UPDATE_NOT_REQUESTED, MSG_NOSIGNAL);
+	if (!ret && request)
+		ctx->tx_key_update_asked = true;
+
+	return ret;
+}
+
 int tls_sw_sendmsg(struct sock *sk, struct msghdr *msg, size_t size)
 {
 	struct tls_context *tls_ctx = tls_get_ctx(sk);
@@ -1019,7 +1165,9 @@ int tls_sw_sendmsg(struct sock *sk, struct msghdr *msg, size_t size)
 	if (ret)
 		return ret;
 	lock_sock(sk);
-	ret = tls_sw_sendmsg_locked(sk, msg, size);
+	ret = tls_tx_key_update_if_due(sk, msg->msg_flags);
+	if (!ret)
+		ret = tls_sw_sendmsg_locked(sk, msg, size);
 	release_sock(sk);
 	mutex_unlock(&tls_ctx->tx_lock);
 	return ret;

-- 
2.53.0



WARNING: multiple messages have this Message-ID (diff)
From: Martin Belanger <martin.nvme.dev@gmail.com>
To: John Fastabend <john.fastabend@gmail.com>,
	 Jakub Kicinski <kuba@kernel.org>,
	Sabrina Dubroca <sd@queasysnail.net>,
	 "David S. Miller" <davem@davemloft.net>,
	Eric Dumazet <edumazet@kernel.org>,
	 Paolo Abeni <pabeni@redhat.com>, Simon Horman <horms@kernel.org>,
	 Jonathan Corbet <corbet@lwn.net>,
	Shuah Khan <skhan@linuxfoundation.org>,
	 Randy Dunlap <rdunlap@infradead.org>,
	Shuah Khan <shuah@kernel.org>
Cc: linux-kernel@vger.kernel.org, netdev@vger.kernel.org,
	 linux-doc@vger.kernel.org, linux-kselftest@vger.kernel.org,
	 Hannes Reinecke <hare@kernel.org>, Chuck Lever <cel@kernel.org>,
	 kernel-tls-handshake@lists.linux.dev,
	linux-nvme@lists.infradead.org,
	 Alistair Francis <alistair.francis@wdc.com>,
	 Martin Belanger <martin.belanger@dell.com>
Subject: [PATCH RFC net-next v2 4/5] tls: send a KeyUpdate at the record limit and on request
Date: Wed, 28 Oct 2026 09:52:54 -0400	[thread overview]
Message-ID: <20261028-keyupdate-rfc-v2-4-eb5143b4c487@gmail.com> (raw)
In-Reply-To: <20261028-keyupdate-rfc-v2-0-eb5143b4c487@gmail.com>

From: Martin Belanger <martin.belanger@dell.com>

RFC 9846 section 5.5 requires a key update before the key reaches its
usage limit, 2^24.5 full-size records for AES-GCM. The limit counts
records, and only the record layer knows where records start and end.
Section 4.7.3 also requires a reply to a KeyUpdate with
update_requested, sent before the next Application Data record. Both
need a KeyUpdate at an exact position in the record stream.

For a socket with a registered TX secret, send a KeyUpdate before the
next record when the key has protected 2^24 records, as GnuTLS does,
or when the peer asked for one. ChaCha20-Poly1305 has no record limit.
The KeyUpdate is sent as a handshake record, which closes the open data
record first. Pending encryptions are then completed, and only then is
the next key installed.

Add TLS_TX_KEY_UPDATE to send a KeyUpdate now, optionally asking the
peer to update too. It can only bring a KeyUpdate forward: the record
limit is enforced either way. Following section 4.7.3, a new request
to the peer waits for the answer to the previous one, and no more than
2^48-1 epochs are used.

Assisted-by: LLM
Signed-off-by: Martin Belanger <martin.belanger@dell.com>
---
 Documentation/networking/tls.rst |  17 +++++
 include/net/tls.h                |   5 +-
 include/uapi/linux/tls.h         |   1 +
 net/tls/tls.h                    |   1 +
 net/tls/tls_keyupdate.c          |   5 ++
 net/tls/tls_main.c               |  38 +++++++++-
 net/tls/tls_sw.c                 | 150 ++++++++++++++++++++++++++++++++++++++-
 7 files changed, 214 insertions(+), 3 deletions(-)

diff --git a/Documentation/networking/tls.rst b/Documentation/networking/tls.rst
index 2e011bd1ce4a..69752a6d67df 100644
--- a/Documentation/networking/tls.rst
+++ b/Documentation/networking/tls.rst
@@ -326,6 +326,23 @@ direction is then left to the application again.
 
 The secrets cannot be read back.
 
+With a registered TX secret, the kernel sends a KeyUpdate before the key
+reaches its record limit (RFC 9846, section 5.5): after 2^24 records, except
+for ChaCha20-Poly1305, which has no limit. It also sends one before the next
+record when a received KeyUpdate asks for it. With a registered RX secret,
+the kernel processes a received KeyUpdate and does not pass the record to
+the application. A peer that sends more than 8 KeyUpdates in one second ends
+the connection.
+
+TLS_TX_KEY_UPDATE
+~~~~~~~~~~~~~~~~~
+
+TLS 1.3 only. Send a KeyUpdate now and install the next TX key. Needs a
+registered TX secret. The value is an ``int``: 0 to send a KeyUpdate, 1 to
+also ask the peer to update its key. Asking the peer needs a registered RX
+secret, and fails with ``EBUSY`` until the peer has answered the previous
+request.
+
 Statistics
 ==========
 
diff --git a/include/net/tls.h b/include/net/tls.h
index a5bc28a7d1f5..d3123b17030c 100644
--- a/include/net/tls.h
+++ b/include/net/tls.h
@@ -282,9 +282,12 @@ struct tls_context {
 	union tls_crypto_context crypto_send;
 	union tls_crypto_context crypto_recv;
 
-	/* Registered for KeyUpdate, protected by the socket lock */
+	/* KeyUpdate state, protected by the socket lock */
 	struct tls_traffic_secret tx_secret;
 	struct tls_traffic_secret rx_secret;
+	u64 tx_key_updates;		/* KeyUpdates sent */
+	bool tx_key_update_reply;	/* peer set update_requested */
+	bool tx_key_update_asked;	/* we set it, no answer yet */
 
 	struct list_head list;
 	refcount_t refcount;
diff --git a/include/uapi/linux/tls.h b/include/uapi/linux/tls.h
index d1b136ecea96..c85408cb6ce7 100644
--- a/include/uapi/linux/tls.h
+++ b/include/uapi/linux/tls.h
@@ -44,6 +44,7 @@
 #define TLS_TX_MAX_PAYLOAD_LEN	5	/* Maximum plaintext size */
 #define TLS_TX_TRAFFIC_SECRET	6	/* TLS 1.3 TX traffic secret */
 #define TLS_RX_TRAFFIC_SECRET	7	/* TLS 1.3 RX traffic secret */
+#define TLS_TX_KEY_UPDATE	8	/* Send a TLS 1.3 KeyUpdate now */
 
 /* Supported versions */
 #define TLS_VERSION_MINOR(ver)	((ver) & 0xFF)
diff --git a/net/tls/tls.h b/net/tls/tls.h
index 63ece8074985..d12c9278c814 100644
--- a/net/tls/tls.h
+++ b/net/tls/tls.h
@@ -151,6 +151,7 @@ int tls_hkdf_expand_label(const u8 *secret, size_t secret_len,
 			  const char *label, u8 *out, size_t out_len);
 int tls_keyupdate_rekey(struct sock *sk, int tx);
 int tls_keyupdate_rx(struct sock *sk, struct sk_buff *skb);
+int tls_sw_key_update(struct sock *sk, int request);
 int tls_set_sw_offload(struct sock *sk, int tx,
 		       struct tls_crypto_info *new_crypto_info);
 void tls_update_rx_zc_capable(struct tls_context *tls_ctx);
diff --git a/net/tls/tls_keyupdate.c b/net/tls/tls_keyupdate.c
index 543bf57599c7..851d5b54b336 100644
--- a/net/tls/tls_keyupdate.c
+++ b/net/tls/tls_keyupdate.c
@@ -146,5 +146,10 @@ int tls_keyupdate_rx(struct sock *sk, struct sk_buff *skb)
 	}
 	TLS_INC_STATS(sock_net(sk), LINUX_MIB_TLSRXREKEYOK);
 
+	/* Any KeyUpdate from the peer answers our update_requested */
+	ctx->tx_key_update_asked = false;
+	if (msg[4] == TLS_KEY_UPDATE_REQUESTED)
+		ctx->tx_key_update_reply = true;
+
 	return 0;
 }
diff --git a/net/tls/tls_main.c b/net/tls/tls_main.c
index 8bb1b2773f3f..d71de99d8890 100644
--- a/net/tls/tls_main.c
+++ b/net/tls/tls_main.c
@@ -765,10 +765,14 @@ static int do_tls_setsockopt_conf(struct sock *sk, sockptr_t optval,
 		/* The registered secret no longer matches the key. The RX
 		 * secret also needs the TX one, see do_tls_setsockopt_secret().
 		 */
-		if (tx)
+		if (tx) {
 			memzero_explicit(&ctx->tx_secret,
 					 sizeof(ctx->tx_secret));
+			ctx->tx_key_update_reply = false;
+		}
 		memzero_explicit(&ctx->rx_secret, sizeof(ctx->rx_secret));
+		/* the answer now goes to user space */
+		ctx->tx_key_update_asked = false;
 		return 0;
 	}
 
@@ -987,6 +991,35 @@ static int do_tls_setsockopt_secret(struct sock *sk, sockptr_t optval,
 	return rc;
 }
 
+static int do_tls_setsockopt_key_update(struct sock *sk, sockptr_t optval,
+					unsigned int optlen)
+{
+	struct tls_context *ctx = tls_get_ctx(sk);
+	int value, rc;
+
+	if (sockptr_is_null(optval) || optlen != sizeof(value))
+		return -EINVAL;
+
+	if (copy_from_sockptr(&value, optval, sizeof(value)))
+		return -EFAULT;
+
+	if (value != 0 && value != 1)
+		return -EINVAL;
+
+	rc = mutex_lock_interruptible(&ctx->tx_lock);
+	if (rc)
+		return rc;
+	lock_sock(sk);
+	if (ctx->tx_conf == TLS_SW)
+		rc = tls_sw_key_update(sk, value);
+	else
+		rc = -EINVAL;
+	release_sock(sk);
+	mutex_unlock(&ctx->tx_lock);
+
+	return rc;
+}
+
 static int do_tls_setsockopt(struct sock *sk, int optname, sockptr_t optval,
 			     unsigned int optlen)
 {
@@ -1020,6 +1053,9 @@ static int do_tls_setsockopt(struct sock *sk, int optname, sockptr_t optval,
 					      optname == TLS_TX_TRAFFIC_SECRET);
 		release_sock(sk);
 		break;
+	case TLS_TX_KEY_UPDATE:
+		rc = do_tls_setsockopt_key_update(sk, optval, optlen);
+		break;
 	default:
 		rc = -ENOPROTOOPT;
 		break;
diff --git a/net/tls/tls_sw.c b/net/tls/tls_sw.c
index cc0177320e7e..4fe78624859d 100644
--- a/net/tls/tls_sw.c
+++ b/net/tls/tls_sw.c
@@ -40,6 +40,7 @@
 #include <linux/module.h>
 #include <linux/kernel.h>
 #include <linux/splice.h>
+#include <linux/unaligned.h>
 #include <crypto/aead.h>
 
 #include <net/strparser.h>
@@ -1005,6 +1006,151 @@ static int tls_sw_sendmsg_locked(struct sock *sk, struct msghdr *msg,
 	return copied > 0 ? copied : ret;
 }
 
+/* RFC 9846 section 5.5 allows 2^24.5 full-size records per AES-GCM key.
+ * Send the KeyUpdate a little earlier.
+ */
+#define TLS_KEYUPDATE_TX_RECORDS	BIT_ULL(24)
+
+/* Section 4.7.3: the epoch must not exceed 2^48-1. The application
+ * traffic secrets start at epoch 3.
+ */
+#define TLS_KEYUPDATE_MAX		(BIT_ULL(48) - 4)
+
+/* Send a KeyUpdate with the current key, then install the next key.
+ * Called with tx_lock held and the socket locked.
+ */
+static int tls_tx_key_update(struct sock *sk, u8 request, int flags)
+{
+	u8 body[TLS_KEYUPDATE_MSG_SIZE] = {
+		TLS_HANDSHAKE_KEYUPDATE, 0, 0, 1, request
+	};
+	struct tls_context *tls_ctx = tls_get_ctx(sk);
+	struct tls_sw_context_tx *ctx = tls_sw_ctx_tx(tls_ctx);
+	u8 record_type = TLS_RECORD_TYPE_HANDSHAKE;
+	u8 buf[CMSG_SPACE(sizeof(record_type))];
+	struct msghdr msg = {};
+	struct cmsghdr *cmsg;
+	struct kvec iov;
+	int ret;
+
+	memset(buf, 0, sizeof(buf));
+	msg.msg_control = buf;
+	msg.msg_controllen = sizeof(buf);
+	msg.msg_flags = flags & (MSG_DONTWAIT | MSG_NOSIGNAL);
+
+	cmsg = CMSG_FIRSTHDR(&msg);
+	cmsg->cmsg_level = SOL_TLS;
+	cmsg->cmsg_type = TLS_SET_RECORD_TYPE;
+	cmsg->cmsg_len = CMSG_LEN(sizeof(record_type));
+	memcpy(CMSG_DATA(cmsg), &record_type, sizeof(record_type));
+
+	iov.iov_base = body;
+	iov.iov_len = sizeof(body);
+	iov_iter_kvec(&msg.msg_iter, ITER_SOURCE, &iov, 1, sizeof(body));
+
+	/* A control record closes the open record first, so all data
+	 * written so far goes out with the current key, ahead of the
+	 * KeyUpdate.
+	 */
+	ret = tls_sw_sendmsg_locked(sk, &msg, sizeof(body));
+	if (ret < 0)
+		return ret;
+
+	/* Nothing may be encrypted with the next key before the KeyUpdate
+	 * is encrypted with the current one.
+	 */
+	ret = tls_encrypt_async_wait(ctx);
+	if (!ret)
+		ret = tls_keyupdate_rekey(sk, 1);
+	if (ret) {
+		/* The peer expects the next key: the connection is lost */
+		TLS_INC_STATS(sock_net(sk), LINUX_MIB_TLSTXREKEYERROR);
+		tls_err_abort(sk, ret);
+		return ret;
+	}
+
+	TLS_INC_STATS(sock_net(sk), LINUX_MIB_TLSTXREKEYOK);
+	tls_ctx->tx_key_updates++;
+	if (request == TLS_KEY_UPDATE_NOT_REQUESTED)
+		tls_ctx->tx_key_update_reply = false;
+
+	return 0;
+}
+
+/* Send a KeyUpdate if one is due: in reply to the peer's
+ * update_requested (RFC 9846 section 4.7.3), or before the key reaches
+ * its record limit (section 5.5). ChaCha20-Poly1305 has no record limit:
+ * the sequence number would wrap first.
+ */
+static int tls_tx_key_update_if_due(struct sock *sk, int flags)
+{
+	struct tls_context *ctx = tls_get_ctx(sk);
+	bool limit;
+
+	if (likely(!ctx->tx_secret.len))
+		return 0;
+
+	limit = ctx->prot_info.cipher_type != TLS_CIPHER_CHACHA20_POLY1305 &&
+		get_unaligned_be64(ctx->tx.rec_seq) >= TLS_KEYUPDATE_TX_RECORDS;
+	if (!limit && !ctx->tx_key_update_reply)
+		return 0;
+
+	if (ctx->tx_key_updates >= TLS_KEYUPDATE_MAX) {
+		/* Ignore update_requested. At the record limit the
+		 * connection must end.
+		 */
+		ctx->tx_key_update_reply = false;
+		return limit ? -EKEYEXPIRED : 0;
+	}
+
+	return tls_tx_key_update(sk, TLS_KEY_UPDATE_NOT_REQUESTED, flags);
+}
+
+/**
+ * tls_sw_key_update - send a KeyUpdate now, for TLS_TX_KEY_UPDATE
+ * @sk: socket
+ * @request: non-zero to also ask the peer to update its key
+ *
+ * Called with tx_lock held and the socket locked.
+ *
+ * Return: 0 on success, or a negative errno.
+ */
+int tls_sw_key_update(struct sock *sk, int request)
+{
+	struct tls_context *ctx = tls_get_ctx(sk);
+	int ret;
+
+	if (!ctx->tx_secret.len)
+		return -EINVAL;
+
+	if (request) {
+		/* Only the kernel sees the answer */
+		if (!ctx->rx_secret.len)
+			return -EINVAL;
+		/* Section 4.7.3: wait for the answer to the previous one */
+		if (ctx->tx_key_update_asked)
+			return -EBUSY;
+	}
+
+	if (ctx->tx_key_updates >= TLS_KEYUPDATE_MAX - 1)
+		return -EKEYEXPIRED;
+
+	/* A reply must carry update_not_requested */
+	if (request && ctx->tx_key_update_reply) {
+		ret = tls_tx_key_update(sk, TLS_KEY_UPDATE_NOT_REQUESTED,
+					MSG_NOSIGNAL);
+		if (ret)
+			return ret;
+	}
+
+	ret = tls_tx_key_update(sk, request ? TLS_KEY_UPDATE_REQUESTED :
+				TLS_KEY_UPDATE_NOT_REQUESTED, MSG_NOSIGNAL);
+	if (!ret && request)
+		ctx->tx_key_update_asked = true;
+
+	return ret;
+}
+
 int tls_sw_sendmsg(struct sock *sk, struct msghdr *msg, size_t size)
 {
 	struct tls_context *tls_ctx = tls_get_ctx(sk);
@@ -1019,7 +1165,9 @@ int tls_sw_sendmsg(struct sock *sk, struct msghdr *msg, size_t size)
 	if (ret)
 		return ret;
 	lock_sock(sk);
-	ret = tls_sw_sendmsg_locked(sk, msg, size);
+	ret = tls_tx_key_update_if_due(sk, msg->msg_flags);
+	if (!ret)
+		ret = tls_sw_sendmsg_locked(sk, msg, size);
 	release_sock(sk);
 	mutex_unlock(&tls_ctx->tx_lock);
 	return ret;

-- 
2.53.0


  parent reply	other threads:[~2026-10-28 13:52 UTC|newest]

Thread overview: 24+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-28 13:52 [PATCH RFC net-next v2 0/5] tls: perform TLS 1.3 KeyUpdate in the kernel Martin Belanger via B4 Relay
2026-10-28 13:52 ` Martin Belanger
2026-10-28 13:52 ` [PATCH RFC net-next v2 1/5] tls: add HKDF-Expand-Label for the TLS 1.3 key schedule Martin Belanger via B4 Relay
2026-10-28 13:52   ` Martin Belanger
2026-10-28 13:52 ` [PATCH RFC net-next v2 2/5] tls: add socket options to register the traffic secrets Martin Belanger via B4 Relay
2026-10-28 13:52   ` Martin Belanger
2026-10-28 14:13   ` Hannes Reinecke
2026-10-28 13:52 ` [PATCH RFC net-next v2 3/5] tls: update the RX key when a KeyUpdate is received Martin Belanger via B4 Relay
2026-10-28 13:52   ` Martin Belanger
2026-10-28 14:19   ` Hannes Reinecke
2026-10-28 17:13     ` Martin Belanger
2026-10-28 13:52 ` Martin Belanger via B4 Relay [this message]
2026-10-28 13:52   ` [PATCH RFC net-next v2 4/5] tls: send a KeyUpdate at the record limit and on request Martin Belanger
2026-10-28 14:23   ` Hannes Reinecke
2026-10-28 17:18     ` Martin Belanger
2026-10-29 13:53   ` sashiko-bot
2026-10-28 13:52 ` [PATCH RFC net-next v2 5/5] selftests/tls: add KeyUpdate tests Martin Belanger via B4 Relay
2026-10-28 13:52   ` Martin Belanger
2026-10-28 14:24   ` Hannes Reinecke
2026-10-28 14:21 ` [PATCH RFC net-next v2 0/5] tls: perform TLS 1.3 KeyUpdate in the kernel Chuck Lever
2026-10-28 14:42   ` Hannes Reinecke
     [not found]     ` <CAPG1HMsnUDa=Q5O4fwZOU=Pd+m7ahzu574Cs+mWoB_9KUT5ASA@mail.gmail.com>
2026-10-28 15:01       ` Chuck Lever
2026-10-28 15:12         ` Martin Belanger
2026-10-28 15:04     ` Martin Belanger

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20261028-keyupdate-rfc-v2-4-eb5143b4c487@gmail.com \
    --to=devnull+martin.nvme.dev.gmail.com@kernel.org \
    --cc=alistair.francis@wdc.com \
    --cc=cel@kernel.org \
    --cc=corbet@lwn.net \
    --cc=davem@davemloft.net \
    --cc=edumazet@kernel.org \
    --cc=hare@kernel.org \
    --cc=horms@kernel.org \
    --cc=john.fastabend@gmail.com \
    --cc=kernel-tls-handshake@lists.linux.dev \
    --cc=kuba@kernel.org \
    --cc=linux-doc@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-kselftest@vger.kernel.org \
    --cc=linux-nvme@lists.infradead.org \
    --cc=martin.belanger@dell.com \
    --cc=martin.nvme.dev@gmail.com \
    --cc=netdev@vger.kernel.org \
    --cc=pabeni@redhat.com \
    --cc=rdunlap@infradead.org \
    --cc=sd@queasysnail.net \
    --cc=shuah@kernel.org \
    --cc=skhan@linuxfoundation.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.