git: c4e047ab6eb1 - stable/14 - sctp: improve shutting down the read side of a socket

From: Michael Tuexen <tuexen_at_FreeBSD.org>
Date: Fri, 15 Sep 2023 17:47:32 UTC
The branch stable/14 has been updated by tuexen:

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

commit c4e047ab6eb15d27f95f46ccf12d55c46d30debd
Author:     Michael Tuexen <tuexen@FreeBSD.org>
AuthorDate: 2023-09-12 23:33:54 +0000
Commit:     Michael Tuexen <tuexen@FreeBSD.org>
CommitDate: 2023-09-15 17:45:42 +0000

    sctp: improve shutting down the read side of a socket
    
    When shutdown(..., SHUT_RD) or shutdown(..., SHUT_RDWR) is called,
    really clean up the read queue and issue an ungraceful shutdown if
    user messages are affected.
    
    Reported by:    syzbot+d4e1d30d578891245f59@syzkaller.appspotmail.com
---
 sys/netinet/sctp_usrreq.c | 90 ++++++++++++++++++++++++++++++-----------------
 1 file changed, 57 insertions(+), 33 deletions(-)

diff --git a/sys/netinet/sctp_usrreq.c b/sys/netinet/sctp_usrreq.c
index 52b4faf48574..02bb87578528 100644
--- a/sys/netinet/sctp_usrreq.c
+++ b/sys/netinet/sctp_usrreq.c
@@ -777,52 +777,76 @@ sctp_disconnect(struct socket *so)
 int
 sctp_flush(struct socket *so, int how)
 {
-	/*
-	 * We will just clear out the values and let subsequent close clear
-	 * out the data, if any. Note if the user did a shutdown(SHUT_RD)
-	 * they will not be able to read the data, the socket will block
-	 * that from happening.
-	 */
+	struct epoch_tracker et;
+	struct sctp_tcb *stcb;
+	struct sctp_queued_to_read *control, *ncontrol;
 	struct sctp_inpcb *inp;
+	struct mbuf *m, *op_err;
+	bool need_to_abort = false;
 
+	/*
+	 * For 1-to-1 style sockets, flush the read queue and trigger an
+	 * ungraceful shutdown of the association, if and only if user
+	 * messages are lost. Loosing notifications does not need to be
+	 * signalled to the peer.
+	 */
+	if (how == PRU_FLUSH_WR) {
+		/* This function is only relevant for the read directions. */
+		return (0);
+	}
 	inp = (struct sctp_inpcb *)so->so_pcb;
 	if (inp == NULL) {
 		SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
 		return (EINVAL);
 	}
-	SCTP_INP_RLOCK(inp);
-	/* For the 1 to many model this does nothing */
+	SCTP_INP_WLOCK(inp);
 	if (inp->sctp_flags & SCTP_PCB_FLAGS_UDPTYPE) {
-		SCTP_INP_RUNLOCK(inp);
+		/* For 1-to-many style sockets this function does nothing. */
+		SCTP_INP_WUNLOCK(inp);
 		return (0);
 	}
-	SCTP_INP_RUNLOCK(inp);
-	if ((how == PRU_FLUSH_RD) || (how == PRU_FLUSH_RDWR)) {
-		/*
-		 * First make sure the sb will be happy, we don't use these
-		 * except maybe the count
-		 */
-		SCTP_INP_WLOCK(inp);
-		SCTP_INP_READ_LOCK(inp);
-		inp->sctp_flags |= SCTP_PCB_FLAGS_SOCKET_CANT_READ;
-		SCTP_INP_READ_UNLOCK(inp);
+	stcb = LIST_FIRST(&inp->sctp_asoc_list);
+	if (stcb == NULL) {
 		SCTP_INP_WUNLOCK(inp);
-		SOCK_LOCK(so);
-		KASSERT(!SOLISTENING(so),
-		    ("sctp_flush: called on listening socket %p", so));
-		SCTP_SB_CLEAR(so->so_rcv);
-		SOCK_UNLOCK(so);
+		return (ENOTCONN);
 	}
-	if ((how == PRU_FLUSH_WR) || (how == PRU_FLUSH_RDWR)) {
-		/*
-		 * First make sure the sb will be happy, we don't use these
-		 * except maybe the count
-		 */
-		SOCK_LOCK(so);
-		KASSERT(!SOLISTENING(so),
-		    ("sctp_flush: called on listening socket %p", so));
-		SOCK_UNLOCK(so);
+	SCTP_TCB_LOCK(stcb);
+	SCTP_INP_READ_LOCK(inp);
+	inp->sctp_flags |= SCTP_PCB_FLAGS_SOCKET_CANT_READ;
+	SOCK_LOCK(so);
+	TAILQ_FOREACH_SAFE(control, &inp->read_queue, next, ncontrol) {
+		if ((control->spec_flags & M_NOTIFICATION) == 0) {
+			need_to_abort = true;
+		}
+		TAILQ_REMOVE(&inp->read_queue, control, next);
+		control->on_read_q = 0;
+		for (m = control->data; m; m = SCTP_BUF_NEXT(m)) {
+			sctp_sbfree(control, control->stcb, &so->so_rcv, m);
+		}
+		if (control->on_strm_q == 0) {
+			sctp_free_remote_addr(control->whoFrom);
+			if (control->data) {
+				sctp_m_freem(control->data);
+				control->data = NULL;
+			}
+			sctp_free_a_readq(stcb, control);
+		} else {
+			stcb->asoc.size_on_all_streams += control->length;
+		}
 	}
+	SOCK_UNLOCK(so);
+	SCTP_INP_READ_UNLOCK(inp);
+	if (need_to_abort) {
+		inp->last_abort_code = SCTP_FROM_SCTP_USRREQ + SCTP_LOC_6;
+		SCTP_INP_WUNLOCK(inp);
+		op_err = sctp_generate_cause(SCTP_CAUSE_OUT_OF_RESC, "");
+		NET_EPOCH_ENTER(et);
+		sctp_abort_an_association(inp, stcb, op_err, false, SCTP_SO_LOCKED);
+		NET_EPOCH_EXIT(et);
+		return (ECONNABORTED);
+	}
+	SCTP_TCB_UNLOCK(stcb);
+	SCTP_INP_WUNLOCK(inp);
 	return (0);
 }