Fix a double-free when an inp transitions to INP_TIMEWAIT state

after having been dropped.

This fixes enforces in_pcbdrop() logic in tcp_input():

"in_pcbdrop() is used by TCP to mark an inpcb as unused and avoid future packet
delivery or event notification when a socket remains open but TCP has closed."

PR:			203175
Reported by:		Palle Girgensohn, Slawa Olhovchenkov
Tested by:		Slawa Olhovchenkov
Reviewed by:		Slawa Olhovchenkov
Approved by:		gnn, Slawa Olhovchenkov
Differential Revision:	https://reviews.freebsd.org/D8211
MFC after:		1 week
Sponsored by:		Verisign, inc
This commit is contained in:
Julien Charbon 2016-10-18 07:16:49 +00:00
parent c30dcf40ba
commit f5cf1e5f5a
Notes: svn2git 2020-12-20 02:59:44 +00:00
svn path=/head/; revision=307551
3 changed files with 42 additions and 3 deletions

View File

@ -920,6 +920,16 @@ tcp_input(struct mbuf **mp, int *offp, int proto)
goto dropwithreset;
}
INP_WLOCK_ASSERT(inp);
/*
* While waiting for inp lock during the lookup, another thread
* can have dropped the inpcb, in which case we need to loop back
* and try to find a new inpcb to deliver to.
*/
if (inp->inp_flags & INP_DROPPED) {
INP_WUNLOCK(inp);
inp = NULL;
goto findpcb;
}
if ((inp->inp_flowtype == M_HASHTYPE_NONE) &&
(M_HASHTYPE_GET(m) != M_HASHTYPE_NONE) &&
((inp->inp_socket == NULL) ||
@ -980,6 +990,10 @@ tcp_input(struct mbuf **mp, int *offp, int proto)
if (in_pcbrele_wlocked(inp)) {
inp = NULL;
goto findpcb;
} else if (inp->inp_flags & INP_DROPPED) {
INP_WUNLOCK(inp);
inp = NULL;
goto findpcb;
}
} else
ti_locked = TI_RLOCKED;
@ -1039,6 +1053,10 @@ tcp_input(struct mbuf **mp, int *offp, int proto)
if (in_pcbrele_wlocked(inp)) {
inp = NULL;
goto findpcb;
} else if (inp->inp_flags & INP_DROPPED) {
INP_WUNLOCK(inp);
inp = NULL;
goto findpcb;
}
goto relocked;
} else

View File

@ -231,6 +231,10 @@ tcp_twstart(struct tcpcb *tp)
INP_INFO_RLOCK_ASSERT(&V_tcbinfo);
INP_WLOCK_ASSERT(inp);
/* A dropped inp should never transition to TIME_WAIT state. */
KASSERT((inp->inp_flags & INP_DROPPED) == 0, ("tcp_twstart: "
"(inp->inp_flags & INP_DROPPED) != 0"));
if (V_nolocaltimewait) {
int error = 0;
#ifdef INET6

View File

@ -59,6 +59,7 @@ __FBSDID("$FreeBSD$");
#include <sys/protosw.h>
#include <sys/proc.h>
#include <sys/jail.h>
#include <sys/syslog.h>
#ifdef DDB
#include <ddb/ddb.h>
@ -210,10 +211,26 @@ tcp_detach(struct socket *so, struct inpcb *inp)
* In all three cases the tcptw should not be freed here.
*/
if (inp->inp_flags & INP_DROPPED) {
KASSERT(tp == NULL, ("tcp_detach: INP_TIMEWAIT && "
"INP_DROPPED && tp != NULL"));
in_pcbdetach(inp);
in_pcbfree(inp);
if (__predict_true(tp == NULL)) {
in_pcbfree(inp);
} else {
/*
* This case should not happen as in TIMEWAIT
* state the inp should not be destroyed before
* its tcptw. If INVARIANTS is defined, panic.
*/
#ifdef INVARIANTS
panic("%s: Panic before an inp double-free: "
"INP_TIMEWAIT && INP_DROPPED && tp != NULL"
, __func__);
#else
log(LOG_ERR, "%s: Avoid an inp double-free: "
"INP_TIMEWAIT && INP_DROPPED && tp != NULL"
, __func__);
#endif
INP_WUNLOCK(inp);
}
} else {
in_pcbdetach(inp);
INP_WUNLOCK(inp);