next up previous
Next: Other Helper Protocols Up: Network Layer Attacks Previous: Back to Access Control

DNS data sizes

Whoops! Did you think we were done with DNS related problems already? No Way!

Another common problem with network applications that use gethostbyname and friends occurs when the programmer assumes that data returned by these functions is within certain limits specified by the DNS protocol definition.

Check with RFC 1030: what are the exact limits?

For instance, the DNS protocol specification says that a DNS domain name cannot be longer that 255 characters, including the dots, and that each component of the name can be 63 bytes at most. But, as I said above, a network protocol is just a convention, and if your implementation does everything the spec mandates everything should work fine. However, the opposite is not automatically true: if your peer intentionally violates the specification, things will often continue to work. In fact, there's an oft-repeated rule of network development that says: ``be strict in what you send, but be liberal in what you accept.'' The intention behind this is that minor protocol glitches should be accepted for the sake of interoperability.

However, this can have serious effects on your security. For instance, there used to be many network applications that assumed that host names returned by gethostbyaddr would always fit into a buffer of 1024 bytes. But that's not necessarily true! On older Linux versions using the libc5 library, gethostbyaddr would happily accept DNS replies that contained host names of up to 1500-odd bytes and return these names to the application, resulting in a buffer overflow. And even in current versions, which do reject DNS responses that contain host names longer than 255 bytes, the representation of a domain name returned may be as long as 1000 bytes, because non-printable bytes are represented using the \nnn convention for octal numbers.

Similarly, it's a bad idea to declare a character buffer of MAXHOSTNAMELEN bytes and assume that this is big enough to hold the results of any DNS query. On several platforms, I've seen different values for this macro, on some as small as 64, which is clearly way too small to hold a maximum size DNS name.

It is worth pointing out that this sort of buffer overflow is harder to exploit than others, because you're limited to printable ASCII characters on most platforms. But it's not impossible! There have been buffer overflow exploits that consisted entirely of characters in the printable ASCII range.

The bottom line is: always make sure host names returned by gethostbyaddr and friends actually fit into your buffer, and reject the name if not.

There's a similar problem with addresses returned by the DNS. To understand the issue here, let's take a closer look at the struct hostent returned by the gethostby* family of functions:

struct hostent {
    char *      h_name;       /* canonical hostname */
    char **     h_aliases;    /* array of aliases */
    int         h_addrtype;   /* address type */
    int         h_length;     /* length of each address */
    char **     h_addr_list;  /* list of pointers to addresses */
};

When you use gethostbyname for the address associated with a given host name, it will populate this struct with the canonical host name, list of aliases, and address information. For IPv4 addresses, the address type is AF_INET, and the address length is 4. The h_addr_list array contains pointers to 4 byte sized chunks of memory, each holding an IPv4 address. This leads many programmers to believe that the following piece of code is okay:

    struct sockaddr_in sin;
    struct hostent     *hp;

    if (!(hp = gethostbyname(hostname)))
        fatal("unable to resolve hostname");
    memcpy(&sin.sin_addr, hp->h_addr_list[0], hp->h_length);

The bad news is that some resolver libraries would also accept DNS responses that contained address information longer than four bytes, and set h_length to the address length given in the DNS reply. This makes it quite easy for an attacker to overflow the the sockaddr_in variable above! Simply send a DNS response that contains some standard buffer overflow exploit as the ``address,'' and the memcpy call above will dutifully copy the exploit onto the stack. For example, code like this was present in older versions of traceroute which is setuid root on many systems, and could be exploited by a local user to obtain root privilege.

Therefore, it is a good idea to always make sure that the address information returned in a struct hostent is actually what you expect it is by doing something like this:

    if (!(hp = gethostbyname(hostname)))
        fatal("unable to resolve hostname");
    if (hp->h_addrtype != AF_INET || hp->h_length != 4)
        fatal("bad address data from DNS!");
    memcpy(&sin.sin_addr, hp->h_addr_list[0], 4);


next up previous
Next: Other Helper Protocols Up: Network Layer Attacks Previous: Back to Access Control
Olaf Kirch 2002-01-16