1 |
From e14a27723cc3a154d67f3f26e719d08c0ba9ad25 Mon Sep 17 00:00:00 2001 |
2 |
From: Florian Weimer <fweimer@redhat.com> |
3 |
Date: Thu, 13 Apr 2017 13:09:38 +0200 |
4 |
Subject: [PATCH] resolv: Reduce EDNS payload size to 1200 bytes [BZ #21361] |
5 |
|
6 |
This hardens the stub resolver against fragmentation-based attacks. |
7 |
--- |
8 |
[ Rebased for 2.22 /tmb ] |
9 |
Signed-off-by: Thomas Backlund <tmb@mageia.org> |
10 |
|
11 |
--- |
12 |
include/resolv.h | 3 |
13 |
resolv/Makefile | 4 |
14 |
resolv/res_mkquery.c | 28 ++ |
15 |
resolv/res_query.c | 23 +- |
16 |
resolv/resolv-internal.h | 43 ++++ |
17 |
resolv/tst-resolv-edns.c | 501 +++++++++++++++++++++++++++++++++++++++++++++++ |
18 |
6 files changed, 591 insertions(+), 11 deletions(-) |
19 |
|
20 |
#diff --git a/ChangeLog b/ChangeLog |
21 |
#index 2cdf82cc7e..1cd7a7b48a 100644 |
22 |
#--- a/ChangeLog |
23 |
#+++ b/ChangeLog |
24 |
#@@ -1,3 +1,24 @@ |
25 |
#+2017-04-13 Florian Weimer <fweimer@redhat.com> |
26 |
##+ |
27 |
#+ [BZ #21361] |
28 |
#+ Limit EDNS buffer size to 1200 bytes. |
29 |
#+ * include/resolv.h (__res_nopt): Remove declaration. |
30 |
#+ * resolv/Makefile (tests): tst-resolv-edns. |
31 |
#+ (tst-resolv-edns): Link with -lresolv, -lpthread. |
32 |
#+ * resolv/res_mkquery.c (__res_ntop): Limit EDNS buffer size to the |
33 |
#+ interval [512, 1200]. |
34 |
#+ * resolv/res_query.c (__libc_res_nquery): Use 1200 buffer size if |
35 |
#+ we can resize the buffer. |
36 |
#+ * resolv/resolv-internal.h (RESOLV_EDNS_BUFFER_SIZE): Define. |
37 |
#+ (__res_nopt): Declare. |
38 |
#+ * resolv/tst-resolv-edns.c: New file. |
39 |
#+ * resolv/resolv_test.h (struct resolv_edns_info): Define. |
40 |
#+ (struct resolv_response_context): Add edns member. |
41 |
#+ * resolv/resolv_test.c (struct query_info): Add edns member. |
42 |
#+ (parse_query): Extract EDNS information from the query. |
43 |
#+ (server_thread_udp_process_one): Propagate EDNS data. |
44 |
#+ (server_thread_tcp_client): Likewise. |
45 |
#+ |
46 |
# 2017-04-13 Florian Weimer <fweimer@redhat.com> |
47 |
# |
48 |
# [BZ #21359] |
49 |
#diff --git a/NEWS b/NEWS |
50 |
#index 28bb00887a..99288b5f22 100644 |
51 |
#--- a/NEWS |
52 |
#+++ b/NEWS |
53 |
#@@ -46,7 +46,8 @@ Version 2.26 |
54 |
# |
55 |
# Security related changes: |
56 |
# |
57 |
#- [Add security related changes here] |
58 |
#+* The DNS stub resolver limits the advertised UDP buffer size to 1200 bytes, |
59 |
#+ to avoid fragmentation-based spoofing attacks. |
60 |
# |
61 |
# The following bugs are resolved with this release: |
62 |
# |
63 |
diff -Nurp glibc-2.22.old/include/resolv.h glibc-2.22/include/resolv.h |
64 |
--- glibc-2.22.old/include/resolv.h 2015-08-05 09:42:21.000000000 +0300 |
65 |
+++ glibc-2.22/include/resolv.h 2017-12-16 01:10:19.368966932 +0200 |
66 |
@@ -38,8 +38,6 @@ extern void res_send_setrhook (res_send_ |
67 |
extern int res_ourserver_p (const res_state __statp, |
68 |
const struct sockaddr_in6 *__inp); |
69 |
extern void __res_iclose (res_state statp, bool free_addr); |
70 |
-extern int __res_nopt(res_state statp, int n0, u_char *buf, int buflen, |
71 |
- int anslen); |
72 |
libc_hidden_proto (__res_ninit) |
73 |
libc_hidden_proto (__res_maybe_init) |
74 |
libc_hidden_proto (__res_nclose) |
75 |
@@ -88,7 +86,6 @@ libresolv_hidden_proto (__res_nameinquer |
76 |
libresolv_hidden_proto (__res_queriesmatch) |
77 |
libresolv_hidden_proto (__res_nsend) |
78 |
libresolv_hidden_proto (__b64_ntop) |
79 |
-libresolv_hidden_proto (__res_nopt) |
80 |
libresolv_hidden_proto (__dn_count_labels) |
81 |
libresolv_hidden_proto (__p_secstodate) |
82 |
|
83 |
diff -Nurp glibc-2.20.old/resolv/Makefile glibc-2.20/resolv/Makefile |
84 |
--- glibc-2.20.old/resolv/Makefile |
85 |
+++ glibc-2.20/resolv/Makefile |
86 |
@@ -39,6 +39,7 @@ extra-libs := libresolv libnss_dns |
87 |
ifeq ($(have-thread-library),yes) |
88 |
extra-libs += libanl |
89 |
routines += gai_sigqueue |
90 |
+tests += tst-resolv-edns |
91 |
|
92 |
# This test sends millions of packets and is rather slow. |
93 |
xtests += tst-resolv-qtypes |
94 |
@@ -113,3 +114,4 @@ $(objpfx)mtrace-tst-leaks2.out: $(objpfx |
95 |
$(evaluate-test) |
96 |
|
97 |
$(objpfx)tst-resolv-qtypes: $(objpfx)libresolv.so $(shared-thread-library) |
98 |
+$(objpfx)tst-resolv-edns: $(objpfx)libresolv.so $(shared-thread-library) |
99 |
diff -Nurp glibc-2.22.old/resolv/res_mkquery.c glibc-2.22/resolv/res_mkquery.c |
100 |
--- glibc-2.22.old/resolv/res_mkquery.c 2017-12-16 00:58:28.803564502 +0200 |
101 |
+++ glibc-2.22/resolv/res_mkquery.c 2017-12-16 01:01:59.249647472 +0200 |
102 |
@@ -74,7 +74,7 @@ static const char rcsid[] = "$BINDId: re |
103 |
#include <netinet/in.h> |
104 |
#include <arpa/nameser.h> |
105 |
#include <netdb.h> |
106 |
-#include <resolv.h> |
107 |
+#include <resolv/resolv-internal.h> |
108 |
#include <stdio.h> |
109 |
#include <string.h> |
110 |
#include <sys/time.h> |
111 |
@@ -250,7 +250,30 @@ __res_nopt(res_state statp, |
112 |
*cp++ = 0; /* "." */ |
113 |
|
114 |
NS_PUT16(T_OPT, cp); /* TYPE */ |
115 |
- NS_PUT16(MIN(anslen, 0xffff), cp); /* CLASS = UDP payload size */ |
116 |
+ |
117 |
+ /* Lowering the advertised buffer size based on the actual |
118 |
+ answer buffer size is desirable because the server will |
119 |
+ minimize the reply to fit into the UDP packet (and A |
120 |
+ non-minimal response might not fit the buffer). |
121 |
+ |
122 |
+ The RESOLV_EDNS_BUFFER_SIZE limit could still result in TCP |
123 |
+ fallback and a non-minimal response which has to be |
124 |
+ hard-truncated in the stub resolver, but this is price to |
125 |
+ pay for avoiding fragmentation. (This issue does not |
126 |
+ affect the nss_dns functions because they use the stub |
127 |
+ resolver in such a way that it allocates a properly sized |
128 |
+ response buffer.) */ |
129 |
+ { |
130 |
+ uint16_t buffer_size; |
131 |
+ if (anslen < 512) |
132 |
+ buffer_size = 512; |
133 |
+ else if (anslen > RESOLV_EDNS_BUFFER_SIZE) |
134 |
+ buffer_size = RESOLV_EDNS_BUFFER_SIZE; |
135 |
+ else |
136 |
+ buffer_size = anslen; |
137 |
+ NS_PUT16 (buffer_size, cp); |
138 |
+ } |
139 |
+ |
140 |
*cp++ = NOERROR; /* extended RCODE */ |
141 |
*cp++ = 0; /* EDNS version */ |
142 |
|
143 |
@@ -268,4 +291,3 @@ __res_nopt(res_state statp, |
144 |
|
145 |
return cp - buf; |
146 |
} |
147 |
-libresolv_hidden_def (__res_nopt) |
148 |
diff -Nurp glibc-2.22.old/resolv/resolv-internal.h glibc-2.22/resolv/resolv-internal.h |
149 |
--- glibc-2.22.old/resolv/resolv-internal.h 1970-01-01 02:00:00.000000000 +0200 |
150 |
+++ glibc-2.22/resolv/resolv-internal.h 2017-12-16 01:17:47.557518586 +0200 |
151 |
@@ -0,0 +1,43 @@ |
152 |
+/* libresolv interfaces for internal use across glibc. |
153 |
+ Copyright (C) 2016-2017 Free Software Foundation, Inc. |
154 |
+ This file is part of the GNU C Library. |
155 |
+ |
156 |
+ The GNU C Library is free software; you can redistribute it and/or |
157 |
+ modify it under the terms of the GNU Lesser General Public |
158 |
+ License as published by the Free Software Foundation; either |
159 |
+ version 2.1 of the License, or (at your option) any later version. |
160 |
+ |
161 |
+ The GNU C Library is distributed in the hope that it will be useful, |
162 |
+ but WITHOUT ANY WARRANTY; without even the implied warranty of |
163 |
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
164 |
+ Lesser General Public License for more details. |
165 |
+ |
166 |
+ You should have received a copy of the GNU Lesser General Public |
167 |
+ License along with the GNU C Library; if not, see |
168 |
+ <http://www.gnu.org/licenses/>. */ |
169 |
+ |
170 |
+#ifndef _RESOLV_INTERNAL_H |
171 |
+#define _RESOLV_INTERNAL_H 1 |
172 |
+ |
173 |
+#include <resolv.h> |
174 |
+#include <stdbool.h> |
175 |
+ |
176 |
+enum |
177 |
+ { |
178 |
+ /* The advertized EDNS buffer size. The value 1200 is derived |
179 |
+ from the IPv6 minimum MTU (1280 bytes) minus some arbitrary |
180 |
+ space for tunneling overhead. If the DNS server does not react |
181 |
+ to ICMP Fragmentation Needed But DF Set messages, this should |
182 |
+ avoid all UDP fragments on current networks. Avoiding UDP |
183 |
+ fragments is desirable because it prevents fragmentation-based |
184 |
+ spoofing attacks because the randomness in a DNS packet is |
185 |
+ concentrated in the first fragment (with the headers) and does |
186 |
+ not protect subsequent fragments. */ |
187 |
+ RESOLV_EDNS_BUFFER_SIZE = 1200, |
188 |
+ }; |
189 |
+ |
190 |
+/* Add an OPT record to a DNS query. */ |
191 |
+int __res_nopt (res_state, int n0, unsigned char *buf, int buflen, |
192 |
+ int anslen) attribute_hidden; |
193 |
+ |
194 |
+#endif /* _RESOLV_INTERNAL_H */ |
195 |
diff -Nurp glibc-2.22.old/resolv/res_query.c glibc-2.22/resolv/res_query.c |
196 |
--- glibc-2.22.old/resolv/res_query.c 2017-12-16 00:58:28.803564502 +0200 |
197 |
+++ glibc-2.22/resolv/res_query.c 2017-12-16 01:01:59.249647472 +0200 |
198 |
@@ -82,6 +82,7 @@ static const char rcsid[] = "$BINDId: re |
199 |
#include <stdio.h> |
200 |
#include <stdlib.h> |
201 |
#include <string.h> |
202 |
+#include <resolv/resolv-internal.h> |
203 |
|
204 |
/* Options. Leave them on. */ |
205 |
/* #undef DEBUG */ |
206 |
@@ -151,7 +152,10 @@ __libc_res_nquery(res_state statp, |
207 |
if ((oflags & RES_F_EDNS0ERR) == 0 |
208 |
&& (statp->options & (RES_USE_EDNS0|RES_USE_DNSSEC)) != 0) |
209 |
{ |
210 |
- n = __res_nopt(statp, n, query1, bufsize, anslen / 2); |
211 |
+ /* Use RESOLV_EDNS_BUFFER_SIZE because the receive |
212 |
+ buffer can be reallocated. */ |
213 |
+ n = __res_nopt (statp, n, query1, bufsize, |
214 |
+ RESOLV_EDNS_BUFFER_SIZE); |
215 |
if (n < 0) |
216 |
goto unspec_nomem; |
217 |
} |
218 |
@@ -172,8 +176,10 @@ __libc_res_nquery(res_state statp, |
219 |
if (n > 0 |
220 |
&& (oflags & RES_F_EDNS0ERR) == 0 |
221 |
&& (statp->options & (RES_USE_EDNS0|RES_USE_DNSSEC)) != 0) |
222 |
- n = __res_nopt(statp, n, query2, bufsize - nused - n, |
223 |
- anslen / 2); |
224 |
+ /* Use RESOLV_EDNS_BUFFER_SIZE because the receive |
225 |
+ buffer can be reallocated. */ |
226 |
+ n = __res_nopt (statp, n, query2, bufsize, |
227 |
+ RESOLV_EDNS_BUFFER_SIZE); |
228 |
nquery2 = n; |
229 |
} |
230 |
|
231 |
@@ -187,7 +193,16 @@ __libc_res_nquery(res_state statp, |
232 |
if (n > 0 |
233 |
&& (oflags & RES_F_EDNS0ERR) == 0 |
234 |
&& (statp->options & (RES_USE_EDNS0|RES_USE_DNSSEC)) != 0) |
235 |
- n = __res_nopt(statp, n, query1, bufsize, anslen); |
236 |
+ { |
237 |
+ /* Use RESOLV_EDNS_BUFFER_SIZE if the receive buffer |
238 |
+ can be reallocated. */ |
239 |
+ size_t advertise; |
240 |
+ if (answerp == NULL) |
241 |
+ advertise = anslen; |
242 |
+ else |
243 |
+ advertise = RESOLV_EDNS_BUFFER_SIZE; |
244 |
+ n = __res_nopt (statp, n, query1, bufsize, advertise); |
245 |
+ } |
246 |
|
247 |
nquery1 = n; |
248 |
} |
249 |
diff -Nurp glibc-2.22.old/resolv/tst-resolv-edns.c glibc-2.22/resolv/tst-resolv-edns.c |
250 |
--- glibc-2.22.old/resolv/tst-resolv-edns.c 1970-01-01 02:00:00.000000000 +0200 |
251 |
+++ glibc-2.22/resolv/tst-resolv-edns.c 2017-12-16 01:02:04.695727230 +0200 |
252 |
@@ -0,0 +1,501 @@ |
253 |
+/* Test EDNS handling in the stub resolver. |
254 |
+ Copyright (C) 2016-2017 Free Software Foundation, Inc. |
255 |
+ This file is part of the GNU C Library. |
256 |
+ |
257 |
+ The GNU C Library is free software; you can redistribute it and/or |
258 |
+ modify it under the terms of the GNU Lesser General Public |
259 |
+ License as published by the Free Software Foundation; either |
260 |
+ version 2.1 of the License, or (at your option) any later version. |
261 |
+ |
262 |
+ The GNU C Library is distributed in the hope that it will be useful, |
263 |
+ but WITHOUT ANY WARRANTY; without even the implied warranty of |
264 |
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
265 |
+ Lesser General Public License for more details. |
266 |
+ |
267 |
+ You should have received a copy of the GNU Lesser General Public |
268 |
+ License along with the GNU C Library; if not, see |
269 |
+ <http://www.gnu.org/licenses/>. */ |
270 |
+ |
271 |
+#include <errno.h> |
272 |
+#include <netdb.h> |
273 |
+#include <stdio.h> |
274 |
+#include <stdlib.h> |
275 |
+#include <string.h> |
276 |
+#include <support/check.h> |
277 |
+#include <support/resolv_test.h> |
278 |
+#include <support/support.h> |
279 |
+#include <support/test-driver.h> |
280 |
+#include <support/xthread.h> |
281 |
+ |
282 |
+/* Data produced by a test query. */ |
283 |
+struct response_data |
284 |
+{ |
285 |
+ char *qname; |
286 |
+ uint16_t qtype; |
287 |
+ struct resolv_edns_info edns; |
288 |
+}; |
289 |
+ |
290 |
+/* Global array used by put_response and get_response to record |
291 |
+ response data. The test DNS server returns the index of the array |
292 |
+ element which contains the actual response data. This enables the |
293 |
+ test case to return arbitrary amounts of data with the limited |
294 |
+ number of bits which fit into an IP addres. |
295 |
+ |
296 |
+ The volatile specifier is needed because the test case accesses |
297 |
+ these variables from a callback function called from a function |
298 |
+ which is marked as __THROW (i.e., a leaf function which actually is |
299 |
+ not). */ |
300 |
+static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; |
301 |
+static struct response_data ** volatile response_data_array; |
302 |
+volatile static size_t response_data_count; |
303 |
+ |
304 |
+/* Extract information from the query, store it in a struct |
305 |
+ response_data object, and return its index in the |
306 |
+ response_data_array. */ |
307 |
+static unsigned int |
308 |
+put_response (const struct resolv_response_context *ctx, |
309 |
+ const char *qname, uint16_t qtype) |
310 |
+{ |
311 |
+ xpthread_mutex_lock (&mutex); |
312 |
+ ++response_data_count; |
313 |
+ /* We only can represent 2**24 indexes in 10.0.0.0/8. */ |
314 |
+ TEST_VERIFY (response_data_count < (1 << 24)); |
315 |
+ response_data_array = xrealloc |
316 |
+ (response_data_array, sizeof (*response_data_array) * response_data_count); |
317 |
+ unsigned int index = response_data_count - 1; |
318 |
+ struct response_data *data = xmalloc (sizeof (*data)); |
319 |
+ *data = (struct response_data) |
320 |
+ { |
321 |
+ .qname = xstrdup (qname), |
322 |
+ .qtype = qtype, |
323 |
+ .edns = ctx->edns, |
324 |
+ }; |
325 |
+ response_data_array[index] = data; |
326 |
+ xpthread_mutex_unlock (&mutex); |
327 |
+ return index; |
328 |
+} |
329 |
+ |
330 |
+/* Verify the index into the response_data array and return the data |
331 |
+ at it. */ |
332 |
+static struct response_data * |
333 |
+get_response (unsigned int index) |
334 |
+{ |
335 |
+ xpthread_mutex_lock (&mutex); |
336 |
+ TEST_VERIFY_EXIT (index < response_data_count); |
337 |
+ struct response_data *result = response_data_array[index]; |
338 |
+ xpthread_mutex_unlock (&mutex); |
339 |
+ return result; |
340 |
+} |
341 |
+ |
342 |
+/* Deallocate all response data. */ |
343 |
+static void |
344 |
+free_response_data (void) |
345 |
+{ |
346 |
+ xpthread_mutex_lock (&mutex); |
347 |
+ size_t count = response_data_count; |
348 |
+ struct response_data **array = response_data_array; |
349 |
+ for (unsigned int i = 0; i < count; ++i) |
350 |
+ { |
351 |
+ struct response_data *data = array[i]; |
352 |
+ free (data->qname); |
353 |
+ free (data); |
354 |
+ } |
355 |
+ free (array); |
356 |
+ response_data_array = NULL; |
357 |
+ response_data_count = 0; |
358 |
+ xpthread_mutex_unlock (&mutex); |
359 |
+} |
360 |
+ |
361 |
+#define EDNS_PROBE_EXAMPLE "edns-probe.example" |
362 |
+ |
363 |
+static void |
364 |
+response (const struct resolv_response_context *ctx, |
365 |
+ struct resolv_response_builder *b, |
366 |
+ const char *qname, uint16_t qclass, uint16_t qtype) |
367 |
+{ |
368 |
+ TEST_VERIFY_EXIT (qname != NULL); |
369 |
+ |
370 |
+ /* The "tcp." prefix can be used to request TCP fallback. */ |
371 |
+ const char *qname_compare = qname; |
372 |
+ bool force_tcp; |
373 |
+ if (strncmp ("tcp.", qname_compare, strlen ("tcp.")) == 0) |
374 |
+ { |
375 |
+ force_tcp = true; |
376 |
+ qname_compare += strlen ("tcp."); |
377 |
+ } |
378 |
+ else |
379 |
+ force_tcp = false; |
380 |
+ |
381 |
+ enum {edns_probe} requested_qname; |
382 |
+ if (strcmp (qname_compare, EDNS_PROBE_EXAMPLE) == 0) |
383 |
+ requested_qname = edns_probe; |
384 |
+ else |
385 |
+ { |
386 |
+ support_record_failure (); |
387 |
+ printf ("error: unexpected QNAME: %s\n", qname); |
388 |
+ return; |
389 |
+ } |
390 |
+ TEST_VERIFY_EXIT (qclass == C_IN); |
391 |
+ struct resolv_response_flags flags = {.tc = force_tcp && !ctx->tcp}; |
392 |
+ resolv_response_init (b, flags); |
393 |
+ resolv_response_add_question (b, qname, qclass, qtype); |
394 |
+ if (flags.tc) |
395 |
+ return; |
396 |
+ |
397 |
+ if (test_verbose) |
398 |
+ printf ("info: edns=%d payload_size=%d\n", |
399 |
+ ctx->edns.active, ctx->edns.payload_size); |
400 |
+ |
401 |
+ /* Encode the response_data object in multiple address records. |
402 |
+ Each record carries two bytes of payload data, and an index. */ |
403 |
+ resolv_response_section (b, ns_s_an); |
404 |
+ switch (requested_qname) |
405 |
+ { |
406 |
+ case edns_probe: |
407 |
+ { |
408 |
+ unsigned int index = put_response (ctx, qname, qtype); |
409 |
+ switch (qtype) |
410 |
+ { |
411 |
+ case T_A: |
412 |
+ { |
413 |
+ uint32_t addr = htonl (0x0a000000 | index); |
414 |
+ resolv_response_open_record (b, qname, qclass, qtype, 0); |
415 |
+ resolv_response_add_data (b, &addr, sizeof (addr)); |
416 |
+ resolv_response_close_record (b); |
417 |
+ } |
418 |
+ break; |
419 |
+ case T_AAAA: |
420 |
+ { |
421 |
+ char addr[16] |
422 |
+ = {0x20, 0x01, 0xd, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
423 |
+ index >> 16, index >> 8, index}; |
424 |
+ resolv_response_open_record (b, qname, qclass, qtype, 0); |
425 |
+ resolv_response_add_data (b, &addr, sizeof (addr)); |
426 |
+ resolv_response_close_record (b); |
427 |
+ } |
428 |
+ } |
429 |
+ } |
430 |
+ break; |
431 |
+ } |
432 |
+} |
433 |
+ |
434 |
+/* Update *DATA with data from ADDRESS of SIZE. Set the corresponding |
435 |
+ flag in SHADOW for each byte written. */ |
436 |
+static struct response_data * |
437 |
+decode_address (const void *address, size_t size) |
438 |
+{ |
439 |
+ switch (size) |
440 |
+ { |
441 |
+ case 4: |
442 |
+ TEST_VERIFY (memcmp (address, "\x0a", 1) == 0); |
443 |
+ break; |
444 |
+ case 16: |
445 |
+ TEST_VERIFY (memcmp (address, "\x20\x01\x0d\xb8", 4) == 0); |
446 |
+ break; |
447 |
+ default: |
448 |
+ FAIL_EXIT1 ("unexpected address size %zu", size); |
449 |
+ } |
450 |
+ const unsigned char *addr = address; |
451 |
+ unsigned int index = addr[size - 3] * 256 * 256 |
452 |
+ + addr[size - 2] * 256 |
453 |
+ + addr[size - 1]; |
454 |
+ return get_response (index); |
455 |
+} |
456 |
+ |
457 |
+static struct response_data * |
458 |
+decode_hostent (struct hostent *e) |
459 |
+{ |
460 |
+ TEST_VERIFY_EXIT (e != NULL); |
461 |
+ TEST_VERIFY_EXIT (e->h_addr_list[0] != NULL); |
462 |
+ TEST_VERIFY (e->h_addr_list[1] == NULL); |
463 |
+ return decode_address (e->h_addr_list[0], e->h_length); |
464 |
+} |
465 |
+ |
466 |
+static struct response_data * |
467 |
+decode_addrinfo (struct addrinfo *ai, int family) |
468 |
+{ |
469 |
+ struct response_data *data = NULL; |
470 |
+ while (ai != NULL) |
471 |
+ { |
472 |
+ if (ai->ai_family == family) |
473 |
+ { |
474 |
+ struct response_data *new_data; |
475 |
+ switch (family) |
476 |
+ { |
477 |
+ case AF_INET: |
478 |
+ { |
479 |
+ struct sockaddr_in *pin = (struct sockaddr_in *) ai->ai_addr; |
480 |
+ new_data = decode_address (&pin->sin_addr.s_addr, 4); |
481 |
+ } |
482 |
+ break; |
483 |
+ case AF_INET6: |
484 |
+ { |
485 |
+ struct sockaddr_in6 *pin = (struct sockaddr_in6 *) ai->ai_addr; |
486 |
+ new_data = decode_address (&pin->sin6_addr.s6_addr, 16); |
487 |
+ } |
488 |
+ break; |
489 |
+ default: |
490 |
+ FAIL_EXIT1 ("invalid address family %d", ai->ai_family); |
491 |
+ } |
492 |
+ if (data == NULL) |
493 |
+ data = new_data; |
494 |
+ else |
495 |
+ /* Check pointer equality because this should be the same |
496 |
+ response (same index). */ |
497 |
+ TEST_VERIFY (data == new_data); |
498 |
+ } |
499 |
+ ai = ai->ai_next; |
500 |
+ } |
501 |
+ TEST_VERIFY_EXIT (data != NULL); |
502 |
+ return data; |
503 |
+} |
504 |
+ |
505 |
+/* Updated by the main test loop in accordance with what is set in |
506 |
+ _res.options. */ |
507 |
+static bool use_edns; |
508 |
+static bool use_dnssec; |
509 |
+ |
510 |
+/* Verify the decoded response data against the flags above. */ |
511 |
+static void |
512 |
+verify_response_data_payload (struct response_data *data, |
513 |
+ size_t expected_payload) |
514 |
+{ |
515 |
+ bool edns = use_edns || use_dnssec; |
516 |
+ TEST_VERIFY (data->edns.active == edns); |
517 |
+ if (!edns) |
518 |
+ expected_payload = 0; |
519 |
+ if (data->edns.payload_size != expected_payload) |
520 |
+ { |
521 |
+ support_record_failure (); |
522 |
+ printf ("error: unexpected payload size %d (edns=%d)\n", |
523 |
+ (int) data->edns.payload_size, edns); |
524 |
+ } |
525 |
+ uint16_t expected_flags = 0; |
526 |
+ if (use_dnssec) |
527 |
+ expected_flags |= 0x8000; /* DO flag. */ |
528 |
+ if (data->edns.flags != expected_flags) |
529 |
+ { |
530 |
+ support_record_failure (); |
531 |
+ printf ("error: unexpected EDNS flags 0x%04x (edns=%d)\n", |
532 |
+ (int) data->edns.flags, edns); |
533 |
+ } |
534 |
+} |
535 |
+ |
536 |
+/* Same as verify_response_data_payload, but use the default |
537 |
+ payload. */ |
538 |
+static void |
539 |
+verify_response_data (struct response_data *data) |
540 |
+{ |
541 |
+ verify_response_data_payload (data, 1200); |
542 |
+} |
543 |
+ |
544 |
+static void |
545 |
+check_hostent (struct hostent *e) |
546 |
+{ |
547 |
+ TEST_VERIFY_EXIT (e != NULL); |
548 |
+ verify_response_data (decode_hostent (e)); |
549 |
+} |
550 |
+ |
551 |
+static void |
552 |
+do_ai (int family) |
553 |
+{ |
554 |
+ struct addrinfo hints = { .ai_family = family }; |
555 |
+ struct addrinfo *ai; |
556 |
+ int ret = getaddrinfo (EDNS_PROBE_EXAMPLE, "80", &hints, &ai); |
557 |
+ TEST_VERIFY_EXIT (ret == 0); |
558 |
+ switch (family) |
559 |
+ { |
560 |
+ case AF_INET: |
561 |
+ case AF_INET6: |
562 |
+ verify_response_data (decode_addrinfo (ai, family)); |
563 |
+ break; |
564 |
+ case AF_UNSPEC: |
565 |
+ verify_response_data (decode_addrinfo (ai, AF_INET)); |
566 |
+ verify_response_data (decode_addrinfo (ai, AF_INET6)); |
567 |
+ break; |
568 |
+ default: |
569 |
+ FAIL_EXIT1 ("invalid address family %d", family); |
570 |
+ } |
571 |
+ freeaddrinfo (ai); |
572 |
+} |
573 |
+ |
574 |
+enum res_op |
575 |
+{ |
576 |
+ res_op_search, |
577 |
+ res_op_query, |
578 |
+ res_op_querydomain, |
579 |
+ res_op_nsearch, |
580 |
+ res_op_nquery, |
581 |
+ res_op_nquerydomain, |
582 |
+ |
583 |
+ res_op_last = res_op_nquerydomain, |
584 |
+}; |
585 |
+ |
586 |
+static const char * |
587 |
+res_op_string (enum res_op op) |
588 |
+{ |
589 |
+ switch (op) |
590 |
+ { |
591 |
+ case res_op_search: |
592 |
+ return "res_search"; |
593 |
+ case res_op_query: |
594 |
+ return "res_query"; |
595 |
+ case res_op_querydomain: |
596 |
+ return "res_querydomain"; |
597 |
+ case res_op_nsearch: |
598 |
+ return "res_nsearch"; |
599 |
+ case res_op_nquery: |
600 |
+ return "res_nquery"; |
601 |
+ case res_op_nquerydomain: |
602 |
+ return "res_nquerydomain"; |
603 |
+ } |
604 |
+ FAIL_EXIT1 ("invalid res_op value %d", (int) op); |
605 |
+} |
606 |
+ |
607 |
+/* Call libresolv function OP to look up PROBE_NAME, with an answer |
608 |
+ buffer of SIZE bytes. Check that the advertised UDP buffer size is |
609 |
+ in fact EXPECTED_BUFFER_SIZE. */ |
610 |
+static void |
611 |
+do_res_search (const char *probe_name, enum res_op op, size_t size, |
612 |
+ size_t expected_buffer_size) |
613 |
+{ |
614 |
+ if (test_verbose) |
615 |
+ printf ("info: testing %s with buffer size %zu\n", |
616 |
+ res_op_string (op), size); |
617 |
+ unsigned char *buffer = xmalloc (size); |
618 |
+ int ret = -1; |
619 |
+ switch (op) |
620 |
+ { |
621 |
+ case res_op_search: |
622 |
+ ret = res_search (probe_name, C_IN, T_A, buffer, size); |
623 |
+ break; |
624 |
+ case res_op_query: |
625 |
+ ret = res_query (probe_name, C_IN, T_A, buffer, size); |
626 |
+ break; |
627 |
+ case res_op_nsearch: |
628 |
+ ret = res_nsearch (&_res, probe_name, C_IN, T_A, buffer, size); |
629 |
+ break; |
630 |
+ case res_op_nquery: |
631 |
+ ret = res_nquery (&_res, probe_name, C_IN, T_A, buffer, size); |
632 |
+ break; |
633 |
+ case res_op_querydomain: |
634 |
+ case res_op_nquerydomain: |
635 |
+ { |
636 |
+ char *example_stripped = xstrdup (probe_name); |
637 |
+ char *dot_example = strstr (example_stripped, ".example"); |
638 |
+ if (dot_example != NULL && strcmp (dot_example, ".example") == 0) |
639 |
+ { |
640 |
+ /* Truncate the domain name. */ |
641 |
+ *dot_example = '\0'; |
642 |
+ if (op == res_op_querydomain) |
643 |
+ ret = res_querydomain |
644 |
+ (example_stripped, "example", C_IN, T_A, buffer, size); |
645 |
+ else |
646 |
+ ret = res_nquerydomain |
647 |
+ (&_res, example_stripped, "example", C_IN, T_A, buffer, size); |
648 |
+ } |
649 |
+ else |
650 |
+ FAIL_EXIT1 ("invalid probe name: %s", probe_name); |
651 |
+ free (example_stripped); |
652 |
+ } |
653 |
+ break; |
654 |
+ } |
655 |
+ TEST_VERIFY_EXIT (ret > 12); |
656 |
+ unsigned char *end = buffer + ret; |
657 |
+ |
658 |
+ HEADER *hd = (HEADER *) buffer; |
659 |
+ TEST_VERIFY (ntohs (hd->qdcount) == 1); |
660 |
+ TEST_VERIFY (ntohs (hd->ancount) == 1); |
661 |
+ /* Skip over the header. */ |
662 |
+ unsigned char *p = buffer + sizeof (*hd); |
663 |
+ /* Skip over the question. */ |
664 |
+ ret = dn_skipname (p, end); |
665 |
+ TEST_VERIFY_EXIT (ret > 0); |
666 |
+ p += ret; |
667 |
+ TEST_VERIFY_EXIT (end - p >= 4); |
668 |
+ p += 4; |
669 |
+ /* Skip over the RNAME and the RR header, but stop at the RDATA |
670 |
+ length. */ |
671 |
+ ret = dn_skipname (p, end); |
672 |
+ TEST_VERIFY_EXIT (ret > 0); |
673 |
+ p += ret; |
674 |
+ TEST_VERIFY_EXIT (end - p >= 2 + 2 + 4 + 2 + 4); |
675 |
+ p += 2 + 2 + 4; |
676 |
+ /* The IP address should be 4 bytes long. */ |
677 |
+ TEST_VERIFY_EXIT (p[0] == 0); |
678 |
+ TEST_VERIFY_EXIT (p[1] == 4); |
679 |
+ /* Extract the address information. */ |
680 |
+ p += 2; |
681 |
+ struct response_data *data = decode_address (p, 4); |
682 |
+ |
683 |
+ verify_response_data_payload (data, expected_buffer_size); |
684 |
+ |
685 |
+ free (buffer); |
686 |
+} |
687 |
+ |
688 |
+static void |
689 |
+run_test (const char *probe_name) |
690 |
+{ |
691 |
+ if (test_verbose) |
692 |
+ printf ("\ninfo: * use_edns=%d use_dnssec=%d\n", |
693 |
+ use_edns, use_dnssec); |
694 |
+ check_hostent (gethostbyname (probe_name)); |
695 |
+ check_hostent (gethostbyname2 (probe_name, AF_INET)); |
696 |
+ check_hostent (gethostbyname2 (probe_name, AF_INET6)); |
697 |
+ do_ai (AF_UNSPEC); |
698 |
+ do_ai (AF_INET); |
699 |
+ do_ai (AF_INET6); |
700 |
+ |
701 |
+ for (int op = 0; op <= res_op_last; ++op) |
702 |
+ { |
703 |
+ do_res_search (probe_name, op, 301, 512); |
704 |
+ do_res_search (probe_name, op, 511, 512); |
705 |
+ do_res_search (probe_name, op, 512, 512); |
706 |
+ do_res_search (probe_name, op, 513, 513); |
707 |
+ do_res_search (probe_name, op, 657, 657); |
708 |
+ do_res_search (probe_name, op, 1199, 1199); |
709 |
+ do_res_search (probe_name, op, 1200, 1200); |
710 |
+ do_res_search (probe_name, op, 1201, 1200); |
711 |
+ do_res_search (probe_name, op, 65535, 1200); |
712 |
+ } |
713 |
+} |
714 |
+ |
715 |
+static int |
716 |
+do_test (void) |
717 |
+{ |
718 |
+ for (int do_edns = 0; do_edns < 2; ++do_edns) |
719 |
+ for (int do_dnssec = 0; do_dnssec < 2; ++do_dnssec) |
720 |
+ for (int do_tcp = 0; do_tcp < 2; ++do_tcp) |
721 |
+ { |
722 |
+ struct resolv_test *aux = resolv_test_start |
723 |
+ ((struct resolv_redirect_config) |
724 |
+ { |
725 |
+ .response_callback = response, |
726 |
+ }); |
727 |
+ |
728 |
+ use_edns = do_edns; |
729 |
+ if (do_edns) |
730 |
+ _res.options |= RES_USE_EDNS0; |
731 |
+ use_dnssec = do_dnssec; |
732 |
+ if (do_dnssec) |
733 |
+ _res.options |= RES_USE_DNSSEC; |
734 |
+ |
735 |
+ char *probe_name = xstrdup (EDNS_PROBE_EXAMPLE); |
736 |
+ if (do_tcp) |
737 |
+ { |
738 |
+ char *n = xasprintf ("tcp.%s", probe_name); |
739 |
+ free (probe_name); |
740 |
+ probe_name = n; |
741 |
+ } |
742 |
+ |
743 |
+ run_test (probe_name); |
744 |
+ |
745 |
+ free (probe_name); |
746 |
+ resolv_test_end (aux); |
747 |
+ } |
748 |
+ |
749 |
+ free_response_data (); |
750 |
+ return 0; |
751 |
+} |
752 |
+ |
753 |
+#include <support/test-driver.c> |