git: af3398862ac0 - releng/14.3 - ktls: Don't attempt to modify non-anonymous mbufs on the receive path

From: Mark Johnston <markj_at_FreeBSD.org>
Date: Tue, 09 Jun 2026 19:18:35 UTC
The branch releng/14.3 has been updated by markj:

URL: https://cgit.FreeBSD.org/src/commit/?id=af3398862ac05421ec07eb134da394755c565356

commit af3398862ac05421ec07eb134da394755c565356
Author:     John Baldwin <jhb@FreeBSD.org>
AuthorDate: 2026-06-03 23:22:00 +0000
Commit:     Mark Johnston <markj@FreeBSD.org>
CommitDate: 2026-06-07 15:53:09 +0000

    ktls: Don't attempt to modify non-anonymous mbufs on the receive path
    
    Normally, data processed on the KTLS receive path is contained in
    anonymous mbufs that can be modified in place.  Either the data
    originates in receive buffers from a NIC driver, or for loopback
    connections the data is anonymous-backed mbufs created when writing to
    a socket.  One potential source of non-anonymous mbufs are mbufs
    created by sendfile(2) which borrow the pages of the underlying file,
    either via M_EXTPG or EXT_SFBUF that are sent over a loopback
    connection.  For a well-formed loopback TLS session, the sender should
    only use sendfile(2) if KTLS is enabled.  If TLS is fully handled in
    userspace, the sender must use write(2) or send(2) which allocate
    anonymous mbufs.  If KTLS transmit is enabled, then sendfile(2) on a
    loopback connection will always use crypto via OCF and will allocate
    anonymous pages to hold the encrypted data.
    
    However, if sendfile(2) is used to send file-backed data directly over
    a loopback connection where KTLS is not enabled on the sender side,
    the KTLS receive path can modify the file-backed pages in place
    overwriting the file's data.  One potential fix would be to replace
    non-anonymous mbufs in a received TLS record with anonymous mbufs
    (e.g. via m_dup()) before passing the record to OCF.  However, there
    is no legitimate use case for using sendfile(2) over a loopback TLS
    connection without using KTLS on the sender side, so instead simply
    fail decryption requests and close the connection if non-anonymous
    mbufs are encountered in the RX decryption path.
    
    Add a test for this that verifies that the original data backing the
    file descriptor used as the source for sendfile() is unchanged after
    being processed.
    
    Approved by:    so
    Security:       FreeBSD-SA-26:26.ktls
    Security:       CVE-2026-45257
    Co-authored-by: Drew Gallatin <gallatin@FreeBSD.org>
    Sponsored by:   Chelsio Communications
    Sponsored by:   Netflix
---
 sys/kern/uipc_ktls.c       | 17 +++++++--
 sys/sys/ktls.h             |  1 +
 tests/sys/kern/ktls_test.c | 93 ++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 108 insertions(+), 3 deletions(-)

diff --git a/sys/kern/uipc_ktls.c b/sys/kern/uipc_ktls.c
index 294a196db60d..ff1a94c57673 100644
--- a/sys/kern/uipc_ktls.c
+++ b/sys/kern/uipc_ktls.c
@@ -2329,8 +2329,10 @@ tls13_find_record_type(struct ktls_session *tls, struct mbuf *m, int tls_len,
  * Check if a mbuf chain is fully decrypted at the given offset and
  * length. Returns KTLS_MBUF_CRYPTO_ST_DECRYPTED if all data is
  * decrypted. KTLS_MBUF_CRYPTO_ST_MIXED if there is a mix of encrypted
- * and decrypted data. Else KTLS_MBUF_CRYPTO_ST_ENCRYPTED if all data
- * is encrypted.
+ * and decrypted data. KTLS_MBUF_CRYPTO_ST_ENCRYPTED if all data is
+ * encrypted. KTLS_MBUF_CRYPTO_ST_SHAREDMBUF if any mbuf points at
+ * shared data that must not be modified in place (non-anonymous
+ * M_EXTPG or sendfile M_EXT buffers).
  */
 ktls_mbuf_crypto_st_t
 ktls_mbuf_crypto_state(struct mbuf *mb, int offset, int len)
@@ -2346,6 +2348,13 @@ ktls_mbuf_crypto_state(struct mbuf *mb, int offset, int len)
 	offset += len;
 
 	for (; mb != NULL; mb = mb->m_next) {
+		if ((mb->m_flags & M_EXTPG) != 0 &&
+		    (mb->m_epg_flags & EPG_FLAG_ANON) == 0)
+			return (KTLS_MBUF_CRYPTO_ST_SHAREDMBUF);
+		if ((mb->m_flags & M_EXT) != 0 &&
+		    mb->m_ext.ext_type == EXT_SFBUF)
+			return (KTLS_MBUF_CRYPTO_ST_SHAREDMBUF);
+
 		m_flags_ored |= mb->m_flags;
 		m_flags_anded &= mb->m_flags;
 
@@ -2546,9 +2555,11 @@ ktls_decrypt(struct socket *so)
 				record_type = hdr->tls_type;
 			}
 			break;
-		default:
+		case KTLS_MBUF_CRYPTO_ST_SHAREDMBUF:
 			error = EINVAL;
 			break;
+		default:
+			__assert_unreachable();
 		}
 		if (error) {
 			counter_u64_add(ktls_offload_failed_crypto, 1);
diff --git a/sys/sys/ktls.h b/sys/sys/ktls.h
index 693864394ffe..0fdf4c4ceac7 100644
--- a/sys/sys/ktls.h
+++ b/sys/sys/ktls.h
@@ -210,6 +210,7 @@ typedef enum {
 	KTLS_MBUF_CRYPTO_ST_MIXED = 0,
 	KTLS_MBUF_CRYPTO_ST_ENCRYPTED = 1,
 	KTLS_MBUF_CRYPTO_ST_DECRYPTED = -1,
+	KTLS_MBUF_CRYPTO_ST_SHAREDMBUF = -2,
 } ktls_mbuf_crypto_st_t;
 
 void ktls_check_rx(struct sockbuf *sb);
diff --git a/tests/sys/kern/ktls_test.c b/tests/sys/kern/ktls_test.c
index 72497196b945..3970083e7f72 100644
--- a/tests/sys/kern/ktls_test.c
+++ b/tests/sys/kern/ktls_test.c
@@ -30,6 +30,7 @@
 #include <sys/endian.h>
 #include <sys/event.h>
 #include <sys/ktls.h>
+#include <sys/mman.h>
 #include <sys/socket.h>
 #include <sys/sysctl.h>
 #include <netinet/in.h>
@@ -2817,6 +2818,97 @@ ATF_TC_BODY(ktls_listening_socket, tc)
 	ATF_REQUIRE(close(s) == 0);
 }
 
+/*
+ * Verify that the KTLS receive path does not overwrite data belonging
+ * to a file whose payload is transmitted over a loopback connection
+ * via plain sendfile.
+ */
+ATF_TC_WITHOUT_HEAD(ktls_receive_loopback_sendfile);
+ATF_TC_BODY(ktls_receive_loopback_sendfile, tc)
+{
+	struct tls_enable en;
+	struct msghdr msg;
+	struct sf_hdtr hdtr;
+	struct iovec iov[2];
+	uint64_t seqno;
+	off_t sbytes;
+	char cbuf[CMSG_SPACE(sizeof(struct tls_get_record))];
+	char *plaintext, *ciphertext, *outbuf;
+	void *p;
+	const size_t payload_len = PAGE_SIZE;
+	ssize_t rv;
+	size_t len;
+	int mode, shm, sockets[2];
+	socklen_t slen;
+
+	ATF_REQUIRE_KTLS();
+	seqno = random();
+	build_tls_enable(tc, CRYPTO_AES_NIST_GCM_16, 128 / 8, 0,
+	    TLS_MINOR_VER_TWO, seqno, &en);
+
+	len = tls_header_len(&en) + payload_len + tls_trailer_len(&en);
+	plaintext = alloc_buffer(payload_len);
+	ciphertext = malloc(len);
+	ATF_REQUIRE_INTEQ(len, encrypt_tls_record(tc, &en, TLS_RLTYPE_APP,
+	    seqno, plaintext, payload_len, ciphertext, len, 0));
+
+	ATF_REQUIRE((shm = shm_open(SHM_ANON, O_RDWR, 0600)) > 0);
+	ATF_REQUIRE_INTEQ(0, ftruncate(shm, payload_len));
+	ATF_REQUIRE((p = mmap(NULL, payload_len, PROT_READ | PROT_WRITE,
+	    MAP_SHARED, shm, 0)) != MAP_FAILED);
+	memcpy(p, ciphertext + tls_header_len(&en), payload_len);
+
+	ATF_REQUIRE_MSG(socketpair_tcp(sockets), "failed to create sockets");
+	ATF_REQUIRE(setsockopt(sockets[0], IPPROTO_TCP, TCP_RXTLS_ENABLE, &en,
+	    sizeof(en)) == 0);
+	slen = sizeof(mode);
+	ATF_REQUIRE_INTEQ(0, getsockopt(sockets[0], IPPROTO_TCP, TCP_RXTLS_MODE,
+	    &mode, &slen));
+	ATF_REQUIRE_INTEQ(TCP_TLS_MODE_SW, mode);
+
+	fd_set_blocking(sockets[0]);
+	fd_set_blocking(sockets[1]);
+
+	iov[0].iov_base = ciphertext;
+	iov[0].iov_len = tls_header_len(&en);
+	iov[1].iov_base = ciphertext + tls_header_len(&en) + payload_len;
+	iov[1].iov_len = tls_trailer_len(&en);
+	hdtr.headers = iov;
+	hdtr.hdr_cnt = 1;
+	hdtr.trailers = iov + 1;
+	hdtr.trl_cnt = 1;
+	debug_hexdump(tc, p, payload_len, "shm buffer before");
+	ATF_REQUIRE_INTEQ(0, sendfile(shm, sockets[1], 0, payload_len, &hdtr,
+	    &sbytes, 0));
+	ATF_REQUIRE_INTEQ(sbytes, len);
+
+	outbuf = calloc(payload_len, 1);
+
+	memset(&msg, 0, sizeof(msg));
+
+	msg.msg_control = cbuf;
+	msg.msg_controllen = sizeof(cbuf);
+
+	iov[0].iov_base = outbuf;
+	iov[0].iov_len = payload_len;
+	msg.msg_iov = iov;
+	msg.msg_iovlen = 1;
+
+	rv = recvmsg(sockets[0], &msg, 0);
+	if (rv >= 0) {
+		ATF_REQUIRE_INTEQ(payload_len, rv);
+		ATF_REQUIRE_INTEQ(0, memcmp(outbuf, plaintext, payload_len));
+	} else
+		ATF_REQUIRE_ERRNO(EBADMSG, true);
+
+	debug_hexdump(tc, p, payload_len, "shm buffer after");
+	ATF_REQUIRE_INTEQ(0, memcmp(p, ciphertext + tls_header_len(&en),
+	    payload_len));
+
+	close_sockets_ignore_errors(sockets);
+	(void)close(shm);
+}
+
 ATF_TP_ADD_TCS(tp)
 {
 	/* Transmit tests */
@@ -2843,6 +2935,7 @@ ATF_TP_ADD_TCS(tp)
 	/* Miscellaneous */
 	ATF_TP_ADD_TC(tp, ktls_sendto_baddst);
 	ATF_TP_ADD_TC(tp, ktls_listening_socket);
+	ATF_TP_ADD_TC(tp, ktls_receive_loopback_sendfile);
 
 	return (atf_no_error());
 }