Sophie

Sophie

distrib > Mandriva > 10.0-com > i586 > by-pkgid > c3f48b97eea7ea342086686527f89d24 > files > 12

apache-1.3.29-1.4.100mdk.src.rpm

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