summaryrefslogtreecommitdiffstats
path: root/sys/kern/uipc_socket.c
diff options
context:
space:
mode:
authorrwatson <rwatson@FreeBSD.org>2005-02-21 21:58:17 +0000
committerrwatson <rwatson@FreeBSD.org>2005-02-21 21:58:17 +0000
commit26df80bf2cb36ff3fb93ad6eef085062a90896c6 (patch)
tree485f480fc21fe953c9c79e4896f5536a96968c9f /sys/kern/uipc_socket.c
parentbc73e0ad821773b13eec9322408d7da1c61edeb6 (diff)
downloadFreeBSD-src-26df80bf2cb36ff3fb93ad6eef085062a90896c6.zip
FreeBSD-src-26df80bf2cb36ff3fb93ad6eef085062a90896c6.tar.gz
In the current world order, solisten() implements the state transition of
a socket from a regular socket to a listening socket able to accept new connections. As part of this state transition, solisten() calls into the protocol to update protocol-layer state. There were several bugs in this implementation that could result in a race wherein a TCP SYN received in the interval between the protocol state transition and the shortly following socket layer transition would result in a panic in the TCP code, as the socket would be in the TCPS_LISTEN state, but the socket would not have the SO_ACCEPTCONN flag set. This change does the following: - Pushes the socket state transition from the socket layer solisten() to to socket "library" routines called from the protocol. This permits the socket routines to be called while holding the protocol mutexes, preventing a race exposing the incomplete socket state transition to TCP after the TCP state transition has completed. The check for a socket layer state transition is performed by solisten_proto_check(), and the actual transition is performed by solisten_proto(). - Holds the socket lock for the duration of the socket state test and set, and over the protocol layer state transition, which is now possible as the socket lock is acquired by the protocol layer, rather than vice versa. This prevents additional state related races in the socket layer. This permits the dual transition of socket layer and protocol layer state to occur while holding locks for both layers, making the two changes atomic with respect to one another. Similar changes are likely require elsewhere in the socket/protocol code. Reported by: Peter Holm <peter@holm.cc> Review and fixes from: emax, Antoine Brodin <antoine.brodin@laposte.net> Philosophical head nod: gnn
Diffstat (limited to 'sys/kern/uipc_socket.c')
-rw-r--r--sys/kern/uipc_socket.c56
1 files changed, 42 insertions, 14 deletions
diff --git a/sys/kern/uipc_socket.c b/sys/kern/uipc_socket.c
index 8e34acd..fbf0d53 100644
--- a/sys/kern/uipc_socket.c
+++ b/sys/kern/uipc_socket.c
@@ -1,6 +1,6 @@
/*-
* Copyright (c) 2004 The FreeBSD Foundation
- * Copyright (c) 2004-2005 Robert Watson
+ * Copyright (c) 2004-2005 Robert N. M. Watson
* Copyright (c) 1982, 1986, 1988, 1990, 1993
* The Regents of the University of California. All rights reserved.
*
@@ -271,6 +271,18 @@ sodealloc(struct socket *so)
mtx_unlock(&so_global_mtx);
}
+/*
+ * solisten() transitions a socket from a non-listening state to a listening
+ * state, but can also be used to update the listen queue depth on an
+ * existing listen socket. The protocol will call back into the sockets
+ * layer using solisten_proto_check() and solisten_proto() to check and set
+ * socket-layer listen state. Call backs are used so that the protocol can
+ * acquire both protocol and socket layer locks in whatever order is reuiqred
+ * by the protocol.
+ *
+ * Protocol implementors are advised to hold the socket lock across the
+ * socket-layer test and set to avoid races at the socket layer.
+ */
int
solisten(so, backlog, td)
struct socket *so;
@@ -279,28 +291,44 @@ solisten(so, backlog, td)
{
int error;
- /*
- * XXXRW: Ordering issue here -- perhaps we need to set
- * SO_ACCEPTCONN before the call to pru_listen()?
- * XXXRW: General atomic test-and-set concerns here also.
- */
- if (so->so_state & (SS_ISCONNECTED | SS_ISCONNECTING |
- SS_ISDISCONNECTING))
- return (EINVAL);
error = (*so->so_proto->pr_usrreqs->pru_listen)(so, td);
if (error)
return (error);
- ACCEPT_LOCK();
- SOCK_LOCK(so);
- so->so_options |= SO_ACCEPTCONN;
- SOCK_UNLOCK(so);
+
+ /*
+ * XXXRW: The following state adjustment should occur in
+ * solisten_proto(), but we don't currently pass the backlog request
+ * to the protocol via pru_listen().
+ */
if (backlog < 0 || backlog > somaxconn)
backlog = somaxconn;
so->so_qlimit = backlog;
- ACCEPT_UNLOCK();
return (0);
}
+int
+solisten_proto_check(so)
+ struct socket *so;
+{
+
+ SOCK_LOCK_ASSERT(so);
+
+ if (so->so_state & (SS_ISCONNECTED | SS_ISCONNECTING |
+ SS_ISDISCONNECTING))
+ return (EINVAL);
+ return (0);
+}
+
+void
+solisten_proto(so)
+ struct socket *so;
+{
+
+ SOCK_LOCK_ASSERT(so);
+
+ so->so_options |= SO_ACCEPTCONN;
+}
+
/*
* Attempt to free a socket. This should really be sotryfree().
*
OpenPOWER on IntegriCloud