Date: Thu, 14 Sep 2017 18:05:54 +0000 (UTC) From: Gleb Smirnoff <glebius@FreeBSD.org> To: src-committers@freebsd.org, svn-src-all@freebsd.org, svn-src-head@freebsd.org Subject: svn commit: r323594 - head/sys/kern Message-ID: <201709141805.v8EI5s8r036476@repo.freebsd.org>
next in thread | raw e-mail | index | archive | help
Author: glebius Date: Thu Sep 14 18:05:54 2017 New Revision: 323594 URL: https://svnweb.freebsd.org/changeset/base/323594 Log: Fix locking in soisconnected(). When a newborn socket moves from incomplete queue to complete one, we need to obtain the listening socket lock after the child, which is a wrong order. The old code did that in potentially endless loop of mtx_trylock(). The new one does only one attempt of mtx_trylock(), and in case of failure references listening socket, unlocks child and locks everything in right order. In case if listening socket shuts down during that, just bail out. Reported & tested by: Jason Eggleston <jeggleston llnw.com> Reported & tested by: Jason Wolfe <jason llnw.com> Modified: head/sys/kern/uipc_socket.c Modified: head/sys/kern/uipc_socket.c ============================================================================== --- head/sys/kern/uipc_socket.c Thu Sep 14 17:29:51 2017 (r323593) +++ head/sys/kern/uipc_socket.c Thu Sep 14 18:05:54 2017 (r323594) @@ -3688,24 +3688,41 @@ soisconnecting(struct socket *so) void soisconnected(struct socket *so) { - struct socket *head; - int ret; - /* - * XXXGL: this is the only place where we acquire socket locks - * in reverse order: first child, then listening socket. To - * avoid possible LOR, use try semantics. - */ -restart: SOCK_LOCK(so); - if ((head = so->so_listen) != NULL && - __predict_false(SOLISTEN_TRYLOCK(head) == 0)) { - SOCK_UNLOCK(so); - goto restart; - } so->so_state &= ~(SS_ISCONNECTING|SS_ISDISCONNECTING|SS_ISCONFIRMING); so->so_state |= SS_ISCONNECTED; - if (head != NULL && (so->so_qstate == SQ_INCOMP)) { + + if (so->so_qstate == SQ_INCOMP) { + struct socket *head = so->so_listen; + int ret; + + KASSERT(head, ("%s: so %p on incomp of NULL", __func__, so)); + /* + * Promoting a socket from incomplete queue to complete, we + * need to go through reverse order of locking. We first do + * trylock, and if that doesn't succeed, we go the hard way + * leaving a reference and rechecking consistency after proper + * locking. + */ + if (__predict_false(SOLISTEN_TRYLOCK(head) == 0)) { + soref(head); + SOCK_UNLOCK(so); + SOLISTEN_LOCK(head); + SOCK_LOCK(so); + if (__predict_false(head != so->so_listen)) { + /* + * The socket went off the listen queue, + * should be lost race to close(2) of sol. + * The socket is about to soabort(). + */ + SOCK_UNLOCK(so); + sorele(head); + return; + } + /* Not the last one, as so holds a ref. */ + refcount_release(&head->so_count); + } again: if ((so->so_options & SO_ACCEPTFILTER) == 0) { TAILQ_REMOVE(&head->sol_incomp, so, so_list); @@ -3734,8 +3751,6 @@ again: } return; } - if (head != NULL) - SOLISTEN_UNLOCK(head); SOCK_UNLOCK(so); wakeup(&so->so_timeo); sorwakeup(so);
Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?201709141805.v8EI5s8r036476>