diff options
-rw-r--r-- | include/arpa/nameser.h | 8 | ||||
-rw-r--r-- | resolv/Makefile | 5 | ||||
-rw-r--r-- | resolv/ns_name_length_uncompressed.c | 72 | ||||
-rw-r--r-- | resolv/tst-ns_name_length_uncompressed.c | 135 |
4 files changed, 220 insertions, 0 deletions
diff --git a/include/arpa/nameser.h b/include/arpa/nameser.h index bb1dede187..6e4808f00d 100644 --- a/include/arpa/nameser.h +++ b/include/arpa/nameser.h @@ -95,5 +95,13 @@ libc_hidden_proto (__ns_name_unpack) extern __typeof (ns_samename) __libc_ns_samename; libc_hidden_proto (__libc_ns_samename) +/* Packet parser helper functions. */ + +/* Verify that P points to an uncompressed domain name in wire format. + On success, return the length of the encoded name, including the + terminating null byte. On failure, return -1 and set errno. EOM + must point one past the last byte in the packet. */ +int __ns_name_length_uncompressed (const unsigned char *p, + const unsigned char *eom) attribute_hidden; # endif /* !_ISOMAC */ #endif diff --git a/resolv/Makefile b/resolv/Makefile index 76daf22578..804a3edd0f 100644 --- a/resolv/Makefile +++ b/resolv/Makefile @@ -40,6 +40,7 @@ routines := \ inet_pton \ ns_makecanon \ ns_name_compress \ + ns_name_length_uncompressed \ ns_name_ntop \ ns_name_pack \ ns_name_pton \ @@ -109,6 +110,10 @@ tests-static += tst-resolv-txnid-collision tests-internal += tst-ns_samebinaryname tests-static += tst-ns_samebinaryname +# Likewise for __ns_name_length_uncompressed. +tests-internal += tst-ns_name_length_uncompressed +tests-static += tst-ns_name_length_uncompressed + # These tests need libdl. ifeq (yes,$(build-shared)) tests += \ diff --git a/resolv/ns_name_length_uncompressed.c b/resolv/ns_name_length_uncompressed.c new file mode 100644 index 0000000000..51296b47ef --- /dev/null +++ b/resolv/ns_name_length_uncompressed.c @@ -0,0 +1,72 @@ +/* Skip over an uncompressed name in wire format. + Copyright (C) 2022 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 + <https://www.gnu.org/licenses/>. */ + +#include <arpa/nameser.h> +#include <errno.h> +#include <stdbool.h> + +int +__ns_name_length_uncompressed (const unsigned char *p, + const unsigned char *eom) +{ + const unsigned char *start = p; + + while (true) + { + if (p == eom) + { + /* Truncated packet: no room for label length. */ + __set_errno (EMSGSIZE); + return -1; + } + + unsigned char b = *p; + ++p; + if (b == 0) + { + /* Root label. */ + size_t length = p - start; + if (length > NS_MAXCDNAME) + { + /* Domain name too long. */ + __set_errno (EMSGSIZE); + return -1; + } + return length; + } + + if (b <= 63) + { + /* Regular label. */ + if (b <= eom - p) + p += b; + else + { + /* Truncated packet: label incomplete. */ + __set_errno (EMSGSIZE); + return -1; + } + } + else + { + /* Compression reference or corrupted label length. */ + __set_errno (EMSGSIZE); + return -1; + } + } +} diff --git a/resolv/tst-ns_name_length_uncompressed.c b/resolv/tst-ns_name_length_uncompressed.c new file mode 100644 index 0000000000..c4a2904db7 --- /dev/null +++ b/resolv/tst-ns_name_length_uncompressed.c @@ -0,0 +1,135 @@ +/* Test __ns_name_length_uncompressed. + Copyright (C) 2022 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 + <https://www.gnu.org/licenses/>. */ + +#include <arpa/nameser.h> +#include <array_length.h> +#include <errno.h> +#include <stdio.h> +#include <support/check.h> +#include <support/next_to_fault.h> + +/* Reference implementation based on other building blocks. */ +static int +reference_length (const unsigned char *p, const unsigned char *eom) +{ + unsigned char buf[NS_MAXCDNAME]; + int n = __ns_name_unpack (p, eom, p, buf, sizeof (buf)); + if (n < 0) + return n; + const unsigned char *q = buf; + if (__ns_name_skip (&q, array_end (buf)) < 0) + return -1; + if (q - buf != n) + /* Compressed name. */ + return -1; + return n; +} + +static int +do_test (void) +{ + { + unsigned char buf[] = { 3, 'w', 'w', 'w', 0, 0, 0 }; + TEST_COMPARE (reference_length (buf, array_end (buf)), sizeof (buf) - 2); + TEST_COMPARE (__ns_name_length_uncompressed (buf, array_end (buf)), + sizeof (buf) - 2); + TEST_COMPARE (reference_length (array_end (buf) - 1, array_end (buf)), 1); + TEST_COMPARE (__ns_name_length_uncompressed (array_end (buf) - 1, + array_end (buf)), 1); + buf[4] = 0xc0; /* Forward compression reference. */ + buf[5] = 0x06; + TEST_COMPARE (reference_length (buf, array_end (buf)), -1); + TEST_COMPARE (__ns_name_length_uncompressed (buf, array_end (buf)), -1); + } + + struct support_next_to_fault ntf = support_next_to_fault_allocate (300); + + /* Buffer region with all possible bytes at start and end. */ + for (int length = 1; length <= 300; ++length) + { + unsigned char *end = (unsigned char *) ntf.buffer + ntf.length; + unsigned char *start = end - length; + memset (start, 'X', length); + for (int first = 0; first <= 255; ++first) + { + *start = first; + for (int last = 0; last <= 255; ++last) + { + start[length - 1] = last; + TEST_COMPARE (reference_length (start, end), + __ns_name_length_uncompressed (start, end)); + } + } + } + + /* Poor man's fuzz testing: patch two bytes. */ + { + unsigned char ref[] = + { + 7, 'e', 'x', 'a', 'm', 'p', 'l', 'e', 3, 'n', 'e', 't', 0, 0, 0 + }; + TEST_COMPARE (reference_length (ref, array_end (ref)), 13); + TEST_COMPARE (__ns_name_length_uncompressed (ref, array_end (ref)), 13); + + int good = 0; + int bad = 0; + for (int length = 1; length <= sizeof (ref); ++length) + { + unsigned char *end = (unsigned char *) ntf.buffer + ntf.length; + unsigned char *start = end - length; + memcpy (start, ref, length); + + for (int patch1_pos = 0; patch1_pos < length; ++patch1_pos) + { + for (int patch1_value = 0; patch1_value <= 255; ++patch1_value) + { + start[patch1_pos] = patch1_value; + for (int patch2_pos = 0; patch2_pos < length; ++patch2_pos) + { + for (int patch2_value = 0; patch2_value <= 255; + ++patch2_value) + { + start[patch2_pos] = patch2_value; + int expected = reference_length (start, end); + errno = EINVAL; + int actual + = __ns_name_length_uncompressed (start, end); + if (actual > 0) + ++good; + else + { + TEST_COMPARE (errno, EMSGSIZE); + ++bad; + } + TEST_COMPARE (expected, actual); + } + start[patch2_pos] = ref[patch2_pos]; + } + } + start[patch1_pos] = ref[patch1_pos]; + } + } + printf ("info: patched inputs with success: %d\n", good); + printf ("info: patched inputs with failure: %d\n", bad); + } + + support_next_to_fault_free (&ntf); + return 0; +} + +#include <support/test-driver.c> |