diff --git a/ares/CHANGES b/ares/CHANGES index 0fa138cdf..f76edc858 100644 --- a/ares/CHANGES +++ b/ares/CHANGES @@ -1,5 +1,11 @@ Changelog for the c-ares project +* October 7, 2009 (Yang Tse) +- Overhauled ares__get_hostent() Fixing out of bounds memory overwrite + triggered with malformed /etc/hosts file. Improving parsing of /etc/hosts + file. Validating requested address family. Ensuring that failures always + return a NULL pointer. Adjusting header inclusions. + * 4 Sep 2009 (Daniel Stenberg) - Jakub Hrozek added ares_parse_srv_reply() for SRV parsing diff --git a/ares/RELEASE-NOTES b/ares/RELEASE-NOTES index 4eef9541f..215c4d2ee 100644 --- a/ares/RELEASE-NOTES +++ b/ares/RELEASE-NOTES @@ -19,6 +19,7 @@ Fixed: o only expose/export symbols starting with 'ares_' o fix \Device\TCP handle leaks triggered by buggy iphlpapi.dll o init without internet gone no longer fails + o out of bounds memory overwrite triggered with malformed /etc/hosts file Thanks go to these friendly people for their efforts and contributions: diff --git a/ares/ares__get_hostent.c b/ares/ares__get_hostent.c index 90fd88f48..8402714c4 100644 --- a/ares/ares__get_hostent.c +++ b/ares/ares__get_hostent.c @@ -1,6 +1,6 @@ /* $Id$ */ -/* Copyright 1998 by the Massachusetts Institute of Technology. +/* Copyright 1998, 2009 by the Massachusetts Institute of Technology. * * Permission to use, copy, modify, and distribute this * software and its documentation for any purpose and without @@ -17,25 +17,18 @@ #include "setup.h" -#if !defined(WIN32) || defined(WATT32) #ifdef HAVE_SYS_SOCKET_H -#include +# include #endif #ifdef HAVE_NETINET_IN_H -#include +# include #endif #ifdef HAVE_NETDB_H -#include +# include #endif #ifdef HAVE_ARPA_INET_H -#include +# include #endif -#endif - -#include -#include -#include -#include #include "ares.h" #include "inet_net_pton.h" @@ -43,136 +36,203 @@ int ares__get_hostent(FILE *fp, int family, struct hostent **host) { - char *line = NULL, *p, *q, *canonical, **alias; - int status, linesize, end_at_hostname, naliases; + char *line = NULL, *p, *q, **alias; + char *txtaddr, *txthost, *txtalias; + int status, linesize, addrfam, naliases; struct in_addr addr; struct in6_addr addr6; - size_t addrlen = sizeof(struct in_addr); + size_t addrlen; struct hostent *hostent = NULL; + *host = NULL; /* Assume failure */ + + /* Validate family */ + switch (family) { + case AF_INET: + case AF_INET6: + case AF_UNSPEC: + break; + default: + return ARES_EBADFAMILY; + } + while ((status = ares__read_line(fp, &line, &linesize)) == ARES_SUCCESS) { - /* Skip comment lines; terminate line at comment character. */ - if (*line == '#' || !*line) - continue; - p = strchr(line, '#'); - if (p) - *p = 0; - /* Get the address part. */ + /* Trim line comment. */ p = line; + while (*p && (*p != '#')) + p++; + *p = '\0'; + + /* Trim trailing whitespace. */ + q = p - 1; + while ((q >= line) && ISSPACE(*q)) + q--; + *++q = '\0'; + + /* Skip leading whitespace. */ + p = line; + while (*p && ISSPACE(*p)) + p++; + if (!*p) + /* Ignore line if empty. */ + continue; + + /* Pointer to start of IPv4 or IPv6 address part. */ + txtaddr = p; + + /* Advance past address part. */ while (*p && !ISSPACE(*p)) p++; if (!*p) - continue; - *p = 0; - addr.s_addr = inet_addr(line); - if (addr.s_addr == INADDR_NONE) - { - /* It wasn't an AF_INET dotted address, then AF_UNSPEC and AF_INET6 - families are subject for this further check */ - if ((family != AF_INET) && - (ares_inet_pton(AF_INET6, line, &addr6) > 0)) { - addrlen = sizeof(struct in6_addr); - family = AF_INET6; - } - else - continue; - } - else if (family == AF_UNSPEC) - family = AF_INET; /* now confirmed! */ - else if (family != AF_INET) - /* unknown, keep moving */ + /* Ignore line if reached end of line. */ continue; - /* Get the canonical hostname. */ + /* Null terminate address part. */ + *p = '\0'; + + /* Advance to host name */ p++; - while (ISSPACE(*p)) + while (*p && ISSPACE(*p)) p++; if (!*p) + /* Ignore line if reached end of line. */ continue; - q = p; - while (*q && !ISSPACE(*q)) - q++; - end_at_hostname = (*q == 0); - *q = 0; - canonical = p; - naliases = 0; - if (!end_at_hostname) + /* Pointer to start of host name. */ + txthost = p; + + /* Advance past host name. */ + while (*p && !ISSPACE(*p)) + p++; + + /* Pointer to start of first alias. */ + txtalias = NULL; + if (*p) { - /* Count the aliases. */ - p = q + 1; - while (ISSPACE(*p)) - p++; + q = p + 1; + while (*q && ISSPACE(*q)) + q++; + if (*q) + txtalias = q; + } + + /* Null terminate host name. */ + *p = '\0'; + + /* find out number of aliases. */ + naliases = 0; + if (txtalias) + { + p = txtalias; while (*p) { while (*p && !ISSPACE(*p)) p++; - while (ISSPACE(*p)) + while (*p && ISSPACE(*p)) p++; naliases++; } } - /* Allocate memory for the host structure. */ + /* Convert address string to network address for the requested family. */ + addrlen = 0; + addrfam = AF_UNSPEC; + if ((family == AF_INET) || (family == AF_UNSPEC)) + { + addr.s_addr = inet_addr(txtaddr); + if (addr.s_addr != INADDR_NONE) + { + /* Actual network address family and length. */ + addrfam = AF_INET; + addrlen = sizeof(struct in_addr); + } + } + if ((family == AF_INET6) || ((family == AF_UNSPEC) && (!addrlen))) + { + if (ares_inet_pton(AF_INET6, txtaddr, &addr6) > 0) + { + /* Actual network address family and length. */ + addrfam = AF_INET6; + addrlen = sizeof(struct in6_addr); + } + } + if (!addrlen) + /* Ignore line if invalid address string for the requested family. */ + continue; + + /* + ** Actual address family possible values are AF_INET and AF_INET6 only. + */ + + /* Allocate memory for the hostent structure. */ hostent = malloc(sizeof(struct hostent)); if (!hostent) break; + + /* Initialize fields for out of memory condition. */ hostent->h_aliases = NULL; hostent->h_addr_list = NULL; - hostent->h_name = strdup(canonical); + + /* Copy official host name. */ + hostent->h_name = strdup(txthost); if (!hostent->h_name) break; + + /* Copy network address. */ hostent->h_addr_list = malloc(2 * sizeof(char *)); if (!hostent->h_addr_list) break; + hostent->h_addr_list[1] = NULL; hostent->h_addr_list[0] = malloc(addrlen); if (!hostent->h_addr_list[0]) break; + if (addrfam == AF_INET) + memcpy(hostent->h_addr_list[0], &addr, addrlen); + else + memcpy(hostent->h_addr_list[0], &addr6, addrlen); + + /* Copy aliases. */ hostent->h_aliases = malloc((naliases + 1) * sizeof(char *)); if (!hostent->h_aliases) break; - - /* Copy in aliases. */ - naliases = 0; - if (!end_at_hostname) + alias = hostent->h_aliases; + while (naliases >= 0) + *(alias + naliases--) = NULL; + while (txtalias) { - p = canonical + strlen(canonical) + 1; - while (ISSPACE(*p)) + p = txtalias; + while (*p && !ISSPACE(*p)) p++; - while (*p) - { - q = p; - while (*q && !ISSPACE(*q)) - q++; - hostent->h_aliases[naliases] = malloc(q - p + 1); - if (hostent->h_aliases[naliases] == NULL) - break; - memcpy(hostent->h_aliases[naliases], p, q - p); - hostent->h_aliases[naliases][q - p] = 0; - p = q; - while (ISSPACE(*p)) - p++; - naliases++; - } - if (*p) + q = p; + while (*q && ISSPACE(*q)) + q++; + *p = '\0'; + if ((*alias = strdup(txtalias)) == NULL) break; + alias++; + txtalias = *q ? q : NULL; } - hostent->h_aliases[naliases] = NULL; + if (txtalias) + /* Alias memory allocation failure. */ + break; - hostent->h_addrtype = family; + /* Copy actual network address family and length. */ + hostent->h_addrtype = addrfam; hostent->h_length = (int)addrlen; - if (family == AF_INET) - memcpy(hostent->h_addr_list[0], &addr, addrlen); - else if (family == AF_INET6) - memcpy(hostent->h_addr_list[0], &addr6, addrlen); - hostent->h_addr_list[1] = NULL; - *host = hostent; + + /* Free line buffer. */ free(line); + + /* Return hostent successfully */ + *host = hostent; return ARES_SUCCESS; + } - if(line) + + /* If allocated, free line buffer. */ + if (line) free(line); if (status == ARES_SUCCESS) @@ -180,22 +240,22 @@ int ares__get_hostent(FILE *fp, int family, struct hostent **host) /* Memory allocation failure; clean up. */ if (hostent) { - if(hostent->h_name) + if (hostent->h_name) free((char *) hostent->h_name); if (hostent->h_aliases) { for (alias = hostent->h_aliases; *alias; alias++) free(*alias); + free(hostent->h_aliases); + } + if (hostent->h_addr_list) + { + if (hostent->h_addr_list[0]) + free(hostent->h_addr_list[0]); + free(hostent->h_addr_list); } - if(hostent->h_aliases) - free(hostent->h_aliases); - if (hostent->h_addr_list && hostent->h_addr_list[0]) - free(hostent->h_addr_list[0]); - if(hostent->h_addr_list) - free(hostent->h_addr_list); free(hostent); } - *host = NULL; return ARES_ENOMEM; }