Message ID | 2526706e-18e7-1c30-84b3-812663c43b98@redhat.com |
---|---|
State | New |
Headers | show |
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
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
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 */ >
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 */