svn commit: r323594 - head/sys/kern

Gleb Smirnoff glebius at FreeBSD.org
Thu Sep 14 18:05:55 UTC 2017


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);


More information about the svn-src-head mailing list