From 434ac8b6b730556948f80a3f4084580727de7208 Mon Sep 17 00:00:00 2001 From: Mark Johnston Date: Mon, 8 Aug 2016 20:25:04 +0000 Subject: [PATCH] Handle races with listening socket close when connecting a unix socket. If the listening socket is closed while sonewconn() is executing, the nascent child socket is aborted, which results in recursion on the unp_link lock when the child's pru_detach method is invoked. Fix this by using a flag to mark such sockets, and skip a part of the socket's teardown during detach. Reported by: Raviprakash Darbha Tested by: pho MFC after: 2 weeks Differential Revision: https://reviews.freebsd.org/D7398 --- sys/kern/uipc_usrreq.c | 16 ++++++++++++++-- sys/sys/unpcb.h | 14 +++++++++----- 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/sys/kern/uipc_usrreq.c b/sys/kern/uipc_usrreq.c index f0d282cebd7c..8db97fdcc1fd 100644 --- a/sys/kern/uipc_usrreq.c +++ b/sys/kern/uipc_usrreq.c @@ -430,6 +430,8 @@ uipc_attach(struct socket *so, int proto, struct thread *td) unp->unp_socket = so; so->so_pcb = unp; unp->unp_refcount = 1; + if (so->so_head != NULL) + unp->unp_flags |= UNP_NASCENT; UNP_LIST_LOCK(); unp->unp_gencnt = ++unp_gencnt; @@ -652,14 +654,22 @@ uipc_detach(struct socket *so) unp = sotounpcb(so); KASSERT(unp != NULL, ("uipc_detach: unp == NULL")); - UNP_LINK_WLOCK(); + vp = NULL; + local_unp_rights = 0; + UNP_LIST_LOCK(); - UNP_PCB_LOCK(unp); LIST_REMOVE(unp, unp_link); unp->unp_gencnt = ++unp_gencnt; --unp_count; UNP_LIST_UNLOCK(); + if ((unp->unp_flags & UNP_NASCENT) != 0) { + UNP_PCB_LOCK(unp); + goto teardown; + } + UNP_LINK_WLOCK(); + UNP_PCB_LOCK(unp); + /* * XXXRW: Should assert vp->v_socket == so. */ @@ -687,6 +697,7 @@ uipc_detach(struct socket *so) } local_unp_rights = unp_rights; UNP_LINK_WUNLOCK(); +teardown: unp->unp_socket->so_pcb = NULL; saved_unp_addr = unp->unp_addr; unp->unp_addr = NULL; @@ -1473,6 +1484,7 @@ unp_connect2(struct socket *so, struct socket *so2, int req) if (so2->so_type != so->so_type) return (EPROTOTYPE); + unp2->unp_flags &= ~UNP_NASCENT; unp->unp_conn = unp2; switch (so->so_type) { diff --git a/sys/sys/unpcb.h b/sys/sys/unpcb.h index 41fd5745bbd7..2547fdcbac85 100644 --- a/sys/sys/unpcb.h +++ b/sys/sys/unpcb.h @@ -103,11 +103,6 @@ struct unpcb { #define UNP_WANTCRED 0x004 /* credentials wanted */ #define UNP_CONNWAIT 0x008 /* connect blocks until accepted */ -#define UNPGC_REF 0x1 /* unpcb has external ref. */ -#define UNPGC_DEAD 0x2 /* unpcb might be dead. */ -#define UNPGC_SCANNED 0x4 /* Has been scanned. */ -#define UNPGC_IGNORE_RIGHTS 0x8 /* Attached rights are freed */ - /* * These flags are used to handle non-atomicity in connect() and bind() * operations on a socket: in particular, to avoid races between multiple @@ -115,6 +110,15 @@ struct unpcb { */ #define UNP_CONNECTING 0x010 /* Currently connecting. */ #define UNP_BINDING 0x020 /* Currently binding. */ +#define UNP_NASCENT 0x040 /* Newborn child socket. */ + +/* + * Flags in unp_gcflag. + */ +#define UNPGC_REF 0x1 /* unpcb has external ref. */ +#define UNPGC_DEAD 0x2 /* unpcb might be dead. */ +#define UNPGC_SCANNED 0x4 /* Has been scanned. */ +#define UNPGC_IGNORE_RIGHTS 0x8 /* Attached rights are freed */ #define sotounpcb(so) ((struct unpcb *)((so)->so_pcb))