--- tcp_subr.c.orig +++ tcp_subr.c @@ -1273,23 +1273,47 @@ * process a snapshot of pcbs, ignoring placemarkers * and using our own to allow SYSCTL_OUT to block. */ + int skip = 0; + + /* + * The marker advance (read next, unlink marker, + * relink marker after next) is a non-atomic two-step + * sequence. Nothing serializes this sysctl user + * thread against a same-cpu netisr tearing down the + * inpcb we just stepped to (tcp_close -> + * in_pcbdetach -> LIST_REMOVE + kfree): relinking the + * marker after an unlinked (and possibly freed and + * recycled) inpcb corrupts the list and writes into + * freed memory. The critical section makes the + * advance + snapshot atomic against same-cpu + * preemption; the marker itself keeps the walk + * coherent across the (sleeping) SYSCTL_OUT below. + */ + crit_enter(); LIST_REMOVE(marker, inp_list); LIST_INSERT_AFTER(inp, marker, inp_list); - if (inp->inp_flags & INP_PLACEMARKER) - continue; - if (prison_xinpcb(req->td, inp)) - continue; + if (inp->inp_flags & INP_PLACEMARKER) { + skip = 1; + } else if (prison_xinpcb(req->td, inp)) { + skip = 1; + } else { + xt.xt_len = sizeof xt; + bcopy(inp, &xt.xt_inp, sizeof *inp); + inp_ppcb = inp->inp_ppcb; + if (inp_ppcb != NULL) + bcopy(inp_ppcb, &xt.xt_tp, + sizeof xt.xt_tp); + else + bzero(&xt.xt_tp, sizeof xt.xt_tp); + if (inp->inp_socket) + sotoxsocket(inp->inp_socket, + &xt.xt_socket); + } + crit_exit(); - xt.xt_len = sizeof xt; - bcopy(inp, &xt.xt_inp, sizeof *inp); - inp_ppcb = inp->inp_ppcb; - if (inp_ppcb != NULL) - bcopy(inp_ppcb, &xt.xt_tp, sizeof xt.xt_tp); - else - bzero(&xt.xt_tp, sizeof xt.xt_tp); - if (inp->inp_socket) - sotoxsocket(inp->inp_socket, &xt.xt_socket); + if (skip) + continue; if ((error = SYSCTL_OUT(req, &xt, sizeof xt)) != 0) break; ++i;