DF-3373 / fix.diff
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 | --- a/sys/netinet/tcp_usrreq.c +++ b/sys/netinet/tcp_usrreq.c @@ -452,6 +452,50 @@ COMMON_START(so, inp, 0); + /* + * Already a listener: this is just a backlog update. Checked + * before the state guard so a healthy listener is never + * rejected -- and never has SO_ACCEPTCONN undone. + */ + if (tp->t_flags & TF_LISTEN) + goto out; + + /* + * Reject listen(2) on a socket that is connecting or connected. + * + * solisten()'s SS_ISCONNECTED/SS_ISCONNECTING test is a plain + * check-then-act: with the default async connect path the flag + * is only stored by tcp_usr_preconnect() which runs after + * soconnect()'s checks, and a connect(2) message still in flight + * (possibly mid-migration between netisrs) leaves the inpcb in + * states this handler must not turn into a listener (otherwise + * the same inpcb ends up on both the wildcard hash and the + * connection hash). Handler-side checks are race-free because + * pru messages are serialized per socket. + * + * NOTE: A PRUL_RELINK re-entry (our own migration to netisr0) + * passed this guard before the forward and must not be rejected + * for the then-unlinked inp_pcbinfo == NULL. + */ + if ((msg->listen.nm_flags & PRUL_RELINK) == 0 && + (tp->t_state != TCPS_CLOSED || + (so->so_state & (SS_ISCONNECTED | SS_ISCONNECTING)) || + inp->inp_fport != 0 || inp->inp_pcbinfo == NULL)) { + /* + * solisten() sets SO_ACCEPTCONN before dispatching this + * message; undo it, otherwise tcp_input()'s SO_ACCEPTCONN + * branch and soclose() treat this still-connecting socket + * as a listener. Race-free: segments and further pru + * messages are serialized behind this handler. + */ + lwkt_gettoken(&so->so_rcv.ssb_token); + if (TAILQ_EMPTY(&so->so_comp) && TAILQ_EMPTY(&so->so_incomp)) + so->so_options &= ~SO_ACCEPTCONN; + lwkt_reltoken(&so->so_rcv.ssb_token); + error = EINVAL; + goto out; + } + if (&curthread->td_msgport != port0) { lwkt_msg_t lmsg = &msg->listen.base.lmsg; @@ -531,6 +575,23 @@ if (tp->t_flags & TF_LISTEN) goto out; + /* + * Same race as tcp_usr_listen(): reject sockets that are + * connecting or connected (handler-side, race-free), and undo + * solisten()'s SO_ACCEPTCONN so the socket stack does not treat + * a connecting socket as a listener. + */ + if (tp->t_state != TCPS_CLOSED || + (so->so_state & (SS_ISCONNECTED | SS_ISCONNECTING)) || + inp->inp_fport != 0 || inp->inp_pcbinfo == NULL) { + lwkt_gettoken(&so->so_rcv.ssb_token); + if (TAILQ_EMPTY(&so->so_comp) && TAILQ_EMPTY(&so->so_incomp)) + so->so_options &= ~SO_ACCEPTCONN; + lwkt_reltoken(&so->so_rcv.ssb_token); + error = EINVAL; + goto out; + } + if (inp->inp_lport == 0) { error = in6_pcbbind(inp, NULL, td); if (error) @@ -1141,6 +1202,30 @@ COMMON_START(so, inp, 0); /* + * Reject connect(2) on a socket that is (or has become) a + * listener. soconnect()'s SO_ACCEPTCONN test races + * solisten()'s SO_ACCEPTCONN store, so by the time this + * message is processed (including the PRUC_RECONNECT + * re-entry) the inpcb may already be linked into the wildcard + * hash; continuing would link it into the connection hash + * as well. + */ + if (tp->t_flags & TF_LISTEN) { + if (msg->connect.nm_flags & PRUC_ASYNC) { + /* + * Report the error to the connect(2) caller, but + * do NOT soisdisconnected() the listener (the + * generic error path would): sonewconn() children + * inherit the parent's so_state. + */ + so->so_error = EOPNOTSUPP; + msg->connect.nm_flags &= ~PRUC_ASYNC; + } + error = EOPNOTSUPP; + goto out; + } + + /* * Reconnect our pcb if we have to */ if (msg->connect.nm_flags & PRUC_RECONNECT) { @@ -1323,6 +1408,15 @@ COMMON_START(so, inp, 0); /* + * Reject connect(2) on a socket that is (or has become) a + * listener (see tcp_connect()). + */ + if (tp->t_flags & TF_LISTEN) { + error = EOPNOTSUPP; + goto out; + } + + /* * Reconnect our pcb if we have to */ if (msg->connect.nm_flags & PRUC_RECONNECT) { |