diff --git a/bsd/sys/netinet/tcp_input.cc b/bsd/sys/netinet/tcp_input.cc index a0aa4f1f01..96fbf3ccea 100644 --- a/bsd/sys/netinet/tcp_input.cc +++ b/bsd/sys/netinet/tcp_input.cc @@ -3186,7 +3186,6 @@ tcp_newreno_partial_ack(struct tcpcb *tp, struct tcphdr *th) #include #include -// INP_LOCK held static void tcp_net_channel_packet(tcpcb* tp, mbuf* m) { @@ -3202,11 +3201,41 @@ tcp_net_channel_packet(tcpcb* tp, mbuf* m) auto drop_hdrlen = h - start; tcp_fields_to_host(th); trace_tcp_input_ack(tp, th->th_ack.raw()); - auto so = tp->t_inpcb->inp_socket; + + /* + * Every caller reaches this through net_channel::process_queue(), + * which always runs with the inpcb lock held (that lock aliases the + * socket mutex - see in_pcb.cc: so->set_mutex(&inp->inp_lock)). The + * IRQ classifier only enqueues via post_packet(), it never invokes + * the process function directly, so we can assert the lock rather + * than re-acquire it. + */ + auto inp = tp->t_inpcb; + INP_LOCK_ASSERT(inp); + + /* + * process_queue() drains a batch of mbufs in a loop; an earlier + * segment can close the connection and detach the socket + * (in_pcbdetach() clears inp_socket) before a later iteration runs. + * Drop the packet instead of dereferencing a torn-down socket. + */ + auto so = inp->inp_socket; + if (!so) { + m_freem(m); + return; + } auto ip_len = ntohs(ip_hdr->ip_len); auto tlen = ip_len - (ip_size + (th->th_off << 2)); auto iptos = ip_hdr->ip_tos; - SOCK_LOCK_ASSERT(so); + /* + * tcp_do_segment() only handles a live data-bearing connection. The + * slow path (tcp_input) drops segments for CLOSED/LISTEN/TIME_WAIT + * before reaching it; mirror that here. + */ + if (tp->get_state() <= TCPS_LISTEN || tp->get_state() == TCPS_TIME_WAIT) { + m_freem(m); + return; + } bool want_close; m_trim(m, ETHER_HDR_LEN + ip_len); tcp_do_segment(m, th, so, tp, drop_hdrlen, tlen, iptos, TI_UNLOCKED, want_close);