Bug#722075: libc6: getaddrinfo() sends DNS queries to random file descriptors
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;
}
Reply to: