git: 1000cc4a0d39 - main - so_splice: Disallow splicing with KTLS-enabled sockets

From: Mark Johnston <markj_at_FreeBSD.org>
Date: Tue, 22 Apr 2025 15:59:40 UTC
The branch main has been updated by markj:

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

commit 1000cc4a0d39faeb3e681bb167ed38f164c56604
Author:     Mark Johnston <markj@FreeBSD.org>
AuthorDate: 2025-04-22 14:53:34 +0000
Commit:     Mark Johnston <markj@FreeBSD.org>
CommitDate: 2025-04-22 14:53:34 +0000

    so_splice: Disallow splicing with KTLS-enabled sockets
    
    Suppose the sink socket in a splice has KTLS enabled.  When data is
    transmitted from the source socket, sosend_generic_locked() receives an
    mbuf rather than a UIO as it would if userspace were transferring data.
    In this case, ktls_frame() expects the mbuf to be unmapped, but in
    general this won't be the case.
    
    Simply disallow the combination for now.  Modify so_unsplice() to handle
    dismantling a partially initialized splice, in order to simplify error
    handling in so_splice().  Make sure that one can't enable KTLS on a
    spliced socket, or more specifically, that one can't enable RXTLS on the
    source side of a splice, or TXTLS on the sink side of a splice.
    
    Reported by:    syzbot+9cc248c4b0ca9b931ab4@syzkaller.appspotmail.com
    Reviewed by:    gallatin
    MFC after:      2 weeks
    Differential Revision:  https://reviews.freebsd.org/D49920
---
 sys/kern/uipc_ktls.c   | 14 +++++++++++---
 sys/kern/uipc_socket.c | 42 ++++++++++++++++++++++++++----------------
 2 files changed, 37 insertions(+), 19 deletions(-)

diff --git a/sys/kern/uipc_ktls.c b/sys/kern/uipc_ktls.c
index 6815667594a4..5e9dd254debd 100644
--- a/sys/kern/uipc_ktls.c
+++ b/sys/kern/uipc_ktls.c
@@ -1332,7 +1332,11 @@ ktls_enable_rx(struct socket *so, struct tls_enable *en)
 
 	/* Mark the socket as using TLS offload. */
 	SOCK_RECVBUF_LOCK(so);
-	if (__predict_false(so->so_rcv.sb_tls_info != NULL)) {
+	if (__predict_false(so->so_rcv.sb_tls_info != NULL))
+		error = EALREADY;
+	else if ((so->so_rcv.sb_flags & SB_SPLICED) != 0)
+		error = EINVAL;
+	if (error != 0) {
 		SOCK_RECVBUF_UNLOCK(so);
 		SOCK_IO_RECV_UNLOCK(so);
 		ktls_free(tls);
@@ -1432,12 +1436,16 @@ ktls_enable_tx(struct socket *so, struct tls_enable *en)
 	inp = so->so_pcb;
 	INP_WLOCK(inp);
 	SOCK_SENDBUF_LOCK(so);
-	if (__predict_false(so->so_snd.sb_tls_info != NULL)) {
+	if (__predict_false(so->so_snd.sb_tls_info != NULL))
+		error = EALREADY;
+	else if ((so->so_snd.sb_flags & SB_SPLICED) != 0)
+		error = EINVAL;
+	if (error != 0) {
 		SOCK_SENDBUF_UNLOCK(so);
 		INP_WUNLOCK(inp);
 		SOCK_IO_SEND_UNLOCK(so);
 		ktls_free(tls);
-		return (EALREADY);
+		return (error);
 	}
 	so->so_snd.sb_tls_seqno = be64dec(en->rec_seq);
 	so->so_snd.sb_tls_info = tls;
diff --git a/sys/kern/uipc_socket.c b/sys/kern/uipc_socket.c
index 291a832189e5..10f81a959147 100644
--- a/sys/kern/uipc_socket.c
+++ b/sys/kern/uipc_socket.c
@@ -1699,10 +1699,16 @@ so_splice(struct socket *so, struct socket *so2, struct splice *splice)
 		uma_zfree(splice_zone, sp);
 		return (error);
 	}
-	soref(so);
-	so->so_splice = sp;
 	SOCK_RECVBUF_LOCK(so);
+	if (so->so_rcv.sb_tls_info != NULL) {
+		SOCK_RECVBUF_UNLOCK(so);
+		SOCK_UNLOCK(so);
+		uma_zfree(splice_zone, sp);
+		return (EINVAL);
+	}
 	so->so_rcv.sb_flags |= SB_SPLICED;
+	so->so_splice = sp;
+	soref(so);
 	SOCK_RECVBUF_UNLOCK(so);
 	SOCK_UNLOCK(so);
 
@@ -1716,20 +1722,19 @@ so_splice(struct socket *so, struct socket *so2, struct splice *splice)
 		error = EBUSY;
 	if (error != 0) {
 		SOCK_UNLOCK(so2);
-		SOCK_LOCK(so);
-		so->so_splice = NULL;
-		SOCK_RECVBUF_LOCK(so);
-		so->so_rcv.sb_flags &= ~SB_SPLICED;
-		SOCK_RECVBUF_UNLOCK(so);
-		SOCK_UNLOCK(so);
-		sorele(so);
-		uma_zfree(splice_zone, sp);
+		so_unsplice(so, false);
 		return (error);
 	}
-	soref(so2);
-	so2->so_splice_back = sp;
 	SOCK_SENDBUF_LOCK(so2);
+	if (so->so_snd.sb_tls_info != NULL) {
+		SOCK_SENDBUF_UNLOCK(so2);
+		SOCK_UNLOCK(so2);
+		so_unsplice(so, false);
+		return (EINVAL);
+	}
 	so2->so_snd.sb_flags |= SB_SPLICED;
+	so2->so_splice_back = sp;
+	soref(so2);
 	mtx_lock(&sp->mtx);
 	SOCK_SENDBUF_UNLOCK(so2);
 	SOCK_UNLOCK(so2);
@@ -1754,7 +1759,7 @@ so_unsplice(struct socket *so, bool timeout)
 {
 	struct socket *so2;
 	struct so_splice *sp;
-	bool drain;
+	bool drain, so2rele;
 
 	/*
 	 * First unset SB_SPLICED and hide the splice structure so that
@@ -1793,11 +1798,14 @@ so_unsplice(struct socket *so, bool timeout)
 	SOCK_LOCK(so2);
 	KASSERT(!SOLISTENING(so2), ("%s: so2 is listening", __func__));
 	SOCK_SENDBUF_LOCK(so2);
-	KASSERT((so2->so_snd.sb_flags & SB_SPLICED) != 0,
+	KASSERT(sp->state == SPLICE_INIT ||
+	    (so2->so_snd.sb_flags & SB_SPLICED) != 0,
 	    ("%s: so2 is not spliced", __func__));
-	KASSERT(so2->so_splice_back == sp,
+	KASSERT(sp->state == SPLICE_INIT ||
+	    so2->so_splice_back == sp,
 	    ("%s: so_splice_back != sp", __func__));
 	so2->so_snd.sb_flags &= ~SB_SPLICED;
+	so2rele = so2->so_splice_back != NULL;
 	so2->so_splice_back = NULL;
 	SOCK_SENDBUF_UNLOCK(so2);
 	SOCK_UNLOCK(so2);
@@ -1815,6 +1823,7 @@ so_unsplice(struct socket *so, bool timeout)
 		while (sp->state == SPLICE_CLOSING)
 			msleep(sp, &sp->mtx, PSOCK, "unsplice", 0);
 		break;
+	case SPLICE_INIT:
 	case SPLICE_IDLE:
 	case SPLICE_EXCEPTION:
 		sp->state = SPLICE_CLOSED;
@@ -1840,7 +1849,8 @@ so_unsplice(struct socket *so, bool timeout)
 	CURVNET_SET(so->so_vnet);
 	sorele(so);
 	sowwakeup(so2);
-	sorele(so2);
+	if (so2rele)
+		sorele(so2);
 	CURVNET_RESTORE();
 	so_splice_free(sp);
 	return (0);