aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorKevin Darbyshire-Bryant <ldir@darbyshire-bryant.me.uk>2018-01-19 17:16:08 +0000
committerHans Dedecker <dedeckeh@gmail.com>2018-01-19 22:11:16 +0100
commita3198061f80a7f3933810cd99206b085e4cf49f9 (patch)
tree08af0a10a298d17b794315b66af914e080c73c04
parent9c2ac19b032c81a454f1efbcf5681b80cad2fa39 (diff)
downloadupstream-a3198061f80a7f3933810cd99206b085e4cf49f9.tar.gz
upstream-a3198061f80a7f3933810cd99206b085e4cf49f9.tar.bz2
upstream-a3198061f80a7f3933810cd99206b085e4cf49f9.zip
dnsmasq: backport dnssec security fix
CVE-2017-15107 An interesting problem has turned up in DNSSEC validation. It turns out that NSEC records expanded from wildcards are allowed, so a domain can include an NSEC record for *.example.org and an actual query reply could expand that to anything in example.org and still have it signed by the signature for the wildcard. So, for example !.example.org NSEC zz.example.org is fine. The problem is that most implementers (your author included, but also the Google public DNS people, powerdns and Unbound) then took that record to prove the nothing exists between !.example.org and zz.example.org, whereas in fact it only provides that proof between *.example.org and zz.example.org. This gives an attacker a way to prove that anything between !.example.org and *.example.org doesn't exists, when it may well do so. Signed-off-by: Kevin Darbyshire-Bryant <ldir@darbyshire-bryant.me.uk>
-rw-r--r--package/network/services/dnsmasq/Makefile2
-rw-r--r--package/network/services/dnsmasq/patches/270-dnssec-wildcards.patch202
2 files changed, 203 insertions, 1 deletions
diff --git a/package/network/services/dnsmasq/Makefile b/package/network/services/dnsmasq/Makefile
index 1224ad86f8..3ef7a317d4 100644
--- a/package/network/services/dnsmasq/Makefile
+++ b/package/network/services/dnsmasq/Makefile
@@ -9,7 +9,7 @@ include $(TOPDIR)/rules.mk
PKG_NAME:=dnsmasq
PKG_VERSION:=2.78
-PKG_RELEASE:=8
+PKG_RELEASE:=9
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.xz
PKG_SOURCE_URL:=http://thekelleys.org.uk/dnsmasq/
diff --git a/package/network/services/dnsmasq/patches/270-dnssec-wildcards.patch b/package/network/services/dnsmasq/patches/270-dnssec-wildcards.patch
new file mode 100644
index 0000000000..029e7ea7af
--- /dev/null
+++ b/package/network/services/dnsmasq/patches/270-dnssec-wildcards.patch
@@ -0,0 +1,202 @@
+From 4fe6744a220eddd3f1749b40cac3dfc510787de6 Mon Sep 17 00:00:00 2001
+From: Simon Kelley <simon@thekelleys.org.uk>
+Date: Fri, 19 Jan 2018 12:26:08 +0000
+Subject: [PATCH] DNSSEC fix for wildcard NSEC records. CVE-2017-15107
+ applies.
+
+It's OK for NSEC records to be expanded from wildcards,
+but in that case, the proof of non-existence is only valid
+starting at the wildcard name, *.<domain> NOT the name expanded
+from the wildcard. Without this check it's possible for an
+attacker to craft an NSEC which wrongly proves non-existence
+in a domain which includes a wildcard for NSEC.
+---
+ src/dnssec.c | 117 +++++++++++++++++++++++++++++++++++++++++++++++++++-------
+ 2 files changed, 114 insertions(+), 15 deletions(-)
+
+--- a/src/dnssec.c
++++ b/src/dnssec.c
+@@ -424,15 +424,17 @@ static void from_wire(char *name)
+ static int count_labels(char *name)
+ {
+ int i;
+-
++ char *p;
++
+ if (*name == 0)
+ return 0;
+
+- for (i = 0; *name; name++)
+- if (*name == '.')
++ for (p = name, i = 0; *p; p++)
++ if (*p == '.')
+ i++;
+
+- return i+1;
++ /* Don't count empty first label. */
++ return *name == '.' ? i : i+1;
+ }
+
+ /* Implement RFC1982 wrapped compare for 32-bit numbers */
+@@ -1412,8 +1414,8 @@ static int hostname_cmp(const char *a, c
+ }
+ }
+
+-static int prove_non_existence_nsec(struct dns_header *header, size_t plen, unsigned char **nsecs, int nsec_count,
+- char *workspace1, char *workspace2, char *name, int type, int *nons)
++static int prove_non_existence_nsec(struct dns_header *header, size_t plen, unsigned char **nsecs, unsigned char **labels, int nsec_count,
++ char *workspace1_in, char *workspace2, char *name, int type, int *nons)
+ {
+ int i, rc, rdlen;
+ unsigned char *p, *psave;
+@@ -1426,6 +1428,9 @@ static int prove_non_existence_nsec(stru
+ /* Find NSEC record that proves name doesn't exist */
+ for (i = 0; i < nsec_count; i++)
+ {
++ char *workspace1 = workspace1_in;
++ int sig_labels, name_labels;
++
+ p = nsecs[i];
+ if (!extract_name(header, plen, &p, workspace1, 1, 10))
+ return 0;
+@@ -1434,7 +1439,27 @@ static int prove_non_existence_nsec(stru
+ psave = p;
+ if (!extract_name(header, plen, &p, workspace2, 1, 10))
+ return 0;
+-
++
++ /* If NSEC comes from wildcard expansion, use original wildcard
++ as name for computation. */
++ sig_labels = *labels[i];
++ name_labels = count_labels(workspace1);
++
++ if (sig_labels < name_labels)
++ {
++ int k;
++ for (k = name_labels - sig_labels; k != 0; k--)
++ {
++ while (*workspace1 != '.' && *workspace1 != 0)
++ workspace1++;
++ if (k != 1 && *workspace1 == '.')
++ workspace1++;
++ }
++
++ workspace1--;
++ *workspace1 = '*';
++ }
++
+ rc = hostname_cmp(workspace1, name);
+
+ if (rc == 0)
+@@ -1832,24 +1857,26 @@ static int prove_non_existence_nsec3(str
+
+ static int prove_non_existence(struct dns_header *header, size_t plen, char *keyname, char *name, int qtype, int qclass, char *wildname, int *nons)
+ {
+- static unsigned char **nsecset = NULL;
+- static int nsecset_sz = 0;
++ static unsigned char **nsecset = NULL, **rrsig_labels = NULL;
++ static int nsecset_sz = 0, rrsig_labels_sz = 0;
+
+ int type_found = 0;
+- unsigned char *p = skip_questions(header, plen);
++ unsigned char *auth_start, *p = skip_questions(header, plen);
+ int type, class, rdlen, i, nsecs_found;
+
+ /* Move to NS section */
+ if (!p || !(p = skip_section(p, ntohs(header->ancount), header, plen)))
+ return 0;
++
++ auth_start = p;
+
+ for (nsecs_found = 0, i = ntohs(header->nscount); i != 0; i--)
+ {
+ unsigned char *pstart = p;
+
+- if (!(p = skip_name(p, header, plen, 10)))
++ if (!extract_name(header, plen, &p, daemon->workspacename, 1, 10))
+ return 0;
+-
++
+ GETSHORT(type, p);
+ GETSHORT(class, p);
+ p += 4; /* TTL */
+@@ -1866,7 +1893,69 @@ static int prove_non_existence(struct dn
+ if (!expand_workspace(&nsecset, &nsecset_sz, nsecs_found))
+ return 0;
+
+- nsecset[nsecs_found++] = pstart;
++ if (type == T_NSEC)
++ {
++ /* If we're looking for NSECs, find the corresponding SIGs, to
++ extract the labels value, which we need in case the NSECs
++ are the result of wildcard expansion.
++ Note that the NSEC may not have been validated yet
++ so if there are multiple SIGs, make sure the label value
++ is the same in all, to avoid be duped by a rogue one.
++ If there are no SIGs, that's an error */
++ unsigned char *p1 = auth_start;
++ int res, j, rdlen1, type1, class1;
++
++ if (!expand_workspace(&rrsig_labels, &rrsig_labels_sz, nsecs_found))
++ return 0;
++
++ rrsig_labels[nsecs_found] = NULL;
++
++ for (j = ntohs(header->nscount); j != 0; j--)
++ {
++ if (!(res = extract_name(header, plen, &p1, daemon->workspacename, 0, 10)))
++ return 0;
++
++ GETSHORT(type1, p1);
++ GETSHORT(class1, p1);
++ p1 += 4; /* TTL */
++ GETSHORT(rdlen1, p1);
++
++ if (!CHECK_LEN(header, p1, plen, rdlen1))
++ return 0;
++
++ if (res == 1 && class1 == qclass && type1 == T_RRSIG)
++ {
++ int type_covered;
++ unsigned char *psav = p1;
++
++ if (rdlen < 18)
++ return 0; /* bad packet */
++
++ GETSHORT(type_covered, p1);
++
++ if (type_covered == T_NSEC)
++ {
++ p1++; /* algo */
++
++ /* labels field must be the same in every SIG we find. */
++ if (!rrsig_labels[nsecs_found])
++ rrsig_labels[nsecs_found] = p1;
++ else if (*rrsig_labels[nsecs_found] != *p1) /* algo */
++ return 0;
++ }
++ p1 = psav;
++ }
++
++ if (!ADD_RDLEN(header, p1, plen, rdlen1))
++ return 0;
++ }
++
++ /* Must have found at least one sig. */
++ if (!rrsig_labels[nsecs_found])
++ return 0;
++ }
++
++ nsecset[nsecs_found++] = pstart;
+ }
+
+ if (!ADD_RDLEN(header, p, plen, rdlen))
+@@ -1874,7 +1963,7 @@ static int prove_non_existence(struct dn
+ }
+
+ if (type_found == T_NSEC)
+- return prove_non_existence_nsec(header, plen, nsecset, nsecs_found, daemon->workspacename, keyname, name, qtype, nons);
++ return prove_non_existence_nsec(header, plen, nsecset, rrsig_labels, nsecs_found, daemon->workspacename, keyname, name, qtype, nons);
+ else if (type_found == T_NSEC3)
+ return prove_non_existence_nsec3(header, plen, nsecset, nsecs_found, daemon->workspacename, keyname, name, qtype, wildname, nons);
+ else