socket: Fix a use-after-free in soclose()

After releasing the fd reference to a socket "so", we should avoid
testing SOLISTENING(so) since the socket may have been freed.  Instead,
directly test whether the list of unaccepted sockets is empty.

Fixes:		f4bb1869dd ("Consistently use the SOLISTENING() macro")
Pointy hat:	markj
MFC after:	1 week
Sponsored by:	The FreeBSD Foundation
Differential Revision:	https://reviews.freebsd.org/D31973
This commit is contained in:
Mark Johnston 2021-09-17 12:26:06 -04:00
parent bf25678226
commit dfcef87714

View File

@ -1223,6 +1223,7 @@ int
soclose(struct socket *so)
{
struct accept_queue lqueue;
struct socket *sp, *tsp;
int error = 0;
KASSERT(!(so->so_state & SS_NOFDREF), ("soclose: SS_NOFDREF on enter"));
@ -1257,11 +1258,9 @@ soclose(struct socket *so)
if (so->so_proto->pr_usrreqs->pru_close != NULL)
(*so->so_proto->pr_usrreqs->pru_close)(so);
TAILQ_INIT(&lqueue);
SOCK_LOCK(so);
if (SOLISTENING(so)) {
struct socket *sp;
TAILQ_INIT(&lqueue);
TAILQ_SWAP(&lqueue, &so->sol_incomp, socket, so_list);
TAILQ_CONCAT(&lqueue, &so->sol_comp, so_list);
@ -1279,17 +1278,14 @@ soclose(struct socket *so)
KASSERT((so->so_state & SS_NOFDREF) == 0, ("soclose: NOFDREF"));
so->so_state |= SS_NOFDREF;
sorele(so);
if (SOLISTENING(so)) {
struct socket *sp, *tsp;
TAILQ_FOREACH_SAFE(sp, &lqueue, so_list, tsp) {
SOCK_LOCK(sp);
if (sp->so_count == 0) {
SOCK_UNLOCK(sp);
soabort(sp);
} else
/* sp is now in sofree() */
SOCK_UNLOCK(sp);
TAILQ_FOREACH_SAFE(sp, &lqueue, so_list, tsp) {
SOCK_LOCK(sp);
if (refcount_load(&sp->so_count) == 0) {
SOCK_UNLOCK(sp);
soabort(sp);
} else {
/* sp is now in sofree() */
SOCK_UNLOCK(sp);
}
}
CURVNET_RESTORE();