dnsmasq: Backport some security updates v1.5.4-20210122
authorHauke Mehrtens <hauke@hauke-m.de>
Mon, 11 Jan 2021 00:03:03 +0000 (01:03 +0100)
committerRISCi_ATOM <bob@bobcall.me>
Fri, 22 Jan 2021 15:39:16 +0000 (10:39 -0500)
This fixes the following security problems in dnsmasq:
* CVE-2020-25681:
  Dnsmasq versions before 2.83 is susceptible to a heap-based buffer
  overflow in sort_rrset() when DNSSEC is used. This can allow a remote
  attacker to write arbitrary data into target device's memory that can
  lead to memory corruption and other unexpected behaviors on the target
  device.
* CVE-2020-25682:
  Dnsmasq versions before 2.83 is susceptible to buffer overflow in
  extract_name() function due to missing length check, when DNSSEC is
  enabled. This can allow a remote attacker to cause memory corruption
  on the target device.
* CVE-2020-25683:
  Dnsmasq version before 2.83 is susceptible to a heap-based buffer
  overflow when DNSSEC is enabled. A remote attacker, who can create
  valid DNS replies, could use this flaw to cause an overflow in a heap-
  allocated memory. This flaw is caused by the lack of length checks in
  rtc1035.c:extract_name(), which could be abused to make the code
  execute memcpy() with a negative size in get_rdata() and cause a crash
  in Dnsmasq, resulting in a Denial of Service.
* CVE-2020-25684:
  A lack of proper address/port check implemented in Dnsmasq version <
  2.83 reply_query function makes forging replies easier to an off-path
  attacker.
* CVE-2020-25685:
  A lack of query resource name (RRNAME) checks implemented in Dnsmasq's
  versions before 2.83 reply_query function allows remote attackers to
  spoof DNS traffic that can lead to DNS cache poisoning.
* CVE-2020-25686:
  Multiple DNS query requests for the same resource name (RRNAME) by
  Dnsmasq versions before 2.83 allows for remote attackers to spoof DNS
  traffic, using a birthday attack (RFC 5452), that can lead to DNS
  cache poisoning.
* CVE-2020-25687:
  Dnsmasq versions before 2.83 is vulnerable to a heap-based buffer
  overflow with large memcpy in sort_rrset() when DNSSEC is enabled. A
  remote attacker, who can create valid DNS replies, could use this flaw
  to cause an overflow in a heap-allocated memory. This flaw is caused
  by the lack of length checks in rtc1035.c:extract_name(), which could
  be abused to make the code execute memcpy() with a negative size in
  sort_rrset() and cause a crash in dnsmasq, resulting in a Denial of
  Service.

Signed-off-by: Hauke Mehrtens <hauke@hauke-m.de>
14 files changed:
package/network/services/dnsmasq/Makefile
package/network/services/dnsmasq/patches/0102-Fix-remote-buffer-overflow-CERT-VU-434904.patch [new file with mode: 0644]
package/network/services/dnsmasq/patches/0103-Check-destination-of-DNS-UDP-query-replies.patch [new file with mode: 0644]
package/network/services/dnsmasq/patches/0104-Use-SHA-256-to-provide-security-against-DNS-cache-po.patch [new file with mode: 0644]
package/network/services/dnsmasq/patches/0105-Optimse-RR-digest-calculation-in-DNSSEC.patch [new file with mode: 0644]
package/network/services/dnsmasq/patches/0107-Add-missing-check-for-NULL-return-from-allocate_rfd.patch [new file with mode: 0644]
package/network/services/dnsmasq/patches/0108-Handle-multiple-identical-near-simultaneous-DNS-quer.patch [new file with mode: 0644]
package/network/services/dnsmasq/patches/0109-Handle-caching-with-EDNS-options-better.patch [new file with mode: 0644]
package/network/services/dnsmasq/patches/0110-Support-hash-function-from-nettle-only.patch [new file with mode: 0644]
package/network/services/dnsmasq/patches/0111-Small-cleanups-in-frec_src-datastucture-handling.patch [new file with mode: 0644]
package/network/services/dnsmasq/patches/0112-Add-CVE-numbers-to-security-update-descriptions-in-C.patch [new file with mode: 0644]
package/network/services/dnsmasq/patches/0113-Fix-warning-message-logic.patch [new file with mode: 0644]
package/network/services/dnsmasq/patches/0115-Update-to-new-struct-frec-fields-in-conntrack-code.patch [new file with mode: 0644]
package/network/services/dnsmasq/patches/050-crypto-use-nettle-ecc_curve-access-functions.patch

index d31f4c7..832246c 100644 (file)
@@ -10,7 +10,7 @@ include $(TOPDIR)/rules.mk
 PKG_NAME:=dnsmasq
 PKG_UPSTREAM_VERSION:=2.80
 PKG_VERSION:=$(subst test,~~test,$(subst rc,~rc,$(PKG_UPSTREAM_VERSION)))
-PKG_RELEASE:=16.1
+PKG_RELEASE:=16.2
 
 PKG_SOURCE:=$(PKG_NAME)-$(PKG_UPSTREAM_VERSION).tar.xz
 PKG_SOURCE_URL:=http://thekelleys.org.uk/dnsmasq
diff --git a/package/network/services/dnsmasq/patches/0102-Fix-remote-buffer-overflow-CERT-VU-434904.patch b/package/network/services/dnsmasq/patches/0102-Fix-remote-buffer-overflow-CERT-VU-434904.patch
new file mode 100644 (file)
index 0000000..2f7457c
--- /dev/null
@@ -0,0 +1,375 @@
+From 4e96a4be685c9e4445f6ee79ad0b36b9119b502a Mon Sep 17 00:00:00 2001
+From: Simon Kelley <simon@thekelleys.org.uk>
+Date: Wed, 11 Nov 2020 23:25:04 +0000
+Subject: Fix remote buffer overflow CERT VU#434904
+
+The problem is in the sort_rrset() function and allows a remote
+attacker to overwrite memory. Any dnsmasq instance with DNSSEC
+enabled is vulnerable.
+---
+ CHANGELOG    |   7 +-
+ src/dnssec.c | 273 ++++++++++++++++++++++++++++-----------------------
+ 2 files changed, 158 insertions(+), 122 deletions(-)
+
+--- a/CHANGELOG
++++ b/CHANGELOG
+@@ -1,3 +1,9 @@
++      Fix a remote buffer overflow problem in the DNSSEC code. Any
++      dnsmasq with DNSSEC compiled in and enabled is vulnerable to this,
++      referenced by CERT VU#434904.
++
++
++>>>>>>> Fix remote buffer overflow CERT VU#434904
+ version 2.81
+       Impove cache behaviour for TCP connections. For ease of
+       implementaion, dnsmasq has always forked a new process to handle
+--- a/src/dnssec.c
++++ b/src/dnssec.c
+@@ -222,138 +222,147 @@ static int check_date_range(u32 date_sta
+     && serial_compare_32(curtime, date_end) == SERIAL_LT;
+ }
+-/* Return bytes of canonicalised rdata, when the return value is zero, the remaining 
+-   data, pointed to by *p, should be used raw. */
+-static int get_rdata(struct dns_header *header, size_t plen, unsigned char *end, char *buff, int bufflen,
+-                   unsigned char **p, u16 **desc)
++/* Return bytes of canonicalised rrdata one by one.
++   Init state->ip with the RR, and state->end with the end of same.
++   Init state->op to NULL.
++   Init state->desc to RR descriptor.
++   Init state->buff with a MAXDNAME * 2 buffer.
++   
++   After each call which returns 1, state->op points to the next byte of data.
++   On returning 0, the end has been reached.
++*/
++struct rdata_state {
++  u16 *desc;
++  size_t c;
++  unsigned char *end, *ip, *op;
++  char *buff;
++};
++
++static int get_rdata(struct dns_header *header, size_t plen, struct rdata_state *state)
+ {
+-  int d = **desc;
++  int d;
+   
+-  /* No more data needs mangling */
+-  if (d == (u16)-1)
++  if (state->op && state->c != 1)
+     {
+-      /* If there's more data than we have space for, just return what fits,
+-       we'll get called again for more chunks */
+-      if (end - *p > bufflen)
+-      {
+-        memcpy(buff, *p, bufflen);
+-        *p += bufflen;
+-        return bufflen;
+-      }
+-      
+-      return 0;
++      state->op++;
++      state->c--;
++      return 1;
+     }
+- 
+-  (*desc)++;
+-  
+-  if (d == 0 && extract_name(header, plen, p, buff, 1, 0))
+-    /* domain-name, canonicalise */
+-    return to_wire(buff);
+-  else
+-    { 
+-      /* plain data preceding a domain-name, don't run off the end of the data */
+-      if ((end - *p) < d)
+-      d = end - *p;
++
++  while (1)
++    {
++      d = *(state->desc);
+       
+-      if (d != 0)
++      if (d == (u16)-1)
+       {
+-        memcpy(buff, *p, d);
+-        *p += d;
++        /* all the bytes to the end. */
++        if ((state->c = state->end - state->ip) != 0)
++          {
++            state->op = state->ip;
++            state->ip = state->end;;
++          }
++        else
++          return 0;
++      }
++      else
++      {
++        state->desc++;
++        
++        if (d == (u16)0)
++          {
++            /* domain-name, canonicalise */
++            int len;
++            
++            if (!extract_name(header, plen, &state->ip, state->buff, 1, 0) ||
++                (len = to_wire(state->buff)) == 0)
++              continue;
++            
++            state->c = len;
++            state->op = (unsigned char *)state->buff;
++          }
++        else
++          {
++            /* plain data preceding a domain-name, don't run off the end of the data */
++            if ((state->end - state->ip) < d)
++              d = state->end - state->ip;
++            
++            if (d == 0)
++              continue;
++                
++            state->op = state->ip;
++            state->c = d;
++            state->ip += d;
++          }
+       }
+       
+-      return d;
++      return 1;
+     }
+ }
+-/* Bubble sort the RRset into the canonical order. 
+-   Note that the byte-streams from two RRs may get unsynced: consider 
+-   RRs which have two domain-names at the start and then other data.
+-   The domain-names may have different lengths in each RR, but sort equal
+-
+-   ------------
+-   |abcde|fghi|
+-   ------------
+-   |abcd|efghi|
+-   ------------
+-
+-   leaving the following bytes as deciding the order. Hence the nasty left1 and left2 variables.
+-*/
++/* Bubble sort the RRset into the canonical order. */
+ static int sort_rrset(struct dns_header *header, size_t plen, u16 *rr_desc, int rrsetidx, 
+                     unsigned char **rrset, char *buff1, char *buff2)
+ {
+-  int swap, quit, i, j;
++  int swap, i, j;
+   
+   do
+     {
+       for (swap = 0, i = 0; i < rrsetidx-1; i++)
+       {
+-        int rdlen1, rdlen2, left1, left2, len1, len2, len, rc;
+-        u16 *dp1, *dp2;
+-        unsigned char *end1, *end2;
++        int rdlen1, rdlen2;
++        struct rdata_state state1, state2;
++        
+         /* Note that these have been determined to be OK previously,
+            so we don't need to check for NULL return here. */
+-        unsigned char *p1 = skip_name(rrset[i], header, plen, 10);
+-        unsigned char *p2 = skip_name(rrset[i+1], header, plen, 10);
+-        
+-        p1 += 8; /* skip class, type, ttl */
+-        GETSHORT(rdlen1, p1);
+-        end1 = p1 + rdlen1;
+-        
+-        p2 += 8; /* skip class, type, ttl */
+-        GETSHORT(rdlen2, p2);
+-        end2 = p2 + rdlen2; 
+-        
+-        dp1 = dp2 = rr_desc;
+-        
+-        for (quit = 0, left1 = 0, left2 = 0, len1 = 0, len2 = 0; !quit;)
++        state1.ip = skip_name(rrset[i], header, plen, 10);
++        state2.ip = skip_name(rrset[i+1], header, plen, 10);
++        state1.op = state2.op = NULL;
++        state1.buff = buff1;
++        state2.buff = buff2;
++        state1.desc = state2.desc = rr_desc;
++        
++        state1.ip += 8; /* skip class, type, ttl */
++        GETSHORT(rdlen1, state1.ip);
++        if (!CHECK_LEN(header, state1.ip, plen, rdlen1))
++          return rrsetidx; /* short packet */
++        state1.end = state1.ip + rdlen1;
++        
++        state2.ip += 8; /* skip class, type, ttl */
++        GETSHORT(rdlen2, state2.ip);
++        if (!CHECK_LEN(header, state2.ip, plen, rdlen2))
++          return rrsetidx; /* short packet */
++        state2.end = state2.ip + rdlen2; 
++                
++        while (1)
+           {
+-            if (left1 != 0)
+-              memmove(buff1, buff1 + len1 - left1, left1);
+-            
+-            if ((len1 = get_rdata(header, plen, end1, buff1 + left1, (MAXDNAME * 2) - left1, &p1, &dp1)) == 0)
+-              {
+-                quit = 1;
+-                len1 = end1 - p1;
+-                memcpy(buff1 + left1, p1, len1);
+-              }
+-            len1 += left1;
+-            
+-            if (left2 != 0)
+-              memmove(buff2, buff2 + len2 - left2, left2);
+-            
+-            if ((len2 = get_rdata(header, plen, end2, buff2 + left2, (MAXDNAME *2) - left2, &p2, &dp2)) == 0)
+-              {
+-                quit = 1;
+-                len2 = end2 - p2;
+-                memcpy(buff2 + left2, p2, len2);
+-              }
+-            len2 += left2;
+-             
+-            if (len1 > len2)
+-              left1 = len1 - len2, left2 = 0, len = len2;
+-            else
+-              left2 = len2 - len1, left1 = 0, len = len1;
++            int ok1, ok2;
+             
+-            rc = (len == 0) ? 0 : memcmp(buff1, buff2, len);
+-            
+-            if (rc > 0 || (rc == 0 && quit && len1 > len2))
+-              {
+-                unsigned char *tmp = rrset[i+1];
+-                rrset[i+1] = rrset[i];
+-                rrset[i] = tmp;
+-                swap = quit = 1;
+-              }
+-            else if (rc == 0 && quit && len1 == len2)
++            ok1 = get_rdata(header, plen, &state1);
++            ok2 = get_rdata(header, plen, &state2);
++
++            if (!ok1 && !ok2)
+               {
+                 /* Two RRs are equal, remove one copy. RFC 4034, para 6.3 */
+                 for (j = i+1; j < rrsetidx-1; j++)
+                   rrset[j] = rrset[j+1];
+                 rrsetidx--;
+                 i--;
++                break;
++              }
++            else if (ok1 && (!ok2 || *state1.op > *state2.op)) 
++              {
++                unsigned char *tmp = rrset[i+1];
++                rrset[i+1] = rrset[i];
++                rrset[i] = tmp;
++                swap = 1;
++                break;
+               }
+-            else if (rc < 0)
+-              quit = 1;
++            else if (ok2 && (!ok1 || *state2.op > *state1.op))
++              break;
++            
++            /* arrive here when bytes are equal, go round the loop again
++               and compare the next ones. */
+           }
+       }
+     } while (swap);
+@@ -549,15 +558,18 @@ static int validate_rrset(time_t now, st
+       wire_len = to_wire(keyname);
+       hash->update(ctx, (unsigned int)wire_len, (unsigned char*)keyname);
+       from_wire(keyname);
++
++#define RRBUFLEN 300 /* Most RRs are smaller than this. */
+       
+       for (i = 0; i < rrsetidx; ++i)
+       {
+-        int seg;
+-        unsigned char *end, *cp;
+-        u16 len, *dp;
++        int j;
++        struct rdata_state state;
++        u16 len;
++        unsigned char rrbuf[RRBUFLEN];
+         
+         p = rrset[i];
+-                
++        
+         if (!extract_name(header, plen, &p, name, 1, 10)) 
+           return STAT_BOGUS;
+@@ -566,12 +578,11 @@ static int validate_rrset(time_t now, st
+         /* if more labels than in RRsig name, hash *.<no labels in rrsig labels field>  4035 5.3.2 */
+         if (labels < name_labels)
+           {
+-            int k;
+-            for (k = name_labels - labels; k != 0; k--)
++            for (j = name_labels - labels; j != 0; j--)
+               {
+                 while (*name_start != '.' && *name_start != 0)
+                   name_start++;
+-                if (k != 1 && *name_start == '.')
++                if (j != 1 && *name_start == '.')
+                   name_start++;
+               }
+             
+@@ -592,24 +603,44 @@ static int validate_rrset(time_t now, st
+         if (!CHECK_LEN(header, p, plen, rdlen))
+           return STAT_BOGUS; 
+         
+-        end = p + rdlen;
+-        
+-        /* canonicalise rdata and calculate length of same, use name buffer as workspace.
+-           Note that name buffer is twice MAXDNAME long in DNSSEC mode. */
+-        cp = p;
+-        dp = rr_desc;
+-        for (len = 0; (seg = get_rdata(header, plen, end, name, MAXDNAME * 2, &cp, &dp)) != 0; len += seg);
+-        len += end - cp;
+-        len = htons(len);
++        /* canonicalise rdata and calculate length of same, use 
++           name buffer as workspace for get_rdata. */
++        state.ip = p;
++        state.op = NULL;
++        state.desc = rr_desc;
++        state.buff = name;
++        state.end = p + rdlen;
++        
++        for (j = 0; get_rdata(header, plen, &state); j++)
++          if (j < RRBUFLEN)
++            rrbuf[j] = *state.op;
++
++        len = htons((u16)j);
+         hash->update(ctx, 2, (unsigned char *)&len); 
++
++        /* If the RR is shorter than RRBUFLEN (most of them, in practice)
++           then we can just digest it now. If it exceeds RRBUFLEN we have to
++           go back to the start and do it in chunks. */
++        if (j >= RRBUFLEN)
++          {
++            state.ip = p;
++            state.op = NULL;
++            state.desc = rr_desc;
++
++            for (j = 0; get_rdata(header, plen, &state); j++)
++              {
++                 rrbuf[j] = *state.op;
++
++                 if (j == RRBUFLEN - 1)
++                   {
++                     hash->update(ctx, RRBUFLEN, rrbuf);
++                     j = -1;
++                   }
++              }
++          }
+         
+-        /* Now canonicalise again and digest. */
+-        cp = p;
+-        dp = rr_desc;
+-        while ((seg = get_rdata(header, plen, end, name, MAXDNAME * 2, &cp, &dp)))
+-          hash->update(ctx, seg, (unsigned char *)name);
+-        if (cp != end)
+-          hash->update(ctx, end - cp, cp);
++        if (j != 0)
++          hash->update(ctx, j, rrbuf);
+       }
+      
+       hash->digest(ctx, hash->digest_size, digest);
diff --git a/package/network/services/dnsmasq/patches/0103-Check-destination-of-DNS-UDP-query-replies.patch b/package/network/services/dnsmasq/patches/0103-Check-destination-of-DNS-UDP-query-replies.patch
new file mode 100644 (file)
index 0000000..b13ba2d
--- /dev/null
@@ -0,0 +1,106 @@
+From 257ac0c5f7732cbc6aa96fdd3b06602234593aca Mon Sep 17 00:00:00 2001
+From: Simon Kelley <simon@thekelleys.org.uk>
+Date: Thu, 12 Nov 2020 18:49:23 +0000
+Subject: Check destination of DNS UDP query replies.
+
+At any time, dnsmasq will have a set of sockets open, bound to
+random ports, on which it sends queries to upstream nameservers.
+This patch fixes the existing problem that a reply for ANY in-flight
+query would be accepted via ANY open port, which increases the
+chances of an attacker flooding answers "in the blind" in an
+attempt to poison the DNS cache. CERT VU#434904 refers.
+---
+ CHANGELOG     |  6 +++++-
+ src/forward.c | 37 ++++++++++++++++++++++++++++---------
+ 2 files changed, 33 insertions(+), 10 deletions(-)
+
+--- a/CHANGELOG
++++ b/CHANGELOG
+@@ -2,8 +2,12 @@
+       dnsmasq with DNSSEC compiled in and enabled is vulnerable to this,
+       referenced by CERT VU#434904.
++      Be sure to only accept UDP DNS query replies at the address
++      from which the query was originated. This keeps as much entropy
++      in the {query-ID, random-port} tuple as possible, help defeat
++      cache poisoning attacks. Refer: CERT VU#434904.
++
+->>>>>>> Fix remote buffer overflow CERT VU#434904
+ version 2.81
+       Impove cache behaviour for TCP connections. For ease of
+       implementaion, dnsmasq has always forked a new process to handle
+--- a/src/forward.c
++++ b/src/forward.c
+@@ -16,7 +16,7 @@
+ #include "dnsmasq.h"
+-static struct frec *lookup_frec(unsigned short id, void *hash);
++static struct frec *lookup_frec(unsigned short id, int fd, int family, void *hash);
+ static struct frec *lookup_frec_by_sender(unsigned short id,
+                                         union mysockaddr *addr,
+                                         void *hash);
+@@ -797,7 +797,7 @@ void reply_query(int fd, int family, tim
+   crc = questions_crc(header, n, daemon->namebuff);
+ #endif
+   
+-  if (!(forward = lookup_frec(ntohs(header->id), hash)))
++  if (!(forward = lookup_frec(ntohs(header->id), fd, family, hash)))
+     return;
+   
+ #ifdef HAVE_DUMPFILE
+@@ -2289,14 +2289,25 @@ struct frec *get_new_frec(time_t now, in
+ }
+ /* crc is all-ones if not known. */
+-static struct frec *lookup_frec(unsigned short id, void *hash)
++static struct frec *lookup_frec(unsigned short id, int fd, int family, void *hash)
+ {
+   struct frec *f;
+   for(f = daemon->frec_list; f; f = f->next)
+     if (f->sentto && f->new_id == id && 
+       (!hash || memcmp(hash, f->hash, HASH_SIZE) == 0))
+-      return f;
++      {
++      /* sent from random port */
++      if (family == AF_INET && f->rfd4 && f->rfd4->fd == fd)
++        return f;
++
++      if (family == AF_INET6 && f->rfd6 && f->rfd6->fd == fd)
++        return f;
++
++      /* sent to upstream from bound socket. */
++      if (f->sentto->sfd && f->sentto->sfd->fd == fd)
++        return f;
++      }
+       
+   return NULL;
+ }
+@@ -2357,12 +2368,20 @@ void server_gone(struct server *server)
+ static unsigned short get_id(void)
+ {
+   unsigned short ret = 0;
++  struct frec *f;
+   
+-  do 
+-    ret = rand16();
+-  while (lookup_frec(ret, NULL));
+-  
+-  return ret;
++  while (1)
++    {
++      ret = rand16();
++
++      /* ensure id is unique. */
++      for (f = daemon->frec_list; f; f = f->next)
++      if (f->sentto && f->new_id == ret)
++        break;
++
++      if (!f)
++      return ret;
++    }
+ }
diff --git a/package/network/services/dnsmasq/patches/0104-Use-SHA-256-to-provide-security-against-DNS-cache-po.patch b/package/network/services/dnsmasq/patches/0104-Use-SHA-256-to-provide-security-against-DNS-cache-po.patch
new file mode 100644 (file)
index 0000000..bcf6815
--- /dev/null
@@ -0,0 +1,581 @@
+From 2d765867c597db18be9d876c9c17e2c0fe1953cd Mon Sep 17 00:00:00 2001
+From: Simon Kelley <simon@thekelleys.org.uk>
+Date: Thu, 12 Nov 2020 22:06:07 +0000
+Subject: Use SHA-256 to provide security against DNS cache poisoning.
+
+Use the SHA-256 hash function to verify that DNS answers
+received are for the questions originally asked. This replaces
+the slightly insecure SHA-1 (when compiled with DNSSEC) or
+the very insecure CRC32 (otherwise). Refer: CERT VU#434904.
+---
+ CHANGELOG            |   5 +
+ Makefile             |   3 +-
+ bld/Android.mk       |   2 +-
+ src/dnsmasq.h        |  11 +-
+ src/dnssec.c         |  31 -----
+ src/forward.c        |  43 ++-----
+ src/hash_questions.c | 281 +++++++++++++++++++++++++++++++++++++++++++
+ src/rfc1035.c        |  49 --------
+ 8 files changed, 301 insertions(+), 124 deletions(-)
+ create mode 100644 src/hash_questions.c
+
+--- a/CHANGELOG
++++ b/CHANGELOG
+@@ -7,6 +7,11 @@
+       in the {query-ID, random-port} tuple as possible, help defeat
+       cache poisoning attacks. Refer: CERT VU#434904.
++      Use the SHA-256 hash function to verify that DNS answers
++      received are for the questions originally asked. This replaces
++      the slightly insecure SHA-1 (when compiled with DNSSEC) or
++      the very insecure CRC32 (otherwise). Refer: CERT VU#434904.
++      
+ version 2.81
+       Impove cache behaviour for TCP connections. For ease of
+--- a/Makefile
++++ b/Makefile
+@@ -77,7 +77,8 @@ objs = cache.o rfc1035.o util.o option.o
+        helper.o tftp.o log.o conntrack.o dhcp6.o rfc3315.o \
+        dhcp-common.o outpacket.o radv.o slaac.o auth.o ipset.o \
+        domain.o dnssec.o blockdata.o tables.o loop.o inotify.o \
+-       poll.o rrfilter.o edns0.o arp.o crypto.o dump.o ubus.o metrics.o
++       poll.o rrfilter.o edns0.o arp.o crypto.o dump.o ubus.o \
++       metrics.o hash_questions.o
+ hdrs = dnsmasq.h config.h dhcp-protocol.h dhcp6-protocol.h \
+        dns-protocol.h radv-protocol.h ip6addr.h metrics.h
+--- a/bld/Android.mk
++++ b/bld/Android.mk
+@@ -11,7 +11,7 @@ LOCAL_SRC_FILES :=  bpf.c cache.c dbus.c
+                   radv.c slaac.c auth.c ipset.c domain.c \
+                   dnssec.c dnssec-openssl.c blockdata.c tables.c \
+                   loop.c inotify.c poll.c rrfilter.c edns0.c arp.c \
+-                  crypto.c dump.c ubus.c
++                  crypto.c dump.c ubus.c metrics.c hash_questions.c
+ LOCAL_MODULE := dnsmasq
+--- a/src/dnsmasq.h
++++ b/src/dnsmasq.h
+@@ -644,11 +644,7 @@ struct hostsfile {
+ #define FREC_TEST_PKTSZ       256
+ #define FREC_HAS_EXTRADATA    512        
+-#ifdef HAVE_DNSSEC
+-#define HASH_SIZE 20 /* SHA-1 digest size */
+-#else
+-#define HASH_SIZE sizeof(int)
+-#endif
++#define HASH_SIZE 32 /* SHA-256 digest size */
+ struct frec {
+   union mysockaddr source;
+@@ -1199,7 +1195,6 @@ int check_for_bogus_wildcard(struct dns_
+                            struct bogus_addr *baddr, time_t now);
+ int check_for_ignored_address(struct dns_header *header, size_t qlen, struct bogus_addr *baddr);
+ int check_for_local_domain(char *name, time_t now);
+-unsigned int questions_crc(struct dns_header *header, size_t plen, char *name);
+ size_t resize_packet(struct dns_header *header, size_t plen, 
+                 unsigned char *pheader, size_t hlen);
+ int add_resource_record(struct dns_header *header, char *limit, int *truncp,
+@@ -1227,9 +1222,11 @@ int dnssec_validate_reply(time_t now, st
+                         int check_unsigned, int *neganswer, int *nons);
+ int dnskey_keytag(int alg, int flags, unsigned char *key, int keylen);
+ size_t filter_rrsigs(struct dns_header *header, size_t plen);
+-unsigned char* hash_questions(struct dns_header *header, size_t plen, char *name);
+ int setup_timestamp(void);
++/* hash_questions.c */
++unsigned char *hash_questions(struct dns_header *header, size_t plen, char *name);
++
+ /* crypto.c */
+ const struct nettle_hash *hash_find(char *name);
+ int hash_init(const struct nettle_hash *hash, void **ctxp, unsigned char **digestp);
+--- a/src/dnssec.c
++++ b/src/dnssec.c
+@@ -2082,35 +2082,4 @@ size_t dnssec_generate_query(struct dns_
+   return ret;
+ }
+-unsigned char* hash_questions(struct dns_header *header, size_t plen, char *name)
+-{
+-  int q;
+-  unsigned int len;
+-  unsigned char *p = (unsigned char *)(header+1);
+-  const struct nettle_hash *hash;
+-  void *ctx;
+-  unsigned char *digest;
+-  
+-  if (!(hash = hash_find("sha1")) || !hash_init(hash, &ctx, &digest))
+-    return NULL;
+-  
+-  for (q = ntohs(header->qdcount); q != 0; q--) 
+-    {
+-      if (!extract_name(header, plen, &p, name, 1, 4))
+-      break; /* bad packet */
+-      
+-      len = to_wire(name);
+-      hash->update(ctx, len, (unsigned char *)name);
+-      /* CRC the class and type as well */
+-      hash->update(ctx, 4, p);
+-
+-      p += 4;
+-      if (!CHECK_LEN(header, p, plen, 0))
+-      break; /* bad packet */
+-    }
+-  
+-  hash->digest(ctx, hash->digest_size, digest);
+-  return digest;
+-}
+-
+ #endif /* HAVE_DNSSEC */
+--- a/src/forward.c
++++ b/src/forward.c
+@@ -248,19 +248,16 @@ static int forward_query(int udpfd, unio
+   union all_addr *addrp = NULL;
+   unsigned int flags = 0;
+   struct server *start = NULL;
+-#ifdef HAVE_DNSSEC
+   void *hash = hash_questions(header, plen, daemon->namebuff);
++#ifdef HAVE_DNSSEC
+   int do_dnssec = 0;
+-#else
+-  unsigned int crc = questions_crc(header, plen, daemon->namebuff);
+-  void *hash = &crc;
+ #endif
+   unsigned int gotname = extract_request(header, plen, daemon->namebuff, NULL);
+   unsigned char *oph = find_pseudoheader(header, plen, NULL, NULL, NULL, NULL);
+   (void)do_bit;
+   /* may be no servers available. */
+-  if (forward || (hash && (forward = lookup_frec_by_sender(ntohs(header->id), udpaddr, hash))))
++  if (forward || (forward = lookup_frec_by_sender(ntohs(header->id), udpaddr, hash)))
+     {
+       /* If we didn't get an answer advertising a maximal packet in EDNS,
+        fall back to 1280, which should work everywhere on IPv6.
+@@ -761,9 +758,6 @@ void reply_query(int fd, int family, tim
+   size_t nn;
+   struct server *server;
+   void *hash;
+-#ifndef HAVE_DNSSEC
+-  unsigned int crc;
+-#endif
+   /* packet buffer overwritten */
+   daemon->srv_save = NULL;
+@@ -790,12 +784,7 @@ void reply_query(int fd, int family, tim
+   if (difftime(now, server->pktsz_reduced) > UDP_TEST_TIME)
+     server->edns_pktsz = daemon->edns_pktsz;
+-#ifdef HAVE_DNSSEC
+   hash = hash_questions(header, n, daemon->namebuff);
+-#else
+-  hash = &crc;
+-  crc = questions_crc(header, n, daemon->namebuff);
+-#endif
+   
+   if (!(forward = lookup_frec(ntohs(header->id), fd, family, hash)))
+     return;
+@@ -1100,8 +1089,7 @@ void reply_query(int fd, int family, tim
+                       log_query(F_NOEXTRA | F_DNSSEC | F_IPV6, daemon->keyname, (union all_addr *)&(server->addr.in6.sin6_addr),
+                                 querystr("dnssec-query", querytype));
+   
+-                    if ((hash = hash_questions(header, nn, daemon->namebuff)))
+-                      memcpy(new->hash, hash, HASH_SIZE);
++                    memcpy(new->hash, hash_questions(header, nn, daemon->namebuff), HASH_SIZE);
+                     new->new_id = get_id();
+                     header->id = htons(new->new_id);
+                     /* Save query for retransmission */
+@@ -1937,15 +1925,9 @@ unsigned char *tcp_request(int confd, ti
+             if (!flags && last_server)
+               {
+                 struct server *firstsendto = NULL;
+-#ifdef HAVE_DNSSEC
+-                unsigned char *newhash, hash[HASH_SIZE];
+-                if ((newhash = hash_questions(header, (unsigned int)size, daemon->namebuff)))
+-                  memcpy(hash, newhash, HASH_SIZE);
+-                else
+-                  memset(hash, 0, HASH_SIZE);
+-#else
+-                unsigned int crc = questions_crc(header, (unsigned int)size, daemon->namebuff);
+-#endif                  
++                unsigned char hash[HASH_SIZE];
++                memcpy(hash, hash_questions(header, (unsigned int)size, daemon->namebuff), HASH_SIZE);
++
+                 /* Loop round available servers until we succeed in connecting to one.
+                    Note that this code subtly ensures that consecutive queries on this connection
+                    which can go to the same server, do so. */
+@@ -2068,20 +2050,11 @@ unsigned char *tcp_request(int confd, ti
+                     /* If the crc of the question section doesn't match the crc we sent, then
+                        someone might be attempting to insert bogus values into the cache by 
+                        sending replies containing questions and bogus answers. */
+-#ifdef HAVE_DNSSEC
+-                    newhash = hash_questions(header, (unsigned int)m, daemon->namebuff);
+-                    if (!newhash || memcmp(hash, newhash, HASH_SIZE) != 0)
++                    if (memcmp(hash, hash_questions(header, (unsigned int)m, daemon->namebuff), HASH_SIZE) != 0)
+                       { 
+                         m = 0;
+                         break;
+                       }
+-#else                   
+-                    if (crc != questions_crc(header, (unsigned int)m, daemon->namebuff))
+-                      {
+-                        m = 0;
+-                        break;
+-                      }
+-#endif
+                     m = process_reply(header, now, last_server, (unsigned int)m, 
+                                       option_bool(OPT_NO_REBIND) && !norebind, no_cache_dnssec, cache_secure, bogusanswer,
+@@ -2295,7 +2268,7 @@ static struct frec *lookup_frec(unsigned
+   for(f = daemon->frec_list; f; f = f->next)
+     if (f->sentto && f->new_id == id && 
+-      (!hash || memcmp(hash, f->hash, HASH_SIZE) == 0))
++      (memcmp(hash, f->hash, HASH_SIZE) == 0))
+       {
+       /* sent from random port */
+       if (family == AF_INET && f->rfd4 && f->rfd4->fd == fd)
+--- /dev/null
++++ b/src/hash_questions.c
+@@ -0,0 +1,281 @@
++/* Copyright (c) 2012-2020 Simon Kelley
++
++   This program is free software; you can redistribute it and/or modify
++   it under the terms of the GNU General Public License as published by
++   the Free Software Foundation; version 2 dated June, 1991, or
++   (at your option) version 3 dated 29 June, 2007.
++
++   This program 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 General Public License for more details.
++
++   You should have received a copy of the GNU General Public License
++   along with this program.  If not, see <http://www.gnu.org/licenses/>.
++*/
++
++
++/* Hash the question section. This is used to safely detect query 
++   retransmission and to detect answers to questions we didn't ask, which 
++   might be poisoning attacks. Note that we decode the name rather 
++   than CRC the raw bytes, since replies might be compressed differently. 
++   We ignore case in the names for the same reason. 
++
++   The hash used is SHA-256. If we're building with DNSSEC support,
++   we use the Nettle cypto library. If not, we prefer not to
++   add a dependency on Nettle, and use a stand-alone implementaion. 
++*/
++
++#include "dnsmasq.h"
++
++#ifdef HAVE_DNSSEC
++unsigned char *hash_questions(struct dns_header *header, size_t plen, char *name)
++{
++  int q;
++  unsigned char *p = (unsigned char *)(header+1);
++  const struct nettle_hash *hash;
++  void *ctx;
++  unsigned char *digest;
++  
++  if (!(hash = hash_find("sha256")) || !hash_init(hash, &ctx, &digest))
++    {
++      /* don't think this can ever happen. */
++      static unsigned char dummy[HASH_SIZE];
++      static int warned = 0;
++
++      if (warned)
++      my_syslog(LOG_ERR, _("Failed to create SHA-256 hash object"));
++      warned = 1;
++     
++      return dummy;
++    }
++  
++  for (q = ntohs(header->qdcount); q != 0; q--) 
++    {
++      char *cp, c;
++
++      if (!extract_name(header, plen, &p, name, 1, 4))
++      break; /* bad packet */
++
++      for (cp = name; (c = *cp); cp++)
++       if (c >= 'A' && c <= 'Z')
++         *cp += 'a' - 'A';
++
++      hash->update(ctx, cp - name, (unsigned char *)name);
++      /* CRC the class and type as well */
++      hash->update(ctx, 4, p);
++
++      p += 4;
++      if (!CHECK_LEN(header, p, plen, 0))
++      break; /* bad packet */
++    }
++  
++  hash->digest(ctx, hash->digest_size, digest);
++  return digest;
++}
++
++#else /* HAVE_DNSSEC */
++
++#define SHA256_BLOCK_SIZE 32            // SHA256 outputs a 32 byte digest
++typedef unsigned char BYTE;             // 8-bit byte
++typedef unsigned int  WORD;             // 32-bit word, change to "long" for 16-bit machines
++
++typedef struct {
++  BYTE data[64];
++  WORD datalen;
++  unsigned long long bitlen;
++  WORD state[8];
++} SHA256_CTX;
++
++static void sha256_init(SHA256_CTX *ctx);
++static void sha256_update(SHA256_CTX *ctx, const BYTE data[], size_t len);
++static void sha256_final(SHA256_CTX *ctx, BYTE hash[]);
++
++
++unsigned char *hash_questions(struct dns_header *header, size_t plen, char *name)
++{
++  int q;
++  unsigned char *p = (unsigned char *)(header+1);
++  SHA256_CTX ctx;
++  static BYTE digest[SHA256_BLOCK_SIZE];
++  
++  sha256_init(&ctx);
++    
++  for (q = ntohs(header->qdcount); q != 0; q--) 
++    {
++      char *cp, c;
++
++      if (!extract_name(header, plen, &p, name, 1, 4))
++      break; /* bad packet */
++
++      for (cp = name; (c = *cp); cp++)
++       if (c >= 'A' && c <= 'Z')
++         *cp += 'a' - 'A';
++
++      sha256_update(&ctx, (BYTE *)name, cp - name);
++      /* CRC the class and type as well */
++      sha256_update(&ctx, (BYTE *)p, 4);
++
++      p += 4;
++      if (!CHECK_LEN(header, p, plen, 0))
++      break; /* bad packet */
++    }
++  
++  sha256_final(&ctx, digest);
++  return (unsigned char *)digest;
++}
++
++/* Code from here onwards comes from https://github.com/B-Con/crypto-algorithms
++   and was written by Brad Conte (brad@bradconte.com), to whom all credit is given.
++
++   This code is in the public domain, and the copyright notice at the head of this 
++   file does not apply to it.
++*/
++
++
++/****************************** MACROS ******************************/
++#define ROTLEFT(a,b) (((a) << (b)) | ((a) >> (32-(b))))
++#define ROTRIGHT(a,b) (((a) >> (b)) | ((a) << (32-(b))))
++
++#define CH(x,y,z) (((x) & (y)) ^ (~(x) & (z)))
++#define MAJ(x,y,z) (((x) & (y)) ^ ((x) & (z)) ^ ((y) & (z)))
++#define EP0(x) (ROTRIGHT(x,2) ^ ROTRIGHT(x,13) ^ ROTRIGHT(x,22))
++#define EP1(x) (ROTRIGHT(x,6) ^ ROTRIGHT(x,11) ^ ROTRIGHT(x,25))
++#define SIG0(x) (ROTRIGHT(x,7) ^ ROTRIGHT(x,18) ^ ((x) >> 3))
++#define SIG1(x) (ROTRIGHT(x,17) ^ ROTRIGHT(x,19) ^ ((x) >> 10))
++
++/**************************** VARIABLES *****************************/
++static const WORD k[64] = {
++                         0x428a2f98,0x71374491,0xb5c0fbcf,0xe9b5dba5,0x3956c25b,0x59f111f1,0x923f82a4,0xab1c5ed5,
++                         0xd807aa98,0x12835b01,0x243185be,0x550c7dc3,0x72be5d74,0x80deb1fe,0x9bdc06a7,0xc19bf174,
++                         0xe49b69c1,0xefbe4786,0x0fc19dc6,0x240ca1cc,0x2de92c6f,0x4a7484aa,0x5cb0a9dc,0x76f988da,
++                         0x983e5152,0xa831c66d,0xb00327c8,0xbf597fc7,0xc6e00bf3,0xd5a79147,0x06ca6351,0x14292967,
++                         0x27b70a85,0x2e1b2138,0x4d2c6dfc,0x53380d13,0x650a7354,0x766a0abb,0x81c2c92e,0x92722c85,
++                         0xa2bfe8a1,0xa81a664b,0xc24b8b70,0xc76c51a3,0xd192e819,0xd6990624,0xf40e3585,0x106aa070,
++                         0x19a4c116,0x1e376c08,0x2748774c,0x34b0bcb5,0x391c0cb3,0x4ed8aa4a,0x5b9cca4f,0x682e6ff3,
++                         0x748f82ee,0x78a5636f,0x84c87814,0x8cc70208,0x90befffa,0xa4506ceb,0xbef9a3f7,0xc67178f2
++};
++
++/*********************** FUNCTION DEFINITIONS ***********************/
++static void sha256_transform(SHA256_CTX *ctx, const BYTE data[])
++{
++  WORD a, b, c, d, e, f, g, h, i, j, t1, t2, m[64];
++  
++  for (i = 0, j = 0; i < 16; ++i, j += 4)
++    m[i] = (data[j] << 24) | (data[j + 1] << 16) | (data[j + 2] << 8) | (data[j + 3]);
++  for ( ; i < 64; ++i)
++    m[i] = SIG1(m[i - 2]) + m[i - 7] + SIG0(m[i - 15]) + m[i - 16];
++
++  a = ctx->state[0];
++  b = ctx->state[1];
++  c = ctx->state[2];
++  d = ctx->state[3];
++  e = ctx->state[4];
++  f = ctx->state[5];
++  g = ctx->state[6];
++  h = ctx->state[7];
++
++  for (i = 0; i < 64; ++i)
++    {
++      t1 = h + EP1(e) + CH(e,f,g) + k[i] + m[i];
++      t2 = EP0(a) + MAJ(a,b,c);
++      h = g;
++      g = f;
++      f = e;
++      e = d + t1;
++      d = c;
++      c = b;
++      b = a;
++      a = t1 + t2;
++    }
++  
++  ctx->state[0] += a;
++  ctx->state[1] += b;
++  ctx->state[2] += c;
++  ctx->state[3] += d;
++  ctx->state[4] += e;
++  ctx->state[5] += f;
++  ctx->state[6] += g;
++  ctx->state[7] += h;
++}
++
++static void sha256_init(SHA256_CTX *ctx)
++{
++  ctx->datalen = 0;
++  ctx->bitlen = 0;
++  ctx->state[0] = 0x6a09e667;
++  ctx->state[1] = 0xbb67ae85;
++  ctx->state[2] = 0x3c6ef372;
++  ctx->state[3] = 0xa54ff53a;
++  ctx->state[4] = 0x510e527f;
++  ctx->state[5] = 0x9b05688c;
++  ctx->state[6] = 0x1f83d9ab;
++  ctx->state[7] = 0x5be0cd19;
++}
++
++static void sha256_update(SHA256_CTX *ctx, const BYTE data[], size_t len)
++{
++  WORD i;
++  
++  for (i = 0; i < len; ++i)
++    {
++      ctx->data[ctx->datalen] = data[i];
++      ctx->datalen++;
++      if (ctx->datalen == 64) {
++      sha256_transform(ctx, ctx->data);
++      ctx->bitlen += 512;
++      ctx->datalen = 0;
++      }
++    }
++}
++
++static void sha256_final(SHA256_CTX *ctx, BYTE hash[])
++{
++  WORD i;
++  
++  i = ctx->datalen;
++
++  // Pad whatever data is left in the buffer.
++  if (ctx->datalen < 56)
++    {
++      ctx->data[i++] = 0x80;
++      while (i < 56)
++      ctx->data[i++] = 0x00;
++    }
++  else
++    {
++      ctx->data[i++] = 0x80;
++      while (i < 64)
++      ctx->data[i++] = 0x00;
++      sha256_transform(ctx, ctx->data);
++      memset(ctx->data, 0, 56);
++    }
++  
++  // Append to the padding the total message's length in bits and transform.
++  ctx->bitlen += ctx->datalen * 8;
++  ctx->data[63] = ctx->bitlen;
++  ctx->data[62] = ctx->bitlen >> 8;
++  ctx->data[61] = ctx->bitlen >> 16;
++  ctx->data[60] = ctx->bitlen >> 24;
++  ctx->data[59] = ctx->bitlen >> 32;
++  ctx->data[58] = ctx->bitlen >> 40;
++  ctx->data[57] = ctx->bitlen >> 48;
++  ctx->data[56] = ctx->bitlen >> 56;
++  sha256_transform(ctx, ctx->data);
++  
++  // Since this implementation uses little endian byte ordering and SHA uses big endian,
++  // reverse all the bytes when copying the final state to the output hash.
++  for (i = 0; i < 4; ++i)
++    {
++      hash[i]      = (ctx->state[0] >> (24 - i * 8)) & 0x000000ff;
++      hash[i + 4]  = (ctx->state[1] >> (24 - i * 8)) & 0x000000ff;
++      hash[i + 8]  = (ctx->state[2] >> (24 - i * 8)) & 0x000000ff;
++      hash[i + 12] = (ctx->state[3] >> (24 - i * 8)) & 0x000000ff;
++      hash[i + 16] = (ctx->state[4] >> (24 - i * 8)) & 0x000000ff;
++      hash[i + 20] = (ctx->state[5] >> (24 - i * 8)) & 0x000000ff;
++      hash[i + 24] = (ctx->state[6] >> (24 - i * 8)) & 0x000000ff;
++      hash[i + 28] = (ctx->state[7] >> (24 - i * 8)) & 0x000000ff;
++    }
++}
++
++#endif
+--- a/src/rfc1035.c
++++ b/src/rfc1035.c
+@@ -333,55 +333,6 @@ unsigned char *skip_section(unsigned cha
+   return ansp;
+ }
+-/* CRC the question section. This is used to safely detect query 
+-   retransmission and to detect answers to questions we didn't ask, which 
+-   might be poisoning attacks. Note that we decode the name rather 
+-   than CRC the raw bytes, since replies might be compressed differently. 
+-   We ignore case in the names for the same reason. Return all-ones
+-   if there is not question section. */
+-#ifndef HAVE_DNSSEC
+-unsigned int questions_crc(struct dns_header *header, size_t plen, char *name)
+-{
+-  int q;
+-  unsigned int crc = 0xffffffff;
+-  unsigned char *p1, *p = (unsigned char *)(header+1);
+-
+-  for (q = ntohs(header->qdcount); q != 0; q--) 
+-    {
+-      if (!extract_name(header, plen, &p, name, 1, 4))
+-      return crc; /* bad packet */
+-      
+-      for (p1 = (unsigned char *)name; *p1; p1++)
+-      {
+-        int i = 8;
+-        char c = *p1;
+-
+-        if (c >= 'A' && c <= 'Z')
+-          c += 'a' - 'A';
+-
+-        crc ^= c << 24;
+-        while (i--)
+-          crc = crc & 0x80000000 ? (crc << 1) ^ 0x04c11db7 : crc << 1;
+-      }
+-      
+-      /* CRC the class and type as well */
+-      for (p1 = p; p1 < p+4; p1++)
+-      {
+-        int i = 8;
+-        crc ^= *p1 << 24;
+-        while (i--)
+-          crc = crc & 0x80000000 ? (crc << 1) ^ 0x04c11db7 : crc << 1;
+-      }
+-
+-      p += 4;
+-      if (!CHECK_LEN(header, p, plen, 0))
+-      return crc; /* bad packet */
+-    }
+-
+-  return crc;
+-}
+-#endif
+-
+ size_t resize_packet(struct dns_header *header, size_t plen, unsigned char *pheader, size_t hlen)
+ {
+   unsigned char *ansp = skip_questions(header, plen);
diff --git a/package/network/services/dnsmasq/patches/0105-Optimse-RR-digest-calculation-in-DNSSEC.patch b/package/network/services/dnsmasq/patches/0105-Optimse-RR-digest-calculation-in-DNSSEC.patch
new file mode 100644 (file)
index 0000000..10f9662
--- /dev/null
@@ -0,0 +1,122 @@
+From 059aded0700309308dafd9720b0313ce52f6e189 Mon Sep 17 00:00:00 2001
+From: Simon Kelley <simon@thekelleys.org.uk>
+Date: Thu, 12 Nov 2020 23:09:15 +0000
+Subject: Optimse RR digest calculation in DNSSEC.
+
+If an RR is of a type which doesn't need canonicalisation,
+bypass the relatively slow canonicalisation code, and insert
+it direct into the digest.
+---
+ src/dnssec.c | 82 +++++++++++++++++++++++++++++++---------------------
+ 1 file changed, 49 insertions(+), 33 deletions(-)
+
+--- a/src/dnssec.c
++++ b/src/dnssec.c
+@@ -559,7 +559,7 @@ static int validate_rrset(time_t now, st
+       hash->update(ctx, (unsigned int)wire_len, (unsigned char*)keyname);
+       from_wire(keyname);
+-#define RRBUFLEN 300 /* Most RRs are smaller than this. */
++#define RRBUFLEN 128 /* Most RRs are smaller than this. */
+       
+       for (i = 0; i < rrsetidx; ++i)
+       {
+@@ -597,50 +597,66 @@ static int validate_rrset(time_t now, st
+         hash->update(ctx, (unsigned int)wire_len, (unsigned char *)name_start);
+         hash->update(ctx, 4, p); /* class and type */
+         hash->update(ctx, 4, (unsigned char *)&nsigttl);
+-        
+-        p += 8; /* skip class, type, ttl */
++
++        p += 8; /* skip type, class, ttl */
+         GETSHORT(rdlen, p);
+         if (!CHECK_LEN(header, p, plen, rdlen))
+           return STAT_BOGUS; 
+-        
+-        /* canonicalise rdata and calculate length of same, use 
+-           name buffer as workspace for get_rdata. */
+-        state.ip = p;
+-        state.op = NULL;
+-        state.desc = rr_desc;
+-        state.buff = name;
+-        state.end = p + rdlen;
+-        
+-        for (j = 0; get_rdata(header, plen, &state); j++)
+-          if (j < RRBUFLEN)
+-            rrbuf[j] = *state.op;
+-        len = htons((u16)j);
+-        hash->update(ctx, 2, (unsigned char *)&len); 
+-
+-        /* If the RR is shorter than RRBUFLEN (most of them, in practice)
+-           then we can just digest it now. If it exceeds RRBUFLEN we have to
+-           go back to the start and do it in chunks. */
+-        if (j >= RRBUFLEN)
++        /* Optimisation for RR types which need no cannonicalisation.
++           This includes DNSKEY DS NSEC and NSEC3, which are also long, so
++           it saves lots of calls to get_rdata, and avoids the pessimal
++           segmented insertion, even with a small rrbuf[].
++           
++           If canonicalisation is not needed, a simple insertion into the hash works.
++        */
++        if (*rr_desc == (u16)-1)
++          {
++            len = htons(rdlen);
++            hash->update(ctx, 2, (unsigned char *)&len);
++            hash->update(ctx, rdlen, p);
++          }
++        else
+           {
++            /* canonicalise rdata and calculate length of same, use 
++               name buffer as workspace for get_rdata. */
+             state.ip = p;
+             state.op = NULL;
+             state.desc = rr_desc;
+-
++            state.buff = name;
++            state.end = p + rdlen;
++            
+             for (j = 0; get_rdata(header, plen, &state); j++)
++              if (j < RRBUFLEN)
++                rrbuf[j] = *state.op;
++            
++            len = htons((u16)j);
++            hash->update(ctx, 2, (unsigned char *)&len); 
++            
++            /* If the RR is shorter than RRBUFLEN (most of them, in practice)
++               then we can just digest it now. If it exceeds RRBUFLEN we have to
++               go back to the start and do it in chunks. */
++            if (j >= RRBUFLEN)
+               {
+-                 rrbuf[j] = *state.op;
+-
+-                 if (j == RRBUFLEN - 1)
+-                   {
+-                     hash->update(ctx, RRBUFLEN, rrbuf);
+-                     j = -1;
+-                   }
++                state.ip = p;
++                state.op = NULL;
++                state.desc = rr_desc;
++                
++                for (j = 0; get_rdata(header, plen, &state); j++)
++                  {
++                    rrbuf[j] = *state.op;
++                    
++                    if (j == RRBUFLEN - 1)
++                      {
++                        hash->update(ctx, RRBUFLEN, rrbuf);
++                        j = -1;
++                      }
++                  }
+               }
++            
++            if (j != 0)
++              hash->update(ctx, j, rrbuf);
+           }
+-        
+-        if (j != 0)
+-          hash->update(ctx, j, rrbuf);
+       }
+      
+       hash->digest(ctx, hash->digest_size, digest);
diff --git a/package/network/services/dnsmasq/patches/0107-Add-missing-check-for-NULL-return-from-allocate_rfd.patch b/package/network/services/dnsmasq/patches/0107-Add-missing-check-for-NULL-return-from-allocate_rfd.patch
new file mode 100644 (file)
index 0000000..f9b4b5c
--- /dev/null
@@ -0,0 +1,64 @@
+From 824461192ca5098043f9ca4ddeba7df1f65b30ba Mon Sep 17 00:00:00 2001
+From: Simon Kelley <simon@thekelleys.org.uk>
+Date: Sun, 15 Nov 2020 22:13:25 +0000
+Subject: Add missing check for NULL return from allocate_rfd().
+
+---
+ src/forward.c | 18 ++++++++++--------
+ 1 file changed, 10 insertions(+), 8 deletions(-)
+
+--- a/src/forward.c
++++ b/src/forward.c
+@@ -815,7 +815,6 @@ void reply_query(int fd, int family, tim
+       int is_sign;
+ #ifdef HAVE_DNSSEC
+-      /* For DNSSEC originated queries, just retry the query to the same server. */
+       if (forward->flags & (FREC_DNSKEY_QUERY | FREC_DS_QUERY))
+       {
+         struct server *start;
+@@ -841,6 +840,8 @@ void reply_query(int fd, int family, tim
+             }
+           
+         
++        fd = -1;
++
+         if (start->sfd)
+           fd = start->sfd->fd;
+         else
+@@ -848,19 +849,21 @@ void reply_query(int fd, int family, tim
+             if (start->addr.sa.sa_family == AF_INET6)
+               {
+                 /* may have changed family */
+-                if (!forward->rfd6)
+-                  forward->rfd6 = allocate_rfd(AF_INET6);
+-                fd = forward->rfd6->fd;
++                if (forward->rfd6 || (forward->rfd6 = allocate_rfd(AF_INET6)))
++                  fd = forward->rfd6->fd;
+               }
+             else
+               {
+                 /* may have changed family */
+-                if (!forward->rfd4)
+-                  forward->rfd4 = allocate_rfd(AF_INET);
+-                fd = forward->rfd4->fd;
++                if (forward->rfd4 || (forward->rfd4 = allocate_rfd(AF_INET)))
++                  fd = forward->rfd4->fd;
+               }
+           }
+       
++        /* Can't get socket. */
++        if (fd == -1)
++          return;
++        
+         while (retry_send(sendto(fd, (char *)header, plen, 0,
+                                  &start->addr.sa,
+                                  sa_len(&start->addr))));
+@@ -2261,7 +2264,6 @@ struct frec *get_new_frec(time_t now, in
+   return f; /* OK if malloc fails and this is NULL */
+ }
+-/* crc is all-ones if not known. */
+ static struct frec *lookup_frec(unsigned short id, int fd, int family, void *hash)
+ {
+   struct frec *f;
diff --git a/package/network/services/dnsmasq/patches/0108-Handle-multiple-identical-near-simultaneous-DNS-quer.patch b/package/network/services/dnsmasq/patches/0108-Handle-multiple-identical-near-simultaneous-DNS-quer.patch
new file mode 100644 (file)
index 0000000..c4beb6e
--- /dev/null
@@ -0,0 +1,352 @@
+From 15b60ddf935a531269bb8c68198de012a4967156 Mon Sep 17 00:00:00 2001
+From: Simon Kelley <simon@thekelleys.org.uk>
+Date: Wed, 18 Nov 2020 18:34:55 +0000
+Subject: Handle multiple identical near simultaneous DNS queries better.
+
+Previously, such queries would all be forwarded
+independently. This is, in theory, inefficent but in practise
+not a problem, _except_ that is means that an answer for any
+of the forwarded queries will be accepted and cached.
+An attacker can send a query multiple times, and for each repeat,
+another {port, ID} becomes capable of accepting the answer he is
+sending in the blind, to random IDs and ports. The chance of a
+succesful attack is therefore multiplied by the number of repeats
+of the query. The new behaviour detects repeated queries and
+merely stores the clients sending repeats so that when the
+first query completes, the answer can be sent to all the
+clients who asked. Refer: CERT VU#434904.
+---
+ CHANGELOG     |  16 +++++-
+ src/dnsmasq.h |  19 ++++---
+ src/forward.c | 142 ++++++++++++++++++++++++++++++++++++++++++--------
+ 3 files changed, 147 insertions(+), 30 deletions(-)
+
+--- a/CHANGELOG
++++ b/CHANGELOG
+@@ -4,13 +4,27 @@
+       Be sure to only accept UDP DNS query replies at the address
+       from which the query was originated. This keeps as much entropy
+-      in the {query-ID, random-port} tuple as possible, help defeat
++      in the {query-ID, random-port} tuple as possible, to help defeat
+       cache poisoning attacks. Refer: CERT VU#434904.
+       Use the SHA-256 hash function to verify that DNS answers
+       received are for the questions originally asked. This replaces
+       the slightly insecure SHA-1 (when compiled with DNSSEC) or
+       the very insecure CRC32 (otherwise). Refer: CERT VU#434904.
++
++      Handle multiple identical near simultaneous DNS queries better.
++      Previously, such queries would all be forwarded
++      independently. This is, in theory, inefficent but in practise
++      not a problem, _except_ that is means that an answer for any
++      of the forwarded queries will be accepted and cached.
++      An attacker can send a query multiple times, and for each repeat,
++      another {port, ID} becomes capable of accepting the answer he is
++      sending in the blind, to random IDs and ports. The chance of a
++      succesful attack is therefore multiplied by the number of repeats
++      of the query. The new behaviour detects repeated queries and
++      merely stores the clients sending repeats so that when the
++      first query completes, the answer can be sent to all the
++      clients who asked. Refer: CERT VU#434904.
+       
+ version 2.81
+--- a/src/dnsmasq.h
++++ b/src/dnsmasq.h
+@@ -642,19 +642,24 @@ struct hostsfile {
+ #define FREC_DO_QUESTION       64
+ #define FREC_ADDED_PHEADER    128
+ #define FREC_TEST_PKTSZ       256
+-#define FREC_HAS_EXTRADATA    512        
++#define FREC_HAS_EXTRADATA    512
++#define FREC_HAS_PHEADER     1024
+ #define HASH_SIZE 32 /* SHA-256 digest size */
+ struct frec {
+-  union mysockaddr source;
+-  union all_addr dest;
++  struct frec_src {
++    union mysockaddr source;
++    union all_addr dest;
++    unsigned int iface, log_id;
++    unsigned short orig_id;
++    struct frec_src *next;
++  } frec_src;
+   struct server *sentto; /* NULL means free */
+   struct randfd *rfd4;
+   struct randfd *rfd6;
+-  unsigned int iface;
+-  unsigned short orig_id, new_id;
+-  int log_id, fd, forwardall, flags;
++  unsigned short new_id;
++  int fd, forwardall, flags;
+   time_t time;
+   unsigned char *hash[HASH_SIZE];
+ #ifdef HAVE_DNSSEC 
+@@ -1069,6 +1074,8 @@ extern struct daemon {
+   int back_to_the_future;
+ #endif
+   struct frec *frec_list;
++  struct frec_src *free_frec_src;
++  int frec_src_count;
+   struct serverfd *sfds;
+   struct irec *interfaces;
+   struct listener *listeners;
+--- a/src/forward.c
++++ b/src/forward.c
+@@ -20,6 +20,8 @@ static struct frec *lookup_frec(unsigned
+ static struct frec *lookup_frec_by_sender(unsigned short id,
+                                         union mysockaddr *addr,
+                                         void *hash);
++static struct frec *lookup_frec_by_query(void *hash, unsigned int flags);
++
+ static unsigned short get_id(void);
+ static void free_frec(struct frec *f);
+@@ -247,6 +249,7 @@ static int forward_query(int udpfd, unio
+   int type = SERV_DO_DNSSEC, norebind = 0;
+   union all_addr *addrp = NULL;
+   unsigned int flags = 0;
++  unsigned int fwd_flags = 0;
+   struct server *start = NULL;
+   void *hash = hash_questions(header, plen, daemon->namebuff);
+ #ifdef HAVE_DNSSEC
+@@ -255,7 +258,18 @@ static int forward_query(int udpfd, unio
+   unsigned int gotname = extract_request(header, plen, daemon->namebuff, NULL);
+   unsigned char *oph = find_pseudoheader(header, plen, NULL, NULL, NULL, NULL);
+   (void)do_bit;
+-
++  
++  if (header->hb4 & HB4_CD)
++    fwd_flags |= FREC_CHECKING_DISABLED;
++  if (ad_reqd)
++    fwd_flags |= FREC_AD_QUESTION;
++  if (oph)
++    fwd_flags |= FREC_HAS_PHEADER;
++#ifdef HAVE_DNSSEC
++  if (do_bit)
++    fwd_flags |= FREC_DO_QUESTION;
++#endif
++  
+   /* may be no servers available. */
+   if (forward || (forward = lookup_frec_by_sender(ntohs(header->id), udpaddr, hash)))
+     {
+@@ -328,6 +342,39 @@ static int forward_query(int udpfd, unio
+     }
+   else 
+     {
++      /* Query from new source, but the same query may be in progress
++       from another source. If so, just add this client to the
++       list that will get the reply.
++       
++       Note that is the EDNS client subnet option is in use, we can't do this,
++       as the clients (and therefore query EDNS options) will be different
++       for each query. The EDNS subnet code has checks to avoid
++       attacks in this case. */
++      if (!option_bool(OPT_CLIENT_SUBNET) && (forward = lookup_frec_by_query(hash, fwd_flags)))
++      {
++        /* Note whine_malloc() zeros memory. */
++        if (!daemon->free_frec_src &&
++            daemon->frec_src_count < daemon->ftabsize &&
++            (daemon->free_frec_src = whine_malloc(sizeof(struct frec_src))))
++          daemon->frec_src_count++;
++        
++        /* If we've been spammed with many duplicates, just drop the query. */
++        if (daemon->free_frec_src)
++          {
++            struct frec_src *new = daemon->free_frec_src;
++            daemon->free_frec_src = new->next;
++            new->next = forward->frec_src.next;
++            forward->frec_src.next = new;
++            new->orig_id = ntohs(header->id);
++            new->source = *udpaddr;
++            new->dest = *dst_addr;
++            new->log_id = daemon->log_id;
++            new->iface = dst_iface;
++          }
++        
++        return 1;
++      }
++      
+       if (gotname)
+       flags = search_servers(now, &addrp, gotname, daemon->namebuff, &type, &domain, &norebind);
+       
+@@ -335,22 +382,22 @@ static int forward_query(int udpfd, unio
+       do_dnssec = type & SERV_DO_DNSSEC;
+ #endif
+       type &= ~SERV_DO_DNSSEC;      
+-
++      
+       if (daemon->servers && !flags)
+       forward = get_new_frec(now, NULL, 0);
+       /* table full - flags == 0, return REFUSED */
+       
+       if (forward)
+       {
+-        forward->source = *udpaddr;
+-        forward->dest = *dst_addr;
+-        forward->iface = dst_iface;
+-        forward->orig_id = ntohs(header->id);
++        forward->frec_src.source = *udpaddr;
++        forward->frec_src.orig_id = ntohs(header->id);
++        forward->frec_src.dest = *dst_addr;
++        forward->frec_src.iface = dst_iface;
+         forward->new_id = get_id();
+         forward->fd = udpfd;
+         memcpy(forward->hash, hash, HASH_SIZE);
+         forward->forwardall = 0;
+-        forward->flags = 0;
++        forward->flags = fwd_flags;
+         if (norebind)
+           forward->flags |= FREC_NOREBIND;
+         if (header->hb4 & HB4_CD)
+@@ -405,9 +452,9 @@ static int forward_query(int udpfd, unio
+       unsigned char *pheader;
+       
+       /* If a query is retried, use the log_id for the retry when logging the answer. */
+-      forward->log_id = daemon->log_id;
++      forward->frec_src.log_id = daemon->log_id;
+       
+-      plen = add_edns0_config(header, plen, ((unsigned char *)header) + PACKETSZ, &forward->source, now, &subnet);
++      plen = add_edns0_config(header, plen, ((unsigned char *)header) + PACKETSZ, &forward->frec_src.source, now, &subnet);
+       
+       if (subnet)
+       forward->flags |= FREC_HAS_SUBNET;
+@@ -544,7 +591,7 @@ static int forward_query(int udpfd, unio
+       return 1;
+       
+       /* could not send on, prepare to return */ 
+-      header->id = htons(forward->orig_id);
++      header->id = htons(forward->frec_src.orig_id);
+       free_frec(forward); /* cancel */
+     }   
+   
+@@ -796,8 +843,8 @@ void reply_query(int fd, int family, tim
+   /* log_query gets called indirectly all over the place, so 
+      pass these in global variables - sorry. */
+-  daemon->log_display_id = forward->log_id;
+-  daemon->log_source_addr = &forward->source;
++  daemon->log_display_id = forward->frec_src.log_id;
++  daemon->log_source_addr = &forward->frec_src.source;
+   
+   if (daemon->ignore_addr && RCODE(header) == NOERROR &&
+       check_for_ignored_address(header, n, daemon->ignore_addr))
+@@ -1065,6 +1112,7 @@ void reply_query(int fd, int family, tim
+                     new->sentto = server;
+                     new->rfd4 = NULL;
+                     new->rfd6 = NULL;
++                    new->frec_src.next = NULL;
+                     new->flags &= ~(FREC_DNSKEY_QUERY | FREC_DS_QUERY | FREC_HAS_EXTRADATA);
+                     new->forwardall = 0;
+                     
+@@ -1199,9 +1247,11 @@ void reply_query(int fd, int family, tim
+       
+       if ((nn = process_reply(header, now, forward->sentto, (size_t)n, check_rebind, no_cache_dnssec, cache_secure, bogusanswer, 
+                             forward->flags & FREC_AD_QUESTION, forward->flags & FREC_DO_QUESTION, 
+-                            forward->flags & FREC_ADDED_PHEADER, forward->flags & FREC_HAS_SUBNET, &forward->source)))
++                            forward->flags & FREC_ADDED_PHEADER, forward->flags & FREC_HAS_SUBNET, &forward->frec_src.source)))
+       {
+-        header->id = htons(forward->orig_id);
++        struct frec_src *src;
++
++        header->id = htons(forward->frec_src.orig_id);
+         header->hb4 |= HB4_RA; /* recursion if available */
+ #ifdef HAVE_DNSSEC
+         /* We added an EDNSO header for the purpose of getting DNSSEC RRs, and set the value of the UDP payload size
+@@ -1217,13 +1267,26 @@ void reply_query(int fd, int family, tim
+           }
+ #endif
++        for (src = &forward->frec_src; src; src = src->next)
++          {
++            header->id = htons(src->orig_id);
++            
+ #ifdef HAVE_DUMPFILE
+-        dump_packet(DUMP_REPLY, daemon->packet, (size_t)nn, NULL, &forward->source);
++            dump_packet(DUMP_REPLY, daemon->packet, (size_t)nn, NULL, &src->source);
+ #endif
+-        
+-        send_from(forward->fd, option_bool(OPT_NOWILD) || option_bool (OPT_CLEVERBIND), daemon->packet, nn, 
+-                  &forward->source, &forward->dest, forward->iface);
++            
++            send_from(forward->fd, option_bool(OPT_NOWILD) || option_bool (OPT_CLEVERBIND), daemon->packet, nn, 
++                      &src->source, &src->dest, src->iface);
++
++            if (option_bool(OPT_EXTRALOG) && src != &forward->frec_src)
++              {
++                daemon->log_display_id = src->log_id;
++                daemon->log_source_addr = &src->source;
++                log_query(F_UPSTREAM, "query", NULL, "duplicate");
++              }
++          }
+       }
++
+       free_frec(forward); /* cancel */
+     }
+ }
+@@ -2153,6 +2216,17 @@ void free_rfd(struct randfd *rfd)
+ static void free_frec(struct frec *f)
+ {
++  struct frec_src *src, *tmp;
++
++   /* add back to freelist of not the record builtin to every frec. */
++  for (src = f->frec_src.next; src; src = tmp)
++    {
++      tmp = src->next;
++      src->next = daemon->free_frec_src;
++      daemon->free_frec_src = src;
++    }
++  
++  f->frec_src.next = NULL;    
+   free_rfd(f->rfd4);
+   f->rfd4 = NULL;
+   f->sentto = NULL;
+@@ -2292,17 +2366,39 @@ static struct frec *lookup_frec_by_sende
+                                         void *hash)
+ {
+   struct frec *f;
++  struct frec_src *src;
++
++  for (f = daemon->frec_list; f; f = f->next)
++    if (f->sentto &&
++      !(f->flags & (FREC_DNSKEY_QUERY | FREC_DS_QUERY)) &&
++      memcmp(hash, f->hash, HASH_SIZE) == 0)
++      for (src = &f->frec_src; src; src = src->next)
++      if (src->orig_id == id && 
++          sockaddr_isequal(&src->source, addr))
++        return f;
++  
++  return NULL;
++}
++
++static struct frec *lookup_frec_by_query(void *hash, unsigned int flags)
++{
++  struct frec *f;
++
++  /* FREC_DNSKEY and FREC_DS_QUERY are never set in flags, so the test below 
++     ensures that no frec created for internal DNSSEC query can be returned here. */
++
++#define FLAGMASK (FREC_CHECKING_DISABLED | FREC_AD_QUESTION | FREC_DO_QUESTION \
++                | FREC_HAS_PHEADER | FREC_DNSKEY_QUERY | FREC_DS_QUERY)
+   
+   for(f = daemon->frec_list; f; f = f->next)
+     if (f->sentto &&
+-      f->orig_id == id && 
+-      memcmp(hash, f->hash, HASH_SIZE) == 0 &&
+-      sockaddr_isequal(&f->source, addr))
++      (f->flags & FLAGMASK) == flags &&
++      memcmp(hash, f->hash, HASH_SIZE) == 0)
+       return f;
+-   
++  
+   return NULL;
+ }
+- 
++
+ /* Send query packet again, if we can. */
+ void resend_query()
+ {
diff --git a/package/network/services/dnsmasq/patches/0109-Handle-caching-with-EDNS-options-better.patch b/package/network/services/dnsmasq/patches/0109-Handle-caching-with-EDNS-options-better.patch
new file mode 100644 (file)
index 0000000..64fb0dc
--- /dev/null
@@ -0,0 +1,350 @@
+From 25e63f1e56f5acdcf91893a1b92ad1e0f2f552d8 Mon Sep 17 00:00:00 2001
+From: Simon Kelley <simon@thekelleys.org.uk>
+Date: Wed, 25 Nov 2020 21:17:52 +0000
+Subject: Handle caching with EDNS options better.
+
+If we add the EDNS client subnet option, or the client's
+MAC address, then the reply we get back may very depending on
+that. Since the cache is ignorant of such things, it's not safe to
+cache such replies. This patch determines when a dangerous EDNS
+option is being added and disables caching.
+
+Note that for much the same reason, we can't combine multiple
+queries for the same question when dangerous EDNS options are
+being added, and the code now handles that in the same way. This
+query combining is required for security against cache poisoning,
+so disabling the cache has a security function as well as a
+correctness one.
+---
+ man/dnsmasq.8 |  4 +--
+ src/dnsmasq.h |  3 ++-
+ src/edns0.c   | 75 ++++++++++++++++++++++++++++++++-------------------
+ src/forward.c | 41 ++++++++++++++++++----------
+ 4 files changed, 78 insertions(+), 45 deletions(-)
+
+--- a/man/dnsmasq.8
++++ b/man/dnsmasq.8
+@@ -690,8 +690,8 @@ still marks the request so that no upstr
+ address information either. The default is zero for both IPv4 and
+ IPv6. Note that upstream nameservers may be configured to return
+ different results based on this information, but the dnsmasq cache
+-does not take account. If a dnsmasq instance is configured such that
+-different results may be encountered, caching should be disabled.
++does not take account. Caching is therefore disabled for such replies,
++unless the subnet address being added is constant.
+ For example,
+ .B --add-subnet=24,96
+--- a/src/dnsmasq.h
++++ b/src/dnsmasq.h
+@@ -644,6 +644,7 @@ struct hostsfile {
+ #define FREC_TEST_PKTSZ       256
+ #define FREC_HAS_EXTRADATA    512
+ #define FREC_HAS_PHEADER     1024
++#define FREC_NO_CACHE        2048
+ #define HASH_SIZE 32 /* SHA-256 digest size */
+@@ -1628,7 +1629,7 @@ size_t add_pseudoheader(struct dns_heade
+                       unsigned short udp_sz, int optno, unsigned char *opt, size_t optlen, int set_do, int replace);
+ size_t add_do_bit(struct dns_header *header, size_t plen, unsigned char *limit);
+ size_t add_edns0_config(struct dns_header *header, size_t plen, unsigned char *limit, 
+-                      union mysockaddr *source, time_t now, int *check_subnet);
++                      union mysockaddr *source, time_t now, int *check_subnet, int *cacheable);
+ int check_source(struct dns_header *header, size_t plen, unsigned char *pseudoheader, union mysockaddr *peer);
+ /* arp.c */
+--- a/src/edns0.c
++++ b/src/edns0.c
+@@ -264,7 +264,8 @@ static void encoder(unsigned char *in, c
+   out[3] = char64(in[2]);
+ }
+-static size_t add_dns_client(struct dns_header *header, size_t plen, unsigned char *limit, union mysockaddr *l3, time_t now)
++static size_t add_dns_client(struct dns_header *header, size_t plen, unsigned char *limit,
++                           union mysockaddr *l3, time_t now, int *cacheablep)
+ {
+   int maclen, replace = 2; /* can't get mac address, just delete any incoming. */
+   unsigned char mac[DHCP_CHADDR_MAX];
+@@ -273,6 +274,7 @@ static size_t add_dns_client(struct dns_
+   if ((maclen = find_mac(l3, mac, 1, now)) == 6)
+     {
+       replace = 1;
++      *cacheablep = 0;
+       if (option_bool(OPT_MAC_HEX))
+       print_mac(encode, mac, maclen);
+@@ -288,14 +290,18 @@ static size_t add_dns_client(struct dns_
+ }
+-static size_t add_mac(struct dns_header *header, size_t plen, unsigned char *limit, union mysockaddr *l3, time_t now)
++static size_t add_mac(struct dns_header *header, size_t plen, unsigned char *limit,
++                    union mysockaddr *l3, time_t now, int *cacheablep)
+ {
+   int maclen;
+   unsigned char mac[DHCP_CHADDR_MAX];
+   if ((maclen = find_mac(l3, mac, 1, now)) != 0)
+-    plen = add_pseudoheader(header, plen, limit, PACKETSZ, EDNS0_OPTION_MAC, mac, maclen, 0, 0); 
+-    
++    {
++      *cacheablep = 0;
++      plen = add_pseudoheader(header, plen, limit, PACKETSZ, EDNS0_OPTION_MAC, mac, maclen, 0, 0); 
++    }
++  
+   return plen; 
+ }
+@@ -313,17 +319,18 @@ static void *get_addrp(union mysockaddr
+   return &addr->in.sin_addr;
+ }
+-static size_t calc_subnet_opt(struct subnet_opt *opt, union mysockaddr *source)
++static size_t calc_subnet_opt(struct subnet_opt *opt, union mysockaddr *source, int *cacheablep)
+ {
+   /* http://tools.ietf.org/html/draft-vandergaast-edns-client-subnet-02 */
+   
+   int len;
+   void *addrp = NULL;
+   int sa_family = source->sa.sa_family;
+-
++  int cacheable = 0;
++  
+   opt->source_netmask = 0;
+   opt->scope_netmask = 0;
+-
++    
+   if (source->sa.sa_family == AF_INET6 && daemon->add_subnet6)
+     {
+       opt->source_netmask = daemon->add_subnet6->mask;
+@@ -331,6 +338,7 @@ static size_t calc_subnet_opt(struct sub
+       {
+         sa_family = daemon->add_subnet6->addr.sa.sa_family;
+         addrp = get_addrp(&daemon->add_subnet6->addr, sa_family);
++        cacheable = 1;
+       } 
+       else 
+       addrp = &source->in6.sin6_addr;
+@@ -343,6 +351,7 @@ static size_t calc_subnet_opt(struct sub
+       {
+         sa_family = daemon->add_subnet4->addr.sa.sa_family;
+         addrp = get_addrp(&daemon->add_subnet4->addr, sa_family);
++        cacheable = 1; /* Address is constant */
+       } 
+       else 
+         addrp = &source->in.sin_addr;
+@@ -350,8 +359,6 @@ static size_t calc_subnet_opt(struct sub
+   
+   opt->family = htons(sa_family == AF_INET6 ? 2 : 1);
+   
+-  len = 0;
+-  
+   if (addrp && opt->source_netmask != 0)
+     {
+       len = ((opt->source_netmask - 1) >> 3) + 1;
+@@ -359,18 +366,26 @@ static size_t calc_subnet_opt(struct sub
+       if (opt->source_netmask & 7)
+       opt->addr[len-1] &= 0xff << (8 - (opt->source_netmask & 7));
+     }
++  else
++    {
++      cacheable = 1; /* No address ever supplied. */
++      len = 0;
++    }
++
++  if (cacheablep)
++    *cacheablep = cacheable;
+   
+   return len + 4;
+ }
+  
+-static size_t add_source_addr(struct dns_header *header, size_t plen, unsigned char *limit, union mysockaddr *source)
++static size_t add_source_addr(struct dns_header *header, size_t plen, unsigned char *limit, union mysockaddr *source, int *cacheable)
+ {
+   /* http://tools.ietf.org/html/draft-vandergaast-edns-client-subnet-02 */
+   
+   int len;
+   struct subnet_opt opt;
+   
+-  len = calc_subnet_opt(&opt, source);
++  len = calc_subnet_opt(&opt, source, cacheable);
+   return add_pseudoheader(header, plen, (unsigned char *)limit, PACKETSZ, EDNS0_OPTION_CLIENT_SUBNET, (unsigned char *)&opt, len, 0, 0);
+ }
+@@ -383,18 +398,18 @@ int check_source(struct dns_header *head
+   unsigned char *p;
+   int code, i, rdlen;
+   
+-   calc_len = calc_subnet_opt(&opt, peer);
+-   
+-   if (!(p = skip_name(pseudoheader, header, plen, 10)))
+-     return 1;
+-   
+-   p += 8; /* skip UDP length and RCODE */
++  calc_len = calc_subnet_opt(&opt, peer, NULL);
+    
+-   GETSHORT(rdlen, p);
+-   if (!CHECK_LEN(header, p, plen, rdlen))
+-     return 1; /* bad packet */
+-   
+-   /* check if option there */
++  if (!(p = skip_name(pseudoheader, header, plen, 10)))
++    return 1;
++  
++  p += 8; /* skip UDP length and RCODE */
++  
++  GETSHORT(rdlen, p);
++  if (!CHECK_LEN(header, p, plen, rdlen))
++    return 1; /* bad packet */
++  
++  /* check if option there */
+    for (i = 0; i + 4 < rdlen; i += len + 4)
+      {
+        GETSHORT(code, p);
+@@ -412,24 +427,28 @@ int check_source(struct dns_header *head
+    return 1;
+ }
++/* Set *check_subnet if we add a client subnet option, which needs to checked 
++   in the reply. Set *cacheable to zero if we add an option which the answer
++   may depend on. */
+ size_t add_edns0_config(struct dns_header *header, size_t plen, unsigned char *limit, 
+-                      union mysockaddr *source, time_t now, int *check_subnet)    
++                      union mysockaddr *source, time_t now, int *check_subnet, int *cacheable)    
+ {
+   *check_subnet = 0;
+-
++  *cacheable = 1;
++  
+   if (option_bool(OPT_ADD_MAC))
+-    plen  = add_mac(header, plen, limit, source, now);
++    plen  = add_mac(header, plen, limit, source, now, cacheable);
+   
+   if (option_bool(OPT_MAC_B64) || option_bool(OPT_MAC_HEX))
+-    plen = add_dns_client(header, plen, limit, source, now);
+-
++    plen = add_dns_client(header, plen, limit, source, now, cacheable);
++  
+   if (daemon->dns_client_id)
+     plen = add_pseudoheader(header, plen, limit, PACKETSZ, EDNS0_OPTION_NOMCPEID, 
+                           (unsigned char *)daemon->dns_client_id, strlen(daemon->dns_client_id), 0, 1);
+   
+   if (option_bool(OPT_CLIENT_SUBNET))
+     {
+-      plen = add_source_addr(header, plen, limit, source); 
++      plen = add_source_addr(header, plen, limit, source, cacheable); 
+       *check_subnet = 1;
+     }
+         
+--- a/src/forward.c
++++ b/src/forward.c
+@@ -344,13 +344,10 @@ static int forward_query(int udpfd, unio
+     {
+       /* Query from new source, but the same query may be in progress
+        from another source. If so, just add this client to the
+-       list that will get the reply.
++       list that will get the reply.*/
+        
+-       Note that is the EDNS client subnet option is in use, we can't do this,
+-       as the clients (and therefore query EDNS options) will be different
+-       for each query. The EDNS subnet code has checks to avoid
+-       attacks in this case. */
+-      if (!option_bool(OPT_CLIENT_SUBNET) && (forward = lookup_frec_by_query(hash, fwd_flags)))
++      if (!option_bool(OPT_ADD_MAC) && !option_bool(OPT_MAC_B64) &&
++        (forward = lookup_frec_by_query(hash, fwd_flags)))
+       {
+         /* Note whine_malloc() zeros memory. */
+         if (!daemon->free_frec_src &&
+@@ -447,18 +444,21 @@ static int forward_query(int udpfd, unio
+   if (!flags && forward)
+     {
+       struct server *firstsentto = start;
+-      int subnet, forwarded = 0;
++      int subnet, cacheable, forwarded = 0;
+       size_t edns0_len;
+       unsigned char *pheader;
+       
+       /* If a query is retried, use the log_id for the retry when logging the answer. */
+       forward->frec_src.log_id = daemon->log_id;
+       
+-      plen = add_edns0_config(header, plen, ((unsigned char *)header) + PACKETSZ, &forward->frec_src.source, now, &subnet);
++      plen = add_edns0_config(header, plen, ((unsigned char *)header) + PACKETSZ, &forward->frec_src.source, now, &subnet, &cacheable);
+       
+       if (subnet)
+       forward->flags |= FREC_HAS_SUBNET;
+-      
++
++      if (!cacheable)
++      forward->flags |= FREC_NO_CACHE;
++
+ #ifdef HAVE_DNSSEC
+       if (option_bool(OPT_DNSSEC_VALID) && do_dnssec)
+       {
+@@ -642,7 +642,7 @@ static size_t process_reply(struct dns_h
+       }
+     }
+ #endif
+-  
++
+   if ((pheader = find_pseudoheader(header, n, &plen, &sizep, &is_sign, NULL)))
+     {
+       /* Get extended RCODE. */
+@@ -1244,6 +1244,11 @@ void reply_query(int fd, int family, tim
+       header->hb4 |= HB4_CD;
+       else
+       header->hb4 &= ~HB4_CD;
++
++      /* Never cache answers which are contingent on the source or MAC address EDSN0 option,
++       since the cache is ignorant of such things. */
++      if (forward->flags & FREC_NO_CACHE)
++      no_cache_dnssec = 1;
+       
+       if ((nn = process_reply(header, now, forward->sentto, (size_t)n, check_rebind, no_cache_dnssec, cache_secure, bogusanswer, 
+                             forward->flags & FREC_AD_QUESTION, forward->flags & FREC_DO_QUESTION, 
+@@ -1788,7 +1793,7 @@ unsigned char *tcp_request(int confd, ti
+   int local_auth = 0;
+ #endif
+   int checking_disabled, do_bit, added_pheader = 0, have_pseudoheader = 0;
+-  int check_subnet, no_cache_dnssec = 0, cache_secure = 0, bogusanswer = 0;
++  int check_subnet, cacheable, no_cache_dnssec = 0, cache_secure = 0, bogusanswer = 0;
+   size_t m;
+   unsigned short qtype;
+   unsigned int gotname;
+@@ -1959,7 +1964,7 @@ unsigned char *tcp_request(int confd, ti
+             char *domain = NULL;
+             unsigned char *oph = find_pseudoheader(header, size, NULL, NULL, NULL, NULL);
+-            size = add_edns0_config(header, size, ((unsigned char *) header) + 65536, &peer_addr, now, &check_subnet);
++            size = add_edns0_config(header, size, ((unsigned char *) header) + 65536, &peer_addr, now, &check_subnet, &cacheable);
+             if (gotname)
+               flags = search_servers(now, &addrp, gotname, daemon->namebuff, &type, &domain, &norebind);
+@@ -2122,6 +2127,11 @@ unsigned char *tcp_request(int confd, ti
+                         break;
+                       }
++                    /* Never cache answers which are contingent on the source or MAC address EDSN0 option,
++                       since the cache is ignorant of such things. */
++                    if (!cacheable)
++                      no_cache_dnssec = 1;
++                    
+                     m = process_reply(header, now, last_server, (unsigned int)m, 
+                                       option_bool(OPT_NO_REBIND) && !norebind, no_cache_dnssec, cache_secure, bogusanswer,
+                                       ad_reqd, do_bit, added_pheader, check_subnet, &peer_addr); 
+@@ -2385,10 +2395,13 @@ static struct frec *lookup_frec_by_query
+   struct frec *f;
+   /* FREC_DNSKEY and FREC_DS_QUERY are never set in flags, so the test below 
+-     ensures that no frec created for internal DNSSEC query can be returned here. */
++     ensures that no frec created for internal DNSSEC query can be returned here.
++     
++     Similarly FREC_NO_CACHE is never set in flags, so a query which is
++     contigent on a particular source address EDNS0 option will never be matched. */
+ #define FLAGMASK (FREC_CHECKING_DISABLED | FREC_AD_QUESTION | FREC_DO_QUESTION \
+-                | FREC_HAS_PHEADER | FREC_DNSKEY_QUERY | FREC_DS_QUERY)
++                | FREC_HAS_PHEADER | FREC_DNSKEY_QUERY | FREC_DS_QUERY | FREC_NO_CACHE)
+   
+   for(f = daemon->frec_list; f; f = f->next)
+     if (f->sentto &&
diff --git a/package/network/services/dnsmasq/patches/0110-Support-hash-function-from-nettle-only.patch b/package/network/services/dnsmasq/patches/0110-Support-hash-function-from-nettle-only.patch
new file mode 100644 (file)
index 0000000..d671af2
--- /dev/null
@@ -0,0 +1,181 @@
+From 2024f9729713fd657d65e64c2e4e471baa0a3e5b Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Petr=20Men=C5=A1=C3=ADk?= <pemensik@redhat.com>
+Date: Wed, 25 Nov 2020 17:18:55 +0100
+Subject: Support hash function from nettle (only)
+
+Unlike COPTS=-DHAVE_DNSSEC, allow usage of just sha256 function from
+nettle, but keep DNSSEC disabled at build time. Skips use of internal
+hash implementation without support for validation built-in.
+---
+ Makefile             |  8 +++++---
+ bld/pkg-wrapper      | 41 ++++++++++++++++++++++-------------------
+ src/config.h         |  8 ++++++++
+ src/crypto.c         |  7 +++++++
+ src/dnsmasq.h        |  2 +-
+ src/hash_questions.c |  2 +-
+ 6 files changed, 44 insertions(+), 24 deletions(-)
+
+--- a/Makefile
++++ b/Makefile
+@@ -53,7 +53,7 @@ top?=$(CURDIR)
+ dbus_cflags =   `echo $(COPTS) | $(top)/bld/pkg-wrapper HAVE_DBUS $(PKG_CONFIG) --cflags dbus-1` 
+ dbus_libs =     `echo $(COPTS) | $(top)/bld/pkg-wrapper HAVE_DBUS $(PKG_CONFIG) --libs dbus-1` 
+-ubus_libs =     `echo $(COPTS) | $(top)/bld/pkg-wrapper HAVE_UBUS "" --copy -lubox -lubus`
++ubus_libs =     `echo $(COPTS) | $(top)/bld/pkg-wrapper HAVE_UBUS "" --copy '-lubox -lubus'`
+ idn_cflags =    `echo $(COPTS) | $(top)/bld/pkg-wrapper HAVE_IDN $(PKG_CONFIG) --cflags libidn` 
+ idn_libs =      `echo $(COPTS) | $(top)/bld/pkg-wrapper HAVE_IDN $(PKG_CONFIG) --libs libidn` 
+ idn2_cflags =   `echo $(COPTS) | $(top)/bld/pkg-wrapper HAVE_LIBIDN2 $(PKG_CONFIG) --cflags libidn2`
+@@ -62,8 +62,10 @@ ct_cflags =     `echo $(COPTS) | $(top)/
+ ct_libs =       `echo $(COPTS) | $(top)/bld/pkg-wrapper HAVE_CONNTRACK $(PKG_CONFIG) --libs libnetfilter_conntrack`
+ lua_cflags =    `echo $(COPTS) | $(top)/bld/pkg-wrapper HAVE_LUASCRIPT $(PKG_CONFIG) --cflags lua5.2` 
+ lua_libs =      `echo $(COPTS) | $(top)/bld/pkg-wrapper HAVE_LUASCRIPT $(PKG_CONFIG) --libs lua5.2` 
+-nettle_cflags = `echo $(COPTS) | $(top)/bld/pkg-wrapper HAVE_DNSSEC $(PKG_CONFIG) --cflags nettle hogweed`
+-nettle_libs =   `echo $(COPTS) | $(top)/bld/pkg-wrapper HAVE_DNSSEC $(PKG_CONFIG) --libs nettle hogweed`
++nettle_cflags = `echo $(COPTS) | $(top)/bld/pkg-wrapper HAVE_DNSSEC     $(PKG_CONFIG) --cflags 'nettle hogweed' \
++                                                        HAVE_NETTLEHASH $(PKG_CONFIG) --cflags nettle`
++nettle_libs =   `echo $(COPTS) | $(top)/bld/pkg-wrapper HAVE_DNSSEC     $(PKG_CONFIG) --libs 'nettle hogweed' \
++                                                        HAVE_NETTLEHASH $(PKG_CONFIG) --libs nettle`
+ gmp_libs =      `echo $(COPTS) | $(top)/bld/pkg-wrapper HAVE_DNSSEC NO_GMP --copy -lgmp`
+ sunos_libs =    `if uname | grep SunOS >/dev/null 2>&1; then echo -lsocket -lnsl -lposix4; fi`
+ version =     -DVERSION='\"`$(top)/bld/get-version $(top)`\"'
+--- a/bld/pkg-wrapper
++++ b/bld/pkg-wrapper
+@@ -1,35 +1,37 @@
+ #!/bin/sh
+-search=$1
+-shift
+-pkg=$1
+-shift
+-op=$1
+-shift
+-
+ in=`cat`
+-if grep "^\#[[:space:]]*define[[:space:]]*$search" config.h >/dev/null 2>&1 || \
+-    echo $in | grep $search >/dev/null 2>&1; then
++search()
++{
++    grep "^\#[[:space:]]*define[[:space:]]*$1" config.h >/dev/null 2>&1 || \
++    echo $in | grep $1 >/dev/null 2>&1
++}
++
++while [ "$#" -gt 0 ]; do
++    search=$1
++    pkg=$2
++    op=$3
++    lib=$4
++    shift 4
++if search "$search"; then
++
+ # Nasty, nasty, in --copy, arg 2 (if non-empty) is another config to search for, used with NO_GMP
+     if [ $op = "--copy" ]; then
+       if [ -z "$pkg" ]; then
+-          pkg="$*"
+-      elif grep "^\#[[:space:]]*define[[:space:]]*$pkg" config.h >/dev/null 2>&1 || \
+-               echo $in | grep $pkg >/dev/null 2>&1; then
++          pkg="$lib"
++      elif search "$pkg"; then
+           pkg=""
+       else 
+-          pkg="$*"
++          pkg="$lib"
+       fi
+-    elif grep "^\#[[:space:]]*define[[:space:]]*${search}_STATIC" config.h >/dev/null 2>&1 || \
+-           echo $in | grep ${search}_STATIC >/dev/null 2>&1; then
+-      pkg=`$pkg  --static $op $*`
++    elif search "${search}_STATIC"; then
++      pkg=`$pkg  --static $op $lib`
+     else
+-      pkg=`$pkg $op $*`
++      pkg=`$pkg $op $lib`
+     fi
+     
+-    if grep "^\#[[:space:]]*define[[:space:]]*${search}_STATIC" config.h >/dev/null 2>&1 || \
+-         echo $in | grep ${search}_STATIC >/dev/null 2>&1; then
++    if search "${search}_STATIC"; then
+       if [ $op = "--libs" ] || [ $op = "--copy" ]; then
+           echo "-Wl,-Bstatic $pkg -Wl,-Bdynamic"
+       else
+@@ -40,3 +42,4 @@ if grep "^\#[[:space:]]*define[[:space:]
+     fi
+ fi
++done
+--- a/src/config.h
++++ b/src/config.h
+@@ -117,6 +117,9 @@ HAVE_AUTH
+    define this to include the facility to act as an authoritative DNS
+    server for one or more zones.
++HAVE_NETTLEHASH
++   include just hash function from nettle, but no DNSSEC.
++
+ HAVE_DNSSEC
+    include DNSSEC validator.
+@@ -184,6 +187,7 @@ RESOLVFILE
+ /* #define HAVE_IDN */
+ /* #define HAVE_LIBIDN2 */
+ /* #define HAVE_CONNTRACK */
++/* #define HAVE_NETTLEHASH */
+ /* #define HAVE_DNSSEC */
+@@ -408,6 +412,10 @@ static char *compile_opts =
+ "no-"
+ #endif
+ "auth "
++#if !defined(HAVE_NETTLEHASH) && !defined(HAVE_DNSSEC)
++"no-"
++#endif
++"nettlehash "
+ #ifndef HAVE_DNSSEC
+ "no-"
+ #endif
+--- a/src/crypto.c
++++ b/src/crypto.c
+@@ -23,6 +23,9 @@
+ #include <nettle/ecdsa.h>
+ #include <nettle/ecc-curve.h>
+ #include <nettle/eddsa.h>
++#endif
++
++#if defined(HAVE_DNSSEC) || defined(HAVE_NETTLEHASH)
+ #include <nettle/nettle-meta.h>
+ #include <nettle/bignum.h>
+@@ -165,6 +168,10 @@ int hash_init(const struct nettle_hash *
+   return 1;
+ }
++
++#endif
++
++#ifdef HAVE_DNSSEC
+   
+ static int dnsmasq_rsa_verify(struct blockdata *key_data, unsigned int key_len, unsigned char *sig, size_t sig_len,
+                             unsigned char *digest, size_t digest_len, int algo)
+--- a/src/dnsmasq.h
++++ b/src/dnsmasq.h
+@@ -150,7 +150,7 @@ extern int capget(cap_user_header_t head
+ #include <priv.h>
+ #endif
+-#ifdef HAVE_DNSSEC
++#if defined(HAVE_DNSSEC) || defined(HAVE_NETTLEHASH)
+ #  include <nettle/nettle-meta.h>
+ #endif
+--- a/src/hash_questions.c
++++ b/src/hash_questions.c
+@@ -28,7 +28,7 @@
+ #include "dnsmasq.h"
+-#ifdef HAVE_DNSSEC
++#if defined(HAVE_DNSSEC) || defined(HAVE_NETTLEHASH)
+ unsigned char *hash_questions(struct dns_header *header, size_t plen, char *name)
+ {
+   int q;
diff --git a/package/network/services/dnsmasq/patches/0111-Small-cleanups-in-frec_src-datastucture-handling.patch b/package/network/services/dnsmasq/patches/0111-Small-cleanups-in-frec_src-datastucture-handling.patch
new file mode 100644 (file)
index 0000000..45e04bd
--- /dev/null
@@ -0,0 +1,56 @@
+From 6a6e06fbb0d4690507ceaf2bb6f0d8910f3d4914 Mon Sep 17 00:00:00 2001
+From: Simon Kelley <simon@thekelleys.org.uk>
+Date: Fri, 4 Dec 2020 18:35:11 +0000
+Subject: Small cleanups in frec_src datastucture handling.
+
+---
+ src/forward.c | 22 +++++++++++++---------
+ 1 file changed, 13 insertions(+), 9 deletions(-)
+
+--- a/src/forward.c
++++ b/src/forward.c
+@@ -353,7 +353,10 @@ static int forward_query(int udpfd, unio
+         if (!daemon->free_frec_src &&
+             daemon->frec_src_count < daemon->ftabsize &&
+             (daemon->free_frec_src = whine_malloc(sizeof(struct frec_src))))
+-          daemon->frec_src_count++;
++          {
++            daemon->frec_src_count++;
++            daemon->free_frec_src->next = NULL;
++          }
+         
+         /* If we've been spammed with many duplicates, just drop the query. */
+         if (daemon->free_frec_src)
+@@ -390,6 +393,7 @@ static int forward_query(int udpfd, unio
+         forward->frec_src.orig_id = ntohs(header->id);
+         forward->frec_src.dest = *dst_addr;
+         forward->frec_src.iface = dst_iface;
++        forward->frec_src.next = NULL;
+         forward->new_id = get_id();
+         forward->fd = udpfd;
+         memcpy(forward->hash, hash, HASH_SIZE);
+@@ -2226,16 +2230,16 @@ void free_rfd(struct randfd *rfd)
+ static void free_frec(struct frec *f)
+ {
+-  struct frec_src *src, *tmp;
+-
+-   /* add back to freelist of not the record builtin to every frec. */
+-  for (src = f->frec_src.next; src; src = tmp)
++  struct frec_src *last;
++  
++  /* add back to freelist if not the record builtin to every frec. */
++  for (last = f->frec_src.next; last && last->next; last = last->next) ;
++  if (last)
+     {
+-      tmp = src->next;
+-      src->next = daemon->free_frec_src;
+-      daemon->free_frec_src = src;
++      last->next = daemon->free_frec_src;
++      daemon->free_frec_src = f->frec_src.next;
+     }
+-  
++    
+   f->frec_src.next = NULL;    
+   free_rfd(f->rfd4);
+   f->rfd4 = NULL;
diff --git a/package/network/services/dnsmasq/patches/0112-Add-CVE-numbers-to-security-update-descriptions-in-C.patch b/package/network/services/dnsmasq/patches/0112-Add-CVE-numbers-to-security-update-descriptions-in-C.patch
new file mode 100644 (file)
index 0000000..1d7d3a7
--- /dev/null
@@ -0,0 +1,41 @@
+From e01e09c7125b40646aff4a582672e711a18a69a4 Mon Sep 17 00:00:00 2001
+From: Simon Kelley <simon@thekelleys.org.uk>
+Date: Fri, 8 Jan 2021 22:50:03 +0000
+Subject: Add CVE numbers to security update descriptions in CHANGELOG
+
+---
+ CHANGELOG | 9 +++++----
+ 1 file changed, 5 insertions(+), 4 deletions(-)
+
+--- a/CHANGELOG
++++ b/CHANGELOG
+@@ -1,16 +1,17 @@
+       Fix a remote buffer overflow problem in the DNSSEC code. Any
+       dnsmasq with DNSSEC compiled in and enabled is vulnerable to this,
+-      referenced by CERT VU#434904.
++      referenced by CVE-2020-25681, CVE-2020-25682, CVE-2020-25683
++      CVE-2020-25687.
+       Be sure to only accept UDP DNS query replies at the address
+       from which the query was originated. This keeps as much entropy
+       in the {query-ID, random-port} tuple as possible, to help defeat
+-      cache poisoning attacks. Refer: CERT VU#434904.
++      cache poisoning attacks. Refer: CVE-2020-25684.
+       Use the SHA-256 hash function to verify that DNS answers
+       received are for the questions originally asked. This replaces
+       the slightly insecure SHA-1 (when compiled with DNSSEC) or
+-      the very insecure CRC32 (otherwise). Refer: CERT VU#434904.
++      the very insecure CRC32 (otherwise). Refer: CVE-2020-25685.
+       Handle multiple identical near simultaneous DNS queries better.
+       Previously, such queries would all be forwarded
+@@ -24,7 +25,7 @@
+       of the query. The new behaviour detects repeated queries and
+       merely stores the clients sending repeats so that when the
+       first query completes, the answer can be sent to all the
+-      clients who asked. Refer: CERT VU#434904.
++      clients who asked. Refer: CVE-2020-25686.
+       
+ version 2.81
diff --git a/package/network/services/dnsmasq/patches/0113-Fix-warning-message-logic.patch b/package/network/services/dnsmasq/patches/0113-Fix-warning-message-logic.patch
new file mode 100644 (file)
index 0000000..667aea1
--- /dev/null
@@ -0,0 +1,20 @@
+From 503f68dbc437df20a45aab440e6fad92062af229 Mon Sep 17 00:00:00 2001
+From: Simon Kelley <simon@thekelleys.org.uk>
+Date: Fri, 15 Jan 2021 21:53:29 +0000
+Subject: Fix warning message logic.
+
+---
+ src/hash_questions.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/src/hash_questions.c
++++ b/src/hash_questions.c
+@@ -43,7 +43,7 @@ unsigned char *hash_questions(struct dns
+       static unsigned char dummy[HASH_SIZE];
+       static int warned = 0;
+-      if (warned)
++      if (!warned)
+       my_syslog(LOG_ERR, _("Failed to create SHA-256 hash object"));
+       warned = 1;
+      
diff --git a/package/network/services/dnsmasq/patches/0115-Update-to-new-struct-frec-fields-in-conntrack-code.patch b/package/network/services/dnsmasq/patches/0115-Update-to-new-struct-frec-fields-in-conntrack-code.patch
new file mode 100644 (file)
index 0000000..49648dc
--- /dev/null
@@ -0,0 +1,29 @@
+From cc0b4489c782f6b90ca118abb18e716a7a831289 Mon Sep 17 00:00:00 2001
+From: Simon Kelley <simon@thekelleys.org.uk>
+Date: Fri, 15 Jan 2021 22:21:52 +0000
+Subject: Update to new struct frec fields in conntrack code.
+
+---
+ src/forward.c | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+--- a/src/forward.c
++++ b/src/forward.c
+@@ -530,7 +530,7 @@ static int forward_query(int udpfd, unio
+                 if (option_bool(OPT_CONNTRACK))
+                   {
+                     unsigned int mark;
+-                    if (get_incoming_mark(&forward->source, &forward->dest, 0, &mark))
++                    if (get_incoming_mark(&forward->frec_src.source, &forward->frec_src.dest, 0, &mark))
+                       setsockopt(fd, SOL_SOCKET, SO_MARK, &mark, sizeof(unsigned int));
+                   }
+ #endif
+@@ -1178,7 +1178,7 @@ void reply_query(int fd, int family, tim
+                         if (option_bool(OPT_CONNTRACK))
+                           {
+                             unsigned int mark;
+-                            if (get_incoming_mark(&orig->source, &orig->dest, 0, &mark))
++                            if (get_incoming_mark(&orig->frec_src.source, &orig->frec_src.dest, 0, &mark))
+                               setsockopt(fd, SOL_SOCKET, SO_MARK, &mark, sizeof(unsigned int));
+                           }
+ #endif
index c52a6bc..df1aaae 100644 (file)
@@ -15,7 +15,7 @@ Signed-off-by: Hans Dedecker <dedeckeh@gmail.com>
 
 --- a/src/crypto.c
 +++ b/src/crypto.c
-@@ -294,7 +294,7 @@ static int dnsmasq_ecdsa_verify(struct b
+@@ -301,7 +301,7 @@ static int dnsmasq_ecdsa_verify(struct b
          if (!(key_256 = whine_malloc(sizeof(struct ecc_point))))
            return 0;
          
@@ -24,7 +24,7 @@ Signed-off-by: Hans Dedecker <dedeckeh@gmail.com>
        }
        
        key = key_256;
-@@ -307,7 +307,7 @@ static int dnsmasq_ecdsa_verify(struct b
+@@ -314,7 +314,7 @@ static int dnsmasq_ecdsa_verify(struct b
          if (!(key_384 = whine_malloc(sizeof(struct ecc_point))))
            return 0;