1 From 15b60ddf935a531269bb8c68198de012a4967156 Mon Sep 17 00:00:00 2001
2 From: Simon Kelley <simon@thekelleys.org.uk>
3 Date: Wed, 18 Nov 2020 18:34:55 +0000
4 Subject: Handle multiple identical near simultaneous DNS queries better.
6 Previously, such queries would all be forwarded
7 independently. This is, in theory, inefficent but in practise
8 not a problem, _except_ that is means that an answer for any
9 of the forwarded queries will be accepted and cached.
10 An attacker can send a query multiple times, and for each repeat,
11 another {port, ID} becomes capable of accepting the answer he is
12 sending in the blind, to random IDs and ports. The chance of a
13 succesful attack is therefore multiplied by the number of repeats
14 of the query. The new behaviour detects repeated queries and
15 merely stores the clients sending repeats so that when the
16 first query completes, the answer can be sent to all the
17 clients who asked. Refer: CERT VU#434904.
20 src/dnsmasq.h | 19 ++++---
21 src/forward.c | 142 ++++++++++++++++++++++++++++++++++++++++++--------
22 3 files changed, 147 insertions(+), 30 deletions(-)
28 Be sure to only accept UDP DNS query replies at the address
29 from which the query was originated. This keeps as much entropy
30 - in the {query-ID, random-port} tuple as possible, help defeat
31 + in the {query-ID, random-port} tuple as possible, to help defeat
32 cache poisoning attacks. Refer: CERT VU#434904.
34 Use the SHA-256 hash function to verify that DNS answers
35 received are for the questions originally asked. This replaces
36 the slightly insecure SHA-1 (when compiled with DNSSEC) or
37 the very insecure CRC32 (otherwise). Refer: CERT VU#434904.
39 + Handle multiple identical near simultaneous DNS queries better.
40 + Previously, such queries would all be forwarded
41 + independently. This is, in theory, inefficent but in practise
42 + not a problem, _except_ that is means that an answer for any
43 + of the forwarded queries will be accepted and cached.
44 + An attacker can send a query multiple times, and for each repeat,
45 + another {port, ID} becomes capable of accepting the answer he is
46 + sending in the blind, to random IDs and ports. The chance of a
47 + succesful attack is therefore multiplied by the number of repeats
48 + of the query. The new behaviour detects repeated queries and
49 + merely stores the clients sending repeats so that when the
50 + first query completes, the answer can be sent to all the
51 + clients who asked. Refer: CERT VU#434904.
57 @@ -642,19 +642,24 @@ struct hostsfile {
58 #define FREC_DO_QUESTION 64
59 #define FREC_ADDED_PHEADER 128
60 #define FREC_TEST_PKTSZ 256
61 -#define FREC_HAS_EXTRADATA 512
62 +#define FREC_HAS_EXTRADATA 512
63 +#define FREC_HAS_PHEADER 1024
65 #define HASH_SIZE 32 /* SHA-256 digest size */
68 - union mysockaddr source;
69 - union all_addr dest;
71 + union mysockaddr source;
72 + union all_addr dest;
73 + unsigned int iface, log_id;
74 + unsigned short orig_id;
75 + struct frec_src *next;
77 struct server *sentto; /* NULL means free */
81 - unsigned short orig_id, new_id;
82 - int log_id, fd, forwardall, flags;
83 + unsigned short new_id;
84 + int fd, forwardall, flags;
86 unsigned char *hash[HASH_SIZE];
88 @@ -1069,6 +1074,8 @@ extern struct daemon {
89 int back_to_the_future;
91 struct frec *frec_list;
92 + struct frec_src *free_frec_src;
94 struct serverfd *sfds;
95 struct irec *interfaces;
96 struct listener *listeners;
99 @@ -20,6 +20,8 @@ static struct frec *lookup_frec(unsigned
100 static struct frec *lookup_frec_by_sender(unsigned short id,
101 union mysockaddr *addr,
103 +static struct frec *lookup_frec_by_query(void *hash, unsigned int flags);
105 static unsigned short get_id(void);
106 static void free_frec(struct frec *f);
108 @@ -247,6 +249,7 @@ static int forward_query(int udpfd, unio
109 int type = SERV_DO_DNSSEC, norebind = 0;
110 union all_addr *addrp = NULL;
111 unsigned int flags = 0;
112 + unsigned int fwd_flags = 0;
113 struct server *start = NULL;
114 void *hash = hash_questions(header, plen, daemon->namebuff);
116 @@ -255,7 +258,18 @@ static int forward_query(int udpfd, unio
117 unsigned int gotname = extract_request(header, plen, daemon->namebuff, NULL);
118 unsigned char *oph = find_pseudoheader(header, plen, NULL, NULL, NULL, NULL);
122 + if (header->hb4 & HB4_CD)
123 + fwd_flags |= FREC_CHECKING_DISABLED;
125 + fwd_flags |= FREC_AD_QUESTION;
127 + fwd_flags |= FREC_HAS_PHEADER;
130 + fwd_flags |= FREC_DO_QUESTION;
133 /* may be no servers available. */
134 if (forward || (forward = lookup_frec_by_sender(ntohs(header->id), udpaddr, hash)))
136 @@ -328,6 +342,39 @@ static int forward_query(int udpfd, unio
140 + /* Query from new source, but the same query may be in progress
141 + from another source. If so, just add this client to the
142 + list that will get the reply.
144 + Note that is the EDNS client subnet option is in use, we can't do this,
145 + as the clients (and therefore query EDNS options) will be different
146 + for each query. The EDNS subnet code has checks to avoid
147 + attacks in this case. */
148 + if (!option_bool(OPT_CLIENT_SUBNET) && (forward = lookup_frec_by_query(hash, fwd_flags)))
150 + /* Note whine_malloc() zeros memory. */
151 + if (!daemon->free_frec_src &&
152 + daemon->frec_src_count < daemon->ftabsize &&
153 + (daemon->free_frec_src = whine_malloc(sizeof(struct frec_src))))
154 + daemon->frec_src_count++;
156 + /* If we've been spammed with many duplicates, just drop the query. */
157 + if (daemon->free_frec_src)
159 + struct frec_src *new = daemon->free_frec_src;
160 + daemon->free_frec_src = new->next;
161 + new->next = forward->frec_src.next;
162 + forward->frec_src.next = new;
163 + new->orig_id = ntohs(header->id);
164 + new->source = *udpaddr;
165 + new->dest = *dst_addr;
166 + new->log_id = daemon->log_id;
167 + new->iface = dst_iface;
174 flags = search_servers(now, &addrp, gotname, daemon->namebuff, &type, &domain, &norebind);
176 @@ -335,22 +382,22 @@ static int forward_query(int udpfd, unio
177 do_dnssec = type & SERV_DO_DNSSEC;
179 type &= ~SERV_DO_DNSSEC;
182 if (daemon->servers && !flags)
183 forward = get_new_frec(now, NULL, 0);
184 /* table full - flags == 0, return REFUSED */
188 - forward->source = *udpaddr;
189 - forward->dest = *dst_addr;
190 - forward->iface = dst_iface;
191 - forward->orig_id = ntohs(header->id);
192 + forward->frec_src.source = *udpaddr;
193 + forward->frec_src.orig_id = ntohs(header->id);
194 + forward->frec_src.dest = *dst_addr;
195 + forward->frec_src.iface = dst_iface;
196 forward->new_id = get_id();
198 memcpy(forward->hash, hash, HASH_SIZE);
199 forward->forwardall = 0;
200 - forward->flags = 0;
201 + forward->flags = fwd_flags;
203 forward->flags |= FREC_NOREBIND;
204 if (header->hb4 & HB4_CD)
205 @@ -405,9 +452,9 @@ static int forward_query(int udpfd, unio
206 unsigned char *pheader;
208 /* If a query is retried, use the log_id for the retry when logging the answer. */
209 - forward->log_id = daemon->log_id;
210 + forward->frec_src.log_id = daemon->log_id;
212 - plen = add_edns0_config(header, plen, ((unsigned char *)header) + PACKETSZ, &forward->source, now, &subnet);
213 + plen = add_edns0_config(header, plen, ((unsigned char *)header) + PACKETSZ, &forward->frec_src.source, now, &subnet);
216 forward->flags |= FREC_HAS_SUBNET;
217 @@ -544,7 +591,7 @@ static int forward_query(int udpfd, unio
220 /* could not send on, prepare to return */
221 - header->id = htons(forward->orig_id);
222 + header->id = htons(forward->frec_src.orig_id);
223 free_frec(forward); /* cancel */
226 @@ -796,8 +843,8 @@ void reply_query(int fd, int family, tim
228 /* log_query gets called indirectly all over the place, so
229 pass these in global variables - sorry. */
230 - daemon->log_display_id = forward->log_id;
231 - daemon->log_source_addr = &forward->source;
232 + daemon->log_display_id = forward->frec_src.log_id;
233 + daemon->log_source_addr = &forward->frec_src.source;
235 if (daemon->ignore_addr && RCODE(header) == NOERROR &&
236 check_for_ignored_address(header, n, daemon->ignore_addr))
237 @@ -1065,6 +1112,7 @@ void reply_query(int fd, int family, tim
238 new->sentto = server;
241 + new->frec_src.next = NULL;
242 new->flags &= ~(FREC_DNSKEY_QUERY | FREC_DS_QUERY | FREC_HAS_EXTRADATA);
245 @@ -1199,9 +1247,11 @@ void reply_query(int fd, int family, tim
247 if ((nn = process_reply(header, now, forward->sentto, (size_t)n, check_rebind, no_cache_dnssec, cache_secure, bogusanswer,
248 forward->flags & FREC_AD_QUESTION, forward->flags & FREC_DO_QUESTION,
249 - forward->flags & FREC_ADDED_PHEADER, forward->flags & FREC_HAS_SUBNET, &forward->source)))
250 + forward->flags & FREC_ADDED_PHEADER, forward->flags & FREC_HAS_SUBNET, &forward->frec_src.source)))
252 - header->id = htons(forward->orig_id);
253 + struct frec_src *src;
255 + header->id = htons(forward->frec_src.orig_id);
256 header->hb4 |= HB4_RA; /* recursion if available */
258 /* We added an EDNSO header for the purpose of getting DNSSEC RRs, and set the value of the UDP payload size
259 @@ -1217,13 +1267,26 @@ void reply_query(int fd, int family, tim
263 + for (src = &forward->frec_src; src; src = src->next)
265 + header->id = htons(src->orig_id);
268 - dump_packet(DUMP_REPLY, daemon->packet, (size_t)nn, NULL, &forward->source);
269 + dump_packet(DUMP_REPLY, daemon->packet, (size_t)nn, NULL, &src->source);
272 - send_from(forward->fd, option_bool(OPT_NOWILD) || option_bool (OPT_CLEVERBIND), daemon->packet, nn,
273 - &forward->source, &forward->dest, forward->iface);
275 + send_from(forward->fd, option_bool(OPT_NOWILD) || option_bool (OPT_CLEVERBIND), daemon->packet, nn,
276 + &src->source, &src->dest, src->iface);
278 + if (option_bool(OPT_EXTRALOG) && src != &forward->frec_src)
280 + daemon->log_display_id = src->log_id;
281 + daemon->log_source_addr = &src->source;
282 + log_query(F_UPSTREAM, "query", NULL, "duplicate");
287 free_frec(forward); /* cancel */
290 @@ -2153,6 +2216,17 @@ void free_rfd(struct randfd *rfd)
292 static void free_frec(struct frec *f)
294 + struct frec_src *src, *tmp;
296 + /* add back to freelist of not the record builtin to every frec. */
297 + for (src = f->frec_src.next; src; src = tmp)
300 + src->next = daemon->free_frec_src;
301 + daemon->free_frec_src = src;
304 + f->frec_src.next = NULL;
308 @@ -2292,17 +2366,39 @@ static struct frec *lookup_frec_by_sende
312 + struct frec_src *src;
314 + for (f = daemon->frec_list; f; f = f->next)
316 + !(f->flags & (FREC_DNSKEY_QUERY | FREC_DS_QUERY)) &&
317 + memcmp(hash, f->hash, HASH_SIZE) == 0)
318 + for (src = &f->frec_src; src; src = src->next)
319 + if (src->orig_id == id &&
320 + sockaddr_isequal(&src->source, addr))
326 +static struct frec *lookup_frec_by_query(void *hash, unsigned int flags)
330 + /* FREC_DNSKEY and FREC_DS_QUERY are never set in flags, so the test below
331 + ensures that no frec created for internal DNSSEC query can be returned here. */
333 +#define FLAGMASK (FREC_CHECKING_DISABLED | FREC_AD_QUESTION | FREC_DO_QUESTION \
334 + | FREC_HAS_PHEADER | FREC_DNSKEY_QUERY | FREC_DS_QUERY)
336 for(f = daemon->frec_list; f; f = f->next)
338 - f->orig_id == id &&
339 - memcmp(hash, f->hash, HASH_SIZE) == 0 &&
340 - sockaddr_isequal(&f->source, addr))
341 + (f->flags & FLAGMASK) == flags &&
342 + memcmp(hash, f->hash, HASH_SIZE) == 0)
350 /* Send query packet again, if we can. */