aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMichael Tuexen <tuexen@FreeBSD.org>2023-09-12 23:33:54 +0000
committerMichael Tuexen <tuexen@FreeBSD.org>2024-01-11 12:58:11 +0000
commite207b7b8b3716fdb64ed5ef10688e0f0cbe954cc (patch)
tree582cee54cca547d9dc5a6880f5fa1d5c37c032c5
parent2d12958d4b5b0cd7799662f3f69284c716264f92 (diff)
downloadsrc-e207b7b8b3716fdb64ed5ef10688e0f0cbe954cc.tar.gz
src-e207b7b8b3716fdb64ed5ef10688e0f0cbe954cc.zip
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 (cherry picked from commit 81c5f0fac91dfae64205a6c4f9b2a469d1187372)
-rw-r--r--sys/netinet/sctp_usrreq.c90
1 files changed, 57 insertions, 33 deletions
diff --git a/sys/netinet/sctp_usrreq.c b/sys/netinet/sctp_usrreq.c
index a7af1da8624a..9f1a33b5ff2a 100644
--- a/sys/netinet/sctp_usrreq.c
+++ b/sys/netinet/sctp_usrreq.c
@@ -789,52 +789,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);
}