Package: libc6 Version: 2.13-38 Severity: normal Under high load, getaddrinfo() seems to start sending DNS queries to random file descriptors.
If a process has opened connections to remote servers or clients, getaddrinfo() may write DNS queries to these connections. This has been noticed on a real world application written in golang, and the bug was successfuly reproduced using pure C code. The attached code reproduces the bug on libc6 packages 2.13-38 (stable), 2.17-92 (testing). What the code does: - a thread listens to a local unix socket - a thread connects to the unix socket, never writes to it, dups the connection as much as possible (fills the fd space), close the dups, and starts dup()ing again - lots of threads call getaddrinfo() Under less than a minute, the listener starts reading garbage (presumably DNS queries). -- System Information: Debian Release: jessie/sid APT prefers testing APT policy: (990, 'testing'), (500, 'unstable'), (500, 'stable'), (1, 'experimental') Architecture: i386 (x86_64) Foreign Architectures: amd64 Kernel: Linux 3.10-2-amd64 (SMP w/8 CPU cores) Locale: LANG=fr_FR.UTF-8, LC_CTYPE=fr_FR.UTF-8 (charmap=UTF-8) Shell: /bin/sh linked to /bin/dash
// gcc -o bug bug.c -lpthread && ./bug #include <sys/types.h> #include <sys/stat.h> #include <stdio.h> #include <sys/socket.h> #include <netdb.h> #include <errno.h> #include <sys/un.h> #include <stdlib.h> #include <pthread.h> #include <unistd.h> #define LOOKUP_THREADS 1000 #define SOCKET_PATH "/tmp/test.sock" void* lookup_thread(void *_) { for (;;) { struct addrinfo *res; struct addrinfo hints; memset(&hints, 0, sizeof(hints)); if (0 == getaddrinfo("example.com", NULL, &hints, &res)) { freeaddrinfo(res); } } return NULL; } void lookup() { int i; for (i = 0; i < LOOKUP_THREADS; i++) { pthread_t t; pthread_attr_t ta; pthread_attr_init(&ta); pthread_attr_setstacksize(&ta, 100<<10); if (0 != pthread_create(&t, &ta, lookup_thread, (void*)(uintptr_t)i)) { perror("pthread_create lookup thread"); exit(1); } pthread_attr_destroy(&ta); } } void* server_thread(void *_) { struct sockaddr_un saddr; int s = socket(AF_UNIX, SOCK_STREAM, 0); memset(&saddr, 0, sizeof(saddr)); if (s == -1) { perror("server socket"); return NULL; } saddr.sun_family = AF_UNIX; strncpy(saddr.sun_path, SOCKET_PATH, sizeof(saddr.sun_path)-1); if (bind(s, (struct sockaddr *)&saddr, sizeof(struct sockaddr_un)) == -1) { perror("bind"); close(s); return NULL; } if (-1 == listen(s, 0)) { perror("listen"); close(s); return NULL; } for (;;) { struct sockaddr_un paddr; socklen_t paddrlen; int fd = accept(s, (struct sockaddr*) &paddr, &paddrlen); if (fd == -1) { perror("accept"); close(s); return NULL; } for (;;) { char c; int n = read(fd, &c, 1); fprintf(stderr, "BUG: has read char 0x%02hhx: %c\a\n", (unsigned int) c, c); if (n == 0) { break; } } close(fd); } return NULL; } void sock() { int s, i, m; struct sockaddr_un saddr; // open a client socket to SOCK_STREAM for (;;) { s = socket(AF_UNIX, SOCK_STREAM, 0); if (s == -1) { perror("socket"); sleep(1); continue; } memset(&saddr, 0, sizeof(saddr)); saddr.sun_family = AF_UNIX; strncpy(saddr.sun_path, SOCKET_PATH, sizeof(saddr.sun_path)-1); if (connect(s, (struct sockaddr *)&saddr, sizeof(struct sockaddr_un)) == -1) { perror("connect"); close(s); sleep(1); continue; } break; } // repeatedly fill the file descriptor space with dups of the socket, and close the dups int fds[65536]; for (;;) { for (i = 0, m = 0; i < sizeof(fds)/sizeof(*fds); i++) { int fd = dup(s); if (fd == -1) { if (errno == EMFILE) { break; } else { perror("dup"); } } fds[i] = fd; m = i; } for (i = 0; i <= m; i++) { close(fds[i]); } continue; } } int main() { // Listen on SOCKET_PATH; if we receive something on this socket, we have // successfuly reproduced the bug unlink(SOCKET_PATH); pthread_t t; if (0 != pthread_create(&t, NULL, server_thread, NULL)) { perror("pthread_create server thread"); return 1; } // Do many getaddrinfo() in parallel lookup(); // Connect to SOCKET_PATH and dup()&close() the fd repeatedly sock(); return 0; }