public inbox for libc-alpha@sourceware.org
 help / color / mirror / Atom feed
* [PATCH] Allocation buffers for NSS result construction
@ 2017-04-22 19:47 Florian Weimer
  2017-05-08  8:24 ` Florian Weimer
                   ` (3 more replies)
  0 siblings, 4 replies; 17+ messages in thread
From: Florian Weimer @ 2017-04-22 19:47 UTC (permalink / raw)
  To: GNU C Library

[-- Attachment #1: Type: text/plain, Size: 143 bytes --]

I'm proposing the attached patch to reorganize the way result buffers 
for the NSS lookup of functions are filled.

Comments?

Thanks,
Florian

[-- Attachment #2: alloc_buffer.patch --]
[-- Type: text/x-patch, Size: 64768 bytes --]

Implement allocation buffers for internal use

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

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

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

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

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

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

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [PATCH] Allocation buffers for NSS result construction
  2017-04-22 19:47 [PATCH] Allocation buffers for NSS result construction Florian Weimer
@ 2017-05-08  8:24 ` Florian Weimer
  2017-05-15 23:32 ` DJ Delorie
                   ` (2 subsequent siblings)
  3 siblings, 0 replies; 17+ messages in thread
From: Florian Weimer @ 2017-05-08  8:24 UTC (permalink / raw)
  To: libc-alpha

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

Ping?

Thanks,
Florian

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [PATCH] Allocation buffers for NSS result construction
  2017-04-22 19:47 [PATCH] Allocation buffers for NSS result construction Florian Weimer
  2017-05-08  8:24 ` Florian Weimer
@ 2017-05-15 23:32 ` DJ Delorie
  2017-05-16  5:23   ` Florian Weimer
  2017-06-16 12:20 ` Florian Weimer
  2017-06-16 14:42 ` Adhemerval Zanella
  3 siblings, 1 reply; 17+ messages in thread
From: DJ Delorie @ 2017-05-15 23:32 UTC (permalink / raw)
  To: Florian Weimer; +Cc: libc-alpha


I have no specific comments on this, it looks pretty good and I like the
idea of centralizing this (I just did something similar in the NSS
testsuite, could have used this).  I mostly focused on the allocation
routines; I'm not familiar with resolv yet.

I do worry about having "internal" (internal to the allocator, not
internal to glibc) functions in a header.  They're written as if they'll
never be called by "outsiders" (i.e. little argument checking) but
there's no protection against such calls.  I don't know how to add such
protection, though.

Not having a pointer to the buffer's beginning irked me a bit, but I
don't see any reason to include it anyway.

I would recommend a few comments defining the behavior if NULL is passed
as a thing to copy - the NSS testsuite needed to preserve such
"mistakes" in the test data.

I wonder if this new functionality is complex enough to warrant a
separate bit of documentation?  Either in the manual or elsewhere...

The reallocarray patch also includes a __check_mul_overflow_size_t style
function; this duplication will need to be resolved as patches get
committed.

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [PATCH] Allocation buffers for NSS result construction
  2017-05-15 23:32 ` DJ Delorie
@ 2017-05-16  5:23   ` Florian Weimer
  2017-05-16 16:26     ` DJ Delorie
  0 siblings, 1 reply; 17+ messages in thread
From: Florian Weimer @ 2017-05-16  5:23 UTC (permalink / raw)
  To: DJ Delorie; +Cc: libc-alpha

On 05/16/2017 01:32 AM, DJ Delorie wrote:
> 
> I have no specific comments on this, it looks pretty good and I like the
> idea of centralizing this (I just did something similar in the NSS
> testsuite, could have used this).  I mostly focused on the allocation
> routines; I'm not familiar with resolv yet.

Thanks for your comments.

> I do worry about having "internal" (internal to the allocator, not
> internal to glibc) functions in a header.  They're written as if they'll
> never be called by "outsiders" (i.e. little argument checking) but
> there's no protection against such calls.  I don't know how to add such
> protection, though.

We could use #pragma GCC poison.  I'll give it a try.

> Not having a pointer to the buffer's beginning irked me a bit, but I
> don't see any reason to include it anyway.

It's important if you want to flip an output buffer to an input buffer 
(which covers the parts of the buffer that has been written).  I think 
it could make sense to add those as a separate abstraction eventually.

> I would recommend a few comments defining the behavior if NULL is passed
> as a thing to copy - the NSS testsuite needed to preserve such
> "mistakes" in the test data.

Hmm.  It's undefined because of the memcpy.  I'm not sure if I want to 
change that, it would need a mostly pointless pointer comparison before 
the memcpy call.

> I wonder if this new functionality is complex enough to warrant a
> separate bit of documentation?  Either in the manual or elsewhere...

Right.  We actually have internal documentation for NSS in the manual. 
We could add to that (even though the facilities are only available 
within glibc itself).

> The reallocarray patch also includes a __check_mul_overflow_size_t style
> function; this duplication will need to be resolved as patches get
> committed.

Right, it's on my radar.

Thanks,
Florian

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [PATCH] Allocation buffers for NSS result construction
  2017-05-16  5:23   ` Florian Weimer
@ 2017-05-16 16:26     ` DJ Delorie
  2017-05-16 16:46       ` Florian Weimer
  0 siblings, 1 reply; 17+ messages in thread
From: DJ Delorie @ 2017-05-16 16:26 UTC (permalink / raw)
  To: Florian Weimer; +Cc: libc-alpha


Florian Weimer <fweimer@redhat.com> writes:
>> I would recommend a few comments defining the behavior if NULL is passed
>> as a thing to copy - the NSS testsuite needed to preserve such
>> "mistakes" in the test data.
>
> Hmm.  It's undefined because of the memcpy.  I'm not sure if I want to 
> change that, it would need a mostly pointless pointer comparison before 
> the memcpy call.

I was thinking of strlen et al...

In my nss patch, I had to support both NULL pointers and pointers to
empty strings as "valid", which made me wonder...

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [PATCH] Allocation buffers for NSS result construction
  2017-05-16 16:26     ` DJ Delorie
@ 2017-05-16 16:46       ` Florian Weimer
  2017-05-16 17:22         ` DJ Delorie
  0 siblings, 1 reply; 17+ messages in thread
From: Florian Weimer @ 2017-05-16 16:46 UTC (permalink / raw)
  To: DJ Delorie; +Cc: libc-alpha

On 05/16/2017 06:26 PM, DJ Delorie wrote:
> 
> Florian Weimer <fweimer@redhat.com> writes:
>>> I would recommend a few comments defining the behavior if NULL is passed
>>> as a thing to copy - the NSS testsuite needed to preserve such
>>> "mistakes" in the test data.
>>
>> Hmm.  It's undefined because of the memcpy.  I'm not sure if I want to
>> change that, it would need a mostly pointless pointer comparison before
>> the memcpy call.
> 
> I was thinking of strlen et al...
> 
> In my nss patch, I had to support both NULL pointers and pointers to
> empty strings as "valid", which made me wonder...

Oh.  Well.  I don't think I want to paper over *that*.  A crash in 
strlen seems to be just fine, rather than ignoring the issue and perhaps 
returning grossly misleading data.

Thanks,
Florian

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [PATCH] Allocation buffers for NSS result construction
  2017-05-16 16:46       ` Florian Weimer
@ 2017-05-16 17:22         ` DJ Delorie
  2017-06-16 12:18           ` Florian Weimer
  0 siblings, 1 reply; 17+ messages in thread
From: DJ Delorie @ 2017-05-16 17:22 UTC (permalink / raw)
  To: Florian Weimer; +Cc: libc-alpha


Florian Weimer <fweimer@redhat.com> writes:
> Oh.  Well.  I don't think I want to paper over *that*.  A crash in 
> strlen seems to be just fine, rather than ignoring the issue and perhaps 
> returning grossly misleading data.

I think you misunderstand - I *intentionally* put bad data in the test
data to make sure the code handles it without crashing, and passes it
along accurately, and that the testsuite can detect and validate it.

A crash in strlen() means I can't use your code, or have to wrap it in
my own checks because I can't trust it.

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [PATCH] Allocation buffers for NSS result construction
  2017-05-16 17:22         ` DJ Delorie
@ 2017-06-16 12:18           ` Florian Weimer
  2017-06-16 19:06             ` DJ Delorie
  0 siblings, 1 reply; 17+ messages in thread
From: Florian Weimer @ 2017-06-16 12:18 UTC (permalink / raw)
  To: DJ Delorie; +Cc: libc-alpha

On 05/16/2017 07:22 PM, DJ Delorie wrote:
> 
> Florian Weimer <fweimer@redhat.com> writes:
>> Oh.  Well.  I don't think I want to paper over *that*.  A crash in 
>> strlen seems to be just fine, rather than ignoring the issue and perhaps 
>> returning grossly misleading data.
> 
> I think you misunderstand - I *intentionally* put bad data in the test
> data to make sure the code handles it without crashing, and passes it
> along accurately, and that the testsuite can detect and validate it.
> 
> A crash in strlen() means I can't use your code, or have to wrap it in
> my own checks because I can't trust it.

I don't quite understand what you are after.  I think if the code has to
deal with bad data, explicit checks are better than relying on fringe
behavior of library functions (printf and "(null)" is another example).

Please post example code, so that I can better understand your requirement.

Thanks,
Florian

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [PATCH] Allocation buffers for NSS result construction
  2017-04-22 19:47 [PATCH] Allocation buffers for NSS result construction Florian Weimer
  2017-05-08  8:24 ` Florian Weimer
  2017-05-15 23:32 ` DJ Delorie
@ 2017-06-16 12:20 ` Florian Weimer
  2017-06-16 14:42 ` Adhemerval Zanella
  3 siblings, 0 replies; 17+ messages in thread
From: Florian Weimer @ 2017-06-16 12:20 UTC (permalink / raw)
  To: GNU C Library

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

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

Thanks,
Florian

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [PATCH] Allocation buffers for NSS result construction
  2017-04-22 19:47 [PATCH] Allocation buffers for NSS result construction Florian Weimer
                   ` (2 preceding siblings ...)
  2017-06-16 12:20 ` Florian Weimer
@ 2017-06-16 14:42 ` Adhemerval Zanella
  2017-06-21 15:46   ` Florian Weimer
  3 siblings, 1 reply; 17+ messages in thread
From: Adhemerval Zanella @ 2017-06-16 14:42 UTC (permalink / raw)
  To: libc-alpha



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

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

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

A new line maybe?

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

Should we add an overflow test for sanity tests?

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

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

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

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

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

No need to cast to void *.

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

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

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

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

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

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

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

Maybe use ALIGN_UP?

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

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

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

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [PATCH] Allocation buffers for NSS result construction
  2017-06-16 12:18           ` Florian Weimer
@ 2017-06-16 19:06             ` DJ Delorie
  2017-06-16 21:19               ` Florian Weimer
  0 siblings, 1 reply; 17+ messages in thread
From: DJ Delorie @ 2017-06-16 19:06 UTC (permalink / raw)
  To: Florian Weimer; +Cc: libc-alpha


Florian Weimer <fweimer@redhat.com> writes:
> I don't quite understand what you are after.  I think if the code has to
> deal with bad data, explicit checks are better than relying on fringe
> behavior of library functions (printf and "(null)" is another example).
>
> Please post example code, so that I can better understand your requirement.

https://www.sourceware.org/ml/libc-alpha/2017-05/msg00074.html

The data I'm passing from the nss_test helpers may have NULL where a
"char *" string is expected.  If the NSS core calls strlen() on it, it
will crash.  When I'm building the buffer, I can't just call strlen() or
it will crash.  I'm testing to make sure it doesn't crash, and that the
NULL is passed along to the caller.

So either your strlen() (or any other function taking a string) needs to
handle NULL in a defined non-crashing way, or any code using it still
needs to do its own handling.  It would be better if handling NULL were
defined in a graceful way by your API to avoid replicating the NULL case
handling at all callers.

For example, if I want to add a NULL string to an array of strings, your
code needs to safely put a NULL pointer in the list (somehow) without
allocating space for it.  This is separate from a zero-length string,
where a valid pointer is stored and spac for a NUL byte is allocated.

If I can't put a NULL string in an array of strings at all, your code
can't be used for my case.

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [PATCH] Allocation buffers for NSS result construction
  2017-06-16 19:06             ` DJ Delorie
@ 2017-06-16 21:19               ` Florian Weimer
  2017-06-16 21:29                 ` DJ Delorie
  0 siblings, 1 reply; 17+ messages in thread
From: Florian Weimer @ 2017-06-16 21:19 UTC (permalink / raw)
  To: DJ Delorie; +Cc: libc-alpha

On 06/16/2017 09:06 PM, DJ Delorie wrote:
> Florian Weimer <fweimer@redhat.com> writes:
>> I don't quite understand what you are after.  I think if the code has to
>> deal with bad data, explicit checks are better than relying on fringe
>> behavior of library functions (printf and "(null)" is another example).
>>
>> Please post example code, so that I can better understand your requirement.
> https://www.sourceware.org/ml/libc-alpha/2017-05/msg00074.html
> 
> The data I'm passing from the nss_test helpers may have NULL where a
> "char *" string is expected.  If the NSS core calls strlen() on it, it
> will crash.  When I'm building the buffer, I can't just call strlen() or
> it will crash.  I'm testing to make sure it doesn't crash, and that the
> NULL is passed along to the caller

Do you mean the COPY_IF_ROOM macro?

I think you'll have to keep a NULL check in it because this use case is
quite obscure.  I think most other NSS module implementations only copy
data into the output if they actually have any data, so the pointer is
already guaranteed to be not NULL when the data is copied.

Thanks,
Florian

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [PATCH] Allocation buffers for NSS result construction
  2017-06-16 21:19               ` Florian Weimer
@ 2017-06-16 21:29                 ` DJ Delorie
  0 siblings, 0 replies; 17+ messages in thread
From: DJ Delorie @ 2017-06-16 21:29 UTC (permalink / raw)
  To: Florian Weimer; +Cc: libc-alpha


Florian Weimer <fweimer@redhat.com> writes:
> I think you'll have to keep a NULL check in it because this use case is
> quite obscure.

True, but I was suggesting you handle the obscure case in a useful way
to keep your module robust.

And COPY_IF_ROOM is only half the code; the other half generates the
list of pointers that preceeds it - see copy_group() in my patch.

If your code doesn't have a way to generate a *list* of strings, one of
which may be NULL, I have to code the whole thing myself anyway.

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [PATCH] Allocation buffers for NSS result construction
  2017-06-16 14:42 ` Adhemerval Zanella
@ 2017-06-21 15:46   ` Florian Weimer
  2017-06-21 19:44     ` Adhemerval Zanella
  0 siblings, 1 reply; 17+ messages in thread
From: Florian Weimer @ 2017-06-21 15:46 UTC (permalink / raw)
  To: Adhemerval Zanella, libc-alpha

[-- Attachment #1: Type: text/plain, Size: 6038 bytes --]

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

[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: alloc_buffer.patch --]
[-- Type: text/x-patch; name="alloc_buffer.patch", Size: 46907 bytes --]

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>

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [PATCH] Allocation buffers for NSS result construction
  2017-06-21 15:46   ` Florian Weimer
@ 2017-06-21 19:44     ` Adhemerval Zanella
  2017-06-21 20:21       ` Florian Weimer
  0 siblings, 1 reply; 17+ messages in thread
From: Adhemerval Zanella @ 2017-06-21 19:44 UTC (permalink / raw)
  To: Florian Weimer, libc-alpha



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

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [PATCH] Allocation buffers for NSS result construction
  2017-06-21 19:44     ` Adhemerval Zanella
@ 2017-06-21 20:21       ` Florian Weimer
  2017-06-21 20:42         ` Adhemerval Zanella
  0 siblings, 1 reply; 17+ messages in thread
From: Florian Weimer @ 2017-06-21 20:21 UTC (permalink / raw)
  To: Adhemerval Zanella; +Cc: libc-alpha

[-- Attachment #1: Type: text/plain, Size: 1743 bytes --]

On 06/21/2017 09:44 PM, Adhemerval Zanella wrote:

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

I added out-of-line process termination in the attached version.  I
assume that's what you had in mind.

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

GCC recognizes the idiom and should optimize it just like the built-in.
The value of the built-in primarily lies in the accurate, type-generic
check (you get an overflow if the result does not match the result over
the integers, irrespective of the types involved).

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

Thanks, fixed.

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

I expanded the comment and explained why the initial overflow check is
not needed.

Thanks,
Florian

[-- Attachment #2: alloc_buffer.patch --]
[-- Type: text/x-patch, Size: 47576 bytes --]

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,
	alloc_buffer_create_failure.
	* malloc/Versions (__libc_alloc_buffer_alloc_array)
	(__libc_alloc_buffer_allocate, __libc_alloc_buffer_copy_bytes)
	(__libc_alloc_buffer_copy_string)
	(__libc_alloc_buffer_create_failure): 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/alloc_buffer_create_failure.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..d668a60
--- /dev/null
+++ b/include/alloc_buffer.h
@@ -0,0 +1,367 @@
+/* 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 receive 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.  In case of an
+   overflow, one of the subsequent allocations from the buffer will
+   fail.  The initial 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,
+  };
+
+/* Internal function.  Terminate the process using __libc_fatal.  */
+void __libc_alloc_buffer_create_failure (void *start, size_t size);
+
+/* 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)
+{
+  uintptr_t current = (uintptr_t) start;
+  uintptr_t end = (uintptr_t) start + size;
+  if (end < current)
+    __libc_alloc_buffer_create_failure (start, size);
+  return (struct alloc_buffer) { current, end };
+}
+
+/* Internal function.  See alloc_buffer_allocate below.  */
+struct alloc_buffer __libc_alloc_buffer_allocate (size_t size, void **pptr)
+  __attribute__ ((nonnull (2)));
+
+/* 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)));
+
+/* 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)));
+
+/* 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)));
+
+/* 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;
+}
+
+#ifndef _ISOMAC
+libc_hidden_proto (__libc_alloc_buffer_alloc_array)
+libc_hidden_proto (__libc_alloc_buffer_allocate)
+libc_hidden_proto (__libc_alloc_buffer_copy_bytes)
+libc_hidden_proto (__libc_alloc_buffer_copy_string)
+libc_hidden_proto (__libc_alloc_buffer_create_failure)
+#endif
+
+#endif /* _ALLOC_BUFFER_H */
diff --git a/malloc/Makefile b/malloc/Makefile
index 14c13f1..b50de7c 100644
--- a/malloc/Makefile
+++ b/malloc/Makefile
@@ -33,6 +33,7 @@ tests := mallocbug tst-malloc tst-valloc tst-calloc tst-obstack \
 	 tst-mallocfork2 \
 	 tst-interpose-nothread \
 	 tst-interpose-thread \
+	 tst-alloc_buffer \
 
 tests-static := \
 	 tst-interpose-static-nothread \
@@ -63,6 +64,11 @@ 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 \
+  alloc_buffer_create_failure \
 
 install-lib := libmcheck.a
 non-lib.a := libmcheck.a
diff --git a/malloc/Versions b/malloc/Versions
index 5b54306..2357cff 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,12 @@ 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;
+    __libc_alloc_buffer_create_failure;
   }
 }
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/alloc_buffer_create_failure.c b/malloc/alloc_buffer_create_failure.c
new file mode 100644
index 0000000..5ffba22
--- /dev/null
+++ b/malloc/alloc_buffer_create_failure.c
@@ -0,0 +1,31 @@
+/* Terminate the process as the result of an invalid 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 <stdio.h>
+
+void
+__libc_alloc_buffer_create_failure (void *start, size_t size)
+{
+  char buf[200];
+  __snprintf (buf, sizeof (buf), "Fatal glibc error: "
+              "invalid allocation buffer of size %zu\n",
+              size);
+  __libc_fatal (buf);
+}
+libc_hidden_def (__libc_alloc_buffer_create_failure)
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>

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [PATCH] Allocation buffers for NSS result construction
  2017-06-21 20:21       ` Florian Weimer
@ 2017-06-21 20:42         ` Adhemerval Zanella
  0 siblings, 0 replies; 17+ messages in thread
From: Adhemerval Zanella @ 2017-06-21 20:42 UTC (permalink / raw)
  To: Florian Weimer; +Cc: libc-alpha



On 21/06/2017 17:21, Florian Weimer wrote:
> On 06/21/2017 09:44 PM, Adhemerval Zanella wrote:
> 
>> 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.
> 
> I added out-of-line process termination in the attached version.  I
> assume that's what you had in mind.
> 
>> 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).
> 
> GCC recognizes the idiom and should optimize it just like the built-in.
> The value of the built-in primarily lies in the accurate, type-generic
> check (you get an overflow if the result does not match the result over
> the integers, irrespective of the types involved).
> 
>>> +/* 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
> 
> Thanks, fixed.
> 
>>> +     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.
> 
> I expanded the comment and explained why the initial overflow check is
> not needed.
> 
> Thanks,
> Florian
> 

LGTM, thanks.

^ permalink raw reply	[flat|nested] 17+ messages in thread

end of thread, other threads:[~2017-06-21 20:42 UTC | newest]

Thread overview: 17+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2017-04-22 19:47 [PATCH] Allocation buffers for NSS result construction Florian Weimer
2017-05-08  8:24 ` Florian Weimer
2017-05-15 23:32 ` DJ Delorie
2017-05-16  5:23   ` Florian Weimer
2017-05-16 16:26     ` DJ Delorie
2017-05-16 16:46       ` Florian Weimer
2017-05-16 17:22         ` DJ Delorie
2017-06-16 12:18           ` Florian Weimer
2017-06-16 19:06             ` DJ Delorie
2017-06-16 21:19               ` Florian Weimer
2017-06-16 21:29                 ` DJ Delorie
2017-06-16 12:20 ` Florian Weimer
2017-06-16 14:42 ` Adhemerval Zanella
2017-06-21 15:46   ` Florian Weimer
2017-06-21 19:44     ` Adhemerval Zanella
2017-06-21 20:21       ` Florian Weimer
2017-06-21 20:42         ` Adhemerval Zanella

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).