diff mbox

Allocation buffers for NSS result construction

Message ID 2526706e-18e7-1c30-84b3-812663c43b98@redhat.com
State New
Headers show

Commit Message

Florian Weimer April 22, 2017, 7:47 p.m. UTC
I'm proposing the attached patch to reorganize the way result buffers 
for the NSS lookup of functions are filled.

Comments?

Thanks,
Florian

Comments

Florian Weimer May 8, 2017, 8:24 a.m. UTC | #1
On 04/22/2017 09:47 PM, Florian Weimer wrote:
> I'm proposing the attached patch to reorganize the way result buffers 
> for the NSS lookup of functions are filled.
>  > Comments?

Ping?

Thanks,
Florian
Florian Weimer June 16, 2017, 12:20 p.m. UTC | #2
On 04/22/2017 09:47 PM, Florian Weimer wrote:
> 2017-04-22  Florian Weimer  <fweimer@redhat.com>
> 
> 	* malloc/Makefile (tests): Add tst-alloc_buffer.
> 	(routines): Add alloc_buffer_alloc_array, alloc_buffer_allocate,
> 	alloc_buffer_copy_bytes.
> 	* malloc/Versions (__libc_alloc_buffer_alloc_array)
> 	(__libc_alloc_buffer_allocate, __libc_alloc_buffer_copy_bytes):
> 	Export as GLIBC_PRIVATE.
> 	* malloc/alloc_buffer_alloc_array.c: New file.
> 	* malloc/alloc_buffer_allocate.c: Likewise.
> 	* malloc/alloc_buffer_copy_bytes.c: Likewise.
> 	* malloc/alloc_buffer_copy_string.c: Likewise.
> 	* malloc/tst-alloc_buffer.c: Likewise.
> 	* resolv/Versions (__ns_name_ntop_buffer)
> 	(__ns_name_unpack_buffer): Export as GLIBC_PRIVATE.
> 	* resolv/ns_name.c (__ns_name_ntop_buffer): New function.
> 	(ns_name_ntop): Implement using __ns_name_ntop_buffer.
> 	(__ns_name_unpack_buffer): New function.
> 	(ns_name_unpack): Implement using __ns_name_unpack_buffer.
> 	* resolv/nss_dns/dns-network.c (_nss_dns_getnetbyname_r)
> 	(_nss_dns_getnetbyaddr_r): Adjust call to getanswer_r.
> 	(getanswer_r): Store result in an allocation buffer.  Use
> 	__ns_name_ntop_buffer instead of ns_name_ntop.  Remove superfluous
> 	call to res_dnok because ns_name_ntop output is always printable
> 	ASCII.

Any further comments?  I'd like to commit this.

Thanks,
Florian
Adhemerval Zanella Netto June 16, 2017, 2:42 p.m. UTC | #3
On 22/04/2017 16:47, Florian Weimer wrote:
> I'm proposing the attached patch to reorganize the way result buffers for the NSS lookup of functions are filled.
> 
> Comments?
> 
> Thanks,
> Florian
> 
> alloc_buffer.patch
> 
> 
> Implement allocation buffers for internal use
> 
> This commit adds fixed-size allocation buffers.  The primary use
> case is in NSS modules, where dynamically sized data is stored
> in a fixed-size buffer provided by the caller.
> 
> Other uses include a replacement of mempcpy cascades (which is
> safer due to the size checking inherent to allocation buffers).
> 
> The commit converts the "network" handling in nss_dns to allocation
> buffers because this is something for which test coverage already
> exists.
> 
> 2017-04-22  Florian Weimer  <fweimer@redhat.com>
> 
> 	* malloc/Makefile (tests): Add tst-alloc_buffer.
> 	(routines): Add alloc_buffer_alloc_array, alloc_buffer_allocate,
> 	alloc_buffer_copy_bytes.
> 	* malloc/Versions (__libc_alloc_buffer_alloc_array)
> 	(__libc_alloc_buffer_allocate, __libc_alloc_buffer_copy_bytes):
> 	Export as GLIBC_PRIVATE.
> 	* malloc/alloc_buffer_alloc_array.c: New file.
> 	* malloc/alloc_buffer_allocate.c: Likewise.
> 	* malloc/alloc_buffer_copy_bytes.c: Likewise.
> 	* malloc/alloc_buffer_copy_string.c: Likewise.
> 	* malloc/tst-alloc_buffer.c: Likewise.
> 	* resolv/Versions (__ns_name_ntop_buffer)
> 	(__ns_name_unpack_buffer): Export as GLIBC_PRIVATE.
> 	* resolv/ns_name.c (__ns_name_ntop_buffer): New function.
> 	(ns_name_ntop): Implement using __ns_name_ntop_buffer.
> 	(__ns_name_unpack_buffer): New function.
> 	(ns_name_unpack): Implement using __ns_name_unpack_buffer.
> 	* resolv/nss_dns/dns-network.c (_nss_dns_getnetbyname_r)
> 	(_nss_dns_getnetbyaddr_r): Adjust call to getanswer_r.
> 	(getanswer_r): Store result in an allocation buffer.  Use
> 	__ns_name_ntop_buffer instead of ns_name_ntop.  Remove superfluous
> 	call to res_dnok because ns_name_ntop output is always printable
> 	ASCII.

I would prefer to split the patch in two, one for the alloc_buffer adition
and another one for its use in NSS result construction.

> 
> diff --git a/include/alloc_buffer.h b/include/alloc_buffer.h
> new file mode 100644
> index 0000000..386e49c
> --- /dev/null
> +++ b/include/alloc_buffer.h
> @@ -0,0 +1,383 @@
> +/* Allocation from a fixed-size buffer.
> +   Copyright (C) 2017 Free Software Foundation, Inc.
> +   This file is part of the GNU C Library.
> +
> +   The GNU C Library is free software; you can redistribute it and/or
> +   modify it under the terms of the GNU Lesser General Public
> +   License as published by the Free Software Foundation; either
> +   version 2.1 of the License, or (at your option) any later version.
> +
> +   The GNU C Library is distributed in the hope that it will be useful,
> +   but WITHOUT ANY WARRANTY; without even the implied warranty of
> +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> +   Lesser General Public License for more details.
> +
> +   You should have received a copy of the GNU Lesser General Public
> +   License along with the GNU C Library; if not, see
> +   <http://www.gnu.org/licenses/>.  */
> +
> +/* Allocation buffers are used to carve out sub-allocations from a
> +   larger allocation.  Their primary application is in writing NSS
> +   modules, which recieve a caller-allocated buffer in which they are
> +   expected to store variable-length results:
> +
> +     void *buffer = ...;
> +     size_t buffer_size = ...;
> +
> +     struct alloc_buffer buf = alloc_buffer_create (buffer, buffer_size);
> +     result->gr_name = alloc_buffer_copy_string (&buf, name);
> +
> +     // Allocate a list of group_count groups and copy strings into it.
> +     char **group_list = alloc_buffer_alloc_array
> +       (&buf, char *, group_count  + 1);
> +     if (group_list == NULL)
> +       return ...; // Request a larger buffer.
> +     for (int i = 0; i < group_count; ++i)
> +       group_list[i] = alloc_buffer_copy_string (&buf, group_list_src[i]);
> +     group_list[group_count] = NULL;
> +     ...
> +
> +     if (alloc_buffer_has_failed (&buf))
> +       return ...; // Request a larger buffer.
> +     result->gr_mem = group_list;
> +     ...
> +
> +   Note that it is not necessary to check the results of individual
> +   allocation operations if the returned pointer is not dereferenced.
> +   Allocation failure is sticky, so one check using
> +   alloc_buffer_has_failed at the end covers all previous failures.
> +
> +   A different use case involves combining multiple heap allocations
> +   into a single, large one.  In the following example, an array of
> +   doubles and an array of ints is allocated:
> +
> +     size_t double_array_size = ...;
> +     size_t int_array_size = ...;
> +
> +     struct alloc_buffer buf = alloc_buffer_allocate
> +       (double_array_size * sizeof (double) + int_array_size * sizeof (int));
> +     _Static_assert (__alignof__ (double) >= __alignof__ (int),
> +                     "no padding after double array");
> +     double *double_array = alloc_buffer_alloc_array
> +       (&buf, double, double_array_size);
> +     int *int_array = alloc_buffer_alloc_array (&buf, int, int_array_size);
> +     if (alloc_buffer_has_failed (&buf))
> +       return ...; // Report error.
> +
> +   The advantage over manual coding is that the computation of the
> +   allocation size does not need an overflow check.  The size
> +   computation is checked for consistency at run time, too.  */
> +
> +#ifndef _ALLOC_BUFFER_H
> +#define _ALLOC_BUFFER_H
> +
> +#include <inttypes.h>
> +#include <stdbool.h>
> +#include <stddef.h>
> +#include <stdlib.h>
> +#include <sys/param.h>
> +
> +/* struct alloc_buffer objects refer to a region of bytes in memory of a
> +   fixed size.  The functions below can be used to allocate single
> +   objects and arrays from this memory region, or write to its end.
> +   On allocation failure (or if an attempt to write beyond the end of
> +   the buffer with one of the copy functions), the buffer enters a
> +   failed state.
> +
> +   struct alloc_buffer objects can be copied.  The backing buffer will
> +   be shared, but the current write position will be independent.
> +
> +   Conceptually, the memory region consists of a current write pointer
> +   and a limit, beyond which the write pointer cannot move.  */

A new line maybe?

> +struct alloc_buffer
> +{
> +  /* uintptr_t is used here to simplify the alignment code, and to
> +     avoid issues undefined subtractions if the buffer covers more
> +     than half of the address space (which would result in differences
> +     which could not be represented as a ptrdiff_t value).  */
> +  uintptr_t __alloc_buffer_current;
> +  uintptr_t __alloc_buffer_end;
> +};
> +
> +enum
> +  {
> +    /* The value for the __alloc_buffer_current member which marks the
> +       buffer as invalid (together with a zero-length buffer).  */
> +    __ALLOC_BUFFER_INVALID_POINTER = 0,
> +  };
> +
> +/* Create a new allocation buffer.  The byte range from START to START
> +   + SIZE - 1 must be valid, and the allocation buffer allocates
> +   objects from that range.  If START is NULL (so that SIZE must be
> +   0), the buffer is marked as failed immediately.  */
> +static inline struct alloc_buffer
> +alloc_buffer_create (void *start, size_t size)
> +{
> +  return (struct alloc_buffer)
> +    {
> +      .__alloc_buffer_current = (uintptr_t) start,
> +      .__alloc_buffer_end = (uintptr_t) start + size
> +    };
> +}

Should we add an overflow test for sanity tests?

> +
> +/* Internal function.  See alloc_buffer_allocate below.  */
> +struct alloc_buffer __libc_alloc_buffer_allocate (size_t size);
> +libc_hidden_proto (__libc_alloc_buffer_allocate)

I am getting this while trying to build malloc/tst-alloc_buffer.o:

../include/alloc_buffer.h:125:1: error: return type defaults to ‘int’ [-Werror=implicit-int]
 libc_hidden_proto (__libc_alloc_buffer_allocate)

This is due 7c3018f9 (Suppress internal declarations for most of the
testsuite) which suppress libc_hidden_proto for testsuite.  I think we
need either to make them empty macros in this case (move their definition
outside the _ISOMAC) or move the libc_hidden_proto to another internal
header.

> +
> +/* Allocate a buffer of SIZE bytes using malloc.  The returned buffer
> +   is in a failed state if malloc fails.  */
> +static __always_inline
> +struct alloc_buffer alloc_buffer_allocate (size_t size)
> +{
> +  return __libc_alloc_buffer_allocate (size);
> +}
> +
> +/* Mark the buffer as failed.  */
> +static inline void
> +alloc_buffer_mark_failed (struct alloc_buffer *buf)
> +{
> +  buf->__alloc_buffer_current = __ALLOC_BUFFER_INVALID_POINTER;
> +  buf->__alloc_buffer_end = __ALLOC_BUFFER_INVALID_POINTER;
> +}
> +
> +/* Deallocate the buffer and mark it as failed.  The buffer must be in
> +   its initial state; if data has been added to it, an invocation of
> +   alloc_buffer_free results in undefined behavior.  This means that
> +   callers need to make a copy of the buffer if they need to free it
> +   later.  Deallocating a failed buffer is allowed; it has no
> +   effect.  */
> +static inline void
> +alloc_buffer_free (struct alloc_buffer *buf)
> +{
> +  _Static_assert (__ALLOC_BUFFER_INVALID_POINTER == 0,
> +		  "free can be called on __ALLOC_BUFFER_INVALID_POINTER");
> +  free ((void *) buf->__alloc_buffer_current);
> +  alloc_buffer_mark_failed (buf);
> +}

No need to cast to void *.

> +
> +/* Return the remaining number of bytes in the buffer.  */
> +static __always_inline size_t
> +alloc_buffer_size (const struct alloc_buffer *buf)
> +{
> +  return buf->__alloc_buffer_end - buf->__alloc_buffer_current;
> +}
> +
> +/* Return true if the buffer has been marked as failed.  */
> +static inline bool
> +alloc_buffer_has_failed (const struct alloc_buffer *buf)
> +{
> +  return buf->__alloc_buffer_current == __ALLOC_BUFFER_INVALID_POINTER;
> +}
> +
> +/* Add a single byte to the buffer (consuming the space for this
> +   byte).  Mark the buffer as failed if there is not enough room.  */
> +static inline void
> +alloc_buffer_add_byte (struct alloc_buffer *buf, unsigned char b)
> +{
> +  if (__glibc_likely (buf->__alloc_buffer_current < buf->__alloc_buffer_end))
> +    {
> +      *(unsigned char *) buf->__alloc_buffer_current = b;
> +      ++buf->__alloc_buffer_current;
> +    }
> +  else
> +    alloc_buffer_mark_failed (buf);
> +}
> +
> +/* Obtain a pointer to LENGTH bytes in BUF, and consume these bytes.
> +   NULL is returned if there is not enough room, and the buffer is
> +   marked as failed, or if the buffer has already failed.
> +   (Zero-length allocations from an empty buffer which has not yet
> +   failed succeed.)  */
> +static inline void *
> +alloc_buffer_alloc_bytes (struct alloc_buffer *buf, size_t length)
> +{
> +  if (length <= alloc_buffer_size (buf))
> +    {
> +      void *result = (void *) buf->__alloc_buffer_current;
> +      buf->__alloc_buffer_current += length;
> +      return result;
> +    }
> +  else
> +    {
> +      alloc_buffer_mark_failed (buf);
> +      return NULL;
> +    }
> +}
> +
> +/* Internal function.  Statically assert that the type size is
> +   constant and valid.  */
> +static __always_inline size_t
> +__alloc_buffer_assert_size (size_t size)
> +{
> +  if (!__builtin_constant_p (size))
> +    {
> +      __errordecl (error, "type size is not constant");
> +      error ();
> +    }
> +  else if (size == 0)
> +    {
> +      __errordecl (error, "type size is zero");
> +      error ();
> +    }
> +  return size;
> +}
> +
> +/* Internal function.  Statically assert that the type alignment is
> +   constant and valid.  */
> +static __always_inline size_t
> +__alloc_buffer_assert_align (size_t align)
> +{
> +  if (!__builtin_constant_p (align))
> +    {
> +      __errordecl (error, "type alignment is not constant");
> +      error ();
> +    }
> +  else if (align == 0)
> +    {
> +      __errordecl (error, "type alignment is zero");
> +      error ();
> +    }
> +  else if (!powerof2 (align))
> +    {
> +      __errordecl (error, "type alignment is not a power of two");
> +      error ();
> +    }
> +  return align;
> +}
> +
> +/* Internal function.  Obtain a pointer to an object.  */
> +static inline void *
> +__alloc_buffer_alloc (struct alloc_buffer *buf, size_t size, size_t align)
> +{
> +  if (size == 1 && align == 1)
> +    return alloc_buffer_alloc_bytes (buf, size);
> +
> +  size_t current = buf->__alloc_buffer_current;
> +  size_t aligned = roundup (current, align);
> +  size_t new_current = aligned + size;
> +  if (aligned >= current        /* No overflow in align step.  */
> +      && new_current >= size    /* No overflow in size computation.  */
> +      && new_current <= buf->__alloc_buffer_end) /* Room in buffer.  */
> +    {
> +      buf->__alloc_buffer_current = new_current;
> +      return (void *) aligned;
> +    }
> +  else
> +    {
> +      alloc_buffer_mark_failed (buf);
> +      return NULL;
> +    }
> +}

Maybe use/add the check_add_wrapv_size_t from my char_array patch (also
for the other occurences)?

> +
> +/* Obtain a TYPE * pointer to an object in BUF of TYPE.  Consume these
> +   bytes from the buffer.  Return NULL and mark the buffer as failed
> +   if if there is not enough room in the buffer, or if the buffer has
> +   failed before.  */
> +#define alloc_buffer_alloc(buf, type)				\
> +  ((type *) __alloc_buffer_alloc				\
> +   (buf, __alloc_buffer_assert_size (sizeof (type)),		\
> +    __alloc_buffer_assert_align (__alignof__ (type))))

I would prefer to use a static inline function to type check, but we
can like with it (same for other occurencies).

> +
> +/* Internal function.  Obtain a pointer to an object which is
> +   subsequently added.  */
> +static inline const void *
> +__alloc_buffer_next (struct alloc_buffer *buf, size_t align)
> +{
> +  if (align == 1)
> +    return (const void *) buf->__alloc_buffer_current;
> +
> +  size_t current = buf->__alloc_buffer_current;
> +  size_t aligned = roundup (current, align);
> +  if (aligned >= current        /* No overflow in align step.  */
> +      && aligned <= buf->__alloc_buffer_end) /* Room in buffer.  */
> +    {
> +      buf->__alloc_buffer_current = aligned;
> +      return (const void *) aligned;
> +    }
> +  else
> +    {
> +      alloc_buffer_mark_failed (buf);
> +      return NULL;
> +    }
> +}
> +
> +/* Like alloc_buffer_alloc, but do not advance the pointer beyond the
> +   object (so a subseqent call to alloc_buffer_next or
> +   alloc_buffer_alloc returns the same pointer).  Note that the buffer
> +   is still aligned according to the requirements of TYPE.  The effect
> +   of this function is similar to allocating a zero-length array from
> +   the buffer.  */
> +#define alloc_buffer_next(buf, type)				\
> +  ((const type *) __alloc_buffer_next				\
> +   (buf, __alloc_buffer_assert_align (__alignof__ (type))))
> +
> +/* Internal function.  Allocate an array.  */
> +void * __libc_alloc_buffer_alloc_array (struct alloc_buffer *buf,
> +				      size_t size, size_t align,
> +				      size_t count);
> +libc_hidden_proto (__libc_alloc_buffer_alloc_array)
> +
> +/* Obtain a TYPE * pointer to an array of COUNT objects in BUF of
> +   TYPE.  Consume these bytes from the buffer.  Return NULL and mark
> +   the buffer as failed if if there is not enough room in the buffer,
> +   or if the buffer has failed before.  (Zero-length allocations from
> +   an empty buffer which has not yet failed succeed.)  */
> +#define alloc_buffer_alloc_array(buf, type, count)       \
> +  ((type *) __libc_alloc_buffer_alloc_array		 \
> +   (buf, __alloc_buffer_assert_size (sizeof (type)),	 \
> +    __alloc_buffer_assert_align (__alignof__ (type)),	 \
> +    count))
> +
> +/* Internal function.  See alloc_buffer_copy_bytes below.  */
> +struct alloc_buffer __libc_alloc_buffer_copy_bytes (struct alloc_buffer,
> +						const void *, size_t);
> +libc_hidden_proto (__libc_alloc_buffer_copy_bytes)
> +
> +/* Copy SIZE bytes starting at SRC into the buffer.  If there is not
> +   enough room in the buffer, the buffer is marked as failed.  No
> +   alignment of the buffer is performed.  */
> +static inline void
> +alloc_buffer_copy_bytes (struct alloc_buffer *buf, const void *src, size_t size)
> +{
> +  *buf = __libc_alloc_buffer_copy_bytes (*buf, src, size);
> +}
> +
> +/* Internal function.  See alloc_buffer_copy_string below.  */
> +struct alloc_buffer __libc_alloc_buffer_copy_string (struct alloc_buffer,
> +						     const char *);
> +libc_hidden_proto (__libc_alloc_buffer_copy_string)
> +
> +/* Copy the string at SRC into the buffer, including its null
> +   terminator.  If there is not enough room in the buffer, the buffer
> +   is marked as failed.  Return a pointer to the string.  */
> +static inline char *
> +alloc_buffer_copy_string (struct alloc_buffer *buf, const char *src)
> +{
> +  char *result = (char *) buf->__alloc_buffer_current;
> +  *buf = __libc_alloc_buffer_copy_string (*buf, src);
> +  if (alloc_buffer_has_failed (buf))
> +    result = NULL;
> +  return result;
> +}
> +
> +/* Internal function.  Set *RESULT to LEFT * RIGHT.  Return true if
> +   the result overflowed.  */
> +static inline bool
> +__check_mul_overflow_size_t (size_t left, size_t right, size_t *result)
> +{
> +#if __GNUC__ >= 5
> +  return __builtin_mul_overflow (left, right, result);
> +#else
> +  /* size_t is unsigned so the behavior on overflow is defined.  */
> +  *result = left * right;
> +  size_t half_size_t = ((size_t) 1) << (8 * sizeof (size_t) / 2);
> +  if (__glibc_unlikely ((left | right) >= half_size_t))
> +    {
> +      if (__glibc_unlikely (right != 0 && *result / right != left))
> +        return true;
> +    }
> +  return false;
> +#endif
> +}

This was pushed already on malloc/malloc-internal.h.

> +
> +#endif /* _ALLOC_BUFFER_H */
> diff --git a/malloc/Makefile b/malloc/Makefile
> index e93b83b..2f7bb5a 100644
> --- a/malloc/Makefile
> +++ b/malloc/Makefile
> @@ -33,6 +33,7 @@ tests := mallocbug tst-malloc tst-valloc tst-calloc tst-obstack \
>  	 tst-mallocfork2 \
>  	 tst-interpose-nothread \
>  	 tst-interpose-thread \
> +	 tst-alloc_buffer \
>  
>  tests-static := \
>  	 tst-interpose-static-nothread \
> @@ -49,7 +50,11 @@ test-srcs = tst-mtrace
>  
>  routines = malloc morecore mcheck mtrace obstack \
>    scratch_buffer_grow scratch_buffer_grow_preserve \
> -  scratch_buffer_set_array_size
> +  scratch_buffer_set_array_size \
> +  alloc_buffer_alloc_array \
> +  alloc_buffer_allocate \
> +  alloc_buffer_copy_bytes  \
> +  alloc_buffer_copy_string \
>  
>  install-lib := libmcheck.a
>  non-lib.a := libmcheck.a
> diff --git a/malloc/Versions b/malloc/Versions
> index e34ab17..361550b 100644
> --- a/malloc/Versions
> +++ b/malloc/Versions
> @@ -74,5 +74,11 @@ libc {
>      __libc_scratch_buffer_grow;
>      __libc_scratch_buffer_grow_preserve;
>      __libc_scratch_buffer_set_array_size;
> +
> +    # struct alloc_buffer support
> +    __libc_alloc_buffer_alloc_array;
> +    __libc_alloc_buffer_allocate;
> +    __libc_alloc_buffer_copy_bytes;
> +    __libc_alloc_buffer_copy_string;
>    }
>  }
> diff --git a/malloc/alloc_buffer_alloc_array.c b/malloc/alloc_buffer_alloc_array.c
> new file mode 100644
> index 0000000..0172029
> --- /dev/null
> +++ b/malloc/alloc_buffer_alloc_array.c
> @@ -0,0 +1,45 @@
> +/* Array allocation from a fixed-size buffer.
> +   Copyright (C) 2017 Free Software Foundation, Inc.
> +   This file is part of the GNU C Library.
> +
> +   The GNU C Library is free software; you can redistribute it and/or
> +   modify it under the terms of the GNU Lesser General Public
> +   License as published by the Free Software Foundation; either
> +   version 2.1 of the License, or (at your option) any later version.
> +
> +   The GNU C Library is distributed in the hope that it will be useful,
> +   but WITHOUT ANY WARRANTY; without even the implied warranty of
> +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> +   Lesser General Public License for more details.
> +
> +   You should have received a copy of the GNU Lesser General Public
> +   License along with the GNU C Library; if not, see
> +   <http://www.gnu.org/licenses/>.  */
> +
> +#include <alloc_buffer.h>
> +
> +void *
> +__libc_alloc_buffer_alloc_array (struct alloc_buffer *buf, size_t element_size,
> +                                 size_t align, size_t count)
> +{
> +  size_t current = buf->__alloc_buffer_current;
> +  /* The caller asserts that align is a power of two.  */
> +  size_t aligned = (current + align - 1) & ~(align - 1);

Maybe use ALIGN_UP?

> +  size_t size;
> +  bool overflow = __check_mul_overflow_size_t (element_size, count, &size);
> +  size_t new_current = aligned + size;
> +  if (!overflow                /* Multiplication did not overflow.  */
> +      && aligned >= current    /* No overflow in align step.  */
> +      && new_current >= size   /* No overflow in size computation.  */
> +      && new_current <= buf->__alloc_buffer_end) /* Room in buffer.  */
> +    {
> +      buf->__alloc_buffer_current = new_current;
> +      return (void *) aligned;
> +    }
> +  else
> +    {
> +      alloc_buffer_mark_failed (buf);
> +      return NULL;
> +    }
> +}
> +libc_hidden_def (__libc_alloc_buffer_alloc_array)
> diff --git a/malloc/alloc_buffer_allocate.c b/malloc/alloc_buffer_allocate.c
> new file mode 100644
> index 0000000..a9fd3f1
> --- /dev/null
> +++ b/malloc/alloc_buffer_allocate.c
> @@ -0,0 +1,36 @@
> +/* Allocate a fixed-size allocation buffer using malloc.
> +   Copyright (C) 2017 Free Software Foundation, Inc.
> +   This file is part of the GNU C Library.
> +
> +   The GNU C Library is free software; you can redistribute it and/or
> +   modify it under the terms of the GNU Lesser General Public
> +   License as published by the Free Software Foundation; either
> +   version 2.1 of the License, or (at your option) any later version.
> +
> +   The GNU C Library is distributed in the hope that it will be useful,
> +   but WITHOUT ANY WARRANTY; without even the implied warranty of
> +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> +   Lesser General Public License for more details.
> +
> +   You should have received a copy of the GNU Lesser General Public
> +   License along with the GNU C Library; if not, see
> +   <http://www.gnu.org/licenses/>.  */
> +
> +#include <alloc_buffer.h>
> +
> +#include <stdlib.h>
> +
> +struct alloc_buffer
> +__libc_alloc_buffer_allocate (size_t size)
> +{
> +  void *ptr = malloc (size);
> +  if (ptr == NULL)
> +    return (struct alloc_buffer)
> +      {
> +        .__alloc_buffer_current = __ALLOC_BUFFER_INVALID_POINTER,
> +        .__alloc_buffer_end = __ALLOC_BUFFER_INVALID_POINTER
> +      };
> +  else
> +    return alloc_buffer_create (ptr, size);
> +}
> +libc_hidden_def (__libc_alloc_buffer_allocate)
> diff --git a/malloc/alloc_buffer_copy_bytes.c b/malloc/alloc_buffer_copy_bytes.c
> new file mode 100644
> index 0000000..66196f1
> --- /dev/null
> +++ b/malloc/alloc_buffer_copy_bytes.c
> @@ -0,0 +1,34 @@
> +/* Copy an array of bytes into the buffer.
> +   Copyright (C) 2017 Free Software Foundation, Inc.
> +   This file is part of the GNU C Library.
> +
> +   The GNU C Library is free software; you can redistribute it and/or
> +   modify it under the terms of the GNU Lesser General Public
> +   License as published by the Free Software Foundation; either
> +   version 2.1 of the License, or (at your option) any later version.
> +
> +   The GNU C Library is distributed in the hope that it will be useful,
> +   but WITHOUT ANY WARRANTY; without even the implied warranty of
> +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> +   Lesser General Public License for more details.
> +
> +   You should have received a copy of the GNU Lesser General Public
> +   License along with the GNU C Library; if not, see
> +   <http://www.gnu.org/licenses/>.  */
> +
> +#include <alloc_buffer.h>
> +
> +#include <string.h>
> +
> +/* This function works on a copy of the buffer object, so that it can
> +   remain non-addressable in the caller.  */
> +struct alloc_buffer
> +__libc_alloc_buffer_copy_bytes (struct alloc_buffer buf,
> +                                const void *src, size_t len)
> +{
> +  void *ptr = alloc_buffer_alloc_bytes (&buf, len);
> +  if (ptr != NULL)
> +    memcpy (ptr, src, len);
> +  return buf;
> +}
> +libc_hidden_def (__libc_alloc_buffer_copy_bytes)
> diff --git a/malloc/alloc_buffer_copy_string.c b/malloc/alloc_buffer_copy_string.c
> new file mode 100644
> index 0000000..77c0023
> --- /dev/null
> +++ b/malloc/alloc_buffer_copy_string.c
> @@ -0,0 +1,30 @@
> +/* Copy a string into the allocation buffer.
> +   Copyright (C) 2017 Free Software Foundation, Inc.
> +   This file is part of the GNU C Library.
> +
> +   The GNU C Library is free software; you can redistribute it and/or
> +   modify it under the terms of the GNU Lesser General Public
> +   License as published by the Free Software Foundation; either
> +   version 2.1 of the License, or (at your option) any later version.
> +
> +   The GNU C Library is distributed in the hope that it will be useful,
> +   but WITHOUT ANY WARRANTY; without even the implied warranty of
> +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> +   Lesser General Public License for more details.
> +
> +   You should have received a copy of the GNU Lesser General Public
> +   License along with the GNU C Library; if not, see
> +   <http://www.gnu.org/licenses/>.  */
> +
> +#include <alloc_buffer.h>
> +
> +#include <string.h>
> +
> +/* This function works on a copy of the buffer object, so that it can
> +   remain non-addressable in the caller.  */
> +struct alloc_buffer
> +__libc_alloc_buffer_copy_string (struct alloc_buffer buf, const char *src)
> +{
> +  return __libc_alloc_buffer_copy_bytes (buf, src, strlen (src) + 1);
> +}
> +libc_hidden_def (__libc_alloc_buffer_copy_string)
> diff --git a/malloc/tst-alloc_buffer.c b/malloc/tst-alloc_buffer.c
> new file mode 100644
> index 0000000..362dae2
> --- /dev/null
> +++ b/malloc/tst-alloc_buffer.c
> @@ -0,0 +1,663 @@
> +/* Tests for struct alloc_buffer.
> +   Copyright (C) 2017 Free Software Foundation, Inc.
> +   This file is part of the GNU C Library.
> +
> +   The GNU C Library is free software; you can redistribute it and/or
> +   modify it under the terms of the GNU Lesser General Public
> +   License as published by the Free Software Foundation; either
> +   version 2.1 of the License, or (at your option) any later version.
> +
> +   The GNU C Library is distributed in the hope that it will be useful,
> +   but WITHOUT ANY WARRANTY; without even the implied warranty of
> +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> +   Lesser General Public License for more details.
> +
> +   You should have received a copy of the GNU Lesser General Public
> +   License along with the GNU C Library; if not, see
> +   <http://www.gnu.org/licenses/>.  */
> +
> +#include <arpa/inet.h>
> +#include <alloc_buffer.h>
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <string.h>
> +#include <support/check.h>
> +#include <support/support.h>
> +#include <support/test-driver.h>
> +
> +/* Return true if PTR is sufficiently aligned for TYPE.  */
> +#define IS_ALIGNED(ptr, type) \
> +  ((((uintptr_t) ptr) & (__alloc_buffer_assert_align (__alignof (type)) - 1)) \
> +   == 0)
> +
> +/* Structure with non-power-of-two size.  */
> +struct twelve
> +{
> +  uint32_t buffer[3] __attribute__ ((aligned (4)));
> +};
> +_Static_assert (sizeof (struct twelve) == 12, "struct twelve");
> +_Static_assert (__alignof__ (struct twelve) == 4, "struct twelve");
> +
> +/* Check for success obtaining empty arrays.  Does not assume the
> +   buffer is empty.  */
> +static void
> +test_empty_array (struct alloc_buffer refbuf)
> +{
> +  bool refbuf_failed = alloc_buffer_has_failed (&refbuf);
> +  if (test_verbose)
> +    printf ("info: %s: current=0x%llx end=0x%llx refbuf_failed=%d\n",
> +            __func__, (unsigned long long) refbuf.__alloc_buffer_current,
> +            (unsigned long long) refbuf.__alloc_buffer_end, refbuf_failed);
> +  {
> +    struct alloc_buffer buf = refbuf;
> +    TEST_VERIFY ((alloc_buffer_alloc_bytes (&buf, 0) == NULL)
> +                 == refbuf_failed);
> +    TEST_VERIFY (alloc_buffer_has_failed (&buf) == refbuf_failed);
> +  }
> +  {
> +    struct alloc_buffer buf = refbuf;
> +    TEST_VERIFY ((alloc_buffer_alloc_array (&buf, char, 0) == NULL)
> +                 == refbuf_failed);
> +    TEST_VERIFY (alloc_buffer_has_failed (&buf) == refbuf_failed);
> +  }
> +  /* The following tests can fail due to the need for aligning the
> +     returned pointer.  */
> +  {
> +    struct alloc_buffer buf = refbuf;
> +    bool expect_failure = refbuf_failed
> +      || !IS_ALIGNED (alloc_buffer_next (&buf, void), double);
> +    double *ptr = alloc_buffer_alloc_array (&buf, double, 0);
> +    TEST_VERIFY (IS_ALIGNED (ptr, double));
> +    TEST_VERIFY ((ptr == NULL) == expect_failure);
> +    TEST_VERIFY (alloc_buffer_has_failed (&buf) == expect_failure);
> +  }
> +  {
> +    struct alloc_buffer buf = refbuf;
> +    bool expect_failure = refbuf_failed
> +      || !IS_ALIGNED (alloc_buffer_next (&buf, void), struct twelve);
> +    struct twelve *ptr = alloc_buffer_alloc_array (&buf, struct twelve, 0);
> +    TEST_VERIFY (IS_ALIGNED (ptr, struct twelve));
> +    TEST_VERIFY ((ptr == NULL) == expect_failure);
> +    TEST_VERIFY (alloc_buffer_has_failed (&buf) == expect_failure);
> +  }
> +}
> +
> +/* Test allocation of impossibly large arrays.  */
> +static void
> +test_impossible_array (struct alloc_buffer refbuf)
> +{
> +  if (test_verbose)
> +    printf ("info: %s: current=0x%llx end=0x%llx\n",
> +            __func__, (unsigned long long) refbuf.__alloc_buffer_current,
> +            (unsigned long long) refbuf.__alloc_buffer_end);
> +  static const size_t counts[] =
> +    { SIZE_MAX, SIZE_MAX - 1, SIZE_MAX - 2, SIZE_MAX - 3, SIZE_MAX - 4,
> +      SIZE_MAX / 2, SIZE_MAX / 2 + 1, SIZE_MAX / 2 - 1, 0};
> +
> +  for (int i = 0; counts[i] != 0; ++i)
> +    {
> +      size_t count = counts[i];
> +      if (test_verbose)
> +        printf ("info: %s: count=%zu\n", __func__, count);
> +      {
> +        struct alloc_buffer buf = refbuf;
> +        TEST_VERIFY (alloc_buffer_alloc_bytes (&buf, count) == NULL);
> +        TEST_VERIFY (alloc_buffer_has_failed (&buf));
> +      }
> +      {
> +        struct alloc_buffer buf = refbuf;
> +        TEST_VERIFY (alloc_buffer_alloc_array (&buf, char, count) == NULL);
> +        TEST_VERIFY (alloc_buffer_has_failed (&buf));
> +      }
> +      {
> +        struct alloc_buffer buf = refbuf;
> +        TEST_VERIFY (alloc_buffer_alloc_array (&buf, short, count) == NULL);
> +        TEST_VERIFY (alloc_buffer_has_failed (&buf));
> +      }
> +      {
> +        struct alloc_buffer buf = refbuf;
> +        TEST_VERIFY (alloc_buffer_alloc_array (&buf, double, count) == NULL);
> +        TEST_VERIFY (alloc_buffer_has_failed (&buf));
> +      }
> +      {
> +        struct alloc_buffer buf = refbuf;
> +        TEST_VERIFY (alloc_buffer_alloc_array (&buf, struct twelve, count)
> +                     == NULL);
> +        TEST_VERIFY (alloc_buffer_has_failed (&buf));
> +      }
> +    }
> +}
> +
> +/* Check for failure to obtain anything from a failed buffer.  */
> +static void
> +test_after_failure (struct alloc_buffer refbuf)
> +{
> +  if (test_verbose)
> +    printf ("info: %s: current=0x%llx end=0x%llx\n",
> +            __func__, (unsigned long long) refbuf.__alloc_buffer_current,
> +            (unsigned long long) refbuf.__alloc_buffer_end);
> +  TEST_VERIFY (alloc_buffer_has_failed (&refbuf));
> +  {
> +    struct alloc_buffer buf = refbuf;
> +    alloc_buffer_add_byte (&buf, 17);
> +    TEST_VERIFY (alloc_buffer_has_failed (&buf));
> +  }
> +  {
> +    struct alloc_buffer buf = refbuf;
> +    TEST_VERIFY (alloc_buffer_alloc (&buf, char) == NULL);
> +    TEST_VERIFY (alloc_buffer_has_failed (&buf));
> +  }
> +  {
> +    struct alloc_buffer buf = refbuf;
> +    TEST_VERIFY (alloc_buffer_alloc (&buf, double) == NULL);
> +    TEST_VERIFY (alloc_buffer_has_failed (&buf));
> +  }
> +  {
> +    struct alloc_buffer buf = refbuf;
> +    TEST_VERIFY (alloc_buffer_alloc (&buf, struct twelve) == NULL);
> +    TEST_VERIFY (alloc_buffer_has_failed (&buf));
> +  }
> +
> +  test_impossible_array (refbuf);
> +  for (int count = 0; count <= 4; ++count)
> +    {
> +      {
> +        struct alloc_buffer buf = refbuf;
> +        TEST_VERIFY (alloc_buffer_alloc_bytes (&buf, count) == NULL);
> +        TEST_VERIFY (alloc_buffer_has_failed (&buf));
> +      }
> +      {
> +        struct alloc_buffer buf = refbuf;
> +        TEST_VERIFY (alloc_buffer_alloc_array (&buf, char, count) == NULL);
> +        TEST_VERIFY (alloc_buffer_has_failed (&buf));
> +      }
> +      {
> +        struct alloc_buffer buf = refbuf;
> +        TEST_VERIFY (alloc_buffer_alloc_array (&buf, double, count) == NULL);
> +        TEST_VERIFY (alloc_buffer_has_failed (&buf));
> +      }
> +      {
> +        struct alloc_buffer buf = refbuf;
> +        TEST_VERIFY (alloc_buffer_alloc_array (&buf, struct twelve, count)
> +                     == NULL);
> +        TEST_VERIFY (alloc_buffer_has_failed (&buf));
> +      }
> +    }
> +}
> +
> +static void
> +test_empty (struct alloc_buffer refbuf)
> +{
> +  TEST_VERIFY (alloc_buffer_size (&refbuf) == 0);
> +  if (alloc_buffer_next (&refbuf, void) != NULL)
> +    TEST_VERIFY (!alloc_buffer_has_failed (&refbuf));
> +  test_empty_array (refbuf);
> +  test_impossible_array (refbuf);
> +
> +  /* Failure to obtain non-empty objects.  */
> +  {
> +    struct alloc_buffer buf = refbuf;
> +    alloc_buffer_add_byte (&buf, 17);
> +    test_after_failure (buf);
> +  }
> +  {
> +    struct alloc_buffer buf = refbuf;
> +    TEST_VERIFY (alloc_buffer_alloc (&buf, char) == NULL);
> +    test_after_failure (buf);
> +  }
> +  {
> +    struct alloc_buffer buf = refbuf;
> +    TEST_VERIFY (alloc_buffer_alloc (&buf, double) == NULL);
> +    test_after_failure (buf);
> +  }
> +  {
> +    struct alloc_buffer buf = refbuf;
> +    TEST_VERIFY (alloc_buffer_alloc (&buf, struct twelve) == NULL);
> +    test_after_failure (buf);
> +  }
> +  {
> +    struct alloc_buffer buf = refbuf;
> +    TEST_VERIFY (alloc_buffer_alloc_array (&buf, char, 1) == NULL);
> +    test_after_failure (buf);
> +  }
> +  {
> +    struct alloc_buffer buf = refbuf;
> +    TEST_VERIFY (alloc_buffer_alloc_array (&buf, double, 1) == NULL);
> +    test_after_failure (buf);
> +  }
> +  {
> +    struct alloc_buffer buf = refbuf;
> +    TEST_VERIFY (alloc_buffer_alloc_array (&buf, struct twelve, 1) == NULL);
> +    test_after_failure (buf);
> +  }
> +}
> +
> +static void
> +test_size_1 (struct alloc_buffer refbuf)
> +{
> +  TEST_VERIFY (!alloc_buffer_has_failed (&refbuf));
> +  TEST_VERIFY (alloc_buffer_size (&refbuf) == 1);
> +  test_empty_array (refbuf);
> +  test_impossible_array (refbuf);
> +
> +  /* Success adding a single byte.  */
> +  {
> +    struct alloc_buffer buf = refbuf;
> +    alloc_buffer_add_byte (&buf, 17);
> +    TEST_VERIFY (!alloc_buffer_has_failed (&buf));
> +    test_empty (buf);
> +  }
> +  TEST_VERIFY (memcmp (alloc_buffer_next (&refbuf, void), "\x11", 1) == 0);
> +  {
> +    struct alloc_buffer buf = refbuf;
> +    signed char *ptr = alloc_buffer_alloc (&buf, signed char);
> +    TEST_VERIFY_EXIT (ptr != NULL);
> +    TEST_VERIFY (!alloc_buffer_has_failed (&buf));
> +    *ptr = 126;
> +    test_empty (buf);
> +  }
> +  TEST_VERIFY (memcmp (alloc_buffer_next (&refbuf, void), "\176", 1) == 0);
> +  {
> +    struct alloc_buffer buf = refbuf;
> +    char *ptr = alloc_buffer_alloc_array (&buf, char, 1);
> +    TEST_VERIFY_EXIT (ptr != NULL);
> +    TEST_VERIFY (!alloc_buffer_has_failed (&buf));
> +    *ptr = (char) 253;
> +    test_empty (buf);
> +  }
> +  TEST_VERIFY (memcmp (alloc_buffer_next (&refbuf, void), "\xfd", 1) == 0);
> +
> +  /* Failure with larger objects.  */
> +  {
> +    struct alloc_buffer buf = refbuf;
> +    TEST_VERIFY (alloc_buffer_alloc (&buf, short) == NULL);
> +    test_after_failure (buf);
> +  }
> +  {
> +    struct alloc_buffer buf = refbuf;
> +    TEST_VERIFY (alloc_buffer_alloc (&buf, double) == NULL);
> +    test_after_failure (buf);
> +  }
> +  {
> +    struct alloc_buffer buf = refbuf;
> +    TEST_VERIFY (alloc_buffer_alloc (&buf, struct twelve) == NULL);
> +    test_after_failure (buf);
> +  }
> +  {
> +    struct alloc_buffer buf = refbuf;
> +    TEST_VERIFY (alloc_buffer_alloc_array (&buf, short, 1) == NULL);
> +    test_after_failure (buf);
> +  }
> +  {
> +    struct alloc_buffer buf = refbuf;
> +    TEST_VERIFY (alloc_buffer_alloc_array (&buf, double, 1) == NULL);
> +    test_after_failure (buf);
> +  }
> +  {
> +    struct alloc_buffer buf = refbuf;
> +    TEST_VERIFY (alloc_buffer_alloc_array (&buf, struct twelve, 1) == NULL);
> +    test_after_failure (buf);
> +  }
> +}
> +
> +static void
> +test_size_2 (struct alloc_buffer refbuf)
> +{
> +  TEST_VERIFY (!alloc_buffer_has_failed (&refbuf));
> +  TEST_VERIFY (alloc_buffer_size (&refbuf) == 2);
> +  TEST_VERIFY (IS_ALIGNED (alloc_buffer_next (&refbuf, void), short));
> +  test_empty_array (refbuf);
> +  test_impossible_array (refbuf);
> +
> +  /* Success adding two bytes.  */
> +  {
> +    struct alloc_buffer buf = refbuf;
> +    alloc_buffer_add_byte (&buf, '@');
> +    TEST_VERIFY (!alloc_buffer_has_failed (&buf));
> +    test_size_1 (buf);
> +  }
> +  TEST_VERIFY (memcmp (alloc_buffer_next (&refbuf, void), "@\xfd", 2) == 0);
> +  {
> +    struct alloc_buffer buf = refbuf;
> +    signed char *ptr = alloc_buffer_alloc (&buf, signed char);
> +    TEST_VERIFY_EXIT (ptr != NULL);
> +    TEST_VERIFY (!alloc_buffer_has_failed (&buf));
> +    *ptr = 'A';
> +    test_size_1 (buf);
> +  }
> +  TEST_VERIFY (memcmp (alloc_buffer_next (&refbuf, void), "A\xfd", 2) == 0);
> +  {
> +    struct alloc_buffer buf = refbuf;
> +    char *ptr = alloc_buffer_alloc_array (&buf, char, 1);
> +    TEST_VERIFY_EXIT (ptr != NULL);
> +    TEST_VERIFY (!alloc_buffer_has_failed (&buf));
> +    *ptr = 'B';
> +    test_size_1 (buf);
> +  }
> +  TEST_VERIFY (memcmp (alloc_buffer_next (&refbuf, void), "B\xfd", 2) == 0);
> +  {
> +    struct alloc_buffer buf = refbuf;
> +    unsigned short *ptr = alloc_buffer_alloc (&buf, unsigned short);
> +    TEST_VERIFY_EXIT (ptr != NULL);
> +    TEST_VERIFY (IS_ALIGNED (ptr, unsigned short));
> +    TEST_VERIFY (!alloc_buffer_has_failed (&buf));
> +    *ptr = htons (0x12f4);
> +    test_empty (buf);
> +  }
> +  TEST_VERIFY (memcmp (alloc_buffer_next (&refbuf, void), "\x12\xf4", 2) == 0);
> +  {
> +    struct alloc_buffer buf = refbuf;
> +    unsigned short *ptr = alloc_buffer_alloc_array (&buf, unsigned short, 1);
> +    TEST_VERIFY_EXIT (ptr != NULL);
> +    TEST_VERIFY (IS_ALIGNED (ptr, unsigned short));
> +    TEST_VERIFY (!alloc_buffer_has_failed (&buf));
> +    *ptr = htons (0x13f5);
> +    test_empty (buf);
> +  }
> +  TEST_VERIFY (memcmp (alloc_buffer_next (&refbuf, void), "\x13\xf5", 2) == 0);
> +  {
> +    struct alloc_buffer buf = refbuf;
> +    char *ptr = alloc_buffer_alloc_array (&buf, char, 2);
> +    TEST_VERIFY_EXIT (ptr != NULL);
> +    TEST_VERIFY (!alloc_buffer_has_failed (&buf));
> +    memcpy (ptr, "12", 2);
> +    test_empty (buf);
> +  }
> +  TEST_VERIFY (memcmp (alloc_buffer_next (&refbuf, void), "12", 2) == 0);
> +}
> +
> +static void
> +test_misaligned (char pad)
> +{
> +  enum { SIZE = 23 };
> +  char *backing = xmalloc (SIZE + 2);
> +  backing[0] = ~pad;
> +  backing[SIZE + 1] = pad;
> +  struct alloc_buffer refbuf = alloc_buffer_create (backing + 1, SIZE);
> +
> +  {
> +    struct alloc_buffer buf = refbuf;
> +    short *ptr = alloc_buffer_alloc_array (&buf, short, SIZE / sizeof (short));
> +    TEST_VERIFY_EXIT (ptr != NULL);
> +    TEST_VERIFY (IS_ALIGNED (ptr, short));
> +    TEST_VERIFY (!alloc_buffer_has_failed (&buf));
> +    for (int i = 0; i < SIZE / sizeof (short); ++i)
> +      ptr[i] = htons (0xff01 + i);
> +    TEST_VERIFY (memcmp (ptr,
> +                         "\xff\x01\xff\x02\xff\x03\xff\x04"
> +                         "\xff\x05\xff\x06\xff\x07\xff\x08"
> +                         "\xff\x09\xff\x0a\xff\x0b", 22) == 0);
> +  }
> +  {
> +    struct alloc_buffer buf = refbuf;
> +    uint32_t *ptr = alloc_buffer_alloc_array
> +      (&buf, uint32_t, SIZE / sizeof (uint32_t));
> +    TEST_VERIFY_EXIT (ptr != NULL);
> +    TEST_VERIFY (IS_ALIGNED (ptr, uint32_t));
> +    TEST_VERIFY (!alloc_buffer_has_failed (&buf));
> +    for (int i = 0; i < SIZE / sizeof (uint32_t); ++i)
> +      ptr[i] = htonl (0xf1e2d301 + i);
> +    TEST_VERIFY (memcmp (ptr,
> +                         "\xf1\xe2\xd3\x01\xf1\xe2\xd3\x02"
> +                         "\xf1\xe2\xd3\x03\xf1\xe2\xd3\x04"
> +                         "\xf1\xe2\xd3\x05", 20) == 0);
> +  }
> +  {
> +    struct alloc_buffer buf = refbuf;
> +    struct twelve *ptr = alloc_buffer_alloc (&buf, struct twelve);
> +    TEST_VERIFY_EXIT (ptr != NULL);
> +    TEST_VERIFY (IS_ALIGNED (ptr, struct twelve));
> +    TEST_VERIFY (!alloc_buffer_has_failed (&buf));
> +    ptr->buffer[0] = htonl (0x11223344);
> +    ptr->buffer[1] = htonl (0x55667788);
> +    ptr->buffer[2] = htonl (0x99aabbcc);
> +    TEST_VERIFY (memcmp (ptr,
> +                         "\x11\x22\x33\x44"
> +                         "\x55\x66\x77\x88"
> +                         "\x99\xaa\xbb\xcc", 12) == 0);
> +  }
> +  {
> +    static const double nums[] = { 1, 2 };
> +    struct alloc_buffer buf = refbuf;
> +    double *ptr = alloc_buffer_alloc_array (&buf, double, 2);
> +    TEST_VERIFY_EXIT (ptr != NULL);
> +    TEST_VERIFY (IS_ALIGNED (ptr, double));
> +    TEST_VERIFY (!alloc_buffer_has_failed (&buf));
> +    ptr[0] = nums[0];
> +    ptr[1] = nums[1];
> +    TEST_VERIFY (memcmp (ptr, nums, sizeof (nums)) == 0);
> +  }
> +
> +  /* Verify that padding was not overwritten.  */
> +  TEST_VERIFY (backing[0] == ~pad);
> +  TEST_VERIFY (backing[SIZE + 1] == pad);
> +  free (backing);
> +}
> +
> +/* Check that overflow during alignment is handled properly.  */
> +static void
> +test_large_misaligned (void)
> +{
> +  uintptr_t minus1 = -1;
> +  uintptr_t start = minus1 & ~0xfe;
> +  struct alloc_buffer refbuf = alloc_buffer_create ((void *) start, 16);
> +  TEST_VERIFY (!alloc_buffer_has_failed (&refbuf));
> +
> +  struct __attribute__ ((aligned (256))) align256
> +  {
> +    int dymmy;
> +  };
> +
> +  {
> +    struct alloc_buffer buf = refbuf;
> +    TEST_VERIFY (alloc_buffer_alloc (&buf, struct align256) == NULL);
> +    test_after_failure (buf);
> +  }
> +  for (int count = 0; count < 3; ++count)
> +    {
> +      struct alloc_buffer buf = refbuf;
> +      TEST_VERIFY (alloc_buffer_alloc_array (&buf, struct align256, count)
> +                   == NULL);
> +      test_after_failure (buf);
> +    }
> +}
> +
> +/* Check behavior of large allocations.  */
> +static void
> +test_large (void)
> +{
> +  {
> +    /* Allocation which wraps around.  */
> +    struct alloc_buffer buf = { 1, SIZE_MAX };
> +    TEST_VERIFY (alloc_buffer_alloc_array (&buf, char, SIZE_MAX) == NULL);
> +    TEST_VERIFY (alloc_buffer_has_failed (&buf));
> +  }
> +
> +  {
> +    /* Successful very large allocation.  */
> +    struct alloc_buffer buf = { 1, SIZE_MAX };
> +    uintptr_t val = (uintptr_t) alloc_buffer_alloc_array
> +      (&buf, char, SIZE_MAX - 1);
> +    TEST_VERIFY (val == 1);
> +    TEST_VERIFY (!alloc_buffer_has_failed (&buf));
> +    test_empty (buf);
> +  }
> +
> +  {
> +    typedef char __attribute__ ((aligned (2))) char2;
> +
> +    /* Overflow in array size computation.   */
> +    struct alloc_buffer buf = { 1, SIZE_MAX };
> +    TEST_VERIFY (alloc_buffer_alloc_array (&buf, char2, SIZE_MAX - 1) == NULL);
> +    TEST_VERIFY (alloc_buffer_has_failed (&buf));
> +
> +    /* Successful allocation after alignment.  */
> +    buf = (struct alloc_buffer) { 1, SIZE_MAX };
> +    uintptr_t val = (uintptr_t) alloc_buffer_alloc_array
> +      (&buf, char2, SIZE_MAX - 2);
> +    TEST_VERIFY (val == 2);
> +    test_empty (buf);
> +
> +    /* Alignment behavior near the top of the address space.  */
> +    buf = (struct alloc_buffer) { SIZE_MAX, SIZE_MAX };
> +    TEST_VERIFY (alloc_buffer_next (&buf, char2) == NULL);
> +    TEST_VERIFY (alloc_buffer_has_failed (&buf));
> +    buf = (struct alloc_buffer) { SIZE_MAX, SIZE_MAX };
> +    TEST_VERIFY (alloc_buffer_alloc_array (&buf, char2, 0) == NULL);
> +    TEST_VERIFY (alloc_buffer_has_failed (&buf));
> +  }
> +
> +  {
> +    typedef short __attribute__ ((aligned (2))) short2;
> +
> +    /* Test overflow in size computation.  */
> +    struct alloc_buffer buf = { 1, SIZE_MAX };
> +    TEST_VERIFY (alloc_buffer_alloc_array (&buf, short2, SIZE_MAX / 2)
> +                 == NULL);
> +    TEST_VERIFY (alloc_buffer_has_failed (&buf));
> +
> +    /* A slightly smaller array fits within the allocation.  */
> +    buf = (struct alloc_buffer) { 2, SIZE_MAX - 1 };
> +    uintptr_t val = (uintptr_t) alloc_buffer_alloc_array
> +      (&buf, short2, SIZE_MAX / 2 - 1);
> +    TEST_VERIFY (val == 2);
> +    test_empty (buf);
> +  }
> +}
> +
> +static void
> +test_copy_bytes (void)
> +{
> +  char backing[4];
> +  {
> +    memset (backing, '@', sizeof (backing));
> +    struct alloc_buffer buf = alloc_buffer_create (backing, sizeof (backing));
> +    alloc_buffer_copy_bytes (&buf, "1", 1);
> +    TEST_VERIFY (!alloc_buffer_has_failed (&buf));
> +    TEST_VERIFY (alloc_buffer_size (&buf) == 3);
> +    TEST_VERIFY (memcmp (backing, "1@@@", 4) == 0);
> +  }
> +  {
> +    memset (backing, '@', sizeof (backing));
> +    struct alloc_buffer buf = alloc_buffer_create (backing, sizeof (backing));
> +    alloc_buffer_copy_bytes (&buf, "12", 3);
> +    TEST_VERIFY (!alloc_buffer_has_failed (&buf));
> +    TEST_VERIFY (alloc_buffer_size (&buf) == 1);
> +    TEST_VERIFY (memcmp (backing, "12\0@", 4) == 0);
> +  }
> +  {
> +    memset (backing, '@', sizeof (backing));
> +    struct alloc_buffer buf = alloc_buffer_create (backing, sizeof (backing));
> +    alloc_buffer_copy_bytes (&buf, "1234", 4);
> +    TEST_VERIFY (!alloc_buffer_has_failed (&buf));
> +    TEST_VERIFY (alloc_buffer_size (&buf) == 0);
> +    TEST_VERIFY (memcmp (backing, "1234", 4) == 0);
> +  }
> +  {
> +    memset (backing, '@', sizeof (backing));
> +    struct alloc_buffer buf = alloc_buffer_create (backing, sizeof (backing));
> +    alloc_buffer_copy_bytes (&buf, "1234", 5);
> +    TEST_VERIFY (alloc_buffer_has_failed (&buf));
> +    TEST_VERIFY (memcmp (backing, "@@@@", 4) == 0);
> +  }
> +  {
> +    memset (backing, '@', sizeof (backing));
> +    struct alloc_buffer buf = alloc_buffer_create (backing, sizeof (backing));
> +    alloc_buffer_copy_bytes (&buf, "1234", -1);
> +    TEST_VERIFY (alloc_buffer_has_failed (&buf));
> +    TEST_VERIFY (memcmp (backing, "@@@@", 4) == 0);
> +  }
> +}
> +
> +static void
> +test_copy_string (void)
> +{
> +  char backing[4];
> +  {
> +    memset (backing, '@', sizeof (backing));
> +    struct alloc_buffer buf = alloc_buffer_create (backing, sizeof (backing));
> +    const char *p = alloc_buffer_copy_string (&buf, "");
> +    TEST_VERIFY (p == backing);
> +    TEST_VERIFY (strcmp (p, "") == 0);
> +    TEST_VERIFY (!alloc_buffer_has_failed (&buf));
> +    TEST_VERIFY (alloc_buffer_size (&buf) == 3);
> +    TEST_VERIFY (memcmp (backing, "\0@@@", 4) == 0);
> +  }
> +  {
> +    memset (backing, '@', sizeof (backing));
> +    struct alloc_buffer buf = alloc_buffer_create (backing, sizeof (backing));
> +    const char *p = alloc_buffer_copy_string (&buf, "1");
> +    TEST_VERIFY (p == backing);
> +    TEST_VERIFY (strcmp (p, "1") == 0);
> +    TEST_VERIFY (!alloc_buffer_has_failed (&buf));
> +    TEST_VERIFY (alloc_buffer_size (&buf) == 2);
> +    TEST_VERIFY (memcmp (backing, "1\0@@", 4) == 0);
> +  }
> +  {
> +    memset (backing, '@', sizeof (backing));
> +    struct alloc_buffer buf = alloc_buffer_create (backing, sizeof (backing));
> +    const char *p = alloc_buffer_copy_string (&buf, "12");
> +    TEST_VERIFY (p == backing);
> +    TEST_VERIFY (strcmp (p, "12") == 0);
> +    TEST_VERIFY (!alloc_buffer_has_failed (&buf));
> +    TEST_VERIFY (alloc_buffer_size (&buf) == 1);
> +    TEST_VERIFY (memcmp (backing, "12\0@", 4) == 0);
> +  }
> +  {
> +    memset (backing, '@', sizeof (backing));
> +    struct alloc_buffer buf = alloc_buffer_create (backing, sizeof (backing));
> +    const char *p = alloc_buffer_copy_string (&buf, "123");
> +    TEST_VERIFY (p == backing);
> +    TEST_VERIFY (strcmp (p, "123") == 0);
> +    TEST_VERIFY (!alloc_buffer_has_failed (&buf));
> +    TEST_VERIFY (alloc_buffer_size (&buf) == 0);
> +    TEST_VERIFY (memcmp (backing, "123", 4) == 0);
> +  }
> +  {
> +    memset (backing, '@', sizeof (backing));
> +    struct alloc_buffer buf = alloc_buffer_create (backing, sizeof (backing));
> +    TEST_VERIFY (alloc_buffer_copy_string (&buf, "1234") == NULL);
> +    TEST_VERIFY (alloc_buffer_has_failed (&buf));
> +    TEST_VERIFY (memcmp (backing, "@@@@", 4) == 0);
> +  }
> +  {
> +    memset (backing, '@', sizeof (backing));
> +    struct alloc_buffer buf = alloc_buffer_create (backing, sizeof (backing));
> +    TEST_VERIFY (alloc_buffer_copy_string (&buf, "12345") == NULL);
> +    TEST_VERIFY (alloc_buffer_has_failed (&buf));
> +    TEST_VERIFY (memcmp (backing, "@@@@", 4) == 0);
> +  }
> +}
> +
> +static int
> +do_test (void)
> +{
> +  test_empty (alloc_buffer_create (NULL, 0));
> +  test_empty (alloc_buffer_create ((char *) "", 0));
> +  test_empty (alloc_buffer_create ((void *) 1, 0));
> +
> +  {
> +    struct alloc_buffer buf = alloc_buffer_allocate (1);
> +    test_size_1 (buf);
> +    alloc_buffer_free (&buf);
> +  }
> +
> +  {
> +    struct alloc_buffer buf = alloc_buffer_allocate (2);
> +    test_size_2 (buf);
> +    alloc_buffer_free (&buf);
> +  }
> +
> +  test_misaligned (0);
> +  test_misaligned (0xc7);
> +  test_misaligned (0xff);
> +
> +  test_large_misaligned ();
> +  test_large ();
> +  test_copy_bytes ();
> +  test_copy_string ();
> +
> +  return 0;
> +}
> +
> +#include <support/test-driver.c>
> diff --git a/resolv/Versions b/resolv/Versions
> index e561bce..aea43d4 100644
> --- a/resolv/Versions
> +++ b/resolv/Versions
> @@ -79,6 +79,7 @@ libresolv {
>      __ns_name_unpack; __ns_name_ntop;
>      __ns_get16; __ns_get32;
>      __libc_res_nquery; __libc_res_nsearch;
> +    __ns_name_unpack_buffer; __ns_name_ntop_buffer;
>    }
>  }
>  
> diff --git a/resolv/ns_name.c b/resolv/ns_name.c
> index 08a75e2..4cbb07d 100644
> --- a/resolv/ns_name.c
> +++ b/resolv/ns_name.c
> @@ -1,3 +1,21 @@
> +/* DNS name processing functions.
> + * Copyright (C) 1999-2017 Free Software Foundation, Inc.
> + * This file is part of the GNU C Library.
> + *
> + * The GNU C Library is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU Lesser General Public
> + * License as published by the Free Software Foundation; either
> + * version 2.1 of the License, or (at your option) any later version.
> + *
> + * The GNU C Library is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> + * Lesser General Public License for more details.
> + *
> + * You should have received a copy of the GNU Lesser General Public
> + * License along with the GNU C Library; if not, see
> + * <http://www.gnu.org/licenses/>.  */
> +
>  /*
>   * Copyright (c) 2004 by Internet Systems Consortium, Inc. ("ISC")
>   * Copyright (c) 1996,1999 by Internet Software Consortium.
> @@ -19,6 +37,7 @@
>  
>  #include <netinet/in.h>
>  #include <arpa/nameser.h>
> +#include <resolv/resolv-internal.h>
>  
>  #include <errno.h>
>  #include <resolv.h>
> @@ -26,6 +45,7 @@
>  #include <ctype.h>
>  #include <stdlib.h>
>  #include <limits.h>
> +#include <alloc_buffer.h>
>  
>  # define SPRINTF(x) ((size_t)sprintf x)
>  
> @@ -44,90 +64,94 @@ static int		labellen(const u_char *);
>  
>  /* Public. */
>  
> -/*%
> - *	Convert an encoded domain name to printable ascii as per RFC1035.
> +const char *
> +__ns_name_ntop_buffer (struct alloc_buffer *pdst,
> +		       const unsigned char *src)
> +{
> +  /* Make copy to help with aliasing analysis.  */
> +  struct alloc_buffer dst = *pdst;
> +  bool first = true;
> +  while (true)
> +    {
> +      unsigned char n = *src;
> +      ++src;
> +      if (n == 0)
> +	/* End of domain name.  */
> +	break;
> +      if (n > 63)
> +	{
> +	  /* Some kind of compression pointer.  This means that the
> +	     name has not been unpacked.  */
> +	  alloc_buffer_mark_failed (&dst);
> +	  break;
> +	}
> +
> +      /* Separate subsequent labels from their predecessor.  */
> +      if (first)
> +	first = false;
> +      else
> +	alloc_buffer_add_byte (&dst, '.');
> +
> +      for (int i = 0; i < n; ++i)
> +	{
> +	  unsigned char c = *src;
> +	  ++src;
> +	  if (special (c))
> +	    {
> +	      /* Non-decimal escape.  */
> +	      char *p = alloc_buffer_alloc_bytes (&dst, 2);
> +	      if (p == NULL)
> +		{
> +		  alloc_buffer_mark_failed (&dst);
> +		  break;
> +		}
> +	      p[0] = '\\';
> +	      p[1] = c;
> +	    }
> +	  else if (!printable (c))
> +	    {
> +	      /* Decimal escape.  */
> +	      char *p = alloc_buffer_alloc_bytes (&dst, 4);
> +	      if (p == NULL)
> +		{
> +		  alloc_buffer_mark_failed (pdst);
> +		  break;
> +		}
> +	      p[0] = '\\';
> +	      p[1] = '0' + (c / 100);
> +	      p[2] = '0' + ((c % 100) / 10);
> +	      p[3] = '0' + (c % 10);
> +	    }
> +	  else
> +	    /* Regular character.  */
> +	    alloc_buffer_add_byte (&dst, c);
> +	}
> +    }
> +  if (first)
> +    /* Root domain.  */
> +    alloc_buffer_add_byte (&dst, '.');
> +  alloc_buffer_add_byte (&dst, '\0');
> +  if (alloc_buffer_has_failed (&dst))
> +    {
> +      alloc_buffer_mark_failed (pdst);
> +      return NULL;
> +    }
> +  const char *start = alloc_buffer_next (&dst, char);
> +  *pdst = dst;
> +  return start;
> +}
> +libresolv_hidden_def (__ns_name_ntop_buffer)
>  
> - * return:
> - *\li	Number of bytes written to buffer, or -1 (with errno set)
> - *
> - * notes:
> - *\li	The root is returned as "."
> - *\li	All other domains are returned in non absolute form
> - */
>  int
> -ns_name_ntop(const u_char *src, char *dst, size_t dstsiz)
> +ns_name_ntop (const u_char *src, char *dst, size_t dstsiz)
>  {
> -	const u_char *cp;
> -	char *dn, *eom;
> -	u_char c;
> -	u_int n;
> -	int l;
> -
> -	cp = src;
> -	dn = dst;
> -	eom = dst + dstsiz;
> -
> -	while ((n = *cp++) != 0) {
> -		if ((n & NS_CMPRSFLGS) == NS_CMPRSFLGS) {
> -			/* Some kind of compression pointer. */
> -			__set_errno (EMSGSIZE);
> -			return (-1);
> -		}
> -		if (dn != dst) {
> -			if (dn >= eom) {
> -				__set_errno (EMSGSIZE);
> -				return (-1);
> -			}
> -			*dn++ = '.';
> -		}
> -		if ((l = labellen(cp - 1)) < 0) {
> -			__set_errno (EMSGSIZE);
> -			return(-1);
> -		}
> -		if (dn + l >= eom) {
> -			__set_errno (EMSGSIZE);
> -			return (-1);
> -		}
> -		for ((void)NULL; l > 0; l--) {
> -			c = *cp++;
> -			if (special(c)) {
> -				if (dn + 1 >= eom) {
> -					__set_errno (EMSGSIZE);
> -					return (-1);
> -				}
> -				*dn++ = '\\';
> -				*dn++ = (char)c;
> -			} else if (!printable(c)) {
> -				if (dn + 3 >= eom) {
> -					__set_errno (EMSGSIZE);
> -					return (-1);
> -				}
> -				*dn++ = '\\';
> -				*dn++ = digits[c / 100];
> -				*dn++ = digits[(c % 100) / 10];
> -				*dn++ = digits[c % 10];
> -			} else {
> -				if (dn >= eom) {
> -					__set_errno (EMSGSIZE);
> -					return (-1);
> -				}
> -				*dn++ = (char)c;
> -			}
> -		}
> -	}
> -	if (dn == dst) {
> -		if (dn >= eom) {
> -			__set_errno (EMSGSIZE);
> -			return (-1);
> -		}
> -		*dn++ = '.';
> -	}
> -	if (dn >= eom) {
> -		__set_errno (EMSGSIZE);
> -		return (-1);
> -	}
> -	*dn++ = '\0';
> -	return (dn - dst);
> +  struct alloc_buffer buf = alloc_buffer_create (dst, dstsiz);
> +  if (__ns_name_ntop_buffer (&buf, src) == NULL)
> +    {
> +      __set_errno (EMSGSIZE);
> +      return -1;
> +    }
> +  return alloc_buffer_next (&buf, void) - (const void *) dst;

I am not sure how safe is convert a ptrdiff_t to a int at this point.  Does
it worth add a check for it?

>  }
>  libresolv_hidden_def (ns_name_ntop)
>  strong_alias (ns_name_ntop, __ns_name_ntop)
> @@ -301,6 +325,101 @@ ns_name_ntol(const u_char *src, u_char *dst, size_t dstsiz)
>  	return (dn - dst);
>  }
>  
> +size_t
> +__ns_name_unpack_buffer (const unsigned char *msg, const unsigned char *eom,
> +			 const unsigned char *src, struct alloc_buffer *pdst)
> +{
> +  const unsigned char *initial_src = src;
> +  size_t consumed = 0;
> +  struct alloc_buffer dst = *pdst;
> +  if (src < msg || src >= eom)
> +    alloc_buffer_mark_failed (&dst);
> +  else
> +    {
> +      size_t packet_size = eom - msg;
> +      /* Count the number of bytes processed by the loop below.  If we
> +	 have covered the whole packet, this means we have a
> +	 compression loop.  */
> +      size_t processed = 0;
> +      while (true)
> +	{
> +	  if (src == eom)
> +	    {
> +	      /* Missing NUL byte at end of domain name.  */
> +	      alloc_buffer_mark_failed (&dst);
> +	      break;
> +	    }
> +
> +	  unsigned char b = *src;
> +	  ++src;
> +	  if (b == 0)
> +	    /* End of domain name.  */
> +	    {
> +	      alloc_buffer_add_byte (&dst, '\0');
> +	      if (consumed == 0)
> +		consumed = src - initial_src;
> +	      break;
> +	    }
> +	  else if (b <= 63)
> +	    {
> +	      /* Regular label.  */
> +	      size_t remaining = eom - src;
> +	      if (b > remaining)
> +		{
> +		  /* Input domain name is truncated.  */
> +		  alloc_buffer_mark_failed (&dst);
> +		  break;
> +		}
> +	      /* Include label length in copy.  */
> +	      alloc_buffer_copy_bytes (&dst, src - 1, 1 + b);
> +	      src += b;
> +	      processed += 1 + b;
> +	    }
> +	  else if ((b & NS_CMPRSFLGS) == NS_CMPRSFLGS && src < eom)
> +	    {
> +	      /* Compression reference.  */
> +	      unsigned char b2 = *src;
> +	      unsigned offset = (b - NS_CMPRSFLGS) * 256 + b2;
> +	      if (offset >= packet_size)
> +		{
> +		  /* Out-of-range compression reference.  */
> +		  alloc_buffer_mark_failed (&dst);
> +		  break;
> +		}
> +
> +	      /* Record the position after the first compression
> +		 reference.  */
> +	      if (consumed == 0)
> +		consumed = src + 1 - initial_src;
> +
> +	      /* Seek to the new position in the packet.  */
> +	      src = msg + offset;
> +
> +	      /* Compression loop detection.  Account for the two
> +		 bytes in the compression reference.  */
> +	      processed += 2;
> +	      if (processed >= packet_size)
> +		{
> +		  /* There is a compression loop.  */
> +		  alloc_buffer_mark_failed (&dst);
> +		  break;
> +		}
> +	    }
> +	  else
> +	    {
> +	      /* Invalid label length, or truncated compression
> +		 reference.  */
> +	      alloc_buffer_mark_failed (&dst);
> +	      break;
> +	    }
> +	}
> +    }
> +  if (alloc_buffer_has_failed (&dst))
> +    consumed = 0;
> +  *pdst = dst;
> +  return consumed;
> +}
> +
>  /*%
>   *	Unpack a domain name from a message, source may be compressed.
>   *
> @@ -311,73 +430,14 @@ int
>  ns_name_unpack(const u_char *msg, const u_char *eom, const u_char *src,
>  	       u_char *dst, size_t dstsiz)
>  {
> -	const u_char *srcp, *dstlim;
> -	u_char *dstp;
> -	int n, len, checked, l;
> -
> -	len = -1;
> -	checked = 0;
> -	dstp = dst;
> -	srcp = src;
> -	dstlim = dst + dstsiz;
> -	if (srcp < msg || srcp >= eom) {
> -		__set_errno (EMSGSIZE);
> -		return (-1);
> -	}
> -	/* Fetch next label in domain name. */
> -	while ((n = *srcp++) != 0) {
> -		/* Check for indirection. */
> -		switch (n & NS_CMPRSFLGS) {
> -		case 0:
> -			/* Limit checks. */
> -			if ((l = labellen(srcp - 1)) < 0) {
> -				__set_errno (EMSGSIZE);
> -				return(-1);
> -			}
> -			if (dstp + l + 1 >= dstlim || srcp + l >= eom) {
> -				__set_errno (EMSGSIZE);
> -				return (-1);
> -			}
> -			checked += l + 1;
> -			*dstp++ = n;
> -			memcpy(dstp, srcp, l);
> -			dstp += l;
> -			srcp += l;
> -			break;
> -
> -		case NS_CMPRSFLGS:
> -			if (srcp >= eom) {
> -				__set_errno (EMSGSIZE);
> -				return (-1);
> -			}
> -			if (len < 0)
> -				len = srcp - src + 1;
> -			srcp = msg + (((n & 0x3f) << 8) | (*srcp & 0xff));
> -			if (srcp < msg || srcp >= eom) {  /*%< Out of range. */
> -				__set_errno (EMSGSIZE);
> -				return (-1);
> -			}
> -			checked += 2;
> -			/*
> -			 * Check for loops in the compressed name;
> -			 * if we've looked at the whole message,
> -			 * there must be a loop.
> -			 */
> -			if (checked >= eom - msg) {
> -				__set_errno (EMSGSIZE);
> -				return (-1);
> -			}
> -			break;
> -
> -		default:
> -			__set_errno (EMSGSIZE);
> -			return (-1);			/*%< flag error */
> -		}
> -	}
> -	*dstp = '\0';
> -	if (len < 0)
> -		len = srcp - src;
> -	return (len);
> +  struct alloc_buffer buf = alloc_buffer_create (dst, dstsiz);
> +  size_t consumed = __ns_name_unpack_buffer (msg, eom, src, &buf);
> +  if (alloc_buffer_has_failed (&buf) || consumed > INT_MAX)
> +    {
> +      __set_errno (EMSGSIZE);
> +      return -1;
> +    }
> +  return consumed;
>  }
>  libresolv_hidden_def (ns_name_unpack)
>  strong_alias (ns_name_unpack, __ns_name_unpack)
> diff --git a/resolv/nss_dns/dns-network.c b/resolv/nss_dns/dns-network.c
> index 2be72d3..3c0ed82 100644
> --- a/resolv/nss_dns/dns-network.c
> +++ b/resolv/nss_dns/dns-network.c
> @@ -67,6 +67,8 @@
>  #include "nsswitch.h"
>  #include <arpa/inet.h>
>  #include <arpa/nameser.h>
> +#include <alloc_buffer.h>
> +#include <resolv/resolv-internal.h>
>  
>  /* Maximum number of aliases we allow.  */
>  #define MAX_NR_ALIASES	48
> @@ -95,8 +97,8 @@ typedef union querybuf
>  
>  /* Prototypes for local functions.  */
>  static enum nss_status getanswer_r (const querybuf *answer, int anslen,
> -				    struct netent *result, char *buffer,
> -				    size_t buflen, int *errnop, int *h_errnop,
> +				    struct netent *result, struct alloc_buffer,
> +				    int *errnop, int *h_errnop,
>  				    lookup_method net_i);
>  
>  
> @@ -134,7 +136,8 @@ _nss_dns_getnetbyname_r (const char *name, struct netent *result,
>  	? NSS_STATUS_UNAVAIL : NSS_STATUS_NOTFOUND;
>      }
>  
> -  status = getanswer_r (net_buffer.buf, anslen, result, buffer, buflen,
> +  status = getanswer_r (net_buffer.buf, anslen, result,
> +			alloc_buffer_create (buffer, buflen),
>  			errnop, herrnop, BYNAME);
>    if (net_buffer.buf != orig_net_buffer)
>      free (net_buffer.buf);
> @@ -211,7 +214,8 @@ _nss_dns_getnetbyaddr_r (uint32_t net, int type, struct netent *result,
>  	? NSS_STATUS_UNAVAIL : NSS_STATUS_NOTFOUND;
>      }
>  
> -  status = getanswer_r (net_buffer.buf, anslen, result, buffer, buflen,
> +  status = getanswer_r (net_buffer.buf, anslen, result,
> +			alloc_buffer_create (buffer, buflen),
>  			errnop, herrnop, BYADDR);
>    if (net_buffer.buf != orig_net_buffer)
>      free (net_buffer.buf);
> @@ -231,7 +235,7 @@ _nss_dns_getnetbyaddr_r (uint32_t net, int type, struct netent *result,
>  
>  static enum nss_status
>  getanswer_r (const querybuf *answer, int anslen, struct netent *result,
> -	     char *buffer, size_t buflen, int *errnop, int *h_errnop,
> +	     struct alloc_buffer buf, int *errnop, int *h_errnop,
>  	     lookup_method net_i)
>  {
>    /*
> @@ -248,16 +252,9 @@ getanswer_r (const querybuf *answer, int anslen, struct netent *result,
>     *                 | Additional | RRs holding additional information
>     *                 +------------+
>     */
> -  struct net_data
> -  {
> -    char *aliases[MAX_NR_ALIASES];
> -    char linebuffer[0];
> -  } *net_data;
>  
> -  uintptr_t pad = -(uintptr_t) buffer % __alignof__ (struct net_data);
> -  buffer += pad;
> -
> -  if (__glibc_unlikely (buflen < sizeof (*net_data) + pad))
> +  char **aliases = alloc_buffer_alloc_array (&buf, char *, MAX_NR_ALIASES);
> +  if (__glibc_unlikely (aliases == NULL))
>      {
>        /* The buffer is too small.  */
>      too_small:
> @@ -265,22 +262,15 @@ getanswer_r (const querybuf *answer, int anslen, struct netent *result,
>        *h_errnop = NETDB_INTERNAL;
>        return NSS_STATUS_TRYAGAIN;
>      }
> -  buflen -= pad;
> +  int alias_count = 0;
>  
> -  net_data = (struct net_data *) buffer;
> -  int linebuflen = buflen - offsetof (struct net_data, linebuffer);
> -  if (buflen - offsetof (struct net_data, linebuffer) != linebuflen)
> -    linebuflen = INT_MAX;
>    const unsigned char *end_of_message = &answer->buf[anslen];
>    const HEADER *header_pointer = &answer->hdr;
>    /* #/records in the answer section.  */
>    int answer_count =  ntohs (header_pointer->ancount);
>    /* #/entries in the question section.  */
>    int question_count = ntohs (header_pointer->qdcount);
> -  char *bp = net_data->linebuffer;
>    const unsigned char *cp = &answer->buf[HFIXEDSZ];
> -  char **alias_pointer;
> -  int have_answer;
>    u_char packtmp[NS_MAXCDNAME];
>  
>    if (question_count == 0)
> @@ -312,29 +302,14 @@ getanswer_r (const querybuf *answer, int anslen, struct netent *result,
>        cp += n + QFIXEDSZ;
>      }
>  
> -  alias_pointer = result->n_aliases = &net_data->aliases[0];
> -  *alias_pointer = NULL;
> -  have_answer = 0;
> -
>    while (--answer_count >= 0 && cp < end_of_message)
>      {
>        int n = __ns_name_unpack (answer->buf, end_of_message, cp,
>  				packtmp, sizeof packtmp);
> -      if (n != -1 && __ns_name_ntop (packtmp, bp, linebuflen) == -1)
> -	{
> -	  if (errno == EMSGSIZE)
> -	    goto too_small;
> -
> -	  n = -1;
> -	}
> -
> -      if (n > 0 && bp[0] == '.')
> -	bp[0] = '\0';
> -
> -      if (n < 0 || res_dnok (bp) == 0)
> +      if (n == -1)
>  	break;
> +
>        cp += n;
> -
>        if (end_of_message - cp < 10)
>  	{
>  	  __set_h_errno (NO_RECOVERY);
> @@ -357,7 +332,10 @@ getanswer_r (const querybuf *answer, int anslen, struct netent *result,
>  	{
>  	  n = __ns_name_unpack (answer->buf, end_of_message, cp,
>  				packtmp, sizeof packtmp);
> -	  if (n != -1 && __ns_name_ntop (packtmp, bp, linebuflen) == -1)
> +	  char *alias = NULL;
> +	  if (n != -1
> +	      && ((alias = (char *) __ns_name_ntop_buffer (&buf, packtmp))
> +		  == NULL))
>  	    {
>  	      if (errno == EMSGSIZE)
>  		goto too_small;
> @@ -365,7 +343,7 @@ getanswer_r (const querybuf *answer, int anslen, struct netent *result,
>  	      n = -1;
>  	    }
>  
> -	  if (n < 0 || !res_hnok (bp))
> +	  if (n < 0 || !res_hnok (alias))
>  	    {
>  	      /* XXX What does this mean?  The original form from bind
>  		 returns NULL. Incrementing cp has no effect in any case.
> @@ -374,35 +352,33 @@ getanswer_r (const querybuf *answer, int anslen, struct netent *result,
>  	      return NSS_STATUS_UNAVAIL;
>  	    }
>  	  cp += rdatalen;
> -         if (alias_pointer + 2 < &net_data->aliases[MAX_NR_ALIASES])
> -           {
> -             *alias_pointer++ = bp;
> -             n = strlen (bp) + 1;
> -             bp += n;
> -             linebuflen -= n;
> -             result->n_addrtype = class == C_IN ? AF_INET : AF_UNSPEC;
> -             ++have_answer;
> -           }
> +	  if (alias_count + 1 < MAX_NR_ALIASES)
> +	    {
> +	      aliases[alias_count++] = alias;
> +	      result->n_addrtype = class == C_IN ? AF_INET : AF_UNSPEC;
> +	    }
>  	}
>        else
>  	/* Skip over unknown record data.  */
>  	cp += rdatalen;
>      }
> +  aliases[alias_count] = NULL;
>  
> -  if (have_answer)
> +  if (alias_count > 0)
>      {
> -      *alias_pointer = NULL;
>        switch (net_i)
>  	{
>  	case BYADDR:
> -	  result->n_name = *result->n_aliases++;
> +	  /* Use the first alias as the name.  */
> +	  result->n_name = aliases[0];
> +	  result->n_aliases = aliases + 1;
>  	  result->n_net = 0L;
>  	  return NSS_STATUS_SUCCESS;
>  
>  	case BYNAME:
>  	  {
>  	    char **ap;
> -	    for (ap = result->n_aliases; *ap != NULL; ++ap)
> +	    for (ap = aliases; *ap != NULL; ++ap)
>  	      {
>  		/* Check each alias name for being of the forms:
>  		   4.3.2.1.in-addr.arpa		= net 1.2.3.4
> @@ -455,6 +431,8 @@ getanswer_r (const querybuf *answer, int anslen, struct netent *result,
>  		       2. This is not the droid we are looking for.  */
>  		    if (!isdigit (*p) && !strcasecmp (p, "in-addr.arpa"))
>  		      {
> +			result->n_name = aliases[0];
> +			result->n_aliases = aliases;
>  			result->n_net = val;
>  			return NSS_STATUS_SUCCESS;
>  		      }
> diff --git a/resolv/resolv-internal.h b/resolv/resolv-internal.h
> index 0d69ce1..f735a6b 100644
> --- a/resolv/resolv-internal.h
> +++ b/resolv/resolv-internal.h
> @@ -56,4 +56,24 @@ enum
>  int __res_nopt (res_state, int n0, unsigned char *buf, int buflen,
>                  int anslen) attribute_hidden;
>  
> +struct alloc_buffer;
> +
> +/* Convert the expanded domain name at SRC from wire format to text
> +   format.  Use storage in *DST.  Return a pointer to data in *DST, or
> +   NULL on error (and put the allocation buffer into the failed
> +   state).  */
> +const char *__ns_name_ntop_buffer (struct alloc_buffer *, const u_char *)
> +  __THROW;
> +libresolv_hidden_proto (__ns_name_ntop_buffer)
> +
> +/* Decompress the domain name at SRC (within the DNS packet extending
> +   from MSG to EOM) into the allocation buffer.  Returns the number of
> +   bytes consumed from the input, or 0 on failure.  On failure, put
> +   the allocation buffer into the failed state.  */
> +size_t __ns_name_unpack_buffer (const unsigned char *msg,
> +                                const unsigned char *eom,
> +                                const unsigned char *src,
> +                                struct alloc_buffer *) __THROW;
> +libresolv_hidden_proto (__ns_name_unpack_buffer)
> +
>  #endif  /* _RESOLV_INTERNAL_H */
>
diff mbox

Patch

Implement allocation buffers for internal use

This commit adds fixed-size allocation buffers.  The primary use
case is in NSS modules, where dynamically sized data is stored
in a fixed-size buffer provided by the caller.

Other uses include a replacement of mempcpy cascades (which is
safer due to the size checking inherent to allocation buffers).

The commit converts the "network" handling in nss_dns to allocation
buffers because this is something for which test coverage already
exists.

2017-04-22  Florian Weimer  <fweimer@redhat.com>

	* malloc/Makefile (tests): Add tst-alloc_buffer.
	(routines): Add alloc_buffer_alloc_array, alloc_buffer_allocate,
	alloc_buffer_copy_bytes.
	* malloc/Versions (__libc_alloc_buffer_alloc_array)
	(__libc_alloc_buffer_allocate, __libc_alloc_buffer_copy_bytes):
	Export as GLIBC_PRIVATE.
	* malloc/alloc_buffer_alloc_array.c: New file.
	* malloc/alloc_buffer_allocate.c: Likewise.
	* malloc/alloc_buffer_copy_bytes.c: Likewise.
	* malloc/alloc_buffer_copy_string.c: Likewise.
	* malloc/tst-alloc_buffer.c: Likewise.
	* resolv/Versions (__ns_name_ntop_buffer)
	(__ns_name_unpack_buffer): Export as GLIBC_PRIVATE.
	* resolv/ns_name.c (__ns_name_ntop_buffer): New function.
	(ns_name_ntop): Implement using __ns_name_ntop_buffer.
	(__ns_name_unpack_buffer): New function.
	(ns_name_unpack): Implement using __ns_name_unpack_buffer.
	* resolv/nss_dns/dns-network.c (_nss_dns_getnetbyname_r)
	(_nss_dns_getnetbyaddr_r): Adjust call to getanswer_r.
	(getanswer_r): Store result in an allocation buffer.  Use
	__ns_name_ntop_buffer instead of ns_name_ntop.  Remove superfluous
	call to res_dnok because ns_name_ntop output is always printable
	ASCII.

diff --git a/include/alloc_buffer.h b/include/alloc_buffer.h
new file mode 100644
index 0000000..386e49c
--- /dev/null
+++ b/include/alloc_buffer.h
@@ -0,0 +1,383 @@ 
+/* Allocation from a fixed-size buffer.
+   Copyright (C) 2017 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <http://www.gnu.org/licenses/>.  */
+
+/* Allocation buffers are used to carve out sub-allocations from a
+   larger allocation.  Their primary application is in writing NSS
+   modules, which recieve a caller-allocated buffer in which they are
+   expected to store variable-length results:
+
+     void *buffer = ...;
+     size_t buffer_size = ...;
+
+     struct alloc_buffer buf = alloc_buffer_create (buffer, buffer_size);
+     result->gr_name = alloc_buffer_copy_string (&buf, name);
+
+     // Allocate a list of group_count groups and copy strings into it.
+     char **group_list = alloc_buffer_alloc_array
+       (&buf, char *, group_count  + 1);
+     if (group_list == NULL)
+       return ...; // Request a larger buffer.
+     for (int i = 0; i < group_count; ++i)
+       group_list[i] = alloc_buffer_copy_string (&buf, group_list_src[i]);
+     group_list[group_count] = NULL;
+     ...
+
+     if (alloc_buffer_has_failed (&buf))
+       return ...; // Request a larger buffer.
+     result->gr_mem = group_list;
+     ...
+
+   Note that it is not necessary to check the results of individual
+   allocation operations if the returned pointer is not dereferenced.
+   Allocation failure is sticky, so one check using
+   alloc_buffer_has_failed at the end covers all previous failures.
+
+   A different use case involves combining multiple heap allocations
+   into a single, large one.  In the following example, an array of
+   doubles and an array of ints is allocated:
+
+     size_t double_array_size = ...;
+     size_t int_array_size = ...;
+
+     struct alloc_buffer buf = alloc_buffer_allocate
+       (double_array_size * sizeof (double) + int_array_size * sizeof (int));
+     _Static_assert (__alignof__ (double) >= __alignof__ (int),
+                     "no padding after double array");
+     double *double_array = alloc_buffer_alloc_array
+       (&buf, double, double_array_size);
+     int *int_array = alloc_buffer_alloc_array (&buf, int, int_array_size);
+     if (alloc_buffer_has_failed (&buf))
+       return ...; // Report error.
+
+   The advantage over manual coding is that the computation of the
+   allocation size does not need an overflow check.  The size
+   computation is checked for consistency at run time, too.  */
+
+#ifndef _ALLOC_BUFFER_H
+#define _ALLOC_BUFFER_H
+
+#include <inttypes.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <sys/param.h>
+
+/* struct alloc_buffer objects refer to a region of bytes in memory of a
+   fixed size.  The functions below can be used to allocate single
+   objects and arrays from this memory region, or write to its end.
+   On allocation failure (or if an attempt to write beyond the end of
+   the buffer with one of the copy functions), the buffer enters a
+   failed state.
+
+   struct alloc_buffer objects can be copied.  The backing buffer will
+   be shared, but the current write position will be independent.
+
+   Conceptually, the memory region consists of a current write pointer
+   and a limit, beyond which the write pointer cannot move.  */
+struct alloc_buffer
+{
+  /* uintptr_t is used here to simplify the alignment code, and to
+     avoid issues undefined subtractions if the buffer covers more
+     than half of the address space (which would result in differences
+     which could not be represented as a ptrdiff_t value).  */
+  uintptr_t __alloc_buffer_current;
+  uintptr_t __alloc_buffer_end;
+};
+
+enum
+  {
+    /* The value for the __alloc_buffer_current member which marks the
+       buffer as invalid (together with a zero-length buffer).  */
+    __ALLOC_BUFFER_INVALID_POINTER = 0,
+  };
+
+/* Create a new allocation buffer.  The byte range from START to START
+   + SIZE - 1 must be valid, and the allocation buffer allocates
+   objects from that range.  If START is NULL (so that SIZE must be
+   0), the buffer is marked as failed immediately.  */
+static inline struct alloc_buffer
+alloc_buffer_create (void *start, size_t size)
+{
+  return (struct alloc_buffer)
+    {
+      .__alloc_buffer_current = (uintptr_t) start,
+      .__alloc_buffer_end = (uintptr_t) start + size
+    };
+}
+
+/* Internal function.  See alloc_buffer_allocate below.  */
+struct alloc_buffer __libc_alloc_buffer_allocate (size_t size);
+libc_hidden_proto (__libc_alloc_buffer_allocate)
+
+/* Allocate a buffer of SIZE bytes using malloc.  The returned buffer
+   is in a failed state if malloc fails.  */
+static __always_inline
+struct alloc_buffer alloc_buffer_allocate (size_t size)
+{
+  return __libc_alloc_buffer_allocate (size);
+}
+
+/* Mark the buffer as failed.  */
+static inline void
+alloc_buffer_mark_failed (struct alloc_buffer *buf)
+{
+  buf->__alloc_buffer_current = __ALLOC_BUFFER_INVALID_POINTER;
+  buf->__alloc_buffer_end = __ALLOC_BUFFER_INVALID_POINTER;
+}
+
+/* Deallocate the buffer and mark it as failed.  The buffer must be in
+   its initial state; if data has been added to it, an invocation of
+   alloc_buffer_free results in undefined behavior.  This means that
+   callers need to make a copy of the buffer if they need to free it
+   later.  Deallocating a failed buffer is allowed; it has no
+   effect.  */
+static inline void
+alloc_buffer_free (struct alloc_buffer *buf)
+{
+  _Static_assert (__ALLOC_BUFFER_INVALID_POINTER == 0,
+		  "free can be called on __ALLOC_BUFFER_INVALID_POINTER");
+  free ((void *) buf->__alloc_buffer_current);
+  alloc_buffer_mark_failed (buf);
+}
+
+/* Return the remaining number of bytes in the buffer.  */
+static __always_inline size_t
+alloc_buffer_size (const struct alloc_buffer *buf)
+{
+  return buf->__alloc_buffer_end - buf->__alloc_buffer_current;
+}
+
+/* Return true if the buffer has been marked as failed.  */
+static inline bool
+alloc_buffer_has_failed (const struct alloc_buffer *buf)
+{
+  return buf->__alloc_buffer_current == __ALLOC_BUFFER_INVALID_POINTER;
+}
+
+/* Add a single byte to the buffer (consuming the space for this
+   byte).  Mark the buffer as failed if there is not enough room.  */
+static inline void
+alloc_buffer_add_byte (struct alloc_buffer *buf, unsigned char b)
+{
+  if (__glibc_likely (buf->__alloc_buffer_current < buf->__alloc_buffer_end))
+    {
+      *(unsigned char *) buf->__alloc_buffer_current = b;
+      ++buf->__alloc_buffer_current;
+    }
+  else
+    alloc_buffer_mark_failed (buf);
+}
+
+/* Obtain a pointer to LENGTH bytes in BUF, and consume these bytes.
+   NULL is returned if there is not enough room, and the buffer is
+   marked as failed, or if the buffer has already failed.
+   (Zero-length allocations from an empty buffer which has not yet
+   failed succeed.)  */
+static inline void *
+alloc_buffer_alloc_bytes (struct alloc_buffer *buf, size_t length)
+{
+  if (length <= alloc_buffer_size (buf))
+    {
+      void *result = (void *) buf->__alloc_buffer_current;
+      buf->__alloc_buffer_current += length;
+      return result;
+    }
+  else
+    {
+      alloc_buffer_mark_failed (buf);
+      return NULL;
+    }
+}
+
+/* Internal function.  Statically assert that the type size is
+   constant and valid.  */
+static __always_inline size_t
+__alloc_buffer_assert_size (size_t size)
+{
+  if (!__builtin_constant_p (size))
+    {
+      __errordecl (error, "type size is not constant");
+      error ();
+    }
+  else if (size == 0)
+    {
+      __errordecl (error, "type size is zero");
+      error ();
+    }
+  return size;
+}
+
+/* Internal function.  Statically assert that the type alignment is
+   constant and valid.  */
+static __always_inline size_t
+__alloc_buffer_assert_align (size_t align)
+{
+  if (!__builtin_constant_p (align))
+    {
+      __errordecl (error, "type alignment is not constant");
+      error ();
+    }
+  else if (align == 0)
+    {
+      __errordecl (error, "type alignment is zero");
+      error ();
+    }
+  else if (!powerof2 (align))
+    {
+      __errordecl (error, "type alignment is not a power of two");
+      error ();
+    }
+  return align;
+}
+
+/* Internal function.  Obtain a pointer to an object.  */
+static inline void *
+__alloc_buffer_alloc (struct alloc_buffer *buf, size_t size, size_t align)
+{
+  if (size == 1 && align == 1)
+    return alloc_buffer_alloc_bytes (buf, size);
+
+  size_t current = buf->__alloc_buffer_current;
+  size_t aligned = roundup (current, align);
+  size_t new_current = aligned + size;
+  if (aligned >= current        /* No overflow in align step.  */
+      && new_current >= size    /* No overflow in size computation.  */
+      && new_current <= buf->__alloc_buffer_end) /* Room in buffer.  */
+    {
+      buf->__alloc_buffer_current = new_current;
+      return (void *) aligned;
+    }
+  else
+    {
+      alloc_buffer_mark_failed (buf);
+      return NULL;
+    }
+}
+
+/* Obtain a TYPE * pointer to an object in BUF of TYPE.  Consume these
+   bytes from the buffer.  Return NULL and mark the buffer as failed
+   if if there is not enough room in the buffer, or if the buffer has
+   failed before.  */
+#define alloc_buffer_alloc(buf, type)				\
+  ((type *) __alloc_buffer_alloc				\
+   (buf, __alloc_buffer_assert_size (sizeof (type)),		\
+    __alloc_buffer_assert_align (__alignof__ (type))))
+
+/* Internal function.  Obtain a pointer to an object which is
+   subsequently added.  */
+static inline const void *
+__alloc_buffer_next (struct alloc_buffer *buf, size_t align)
+{
+  if (align == 1)
+    return (const void *) buf->__alloc_buffer_current;
+
+  size_t current = buf->__alloc_buffer_current;
+  size_t aligned = roundup (current, align);
+  if (aligned >= current        /* No overflow in align step.  */
+      && aligned <= buf->__alloc_buffer_end) /* Room in buffer.  */
+    {
+      buf->__alloc_buffer_current = aligned;
+      return (const void *) aligned;
+    }
+  else
+    {
+      alloc_buffer_mark_failed (buf);
+      return NULL;
+    }
+}
+
+/* Like alloc_buffer_alloc, but do not advance the pointer beyond the
+   object (so a subseqent call to alloc_buffer_next or
+   alloc_buffer_alloc returns the same pointer).  Note that the buffer
+   is still aligned according to the requirements of TYPE.  The effect
+   of this function is similar to allocating a zero-length array from
+   the buffer.  */
+#define alloc_buffer_next(buf, type)				\
+  ((const type *) __alloc_buffer_next				\
+   (buf, __alloc_buffer_assert_align (__alignof__ (type))))
+
+/* Internal function.  Allocate an array.  */
+void * __libc_alloc_buffer_alloc_array (struct alloc_buffer *buf,
+				      size_t size, size_t align,
+				      size_t count);
+libc_hidden_proto (__libc_alloc_buffer_alloc_array)
+
+/* Obtain a TYPE * pointer to an array of COUNT objects in BUF of
+   TYPE.  Consume these bytes from the buffer.  Return NULL and mark
+   the buffer as failed if if there is not enough room in the buffer,
+   or if the buffer has failed before.  (Zero-length allocations from
+   an empty buffer which has not yet failed succeed.)  */
+#define alloc_buffer_alloc_array(buf, type, count)       \
+  ((type *) __libc_alloc_buffer_alloc_array		 \
+   (buf, __alloc_buffer_assert_size (sizeof (type)),	 \
+    __alloc_buffer_assert_align (__alignof__ (type)),	 \
+    count))
+
+/* Internal function.  See alloc_buffer_copy_bytes below.  */
+struct alloc_buffer __libc_alloc_buffer_copy_bytes (struct alloc_buffer,
+						const void *, size_t);
+libc_hidden_proto (__libc_alloc_buffer_copy_bytes)
+
+/* Copy SIZE bytes starting at SRC into the buffer.  If there is not
+   enough room in the buffer, the buffer is marked as failed.  No
+   alignment of the buffer is performed.  */
+static inline void
+alloc_buffer_copy_bytes (struct alloc_buffer *buf, const void *src, size_t size)
+{
+  *buf = __libc_alloc_buffer_copy_bytes (*buf, src, size);
+}
+
+/* Internal function.  See alloc_buffer_copy_string below.  */
+struct alloc_buffer __libc_alloc_buffer_copy_string (struct alloc_buffer,
+						     const char *);
+libc_hidden_proto (__libc_alloc_buffer_copy_string)
+
+/* Copy the string at SRC into the buffer, including its null
+   terminator.  If there is not enough room in the buffer, the buffer
+   is marked as failed.  Return a pointer to the string.  */
+static inline char *
+alloc_buffer_copy_string (struct alloc_buffer *buf, const char *src)
+{
+  char *result = (char *) buf->__alloc_buffer_current;
+  *buf = __libc_alloc_buffer_copy_string (*buf, src);
+  if (alloc_buffer_has_failed (buf))
+    result = NULL;
+  return result;
+}
+
+/* Internal function.  Set *RESULT to LEFT * RIGHT.  Return true if
+   the result overflowed.  */
+static inline bool
+__check_mul_overflow_size_t (size_t left, size_t right, size_t *result)
+{
+#if __GNUC__ >= 5
+  return __builtin_mul_overflow (left, right, result);
+#else
+  /* size_t is unsigned so the behavior on overflow is defined.  */
+  *result = left * right;
+  size_t half_size_t = ((size_t) 1) << (8 * sizeof (size_t) / 2);
+  if (__glibc_unlikely ((left | right) >= half_size_t))
+    {
+      if (__glibc_unlikely (right != 0 && *result / right != left))
+        return true;
+    }
+  return false;
+#endif
+}
+
+#endif /* _ALLOC_BUFFER_H */
diff --git a/malloc/Makefile b/malloc/Makefile
index e93b83b..2f7bb5a 100644
--- a/malloc/Makefile
+++ b/malloc/Makefile
@@ -33,6 +33,7 @@  tests := mallocbug tst-malloc tst-valloc tst-calloc tst-obstack \
 	 tst-mallocfork2 \
 	 tst-interpose-nothread \
 	 tst-interpose-thread \
+	 tst-alloc_buffer \
 
 tests-static := \
 	 tst-interpose-static-nothread \
@@ -49,7 +50,11 @@  test-srcs = tst-mtrace
 
 routines = malloc morecore mcheck mtrace obstack \
   scratch_buffer_grow scratch_buffer_grow_preserve \
-  scratch_buffer_set_array_size
+  scratch_buffer_set_array_size \
+  alloc_buffer_alloc_array \
+  alloc_buffer_allocate \
+  alloc_buffer_copy_bytes  \
+  alloc_buffer_copy_string \
 
 install-lib := libmcheck.a
 non-lib.a := libmcheck.a
diff --git a/malloc/Versions b/malloc/Versions
index e34ab17..361550b 100644
--- a/malloc/Versions
+++ b/malloc/Versions
@@ -74,5 +74,11 @@  libc {
     __libc_scratch_buffer_grow;
     __libc_scratch_buffer_grow_preserve;
     __libc_scratch_buffer_set_array_size;
+
+    # struct alloc_buffer support
+    __libc_alloc_buffer_alloc_array;
+    __libc_alloc_buffer_allocate;
+    __libc_alloc_buffer_copy_bytes;
+    __libc_alloc_buffer_copy_string;
   }
 }
diff --git a/malloc/alloc_buffer_alloc_array.c b/malloc/alloc_buffer_alloc_array.c
new file mode 100644
index 0000000..0172029
--- /dev/null
+++ b/malloc/alloc_buffer_alloc_array.c
@@ -0,0 +1,45 @@ 
+/* Array allocation from a fixed-size buffer.
+   Copyright (C) 2017 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <http://www.gnu.org/licenses/>.  */
+
+#include <alloc_buffer.h>
+
+void *
+__libc_alloc_buffer_alloc_array (struct alloc_buffer *buf, size_t element_size,
+                                 size_t align, size_t count)
+{
+  size_t current = buf->__alloc_buffer_current;
+  /* The caller asserts that align is a power of two.  */
+  size_t aligned = (current + align - 1) & ~(align - 1);
+  size_t size;
+  bool overflow = __check_mul_overflow_size_t (element_size, count, &size);
+  size_t new_current = aligned + size;
+  if (!overflow                /* Multiplication did not overflow.  */
+      && aligned >= current    /* No overflow in align step.  */
+      && new_current >= size   /* No overflow in size computation.  */
+      && new_current <= buf->__alloc_buffer_end) /* Room in buffer.  */
+    {
+      buf->__alloc_buffer_current = new_current;
+      return (void *) aligned;
+    }
+  else
+    {
+      alloc_buffer_mark_failed (buf);
+      return NULL;
+    }
+}
+libc_hidden_def (__libc_alloc_buffer_alloc_array)
diff --git a/malloc/alloc_buffer_allocate.c b/malloc/alloc_buffer_allocate.c
new file mode 100644
index 0000000..a9fd3f1
--- /dev/null
+++ b/malloc/alloc_buffer_allocate.c
@@ -0,0 +1,36 @@ 
+/* Allocate a fixed-size allocation buffer using malloc.
+   Copyright (C) 2017 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <http://www.gnu.org/licenses/>.  */
+
+#include <alloc_buffer.h>
+
+#include <stdlib.h>
+
+struct alloc_buffer
+__libc_alloc_buffer_allocate (size_t size)
+{
+  void *ptr = malloc (size);
+  if (ptr == NULL)
+    return (struct alloc_buffer)
+      {
+        .__alloc_buffer_current = __ALLOC_BUFFER_INVALID_POINTER,
+        .__alloc_buffer_end = __ALLOC_BUFFER_INVALID_POINTER
+      };
+  else
+    return alloc_buffer_create (ptr, size);
+}
+libc_hidden_def (__libc_alloc_buffer_allocate)
diff --git a/malloc/alloc_buffer_copy_bytes.c b/malloc/alloc_buffer_copy_bytes.c
new file mode 100644
index 0000000..66196f1
--- /dev/null
+++ b/malloc/alloc_buffer_copy_bytes.c
@@ -0,0 +1,34 @@ 
+/* Copy an array of bytes into the buffer.
+   Copyright (C) 2017 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <http://www.gnu.org/licenses/>.  */
+
+#include <alloc_buffer.h>
+
+#include <string.h>
+
+/* This function works on a copy of the buffer object, so that it can
+   remain non-addressable in the caller.  */
+struct alloc_buffer
+__libc_alloc_buffer_copy_bytes (struct alloc_buffer buf,
+                                const void *src, size_t len)
+{
+  void *ptr = alloc_buffer_alloc_bytes (&buf, len);
+  if (ptr != NULL)
+    memcpy (ptr, src, len);
+  return buf;
+}
+libc_hidden_def (__libc_alloc_buffer_copy_bytes)
diff --git a/malloc/alloc_buffer_copy_string.c b/malloc/alloc_buffer_copy_string.c
new file mode 100644
index 0000000..77c0023
--- /dev/null
+++ b/malloc/alloc_buffer_copy_string.c
@@ -0,0 +1,30 @@ 
+/* Copy a string into the allocation buffer.
+   Copyright (C) 2017 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <http://www.gnu.org/licenses/>.  */
+
+#include <alloc_buffer.h>
+
+#include <string.h>
+
+/* This function works on a copy of the buffer object, so that it can
+   remain non-addressable in the caller.  */
+struct alloc_buffer
+__libc_alloc_buffer_copy_string (struct alloc_buffer buf, const char *src)
+{
+  return __libc_alloc_buffer_copy_bytes (buf, src, strlen (src) + 1);
+}
+libc_hidden_def (__libc_alloc_buffer_copy_string)
diff --git a/malloc/tst-alloc_buffer.c b/malloc/tst-alloc_buffer.c
new file mode 100644
index 0000000..362dae2
--- /dev/null
+++ b/malloc/tst-alloc_buffer.c
@@ -0,0 +1,663 @@ 
+/* Tests for struct alloc_buffer.
+   Copyright (C) 2017 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <http://www.gnu.org/licenses/>.  */
+
+#include <arpa/inet.h>
+#include <alloc_buffer.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <support/check.h>
+#include <support/support.h>
+#include <support/test-driver.h>
+
+/* Return true if PTR is sufficiently aligned for TYPE.  */
+#define IS_ALIGNED(ptr, type) \
+  ((((uintptr_t) ptr) & (__alloc_buffer_assert_align (__alignof (type)) - 1)) \
+   == 0)
+
+/* Structure with non-power-of-two size.  */
+struct twelve
+{
+  uint32_t buffer[3] __attribute__ ((aligned (4)));
+};
+_Static_assert (sizeof (struct twelve) == 12, "struct twelve");
+_Static_assert (__alignof__ (struct twelve) == 4, "struct twelve");
+
+/* Check for success obtaining empty arrays.  Does not assume the
+   buffer is empty.  */
+static void
+test_empty_array (struct alloc_buffer refbuf)
+{
+  bool refbuf_failed = alloc_buffer_has_failed (&refbuf);
+  if (test_verbose)
+    printf ("info: %s: current=0x%llx end=0x%llx refbuf_failed=%d\n",
+            __func__, (unsigned long long) refbuf.__alloc_buffer_current,
+            (unsigned long long) refbuf.__alloc_buffer_end, refbuf_failed);
+  {
+    struct alloc_buffer buf = refbuf;
+    TEST_VERIFY ((alloc_buffer_alloc_bytes (&buf, 0) == NULL)
+                 == refbuf_failed);
+    TEST_VERIFY (alloc_buffer_has_failed (&buf) == refbuf_failed);
+  }
+  {
+    struct alloc_buffer buf = refbuf;
+    TEST_VERIFY ((alloc_buffer_alloc_array (&buf, char, 0) == NULL)
+                 == refbuf_failed);
+    TEST_VERIFY (alloc_buffer_has_failed (&buf) == refbuf_failed);
+  }
+  /* The following tests can fail due to the need for aligning the
+     returned pointer.  */
+  {
+    struct alloc_buffer buf = refbuf;
+    bool expect_failure = refbuf_failed
+      || !IS_ALIGNED (alloc_buffer_next (&buf, void), double);
+    double *ptr = alloc_buffer_alloc_array (&buf, double, 0);
+    TEST_VERIFY (IS_ALIGNED (ptr, double));
+    TEST_VERIFY ((ptr == NULL) == expect_failure);
+    TEST_VERIFY (alloc_buffer_has_failed (&buf) == expect_failure);
+  }
+  {
+    struct alloc_buffer buf = refbuf;
+    bool expect_failure = refbuf_failed
+      || !IS_ALIGNED (alloc_buffer_next (&buf, void), struct twelve);
+    struct twelve *ptr = alloc_buffer_alloc_array (&buf, struct twelve, 0);
+    TEST_VERIFY (IS_ALIGNED (ptr, struct twelve));
+    TEST_VERIFY ((ptr == NULL) == expect_failure);
+    TEST_VERIFY (alloc_buffer_has_failed (&buf) == expect_failure);
+  }
+}
+
+/* Test allocation of impossibly large arrays.  */
+static void
+test_impossible_array (struct alloc_buffer refbuf)
+{
+  if (test_verbose)
+    printf ("info: %s: current=0x%llx end=0x%llx\n",
+            __func__, (unsigned long long) refbuf.__alloc_buffer_current,
+            (unsigned long long) refbuf.__alloc_buffer_end);
+  static const size_t counts[] =
+    { SIZE_MAX, SIZE_MAX - 1, SIZE_MAX - 2, SIZE_MAX - 3, SIZE_MAX - 4,
+      SIZE_MAX / 2, SIZE_MAX / 2 + 1, SIZE_MAX / 2 - 1, 0};
+
+  for (int i = 0; counts[i] != 0; ++i)
+    {
+      size_t count = counts[i];
+      if (test_verbose)
+        printf ("info: %s: count=%zu\n", __func__, count);
+      {
+        struct alloc_buffer buf = refbuf;
+        TEST_VERIFY (alloc_buffer_alloc_bytes (&buf, count) == NULL);
+        TEST_VERIFY (alloc_buffer_has_failed (&buf));
+      }
+      {
+        struct alloc_buffer buf = refbuf;
+        TEST_VERIFY (alloc_buffer_alloc_array (&buf, char, count) == NULL);
+        TEST_VERIFY (alloc_buffer_has_failed (&buf));
+      }
+      {
+        struct alloc_buffer buf = refbuf;
+        TEST_VERIFY (alloc_buffer_alloc_array (&buf, short, count) == NULL);
+        TEST_VERIFY (alloc_buffer_has_failed (&buf));
+      }
+      {
+        struct alloc_buffer buf = refbuf;
+        TEST_VERIFY (alloc_buffer_alloc_array (&buf, double, count) == NULL);
+        TEST_VERIFY (alloc_buffer_has_failed (&buf));
+      }
+      {
+        struct alloc_buffer buf = refbuf;
+        TEST_VERIFY (alloc_buffer_alloc_array (&buf, struct twelve, count)
+                     == NULL);
+        TEST_VERIFY (alloc_buffer_has_failed (&buf));
+      }
+    }
+}
+
+/* Check for failure to obtain anything from a failed buffer.  */
+static void
+test_after_failure (struct alloc_buffer refbuf)
+{
+  if (test_verbose)
+    printf ("info: %s: current=0x%llx end=0x%llx\n",
+            __func__, (unsigned long long) refbuf.__alloc_buffer_current,
+            (unsigned long long) refbuf.__alloc_buffer_end);
+  TEST_VERIFY (alloc_buffer_has_failed (&refbuf));
+  {
+    struct alloc_buffer buf = refbuf;
+    alloc_buffer_add_byte (&buf, 17);
+    TEST_VERIFY (alloc_buffer_has_failed (&buf));
+  }
+  {
+    struct alloc_buffer buf = refbuf;
+    TEST_VERIFY (alloc_buffer_alloc (&buf, char) == NULL);
+    TEST_VERIFY (alloc_buffer_has_failed (&buf));
+  }
+  {
+    struct alloc_buffer buf = refbuf;
+    TEST_VERIFY (alloc_buffer_alloc (&buf, double) == NULL);
+    TEST_VERIFY (alloc_buffer_has_failed (&buf));
+  }
+  {
+    struct alloc_buffer buf = refbuf;
+    TEST_VERIFY (alloc_buffer_alloc (&buf, struct twelve) == NULL);
+    TEST_VERIFY (alloc_buffer_has_failed (&buf));
+  }
+
+  test_impossible_array (refbuf);
+  for (int count = 0; count <= 4; ++count)
+    {
+      {
+        struct alloc_buffer buf = refbuf;
+        TEST_VERIFY (alloc_buffer_alloc_bytes (&buf, count) == NULL);
+        TEST_VERIFY (alloc_buffer_has_failed (&buf));
+      }
+      {
+        struct alloc_buffer buf = refbuf;
+        TEST_VERIFY (alloc_buffer_alloc_array (&buf, char, count) == NULL);
+        TEST_VERIFY (alloc_buffer_has_failed (&buf));
+      }
+      {
+        struct alloc_buffer buf = refbuf;
+        TEST_VERIFY (alloc_buffer_alloc_array (&buf, double, count) == NULL);
+        TEST_VERIFY (alloc_buffer_has_failed (&buf));
+      }
+      {
+        struct alloc_buffer buf = refbuf;
+        TEST_VERIFY (alloc_buffer_alloc_array (&buf, struct twelve, count)
+                     == NULL);
+        TEST_VERIFY (alloc_buffer_has_failed (&buf));
+      }
+    }
+}
+
+static void
+test_empty (struct alloc_buffer refbuf)
+{
+  TEST_VERIFY (alloc_buffer_size (&refbuf) == 0);
+  if (alloc_buffer_next (&refbuf, void) != NULL)
+    TEST_VERIFY (!alloc_buffer_has_failed (&refbuf));
+  test_empty_array (refbuf);
+  test_impossible_array (refbuf);
+
+  /* Failure to obtain non-empty objects.  */
+  {
+    struct alloc_buffer buf = refbuf;
+    alloc_buffer_add_byte (&buf, 17);
+    test_after_failure (buf);
+  }
+  {
+    struct alloc_buffer buf = refbuf;
+    TEST_VERIFY (alloc_buffer_alloc (&buf, char) == NULL);
+    test_after_failure (buf);
+  }
+  {
+    struct alloc_buffer buf = refbuf;
+    TEST_VERIFY (alloc_buffer_alloc (&buf, double) == NULL);
+    test_after_failure (buf);
+  }
+  {
+    struct alloc_buffer buf = refbuf;
+    TEST_VERIFY (alloc_buffer_alloc (&buf, struct twelve) == NULL);
+    test_after_failure (buf);
+  }
+  {
+    struct alloc_buffer buf = refbuf;
+    TEST_VERIFY (alloc_buffer_alloc_array (&buf, char, 1) == NULL);
+    test_after_failure (buf);
+  }
+  {
+    struct alloc_buffer buf = refbuf;
+    TEST_VERIFY (alloc_buffer_alloc_array (&buf, double, 1) == NULL);
+    test_after_failure (buf);
+  }
+  {
+    struct alloc_buffer buf = refbuf;
+    TEST_VERIFY (alloc_buffer_alloc_array (&buf, struct twelve, 1) == NULL);
+    test_after_failure (buf);
+  }
+}
+
+static void
+test_size_1 (struct alloc_buffer refbuf)
+{
+  TEST_VERIFY (!alloc_buffer_has_failed (&refbuf));
+  TEST_VERIFY (alloc_buffer_size (&refbuf) == 1);
+  test_empty_array (refbuf);
+  test_impossible_array (refbuf);
+
+  /* Success adding a single byte.  */
+  {
+    struct alloc_buffer buf = refbuf;
+    alloc_buffer_add_byte (&buf, 17);
+    TEST_VERIFY (!alloc_buffer_has_failed (&buf));
+    test_empty (buf);
+  }
+  TEST_VERIFY (memcmp (alloc_buffer_next (&refbuf, void), "\x11", 1) == 0);
+  {
+    struct alloc_buffer buf = refbuf;
+    signed char *ptr = alloc_buffer_alloc (&buf, signed char);
+    TEST_VERIFY_EXIT (ptr != NULL);
+    TEST_VERIFY (!alloc_buffer_has_failed (&buf));
+    *ptr = 126;
+    test_empty (buf);
+  }
+  TEST_VERIFY (memcmp (alloc_buffer_next (&refbuf, void), "\176", 1) == 0);
+  {
+    struct alloc_buffer buf = refbuf;
+    char *ptr = alloc_buffer_alloc_array (&buf, char, 1);
+    TEST_VERIFY_EXIT (ptr != NULL);
+    TEST_VERIFY (!alloc_buffer_has_failed (&buf));
+    *ptr = (char) 253;
+    test_empty (buf);
+  }
+  TEST_VERIFY (memcmp (alloc_buffer_next (&refbuf, void), "\xfd", 1) == 0);
+
+  /* Failure with larger objects.  */
+  {
+    struct alloc_buffer buf = refbuf;
+    TEST_VERIFY (alloc_buffer_alloc (&buf, short) == NULL);
+    test_after_failure (buf);
+  }
+  {
+    struct alloc_buffer buf = refbuf;
+    TEST_VERIFY (alloc_buffer_alloc (&buf, double) == NULL);
+    test_after_failure (buf);
+  }
+  {
+    struct alloc_buffer buf = refbuf;
+    TEST_VERIFY (alloc_buffer_alloc (&buf, struct twelve) == NULL);
+    test_after_failure (buf);
+  }
+  {
+    struct alloc_buffer buf = refbuf;
+    TEST_VERIFY (alloc_buffer_alloc_array (&buf, short, 1) == NULL);
+    test_after_failure (buf);
+  }
+  {
+    struct alloc_buffer buf = refbuf;
+    TEST_VERIFY (alloc_buffer_alloc_array (&buf, double, 1) == NULL);
+    test_after_failure (buf);
+  }
+  {
+    struct alloc_buffer buf = refbuf;
+    TEST_VERIFY (alloc_buffer_alloc_array (&buf, struct twelve, 1) == NULL);
+    test_after_failure (buf);
+  }
+}
+
+static void
+test_size_2 (struct alloc_buffer refbuf)
+{
+  TEST_VERIFY (!alloc_buffer_has_failed (&refbuf));
+  TEST_VERIFY (alloc_buffer_size (&refbuf) == 2);
+  TEST_VERIFY (IS_ALIGNED (alloc_buffer_next (&refbuf, void), short));
+  test_empty_array (refbuf);
+  test_impossible_array (refbuf);
+
+  /* Success adding two bytes.  */
+  {
+    struct alloc_buffer buf = refbuf;
+    alloc_buffer_add_byte (&buf, '@');
+    TEST_VERIFY (!alloc_buffer_has_failed (&buf));
+    test_size_1 (buf);
+  }
+  TEST_VERIFY (memcmp (alloc_buffer_next (&refbuf, void), "@\xfd", 2) == 0);
+  {
+    struct alloc_buffer buf = refbuf;
+    signed char *ptr = alloc_buffer_alloc (&buf, signed char);
+    TEST_VERIFY_EXIT (ptr != NULL);
+    TEST_VERIFY (!alloc_buffer_has_failed (&buf));
+    *ptr = 'A';
+    test_size_1 (buf);
+  }
+  TEST_VERIFY (memcmp (alloc_buffer_next (&refbuf, void), "A\xfd", 2) == 0);
+  {
+    struct alloc_buffer buf = refbuf;
+    char *ptr = alloc_buffer_alloc_array (&buf, char, 1);
+    TEST_VERIFY_EXIT (ptr != NULL);
+    TEST_VERIFY (!alloc_buffer_has_failed (&buf));
+    *ptr = 'B';
+    test_size_1 (buf);
+  }
+  TEST_VERIFY (memcmp (alloc_buffer_next (&refbuf, void), "B\xfd", 2) == 0);
+  {
+    struct alloc_buffer buf = refbuf;
+    unsigned short *ptr = alloc_buffer_alloc (&buf, unsigned short);
+    TEST_VERIFY_EXIT (ptr != NULL);
+    TEST_VERIFY (IS_ALIGNED (ptr, unsigned short));
+    TEST_VERIFY (!alloc_buffer_has_failed (&buf));
+    *ptr = htons (0x12f4);
+    test_empty (buf);
+  }
+  TEST_VERIFY (memcmp (alloc_buffer_next (&refbuf, void), "\x12\xf4", 2) == 0);
+  {
+    struct alloc_buffer buf = refbuf;
+    unsigned short *ptr = alloc_buffer_alloc_array (&buf, unsigned short, 1);
+    TEST_VERIFY_EXIT (ptr != NULL);
+    TEST_VERIFY (IS_ALIGNED (ptr, unsigned short));
+    TEST_VERIFY (!alloc_buffer_has_failed (&buf));
+    *ptr = htons (0x13f5);
+    test_empty (buf);
+  }
+  TEST_VERIFY (memcmp (alloc_buffer_next (&refbuf, void), "\x13\xf5", 2) == 0);
+  {
+    struct alloc_buffer buf = refbuf;
+    char *ptr = alloc_buffer_alloc_array (&buf, char, 2);
+    TEST_VERIFY_EXIT (ptr != NULL);
+    TEST_VERIFY (!alloc_buffer_has_failed (&buf));
+    memcpy (ptr, "12", 2);
+    test_empty (buf);
+  }
+  TEST_VERIFY (memcmp (alloc_buffer_next (&refbuf, void), "12", 2) == 0);
+}
+
+static void
+test_misaligned (char pad)
+{
+  enum { SIZE = 23 };
+  char *backing = xmalloc (SIZE + 2);
+  backing[0] = ~pad;
+  backing[SIZE + 1] = pad;
+  struct alloc_buffer refbuf = alloc_buffer_create (backing + 1, SIZE);
+
+  {
+    struct alloc_buffer buf = refbuf;
+    short *ptr = alloc_buffer_alloc_array (&buf, short, SIZE / sizeof (short));
+    TEST_VERIFY_EXIT (ptr != NULL);
+    TEST_VERIFY (IS_ALIGNED (ptr, short));
+    TEST_VERIFY (!alloc_buffer_has_failed (&buf));
+    for (int i = 0; i < SIZE / sizeof (short); ++i)
+      ptr[i] = htons (0xff01 + i);
+    TEST_VERIFY (memcmp (ptr,
+                         "\xff\x01\xff\x02\xff\x03\xff\x04"
+                         "\xff\x05\xff\x06\xff\x07\xff\x08"
+                         "\xff\x09\xff\x0a\xff\x0b", 22) == 0);
+  }
+  {
+    struct alloc_buffer buf = refbuf;
+    uint32_t *ptr = alloc_buffer_alloc_array
+      (&buf, uint32_t, SIZE / sizeof (uint32_t));
+    TEST_VERIFY_EXIT (ptr != NULL);
+    TEST_VERIFY (IS_ALIGNED (ptr, uint32_t));
+    TEST_VERIFY (!alloc_buffer_has_failed (&buf));
+    for (int i = 0; i < SIZE / sizeof (uint32_t); ++i)
+      ptr[i] = htonl (0xf1e2d301 + i);
+    TEST_VERIFY (memcmp (ptr,
+                         "\xf1\xe2\xd3\x01\xf1\xe2\xd3\x02"
+                         "\xf1\xe2\xd3\x03\xf1\xe2\xd3\x04"
+                         "\xf1\xe2\xd3\x05", 20) == 0);
+  }
+  {
+    struct alloc_buffer buf = refbuf;
+    struct twelve *ptr = alloc_buffer_alloc (&buf, struct twelve);
+    TEST_VERIFY_EXIT (ptr != NULL);
+    TEST_VERIFY (IS_ALIGNED (ptr, struct twelve));
+    TEST_VERIFY (!alloc_buffer_has_failed (&buf));
+    ptr->buffer[0] = htonl (0x11223344);
+    ptr->buffer[1] = htonl (0x55667788);
+    ptr->buffer[2] = htonl (0x99aabbcc);
+    TEST_VERIFY (memcmp (ptr,
+                         "\x11\x22\x33\x44"
+                         "\x55\x66\x77\x88"
+                         "\x99\xaa\xbb\xcc", 12) == 0);
+  }
+  {
+    static const double nums[] = { 1, 2 };
+    struct alloc_buffer buf = refbuf;
+    double *ptr = alloc_buffer_alloc_array (&buf, double, 2);
+    TEST_VERIFY_EXIT (ptr != NULL);
+    TEST_VERIFY (IS_ALIGNED (ptr, double));
+    TEST_VERIFY (!alloc_buffer_has_failed (&buf));
+    ptr[0] = nums[0];
+    ptr[1] = nums[1];
+    TEST_VERIFY (memcmp (ptr, nums, sizeof (nums)) == 0);
+  }
+
+  /* Verify that padding was not overwritten.  */
+  TEST_VERIFY (backing[0] == ~pad);
+  TEST_VERIFY (backing[SIZE + 1] == pad);
+  free (backing);
+}
+
+/* Check that overflow during alignment is handled properly.  */
+static void
+test_large_misaligned (void)
+{
+  uintptr_t minus1 = -1;
+  uintptr_t start = minus1 & ~0xfe;
+  struct alloc_buffer refbuf = alloc_buffer_create ((void *) start, 16);
+  TEST_VERIFY (!alloc_buffer_has_failed (&refbuf));
+
+  struct __attribute__ ((aligned (256))) align256
+  {
+    int dymmy;
+  };
+
+  {
+    struct alloc_buffer buf = refbuf;
+    TEST_VERIFY (alloc_buffer_alloc (&buf, struct align256) == NULL);
+    test_after_failure (buf);
+  }
+  for (int count = 0; count < 3; ++count)
+    {
+      struct alloc_buffer buf = refbuf;
+      TEST_VERIFY (alloc_buffer_alloc_array (&buf, struct align256, count)
+                   == NULL);
+      test_after_failure (buf);
+    }
+}
+
+/* Check behavior of large allocations.  */
+static void
+test_large (void)
+{
+  {
+    /* Allocation which wraps around.  */
+    struct alloc_buffer buf = { 1, SIZE_MAX };
+    TEST_VERIFY (alloc_buffer_alloc_array (&buf, char, SIZE_MAX) == NULL);
+    TEST_VERIFY (alloc_buffer_has_failed (&buf));
+  }
+
+  {
+    /* Successful very large allocation.  */
+    struct alloc_buffer buf = { 1, SIZE_MAX };
+    uintptr_t val = (uintptr_t) alloc_buffer_alloc_array
+      (&buf, char, SIZE_MAX - 1);
+    TEST_VERIFY (val == 1);
+    TEST_VERIFY (!alloc_buffer_has_failed (&buf));
+    test_empty (buf);
+  }
+
+  {
+    typedef char __attribute__ ((aligned (2))) char2;
+
+    /* Overflow in array size computation.   */
+    struct alloc_buffer buf = { 1, SIZE_MAX };
+    TEST_VERIFY (alloc_buffer_alloc_array (&buf, char2, SIZE_MAX - 1) == NULL);
+    TEST_VERIFY (alloc_buffer_has_failed (&buf));
+
+    /* Successful allocation after alignment.  */
+    buf = (struct alloc_buffer) { 1, SIZE_MAX };
+    uintptr_t val = (uintptr_t) alloc_buffer_alloc_array
+      (&buf, char2, SIZE_MAX - 2);
+    TEST_VERIFY (val == 2);
+    test_empty (buf);
+
+    /* Alignment behavior near the top of the address space.  */
+    buf = (struct alloc_buffer) { SIZE_MAX, SIZE_MAX };
+    TEST_VERIFY (alloc_buffer_next (&buf, char2) == NULL);
+    TEST_VERIFY (alloc_buffer_has_failed (&buf));
+    buf = (struct alloc_buffer) { SIZE_MAX, SIZE_MAX };
+    TEST_VERIFY (alloc_buffer_alloc_array (&buf, char2, 0) == NULL);
+    TEST_VERIFY (alloc_buffer_has_failed (&buf));
+  }
+
+  {
+    typedef short __attribute__ ((aligned (2))) short2;
+
+    /* Test overflow in size computation.  */
+    struct alloc_buffer buf = { 1, SIZE_MAX };
+    TEST_VERIFY (alloc_buffer_alloc_array (&buf, short2, SIZE_MAX / 2)
+                 == NULL);
+    TEST_VERIFY (alloc_buffer_has_failed (&buf));
+
+    /* A slightly smaller array fits within the allocation.  */
+    buf = (struct alloc_buffer) { 2, SIZE_MAX - 1 };
+    uintptr_t val = (uintptr_t) alloc_buffer_alloc_array
+      (&buf, short2, SIZE_MAX / 2 - 1);
+    TEST_VERIFY (val == 2);
+    test_empty (buf);
+  }
+}
+
+static void
+test_copy_bytes (void)
+{
+  char backing[4];
+  {
+    memset (backing, '@', sizeof (backing));
+    struct alloc_buffer buf = alloc_buffer_create (backing, sizeof (backing));
+    alloc_buffer_copy_bytes (&buf, "1", 1);
+    TEST_VERIFY (!alloc_buffer_has_failed (&buf));
+    TEST_VERIFY (alloc_buffer_size (&buf) == 3);
+    TEST_VERIFY (memcmp (backing, "1@@@", 4) == 0);
+  }
+  {
+    memset (backing, '@', sizeof (backing));
+    struct alloc_buffer buf = alloc_buffer_create (backing, sizeof (backing));
+    alloc_buffer_copy_bytes (&buf, "12", 3);
+    TEST_VERIFY (!alloc_buffer_has_failed (&buf));
+    TEST_VERIFY (alloc_buffer_size (&buf) == 1);
+    TEST_VERIFY (memcmp (backing, "12\0@", 4) == 0);
+  }
+  {
+    memset (backing, '@', sizeof (backing));
+    struct alloc_buffer buf = alloc_buffer_create (backing, sizeof (backing));
+    alloc_buffer_copy_bytes (&buf, "1234", 4);
+    TEST_VERIFY (!alloc_buffer_has_failed (&buf));
+    TEST_VERIFY (alloc_buffer_size (&buf) == 0);
+    TEST_VERIFY (memcmp (backing, "1234", 4) == 0);
+  }
+  {
+    memset (backing, '@', sizeof (backing));
+    struct alloc_buffer buf = alloc_buffer_create (backing, sizeof (backing));
+    alloc_buffer_copy_bytes (&buf, "1234", 5);
+    TEST_VERIFY (alloc_buffer_has_failed (&buf));
+    TEST_VERIFY (memcmp (backing, "@@@@", 4) == 0);
+  }
+  {
+    memset (backing, '@', sizeof (backing));
+    struct alloc_buffer buf = alloc_buffer_create (backing, sizeof (backing));
+    alloc_buffer_copy_bytes (&buf, "1234", -1);
+    TEST_VERIFY (alloc_buffer_has_failed (&buf));
+    TEST_VERIFY (memcmp (backing, "@@@@", 4) == 0);
+  }
+}
+
+static void
+test_copy_string (void)
+{
+  char backing[4];
+  {
+    memset (backing, '@', sizeof (backing));
+    struct alloc_buffer buf = alloc_buffer_create (backing, sizeof (backing));
+    const char *p = alloc_buffer_copy_string (&buf, "");
+    TEST_VERIFY (p == backing);
+    TEST_VERIFY (strcmp (p, "") == 0);
+    TEST_VERIFY (!alloc_buffer_has_failed (&buf));
+    TEST_VERIFY (alloc_buffer_size (&buf) == 3);
+    TEST_VERIFY (memcmp (backing, "\0@@@", 4) == 0);
+  }
+  {
+    memset (backing, '@', sizeof (backing));
+    struct alloc_buffer buf = alloc_buffer_create (backing, sizeof (backing));
+    const char *p = alloc_buffer_copy_string (&buf, "1");
+    TEST_VERIFY (p == backing);
+    TEST_VERIFY (strcmp (p, "1") == 0);
+    TEST_VERIFY (!alloc_buffer_has_failed (&buf));
+    TEST_VERIFY (alloc_buffer_size (&buf) == 2);
+    TEST_VERIFY (memcmp (backing, "1\0@@", 4) == 0);
+  }
+  {
+    memset (backing, '@', sizeof (backing));
+    struct alloc_buffer buf = alloc_buffer_create (backing, sizeof (backing));
+    const char *p = alloc_buffer_copy_string (&buf, "12");
+    TEST_VERIFY (p == backing);
+    TEST_VERIFY (strcmp (p, "12") == 0);
+    TEST_VERIFY (!alloc_buffer_has_failed (&buf));
+    TEST_VERIFY (alloc_buffer_size (&buf) == 1);
+    TEST_VERIFY (memcmp (backing, "12\0@", 4) == 0);
+  }
+  {
+    memset (backing, '@', sizeof (backing));
+    struct alloc_buffer buf = alloc_buffer_create (backing, sizeof (backing));
+    const char *p = alloc_buffer_copy_string (&buf, "123");
+    TEST_VERIFY (p == backing);
+    TEST_VERIFY (strcmp (p, "123") == 0);
+    TEST_VERIFY (!alloc_buffer_has_failed (&buf));
+    TEST_VERIFY (alloc_buffer_size (&buf) == 0);
+    TEST_VERIFY (memcmp (backing, "123", 4) == 0);
+  }
+  {
+    memset (backing, '@', sizeof (backing));
+    struct alloc_buffer buf = alloc_buffer_create (backing, sizeof (backing));
+    TEST_VERIFY (alloc_buffer_copy_string (&buf, "1234") == NULL);
+    TEST_VERIFY (alloc_buffer_has_failed (&buf));
+    TEST_VERIFY (memcmp (backing, "@@@@", 4) == 0);
+  }
+  {
+    memset (backing, '@', sizeof (backing));
+    struct alloc_buffer buf = alloc_buffer_create (backing, sizeof (backing));
+    TEST_VERIFY (alloc_buffer_copy_string (&buf, "12345") == NULL);
+    TEST_VERIFY (alloc_buffer_has_failed (&buf));
+    TEST_VERIFY (memcmp (backing, "@@@@", 4) == 0);
+  }
+}
+
+static int
+do_test (void)
+{
+  test_empty (alloc_buffer_create (NULL, 0));
+  test_empty (alloc_buffer_create ((char *) "", 0));
+  test_empty (alloc_buffer_create ((void *) 1, 0));
+
+  {
+    struct alloc_buffer buf = alloc_buffer_allocate (1);
+    test_size_1 (buf);
+    alloc_buffer_free (&buf);
+  }
+
+  {
+    struct alloc_buffer buf = alloc_buffer_allocate (2);
+    test_size_2 (buf);
+    alloc_buffer_free (&buf);
+  }
+
+  test_misaligned (0);
+  test_misaligned (0xc7);
+  test_misaligned (0xff);
+
+  test_large_misaligned ();
+  test_large ();
+  test_copy_bytes ();
+  test_copy_string ();
+
+  return 0;
+}
+
+#include <support/test-driver.c>
diff --git a/resolv/Versions b/resolv/Versions
index e561bce..aea43d4 100644
--- a/resolv/Versions
+++ b/resolv/Versions
@@ -79,6 +79,7 @@  libresolv {
     __ns_name_unpack; __ns_name_ntop;
     __ns_get16; __ns_get32;
     __libc_res_nquery; __libc_res_nsearch;
+    __ns_name_unpack_buffer; __ns_name_ntop_buffer;
   }
 }
 
diff --git a/resolv/ns_name.c b/resolv/ns_name.c
index 08a75e2..4cbb07d 100644
--- a/resolv/ns_name.c
+++ b/resolv/ns_name.c
@@ -1,3 +1,21 @@ 
+/* DNS name processing functions.
+ * Copyright (C) 1999-2017 Free Software Foundation, Inc.
+ * This file is part of the GNU C Library.
+ *
+ * The GNU C Library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * The GNU C Library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with the GNU C Library; if not, see
+ * <http://www.gnu.org/licenses/>.  */
+
 /*
  * Copyright (c) 2004 by Internet Systems Consortium, Inc. ("ISC")
  * Copyright (c) 1996,1999 by Internet Software Consortium.
@@ -19,6 +37,7 @@ 
 
 #include <netinet/in.h>
 #include <arpa/nameser.h>
+#include <resolv/resolv-internal.h>
 
 #include <errno.h>
 #include <resolv.h>
@@ -26,6 +45,7 @@ 
 #include <ctype.h>
 #include <stdlib.h>
 #include <limits.h>
+#include <alloc_buffer.h>
 
 # define SPRINTF(x) ((size_t)sprintf x)
 
@@ -44,90 +64,94 @@  static int		labellen(const u_char *);
 
 /* Public. */
 
-/*%
- *	Convert an encoded domain name to printable ascii as per RFC1035.
+const char *
+__ns_name_ntop_buffer (struct alloc_buffer *pdst,
+		       const unsigned char *src)
+{
+  /* Make copy to help with aliasing analysis.  */
+  struct alloc_buffer dst = *pdst;
+  bool first = true;
+  while (true)
+    {
+      unsigned char n = *src;
+      ++src;
+      if (n == 0)
+	/* End of domain name.  */
+	break;
+      if (n > 63)
+	{
+	  /* Some kind of compression pointer.  This means that the
+	     name has not been unpacked.  */
+	  alloc_buffer_mark_failed (&dst);
+	  break;
+	}
+
+      /* Separate subsequent labels from their predecessor.  */
+      if (first)
+	first = false;
+      else
+	alloc_buffer_add_byte (&dst, '.');
+
+      for (int i = 0; i < n; ++i)
+	{
+	  unsigned char c = *src;
+	  ++src;
+	  if (special (c))
+	    {
+	      /* Non-decimal escape.  */
+	      char *p = alloc_buffer_alloc_bytes (&dst, 2);
+	      if (p == NULL)
+		{
+		  alloc_buffer_mark_failed (&dst);
+		  break;
+		}
+	      p[0] = '\\';
+	      p[1] = c;
+	    }
+	  else if (!printable (c))
+	    {
+	      /* Decimal escape.  */
+	      char *p = alloc_buffer_alloc_bytes (&dst, 4);
+	      if (p == NULL)
+		{
+		  alloc_buffer_mark_failed (pdst);
+		  break;
+		}
+	      p[0] = '\\';
+	      p[1] = '0' + (c / 100);
+	      p[2] = '0' + ((c % 100) / 10);
+	      p[3] = '0' + (c % 10);
+	    }
+	  else
+	    /* Regular character.  */
+	    alloc_buffer_add_byte (&dst, c);
+	}
+    }
+  if (first)
+    /* Root domain.  */
+    alloc_buffer_add_byte (&dst, '.');
+  alloc_buffer_add_byte (&dst, '\0');
+  if (alloc_buffer_has_failed (&dst))
+    {
+      alloc_buffer_mark_failed (pdst);
+      return NULL;
+    }
+  const char *start = alloc_buffer_next (&dst, char);
+  *pdst = dst;
+  return start;
+}
+libresolv_hidden_def (__ns_name_ntop_buffer)
 
- * return:
- *\li	Number of bytes written to buffer, or -1 (with errno set)
- *
- * notes:
- *\li	The root is returned as "."
- *\li	All other domains are returned in non absolute form
- */
 int
-ns_name_ntop(const u_char *src, char *dst, size_t dstsiz)
+ns_name_ntop (const u_char *src, char *dst, size_t dstsiz)
 {
-	const u_char *cp;
-	char *dn, *eom;
-	u_char c;
-	u_int n;
-	int l;
-
-	cp = src;
-	dn = dst;
-	eom = dst + dstsiz;
-
-	while ((n = *cp++) != 0) {
-		if ((n & NS_CMPRSFLGS) == NS_CMPRSFLGS) {
-			/* Some kind of compression pointer. */
-			__set_errno (EMSGSIZE);
-			return (-1);
-		}
-		if (dn != dst) {
-			if (dn >= eom) {
-				__set_errno (EMSGSIZE);
-				return (-1);
-			}
-			*dn++ = '.';
-		}
-		if ((l = labellen(cp - 1)) < 0) {
-			__set_errno (EMSGSIZE);
-			return(-1);
-		}
-		if (dn + l >= eom) {
-			__set_errno (EMSGSIZE);
-			return (-1);
-		}
-		for ((void)NULL; l > 0; l--) {
-			c = *cp++;
-			if (special(c)) {
-				if (dn + 1 >= eom) {
-					__set_errno (EMSGSIZE);
-					return (-1);
-				}
-				*dn++ = '\\';
-				*dn++ = (char)c;
-			} else if (!printable(c)) {
-				if (dn + 3 >= eom) {
-					__set_errno (EMSGSIZE);
-					return (-1);
-				}
-				*dn++ = '\\';
-				*dn++ = digits[c / 100];
-				*dn++ = digits[(c % 100) / 10];
-				*dn++ = digits[c % 10];
-			} else {
-				if (dn >= eom) {
-					__set_errno (EMSGSIZE);
-					return (-1);
-				}
-				*dn++ = (char)c;
-			}
-		}
-	}
-	if (dn == dst) {
-		if (dn >= eom) {
-			__set_errno (EMSGSIZE);
-			return (-1);
-		}
-		*dn++ = '.';
-	}
-	if (dn >= eom) {
-		__set_errno (EMSGSIZE);
-		return (-1);
-	}
-	*dn++ = '\0';
-	return (dn - dst);
+  struct alloc_buffer buf = alloc_buffer_create (dst, dstsiz);
+  if (__ns_name_ntop_buffer (&buf, src) == NULL)
+    {
+      __set_errno (EMSGSIZE);
+      return -1;
+    }
+  return alloc_buffer_next (&buf, void) - (const void *) dst;
 }
 libresolv_hidden_def (ns_name_ntop)
 strong_alias (ns_name_ntop, __ns_name_ntop)
@@ -301,6 +325,101 @@  ns_name_ntol(const u_char *src, u_char *dst, size_t dstsiz)
 	return (dn - dst);
 }
 
+size_t
+__ns_name_unpack_buffer (const unsigned char *msg, const unsigned char *eom,
+			 const unsigned char *src, struct alloc_buffer *pdst)
+{
+  const unsigned char *initial_src = src;
+  size_t consumed = 0;
+  struct alloc_buffer dst = *pdst;
+  if (src < msg || src >= eom)
+    alloc_buffer_mark_failed (&dst);
+  else
+    {
+      size_t packet_size = eom - msg;
+      /* Count the number of bytes processed by the loop below.  If we
+	 have covered the whole packet, this means we have a
+	 compression loop.  */
+      size_t processed = 0;
+      while (true)
+	{
+	  if (src == eom)
+	    {
+	      /* Missing NUL byte at end of domain name.  */
+	      alloc_buffer_mark_failed (&dst);
+	      break;
+	    }
+
+	  unsigned char b = *src;
+	  ++src;
+	  if (b == 0)
+	    /* End of domain name.  */
+	    {
+	      alloc_buffer_add_byte (&dst, '\0');
+	      if (consumed == 0)
+		consumed = src - initial_src;
+	      break;
+	    }
+	  else if (b <= 63)
+	    {
+	      /* Regular label.  */
+	      size_t remaining = eom - src;
+	      if (b > remaining)
+		{
+		  /* Input domain name is truncated.  */
+		  alloc_buffer_mark_failed (&dst);
+		  break;
+		}
+	      /* Include label length in copy.  */
+	      alloc_buffer_copy_bytes (&dst, src - 1, 1 + b);
+	      src += b;
+	      processed += 1 + b;
+	    }
+	  else if ((b & NS_CMPRSFLGS) == NS_CMPRSFLGS && src < eom)
+	    {
+	      /* Compression reference.  */
+	      unsigned char b2 = *src;
+	      unsigned offset = (b - NS_CMPRSFLGS) * 256 + b2;
+	      if (offset >= packet_size)
+		{
+		  /* Out-of-range compression reference.  */
+		  alloc_buffer_mark_failed (&dst);
+		  break;
+		}
+
+	      /* Record the position after the first compression
+		 reference.  */
+	      if (consumed == 0)
+		consumed = src + 1 - initial_src;
+
+	      /* Seek to the new position in the packet.  */
+	      src = msg + offset;
+
+	      /* Compression loop detection.  Account for the two
+		 bytes in the compression reference.  */
+	      processed += 2;
+	      if (processed >= packet_size)
+		{
+		  /* There is a compression loop.  */
+		  alloc_buffer_mark_failed (&dst);
+		  break;
+		}
+	    }
+	  else
+	    {
+	      /* Invalid label length, or truncated compression
+		 reference.  */
+	      alloc_buffer_mark_failed (&dst);
+	      break;
+	    }
+	}
+    }
+  if (alloc_buffer_has_failed (&dst))
+    consumed = 0;
+  *pdst = dst;
+  return consumed;
+}
+
 /*%
  *	Unpack a domain name from a message, source may be compressed.
  *
@@ -311,73 +430,14 @@  int
 ns_name_unpack(const u_char *msg, const u_char *eom, const u_char *src,
 	       u_char *dst, size_t dstsiz)
 {
-	const u_char *srcp, *dstlim;
-	u_char *dstp;
-	int n, len, checked, l;
-
-	len = -1;
-	checked = 0;
-	dstp = dst;
-	srcp = src;
-	dstlim = dst + dstsiz;
-	if (srcp < msg || srcp >= eom) {
-		__set_errno (EMSGSIZE);
-		return (-1);
-	}
-	/* Fetch next label in domain name. */
-	while ((n = *srcp++) != 0) {
-		/* Check for indirection. */
-		switch (n & NS_CMPRSFLGS) {
-		case 0:
-			/* Limit checks. */
-			if ((l = labellen(srcp - 1)) < 0) {
-				__set_errno (EMSGSIZE);
-				return(-1);
-			}
-			if (dstp + l + 1 >= dstlim || srcp + l >= eom) {
-				__set_errno (EMSGSIZE);
-				return (-1);
-			}
-			checked += l + 1;
-			*dstp++ = n;
-			memcpy(dstp, srcp, l);
-			dstp += l;
-			srcp += l;
-			break;
-
-		case NS_CMPRSFLGS:
-			if (srcp >= eom) {
-				__set_errno (EMSGSIZE);
-				return (-1);
-			}
-			if (len < 0)
-				len = srcp - src + 1;
-			srcp = msg + (((n & 0x3f) << 8) | (*srcp & 0xff));
-			if (srcp < msg || srcp >= eom) {  /*%< Out of range. */
-				__set_errno (EMSGSIZE);
-				return (-1);
-			}
-			checked += 2;
-			/*
-			 * Check for loops in the compressed name;
-			 * if we've looked at the whole message,
-			 * there must be a loop.
-			 */
-			if (checked >= eom - msg) {
-				__set_errno (EMSGSIZE);
-				return (-1);
-			}
-			break;
-
-		default:
-			__set_errno (EMSGSIZE);
-			return (-1);			/*%< flag error */
-		}
-	}
-	*dstp = '\0';
-	if (len < 0)
-		len = srcp - src;
-	return (len);
+  struct alloc_buffer buf = alloc_buffer_create (dst, dstsiz);
+  size_t consumed = __ns_name_unpack_buffer (msg, eom, src, &buf);
+  if (alloc_buffer_has_failed (&buf) || consumed > INT_MAX)
+    {
+      __set_errno (EMSGSIZE);
+      return -1;
+    }
+  return consumed;
 }
 libresolv_hidden_def (ns_name_unpack)
 strong_alias (ns_name_unpack, __ns_name_unpack)
diff --git a/resolv/nss_dns/dns-network.c b/resolv/nss_dns/dns-network.c
index 2be72d3..3c0ed82 100644
--- a/resolv/nss_dns/dns-network.c
+++ b/resolv/nss_dns/dns-network.c
@@ -67,6 +67,8 @@ 
 #include "nsswitch.h"
 #include <arpa/inet.h>
 #include <arpa/nameser.h>
+#include <alloc_buffer.h>
+#include <resolv/resolv-internal.h>
 
 /* Maximum number of aliases we allow.  */
 #define MAX_NR_ALIASES	48
@@ -95,8 +97,8 @@  typedef union querybuf
 
 /* Prototypes for local functions.  */
 static enum nss_status getanswer_r (const querybuf *answer, int anslen,
-				    struct netent *result, char *buffer,
-				    size_t buflen, int *errnop, int *h_errnop,
+				    struct netent *result, struct alloc_buffer,
+				    int *errnop, int *h_errnop,
 				    lookup_method net_i);
 
 
@@ -134,7 +136,8 @@  _nss_dns_getnetbyname_r (const char *name, struct netent *result,
 	? NSS_STATUS_UNAVAIL : NSS_STATUS_NOTFOUND;
     }
 
-  status = getanswer_r (net_buffer.buf, anslen, result, buffer, buflen,
+  status = getanswer_r (net_buffer.buf, anslen, result,
+			alloc_buffer_create (buffer, buflen),
 			errnop, herrnop, BYNAME);
   if (net_buffer.buf != orig_net_buffer)
     free (net_buffer.buf);
@@ -211,7 +214,8 @@  _nss_dns_getnetbyaddr_r (uint32_t net, int type, struct netent *result,
 	? NSS_STATUS_UNAVAIL : NSS_STATUS_NOTFOUND;
     }
 
-  status = getanswer_r (net_buffer.buf, anslen, result, buffer, buflen,
+  status = getanswer_r (net_buffer.buf, anslen, result,
+			alloc_buffer_create (buffer, buflen),
 			errnop, herrnop, BYADDR);
   if (net_buffer.buf != orig_net_buffer)
     free (net_buffer.buf);
@@ -231,7 +235,7 @@  _nss_dns_getnetbyaddr_r (uint32_t net, int type, struct netent *result,
 
 static enum nss_status
 getanswer_r (const querybuf *answer, int anslen, struct netent *result,
-	     char *buffer, size_t buflen, int *errnop, int *h_errnop,
+	     struct alloc_buffer buf, int *errnop, int *h_errnop,
 	     lookup_method net_i)
 {
   /*
@@ -248,16 +252,9 @@  getanswer_r (const querybuf *answer, int anslen, struct netent *result,
    *                 | Additional | RRs holding additional information
    *                 +------------+
    */
-  struct net_data
-  {
-    char *aliases[MAX_NR_ALIASES];
-    char linebuffer[0];
-  } *net_data;
 
-  uintptr_t pad = -(uintptr_t) buffer % __alignof__ (struct net_data);
-  buffer += pad;
-
-  if (__glibc_unlikely (buflen < sizeof (*net_data) + pad))
+  char **aliases = alloc_buffer_alloc_array (&buf, char *, MAX_NR_ALIASES);
+  if (__glibc_unlikely (aliases == NULL))
     {
       /* The buffer is too small.  */
     too_small:
@@ -265,22 +262,15 @@  getanswer_r (const querybuf *answer, int anslen, struct netent *result,
       *h_errnop = NETDB_INTERNAL;
       return NSS_STATUS_TRYAGAIN;
     }
-  buflen -= pad;
+  int alias_count = 0;
 
-  net_data = (struct net_data *) buffer;
-  int linebuflen = buflen - offsetof (struct net_data, linebuffer);
-  if (buflen - offsetof (struct net_data, linebuffer) != linebuflen)
-    linebuflen = INT_MAX;
   const unsigned char *end_of_message = &answer->buf[anslen];
   const HEADER *header_pointer = &answer->hdr;
   /* #/records in the answer section.  */
   int answer_count =  ntohs (header_pointer->ancount);
   /* #/entries in the question section.  */
   int question_count = ntohs (header_pointer->qdcount);
-  char *bp = net_data->linebuffer;
   const unsigned char *cp = &answer->buf[HFIXEDSZ];
-  char **alias_pointer;
-  int have_answer;
   u_char packtmp[NS_MAXCDNAME];
 
   if (question_count == 0)
@@ -312,29 +302,14 @@  getanswer_r (const querybuf *answer, int anslen, struct netent *result,
       cp += n + QFIXEDSZ;
     }
 
-  alias_pointer = result->n_aliases = &net_data->aliases[0];
-  *alias_pointer = NULL;
-  have_answer = 0;
-
   while (--answer_count >= 0 && cp < end_of_message)
     {
       int n = __ns_name_unpack (answer->buf, end_of_message, cp,
 				packtmp, sizeof packtmp);
-      if (n != -1 && __ns_name_ntop (packtmp, bp, linebuflen) == -1)
-	{
-	  if (errno == EMSGSIZE)
-	    goto too_small;
-
-	  n = -1;
-	}
-
-      if (n > 0 && bp[0] == '.')
-	bp[0] = '\0';
-
-      if (n < 0 || res_dnok (bp) == 0)
+      if (n == -1)
 	break;
+
       cp += n;
-
       if (end_of_message - cp < 10)
 	{
 	  __set_h_errno (NO_RECOVERY);
@@ -357,7 +332,10 @@  getanswer_r (const querybuf *answer, int anslen, struct netent *result,
 	{
 	  n = __ns_name_unpack (answer->buf, end_of_message, cp,
 				packtmp, sizeof packtmp);
-	  if (n != -1 && __ns_name_ntop (packtmp, bp, linebuflen) == -1)
+	  char *alias = NULL;
+	  if (n != -1
+	      && ((alias = (char *) __ns_name_ntop_buffer (&buf, packtmp))
+		  == NULL))
 	    {
 	      if (errno == EMSGSIZE)
 		goto too_small;
@@ -365,7 +343,7 @@  getanswer_r (const querybuf *answer, int anslen, struct netent *result,
 	      n = -1;
 	    }
 
-	  if (n < 0 || !res_hnok (bp))
+	  if (n < 0 || !res_hnok (alias))
 	    {
 	      /* XXX What does this mean?  The original form from bind
 		 returns NULL. Incrementing cp has no effect in any case.
@@ -374,35 +352,33 @@  getanswer_r (const querybuf *answer, int anslen, struct netent *result,
 	      return NSS_STATUS_UNAVAIL;
 	    }
 	  cp += rdatalen;
-         if (alias_pointer + 2 < &net_data->aliases[MAX_NR_ALIASES])
-           {
-             *alias_pointer++ = bp;
-             n = strlen (bp) + 1;
-             bp += n;
-             linebuflen -= n;
-             result->n_addrtype = class == C_IN ? AF_INET : AF_UNSPEC;
-             ++have_answer;
-           }
+	  if (alias_count + 1 < MAX_NR_ALIASES)
+	    {
+	      aliases[alias_count++] = alias;
+	      result->n_addrtype = class == C_IN ? AF_INET : AF_UNSPEC;
+	    }
 	}
       else
 	/* Skip over unknown record data.  */
 	cp += rdatalen;
     }
+  aliases[alias_count] = NULL;
 
-  if (have_answer)
+  if (alias_count > 0)
     {
-      *alias_pointer = NULL;
       switch (net_i)
 	{
 	case BYADDR:
-	  result->n_name = *result->n_aliases++;
+	  /* Use the first alias as the name.  */
+	  result->n_name = aliases[0];
+	  result->n_aliases = aliases + 1;
 	  result->n_net = 0L;
 	  return NSS_STATUS_SUCCESS;
 
 	case BYNAME:
 	  {
 	    char **ap;
-	    for (ap = result->n_aliases; *ap != NULL; ++ap)
+	    for (ap = aliases; *ap != NULL; ++ap)
 	      {
 		/* Check each alias name for being of the forms:
 		   4.3.2.1.in-addr.arpa		= net 1.2.3.4
@@ -455,6 +431,8 @@  getanswer_r (const querybuf *answer, int anslen, struct netent *result,
 		       2. This is not the droid we are looking for.  */
 		    if (!isdigit (*p) && !strcasecmp (p, "in-addr.arpa"))
 		      {
+			result->n_name = aliases[0];
+			result->n_aliases = aliases;
 			result->n_net = val;
 			return NSS_STATUS_SUCCESS;
 		      }
diff --git a/resolv/resolv-internal.h b/resolv/resolv-internal.h
index 0d69ce1..f735a6b 100644
--- a/resolv/resolv-internal.h
+++ b/resolv/resolv-internal.h
@@ -56,4 +56,24 @@  enum
 int __res_nopt (res_state, int n0, unsigned char *buf, int buflen,
                 int anslen) attribute_hidden;
 
+struct alloc_buffer;
+
+/* Convert the expanded domain name at SRC from wire format to text
+   format.  Use storage in *DST.  Return a pointer to data in *DST, or
+   NULL on error (and put the allocation buffer into the failed
+   state).  */
+const char *__ns_name_ntop_buffer (struct alloc_buffer *, const u_char *)
+  __THROW;
+libresolv_hidden_proto (__ns_name_ntop_buffer)
+
+/* Decompress the domain name at SRC (within the DNS packet extending
+   from MSG to EOM) into the allocation buffer.  Returns the number of
+   bytes consumed from the input, or 0 on failure.  On failure, put
+   the allocation buffer into the failed state.  */
+size_t __ns_name_unpack_buffer (const unsigned char *msg,
+                                const unsigned char *eom,
+                                const unsigned char *src,
+                                struct alloc_buffer *) __THROW;
+libresolv_hidden_proto (__ns_name_unpack_buffer)
+
 #endif  /* _RESOLV_INTERNAL_H */