summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFlorian Weimer <fweimer@redhat.com>2017-03-10 20:45:25 +0100
committerFlorian Weimer <fweimer@redhat.com>2017-03-10 20:45:25 +0100
commitddd21a211369658bc087cc5c56cd577bb8370857 (patch)
treed6b0a5128122316f5daa610dad4abc36325fdab7
parent60f9423b6b02d79f8fef65552ecd21e0fd097774 (diff)
downloadglibc-fw/out_buffer.tar.gz
WIP struct out_bufferfw/out_buffer
-rw-r--r--include/out_buffer.h247
-rw-r--r--malloc/Makefile3
-rw-r--r--malloc/Versions3
-rw-r--r--malloc/out_buffer_get_array.c50
-rw-r--r--malloc/tst-out_buffer.c480
-rw-r--r--resolv/Versions2
-rw-r--r--resolv/ns_name.c160
-rw-r--r--resolv/nss_dns/dns-network.c81
-rw-r--r--resolv/resolv-internal.h12
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 */