diff options
author | Florian Weimer <fweimer@redhat.com> | 2016-04-29 10:35:34 +0200 |
---|---|---|
committer | Aurelien Jarno <aurelien@aurel32.net> | 2016-05-11 14:57:20 +0200 |
commit | 9d4fe83c8ccf2a4dcc87f905b474d3053c6c42b7 (patch) | |
tree | a5fde181c1180912f6599726dcd1f3d01e58ef76 | |
parent | 46329bea58c143887c7109926d03901c0ccf81ed (diff) | |
download | glibc-9d4fe83c8ccf2a4dcc87f905b474d3053c6c42b7.tar.gz glibc-9d4fe83c8ccf2a4dcc87f905b474d3053c6c42b7.tar.xz glibc-9d4fe83c8ccf2a4dcc87f905b474d3053c6c42b7.zip |
CVE-2016-3706: getaddrinfo: stack overflow in hostent conversion [BZ #20010]
When converting a struct hostent response to struct gaih_addrtuple, the gethosts macro (which is called from gaih_inet) used alloca, without malloc fallback for large responses. This commit changes this code to use calloc unconditionally. This commit also consolidated a second hostent-to-gaih_addrtuple conversion loop (in gaih_inet) to use the new conversion function. (cherry picked from commit 4ab2ab03d4351914ee53248dc5aef4a8c88ff8b9)
-rw-r--r-- | ChangeLog | 10 | ||||
-rw-r--r-- | NEWS | 7 | ||||
-rw-r--r-- | sysdeps/posix/getaddrinfo.c | 130 |
3 files changed, 85 insertions, 62 deletions
diff --git a/ChangeLog b/ChangeLog index 0471430325..6dfe41a9b0 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,13 @@ +2016-04-29 Florian Weimer <fweimer@redhat.com> + + [BZ #20010] + CVE-2016-3706 + * sysdeps/posix/getaddrinfo.c + (convert_hostent_to_gaih_addrtuple): New function. + (gethosts): Call convert_hostent_to_gaih_addrtuple. + (gaih_inet): Use convert_hostent_to_gaih_addrtuple to convert + AF_INET data. + 2016-05-04 Florian Weimer <fweimer@redhat.com> [BZ #19779] diff --git a/NEWS b/NEWS index fe7ce0f213..99ff28a267 100644 --- a/NEWS +++ b/NEWS @@ -26,7 +26,7 @@ Version 2.22.1 17905, 18420, 18421, 18480, 18589, 18743, 18778, 18781, 18787, 18796, 18870, 18887, 18921, 18928, 18969, 18985, 19003, 19018, 19048, 19058, 19174, 19178, 19182, 19243, 19590, 19682, 19791, 19822, 19853, 19879, - 19779. + 19779, 20010. * The getnetbyname implementation in nss_dns had a potentially unbounded alloca call (in the form of a call to strdupa), leading to a stack @@ -48,6 +48,11 @@ Version 2.22.1 * The glob function suffered from a stack-based buffer overflow when it was called with the GLOB_ALTDIRFUNC flag and encountered a long file name. Reported by Alexander Cherepanov. (CVE-2016-1234) + +* Previously, getaddrinfo copied large amounts of address data to the stack, + even after the fix for CVE-2013-4458 has been applied, potentially + resulting in a stack overflow. getaddrinfo now uses a heap allocation + instead. Reported by Michael Petlan. (CVE-2016-3706) Version 2.22 diff --git a/sysdeps/posix/getaddrinfo.c b/sysdeps/posix/getaddrinfo.c index 31bb7e66dc..715e85823e 100644 --- a/sysdeps/posix/getaddrinfo.c +++ b/sysdeps/posix/getaddrinfo.c @@ -168,9 +168,58 @@ gaih_inet_serv (const char *servicename, const struct gaih_typeproto *tp, return 0; } +/* Convert struct hostent to a list of struct gaih_addrtuple objects. + h_name is not copied, and the struct hostent object must not be + deallocated prematurely. *RESULT must be NULL or a pointer to an + object allocated using malloc, which is freed. */ +static bool +convert_hostent_to_gaih_addrtuple (const struct addrinfo *req, + int family, + struct hostent *h, + struct gaih_addrtuple **result) +{ + free (*result); + *result = NULL; + + /* Count the number of addresses in h->h_addr_list. */ + size_t count = 0; + for (char **p = h->h_addr_list; *p != NULL; ++p) + ++count; + + /* Report no data if no addresses are available, or if the incoming + address size is larger than what we can store. */ + if (count == 0 || h->h_length > sizeof (((struct gaih_addrtuple) {}).addr)) + return true; + + struct gaih_addrtuple *array = calloc (count, sizeof (*array)); + if (array == NULL) + return false; + + for (size_t i = 0; i < count; ++i) + { + if (family == AF_INET && req->ai_family == AF_INET6) + { + /* Perform address mapping. */ + array[i].family = AF_INET6; + memcpy(array[i].addr + 3, h->h_addr_list[i], sizeof (uint32_t)); + array[i].addr[2] = htonl (0xffff); + } + else + { + array[i].family = family; + memcpy (array[i].addr, h->h_addr_list[i], h->h_length); + } + array[i].next = array + i + 1; + } + array[0].name = h->h_name; + array[count - 1].next = NULL; + + *result = array; + return true; +} + #define gethosts(_family, _type) \ { \ - int i; \ int herrno; \ struct hostent th; \ struct hostent *h; \ @@ -219,36 +268,23 @@ gaih_inet_serv (const char *servicename, const struct gaih_typeproto *tp, } \ else if (h != NULL) \ { \ - for (i = 0; h->h_addr_list[i]; i++) \ + /* Make sure that addrmem can be freed. */ \ + if (!malloc_addrmem) \ + addrmem = NULL; \ + if (!convert_hostent_to_gaih_addrtuple (req, _family,h, &addrmem)) \ { \ - if (*pat == NULL) \ - { \ - *pat = __alloca (sizeof (struct gaih_addrtuple)); \ - (*pat)->scopeid = 0; \ - } \ - uint32_t *addr = (*pat)->addr; \ - (*pat)->next = NULL; \ - (*pat)->name = i == 0 ? strdupa (h->h_name) : NULL; \ - if (_family == AF_INET && req->ai_family == AF_INET6) \ - { \ - (*pat)->family = AF_INET6; \ - addr[3] = *(uint32_t *) h->h_addr_list[i]; \ - addr[2] = htonl (0xffff); \ - addr[1] = 0; \ - addr[0] = 0; \ - } \ - else \ - { \ - (*pat)->family = _family; \ - memcpy (addr, h->h_addr_list[i], sizeof(_type)); \ - } \ - pat = &((*pat)->next); \ + _res.options |= old_res_options & RES_USE_INET6; \ + result = -EAI_SYSTEM; \ + goto free_and_return; \ } \ + *pat = addrmem; \ + /* The conversion uses malloc unconditionally. */ \ + malloc_addrmem = true; \ \ if (localcanon != NULL && canon == NULL) \ canon = strdupa (localcanon); \ \ - if (_family == AF_INET6 && i > 0) \ + if (_family == AF_INET6 && *pat != NULL) \ got_ipv6 = true; \ } \ } @@ -612,44 +648,16 @@ gaih_inet (const char *name, const struct gaih_service *service, { if (h != NULL) { - int i; - /* We found data, count the number of addresses. */ - for (i = 0; h->h_addr_list[i]; ++i) - ; - if (i > 0 && *pat != NULL) - --i; - - if (__libc_use_alloca (alloca_used - + i * sizeof (struct gaih_addrtuple))) - addrmem = alloca_account (i * sizeof (struct gaih_addrtuple), - alloca_used); - else - { - addrmem = malloc (i - * sizeof (struct gaih_addrtuple)); - if (addrmem == NULL) - { - result = -EAI_MEMORY; - goto free_and_return; - } - malloc_addrmem = true; - } - - /* Now convert it into the list. */ - struct gaih_addrtuple *addrfree = addrmem; - for (i = 0; h->h_addr_list[i]; ++i) + /* We found data, convert it. */ + if (!convert_hostent_to_gaih_addrtuple + (req, AF_INET, h, &addrmem)) { - if (*pat == NULL) - { - *pat = addrfree++; - (*pat)->scopeid = 0; - } - (*pat)->next = NULL; - (*pat)->family = AF_INET; - memcpy ((*pat)->addr, h->h_addr_list[i], - h->h_length); - pat = &((*pat)->next); + result = -EAI_MEMORY; + goto free_and_return; } + *pat = addrmem; + /* The conversion uses malloc unconditionally. */ + malloc_addrmem = true; } } else |