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
MFC after:	3 days
This commit is contained in:
Michael Tuexen 2023-09-13 01:33:54 +02:00
parent c086d1cbc3
commit 81c5f0fac9

View file

@ -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);
}