[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

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: