Security Fix (CAN-2004-0174): Apache before 2.0.49 and 1.3.30, when using multiple listening sockets on certain platforms, allows remote attackers to cause a Denial of Service (blocked new connections) via a "short-lived connection on a rarely-accessed listening socket." This fixes the starvation issue on listening sockets where a short-lived connection on a rarely-accessed listening socket will cause a child to hold the accept mutex and block out new connections until another connection arrives on that rarely-accessed listening socket. Enabled for some platforms only which are known to have the issue (accept() blocking after select() returns readable). Index: src/include/ap_config.h --- src/include/ap_config.h.orig 2003-05-05 13:45:49.000000000 +0200 +++ src/include/ap_config.h 2004-05-12 10:05:56.000000000 +0200 @@ -193,6 +193,7 @@ int gethostname(char *name, int namelen); #define HAVE_SYSLOG 1 #define SYS_SIGLIST _sys_siglist +#define NONBLOCK_WHEN_MULTI_LISTEN #elif defined(IRIX) #undef HAVE_GMTOFF @@ -216,6 +217,7 @@ #define NO_LONG_DOUBLE #define NO_LINGCLOSE #define HAVE_SYSLOG 1 +#define NONBLOCK_WHEN_MULTI_LISTEN #elif defined(HIUX) #undef HAVE_GMTOFF @@ -299,6 +301,7 @@ #elif AIX >= 420 #define NET_SIZE_T size_t #endif +#define NONBLOCK_WHEN_MULTI_LISTEN #elif defined(ULTRIX) /* we don't want to use sys/resource.h under @@ -325,6 +328,7 @@ #define HAVE_SYSLOG 1 #define HAVE_FLOCK_SERIALIZED_ACCEPT #define SINGLE_LISTEN_UNSERIALIZED_ACCEPT +#define NONBLOCK_WHEN_MULTI_LISTEN #elif defined(PARAGON) #define HAVE_GMTOFF 1 @@ -1015,6 +1019,7 @@ #include <sys/socket.h> #define NET_SIZE_T size_t #define NEED_HASHBANG_EMUL +#define NONBLOCK_WHEN_MULTI_LISTEN #elif defined(CYGWIN) /* Cygwin 1.x POSIX layer for Win32 */ #define SYSTEM_UID 18 @@ -1034,6 +1039,8 @@ #define USE_PTHREAD_SERIALIZED_ACCEPT #endif +#elif defined(NETWARE) +#define NONBLOCK_WHEN_MULTI_LISTEN #else /* Unknown system - Edit these to match */ Index: src/main/http_main.c --- src/main/http_main.c.orig 2003-10-19 20:00:35.000000000 +0200 +++ src/main/http_main.c 2004-05-12 10:05:56.000000000 +0200 @@ -3905,6 +3905,76 @@ old_listeners = NULL; } +#ifdef NONBLOCK_WHEN_MULTI_LISTEN +/* retrieved from APR */ +static int soblock(int sd) +{ +#ifdef NETWARE + u_long one = 0; + + if (ioctlsocket(sd, FIONBIO, &one) == SOCKET_ERROR) { + return -1; + } +#else +#ifndef BEOS + int fd_flags; + + fd_flags = fcntl(sd, F_GETFL, 0); +#if defined(O_NONBLOCK) + fd_flags &= ~O_NONBLOCK; +#elif defined(O_NDELAY) + fd_flags &= ~O_NDELAY; +#elif defined(FNDELAY) + fd_flags &= ~FNDELAY; +#else +#error Teach soblock() how to make a socket blocking on your platform. +#endif + if (fcntl(sd, F_SETFL, fd_flags) == -1) { + return errno; + } +#else + int on = 0; + if (setsockopt(sd, SOL_SOCKET, SO_NONBLOCK, &on, sizeof(int)) < 0) + return errno; +#endif /* BEOS */ +#endif /* NETWARE */ + return 0; +} + +static int sononblock(int sd) +{ +#ifdef NETWARE + u_long one = 1; + + if (ioctlsocket(sd, FIONBIO, &one) == SOCKET_ERROR) { + return -1; + } +#else +#ifndef BEOS + int fd_flags; + + fd_flags = fcntl(sd, F_GETFL, 0); +#if defined(O_NONBLOCK) + fd_flags |= O_NONBLOCK; +#elif defined(O_NDELAY) + fd_flags |= O_NDELAY; +#elif defined(FNDELAY) + fd_flags |= FNDELAY; +#else +#error Teach sononblock() how to make a socket non-blocking on your platform. +#endif + if (fcntl(sd, F_SETFL, fd_flags) == -1) { + return errno; + } +#else + int on = 1; + if (setsockopt(sd, SOL_SOCKET, SO_NONBLOCK, &on, sizeof(int)) < 0) + return errno; +#endif /* BEOS */ +#endif /* NETWARE */ + return 0; +} +#endif /* NONBLOCK_WHEN_MULTI_LISTEN */ /* open sockets, and turn the listeners list into a singly linked ring */ static void setup_listeners(pool *p) @@ -3937,6 +4007,31 @@ head_listener = ap_listeners; close_unused_listeners(); +#ifdef NONBLOCK_WHEN_MULTI_LISTEN + if (ap_listeners->next != ap_listeners) { + lr = ap_listeners; + do { + if (sononblock(lr->fd) < 0) { + ap_log_error(APLOG_MARK, APLOG_CRIT, NULL, + "A listening socket could not be made non-blocking."); + exit(APEXIT_INIT); + } + lr = lr->next; + } while (lr != ap_listeners); + } + else { + /* we could be restarting with a single remaining listening + * socket, still in non-blocking state from a previous + * generation which had more listening sockets + */ + if (soblock(ap_listeners->fd) < 0) { + ap_log_error(APLOG_MARK, APLOG_CRIT, NULL, + "A listening socket could not be made blocking."); + exit(APEXIT_INIT); + } + } +#endif /* NONBLOCK_WHEN_MULTI_LISTEN */ + #ifdef NO_SERIALIZED_ACCEPT /* warn them about the starvation problem if they're using multiple * sockets @@ -4472,6 +4567,19 @@ #ifdef ENETUNREACH case ENETUNREACH: #endif + /* EAGAIN/EWOULDBLOCK can be returned on BSD-derived + * TCP stacks when the connection is aborted before + * we call connect, but only because our listener + * sockets are non-blocking (NONBLOCK_WHEN_MULTI_LISTEN) + */ +#ifdef EAGAIN + case EAGAIN: +#endif +#ifdef EWOULDBLOCK +#if !defined(EAGAIN) || EAGAIN != EWOULDBLOCK + case EWOULDBLOCK: +#endif +#endif break; #ifdef ENETDOWN case ENETDOWN: @@ -4561,6 +4669,21 @@ * socket options, file descriptors, and read/write buffers. */ +#ifdef NONBLOCK_WHEN_MULTI_LISTEN + /* This assumes that on this platform the non-blocking setting of + * a listening socket is inherited. If that isn't the case, + * this is wasted effort. + */ + if (ap_listeners != ap_listeners->next) { + if (soblock(csd) != 0) { + ap_log_error(APLOG_MARK, APLOG_CRIT, NULL, + "couldn't make socket descriptor (%d) blocking again", + csd); + continue; + } + } +#endif /* NONBLOCK_WHEN_MULTI_LISTEN */ + clen = sizeof(sa_server); if (getsockname(csd, &sa_server, &clen) < 0) { ap_log_error(APLOG_MARK, APLOG_DEBUG, server_conf, @@ -6332,15 +6455,22 @@ if (csd == INVALID_SOCKET) { csd = -1; } - } while (csd < 0 && h_errno == EINTR); + } while (csd < 0 && h_errno == WSAEINTR); if (csd == INVALID_SOCKET) { - if (h_errno != WSAECONNABORTED) { + if ((h_errno != WSAECONNABORTED) && (h_errno != WSAEWOULDBLOCK)) { ap_log_error(APLOG_MARK, APLOG_ERR, server_conf, "accept: (client socket) failed with errno = %d",h_errno); } } else { + u_long one = 0; + + if (soblock(csd) != 0) { + ap_log_error(APLOG_MARK, APLOG_ERR, server_conf, + "%d couldn't make socket descriptor (%d) blocking again.", h_errno, csd); + continue; + } add_job(csd); } }