diff options
author | Florian Weimer <fweimer@redhat.com> | 2017-03-10 20:45:25 +0100 |
---|---|---|
committer | Florian Weimer <fweimer@redhat.com> | 2017-03-10 20:45:25 +0100 |
commit | ddd21a211369658bc087cc5c56cd577bb8370857 (patch) | |
tree | d6b0a5128122316f5daa610dad4abc36325fdab7 | |
parent | 60f9423b6b02d79f8fef65552ecd21e0fd097774 (diff) | |
download | glibc-fw/out_buffer.tar.gz |
WIP struct out_bufferfw/out_buffer
-rw-r--r-- | include/out_buffer.h | 247 | ||||
-rw-r--r-- | malloc/Makefile | 3 | ||||
-rw-r--r-- | malloc/Versions | 3 | ||||
-rw-r--r-- | malloc/out_buffer_get_array.c | 50 | ||||
-rw-r--r-- | malloc/tst-out_buffer.c | 480 | ||||
-rw-r--r-- | resolv/Versions | 2 | ||||
-rw-r--r-- | resolv/ns_name.c | 160 | ||||
-rw-r--r-- | resolv/nss_dns/dns-network.c | 81 | ||||
-rw-r--r-- | resolv/resolv-internal.h | 12 |
9 files changed, 902 insertions, 136 deletions
diff --git a/include/out_buffer.h b/include/out_buffer.h new file mode 100644 index 0000000000..4d6e15b0a5 --- /dev/null +++ b/include/out_buffer.h @@ -0,0 +1,247 @@ +/* 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/>. */ + +/* TODOs: + + More documentation, including usage scenarios: + (1) NSS code (with struct scratch_buffer), although this style is + not a good idea (callee should malloc if caller does not know + size). + (2) Compute allocation size of a buffer (without overflow + checking), allocate out_buffer, overflow checking while + writing to the buffer. Timezone parser (time/tzfile.c) + contains a particularly interesting example. + + out_buffer_init should be out_buffer_create and return the struct. + + Add out_buffer_allocate which calls malloc (and returns the struct, + to help aliasing analysis). + + Fix invalid pointer handling. All pointers should point into the + buffer. Use NULL for __OUT_BUFFER_INVALID_POINTER. Make NULL + argument to out_buffer_init invalid, to resolve the + out_buffer_get_bytes ambiguity. + + Add byte array copying functions. + + Add string copying functions. + + Maybe add string formatting functions. + */ + +#ifndef _OUT_BUFFER_H +#define _OUT_BUFFER_H + +#include <inttypes.h> +#include <stdbool.h> +#include <stddef.h> +#include <sys/param.h> + +struct out_buffer +{ + uintptr_t __out_buffer_current; + uintptr_t __out_buffer_end; +}; + +enum + { + __OUT_BUFFER_INVALID_POINTER = 1, + }; + +static inline void +out_buffer_init (struct out_buffer *buf, void *start, size_t size) +{ + if (size > 0) + { + buf->__out_buffer_current = (uintptr_t) start; + buf->__out_buffer_end = buf->__out_buffer_current + size; + } + else + { + /* Avoid storing __OUT_BUFFER_INVALID_POINTER (marking the + buffer as failed) for a zero-length buffer. */ + buf->__out_buffer_current = 0; + buf->__out_buffer_end = 0; + } +} + +static __always_inline void * +out_buffer_current (struct out_buffer *buf) +{ + return (void *) buf->__out_buffer_current; +} + +/* Internal function. Return the remaining number of bytes in the + buffer. */ +static __always_inline size_t +__out_buffer_remaining (const struct out_buffer *buf) +{ + return buf->__out_buffer_end - buf->__out_buffer_current; +} + +/* Internal function. Mark the buffer as failed. */ +static inline void +out_buffer_mark_failed (struct out_buffer *buf) +{ + buf->__out_buffer_current = __OUT_BUFFER_INVALID_POINTER; + buf->__out_buffer_end = __OUT_BUFFER_INVALID_POINTER; +} + +/* Return true if the buffer has been marked as failed. */ +static inline bool +out_buffer_has_failed (const struct out_buffer *buf) +{ + return buf->__out_buffer_current == __OUT_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 +out_buffer_add_byte (struct out_buffer *buf, unsigned char b) +{ + if (__glibc_likely (buf->__out_buffer_current < buf->__out_buffer_end)) + { + *(unsigned char *) buf->__out_buffer_current = b; + ++buf->__out_buffer_current; + } + else + out_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. */ +static inline void * +out_buffer_get_bytes (struct out_buffer *buf, size_t length) +{ + if (__glibc_likely (length > 0)) + { + if (length <= __out_buffer_remaining (buf)) + { + void *result = (void *) buf->__out_buffer_current; + buf->__out_buffer_current += length; + return result; + } + else + { + out_buffer_mark_failed (buf); + return NULL; + } + } + else if (out_buffer_has_failed (buf)) + return NULL; + else + /* Non-null pointer to empty array. */ + return (void *) __OUT_BUFFER_INVALID_POINTER; +} + +/* Internal function. Statically assert that the type size is + constant and valid. */ +static __always_inline size_t +__out_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 +__out_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 * +__out_buffer_get (struct out_buffer *buf, size_t size, size_t align) +{ + if (size == 1 && align == 1) + return out_buffer_get_bytes (buf, size); + + size_t current = buf->__out_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->__out_buffer_end) /* Room in buffer. */ + { + buf->__out_buffer_current = new_current; + return (void *) aligned; + } + else + { + out_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 out_buffer_get(buf, type) \ + ((type *) __out_buffer_get \ + (buf, __out_buffer_assert_size (sizeof (type)), \ + __out_buffer_assert_align (__alignof__ (type)))) + + +/* Internal function. Allocate an array. COUNT_LIMIT is used + internally to check for multiplication overflow without a + division. */ +void * __libc_out_buffer_get_array (struct out_buffer *buf, + size_t size, size_t align, + size_t count, size_t count_limit) + internal_function; +libc_hidden_proto (__libc_out_buffer_get_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. */ +#define out_buffer_get_array(buf, type, count) \ + ((type *) __libc_out_buffer_get_array \ + (buf, __out_buffer_assert_size (sizeof (type)), \ + __out_buffer_assert_align (__alignof__ (type)), \ + count, ((size_t) -1) / sizeof (type))) + +#endif /* _OUT_BUFFER_H */ diff --git a/malloc/Makefile b/malloc/Makefile index e93b83b57d..3aca6fa4bb 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-out_buffer \ tests-static := \ tst-interpose-static-nothread \ @@ -49,7 +50,7 @@ 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 out_buffer_get_array install-lib := libmcheck.a non-lib.a := libmcheck.a diff --git a/malloc/Versions b/malloc/Versions index f3c3d8a093..779bc3db6c 100644 --- a/malloc/Versions +++ b/malloc/Versions @@ -72,5 +72,8 @@ libc { __libc_scratch_buffer_grow; __libc_scratch_buffer_grow_preserve; __libc_scratch_buffer_set_array_size; + + # struct out_buffer support + __libc_out_buffer_get_array; } } diff --git a/malloc/out_buffer_get_array.c b/malloc/out_buffer_get_array.c new file mode 100644 index 0000000000..0c09e33bd4 --- /dev/null +++ b/malloc/out_buffer_get_array.c @@ -0,0 +1,50 @@ +/* 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 <out_buffer.h> + +void * +internal_function +__libc_out_buffer_get_array (struct out_buffer *buf, + size_t element_size, size_t align, + size_t count, size_t count_limit) +{ + /* Avoid spurious success if count == 0. */ + if (out_buffer_has_failed (buf)) + return NULL; + + size_t current = buf->__out_buffer_current; + /* The caller asserts that align is a power of two. */ + size_t aligned = (current + align - 1) & ~(align - 1); + size_t size = element_size * count; + size_t new_current = aligned + size; + if (count <= count_limit /* Multiplication did not overflow. */ + && aligned >= current /* No overflow in align step. */ + && new_current >= size /* No overflow in size computation. */ + && new_current <= buf->__out_buffer_end) /* Room in buffer. */ + { + buf->__out_buffer_current = new_current; + return (void *) aligned; + } + else + { + out_buffer_mark_failed (buf); + return NULL; + } +} +libc_hidden_def (__libc_out_buffer_get_array) diff --git a/malloc/tst-out_buffer.c b/malloc/tst-out_buffer.c new file mode 100644 index 0000000000..9139ae1ba2 --- /dev/null +++ b/malloc/tst-out_buffer.c @@ -0,0 +1,480 @@ +/* Tests for struct out_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 <out_buffer.h> +#include <stdlib.h> +#include <string.h> +#include <support/check.h> +#include <support/support.h> +#include <stdio.h> + +/* Return the start pointer of the buffer. */ +#define START(buf) ((void *) (buf)->__out_buffer_current) + +/* Return true if PTR is sufficiently aligned for TYPE. */ +#define IS_ALIGNED(ptr, type) \ + ((((uintptr_t) ptr) & (__out_buffer_assert_align (__alignof (type)) -1)) \ + == 0) + +/* Structure with non-power-of-two size. */ +struct twelve +{ + uint32_t buffer[3]; +}; +_Static_assert (sizeof (struct twelve) == 12, "struct twelve"); + +/* Check for success obtaining empty arrays. Does not assume the + buffer is empty. */ +static void +test_empty_array (struct out_buffer refbuf) +{ + { + struct out_buffer buf = refbuf; + TEST_VERIFY (out_buffer_get_bytes (&buf, 0) != NULL); + TEST_VERIFY (!out_buffer_has_failed (&buf)); + } + { + struct out_buffer buf = refbuf; + TEST_VERIFY (out_buffer_get_array (&buf, char, 0) != NULL); + TEST_VERIFY (!out_buffer_has_failed (&buf)); + } + { + struct out_buffer buf = refbuf; + TEST_VERIFY (out_buffer_get_array (&buf, double, 0) != NULL); + TEST_VERIFY (!out_buffer_has_failed (&buf)); + } + { + struct out_buffer buf = refbuf; + TEST_VERIFY (out_buffer_get_array (&buf, struct twelve, 0) != NULL); + TEST_VERIFY (!out_buffer_has_failed (&buf)); + } +} + +/* Test allocation of impossibly large arrays. */ +static void +test_impossible_array (struct out_buffer refbuf) +{ + printf ("info: %s: current=0x%llx end=0x%llx\n", + __func__, (unsigned long long) refbuf.__out_buffer_current, + (unsigned long long) refbuf.__out_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]; + printf ("info: %s: count=%zu\n", __func__, count); + { + struct out_buffer buf = refbuf; + TEST_VERIFY (out_buffer_get_bytes (&buf, count) == NULL); + TEST_VERIFY (out_buffer_has_failed (&buf)); + } + { + struct out_buffer buf = refbuf; + TEST_VERIFY (out_buffer_get_array (&buf, char, count) == NULL); + TEST_VERIFY (out_buffer_has_failed (&buf)); + } + { + struct out_buffer buf = refbuf; + TEST_VERIFY (out_buffer_get_array (&buf, short, count) == NULL); + TEST_VERIFY (out_buffer_has_failed (&buf)); + } + { + struct out_buffer buf = refbuf; + TEST_VERIFY (out_buffer_get_array (&buf, double, count) == NULL); + TEST_VERIFY (out_buffer_has_failed (&buf)); + } + { + struct out_buffer buf = refbuf; + TEST_VERIFY (out_buffer_get_array (&buf, struct twelve, count) + == NULL); + TEST_VERIFY (out_buffer_has_failed (&buf)); + } + } +} + +/* Check for failure to obtain anything from a failed buffer. */ +static void +test_after_failure (struct out_buffer refbuf) +{ + TEST_VERIFY (out_buffer_has_failed (&refbuf)); + { + struct out_buffer buf = refbuf; + out_buffer_add_byte (&buf, 17); + TEST_VERIFY (out_buffer_has_failed (&buf)); + } + { + struct out_buffer buf = refbuf; + TEST_VERIFY (out_buffer_get (&buf, char) == NULL); + TEST_VERIFY (out_buffer_has_failed (&buf)); + } + { + struct out_buffer buf = refbuf; + TEST_VERIFY (out_buffer_get (&buf, double) == NULL); + TEST_VERIFY (out_buffer_has_failed (&buf)); + } + { + struct out_buffer buf = refbuf; + TEST_VERIFY (out_buffer_get (&buf, struct twelve) == NULL); + TEST_VERIFY (out_buffer_has_failed (&buf)); + } + + test_impossible_array (refbuf); + for (int count = 0; count <= 4; ++count) + { + { + struct out_buffer buf = refbuf; + TEST_VERIFY (out_buffer_get_bytes (&buf, count) == NULL); + TEST_VERIFY (out_buffer_has_failed (&buf)); + } + { + struct out_buffer buf = refbuf; + TEST_VERIFY (out_buffer_get_array (&buf, char, count) == NULL); + TEST_VERIFY (out_buffer_has_failed (&buf)); + } + { + struct out_buffer buf = refbuf; + TEST_VERIFY (out_buffer_get_array (&buf, double, count) == NULL); + TEST_VERIFY (out_buffer_has_failed (&buf)); + } + { + struct out_buffer buf = refbuf; + TEST_VERIFY (out_buffer_get_array (&buf, struct twelve, count) + == NULL); + TEST_VERIFY (out_buffer_has_failed (&buf)); + } + } +} + +static void +test_empty (struct out_buffer refbuf) +{ + TEST_VERIFY (!out_buffer_has_failed (&refbuf)); + test_empty_array (refbuf); + test_impossible_array (refbuf); + + /* Failure to obtain non-empty objects. */ + { + struct out_buffer buf = refbuf; + out_buffer_add_byte (&buf, 17); + test_after_failure (buf); + } + { + struct out_buffer buf = refbuf; + TEST_VERIFY (out_buffer_get (&buf, char) == NULL); + test_after_failure (buf); + } + { + struct out_buffer buf = refbuf; + TEST_VERIFY (out_buffer_get (&buf, double) == NULL); + test_after_failure (buf); + } + { + struct out_buffer buf = refbuf; + TEST_VERIFY (out_buffer_get (&buf, struct twelve) == NULL); + test_after_failure (buf); + } + { + struct out_buffer buf = refbuf; + TEST_VERIFY (out_buffer_get_array (&buf, char, 1) == NULL); + test_after_failure (buf); + } + { + struct out_buffer buf = refbuf; + TEST_VERIFY (out_buffer_get_array (&buf, double, 1) == NULL); + test_after_failure (buf); + } + { + struct out_buffer buf = refbuf; + TEST_VERIFY (out_buffer_get_array (&buf, struct twelve, 1) == NULL); + test_after_failure (buf); + } +} + +static void +test_size_1 (struct out_buffer refbuf) +{ + TEST_VERIFY (!out_buffer_has_failed (&refbuf)); + test_empty_array (refbuf); + test_impossible_array (refbuf); + + /* Success adding a single byte. */ + { + struct out_buffer buf = refbuf; + out_buffer_add_byte (&buf, 17); + TEST_VERIFY (!out_buffer_has_failed (&buf)); + test_empty (buf); + } + TEST_VERIFY (memcmp (START (&refbuf), "\x11", 1) == 0); + { + struct out_buffer buf = refbuf; + signed char *ptr = out_buffer_get (&buf, signed char); + TEST_VERIFY_EXIT (ptr != NULL); + TEST_VERIFY (!out_buffer_has_failed (&buf)); + *ptr = 126; + test_empty (buf); + } + TEST_VERIFY (memcmp (START (&refbuf), "\176", 1) == 0); + { + struct out_buffer buf = refbuf; + char *ptr = out_buffer_get_array (&buf, char, 1); + TEST_VERIFY_EXIT (ptr != NULL); + TEST_VERIFY (!out_buffer_has_failed (&buf)); + *ptr = (char) 253; + test_empty (buf); + } + TEST_VERIFY (memcmp (START (&refbuf), "\xfd", 1) == 0); + + /* Failure with larger objects. */ + { + struct out_buffer buf = refbuf; + TEST_VERIFY (out_buffer_get (&buf, short) == NULL); + test_after_failure (buf); + } + { + struct out_buffer buf = refbuf; + TEST_VERIFY (out_buffer_get (&buf, double) == NULL); + test_after_failure (buf); + } + { + struct out_buffer buf = refbuf; + TEST_VERIFY (out_buffer_get (&buf, struct twelve) == NULL); + test_after_failure (buf); + } + { + struct out_buffer buf = refbuf; + TEST_VERIFY (out_buffer_get_array (&buf, short, 1) == NULL); + test_after_failure (buf); + } + { + struct out_buffer buf = refbuf; + TEST_VERIFY (out_buffer_get_array (&buf, double, 1) == NULL); + test_after_failure (buf); + } + { + struct out_buffer buf = refbuf; + TEST_VERIFY (out_buffer_get_array (&buf, struct twelve, 1) == NULL); + test_after_failure (buf); + } +} + +static void +test_size_2 (struct out_buffer refbuf) +{ + TEST_VERIFY (!out_buffer_has_failed (&refbuf)); + TEST_VERIFY (IS_ALIGNED (START (&refbuf), short)); + test_empty_array (refbuf); + test_impossible_array (refbuf); + + /* Success adding two bytes. */ + { + struct out_buffer buf = refbuf; + out_buffer_add_byte (&buf, '@'); + TEST_VERIFY (!out_buffer_has_failed (&buf)); + test_size_1 (buf); + } + TEST_VERIFY (memcmp (START (&refbuf), "@\xfd", 2) == 0); + { + struct out_buffer buf = refbuf; + signed char *ptr = out_buffer_get (&buf, signed char); + TEST_VERIFY_EXIT (ptr != NULL); + TEST_VERIFY (!out_buffer_has_failed (&buf)); + *ptr = 'A'; + test_size_1 (buf); + } + TEST_VERIFY (memcmp (START (&refbuf), "A\xfd", 2) == 0); + { + struct out_buffer buf = refbuf; + char *ptr = out_buffer_get_array (&buf, char, 1); + TEST_VERIFY_EXIT (ptr != NULL); + TEST_VERIFY (!out_buffer_has_failed (&buf)); + *ptr = 'B'; + test_size_1 (buf); + } + TEST_VERIFY (memcmp (START (&refbuf), "B\xfd", 2) == 0); + { + struct out_buffer buf = refbuf; + unsigned short *ptr = out_buffer_get (&buf, unsigned short); + TEST_VERIFY_EXIT (ptr != NULL); + TEST_VERIFY (IS_ALIGNED (ptr, unsigned short)); + TEST_VERIFY (!out_buffer_has_failed (&buf)); + *ptr = htons (0x12f4); + test_empty (buf); + } + TEST_VERIFY (memcmp (START (&refbuf), "\x12\xf4", 2) == 0); + { + struct out_buffer buf = refbuf; + unsigned short *ptr = out_buffer_get_array (&buf, unsigned short, 1); + TEST_VERIFY_EXIT (ptr != NULL); + TEST_VERIFY (IS_ALIGNED (ptr, unsigned short)); + TEST_VERIFY (!out_buffer_has_failed (&buf)); + *ptr = htons (0x13f5); + test_empty (buf); + } + TEST_VERIFY (memcmp (START (&refbuf), "\x13\xf5", 2) == 0); + { + struct out_buffer buf = refbuf; + char *ptr = out_buffer_get_array (&buf, char, 2); + TEST_VERIFY_EXIT (ptr != NULL); + TEST_VERIFY (!out_buffer_has_failed (&buf)); + memcpy (ptr, "12", 2); + test_empty (buf); + } + TEST_VERIFY (memcmp (START (&refbuf), "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 out_buffer refbuf; + out_buffer_init (&refbuf, backing + 1, SIZE); + + { + struct out_buffer buf = refbuf; + short *ptr = out_buffer_get_array (&buf, short, SIZE / sizeof (short)); + TEST_VERIFY_EXIT (ptr != NULL); + TEST_VERIFY (IS_ALIGNED (ptr, short)); + TEST_VERIFY (!out_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 out_buffer buf = refbuf; + uint32_t *ptr = out_buffer_get_array + (&buf, uint32_t, SIZE / sizeof (uint32_t)); + TEST_VERIFY_EXIT (ptr != NULL); + TEST_VERIFY (IS_ALIGNED (ptr, uint32_t)); + TEST_VERIFY (!out_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 out_buffer buf = refbuf; + struct twelve *ptr = out_buffer_get (&buf, struct twelve); + TEST_VERIFY_EXIT (ptr != NULL); + TEST_VERIFY (IS_ALIGNED (ptr, struct twelve)); + TEST_VERIFY (!out_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 out_buffer buf = refbuf; + double *ptr = out_buffer_get_array (&buf, double, 2); + TEST_VERIFY_EXIT (ptr != NULL); + TEST_VERIFY (IS_ALIGNED (ptr, double)); + TEST_VERIFY (!out_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 out_buffer refbuf; + out_buffer_init (&refbuf, (void *) start, 16); + TEST_VERIFY (!out_buffer_has_failed (&refbuf)); + + struct __attribute__ ((aligned (256))) align256 + { + int dymmy; + }; + + { + struct out_buffer buf = refbuf; + TEST_VERIFY (out_buffer_get (&buf, struct align256) == NULL); + test_after_failure (buf); + } + for (int count = 0; count < 3; ++count) + { + struct out_buffer buf = refbuf; + TEST_VERIFY (out_buffer_get_array (&buf, struct align256, count) == NULL); + test_after_failure (buf); + } +} + +static struct out_buffer +mkbuffer (void *ptr, size_t len) +{ + struct out_buffer buf; + out_buffer_init (&buf, ptr, len); + return buf; +} + +static int +do_test (void) +{ + test_empty (mkbuffer (NULL, 0)); + test_empty (mkbuffer ((char *) "", 0)); + test_empty (mkbuffer ((void *) 1, 0)); + + { + char *backing = xmalloc (1); + struct out_buffer buf; + out_buffer_init (&buf, backing, 1); + test_size_1 (buf); + free (backing); + } + + { + char *backing = xmalloc (2); + struct out_buffer buf; + out_buffer_init (&buf, backing, 2); + test_size_2 (buf); + free (backing); + } + + test_misaligned (0); + test_misaligned (0xc7); + test_misaligned (0xff); + + test_large_misaligned (); + + return 0; +} + +#include <support/test-driver.c> diff --git a/resolv/Versions b/resolv/Versions index e561bce1a4..9e77270e72 100644 --- a/resolv/Versions +++ b/resolv/Versions @@ -76,7 +76,7 @@ libresolv { } GLIBC_PRIVATE { # Needed in libnss_dns. - __ns_name_unpack; __ns_name_ntop; + __ns_name_unpack; __ns_name_ntop; __ns_name_ntop_buffer; __ns_get16; __ns_get32; __libc_res_nquery; __libc_res_nsearch; } diff --git a/resolv/ns_name.c b/resolv/ns_name.c index 0d76fe53a6..a681506858 100644 --- a/resolv/ns_name.c +++ b/resolv/ns_name.c @@ -19,6 +19,7 @@ #include <netinet/in.h> #include <arpa/nameser.h> +#include <resolv/resolv-internal.h> #include <errno.h> #include <resolv.h> @@ -26,6 +27,7 @@ #include <ctype.h> #include <stdlib.h> #include <limits.h> +#include <out_buffer.h> # define SPRINTF(x) ((size_t)sprintf x) @@ -44,90 +46,90 @@ static int labellen(const u_char *); /* Public. */ -/*% - * Convert an encoded domain name to printable ascii as per RFC1035. - - * 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) +char * +internal_function +__ns_name_ntop_buffer (struct out_buffer *pdst, + const unsigned char *src) { - const u_char *cp; - char *dn, *eom; - u_char c; - u_int n; - int l; - - cp = src; - dn = dst; - eom = dst + dstsiz; + /* Make copy to help with aliasing analysis. */ + struct out_buffer dst = *pdst; + bool first = true; + while (true) + { + unsigned char n = *src; + ++src; + if (n == 0) + break; + if (n > 63) + { + /* Some kind of compression pointer. */ + out_buffer_mark_failed (&dst); + break; + } - 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 (first) + first = false; + else + out_buffer_add_byte (&dst, '.'); + + for (int i = 0; i < n; ++i) + { + unsigned char c = *src; + ++src; + if (special(c)) + { + char *p = out_buffer_get_bytes (&dst, 2); + if (p == NULL) + { + out_buffer_mark_failed (&dst); + break; } - 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); + p[0] = '\\'; + p[1] = c; + } + else if (!printable(c)) + { + char *p = out_buffer_get_bytes (&dst, 4); + if (p == NULL) + { + out_buffer_mark_failed (pdst); + break; } - *dn++ = '.'; + p[0] = '\\'; + p[1] = '0' + (c / 100); + p[2] = '0' + ((c % 100) / 10); + p[3] = '0' + (c % 10); + } + else + out_buffer_add_byte (&dst, c); } - if (dn >= eom) { - __set_errno (EMSGSIZE); - return (-1); - } - *dn++ = '\0'; - return (dn - dst); + } + if (first) + /* Root domain. */ + out_buffer_add_byte (&dst, '.'); + out_buffer_add_byte (&dst, '\0'); + if (out_buffer_has_failed (&dst)) + { + out_buffer_mark_failed (pdst); + return NULL; + } + char *start = out_buffer_current (pdst); + *pdst = dst; + return start; +} +libresolv_hidden_def (__ns_name_ntop_buffer) + +int +ns_name_ntop (const u_char *src, char *dst, size_t dstsiz) +{ + struct out_buffer buf; + out_buffer_init (&buf, dst, dstsiz); + if (__ns_name_ntop_buffer (&buf, src) == NULL) + { + __set_errno (EMSGSIZE); + return -1; + } + return out_buffer_current (&buf) - (void *) dst; } libresolv_hidden_def (ns_name_ntop) strong_alias (ns_name_ntop, __ns_name_ntop) diff --git a/resolv/nss_dns/dns-network.c b/resolv/nss_dns/dns-network.c index 45f7f18057..135871093a 100644 --- a/resolv/nss_dns/dns-network.c +++ b/resolv/nss_dns/dns-network.c @@ -66,6 +66,9 @@ #include "nsswitch.h" #include <arpa/inet.h> +#include <arpa/nameser.h> +#include <out_buffer.h> +#include <resolv/resolv-internal.h> /* Maximum number of aliases we allow. */ #define MAX_NR_ALIASES 48 @@ -92,17 +95,10 @@ typedef union querybuf u_char buf[MAXPACKET]; } querybuf; -/* These functions are defined in res_comp.c. */ -#define NS_MAXCDNAME 255 /* maximum compressed domain name */ -extern int __ns_name_ntop (const u_char *, char *, size_t) __THROW; -extern int __ns_name_unpack (const u_char *, const u_char *, - const u_char *, u_char *, size_t) __THROW; - - /* 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 out_buffer, + int *errnop, int *h_errnop, lookup_method net_i); @@ -140,7 +136,9 @@ _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, + struct out_buffer outbuf; + out_buffer_init (&outbuf, buffer, buflen); + status = getanswer_r (net_buffer.buf, anslen, result, outbuf, errnop, herrnop, BYNAME); if (net_buffer.buf != orig_net_buffer) free (net_buffer.buf); @@ -217,7 +215,9 @@ _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, + struct out_buffer outbuf; + out_buffer_init (&outbuf, buffer, buflen); + status = getanswer_r (net_buffer.buf, anslen, result, outbuf, errnop, herrnop, BYADDR); if (net_buffer.buf != orig_net_buffer) free (net_buffer.buf); @@ -237,7 +237,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 out_buffer buf, int *errnop, int *h_errnop, lookup_method net_i) { /* @@ -254,16 +254,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 = out_buffer_get_array (&buf, char *, MAX_NR_ALIASES); + if (__glibc_unlikely (aliases == NULL)) { /* The buffer is too small. */ too_small: @@ -271,21 +264,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]; @@ -318,32 +305,17 @@ 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 = dn_expand (answer->buf, end_of_message, cp, bp, linebuflen); int type, class; - - 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) + int n = __ns_name_unpack (answer->buf, end_of_message, cp, + packtmp, sizeof packtmp); + if (n == -1) break; - cp += n; + cp += n; if (end_of_message - cp < 10) { __set_h_errno (NO_RECOVERY); @@ -365,7 +337,9 @@ 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 = __ns_name_ntop_buffer (&buf, packtmp)) == NULL) { if (errno == EMSGSIZE) goto too_small; @@ -373,7 +347,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. @@ -382,12 +356,9 @@ 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]) + if (alias_count + 1 < MAX_NR_ALIASES) { - *alias_pointer++ = bp; - n = strlen (bp) + 1; - bp += n; - linebuflen -= n; + aliases[alias_count++] = alias; result->n_addrtype = class == C_IN ? AF_INET : AF_UNSPEC; ++have_answer; } @@ -399,7 +370,7 @@ getanswer_r (const querybuf *answer, int anslen, struct netent *result, if (have_answer) { - *alias_pointer = NULL; + aliases[alias_count] = NULL; switch (net_i) { case BYADDR: diff --git a/resolv/resolv-internal.h b/resolv/resolv-internal.h index 99fc17c609..0453906599 100644 --- a/resolv/resolv-internal.h +++ b/resolv/resolv-internal.h @@ -32,4 +32,16 @@ res_use_inet6 (void) return _res.options & DEPRECATED_RES_USE_INET6; } +struct out_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 sets errno to EMSGSIZE). */ +char *__ns_name_ntop_buffer (struct out_buffer *, const u_char *) + __THROW internal_function; +libresolv_hidden_proto (__ns_name_ntop_buffer) + +int __ns_name_unpack (const u_char *, const u_char *, + const u_char *, u_char *, size_t) __THROW; + #endif /* _RESOLV_INTERNAL_H */ |