mirror of
https://sourceware.org/git/glibc.git
synced 2025-08-08 17:42:12 +03:00
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.
This commit is contained in:
@@ -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;
|
||||
/* 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]);
|
||||
|
||||
bufferend = (char *) &result->h_aliases[naliases + 1];
|
||||
/* 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,46 +192,74 @@ 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
|
||||
|
||||
/* Record the aliases. */
|
||||
for (size_t i = 0; tmp_result_buf.h_aliases[i] != NULL; ++i)
|
||||
{
|
||||
char *alias = tmp_result_buf.h_aliases[i];
|
||||
array_add (&aliases,
|
||||
alloc_buffer_copy_string (&outbuf, alias));
|
||||
}
|
||||
|
||||
/* 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)
|
||||
{
|
||||
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))
|
||||
{
|
||||
++newaliases;
|
||||
newstrlen += strlen (tmp_result_buf.h_name) + 1;
|
||||
*errnop = ENOMEM;
|
||||
*herrnop = NETDB_INTERNAL;
|
||||
status = NSS_STATUS_UNAVAIL;
|
||||
break;
|
||||
}
|
||||
|
||||
/* 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)
|
||||
/* Request a larger output buffer if we ran out of room. */
|
||||
if (alloc_buffer_has_failed (&outbuf))
|
||||
{
|
||||
*errnop = ERANGE;
|
||||
*herrnop = NETDB_INTERNAL;
|
||||
@@ -217,63 +267,6 @@ gethostbyname3_multi (FILE * stream, const char *name, int af,
|
||||
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)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
/* 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;
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user