Allocation buffers for NSS result construction

Message ID 9533de1c-986a-7872-856d-0d3cc354356c@redhat.com
State Superseded
Headers

Commit Message

Florian Weimer June 21, 2017, 3:46 p.m. UTC
  On 06/16/2017 04:42 PM, Adhemerval Zanella wrote:

> 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.

Okay, continuing with the allocation buffers only for now

>> +/* 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?

What do you mean?
>> +/* 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?

I don't think it's worthwhile to do that because the memory range is
already invalid at this point.

>> +/* 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.

Yes, fixed in the attached patch.

>> +/* 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 *.

buf->__alloc_buffer_current is uinptr_t, to help with the alignment
operations.

>> +/* 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)?

Hmm.  I think the comparison idiom is sufficiently common to use it this
way.

>> +
>> +/* 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).

It would need C++ because TYPE is a type parameter.  Everything that can
be a function already is.

>> +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?

Good idea.

>> +  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?

It's a bit ugly, yes.  I should probably check for INT_MAX overflow, too.

In the attached patch, I changed the signature for alloc_buffer_allocate
and removed alloc_buffer_free because the latter encouraged incorrect
use.  Now, alloc_buffer_allocate produces a copy of the initial buffer
pointer which can be used directly with free.

Thanks,
Florian
  

Comments

Adhemerval Zanella June 21, 2017, 7:44 p.m. UTC | #1
On 21/06/2017 12:46, Florian Weimer wrote:
> On 06/16/2017 04:42 PM, Adhemerval Zanella wrote:
> 
>> 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.
> Okay, continuing with the allocation buffers only for now
> 
>>> +/* 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?
> What do you mean?

My mistake here, nvm.

>>> +/* 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?
> I don't think it's worthwhile to do that because the memory range is
> already invalid at this point.

Some functions like 'alloc_buffer_add_byte' does have a check to mark
the buffer failed, but 'alloc_buffer_size' will return a bogus value if
'start' plus 'size' overflows.  It might not get caught in
'alloc_buffer_alloc_bytes' for instance, where the 'length' can be
potentially lower than the returned value of 'alloc_buffer_size'
(since a signed ptrdiff_t will be converted to size_t).  Maybe to add
the same check from 'alloc_buffer_add_byte' might be suffice.

> 
>>> +/* 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.
> Yes, fixed in the attached patch.
> 
>>> +/* 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 *.
> buf->__alloc_buffer_current is uinptr_t, to help with the alignment
> operations.

Ack.

> 
>>> +/* 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)?
> Hmm.  I think the comparison idiom is sufficiently common to use it this
> way.

Indeed, but check_add_wrapv_size_t (or check_add_overflow_size_t as you
suggested) might use a GCC builtin for newer compiler (which might use
more optimized instructions).

> 
>>> +
>>> +/* 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).
> It would need C++ because TYPE is a type parameter.  Everything that can
> be a function already is.

Ack.

> 
>>> +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?
> Good idea.
> 
>>> +  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?
> It's a bit ugly, yes.  I should probably check for INT_MAX overflow, too.
> 
> In the attached patch, I changed the signature for alloc_buffer_allocate
> and removed alloc_buffer_free because the latter encouraged incorrect
> use.  Now, alloc_buffer_allocate produces a copy of the initial buffer
> pointer which can be used directly with free.

Ack.  The rest of the patch looks ok with 2 nits below.

> 
> 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).
> 
> 2017-04-22  Florian Weimer  <fweimer@redhat.com>
> 
> 	* malloc/Makefile (tests-internal): Add tst-alloc_buffer.
> 	(routines): Add alloc_buffer_alloc_array, alloc_buffer_allocate,
> 	alloc_buffer_copy_bytes, alloc_buffer_copy_string.
> 	* malloc/Versions (__libc_alloc_buffer_alloc_array)
> 	(__libc_alloc_buffer_allocate, __libc_alloc_buffer_copy_bytes)
> 	(__libc_alloc_buffer_copy_string): 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.
> 
> diff --git a/include/alloc_buffer.h b/include/alloc_buffer.h
> new file mode 100644
> index 0000000..298bd72
> --- /dev/null
> +++ b/include/alloc_buffer.h
> @@ -0,0 +1,358 @@
> +/* 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

s/recieve/receive

> +   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 = ...;
> +
> +     void *heap_ptr;
> +     struct alloc_buffer buf = alloc_buffer_allocate
> +       (double_array_size * sizeof (double) + int_array_size * sizeof (int),
> +        &heap_ptr);

I am not very found of this example because it does not check for
potentially overflow the size calculation.

> +     _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.
> +     ...
> +     free (heap_ptr);
> +
> +   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, void **pptr)
> +  __attribute__ ((nonnull (2)));
> +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.  *PPTR points to the start of
> +   the buffer and can be used to free it later, after the returned
> +   buffer has been freed.  */
> +static __always_inline __attribute__ ((nonnull (2)))
> +struct alloc_buffer alloc_buffer_allocate (size_t size, void **pptr)
> +{
> +  return __libc_alloc_buffer_allocate (size, pptr);
> +}
> +
> +/* Mark the buffer as failed.  */
> +static inline void __attribute__ ((nonnull (1)))
> +alloc_buffer_mark_failed (struct alloc_buffer *buf)
> +{
> +  buf->__alloc_buffer_current = __ALLOC_BUFFER_INVALID_POINTER;
> +  buf->__alloc_buffer_end = __ALLOC_BUFFER_INVALID_POINTER;
> +}
> +
> +/* Return the remaining number of bytes in the buffer.  */
> +static __always_inline __attribute__ ((nonnull (1))) 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 __attribute__ ((nonnull (1)))
> +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 __attribute__ ((nonnull (1)))
> +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 __attribute__ ((nonnull (1))) 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 __attribute__ ((nonnull (1))) 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 __attribute__ ((nonnull (1))) 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)
> +  __attribute__ ((nonnull (1)));
> +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)
> +  __attribute__ ((nonnull (2)));
> +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 __attribute__ ((nonnull (1, 2))) 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 *)
> +  __attribute__ ((nonnull (2)));
> +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 __attribute__ ((nonnull (1, 2))) 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;
> +}
> +
> +#endif /* _ALLOC_BUFFER_H */
> diff --git a/malloc/Makefile b/malloc/Makefile
> index 14c13f1..dd5d1dc 100644
> --- a/malloc/Makefile
> +++ b/malloc/Makefile
> @@ -39,7 +39,7 @@ tests-static := \
>  	 tst-interpose-static-thread \
>  	 tst-malloc-usable-static \
>  
> -tests-internal := tst-mallocstate tst-scratch_buffer
> +tests-internal := tst-mallocstate tst-scratch_buffer tst-alloc_buffer
>  
>  # The dynarray framework is only available inside glibc.
>  tests-internal += \
> @@ -63,6 +63,10 @@ routines = malloc morecore mcheck mtrace obstack reallocarray \
>    dynarray_finalize \
>    dynarray_resize \
>    dynarray_resize_clear \
> +  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 5b54306..4ead6c2 100644
> --- a/malloc/Versions
> +++ b/malloc/Versions
> @@ -76,7 +76,6 @@ libc {
>      __libc_scratch_buffer_grow_preserve;
>      __libc_scratch_buffer_set_array_size;
>  
> -
>      # Internal name for reallocarray
>      __libc_reallocarray;
>  
> @@ -86,5 +85,11 @@ libc {
>      __libc_dynarray_finalize;
>      __libc_dynarray_resize;
>      __libc_dynarray_resize_clear;
> +
> +    # 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..68e14da
> --- /dev/null
> +++ b/malloc/alloc_buffer_alloc_array.c
> @@ -0,0 +1,47 @@
> +/* 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>
> +#include <malloc-internal.h>
> +#include <libc-pointer-arith.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 = ALIGN_UP (current, align);
> +  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..cbde72b
> --- /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 **pptr)
> +{
> +  *pptr = malloc (size);
> +  if (*pptr == NULL)
> +    return (struct alloc_buffer)
> +      {
> +        .__alloc_buffer_current = __ALLOC_BUFFER_INVALID_POINTER,
> +        .__alloc_buffer_end = __ALLOC_BUFFER_INVALID_POINTER
> +      };
> +  else
> +    return alloc_buffer_create (*pptr, 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..1c14399
> --- /dev/null
> +++ b/malloc/tst-alloc_buffer.c
> @@ -0,0 +1,665 @@
> +/* 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));
> +
> +  {
> +    void *ptr = (void *) "";    /* Cannot be freed. */
> +    struct alloc_buffer buf = alloc_buffer_allocate (1, &ptr);
> +    test_size_1 (buf);
> +    free (ptr);                 /* Should have been overwritten.  */
> +  }
> +
> +  {
> +    void *ptr= (void *) "";     /* Cannot be freed.  */
> +    struct alloc_buffer buf = alloc_buffer_allocate (2, &ptr);
> +    test_size_2 (buf);
> +    free (ptr);                 /* Should have been overwritten.  */
> +  }
> +
> +  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>
>
  

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).

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

	* malloc/Makefile (tests-internal): Add tst-alloc_buffer.
	(routines): Add alloc_buffer_alloc_array, alloc_buffer_allocate,
	alloc_buffer_copy_bytes, alloc_buffer_copy_string.
	* malloc/Versions (__libc_alloc_buffer_alloc_array)
	(__libc_alloc_buffer_allocate, __libc_alloc_buffer_copy_bytes)
	(__libc_alloc_buffer_copy_string): 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.

diff --git a/include/alloc_buffer.h b/include/alloc_buffer.h
new file mode 100644
index 0000000..298bd72
--- /dev/null
+++ b/include/alloc_buffer.h
@@ -0,0 +1,358 @@ 
+/* 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 = ...;
+
+     void *heap_ptr;
+     struct alloc_buffer buf = alloc_buffer_allocate
+       (double_array_size * sizeof (double) + int_array_size * sizeof (int),
+        &heap_ptr);
+     _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.
+     ...
+     free (heap_ptr);
+
+   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, void **pptr)
+  __attribute__ ((nonnull (2)));
+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.  *PPTR points to the start of
+   the buffer and can be used to free it later, after the returned
+   buffer has been freed.  */
+static __always_inline __attribute__ ((nonnull (2)))
+struct alloc_buffer alloc_buffer_allocate (size_t size, void **pptr)
+{
+  return __libc_alloc_buffer_allocate (size, pptr);
+}
+
+/* Mark the buffer as failed.  */
+static inline void __attribute__ ((nonnull (1)))
+alloc_buffer_mark_failed (struct alloc_buffer *buf)
+{
+  buf->__alloc_buffer_current = __ALLOC_BUFFER_INVALID_POINTER;
+  buf->__alloc_buffer_end = __ALLOC_BUFFER_INVALID_POINTER;
+}
+
+/* Return the remaining number of bytes in the buffer.  */
+static __always_inline __attribute__ ((nonnull (1))) 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 __attribute__ ((nonnull (1)))
+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 __attribute__ ((nonnull (1)))
+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 __attribute__ ((nonnull (1))) 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 __attribute__ ((nonnull (1))) 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 __attribute__ ((nonnull (1))) 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)
+  __attribute__ ((nonnull (1)));
+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)
+  __attribute__ ((nonnull (2)));
+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 __attribute__ ((nonnull (1, 2))) 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 *)
+  __attribute__ ((nonnull (2)));
+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 __attribute__ ((nonnull (1, 2))) 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;
+}
+
+#endif /* _ALLOC_BUFFER_H */
diff --git a/malloc/Makefile b/malloc/Makefile
index 14c13f1..dd5d1dc 100644
--- a/malloc/Makefile
+++ b/malloc/Makefile
@@ -39,7 +39,7 @@  tests-static := \
 	 tst-interpose-static-thread \
 	 tst-malloc-usable-static \
 
-tests-internal := tst-mallocstate tst-scratch_buffer
+tests-internal := tst-mallocstate tst-scratch_buffer tst-alloc_buffer
 
 # The dynarray framework is only available inside glibc.
 tests-internal += \
@@ -63,6 +63,10 @@  routines = malloc morecore mcheck mtrace obstack reallocarray \
   dynarray_finalize \
   dynarray_resize \
   dynarray_resize_clear \
+  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 5b54306..4ead6c2 100644
--- a/malloc/Versions
+++ b/malloc/Versions
@@ -76,7 +76,6 @@  libc {
     __libc_scratch_buffer_grow_preserve;
     __libc_scratch_buffer_set_array_size;
 
-
     # Internal name for reallocarray
     __libc_reallocarray;
 
@@ -86,5 +85,11 @@  libc {
     __libc_dynarray_finalize;
     __libc_dynarray_resize;
     __libc_dynarray_resize_clear;
+
+    # 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..68e14da
--- /dev/null
+++ b/malloc/alloc_buffer_alloc_array.c
@@ -0,0 +1,47 @@ 
+/* 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>
+#include <malloc-internal.h>
+#include <libc-pointer-arith.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 = ALIGN_UP (current, align);
+  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..cbde72b
--- /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 **pptr)
+{
+  *pptr = malloc (size);
+  if (*pptr == NULL)
+    return (struct alloc_buffer)
+      {
+        .__alloc_buffer_current = __ALLOC_BUFFER_INVALID_POINTER,
+        .__alloc_buffer_end = __ALLOC_BUFFER_INVALID_POINTER
+      };
+  else
+    return alloc_buffer_create (*pptr, 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..1c14399
--- /dev/null
+++ b/malloc/tst-alloc_buffer.c
@@ -0,0 +1,665 @@ 
+/* 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));
+
+  {
+    void *ptr = (void *) "";    /* Cannot be freed. */
+    struct alloc_buffer buf = alloc_buffer_allocate (1, &ptr);
+    test_size_1 (buf);
+    free (ptr);                 /* Should have been overwritten.  */
+  }
+
+  {
+    void *ptr= (void *) "";     /* Cannot be freed.  */
+    struct alloc_buffer buf = alloc_buffer_allocate (2, &ptr);
+    test_size_2 (buf);
+    free (ptr);                 /* Should have been overwritten.  */
+  }
+
+  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>