From c5b7ab645351fd3767d096a3c500355a9a896878 Mon Sep 17 00:00:00 2001 From: Sona Sarmadi Date: Mon, 11 Sep 2017 13:15:37 +0200 Subject: glibc:CVE-2017-12132 The DNS stub resolver in the glibc or libc6 before version 2.26, when EDNS support is enabled, will solicit large UDP responses from name servers, potentially simplifying off-path DNS spoofing attacks due to IP fragmentation. Reference: https://security-tracker.debian.org/tracker/CVE-2017-12132 Signed-off-by: Sona Sarmadi Signed-off-by: Martin Borg --- recipes-core/glibc/glibc/CVE-2017-12132.patch | 866 ++++++++++++++++++++++++++ recipes-core/glibc/glibc_%.bbappend | 1 + 2 files changed, 867 insertions(+) create mode 100644 recipes-core/glibc/glibc/CVE-2017-12132.patch (limited to 'recipes-core') diff --git a/recipes-core/glibc/glibc/CVE-2017-12132.patch b/recipes-core/glibc/glibc/CVE-2017-12132.patch new file mode 100644 index 0000000..dc184db --- /dev/null +++ b/recipes-core/glibc/glibc/CVE-2017-12132.patch @@ -0,0 +1,866 @@ +From 44cf81c6c008316876cfcc8208ae982621949e0e Mon Sep 17 00:00:00 2001 +From: Sona Sarmadi +Date: Mon, 11 Sep 2017 10:55:45 +0200 +Subject: [PATCH] glibc: CVE-2017-12132 + +From e14a27723cc3a154d67f3f26e719d08c0ba9ad25 Mon Sep 17 00:00:00 2001 +From: Florian Weimer +Date: Thu, 13 Apr 2017 13:09:38 +0200 +Subject: [PATCH] resolv: Reduce EDNS payload size to 1200 bytes [BZ #21361] + +This hardens the stub resolver against fragmentation-based attacks. + +CVE: CVE-2017-12132 +Upstream-Status: Backport [https://sourceware.org/git/gitweb.cgi?p=glibc.git;a=patch;h=e14a27723cc3a154d67f3f26e719d08c0ba9ad25] + +Signed-off-by: Sona Sarmadi +--- + ChangeLog | 21 ++ + include/resolv.h | 3 - + resolv/Makefile | 2 + + resolv/res_mkquery.c | 28 ++- + resolv/res_query.c | 23 ++- + resolv/resolv-internal.h | 18 ++ + resolv/tst-resolv-edns.c | 501 +++++++++++++++++++++++++++++++++++++++++++++++ + support/resolv_test.c | 56 +++++- + support/resolv_test.h | 11 ++ + 9 files changed, 650 insertions(+), 13 deletions(-) + create mode 100644 resolv/tst-resolv-edns.c + +diff --git a/ChangeLog b/ChangeLog +index 7bfdf45..a0c2f51 100644 +--- a/ChangeLog ++++ b/ChangeLog +@@ -1,3 +1,24 @@ ++2017-04-13 Florian Weimer ++ ++ [BZ #21361] ++ Limit EDNS buffer size to 1200 bytes. ++ * include/resolv.h (__res_nopt): Remove declaration. ++ * resolv/Makefile (tests): tst-resolv-edns. ++ (tst-resolv-edns): Link with -lresolv, -lpthread. ++ * resolv/res_mkquery.c (__res_ntop): Limit EDNS buffer size to the ++ interval [512, 1200]. ++ * resolv/res_query.c (__libc_res_nquery): Use 1200 buffer size if ++ we can resize the buffer. ++ * resolv/resolv-internal.h (RESOLV_EDNS_BUFFER_SIZE): Define. ++ (__res_nopt): Declare. ++ * resolv/tst-resolv-edns.c: New file. ++ * resolv/resolv_test.h (struct resolv_edns_info): Define. ++ (struct resolv_response_context): Add edns member. ++ * resolv/resolv_test.c (struct query_info): Add edns member. ++ (parse_query): Extract EDNS information from the query. ++ (server_thread_udp_process_one): Propagate EDNS data. ++ (server_thread_tcp_client): Likewise. ++ + 2017-06-19 Florian Weimer + + [BZ #21624] +diff --git a/include/resolv.h b/include/resolv.h +index 95dcd3c..e8f477c 100644 +--- a/include/resolv.h ++++ b/include/resolv.h +@@ -37,8 +37,6 @@ extern void res_pquery (const res_state __statp, const unsigned char *__msg, + extern int res_ourserver_p (const res_state __statp, + const struct sockaddr_in6 *__inp); + extern void __res_iclose (res_state statp, bool free_addr); +-extern int __res_nopt(res_state statp, int n0, unsigned char *buf, int buflen, +- int anslen); + libc_hidden_proto (__res_ninit) + libc_hidden_proto (__res_maybe_init) + libc_hidden_proto (__res_nclose) +@@ -91,7 +89,6 @@ libresolv_hidden_proto (__res_nameinquery) + libresolv_hidden_proto (__res_queriesmatch) + libresolv_hidden_proto (__res_nsend) + libresolv_hidden_proto (__b64_ntop) +-libresolv_hidden_proto (__res_nopt) + libresolv_hidden_proto (__dn_count_labels) + libresolv_hidden_proto (__p_secstodate) + +diff --git a/resolv/Makefile b/resolv/Makefile +index fdc37ed..01086d5 100644 +--- a/resolv/Makefile ++++ b/resolv/Makefile +@@ -46,6 +46,7 @@ tests += \ + tst-res_hconf_reorder \ + tst-res_use_inet6 \ + tst-resolv-basic \ ++ tst-resolv-edns \ + tst-resolv-network \ + tst-resolv-search \ + +@@ -124,6 +125,7 @@ $(objpfx)tst-bug18665-tcp: $(objpfx)libresolv.so $(shared-thread-library) + $(objpfx)tst-bug18665: $(objpfx)libresolv.so $(shared-thread-library) + $(objpfx)tst-res_use_inet6: $(objpfx)libresolv.so $(shared-thread-library) + $(objpfx)tst-resolv-basic: $(objpfx)libresolv.so $(shared-thread-library) ++$(objpfx)tst-resolv-edns: $(objpfx)libresolv.so $(shared-thread-library) + $(objpfx)tst-resolv-network: $(objpfx)libresolv.so $(shared-thread-library) + $(objpfx)tst-resolv-qtypes: $(objpfx)libresolv.so $(shared-thread-library) + $(objpfx)tst-resolv-search: $(objpfx)libresolv.so $(shared-thread-library) +diff --git a/resolv/res_mkquery.c b/resolv/res_mkquery.c +index d80b531..5a0bb10 100644 +--- a/resolv/res_mkquery.c ++++ b/resolv/res_mkquery.c +@@ -69,7 +69,7 @@ + #include + #include + #include +-#include ++#include + #include + #include + #include +@@ -243,7 +243,30 @@ __res_nopt(res_state statp, + *cp++ = 0; /* "." */ + + NS_PUT16(T_OPT, cp); /* TYPE */ +- NS_PUT16(MIN(anslen, 0xffff), cp); /* CLASS = UDP payload size */ ++ ++ /* Lowering the advertised buffer size based on the actual ++ answer buffer size is desirable because the server will ++ minimize the reply to fit into the UDP packet (and A ++ non-minimal response might not fit the buffer). ++ ++ The RESOLV_EDNS_BUFFER_SIZE limit could still result in TCP ++ fallback and a non-minimal response which has to be ++ hard-truncated in the stub resolver, but this is price to ++ pay for avoiding fragmentation. (This issue does not ++ affect the nss_dns functions because they use the stub ++ resolver in such a way that it allocates a properly sized ++ response buffer.) */ ++ { ++ uint16_t buffer_size; ++ if (anslen < 512) ++ buffer_size = 512; ++ else if (anslen > RESOLV_EDNS_BUFFER_SIZE) ++ buffer_size = RESOLV_EDNS_BUFFER_SIZE; ++ else ++ buffer_size = anslen; ++ NS_PUT16 (buffer_size, cp); ++ } ++ + *cp++ = NOERROR; /* extended RCODE */ + *cp++ = 0; /* EDNS version */ + +@@ -261,4 +284,3 @@ __res_nopt(res_state statp, + + return cp - buf; + } +-libresolv_hidden_def (__res_nopt) +diff --git a/resolv/res_query.c b/resolv/res_query.c +index 07dc6f6..57156d0 100644 +--- a/resolv/res_query.c ++++ b/resolv/res_query.c +@@ -77,6 +77,7 @@ + #include + #include + #include ++#include + + /* Options. Leave them on. */ + /* #undef DEBUG */ +@@ -146,7 +147,10 @@ __libc_res_nquery(res_state statp, + if ((oflags & RES_F_EDNS0ERR) == 0 + && (statp->options & (RES_USE_EDNS0|RES_USE_DNSSEC)) != 0) + { +- n = __res_nopt(statp, n, query1, bufsize, anslen / 2); ++ /* Use RESOLV_EDNS_BUFFER_SIZE because the receive ++ buffer can be reallocated. */ ++ n = __res_nopt (statp, n, query1, bufsize, ++ RESOLV_EDNS_BUFFER_SIZE); + if (n < 0) + goto unspec_nomem; + } +@@ -167,8 +171,10 @@ __libc_res_nquery(res_state statp, + if (n > 0 + && (oflags & RES_F_EDNS0ERR) == 0 + && (statp->options & (RES_USE_EDNS0|RES_USE_DNSSEC)) != 0) +- n = __res_nopt(statp, n, query2, bufsize - nused - n, +- anslen / 2); ++ /* Use RESOLV_EDNS_BUFFER_SIZE because the receive ++ buffer can be reallocated. */ ++ n = __res_nopt (statp, n, query2, bufsize, ++ RESOLV_EDNS_BUFFER_SIZE); + nquery2 = n; + } + +@@ -182,7 +188,16 @@ __libc_res_nquery(res_state statp, + if (n > 0 + && (oflags & RES_F_EDNS0ERR) == 0 + && (statp->options & (RES_USE_EDNS0|RES_USE_DNSSEC)) != 0) +- n = __res_nopt(statp, n, query1, bufsize, anslen); ++ { ++ /* Use RESOLV_EDNS_BUFFER_SIZE if the receive buffer ++ can be reallocated. */ ++ size_t advertise; ++ if (answerp == NULL) ++ advertise = anslen; ++ else ++ advertise = RESOLV_EDNS_BUFFER_SIZE; ++ n = __res_nopt (statp, n, query1, bufsize, advertise); ++ } + + nquery1 = n; + } +diff --git a/resolv/resolv-internal.h b/resolv/resolv-internal.h +index 99fc17c..76fbe2f 100644 +--- a/resolv/resolv-internal.h ++++ b/resolv/resolv-internal.h +@@ -32,4 +32,22 @@ res_use_inet6 (void) + return _res.options & DEPRECATED_RES_USE_INET6; + } + ++enum ++ { ++ /* The advertized EDNS buffer size. The value 1200 is derived ++ from the IPv6 minimum MTU (1280 bytes) minus some arbitrary ++ space for tunneling overhead. If the DNS server does not react ++ to ICMP Fragmentation Needed But DF Set messages, this should ++ avoid all UDP fragments on current networks. Avoiding UDP ++ fragments is desirable because it prevents fragmentation-based ++ spoofing attacks because the randomness in a DNS packet is ++ concentrated in the first fragment (with the headers) and does ++ not protect subsequent fragments. */ ++ RESOLV_EDNS_BUFFER_SIZE = 1200, ++ }; ++ ++/* Add an OPT record to a DNS query. */ ++int __res_nopt (res_state, int n0, unsigned char *buf, int buflen, ++ int anslen) attribute_hidden; ++ + #endif /* _RESOLV_INTERNAL_H */ +diff --git a/resolv/tst-resolv-edns.c b/resolv/tst-resolv-edns.c +new file mode 100644 +index 0000000..f17dbc3 +--- /dev/null ++++ b/resolv/tst-resolv-edns.c +@@ -0,0 +1,501 @@ ++/* Test EDNS handling in the stub resolver. ++ Copyright (C) 2016-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 ++ . */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++/* Data produced by a test query. */ ++struct response_data ++{ ++ char *qname; ++ uint16_t qtype; ++ struct resolv_edns_info edns; ++}; ++ ++/* Global array used by put_response and get_response to record ++ response data. The test DNS server returns the index of the array ++ element which contains the actual response data. This enables the ++ test case to return arbitrary amounts of data with the limited ++ number of bits which fit into an IP addres. ++ ++ The volatile specifier is needed because the test case accesses ++ these variables from a callback function called from a function ++ which is marked as __THROW (i.e., a leaf function which actually is ++ not). */ ++static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; ++static struct response_data ** volatile response_data_array; ++volatile static size_t response_data_count; ++ ++/* Extract information from the query, store it in a struct ++ response_data object, and return its index in the ++ response_data_array. */ ++static unsigned int ++put_response (const struct resolv_response_context *ctx, ++ const char *qname, uint16_t qtype) ++{ ++ xpthread_mutex_lock (&mutex); ++ ++response_data_count; ++ /* We only can represent 2**24 indexes in 10.0.0.0/8. */ ++ TEST_VERIFY (response_data_count < (1 << 24)); ++ response_data_array = xrealloc ++ (response_data_array, sizeof (*response_data_array) * response_data_count); ++ unsigned int index = response_data_count - 1; ++ struct response_data *data = xmalloc (sizeof (*data)); ++ *data = (struct response_data) ++ { ++ .qname = xstrdup (qname), ++ .qtype = qtype, ++ .edns = ctx->edns, ++ }; ++ response_data_array[index] = data; ++ xpthread_mutex_unlock (&mutex); ++ return index; ++} ++ ++/* Verify the index into the response_data array and return the data ++ at it. */ ++static struct response_data * ++get_response (unsigned int index) ++{ ++ xpthread_mutex_lock (&mutex); ++ TEST_VERIFY_EXIT (index < response_data_count); ++ struct response_data *result = response_data_array[index]; ++ xpthread_mutex_unlock (&mutex); ++ return result; ++} ++ ++/* Deallocate all response data. */ ++static void ++free_response_data (void) ++{ ++ xpthread_mutex_lock (&mutex); ++ size_t count = response_data_count; ++ struct response_data **array = response_data_array; ++ for (unsigned int i = 0; i < count; ++i) ++ { ++ struct response_data *data = array[i]; ++ free (data->qname); ++ free (data); ++ } ++ free (array); ++ response_data_array = NULL; ++ response_data_count = 0; ++ xpthread_mutex_unlock (&mutex); ++} ++ ++#define EDNS_PROBE_EXAMPLE "edns-probe.example" ++ ++static void ++response (const struct resolv_response_context *ctx, ++ struct resolv_response_builder *b, ++ const char *qname, uint16_t qclass, uint16_t qtype) ++{ ++ TEST_VERIFY_EXIT (qname != NULL); ++ ++ /* The "tcp." prefix can be used to request TCP fallback. */ ++ const char *qname_compare = qname; ++ bool force_tcp; ++ if (strncmp ("tcp.", qname_compare, strlen ("tcp.")) == 0) ++ { ++ force_tcp = true; ++ qname_compare += strlen ("tcp."); ++ } ++ else ++ force_tcp = false; ++ ++ enum {edns_probe} requested_qname; ++ if (strcmp (qname_compare, EDNS_PROBE_EXAMPLE) == 0) ++ requested_qname = edns_probe; ++ else ++ { ++ support_record_failure (); ++ printf ("error: unexpected QNAME: %s\n", qname); ++ return; ++ } ++ TEST_VERIFY_EXIT (qclass == C_IN); ++ struct resolv_response_flags flags = {.tc = force_tcp && !ctx->tcp}; ++ resolv_response_init (b, flags); ++ resolv_response_add_question (b, qname, qclass, qtype); ++ if (flags.tc) ++ return; ++ ++ if (test_verbose) ++ printf ("info: edns=%d payload_size=%d\n", ++ ctx->edns.active, ctx->edns.payload_size); ++ ++ /* Encode the response_data object in multiple address records. ++ Each record carries two bytes of payload data, and an index. */ ++ resolv_response_section (b, ns_s_an); ++ switch (requested_qname) ++ { ++ case edns_probe: ++ { ++ unsigned int index = put_response (ctx, qname, qtype); ++ switch (qtype) ++ { ++ case T_A: ++ { ++ uint32_t addr = htonl (0x0a000000 | index); ++ resolv_response_open_record (b, qname, qclass, qtype, 0); ++ resolv_response_add_data (b, &addr, sizeof (addr)); ++ resolv_response_close_record (b); ++ } ++ break; ++ case T_AAAA: ++ { ++ char addr[16] ++ = {0x20, 0x01, 0xd, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, ++ index >> 16, index >> 8, index}; ++ resolv_response_open_record (b, qname, qclass, qtype, 0); ++ resolv_response_add_data (b, &addr, sizeof (addr)); ++ resolv_response_close_record (b); ++ } ++ } ++ } ++ break; ++ } ++} ++ ++/* Update *DATA with data from ADDRESS of SIZE. Set the corresponding ++ flag in SHADOW for each byte written. */ ++static struct response_data * ++decode_address (const void *address, size_t size) ++{ ++ switch (size) ++ { ++ case 4: ++ TEST_VERIFY (memcmp (address, "\x0a", 1) == 0); ++ break; ++ case 16: ++ TEST_VERIFY (memcmp (address, "\x20\x01\x0d\xb8", 4) == 0); ++ break; ++ default: ++ FAIL_EXIT1 ("unexpected address size %zu", size); ++ } ++ const unsigned char *addr = address; ++ unsigned int index = addr[size - 3] * 256 * 256 ++ + addr[size - 2] * 256 ++ + addr[size - 1]; ++ return get_response (index); ++} ++ ++static struct response_data * ++decode_hostent (struct hostent *e) ++{ ++ TEST_VERIFY_EXIT (e != NULL); ++ TEST_VERIFY_EXIT (e->h_addr_list[0] != NULL); ++ TEST_VERIFY (e->h_addr_list[1] == NULL); ++ return decode_address (e->h_addr_list[0], e->h_length); ++} ++ ++static struct response_data * ++decode_addrinfo (struct addrinfo *ai, int family) ++{ ++ struct response_data *data = NULL; ++ while (ai != NULL) ++ { ++ if (ai->ai_family == family) ++ { ++ struct response_data *new_data; ++ switch (family) ++ { ++ case AF_INET: ++ { ++ struct sockaddr_in *pin = (struct sockaddr_in *) ai->ai_addr; ++ new_data = decode_address (&pin->sin_addr.s_addr, 4); ++ } ++ break; ++ case AF_INET6: ++ { ++ struct sockaddr_in6 *pin = (struct sockaddr_in6 *) ai->ai_addr; ++ new_data = decode_address (&pin->sin6_addr.s6_addr, 16); ++ } ++ break; ++ default: ++ FAIL_EXIT1 ("invalid address family %d", ai->ai_family); ++ } ++ if (data == NULL) ++ data = new_data; ++ else ++ /* Check pointer equality because this should be the same ++ response (same index). */ ++ TEST_VERIFY (data == new_data); ++ } ++ ai = ai->ai_next; ++ } ++ TEST_VERIFY_EXIT (data != NULL); ++ return data; ++} ++ ++/* Updated by the main test loop in accordance with what is set in ++ _res.options. */ ++static bool use_edns; ++static bool use_dnssec; ++ ++/* Verify the decoded response data against the flags above. */ ++static void ++verify_response_data_payload (struct response_data *data, ++ size_t expected_payload) ++{ ++ bool edns = use_edns || use_dnssec; ++ TEST_VERIFY (data->edns.active == edns); ++ if (!edns) ++ expected_payload = 0; ++ if (data->edns.payload_size != expected_payload) ++ { ++ support_record_failure (); ++ printf ("error: unexpected payload size %d (edns=%d)\n", ++ (int) data->edns.payload_size, edns); ++ } ++ uint16_t expected_flags = 0; ++ if (use_dnssec) ++ expected_flags |= 0x8000; /* DO flag. */ ++ if (data->edns.flags != expected_flags) ++ { ++ support_record_failure (); ++ printf ("error: unexpected EDNS flags 0x%04x (edns=%d)\n", ++ (int) data->edns.flags, edns); ++ } ++} ++ ++/* Same as verify_response_data_payload, but use the default ++ payload. */ ++static void ++verify_response_data (struct response_data *data) ++{ ++ verify_response_data_payload (data, 1200); ++} ++ ++static void ++check_hostent (struct hostent *e) ++{ ++ TEST_VERIFY_EXIT (e != NULL); ++ verify_response_data (decode_hostent (e)); ++} ++ ++static void ++do_ai (int family) ++{ ++ struct addrinfo hints = { .ai_family = family }; ++ struct addrinfo *ai; ++ int ret = getaddrinfo (EDNS_PROBE_EXAMPLE, "80", &hints, &ai); ++ TEST_VERIFY_EXIT (ret == 0); ++ switch (family) ++ { ++ case AF_INET: ++ case AF_INET6: ++ verify_response_data (decode_addrinfo (ai, family)); ++ break; ++ case AF_UNSPEC: ++ verify_response_data (decode_addrinfo (ai, AF_INET)); ++ verify_response_data (decode_addrinfo (ai, AF_INET6)); ++ break; ++ default: ++ FAIL_EXIT1 ("invalid address family %d", family); ++ } ++ freeaddrinfo (ai); ++} ++ ++enum res_op ++{ ++ res_op_search, ++ res_op_query, ++ res_op_querydomain, ++ res_op_nsearch, ++ res_op_nquery, ++ res_op_nquerydomain, ++ ++ res_op_last = res_op_nquerydomain, ++}; ++ ++static const char * ++res_op_string (enum res_op op) ++{ ++ switch (op) ++ { ++ case res_op_search: ++ return "res_search"; ++ case res_op_query: ++ return "res_query"; ++ case res_op_querydomain: ++ return "res_querydomain"; ++ case res_op_nsearch: ++ return "res_nsearch"; ++ case res_op_nquery: ++ return "res_nquery"; ++ case res_op_nquerydomain: ++ return "res_nquerydomain"; ++ } ++ FAIL_EXIT1 ("invalid res_op value %d", (int) op); ++} ++ ++/* Call libresolv function OP to look up PROBE_NAME, with an answer ++ buffer of SIZE bytes. Check that the advertised UDP buffer size is ++ in fact EXPECTED_BUFFER_SIZE. */ ++static void ++do_res_search (const char *probe_name, enum res_op op, size_t size, ++ size_t expected_buffer_size) ++{ ++ if (test_verbose) ++ printf ("info: testing %s with buffer size %zu\n", ++ res_op_string (op), size); ++ unsigned char *buffer = xmalloc (size); ++ int ret = -1; ++ switch (op) ++ { ++ case res_op_search: ++ ret = res_search (probe_name, C_IN, T_A, buffer, size); ++ break; ++ case res_op_query: ++ ret = res_query (probe_name, C_IN, T_A, buffer, size); ++ break; ++ case res_op_nsearch: ++ ret = res_nsearch (&_res, probe_name, C_IN, T_A, buffer, size); ++ break; ++ case res_op_nquery: ++ ret = res_nquery (&_res, probe_name, C_IN, T_A, buffer, size); ++ break; ++ case res_op_querydomain: ++ case res_op_nquerydomain: ++ { ++ char *example_stripped = xstrdup (probe_name); ++ char *dot_example = strstr (example_stripped, ".example"); ++ if (dot_example != NULL && strcmp (dot_example, ".example") == 0) ++ { ++ /* Truncate the domain name. */ ++ *dot_example = '\0'; ++ if (op == res_op_querydomain) ++ ret = res_querydomain ++ (example_stripped, "example", C_IN, T_A, buffer, size); ++ else ++ ret = res_nquerydomain ++ (&_res, example_stripped, "example", C_IN, T_A, buffer, size); ++ } ++ else ++ FAIL_EXIT1 ("invalid probe name: %s", probe_name); ++ free (example_stripped); ++ } ++ break; ++ } ++ TEST_VERIFY_EXIT (ret > 12); ++ unsigned char *end = buffer + ret; ++ ++ HEADER *hd = (HEADER *) buffer; ++ TEST_VERIFY (ntohs (hd->qdcount) == 1); ++ TEST_VERIFY (ntohs (hd->ancount) == 1); ++ /* Skip over the header. */ ++ unsigned char *p = buffer + sizeof (*hd); ++ /* Skip over the question. */ ++ ret = dn_skipname (p, end); ++ TEST_VERIFY_EXIT (ret > 0); ++ p += ret; ++ TEST_VERIFY_EXIT (end - p >= 4); ++ p += 4; ++ /* Skip over the RNAME and the RR header, but stop at the RDATA ++ length. */ ++ ret = dn_skipname (p, end); ++ TEST_VERIFY_EXIT (ret > 0); ++ p += ret; ++ TEST_VERIFY_EXIT (end - p >= 2 + 2 + 4 + 2 + 4); ++ p += 2 + 2 + 4; ++ /* The IP address should be 4 bytes long. */ ++ TEST_VERIFY_EXIT (p[0] == 0); ++ TEST_VERIFY_EXIT (p[1] == 4); ++ /* Extract the address information. */ ++ p += 2; ++ struct response_data *data = decode_address (p, 4); ++ ++ verify_response_data_payload (data, expected_buffer_size); ++ ++ free (buffer); ++} ++ ++static void ++run_test (const char *probe_name) ++{ ++ if (test_verbose) ++ printf ("\ninfo: * use_edns=%d use_dnssec=%d\n", ++ use_edns, use_dnssec); ++ check_hostent (gethostbyname (probe_name)); ++ check_hostent (gethostbyname2 (probe_name, AF_INET)); ++ check_hostent (gethostbyname2 (probe_name, AF_INET6)); ++ do_ai (AF_UNSPEC); ++ do_ai (AF_INET); ++ do_ai (AF_INET6); ++ ++ for (int op = 0; op <= res_op_last; ++op) ++ { ++ do_res_search (probe_name, op, 301, 512); ++ do_res_search (probe_name, op, 511, 512); ++ do_res_search (probe_name, op, 512, 512); ++ do_res_search (probe_name, op, 513, 513); ++ do_res_search (probe_name, op, 657, 657); ++ do_res_search (probe_name, op, 1199, 1199); ++ do_res_search (probe_name, op, 1200, 1200); ++ do_res_search (probe_name, op, 1201, 1200); ++ do_res_search (probe_name, op, 65535, 1200); ++ } ++} ++ ++static int ++do_test (void) ++{ ++ for (int do_edns = 0; do_edns < 2; ++do_edns) ++ for (int do_dnssec = 0; do_dnssec < 2; ++do_dnssec) ++ for (int do_tcp = 0; do_tcp < 2; ++do_tcp) ++ { ++ struct resolv_test *aux = resolv_test_start ++ ((struct resolv_redirect_config) ++ { ++ .response_callback = response, ++ }); ++ ++ use_edns = do_edns; ++ if (do_edns) ++ _res.options |= RES_USE_EDNS0; ++ use_dnssec = do_dnssec; ++ if (do_dnssec) ++ _res.options |= RES_USE_DNSSEC; ++ ++ char *probe_name = xstrdup (EDNS_PROBE_EXAMPLE); ++ if (do_tcp) ++ { ++ char *n = xasprintf ("tcp.%s", probe_name); ++ free (probe_name); ++ probe_name = n; ++ } ++ ++ run_test (probe_name); ++ ++ free (probe_name); ++ resolv_test_end (aux); ++ } ++ ++ free_response_data (); ++ return 0; ++} ++ ++#include +diff --git a/support/resolv_test.c b/support/resolv_test.c +index 2d0ea3c..6b3554f 100644 +--- a/support/resolv_test.c ++++ b/support/resolv_test.c +@@ -428,6 +428,7 @@ struct query_info + char qname[MAXDNAME]; + uint16_t qclass; + uint16_t qtype; ++ struct resolv_edns_info edns; + }; + + /* Update *INFO from the specified DNS packet. */ +@@ -435,10 +436,26 @@ static void + parse_query (struct query_info *info, + const unsigned char *buffer, size_t length) + { +- if (length < 12) ++ HEADER hd; ++ _Static_assert (sizeof (hd) == 12, "DNS header size"); ++ if (length < sizeof (hd)) + FAIL_EXIT1 ("malformed DNS query: too short: %zu bytes", length); +- +- int ret = dn_expand (buffer, buffer + length, buffer + 12, ++ memcpy (&hd, buffer, sizeof (hd)); ++ ++ if (ntohs (hd.qdcount) != 1) ++ FAIL_EXIT1 ("malformed DNS query: wrong question count: %d", ++ (int) ntohs (hd.qdcount)); ++ if (ntohs (hd.ancount) != 0) ++ FAIL_EXIT1 ("malformed DNS query: wrong answer count: %d", ++ (int) ntohs (hd.ancount)); ++ if (ntohs (hd.nscount) != 0) ++ FAIL_EXIT1 ("malformed DNS query: wrong authority count: %d", ++ (int) ntohs (hd.nscount)); ++ if (ntohs (hd.arcount) > 1) ++ FAIL_EXIT1 ("malformed DNS query: wrong additional count: %d", ++ (int) ntohs (hd.arcount)); ++ ++ int ret = dn_expand (buffer, buffer + length, buffer + sizeof (hd), + info->qname, sizeof (info->qname)); + if (ret < 0) + FAIL_EXIT1 ("malformed DNS query: cannot uncompress QNAME"); +@@ -456,6 +473,37 @@ parse_query (struct query_info *info, + memcpy (&qtype_qclass, buffer + 12 + ret, sizeof (qtype_qclass)); + info->qclass = ntohs (qtype_qclass.qclass); + info->qtype = ntohs (qtype_qclass.qtype); ++ ++ memset (&info->edns, 0, sizeof (info->edns)); ++ if (ntohs (hd.arcount) > 0) ++ { ++ /* Parse EDNS record. */ ++ struct __attribute__ ((packed, aligned (1))) ++ { ++ uint8_t root; ++ uint16_t rtype; ++ uint16_t payload; ++ uint8_t edns_extended_rcode; ++ uint8_t edns_version; ++ uint16_t flags; ++ uint16_t rdatalen; ++ } rr; ++ _Static_assert (sizeof (rr) == 11, "EDNS record size"); ++ ++ if (remaining < 4 + sizeof (rr)) ++ FAIL_EXIT1 ("mailformed DNS query: no room for EDNS record"); ++ memcpy (&rr, buffer + 12 + ret + 4, sizeof (rr)); ++ if (rr.root != 0) ++ FAIL_EXIT1 ("malformed DNS query: invalid OPT RNAME: %d\n", rr.root); ++ if (rr.rtype != htons (41)) ++ FAIL_EXIT1 ("malformed DNS query: invalid OPT type: %d\n", ++ ntohs (rr.rtype)); ++ info->edns.active = true; ++ info->edns.extended_rcode = rr.edns_extended_rcode; ++ info->edns.version = rr.edns_version; ++ info->edns.flags = ntohs (rr.flags); ++ info->edns.payload_size = ntohs (rr.payload); ++ } + } + + +@@ -585,6 +633,7 @@ server_thread_udp_process_one (struct resolv_test *obj, int server_index) + .query_length = length, + .server_index = server_index, + .tcp = false, ++ .edns = qinfo.edns, + }; + struct resolv_response_builder *b = response_builder_allocate (query, length); + obj->config.response_callback +@@ -820,6 +869,7 @@ server_thread_tcp_client (void *arg) + .query_length = query_length, + .server_index = closure->server_index, + .tcp = true, ++ .edns = qinfo.edns, + }; + struct resolv_response_builder *b = response_builder_allocate + (query_buffer, query_length); +diff --git a/support/resolv_test.h b/support/resolv_test.h +index 7a9f1f7..6498751 100644 +--- a/support/resolv_test.h ++++ b/support/resolv_test.h +@@ -25,6 +25,16 @@ + + __BEGIN_DECLS + ++/* Information about EDNS properties of a DNS query. */ ++struct resolv_edns_info ++{ ++ bool active; ++ uint8_t extended_rcode; ++ uint8_t version; ++ uint16_t flags; ++ uint16_t payload_size; ++}; ++ + /* This struct provides context information when the response callback + specified in struct resolv_redirect_config is invoked. */ + struct resolv_response_context +@@ -33,6 +43,7 @@ struct resolv_response_context + size_t query_length; + int server_index; + bool tcp; ++ struct resolv_edns_info edns; + }; + + /* This opaque struct is used to construct responses from within the +-- +1.9.1 + diff --git a/recipes-core/glibc/glibc_%.bbappend b/recipes-core/glibc/glibc_%.bbappend index 1ef0688..0e2cb2e 100644 --- a/recipes-core/glibc/glibc_%.bbappend +++ b/recipes-core/glibc/glibc_%.bbappend @@ -2,5 +2,6 @@ FILESEXTRAPATHS_prepend := "${THISDIR}/${PN}:" SRC_URI += "file://CVE-2017-1000366.patch \ + file://CVE-2017-12132.patch \ " -- cgit v1.2.3-54-g00ecf