dnsmasq: Backport some security updates
[librecmc/librecmc.git] / package / network / services / dnsmasq / patches / 0108-Handle-multiple-identical-near-simultaneous-DNS-quer.patch
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.
5
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.
18 ---
19  CHANGELOG     |  16 +++++-
20  src/dnsmasq.h |  19 ++++---
21  src/forward.c | 142 ++++++++++++++++++++++++++++++++++++++++++--------
22  3 files changed, 147 insertions(+), 30 deletions(-)
23
24 --- a/CHANGELOG
25 +++ b/CHANGELOG
26 @@ -4,13 +4,27 @@
27  
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.
33  
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.
38 +
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.
52         
53  
54  version 2.81
55 --- a/src/dnsmasq.h
56 +++ b/src/dnsmasq.h
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
64  
65  #define HASH_SIZE 32 /* SHA-256 digest size */
66  
67  struct frec {
68 -  union mysockaddr source;
69 -  union all_addr dest;
70 +  struct frec_src {
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;
76 +  } frec_src;
77    struct server *sentto; /* NULL means free */
78    struct randfd *rfd4;
79    struct randfd *rfd6;
80 -  unsigned int iface;
81 -  unsigned short orig_id, new_id;
82 -  int log_id, fd, forwardall, flags;
83 +  unsigned short new_id;
84 +  int fd, forwardall, flags;
85    time_t time;
86    unsigned char *hash[HASH_SIZE];
87  #ifdef HAVE_DNSSEC 
88 @@ -1069,6 +1074,8 @@ extern struct daemon {
89    int back_to_the_future;
90  #endif
91    struct frec *frec_list;
92 +  struct frec_src *free_frec_src;
93 +  int frec_src_count;
94    struct serverfd *sfds;
95    struct irec *interfaces;
96    struct listener *listeners;
97 --- a/src/forward.c
98 +++ b/src/forward.c
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,
102                                           void *hash);
103 +static struct frec *lookup_frec_by_query(void *hash, unsigned int flags);
104 +
105  static unsigned short get_id(void);
106  static void free_frec(struct frec *f);
107  
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);
115  #ifdef HAVE_DNSSEC
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);
119    (void)do_bit;
120 -
121 +  
122 +  if (header->hb4 & HB4_CD)
123 +    fwd_flags |= FREC_CHECKING_DISABLED;
124 +  if (ad_reqd)
125 +    fwd_flags |= FREC_AD_QUESTION;
126 +  if (oph)
127 +    fwd_flags |= FREC_HAS_PHEADER;
128 +#ifdef HAVE_DNSSEC
129 +  if (do_bit)
130 +    fwd_flags |= FREC_DO_QUESTION;
131 +#endif
132 +  
133    /* may be no servers available. */
134    if (forward || (forward = lookup_frec_by_sender(ntohs(header->id), udpaddr, hash)))
135      {
136 @@ -328,6 +342,39 @@ static int forward_query(int udpfd, unio
137      }
138    else 
139      {
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.
143 +        
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)))
149 +       {
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++;
155 +         
156 +         /* If we've been spammed with many duplicates, just drop the query. */
157 +         if (daemon->free_frec_src)
158 +           {
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;
168 +           }
169 +         
170 +         return 1;
171 +       }
172 +       
173        if (gotname)
174         flags = search_servers(now, &addrp, gotname, daemon->namebuff, &type, &domain, &norebind);
175        
176 @@ -335,22 +382,22 @@ static int forward_query(int udpfd, unio
177        do_dnssec = type & SERV_DO_DNSSEC;
178  #endif
179        type &= ~SERV_DO_DNSSEC;      
180 -
181 +      
182        if (daemon->servers && !flags)
183         forward = get_new_frec(now, NULL, 0);
184        /* table full - flags == 0, return REFUSED */
185        
186        if (forward)
187         {
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();
197           forward->fd = udpfd;
198           memcpy(forward->hash, hash, HASH_SIZE);
199           forward->forwardall = 0;
200 -         forward->flags = 0;
201 +         forward->flags = fwd_flags;
202           if (norebind)
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;
207        
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;
211        
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);
214        
215        if (subnet)
216         forward->flags |= FREC_HAS_SUBNET;
217 @@ -544,7 +591,7 @@ static int forward_query(int udpfd, unio
218         return 1;
219        
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 */
224      }    
225    
226 @@ -796,8 +843,8 @@ void reply_query(int fd, int family, tim
227  
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;
234    
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;
239                       new->rfd4 = NULL;
240                       new->rfd6 = NULL;
241 +                     new->frec_src.next = NULL;
242                       new->flags &= ~(FREC_DNSKEY_QUERY | FREC_DS_QUERY | FREC_HAS_EXTRADATA);
243                       new->forwardall = 0;
244                       
245 @@ -1199,9 +1247,11 @@ void reply_query(int fd, int family, tim
246        
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)))
251         {
252 -         header->id = htons(forward->orig_id);
253 +         struct frec_src *src;
254 +
255 +         header->id = htons(forward->frec_src.orig_id);
256           header->hb4 |= HB4_RA; /* recursion if available */
257  #ifdef HAVE_DNSSEC
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
260             }
261  #endif
262  
263 +         for (src = &forward->frec_src; src; src = src->next)
264 +           {
265 +             header->id = htons(src->orig_id);
266 +             
267  #ifdef HAVE_DUMPFILE
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);
270  #endif
271 -         
272 -         send_from(forward->fd, option_bool(OPT_NOWILD) || option_bool (OPT_CLEVERBIND), daemon->packet, nn, 
273 -                   &forward->source, &forward->dest, forward->iface);
274 +             
275 +             send_from(forward->fd, option_bool(OPT_NOWILD) || option_bool (OPT_CLEVERBIND), daemon->packet, nn, 
276 +                       &src->source, &src->dest, src->iface);
277 +
278 +             if (option_bool(OPT_EXTRALOG) && src != &forward->frec_src)
279 +               {
280 +                 daemon->log_display_id = src->log_id;
281 +                 daemon->log_source_addr = &src->source;
282 +                 log_query(F_UPSTREAM, "query", NULL, "duplicate");
283 +               }
284 +           }
285         }
286 +
287        free_frec(forward); /* cancel */
288      }
289  }
290 @@ -2153,6 +2216,17 @@ void free_rfd(struct randfd *rfd)
291  
292  static void free_frec(struct frec *f)
293  {
294 +  struct frec_src *src, *tmp;
295 +
296 +   /* add back to freelist of not the record builtin to every frec. */
297 +  for (src = f->frec_src.next; src; src = tmp)
298 +    {
299 +      tmp = src->next;
300 +      src->next = daemon->free_frec_src;
301 +      daemon->free_frec_src = src;
302 +    }
303 +  
304 +  f->frec_src.next = NULL;    
305    free_rfd(f->rfd4);
306    f->rfd4 = NULL;
307    f->sentto = NULL;
308 @@ -2292,17 +2366,39 @@ static struct frec *lookup_frec_by_sende
309                                           void *hash)
310  {
311    struct frec *f;
312 +  struct frec_src *src;
313 +
314 +  for (f = daemon->frec_list; f; f = f->next)
315 +    if (f->sentto &&
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))
321 +         return f;
322 +  
323 +  return NULL;
324 +}
325 +
326 +static struct frec *lookup_frec_by_query(void *hash, unsigned int flags)
327 +{
328 +  struct frec *f;
329 +
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. */
332 +
333 +#define FLAGMASK (FREC_CHECKING_DISABLED | FREC_AD_QUESTION | FREC_DO_QUESTION \
334 +                 | FREC_HAS_PHEADER | FREC_DNSKEY_QUERY | FREC_DS_QUERY)
335    
336    for(f = daemon->frec_list; f; f = f->next)
337      if (f->sentto &&
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)
343        return f;
344 -   
345 +  
346    return NULL;
347  }
348
349 +
350  /* Send query packet again, if we can. */
351  void resend_query()
352  {