diff options
author | Florian Weimer <fweimer@redhat.com> | 2017-10-11 07:07:51 +0200 |
---|---|---|
committer | Florian Weimer <fweimer@redhat.com> | 2017-10-11 07:07:51 +0200 |
commit | d8425e116cdd954fea0c04c0f406179b5daebbb3 (patch) | |
tree | 0d82c5f6f571653665912cf0fd0d3062c594695f /nss/nss_files/files-hosts.c | |
parent | 78e806fd8cd8c918d3bbe1bcdf9091ab365e4a69 (diff) | |
download | glibc-d8425e116cdd954fea0c04c0f406179b5daebbb3.tar.gz glibc-d8425e116cdd954fea0c04c0f406179b5daebbb3.tar.xz glibc-d8425e116cdd954fea0c04c0f406179b5daebbb3.zip |
nss_files: Avoid large buffers with many host addresses [BZ #22078]
The previous implementation had at least a quadratic space requirement in the number of host addresses and aliases.
Diffstat (limited to 'nss/nss_files/files-hosts.c')
-rw-r--r-- | nss/nss_files/files-hosts.c | 225 |
1 files changed, 129 insertions, 96 deletions
diff --git a/nss/nss_files/files-hosts.c b/nss/nss_files/files-hosts.c index 763fa39a47..6f7cc4d94b 100644 --- a/nss/nss_files/files-hosts.c +++ b/nss/nss_files/files-hosts.c @@ -23,6 +23,7 @@ #include <netdb.h> #include <resolv/resolv-internal.h> #include <scratch_buffer.h> +#include <alloc_buffer.h> /* Get implementation for some internal functions. */ @@ -116,24 +117,45 @@ DB_LOOKUP (hostbyaddr, ,,, }, const void *addr, socklen_t len, int af) #undef EXTRA_ARGS_VALUE +/* Type of the address and alias arrays. */ +#define DYNARRAY_STRUCT array +#define DYNARRAY_ELEMENT char * +#define DYNARRAY_PREFIX array_ +#include <malloc/dynarray-skeleton.c> + static enum nss_status gethostbyname3_multi (FILE * stream, const char *name, int af, struct hostent *result, char *buffer, size_t buflen, int *errnop, int *herrnop, int flags) { + assert (af == AF_INET || af == AF_INET6); + /* We have to get all host entries from the file. */ struct scratch_buffer tmp_buffer; scratch_buffer_init (&tmp_buffer); struct hostent tmp_result_buf; - int naddrs = 1; - int naliases = 0; - char *bufferend; + struct array addresses; + array_init (&addresses); + struct array aliases; + array_init (&aliases); enum nss_status status; - while (result->h_aliases[naliases] != NULL) - ++naliases; - - bufferend = (char *) &result->h_aliases[naliases + 1]; + /* Preserve the addresses and aliases encountered so far. */ + for (size_t i = 0; result->h_addr_list[i] != NULL; ++i) + array_add (&addresses, result->h_addr_list[i]); + for (size_t i = 0; result->h_aliases[i] != NULL; ++i) + array_add (&aliases, result->h_aliases[i]); + + /* The output buffer re-uses now-unused space at the end of the + buffer, starting with the aliases array. It comes last in the + data produced by internal_getent. (The alias names themselves + are still located in the line read in internal_getent, which is + stored at the beginning of the buffer.) */ + struct alloc_buffer outbuf; + { + char *bufferend = (char *) result->h_aliases; + outbuf = alloc_buffer_create (bufferend, buffer + buflen - bufferend); + } while (true) { @@ -170,110 +192,81 @@ gethostbyname3_multi (FILE * stream, const char *name, int af, } while ((matches = 0)); + /* If the line matches, we need to copy the addresses and + aliases, so that we can reuse tmp_buffer for the next + line. */ if (matches) { - /* We could be very clever and try to recycle a few bytes - in the buffer instead of generating new arrays. But - we are not doing this here since it's more work than - it's worth. Simply let the user provide a bit bigger - buffer. */ - char **new_h_addr_list; - char **new_h_aliases; - int newaliases = 0; - size_t newstrlen = 0; - int cnt; - - /* Count the new aliases and the length of the strings. */ - while (tmp_result_buf.h_aliases[newaliases] != NULL) + /* Record the addresses. */ + for (size_t i = 0; tmp_result_buf.h_addr_list[i] != NULL; ++i) { - char *cp = tmp_result_buf.h_aliases[newaliases]; - ++newaliases; - newstrlen += strlen (cp) + 1; + /* Allocate the target space in the output buffer, + depending on the address family. */ + void *target; + if (af == AF_INET) + { + assert (tmp_result_buf.h_length == 4); + target = alloc_buffer_alloc (&outbuf, struct in_addr); + } + else if (af == AF_INET6) + { + assert (tmp_result_buf.h_length == 16); + target = alloc_buffer_alloc (&outbuf, struct in6_addr); + } + else + __builtin_unreachable (); + + if (target == NULL) + { + /* Request a larger output buffer. */ + *errnop = ERANGE; + *herrnop = NETDB_INTERNAL; + status = NSS_STATUS_TRYAGAIN; + break; + } + memcpy (target, tmp_result_buf.h_addr_list[i], + tmp_result_buf.h_length); + array_add (&addresses, target); } - /* If the real name is different add it also to the - aliases. This means that there is a duplication - in the alias list but this is really the user's - problem. */ - if (strcmp (old_result->h_name, - tmp_result_buf.h_name) != 0) + + /* Record the aliases. */ + for (size_t i = 0; tmp_result_buf.h_aliases[i] != NULL; ++i) { - ++newaliases; - newstrlen += strlen (tmp_result_buf.h_name) + 1; + char *alias = tmp_result_buf.h_aliases[i]; + array_add (&aliases, + alloc_buffer_copy_string (&outbuf, alias)); } - /* Make sure bufferend is aligned. */ - assert ((bufferend - (char *) 0) % sizeof (char *) == 0); - - /* Now we can check whether the buffer is large enough. - 16 is the maximal size of the IP address. */ - if (bufferend + 16 + (naddrs + 2) * sizeof (char *) - + roundup (newstrlen, sizeof (char *)) - + (naliases + newaliases + 1) * sizeof (char *) - >= buffer + buflen) + /* If the real name is different add, it also to the + aliases. This means that there is a duplication in + the alias list but this is really the user's + problem. */ + { + char *new_name = tmp_result_buf.h_name; + if (strcmp (old_result->h_name, new_name) != 0) + array_add (&aliases, + alloc_buffer_copy_string (&outbuf, new_name)); + } + + /* Report memory allocation failures during the + expansion of the temporary arrays. */ + if (array_has_failed (&addresses) || array_has_failed (&aliases)) { - *errnop = ERANGE; + *errnop = ENOMEM; *herrnop = NETDB_INTERNAL; - status = NSS_STATUS_TRYAGAIN; + status = NSS_STATUS_UNAVAIL; break; } - new_h_addr_list = - (char **) (bufferend - + roundup (newstrlen, sizeof (char *)) - + 16); - new_h_aliases = - (char **) ((char *) new_h_addr_list - + (naddrs + 2) * sizeof (char *)); - - /* Copy the old data in the new arrays. */ - for (cnt = 0; cnt < naddrs; ++cnt) - new_h_addr_list[cnt] = old_result->h_addr_list[cnt]; - - for (cnt = 0; cnt < naliases; ++cnt) - new_h_aliases[cnt] = old_result->h_aliases[cnt]; - - /* Store the new strings. */ - cnt = 0; - while (tmp_result_buf.h_aliases[cnt] != NULL) + /* Request a larger output buffer if we ran out of room. */ + if (alloc_buffer_has_failed (&outbuf)) { - new_h_aliases[naliases++] = bufferend; - bufferend = (__stpcpy (bufferend, - tmp_result_buf.h_aliases[cnt]) - + 1); - ++cnt; - } - - if (cnt < newaliases) - { - new_h_aliases[naliases++] = bufferend; - bufferend = __stpcpy (bufferend, - tmp_result_buf.h_name) + 1; + *errnop = ERANGE; + *herrnop = NETDB_INTERNAL; + status = NSS_STATUS_TRYAGAIN; + break; } - /* Final NULL pointer. */ - new_h_aliases[naliases] = NULL; - - /* Round up the buffer end address. */ - bufferend += (sizeof (char *) - - ((bufferend - (char *) 0) - % sizeof (char *))) % sizeof (char *); - - /* Now the new address. */ - new_h_addr_list[naddrs++] = - memcpy (bufferend, tmp_result_buf.h_addr, - tmp_result_buf.h_length); - - /* Also here a final NULL pointer. */ - new_h_addr_list[naddrs] = NULL; - - /* Store the new array pointers. */ - old_result->h_aliases = new_h_aliases; - old_result->h_addr_list = new_h_addr_list; - - /* Compute the new buffer end. */ - bufferend = (char *) &new_h_aliases[naliases + 1]; - assert (bufferend <= buffer + buflen); - result = old_result; } /* If match was found. */ @@ -293,7 +286,47 @@ gethostbyname3_multi (FILE * stream, const char *name, int af, if (status != NSS_STATUS_TRYAGAIN) status = NSS_STATUS_SUCCESS; + if (status == NSS_STATUS_SUCCESS) + { + /* Copy the address and alias arrays into the output buffer and + add NULL terminators. The pointed-to elements were directly + written into the output buffer above and do not need to be + copied again. */ + size_t addresses_count = array_size (&addresses); + size_t aliases_count = array_size (&aliases); + char **out_addresses = alloc_buffer_alloc_array + (&outbuf, char *, addresses_count + 1); + char **out_aliases = alloc_buffer_alloc_array + (&outbuf, char *, aliases_count + 1); + if (out_addresses == NULL || out_aliases == NULL) + { + /* The output buffer is not large enough. */ + *errnop = ERANGE; + *herrnop = NETDB_INTERNAL; + status = NSS_STATUS_TRYAGAIN; + /* Fall through to function exit. */ + } + else + { + /* Everything is allocated in place. Make the copies and + adjust the array pointers. */ + memcpy (out_addresses, array_begin (&addresses), + addresses_count * sizeof (char *)); + out_addresses[addresses_count] = NULL; + memcpy (out_aliases, array_begin (&aliases), + aliases_count * sizeof (char *)); + out_aliases[aliases_count] = NULL; + + result->h_addr_list = out_addresses; + result->h_aliases = out_aliases; + + status = NSS_STATUS_SUCCESS; + } + } + scratch_buffer_free (&tmp_buffer); + array_free (&addresses); + array_free (&aliases); return status; } |