Compare commits

...
29 Commits
Author SHA1 Message Date
Simon Kelley 3705ec5592 Relax limits imposed by d/t/functions.d/ip-addr.patterns in Debian autotest. 2024-02-15 09:55:57 +00:00
Simon Kelley b6769234bc Bump Debian version. 2024-02-13 13:49:15 +00:00
Simon Kelley 214a046f47 Merge branch 'dnssec-limit'
This merges security fixes for CVE-2023-50387 and CVE-2023-50868

Keytrap - extreme CPU consumption in the DNSSEC validator.
2024-02-13 13:27:25 +00:00
Simon Kelley b38da6b191 Reverse suppression of ANY query answer logging. 2024-02-13 13:26:24 +00:00
Simon Kelley 9621c16a78 Add CHANGELOG entry for DNSSEC security fixes. 2024-02-12 23:11:35 +00:00
Simon Kelley 3ae7f1ab0d Add --dnssec-limits option. 2024-02-12 23:11:35 +00:00
Simon Kelley 39de57499e Better allocation code for DS digest cache. 2024-02-12 23:11:35 +00:00
Simon Kelley 3c91bca943 Better stats and logging from DNSSEC resource limiting. 2024-02-12 23:11:35 +00:00
Simon Kelley 76bceb06c4 Overhaul data checking in NSEC code. 2024-02-12 23:11:35 +00:00
Simon Kelley 6f23a0a75e Rework validate-by-DS to avoid DoS vuln without arbitrary limits.
By calculating the hash of a DNSKEY once for each digest algo,
we reduce the hashing work from (no. DS) x (no. DNSKEY) to
(no. DNSKEY) x (no. distinct digests)

The number of distinct digests can never be more than 255 and
it's limited by which hashes we implement, so currently only 4.
2024-02-12 23:11:35 +00:00
Simon Kelley 06945c4b77 Update EDE code -> text conversion. 2024-02-12 23:11:35 +00:00
Simon Kelley c5aa221e44 Parameterise work limits for DNSSEC validation. 2024-02-12 23:11:35 +00:00
Simon Kelley bfefd6e38c Fix error introduced in 635bc51cac3d5d7dd49ce9e27149cf7e402b7e79 2024-02-12 23:11:35 +00:00
Simon Kelley 59d30390c9 Measure cryptographic work done by DNSSEC. 2024-02-12 23:11:34 +00:00
Simon Kelley 51471cafa5 Update NSEC3 iterations handling to conform with RFC 9276. 2024-02-12 23:11:34 +00:00
Simon Kelley be73efc020 Update header with new EDE values. 2024-02-12 23:11:34 +00:00
Simon Kelley 40595f80d9 Protection against pathalogical DNSSEC domains.
An attacker can create DNSSEC signed domains which need a lot of
work to verfify. We limit the number of crypto operations to
avoid DoS attacks by CPU exhaustion.
2024-02-12 23:11:34 +00:00
Simon Kelley 8c8e5385fd Close debian bug. 2024-02-12 23:11:03 +00:00
Simon Kelley 3de7289bd6 Make --filter-rr=ANY filter the answer to ANY queries.
Thanks to Dominik Derigs for an earlier patch which inspired this.
2024-02-12 20:45:20 +00:00
Simon Kelley febeea9d01 Tweak logging and special handling of T_ANY in rr-filter code. 2024-02-12 13:42:07 +00:00
Heikki Linnakangas 762a3f2430 Don't create a useless inotify file desrcriptor when --port=0
If there are no dynamic configuration directories configured with
dhcp-hostsdir, dhcp-optsdir and hostsdir then we need to use inotify
only to track changes to resolv-files, but we don't need to do
that when DNS is disabled (port=0) or no resolv-files are configured.

It turns out that inotify slots can be a scarce resource, so not
using one when it's not needed is a Goood Thing.

Patch by HL, description above from SRK.
2024-02-07 14:44:49 +00:00
Simon Kelley 6d35601da4 Refactor the accumulated crud of years in process_reply(). 2024-02-05 22:33:09 +00:00
Simon Kelley a827127c77 Handle caching SOA for negative PTR queries.
Also deal with the fact that a root SOA is a thing.
2024-02-03 20:46:23 +00:00
Simon Kelley d4a6f3a93e Fix logic error in signed RR handling.
In extract_addresses() the "secure" argument is only set if the
whole reply is validated (ie the AD bit can be set). Even without
that, some records may be validated, and should be marked
as such in the cache.

Related, the DNS doctor code has to update the flags for individual
RRs as it works, not the global "secure" flag.
2024-02-02 21:36:56 +00:00
Simon Kelley 86c15032ba Fix compiler warning. 2024-02-02 00:26:44 +00:00
Simon Kelley 12ddb2a4b9 Cache SOAs and return them with cached NXDOMAIN/NODATA replies.
Now we can cache arbirary RRs, give more correct answers when
replying negative answers from cache.

To implement this needed the DNS-doctor code to be untangled from
find_soa(), so it should be under suspicion for any regresssions
in that department.
2024-02-01 23:37:11 +00:00
Simon Kelley db07664f2a Hardcode Lua library version in debian/rules, rather than the Makefile. 2024-01-26 23:03:34 +00:00
Simon Kelley 1205fc3541 Let pkg-config select the newest installed Lua version, don't hardcode it.
The version can be overridden with the LUA envvar

Make LUA=lua5.4

Thanks to Petr Menšík for the patch which inspired this one.
2024-01-26 22:44:06 +00:00
Simon Kelley 3a8ebcac77 Debian changelog tweaking: LUA -> Lua and use upstream test version
for experimental release.
2024-01-26 22:23:12 +00:00
20 changed files with 858 additions and 422 deletions

No files matched your search

+38
View File
@@ -26,6 +26,44 @@ version 2.90
when the usptream server is a DOH/DOT proxy. Thanks to Justin He when the usptream server is a DOH/DOT proxy. Thanks to Justin He
for the bug report. for the bug report.
Add configurable caching for arbitrary RR-types.
Add --filter-rr option, to filter arbitrary RR-types.
--filter-rr=ANY has a special meaning: it filters the
answers to queries for the ANY RR-type.
Add limits on the resources used to do DNSSEC validation.
DNSSEC introduces a potential CPU DoS, because a crafted domain
can force a validator to a large number of cryptographic
operations whilst attempting to do validation. When using TCP
transport a DNSKEY RRset contain thousands of members and any
RRset can have thousands of signatures. The potential number
of signature validations to follow the RFC for validation
for one RRset is the cross product of the keys and signatures,
so millions. In practice, the actual numbers are much lower,
so attacks can be mitigated by limiting the amount of
cryptographic "work" to a much lower amount. The actual
limits are number a signature validation fails per RRset(20),
number of signature validations and hash computations
per query(200), number of sub-queries to fetch DS and DNSKEY
RRsets per query(40), and the number of iterations in a
NSEC3 record(150). These values are sensible, but there is, as yet,
no standardisation on the values for a "conforming" domain, so a
new option --dnssec-limit is provided should they need to be altered.
The algorithm to validate DS records has also been altered to reduce
the maximum work from cross product of the number of DS records and
number of DNSKEYs to the cross product of the number of DS records
and supported DS digest types. As the number of DS digest types
is in single figures, this reduces the exposure.
Credit is due to Elias Heftrig, Haya Schulmann, Niklas Vogel,
and Michael Waidner from the German National Research Center for
Applied Cybersecurity ATHENE for finding this vulnerability.
CVE 2023-50387 and CVE 2023-50868 apply.
Note that the is a security vulnerablity only when DNSSEC validation
is enabled.
version 2.89 version 2.89
Fix bug introduced in 2.88 (commit fe91134b) which can result Fix bug introduced in 2.88 (commit fe91134b) which can result
+3 -2
View File
@@ -29,6 +29,7 @@ LDFLAGS =
COPTS = COPTS =
RPM_OPT_FLAGS = RPM_OPT_FLAGS =
LIBS = LIBS =
LUA = lua
################################################################# #################################################################
@@ -60,8 +61,8 @@ idn2_cflags = `echo $(COPTS) | $(top)/bld/pkg-wrapper HAVE_LIBIDN2 $(PKG_CONFI
idn2_libs = `echo $(COPTS) | $(top)/bld/pkg-wrapper HAVE_LIBIDN2 $(PKG_CONFIG) --libs libidn2` idn2_libs = `echo $(COPTS) | $(top)/bld/pkg-wrapper HAVE_LIBIDN2 $(PKG_CONFIG) --libs libidn2`
ct_cflags = `echo $(COPTS) | $(top)/bld/pkg-wrapper HAVE_CONNTRACK $(PKG_CONFIG) --cflags libnetfilter_conntrack` ct_cflags = `echo $(COPTS) | $(top)/bld/pkg-wrapper HAVE_CONNTRACK $(PKG_CONFIG) --cflags libnetfilter_conntrack`
ct_libs = `echo $(COPTS) | $(top)/bld/pkg-wrapper HAVE_CONNTRACK $(PKG_CONFIG) --libs libnetfilter_conntrack` 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.4` lua_cflags = `echo $(COPTS) | $(top)/bld/pkg-wrapper HAVE_LUASCRIPT $(PKG_CONFIG) --cflags $(LUA)`
lua_libs = `echo $(COPTS) | $(top)/bld/pkg-wrapper HAVE_LUASCRIPT $(PKG_CONFIG) --libs lua5.4` lua_libs = `echo $(COPTS) | $(top)/bld/pkg-wrapper HAVE_LUASCRIPT $(PKG_CONFIG) --libs $(LUA)`
nettle_cflags = `echo $(COPTS) | $(top)/bld/pkg-wrapper HAVE_DNSSEC $(PKG_CONFIG) --cflags 'nettle hogweed' \ nettle_cflags = `echo $(COPTS) | $(top)/bld/pkg-wrapper HAVE_DNSSEC $(PKG_CONFIG) --cflags 'nettle hogweed' \
HAVE_CRYPTOHASH $(PKG_CONFIG) --cflags nettle \ HAVE_CRYPTOHASH $(PKG_CONFIG) --cflags nettle \
HAVE_NETTLEHASH $(PKG_CONFIG) --cflags nettle` HAVE_NETTLEHASH $(PKG_CONFIG) --cflags nettle`
+15 -3
View File
@@ -1,7 +1,19 @@
dnsmasq (2.90-1) experimental; urgency=medium dnsmasq (2.90-2) unstable; urgency=medium
[ Sven Geuer ]
* Relax limits imposed by d/t/functions.d/ip-addr.patterns to allow for
successful tests on ci.debian.net.
-- Simon Kelley <simon@thekelleys.org.uk> Wed, 14 Feb 2024 11:33:14 +0000
dnsmasq (2.90-1) unstable; urgency=medium
[ Simon Kelley ] [ Simon Kelley ]
* New upstream. (closes: #1033165) * New upstream. (closes: #1033165)
* Move hard-coding of Lua version from the upstream Makefile
to d/rules.
* Security fixes for Keytrap - DNSSEC validation CPU exhaustion.
CVE-2023-50387 and CVE-2023-50868
[ Sven Geuer ] [ Sven Geuer ]
* Introduce autokpgtests per d/tests/* (closes: #1034135). * Introduce autokpgtests per d/tests/* (closes: #1034135).
* Switch to dpkg-source 3.0 (quilt) format (closes: #1007041). * Switch to dpkg-source 3.0 (quilt) format (closes: #1007041).
@@ -11,7 +23,7 @@ dnsmasq (2.90-1) experimental; urgency=medium
* Rename d/systemd.service to d/dnsmasq.service. * Rename d/systemd.service to d/dnsmasq.service.
* Rename d/systemd@.service to d/dnsmasq@.service. * Rename d/systemd@.service to d/dnsmasq@.service.
* Refactor d/rules to use the DH sequencer and fix major lintian issues * Refactor d/rules to use the DH sequencer and fix major lintian issues
(closes: #844989, #1040923). (closes: #844989, #1040923, #1063551).
Modified files: Modified files:
- d/rules - d/rules
Complete rewrite making use of debhelper and its tools, fixes lintian Complete rewrite making use of debhelper and its tools, fixes lintian
@@ -78,7 +90,7 @@ dnsmasq (2.90-1) experimental; urgency=medium
* Introduce d/u/metadata. * Introduce d/u/metadata.
* Fix lintian issue duplicate-short-description. * Fix lintian issue duplicate-short-description.
* Fix lintian issue capitalization-error-in-description. * Fix lintian issue capitalization-error-in-description.
* Bump LUA version to 5.4 (closes: #1050750). * Bump Lua version to 5.4 (closes: #1050750).
Modified files: Modified files:
- d/control - d/control
- d/t/functions.d/log.patterns - d/t/functions.d/log.patterns
+1 -1
View File
@@ -87,7 +87,7 @@ override_dh_auto_install:
BUILDDIR=debian/auto-build/dnsmasq-base-lua \ BUILDDIR=debian/auto-build/dnsmasq-base-lua \
DESTDIR=$(CURDIR)/debian/dnsmasq-base-lua \ DESTDIR=$(CURDIR)/debian/dnsmasq-base-lua \
PREFIX=$(PREFIX) CFLAGS="$(CFLAGS)" LDFLAGS="$(LDFLAGS)" \ PREFIX=$(PREFIX) CFLAGS="$(CFLAGS)" LDFLAGS="$(LDFLAGS)" \
COPTS="$(COPTS) -DHAVE_LUASCRIPT" LUA=lua5.4 COPTS="$(COPTS) -DHAVE_LUASCRIPT"
dh_auto_build -p dnsmasq-utils -D contrib/lease-tools dh_auto_build -p dnsmasq-utils -D contrib/lease-tools
override_dh_auto_clean: override_dh_auto_clean:
+1 -1
View File
@@ -1,4 +1,4 @@
?: veth1@if?: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000 *: veth1@if*: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
link/ether ??:??:??:??:??:?? brd ff:ff:ff:ff:ff:ff link-netnsid 0 link/ether ??:??:??:??:??:?? brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 192.168.142.?/29 brd 192.168.142.7 scope global dynamic veth1 inet 192.168.142.?/29 brd 192.168.142.7 scope global dynamic veth1
valid_lft 3[56][0-9][0-9]sec preferred_lft 3[56][0-9][0-9]sec valid_lft 3[56][0-9][0-9]sec preferred_lft 3[56][0-9][0-9]sec
+14 -1
View File
@@ -386,7 +386,11 @@ Remove A records from answers. No IPv4 addresses will be returned.
Remove AAAA records from answers. No IPv6 addresses will be returned. Remove AAAA records from answers. No IPv6 addresses will be returned.
.TP .TP
.B --filter-rr=<rrtype>[,<rrtype>...] .B --filter-rr=<rrtype>[,<rrtype>...]
Remove records of the specified type(s) from answers. Remove records of the specified type(s) from answers. The otherwise-nonsensical --filter-rr=ANY has
a special meaning: it filters replies to queries for type ANY. Everything other than A, AAAA, MX and CNAME
records are removed. Since ANY queries with forged source addresses can be used in DNS amplification attacks
(replies to ANY queries can be large) this defangs such attacks, whilst still supporting the
one remaining possible use of ANY queries. See RFC 8482 para 4.3 for details.
.TP .TP
.B --cache-rr=<rrtype>[,<rrtype>...] .B --cache-rr=<rrtype>[,<rrtype>...]
By default, dnsmasq caches A, AAAA, CNAME and SRV DNS record types. By default, dnsmasq caches A, AAAA, CNAME and SRV DNS record types.
@@ -927,6 +931,15 @@ Authenticated Data bit correctly in all cases is not technically possible. If th
when using this option, then the cache should be disabled using --cache-size=0. In most cases, enabling DNSSEC validation when using this option, then the cache should be disabled using --cache-size=0. In most cases, enabling DNSSEC validation
within dnsmasq is a better option. See --dnssec for details. within dnsmasq is a better option. See --dnssec for details.
.TP .TP
.B --dnssec-limits=<limit>[,<limit>.......]
Override the default resource limits applied to DNSSEC validation. Cryptographic operations are expensive and crafted domains
can DoS a DNSSEC validator by forcing it to do hundreds of thousands of such operations. To avoid this, the dnsmasq validation code
applies limits on how much work will be expended in validation. If any of the limits are exceeded, the validation will fail and the
domain treated as BOGUS. There are four limits, in order(default values in parens): number a signature validation fails per RRset(20), number of signature validations and
hash computations per query(200), number of sub-queries to fetch DS and DNSKEY RRsets per query(40), and the number of iterations in a NSEC3 record(150).
The maximum values reached during validation are stored, and dumped as part of the stats generated by SIGUSR1. Supplying a limit value of 0 leaves the default in place, so
\fB--dnssec-limits=0,0,20\fP sets the number of sub-queries to 20 whilst leaving the other limits at default values.
.TP
.B --dnssec-debug .B --dnssec-debug
Set debugging mode for the DNSSEC validation, set the Checking Disabled bit on upstream queries, Set debugging mode for the DNSSEC validation, set the Checking Disabled bit on upstream queries,
and don't convert replies which do not validate to responses with and don't convert replies which do not validate to responses with
+50 -14
View File
@@ -802,9 +802,6 @@ void cache_end_insert(void)
read_write(daemon->pipe_to_parent, (unsigned char *)name, m, 0); read_write(daemon->pipe_to_parent, (unsigned char *)name, m, 0);
read_write(daemon->pipe_to_parent, (unsigned char *)&new_chain->ttd, sizeof(new_chain->ttd), 0); read_write(daemon->pipe_to_parent, (unsigned char *)&new_chain->ttd, sizeof(new_chain->ttd), 0);
read_write(daemon->pipe_to_parent, (unsigned char *)&flags, sizeof(flags), 0); read_write(daemon->pipe_to_parent, (unsigned char *)&flags, sizeof(flags), 0);
if (flags & (F_IPV4 | F_IPV6 | F_DNSKEY | F_DS | F_RR))
{
read_write(daemon->pipe_to_parent, (unsigned char *)&new_chain->addr, sizeof(new_chain->addr), 0); read_write(daemon->pipe_to_parent, (unsigned char *)&new_chain->addr, sizeof(new_chain->addr), 0);
if (flags & F_RR) if (flags & F_RR)
@@ -829,7 +826,6 @@ void cache_end_insert(void)
#endif #endif
} }
} }
}
new_chain = tmp; new_chain = tmp;
} }
@@ -838,7 +834,18 @@ void cache_end_insert(void)
if (daemon->pipe_to_parent != -1) if (daemon->pipe_to_parent != -1)
{ {
ssize_t m = -1; ssize_t m = -1;
read_write(daemon->pipe_to_parent, (unsigned char *)&m, sizeof(m), 0); read_write(daemon->pipe_to_parent, (unsigned char *)&m, sizeof(m), 0);
#ifdef HAVE_DNSSEC
/* Sneak out possibly updated crypto HWM values. */
m = daemon->metrics[METRIC_CRYPTO_HWM];
read_write(daemon->pipe_to_parent, (unsigned char *)&m, sizeof(m), 0);
m = daemon->metrics[METRIC_SIG_FAIL_HWM];
read_write(daemon->pipe_to_parent, (unsigned char *)&m, sizeof(m), 0);
m = daemon->metrics[METRIC_WORK_HWM];
read_write(daemon->pipe_to_parent, (unsigned char *)&m, sizeof(m), 0);
#endif
} }
new_chain = NULL; new_chain = NULL;
@@ -857,7 +864,7 @@ int cache_recv_insert(time_t now, int fd)
cache_start_insert(); cache_start_insert();
while(1) while (1)
{ {
if (!read_write(fd, (unsigned char *)&m, sizeof(m), 1)) if (!read_write(fd, (unsigned char *)&m, sizeof(m), 1))
@@ -865,13 +872,29 @@ int cache_recv_insert(time_t now, int fd)
if (m == -1) if (m == -1)
{ {
#ifdef HAVE_DNSSEC
/* Sneak in possibly updated crypto HWM. */
if (!read_write(fd, (unsigned char *)&m, sizeof(m), 1))
return 0;
if (m > daemon->metrics[METRIC_CRYPTO_HWM])
daemon->metrics[METRIC_CRYPTO_HWM] = m;
if (!read_write(fd, (unsigned char *)&m, sizeof(m), 1))
return 0;
if (m > daemon->metrics[METRIC_SIG_FAIL_HWM])
daemon->metrics[METRIC_SIG_FAIL_HWM] = m;
if (!read_write(fd, (unsigned char *)&m, sizeof(m), 1))
return 0;
if (m > daemon->metrics[METRIC_WORK_HWM])
daemon->metrics[METRIC_WORK_HWM] = m;
#endif
cache_end_insert(); cache_end_insert();
return 1; return 1;
} }
if (!read_write(fd, (unsigned char *)daemon->namebuff, m, 1) || if (!read_write(fd, (unsigned char *)daemon->namebuff, m, 1) ||
!read_write(fd, (unsigned char *)&ttd, sizeof(ttd), 1) || !read_write(fd, (unsigned char *)&ttd, sizeof(ttd), 1) ||
!read_write(fd, (unsigned char *)&flags, sizeof(flags), 1)) !read_write(fd, (unsigned char *)&flags, sizeof(flags), 1) ||
!read_write(fd, (unsigned char *)&addr, sizeof(addr), 1))
return 0; return 0;
daemon->namebuff[m] = 0; daemon->namebuff[m] = 0;
@@ -902,11 +925,6 @@ int cache_recv_insert(time_t now, int fd)
{ {
unsigned short class = C_IN; unsigned short class = C_IN;
if (flags & (F_IPV4 | F_IPV6 | F_DNSKEY | F_DS | F_RR))
{
if (!read_write(fd, (unsigned char *)&addr, sizeof(addr), 1))
return 0;
if ((flags & F_RR) && !(flags & F_NEG) && (flags & F_KEYTAG) if ((flags & F_RR) && !(flags & F_NEG) && (flags & F_KEYTAG)
&& !(addr.rrblock.rrdata = blockdata_read(fd, addr.rrblock.datalen))) && !(addr.rrblock.rrdata = blockdata_read(fd, addr.rrblock.datalen)))
return 0; return 0;
@@ -924,8 +942,6 @@ int cache_recv_insert(time_t now, int fd)
return 0; return 0;
} }
#endif #endif
}
crecp = really_insert(daemon->namebuff, &addr, class, now, ttl, flags); crecp = really_insert(daemon->namebuff, &addr, class, now, ttl, flags);
} }
} }
@@ -1809,8 +1825,18 @@ static void dump_cache_entry(struct crec *cache, time_t now)
p = buff; p = buff;
*a = 0; *a = 0;
if (strlen(n) == 0 && !(cache->flags & F_REVERSE))
if (cache->flags & F_REVERSE)
{
if ((cache->flags & F_NEG))
n = "";
}
else
{
if (strlen(n) == 0)
n = "<Root>"; n = "<Root>";
}
p += sprintf(p, "%-30.30s ", sanitise(n)); p += sprintf(p, "%-30.30s ", sanitise(n));
if ((cache->flags & F_CNAME) && !is_outdated_cname_pointer(cache)) if ((cache->flags & F_CNAME) && !is_outdated_cname_pointer(cache))
a = sanitise(cache_get_cname_target(cache)); a = sanitise(cache_get_cname_target(cache));
@@ -1893,6 +1919,11 @@ void dump_cache(time_t now)
#ifdef HAVE_AUTH #ifdef HAVE_AUTH
my_syslog(LOG_INFO, _("queries for authoritative zones %u"), daemon->metrics[METRIC_DNS_AUTH_ANSWERED]); my_syslog(LOG_INFO, _("queries for authoritative zones %u"), daemon->metrics[METRIC_DNS_AUTH_ANSWERED]);
#endif #endif
#ifdef HAVE_DNSSEC
my_syslog(LOG_INFO, _("DNSSEC per-query subqueries HWM %u"), daemon->metrics[METRIC_WORK_HWM]);
my_syslog(LOG_INFO, _("DNSSEC per-query crypto work HWM %u"), daemon->metrics[METRIC_CRYPTO_HWM]);
my_syslog(LOG_INFO, _("DNSSEC per-RRSet signature fails HWM %u"), daemon->metrics[METRIC_SIG_FAIL_HWM]);
#endif
blockdata_report(); blockdata_report();
my_syslog(LOG_INFO, _("child processes for TCP requests: in use %zu, highest since last SIGUSR1 %zu, max allowed %zu."), my_syslog(LOG_INFO, _("child processes for TCP requests: in use %zu, highest since last SIGUSR1 %zu, max allowed %zu."),
@@ -2052,6 +2083,11 @@ static char *edestr(int ede)
case EDE_NO_AUTH: return "no reachable authority"; case EDE_NO_AUTH: return "no reachable authority";
case EDE_NETERR: return "network error"; case EDE_NETERR: return "network error";
case EDE_INVALID_DATA: return "invalid data"; case EDE_INVALID_DATA: return "invalid data";
case EDE_SIG_E_B_V: return "signature expired before valid";
case EDE_TOO_EARLY: return "too early";
case EDE_UNS_NS3_ITER: return "unsupported NSEC3 iterations value";
case EDE_UNABLE_POLICY: return "uanble to conform to policy";
case EDE_SYNTHESIZED: return "synthesized";
default: return "unknown"; default: return "unknown";
} }
} }
+4 -1
View File
@@ -22,7 +22,10 @@
#define EDNS_PKTSZ 1232 /* default max EDNS.0 UDP packet from from /dnsflagday.net/2020 */ #define EDNS_PKTSZ 1232 /* default max EDNS.0 UDP packet from from /dnsflagday.net/2020 */
#define SAFE_PKTSZ 1232 /* "go anywhere" UDP packet size, see https://dnsflagday.net/2020/ */ #define SAFE_PKTSZ 1232 /* "go anywhere" UDP packet size, see https://dnsflagday.net/2020/ */
#define KEYBLOCK_LEN 40 /* choose to minimise fragmentation when storing DNSSEC keys */ #define KEYBLOCK_LEN 40 /* choose to minimise fragmentation when storing DNSSEC keys */
#define DNSSEC_WORK 50 /* Max number of queries to validate one question */ #define DNSSEC_LIMIT_WORK 40 /* Max number of queries to validate one question */
#define DNSSEC_LIMIT_SIG_FAIL 20 /* Number of signature that can fail to validate in one answer */
#define DNSSEC_LIMIT_CRYPTO 200 /* max no. of crypto operations to validate one query. */
#define DNSSEC_LIMIT_NSEC3_ITERS 150 /* Max. number if iterations allowed in NSEC3 record. */
#define TIMEOUT 10 /* drop UDP queries after TIMEOUT seconds */ #define TIMEOUT 10 /* drop UDP queries after TIMEOUT seconds */
#define SMALL_PORT_RANGE 30 /* If DNS port range is smaller than this, use different allocation. */ #define SMALL_PORT_RANGE 30 /* If DNS port range is smaller than this, use different allocation. */
#define FORWARD_TEST 50 /* try all servers every 50 queries */ #define FORWARD_TEST 50 /* try all servers every 50 queries */
+5 -2
View File
@@ -112,8 +112,11 @@
#define EDE_NO_AUTH 22 /* No Reachable Authority */ #define EDE_NO_AUTH 22 /* No Reachable Authority */
#define EDE_NETERR 23 /* Network error */ #define EDE_NETERR 23 /* Network error */
#define EDE_INVALID_DATA 24 /* Invalid Data */ #define EDE_INVALID_DATA 24 /* Invalid Data */
#define EDE_SIG_E_B_V 25 /* Signature Expired before Valid */
#define EDE_TOO_EARLY 26 /* To Early */
#define EDE_UNS_NS3_ITER 27 /* Unsupported NSEC3 Iterations Value */
#define EDE_UNABLE_POLICY 28 /* Unable to conform to policy */
#define EDE_SYNTHESIZED 29 /* Synthesized */
struct dns_header { struct dns_header {
+2 -10
View File
@@ -131,20 +131,12 @@ int main (int argc, char **argv)
'.' or NAME_ESCAPE then all would have to be escaped, so the '.' or NAME_ESCAPE then all would have to be escaped, so the
presentation format would be twice as long as the spec. */ presentation format would be twice as long as the spec. */
daemon->keyname = safe_malloc((MAXDNAME * 2) + 1); daemon->keyname = safe_malloc((MAXDNAME * 2) + 1);
daemon->workspacename = safe_malloc((MAXDNAME * 2) + 1);
/* one char flag per possible RR in answer section (may get extended). */ /* one char flag per possible RR in answer section (may get extended). */
daemon->rr_status_sz = 64; daemon->rr_status_sz = 64;
daemon->rr_status = safe_malloc(sizeof(*daemon->rr_status) * daemon->rr_status_sz); daemon->rr_status = safe_malloc(sizeof(*daemon->rr_status) * daemon->rr_status_sz);
} }
#endif #endif
#if defined(HAVE_CONNTRACK) && defined(HAVE_UBUS)
/* CONNTRACK UBUS code uses this buffer, so if not allocated above,
we need to allocate it here. */
if (option_bool(OPT_CMARK_ALST_EN) && !daemon->workspacename)
daemon->workspacename = safe_malloc((MAXDNAME * 2) + 1);
#endif
#ifdef HAVE_DHCP #ifdef HAVE_DHCP
if (!daemon->lease_file) if (!daemon->lease_file)
{ {
@@ -429,8 +421,8 @@ int main (int argc, char **argv)
} }
#ifdef HAVE_INOTIFY #ifdef HAVE_INOTIFY
if ((daemon->port != 0 || daemon->dhcp || daemon->doing_dhcp6) if ((daemon->port != 0 && !option_bool(OPT_NO_RESOLV)) ||
&& (!option_bool(OPT_NO_RESOLV) || daemon->dynamic_dirs)) daemon->dynamic_dirs)
inotify_dnsmasq_init(); inotify_dnsmasq_init();
else else
daemon->inotifyfd = -1; daemon->inotifyfd = -1;
+19 -9
View File
@@ -341,7 +341,7 @@ union all_addr {
in the cache flags. */ in the cache flags. */
struct datablock { struct datablock {
unsigned short rrtype; unsigned short rrtype;
unsigned char datalen; unsigned char datalen; /* also length of SOA in negative records. */
char data[]; char data[];
} rrdata; } rrdata;
}; };
@@ -757,6 +757,9 @@ struct dyndir {
#define DNSSEC_FAIL_NONSEC 0x0040 /* No NSEC */ #define DNSSEC_FAIL_NONSEC 0x0040 /* No NSEC */
#define DNSSEC_FAIL_NODSSUP 0x0080 /* no supported DS algo. */ #define DNSSEC_FAIL_NODSSUP 0x0080 /* no supported DS algo. */
#define DNSSEC_FAIL_NOKEY 0x0100 /* no DNSKEY */ #define DNSSEC_FAIL_NOKEY 0x0100 /* no DNSKEY */
#define DNSSEC_FAIL_NSEC3_ITERS 0x0200 /* too many iterations in NSEC3 */
#define DNSSEC_FAIL_BADPACKET 0x0400 /* bad packet */
#define DNSSEC_FAIL_WORK 0x0800 /* too much crypto */
#define STAT_ISEQUAL(a, b) (((a) & 0xffff0000) == (b)) #define STAT_ISEQUAL(a, b) (((a) & 0xffff0000) == (b))
@@ -794,7 +797,7 @@ struct frec {
struct blockdata *stash; /* Saved reply, whilst we validate */ struct blockdata *stash; /* Saved reply, whilst we validate */
size_t stash_len; size_t stash_len;
#ifdef HAVE_DNSSEC #ifdef HAVE_DNSSEC
int class, work_counter; int class, work_counter, validate_counter;
struct frec *dependent; /* Query awaiting internally-generated DNSKEY or DS query */ struct frec *dependent; /* Query awaiting internally-generated DNSKEY or DS query */
struct frec *next_dependent; /* list of above. */ struct frec *next_dependent; /* list of above. */
struct frec *blocking_query; /* Query which is blocking us. */ struct frec *blocking_query; /* Query which is blocking us. */
@@ -831,6 +834,12 @@ struct frec {
#define LEASE_HAVE_HWADDR 128 /* Have set hwaddress */ #define LEASE_HAVE_HWADDR 128 /* Have set hwaddress */
#define LEASE_EXP_CHANGED 256 /* Lease expiry time changed */ #define LEASE_EXP_CHANGED 256 /* Lease expiry time changed */
#define LIMIT_SIG_FAIL 0
#define LIMIT_CRYPTO 1
#define LIMIT_WORK 2
#define LIMIT_NSEC3_ITERS 3
#define LIMIT_MAX 4
struct dhcp_lease { struct dhcp_lease {
int clid_len; /* length of client identifier */ int clid_len; /* length of client identifier */
unsigned char *clid; /* clientid */ unsigned char *clid; /* clientid */
@@ -1233,16 +1242,14 @@ extern struct daemon {
char *packet; /* packet buffer */ char *packet; /* packet buffer */
int packet_buff_sz; /* size of above */ int packet_buff_sz; /* size of above */
char *namebuff; /* MAXDNAME size buffer */ char *namebuff; /* MAXDNAME size buffer */
#if (defined(HAVE_CONNTRACK) && defined(HAVE_UBUS)) || defined(HAVE_DNSSEC)
/* CONNTRACK UBUS code uses this buffer, as well as DNSSEC code. */
char *workspacename; char *workspacename;
#endif
#ifdef HAVE_DNSSEC #ifdef HAVE_DNSSEC
char *keyname; /* MAXDNAME size buffer */ char *keyname; /* MAXDNAME size buffer */
unsigned long *rr_status; /* ceiling in TTL from DNSSEC or zero for insecure */ unsigned long *rr_status; /* ceiling in TTL from DNSSEC or zero for insecure */
int rr_status_sz; int rr_status_sz;
int dnssec_no_time_check; int dnssec_no_time_check;
int back_to_the_future; int back_to_the_future;
int limit[LIMIT_MAX];
#endif #endif
struct frec *frec_list; struct frec *frec_list;
struct frec_src *free_frec_src; struct frec_src *free_frec_src;
@@ -1376,6 +1383,7 @@ int is_name_synthetic(int flags, char *name, union all_addr *addr);
int is_rev_synth(int flag, union all_addr *addr, char *name); int is_rev_synth(int flag, union all_addr *addr, char *name);
/* rfc1035.c */ /* rfc1035.c */
int do_doctor(struct dns_header *header, size_t qlen, char *namebuff);
int extract_name(struct dns_header *header, size_t plen, unsigned char **pp, int extract_name(struct dns_header *header, size_t plen, unsigned char **pp,
char *name, int isExtract, int extrabytes); char *name, int isExtract, int extrabytes);
unsigned char *skip_name(unsigned char *ansp, struct dns_header *header, size_t plen, int extrabytes); unsigned char *skip_name(unsigned char *ansp, struct dns_header *header, size_t plen, int extrabytes);
@@ -1386,7 +1394,7 @@ unsigned int extract_request(struct dns_header *header, size_t qlen,
void setup_reply(struct dns_header *header, unsigned int flags, int ede); void setup_reply(struct dns_header *header, unsigned int flags, int ede);
int extract_addresses(struct dns_header *header, size_t qlen, char *name, int extract_addresses(struct dns_header *header, size_t qlen, char *name,
time_t now, struct ipsets *ipsets, struct ipsets *nftsets, int is_sign, time_t now, struct ipsets *ipsets, struct ipsets *nftsets, int is_sign,
int check_rebind, int no_cache_dnssec, int secure, int *doctored); int check_rebind, int no_cache_dnssec, int secure);
#if defined(HAVE_CONNTRACK) && defined(HAVE_UBUS) #if defined(HAVE_CONNTRACK) && defined(HAVE_UBUS)
void report_addresses(struct dns_header *header, size_t len, u32 mark); void report_addresses(struct dns_header *header, size_t len, u32 mark);
#endif #endif
@@ -1417,10 +1425,12 @@ int in_zone(struct auth_zone *zone, char *name, char **cut);
/* dnssec.c */ /* dnssec.c */
#ifdef HAVE_DNSSEC #ifdef HAVE_DNSSEC
size_t dnssec_generate_query(struct dns_header *header, unsigned char *end, char *name, int class, int type, int edns_pktsz); size_t dnssec_generate_query(struct dns_header *header, unsigned char *end, char *name, int class, int type, int edns_pktsz);
int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int class); int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, char *name,
int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int class); char *keyname, int class, int *validate_count);
int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char *name,
char *keyname, int class, int *validate_count);
int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int *class, int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int *class,
int check_unsigned, int *neganswer, int *nons, int *nsec_ttl); int check_unsigned, int *neganswer, int *nons, int *nsec_ttl, int *validate_count);
int dnskey_keytag(int alg, int flags, unsigned char *key, int keylen); int dnskey_keytag(int alg, int flags, unsigned char *key, int keylen);
size_t filter_rrsigs(struct dns_header *header, size_t plen); size_t filter_rrsigs(struct dns_header *header, size_t plen);
int setup_timestamp(void); int setup_timestamp(void);
+290 -176
View File
@@ -424,12 +424,24 @@ static int explore_rrset(struct dns_header *header, size_t plen, int class, int
return 1; return 1;
} }
int dec_counter(int *counter, char *message)
{
if ((*counter)-- == 0)
{
my_syslog(LOG_WARNING, "limit exceeded: %s", message ? message : _("per-query crypto work"));
return 1;
}
return 0;
}
/* Validate a single RRset (class, type, name) in the supplied DNS reply /* Validate a single RRset (class, type, name) in the supplied DNS reply
Return code: Return code:
STAT_SECURE if it validates. STAT_SECURE if it validates.
STAT_SECURE_WILDCARD if it validates and is the result of wildcard expansion. STAT_SECURE_WILDCARD if it validates and is the result of wildcard expansion.
(In this case *wildcard_out points to the "body" of the wildcard within name.) (In this case *wildcard_out points to the "body" of the wildcard within name.)
STAT_BOGUS signature is wrong, bad packet. STAT_BOGUS signature is wrong, bad packet.
STAT_ABANDONED validation abandoned do to excess resource usage.
STAT_NEED_KEY need DNSKEY to complete validation (name is returned in keyname) STAT_NEED_KEY need DNSKEY to complete validation (name is returned in keyname)
STAT_NEED_DS need DS to complete validation (name is returned in keyname) STAT_NEED_DS need DS to complete validation (name is returned in keyname)
@@ -444,10 +456,10 @@ static int explore_rrset(struct dns_header *header, size_t plen, int class, int
*/ */
static int validate_rrset(time_t now, struct dns_header *header, size_t plen, int class, int type, int sigidx, int rrsetidx, static int validate_rrset(time_t now, struct dns_header *header, size_t plen, int class, int type, int sigidx, int rrsetidx,
char *name, char *keyname, char **wildcard_out, struct blockdata *key, int keylen, char *name, char *keyname, char **wildcard_out, struct blockdata *key, int keylen,
int algo_in, int keytag_in, unsigned long *ttl_out) int algo_in, int keytag_in, unsigned long *ttl_out, int *validate_counter)
{ {
unsigned char *p; unsigned char *p;
int rdlen, j, name_labels, algo, labels, key_tag; int rdlen, j, name_labels, algo, labels, key_tag, sig_fail_cnt;
struct crec *crecp = NULL; struct crec *crecp = NULL;
short *rr_desc = rrfilter_desc(type); short *rr_desc = rrfilter_desc(type);
u32 sig_expiration, sig_inception; u32 sig_expiration, sig_inception;
@@ -467,7 +479,7 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in
rrsetidx = sort_rrset(header, plen, rr_desc, rrsetidx, rrset, daemon->workspacename, keyname); rrsetidx = sort_rrset(header, plen, rr_desc, rrsetidx, rrset, daemon->workspacename, keyname);
/* Now try all the sigs to try and find one which validates */ /* Now try all the sigs to try and find one which validates */
for (j = 0; j <sigidx; j++) for (sig_fail_cnt = daemon->limit[LIMIT_SIG_FAIL], j = 0; j <sigidx; j++)
{ {
unsigned char *psav, *sig, *digest; unsigned char *psav, *sig, *digest;
int i, wire_len, sig_len; int i, wire_len, sig_len;
@@ -654,19 +666,37 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in
if (key) if (key)
{ {
if (algo_in == algo && keytag_in == key_tag && if (algo_in == algo && keytag_in == key_tag)
verify(key, keylen, sig, sig_len, digest, hash->digest_size, algo)) {
if (dec_counter(validate_counter, NULL))
return STAT_ABANDONED;
if (verify(key, keylen, sig, sig_len, digest, hash->digest_size, algo))
return STAT_SECURE; return STAT_SECURE;
} }
}
else else
{ {
/* iterate through all possible keys 4035 5.3.1 */ /* iterate through all possible keys 4035 5.3.1 */
for (; crecp; crecp = cache_find_by_name(crecp, keyname, now, F_DNSKEY)) for (; crecp; crecp = cache_find_by_name(crecp, keyname, now, F_DNSKEY))
if (crecp->addr.key.algo == algo && if (crecp->addr.key.algo == algo &&
crecp->addr.key.keytag == key_tag && crecp->addr.key.keytag == key_tag &&
crecp->uid == (unsigned int)class && crecp->uid == (unsigned int)class)
verify(crecp->addr.key.keydata, crecp->addr.key.keylen, sig, sig_len, digest, hash->digest_size, algo)) {
if (dec_counter(validate_counter, NULL))
return STAT_ABANDONED;
if (verify(crecp->addr.key.keydata, crecp->addr.key.keylen, sig, sig_len, digest, hash->digest_size, algo))
return (labels < name_labels) ? STAT_SECURE_WILDCARD : STAT_SECURE; return (labels < name_labels) ? STAT_SECURE_WILDCARD : STAT_SECURE;
/* An attacker can waste a lot of our CPU by setting up a giant DNSKEY RRSET full of failing
keys, all of which we have to try. Since many failing keys is not likely for
a legitimate domain, set a limit on how many can fail. */
if ((daemon->limit[LIMIT_SIG_FAIL] - (sig_fail_cnt + 1)) > (int)daemon->metrics[METRIC_SIG_FAIL_HWM])
daemon->metrics[METRIC_SIG_FAIL_HWM] = daemon->limit[LIMIT_SIG_FAIL] - (sig_fail_cnt + 1);
if (dec_counter(&sig_fail_cnt, _("per-RRSet signature fails")))
return STAT_ABANDONED;
}
} }
} }
@@ -681,30 +711,36 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in
STAT_OK Done, key(s) in cache. STAT_OK Done, key(s) in cache.
STAT_BOGUS No DNSKEYs found, which can be validated with DS, STAT_BOGUS No DNSKEYs found, which can be validated with DS,
or self-sign for DNSKEY RRset is not valid, bad packet. or self-sign for DNSKEY RRset is not valid, bad packet.
STAT_ABANDONED resource exhaustion.
STAT_NEED_DS DS records to validate a key not found, name in keyname STAT_NEED_DS DS records to validate a key not found, name in keyname
STAT_NEED_KEY DNSKEY records to validate a key not found, name in keyname
*/ */
int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int class) int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, char *name,
char *keyname, int class, int *validate_counter)
{ {
unsigned char *psave, *p = (unsigned char *)(header+1); unsigned char *psave, *p = (unsigned char *)(header+1), *keyaddr;
struct crec *crecp, *recp1; struct crec *crecp, *recp1;
int rc, j, qtype, qclass, rdlen, flags, algo, valid, keytag; int rc, j, qtype, qclass, rdlen, flags, algo, keytag, sigcnt, rrcnt;
unsigned long ttl, sig_ttl; unsigned long ttl, sig_ttl;
struct blockdata *key;
union all_addr a; union all_addr a;
int failflags = DNSSEC_FAIL_NOSIG | DNSSEC_FAIL_NODSSUP | DNSSEC_FAIL_NOZONE | DNSSEC_FAIL_NOKEY; int failflags = DNSSEC_FAIL_NODSSUP | DNSSEC_FAIL_NOZONE;
char valid_digest[255];
static unsigned char **cached_digest;
static size_t cached_digest_size = 0;
if (ntohs(header->qdcount) != 1 || if (ntohs(header->qdcount) != 1 || RCODE(header) != NOERROR || !extract_name(header, plen, &p, name, 1, 4))
RCODE(header) == SERVFAIL || RCODE(header) == REFUSED ||
!extract_name(header, plen, &p, name, 1, 4))
return STAT_BOGUS | DNSSEC_FAIL_NOKEY; return STAT_BOGUS | DNSSEC_FAIL_NOKEY;
GETSHORT(qtype, p); GETSHORT(qtype, p);
GETSHORT(qclass, p); GETSHORT(qclass, p);
if (qtype != T_DNSKEY || qclass != class || ntohs(header->ancount) == 0) if (qtype != T_DNSKEY || qclass != class ||
!explore_rrset(header, plen, class, T_DNSKEY, name, keyname, &sigcnt, &rrcnt) ||
rrcnt == 0)
return STAT_BOGUS | DNSSEC_FAIL_NOKEY; return STAT_BOGUS | DNSSEC_FAIL_NOKEY;
if (sigcnt == 0)
return STAT_BOGUS | DNSSEC_FAIL_NOSIG;
/* See if we have cached a DS record which validates this key */ /* See if we have cached a DS record which validates this key */
if (!(crecp = cache_find_by_name(NULL, name, now, F_DS))) if (!(crecp = cache_find_by_name(NULL, name, now, F_DS)))
{ {
@@ -713,7 +749,7 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch
} }
/* NOTE, we need to find ONE DNSKEY which matches the DS */ /* NOTE, we need to find ONE DNSKEY which matches the DS */
for (valid = 0, j = ntohs(header->ancount); j != 0 && !valid; j--) for (j = ntohs(header->ancount); j != 0; j--)
{ {
/* Ensure we have type, class TTL and length */ /* Ensure we have type, class TTL and length */
if (!(rc = extract_name(header, plen, &p, name, 0, 10))) if (!(rc = extract_name(header, plen, &p, name, 0, 10)))
@@ -724,7 +760,7 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch
GETLONG(ttl, p); GETLONG(ttl, p);
GETSHORT(rdlen, p); GETSHORT(rdlen, p);
if (!CHECK_LEN(header, p, plen, rdlen) || rdlen < 4) if (!CHECK_LEN(header, p, plen, rdlen))
return STAT_BOGUS; /* bad packet */ return STAT_BOGUS; /* bad packet */
if (qclass != class || qtype != T_DNSKEY || rc == 2) if (qclass != class || qtype != T_DNSKEY || rc == 2)
@@ -733,97 +769,133 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch
continue; continue;
} }
if (rdlen < 5)
return STAT_BOGUS; /* min 1 byte key! */
psave = p; psave = p;
GETSHORT(flags, p); GETSHORT(flags, p);
if (*p++ != 3) if (*p++ != 3)
return STAT_BOGUS | DNSSEC_FAIL_NOKEY; {
p = psave + rdlen;
continue;
}
algo = *p++; algo = *p++;
keytag = dnskey_keytag(algo, flags, p, rdlen - 4); keyaddr = p;
key = NULL; keytag = dnskey_keytag(algo, flags, keyaddr, rdlen - 4);
p = psave + rdlen;
/* key must have zone key flag set */ /* key must have zone key flag set */
if (flags & 0x100) if (!(flags & 0x100))
{
key = blockdata_alloc((char*)p, rdlen - 4);
failflags &= ~DNSSEC_FAIL_NOZONE;
}
p = psave;
if (!ADD_RDLEN(header, p, plen, rdlen))
{
if (key)
blockdata_free(key);
return STAT_BOGUS; /* bad packet */
}
/* No zone key flag or malloc failure */
if (!key)
continue; continue;
failflags &= ~DNSSEC_FAIL_NOZONE;
/* clear digest cache. */
memset(valid_digest, 0, sizeof(valid_digest));
for (recp1 = crecp; recp1; recp1 = cache_find_by_name(recp1, name, now, F_DS)) for (recp1 = crecp; recp1; recp1 = cache_find_by_name(recp1, name, now, F_DS))
{ {
void *ctx; void *ctx;
unsigned char *digest, *ds_digest; unsigned char *digest, *ds_digest;
const struct nettle_hash *hash; const struct nettle_hash *hash;
int sigcnt, rrcnt;
int wire_len; int wire_len;
if (recp1->addr.ds.algo == algo && if ((recp1->flags & F_NEG) ||
recp1->addr.ds.keytag == keytag && recp1->addr.ds.algo != algo ||
recp1->uid == (unsigned int)class) recp1->addr.ds.keytag != keytag ||
{ recp1->uid != (unsigned int)class)
failflags &= ~DNSSEC_FAIL_NOKEY; continue;
if (!(hash = hash_find(ds_digest_name(recp1->addr.ds.digest)))) if (!(hash = hash_find(ds_digest_name(recp1->addr.ds.digest))))
continue; continue;
else
failflags &= ~DNSSEC_FAIL_NODSSUP; failflags &= ~DNSSEC_FAIL_NODSSUP;
if (recp1->addr.ds.keylen != (int)hash->digest_size ||
!(ds_digest = blockdata_retrieve(recp1->addr.ds.keydata, recp1->addr.ds.keylen, NULL)))
continue;
if (valid_digest[recp1->addr.ds.digest])
digest = cached_digest[recp1->addr.ds.digest];
else
{
/* computing a hash is a unit of crypto work. */
if (dec_counter(validate_counter, NULL))
return STAT_ABANDONED;
if (!hash_init(hash, &ctx, &digest)) if (!hash_init(hash, &ctx, &digest))
continue; continue;
wire_len = to_wire(name); wire_len = to_wire(name);
/* Note that digest may be different between DSs, so /* Note that digest may be different between DSs, so
we can't move this outside the loop. */ we can't move this outside the loop. We keep
copies of each digest we make for this key,
so maximum digest work is O(keys x digests_types)
rather then O(keys x DSs) */
hash->update(ctx, (unsigned int)wire_len, (unsigned char *)name); hash->update(ctx, (unsigned int)wire_len, (unsigned char *)name);
hash->update(ctx, (unsigned int)rdlen, psave); hash->update(ctx, (unsigned int)rdlen, psave);
hash->digest(ctx, hash->digest_size, digest); hash->digest(ctx, hash->digest_size, digest);
from_wire(name); from_wire(name);
if (!(recp1->flags & F_NEG) && if (recp1->addr.ds.digest >= cached_digest_size)
recp1->addr.ds.keylen == (int)hash->digest_size &&
(ds_digest = blockdata_retrieve(recp1->addr.ds.keydata, recp1->addr.ds.keylen, NULL)) &&
memcmp(ds_digest, digest, recp1->addr.ds.keylen) == 0 &&
explore_rrset(header, plen, class, T_DNSKEY, name, keyname, &sigcnt, &rrcnt) &&
rrcnt != 0)
{ {
if (sigcnt == 0) unsigned char **new;
continue;
else /* whine_malloc zeros memory */
failflags &= ~DNSSEC_FAIL_NOSIG; if ((new = whine_malloc((recp1->addr.ds.digest + 5) * sizeof(unsigned char *))))
{
if (cached_digest_size != 0)
{
memcpy(new, cached_digest, cached_digest_size * sizeof(unsigned char *));
free(cached_digest);
}
cached_digest_size = recp1->addr.ds.digest + 5;
cached_digest = new;
}
}
if (recp1->addr.ds.digest < cached_digest_size)
{
if (!cached_digest[recp1->addr.ds.digest])
cached_digest[recp1->addr.ds.digest] = whine_malloc(recp1->addr.ds.keylen);
if (cached_digest[recp1->addr.ds.digest])
{
memcpy(cached_digest[recp1->addr.ds.digest], digest, recp1->addr.ds.keylen);
valid_digest[recp1->addr.ds.digest] = 1;
}
}
}
if (memcmp(ds_digest, digest, recp1->addr.ds.keylen) == 0)
{
/* Found the key validated by a DS record.
Now check the self-sig for the entire key RRset using that key.
Note that validate_rrset() will never return STAT_NEED_KEY here,
since we supply the key it will use as an argument. */
struct blockdata *key;
if (!(key = blockdata_alloc((char *)keyaddr, rdlen - 4)))
break;
rc = validate_rrset(now, header, plen, class, T_DNSKEY, sigcnt, rrcnt, name, keyname, rc = validate_rrset(now, header, plen, class, T_DNSKEY, sigcnt, rrcnt, name, keyname,
NULL, key, rdlen - 4, algo, keytag, &sig_ttl); NULL, key, rdlen - 4, algo, keytag, &sig_ttl, validate_counter);
failflags &= rc;
if (STAT_ISEQUAL(rc, STAT_SECURE))
{
valid = 1;
break;
}
}
}
}
blockdata_free(key); blockdata_free(key);
}
if (valid) if (STAT_ISEQUAL(rc, STAT_ABANDONED))
{ return rc;
/* can't validate KEY RRset with this key, see if there's another that
will, which is validated by another DS. */
if (!STAT_ISEQUAL(rc, STAT_SECURE))
break;
/* DNSKEY RRset determined to be OK, now cache it. */ /* DNSKEY RRset determined to be OK, now cache it. */
cache_start_insert(); cache_start_insert();
@@ -847,23 +919,22 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch
if (!CHECK_LEN(header, p, plen, rdlen)) if (!CHECK_LEN(header, p, plen, rdlen))
return STAT_BOGUS; /* bad packet */ return STAT_BOGUS; /* bad packet */
if (qclass == class && rc == 1)
{
psave = p; psave = p;
if (qtype == T_DNSKEY) if (qclass == class && rc == 1 && qtype == T_DNSKEY)
{ {
if (rdlen < 4) if (rdlen < 4)
return STAT_BOGUS; /* bad packet */ return STAT_BOGUS; /* min 1 byte key! */
GETSHORT(flags, p); GETSHORT(flags, p);
if (*p++ != 3) if (*p++ == 3)
return STAT_BOGUS; {
algo = *p++; algo = *p++;
keytag = dnskey_keytag(algo, flags, p, rdlen - 4); keytag = dnskey_keytag(algo, flags, p, rdlen - 4);
if ((key = blockdata_alloc((char*)p, rdlen - 4))) if (!(key = blockdata_alloc((char*)p, rdlen - 4)))
{ return STAT_BOGUS;
a.key.keylen = rdlen - 4; a.key.keylen = rdlen - 4;
a.key.keydata = key; a.key.keydata = key;
a.key.algo = algo; a.key.algo = algo;
@@ -875,8 +946,7 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch
blockdata_free(key); blockdata_free(key);
return STAT_BOGUS; return STAT_BOGUS;
} }
else
{
a.log.keytag = keytag; a.log.keytag = keytag;
a.log.algo = algo; a.log.algo = algo;
if (algo_digest_name(algo)) if (algo_digest_name(algo))
@@ -885,19 +955,16 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch
log_query(F_NOEXTRA | F_KEYTAG | F_UPSTREAM, name, &a, "DNSKEY keytag %hu, algo %hu (not supported)", 0); log_query(F_NOEXTRA | F_KEYTAG | F_UPSTREAM, name, &a, "DNSKEY keytag %hu, algo %hu (not supported)", 0);
} }
} }
}
p = psave; p = psave + rdlen;
}
if (!ADD_RDLEN(header, p, plen, rdlen))
return STAT_BOGUS; /* bad packet */
} }
/* commit cache insert. */ /* commit cache insert. */
cache_end_insert(); cache_end_insert();
return STAT_OK; return STAT_OK;
} }
}
}
log_query(F_NOEXTRA | F_UPSTREAM, name, NULL, "BOGUS DNSKEY", 0); log_query(F_NOEXTRA | F_UPSTREAM, name, NULL, "BOGUS DNSKEY", 0);
return STAT_BOGUS | failflags; return STAT_BOGUS | failflags;
@@ -916,9 +983,11 @@ int dnssec_validate_by_ds(time_t now, struct dns_header *header, size_t plen, ch
STAT_BOGUS no DS in reply or not signed, fails validation, bad packet. STAT_BOGUS no DS in reply or not signed, fails validation, bad packet.
STAT_NEED_KEY DNSKEY records to validate a DS not found, name in keyname STAT_NEED_KEY DNSKEY records to validate a DS not found, name in keyname
STAT_NEED_DS DS record needed. STAT_NEED_DS DS record needed.
STAT_ABANDONED resource exhaustion.
*/ */
int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int class) int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char *name,
char *keyname, int class, int *validate_counter)
{ {
unsigned char *p = (unsigned char *)(header+1); unsigned char *p = (unsigned char *)(header+1);
int qtype, qclass, rc, i, neganswer = 0, nons = 0, servfail = 0, neg_ttl = 0, found_supported = 0; int qtype, qclass, rc, i, neganswer = 0, nons = 0, servfail = 0, neg_ttl = 0, found_supported = 0;
@@ -943,7 +1012,7 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char
servfail = neganswer = nons = 1; servfail = neganswer = nons = 1;
else else
{ {
rc = dnssec_validate_reply(now, header, plen, name, keyname, NULL, 0, &neganswer, &nons, &neg_ttl); rc = dnssec_validate_reply(now, header, plen, name, keyname, NULL, 0, &neganswer, &nons, &neg_ttl, validate_counter);
if (STAT_ISEQUAL(rc, STAT_INSECURE)) if (STAT_ISEQUAL(rc, STAT_INSECURE))
{ {
@@ -977,6 +1046,8 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char
for (i = 0; i < ntohs(header->ancount); i++) for (i = 0; i < ntohs(header->ancount); i++)
{ {
unsigned char *psave;
if (!(rc = extract_name(header, plen, &p, name, 0, 10))) if (!(rc = extract_name(header, plen, &p, name, 0, 10)))
return STAT_BOGUS; /* bad packet */ return STAT_BOGUS; /* bad packet */
@@ -988,14 +1059,15 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char
if (!CHECK_LEN(header, p, plen, rdlen)) if (!CHECK_LEN(header, p, plen, rdlen))
return STAT_BOGUS; /* bad packet */ return STAT_BOGUS; /* bad packet */
psave = p;
if (aclass == class && atype == T_DS && rc == 1) if (aclass == class && atype == T_DS && rc == 1)
{ {
int algo, digest, keytag; int algo, digest, keytag;
unsigned char *psave = p;
struct blockdata *key; struct blockdata *key;
if (rdlen < 4) if (rdlen < 5)
return STAT_BOGUS; /* bad packet */ return STAT_BOGUS; /* min 1 byte digest! */
GETSHORT(keytag, p); GETSHORT(keytag, p);
algo = *p++; algo = *p++;
@@ -1006,7 +1078,7 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char
a.log.keytag = keytag; a.log.keytag = keytag;
a.log.algo = algo; a.log.algo = algo;
a.log.digest = digest; a.log.digest = digest;
log_query(F_NOEXTRA | F_KEYTAG | F_UPSTREAM, name, &a, "DS keytag %hu, algo %hu, digest %hu (not supported)", 0); log_query(F_NOEXTRA | F_KEYTAG | F_UPSTREAM, name, &a, "DS for keytag %hu, algo %hu, digest %hu (not supported)", 0);
neg_ttl = ttl; neg_ttl = ttl;
} }
else if ((key = blockdata_alloc((char*)p, rdlen - 4))) else if ((key = blockdata_alloc((char*)p, rdlen - 4)))
@@ -1027,16 +1099,13 @@ int dnssec_validate_ds(time_t now, struct dns_header *header, size_t plen, char
a.log.keytag = keytag; a.log.keytag = keytag;
a.log.algo = algo; a.log.algo = algo;
a.log.digest = digest; a.log.digest = digest;
log_query(F_NOEXTRA | F_KEYTAG | F_UPSTREAM, name, &a, "DS keytag %hu, algo %hu, digest %hu", 0); log_query(F_NOEXTRA | F_KEYTAG | F_UPSTREAM, name, &a, "DS for keytag %hu, algo %hu, digest %hu", 0);
found_supported = 1; found_supported = 1;
} }
} }
p = psave;
} }
if (!ADD_RDLEN(header, p, plen, rdlen)) p = psave + rdlen;
return STAT_BOGUS; /* bad packet */
} }
cache_end_insert(); cache_end_insert();
@@ -1139,6 +1208,7 @@ static int hostname_cmp(const char *a, const char *b)
} }
} }
/* returns 0 on success, or DNSSEC_FAIL_* value on failure. */
static int prove_non_existence_nsec(struct dns_header *header, size_t plen, unsigned char **nsecs, unsigned char **labels, int nsec_count, 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) char *workspace1_in, char *workspace2, char *name, int type, int *nons)
{ {
@@ -1158,12 +1228,12 @@ static int prove_non_existence_nsec(struct dns_header *header, size_t plen, unsi
p = nsecs[i]; p = nsecs[i];
if (!extract_name(header, plen, &p, workspace1, 1, 10)) if (!extract_name(header, plen, &p, workspace1, 1, 10))
return 0; return DNSSEC_FAIL_BADPACKET;
p += 8; /* class, type, TTL */ p += 8; /* class, type, TTL */
GETSHORT(rdlen, p); GETSHORT(rdlen, p);
psave = p; psave = p;
if (!extract_name(header, plen, &p, workspace2, 1, 10)) if (!extract_name(header, plen, &p, workspace2, 1, 0))
return 0; return DNSSEC_FAIL_BADPACKET;
/* If NSEC comes from wildcard expansion, use original wildcard /* If NSEC comes from wildcard expansion, use original wildcard
as name for computation. */ as name for computation. */
@@ -1191,12 +1261,13 @@ static int prove_non_existence_nsec(struct dns_header *header, size_t plen, unsi
{ {
/* 4035 para 5.4. Last sentence */ /* 4035 para 5.4. Last sentence */
if (type == T_NSEC || type == T_RRSIG) if (type == T_NSEC || type == T_RRSIG)
return 1; return 0;
/* NSEC with the same name as the RR we're testing, check /* NSEC with the same name as the RR we're testing, check
that the type in question doesn't appear in the type map */ that the type in question doesn't appear in the type map */
rdlen -= p - psave; rdlen -= p - psave;
/* rdlen is now length of type map, and p points to it */ /* rdlen is now length of type map, and p points to it
packet checked to be as long as rdlen implies in prove_non_existence() */
/* If we can prove that there's no NS record, return that information. */ /* If we can prove that there's no NS record, return that information. */
if (nons && rdlen >= 2 && p[0] == 0 && (p[2] & (0x80 >> T_NS)) != 0) if (nons && rdlen >= 2 && p[0] == 0 && (p[2] & (0x80 >> T_NS)) != 0)
@@ -1207,25 +1278,25 @@ static int prove_non_existence_nsec(struct dns_header *header, size_t plen, unsi
/* A CNAME answer would also be valid, so if there's a CNAME is should /* A CNAME answer would also be valid, so if there's a CNAME is should
have been returned. */ have been returned. */
if ((p[2] & (0x80 >> T_CNAME)) != 0) if ((p[2] & (0x80 >> T_CNAME)) != 0)
return 0; return DNSSEC_FAIL_NONSEC;
/* If the SOA bit is set for a DS record, then we have the /* If the SOA bit is set for a DS record, then we have the
DS from the wrong side of the delegation. For the root DS, DS from the wrong side of the delegation. For the root DS,
this is expected. */ this is expected. */
if (name_labels != 0 && type == T_DS && (p[2] & (0x80 >> T_SOA)) != 0) if (name_labels != 0 && type == T_DS && (p[2] & (0x80 >> T_SOA)) != 0)
return 0; return DNSSEC_FAIL_NONSEC;
} }
while (rdlen >= 2) while (rdlen >= 2)
{ {
if (!CHECK_LEN(header, p, plen, rdlen)) if (!CHECK_LEN(header, p, plen, rdlen))
return 0; return DNSSEC_FAIL_BADPACKET;
if (p[0] == type >> 8) if (p[0] == type >> 8)
{ {
/* Does the NSEC say our type exists? */ /* Does the NSEC say our type exists? */
if (offset < p[1] && (p[offset+2] & mask) != 0) if (offset < p[1] && (p[offset+2] & mask) != 0)
return 0; return DNSSEC_FAIL_NONSEC;
break; /* finished checking */ break; /* finished checking */
} }
@@ -1234,24 +1305,24 @@ static int prove_non_existence_nsec(struct dns_header *header, size_t plen, unsi
p += p[1]; p += p[1];
} }
return 1; return 0;
} }
else if (rc == -1) else if (rc == -1)
{ {
/* Normal case, name falls between NSEC name and next domain name, /* Normal case, name falls between NSEC name and next domain name,
wrap around case, name falls between NSEC name (rc == -1) and end */ wrap around case, name falls between NSEC name (rc == -1) and end */
if (hostname_cmp(workspace2, name) >= 0 || hostname_cmp(workspace1, workspace2) >= 0) if (hostname_cmp(workspace2, name) >= 0 || hostname_cmp(workspace1, workspace2) >= 0)
return 1; return 0;
} }
else else
{ {
/* wrap around case, name falls between start and next domain name */ /* wrap around case, name falls between start and next domain name */
if (hostname_cmp(workspace1, workspace2) >= 0 && hostname_cmp(workspace2, name) >=0 ) if (hostname_cmp(workspace1, workspace2) >= 0 && hostname_cmp(workspace2, name) >=0 )
return 1; return 0;
} }
} }
return 0; return DNSSEC_FAIL_NONSEC;
} }
/* return digest length, or zero on error */ /* return digest length, or zero on error */
@@ -1325,13 +1396,16 @@ static int check_nsec3_coverage(struct dns_header *header, size_t plen, int dige
for (i = 0; i < nsec_count; i++) for (i = 0; i < nsec_count; i++)
if ((p = nsecs[i])) if ((p = nsecs[i]))
{ {
if (!extract_name(header, plen, &p, workspace1, 1, 0) || if (!extract_name(header, plen, &p, workspace1, 1, 10) ||
!(base32_len = base32_decode(workspace1, (unsigned char *)workspace2))) !(base32_len = base32_decode(workspace1, (unsigned char *)workspace2)))
return 0; return 0;
p += 8; /* class, type, TTL */ p += 8; /* class, type, TTL */
GETSHORT(rdlen, p); GETSHORT(rdlen, p);
psave = p; psave = p;
/* packet checked to be as long as implied by rdlen, salt_len and hash_len in prove_non_existence() */
p++; /* algo */ p++; /* algo */
flags = *p++; /* flags */ flags = *p++; /* flags */
p += 2; /* iterations */ p += 2; /* iterations */
@@ -1339,9 +1413,6 @@ static int check_nsec3_coverage(struct dns_header *header, size_t plen, int dige
p += salt_len; /* salt */ p += salt_len; /* salt */
hash_len = *p++; /* p now points to next hashed name */ hash_len = *p++; /* p now points to next hashed name */
if (!CHECK_LEN(header, p, plen, hash_len))
return 0;
if (digest_len == base32_len && hash_len == base32_len) if (digest_len == base32_len && hash_len == base32_len)
{ {
int rc = memcmp(workspace2, digest, digest_len); int rc = memcmp(workspace2, digest, digest_len);
@@ -1349,7 +1420,8 @@ static int check_nsec3_coverage(struct dns_header *header, size_t plen, int dige
if (rc == 0) if (rc == 0)
{ {
/* We found an NSEC3 whose hashed name exactly matches the query, so /* We found an NSEC3 whose hashed name exactly matches the query, so
we just need to check the type map. p points to the RR data for the record. */ we just need to check the type map. p points to the RR data for the record.
Note we have packet length up to rdlen bytes checked. */
int offset = (type & 0xff) >> 3; int offset = (type & 0xff) >> 3;
int mask = 0x80 >> (type & 0x07); int mask = 0x80 >> (type & 0x07);
@@ -1357,9 +1429,6 @@ static int check_nsec3_coverage(struct dns_header *header, size_t plen, int dige
p += hash_len; /* skip next-domain hash */ p += hash_len; /* skip next-domain hash */
rdlen -= p - psave; rdlen -= p - psave;
if (!CHECK_LEN(header, p, plen, rdlen))
return 0;
if (rdlen >= 2 && p[0] == 0) if (rdlen >= 2 && p[0] == 0)
{ {
/* If we can prove that there's no NS record, return that information. */ /* If we can prove that there's no NS record, return that information. */
@@ -1424,8 +1493,9 @@ static int check_nsec3_coverage(struct dns_header *header, size_t plen, int dige
return 0; return 0;
} }
static int prove_non_existence_nsec3(struct dns_header *header, size_t plen, unsigned char **nsecs, int nsec_count, /* returns 0 on success, or DNSSEC_FAIL_* value on failure. */
char *workspace1, char *workspace2, char *name, int type, char *wildname, int *nons) static int prove_non_existence_nsec3(struct dns_header *header, size_t plen, unsigned char **nsecs, int nsec_count, char *workspace1,
char *workspace2, char *name, int type, char *wildname, int *nons, int *validate_counter)
{ {
unsigned char *salt, *p, *digest; unsigned char *salt, *p, *digest;
int digest_len, i, iterations, salt_len, base32_len, algo = 0; int digest_len, i, iterations, salt_len, base32_len, algo = 0;
@@ -1445,7 +1515,7 @@ static int prove_non_existence_nsec3(struct dns_header *header, size_t plen, uns
for (i = 0; i < nsec_count; i++) for (i = 0; i < nsec_count; i++)
{ {
if (!(p = skip_name(nsecs[i], header, plen, 15))) if (!(p = skip_name(nsecs[i], header, plen, 15)))
return 0; /* bad packet */ return DNSSEC_FAIL_BADPACKET; /* bad packet */
p += 10; /* type, class, TTL, rdlen */ p += 10; /* type, class, TTL, rdlen */
algo = *p++; algo = *p++;
@@ -1456,22 +1526,17 @@ static int prove_non_existence_nsec3(struct dns_header *header, size_t plen, uns
/* No usable NSEC3s */ /* No usable NSEC3s */
if (i == nsec_count) if (i == nsec_count)
return 0; return DNSSEC_FAIL_NONSEC;
p++; /* flags */ p++; /* flags */
GETSHORT (iterations, p); GETSHORT (iterations, p);
/* Upper-bound iterations, to avoid DoS. /* Upper-bound iterations, to avoid DoS. RFC 9276 refers. */
Strictly, there are lower bounds for small keys, but if (iterations > daemon->limit[LIMIT_NSEC3_ITERS])
since we don't have key size info here, at least limit return DNSSEC_FAIL_NSEC3_ITERS;
to the largest bound, for 4096-bit keys. RFC 5155 10.3 */
if (iterations > 2500)
return 0;
salt_len = *p++; salt_len = *p++;
salt = p; salt = p;
if (!CHECK_LEN(header, salt, plen, salt_len))
return 0; /* bad packet */
/* Now prune so we only have NSEC3 records with same iterations, salt and algo */ /* Now prune so we only have NSEC3 records with same iterations, salt and algo */
for (i = 0; i < nsec_count; i++) for (i = 0; i < nsec_count; i++)
@@ -1502,9 +1567,6 @@ static int prove_non_existence_nsec3(struct dns_header *header, size_t plen, uns
if (salt_len != *p++) if (salt_len != *p++)
continue; continue;
if (!CHECK_LEN(header, p, plen, salt_len))
return 0; /* bad packet */
if (memcmp(p, salt, salt_len) != 0) if (memcmp(p, salt, salt_len) != 0)
continue; continue;
@@ -1512,11 +1574,14 @@ static int prove_non_existence_nsec3(struct dns_header *header, size_t plen, uns
nsecs[i] = nsec3p; nsecs[i] = nsec3p;
} }
if (dec_counter(validate_counter, NULL))
return DNSSEC_FAIL_WORK;
if ((digest_len = hash_name(name, &digest, hash, salt, salt_len, iterations)) == 0) if ((digest_len = hash_name(name, &digest, hash, salt, salt_len, iterations)) == 0)
return 0; return DNSSEC_FAIL_NONSEC;
if (check_nsec3_coverage(header, plen, digest_len, digest, type, workspace1, workspace2, nsecs, nsec_count, nons, count_labels(name))) if (check_nsec3_coverage(header, plen, digest_len, digest, type, workspace1, workspace2, nsecs, nsec_count, nons, count_labels(name)))
return 1; return 0;
/* Can't find an NSEC3 which covers the name directly, we need the "closest encloser NSEC3" /* Can't find an NSEC3 which covers the name directly, we need the "closest encloser NSEC3"
or an answer inferred from a wildcard record. */ or an answer inferred from a wildcard record. */
@@ -1531,15 +1596,20 @@ static int prove_non_existence_nsec3(struct dns_header *header, size_t plen, uns
if (wildname && hostname_isequal(closest_encloser, wildname)) if (wildname && hostname_isequal(closest_encloser, wildname))
break; break;
if (dec_counter(validate_counter, NULL))
return DNSSEC_FAIL_WORK;
if ((digest_len = hash_name(closest_encloser, &digest, hash, salt, salt_len, iterations)) == 0) if ((digest_len = hash_name(closest_encloser, &digest, hash, salt, salt_len, iterations)) == 0)
return 0; return DNSSEC_FAIL_NONSEC;
for (i = 0; i < nsec_count; i++) for (i = 0; i < nsec_count; i++)
if ((p = nsecs[i])) if ((p = nsecs[i]))
{ {
if (!extract_name(header, plen, &p, workspace1, 1, 0) || if (!extract_name(header, plen, &p, workspace1, 1, 0))
!(base32_len = base32_decode(workspace1, (unsigned char *)workspace2))) return DNSSEC_FAIL_BADPACKET;
return 0;
if (!(base32_len = base32_decode(workspace1, (unsigned char *)workspace2)))
return DNSSEC_FAIL_NONSEC;
if (digest_len == base32_len && if (digest_len == base32_len &&
memcmp(digest, workspace2, digest_len) == 0) memcmp(digest, workspace2, digest_len) == 0)
@@ -1554,14 +1624,17 @@ static int prove_non_existence_nsec3(struct dns_header *header, size_t plen, uns
while ((closest_encloser = strchr(closest_encloser, '.'))); while ((closest_encloser = strchr(closest_encloser, '.')));
if (!closest_encloser || !next_closest) if (!closest_encloser || !next_closest)
return 0; return DNSSEC_FAIL_NONSEC;
/* Look for NSEC3 that proves the non-existence of the next-closest encloser */ /* Look for NSEC3 that proves the non-existence of the next-closest encloser */
if (dec_counter(validate_counter, NULL))
return DNSSEC_FAIL_WORK;
if ((digest_len = hash_name(next_closest, &digest, hash, salt, salt_len, iterations)) == 0) if ((digest_len = hash_name(next_closest, &digest, hash, salt, salt_len, iterations)) == 0)
return 0; return DNSSEC_FAIL_NONSEC;
if (!check_nsec3_coverage(header, plen, digest_len, digest, type, workspace1, workspace2, nsecs, nsec_count, NULL, 1)) if (!check_nsec3_coverage(header, plen, digest_len, digest, type, workspace1, workspace2, nsecs, nsec_count, NULL, 1))
return 0; return DNSSEC_FAIL_NONSEC;
/* Finally, check that there's no seat of wildcard synthesis */ /* Finally, check that there's no seat of wildcard synthesis */
if (!wildname) if (!wildname)
@@ -1572,17 +1645,22 @@ static int prove_non_existence_nsec3(struct dns_header *header, size_t plen, uns
wildcard--; wildcard--;
*wildcard = '*'; *wildcard = '*';
if (dec_counter(validate_counter, NULL))
return DNSSEC_FAIL_WORK;
if ((digest_len = hash_name(wildcard, &digest, hash, salt, salt_len, iterations)) == 0) if ((digest_len = hash_name(wildcard, &digest, hash, salt, salt_len, iterations)) == 0)
return 0; return DNSSEC_FAIL_NONSEC;
if (!check_nsec3_coverage(header, plen, digest_len, digest, type, workspace1, workspace2, nsecs, nsec_count, NULL, 1)) if (!check_nsec3_coverage(header, plen, digest_len, digest, type, workspace1, workspace2, nsecs, nsec_count, NULL, 1))
return 0; return DNSSEC_FAIL_NONSEC;
} }
return 1; return 0;
} }
static int prove_non_existence(struct dns_header *header, size_t plen, char *keyname, char *name, int qtype, int qclass, char *wildname, int *nons, int *nsec_ttl) /* returns 0 on success, or DNSSEC_FAIL_* value on failure. */
static int prove_non_existence(struct dns_header *header, size_t plen, char *keyname, char *name, int qtype, int qclass,
char *wildname, int *nons, int *nsec_ttl, int *validate_counter)
{ {
static unsigned char **nsecset = NULL, **rrsig_labels = NULL; static unsigned char **nsecset = NULL, **rrsig_labels = NULL;
static int nsecset_sz = 0, rrsig_labels_sz = 0; static int nsecset_sz = 0, rrsig_labels_sz = 0;
@@ -1594,7 +1672,7 @@ static int prove_non_existence(struct dns_header *header, size_t plen, char *key
/* Move to NS section */ /* Move to NS section */
if (!p || !(p = skip_section(p, ntohs(header->ancount), header, plen))) if (!p || !(p = skip_section(p, ntohs(header->ancount), header, plen)))
return 0; return DNSSEC_FAIL_BADPACKET;
auth_start = p; auth_start = p;
@@ -1603,13 +1681,16 @@ static int prove_non_existence(struct dns_header *header, size_t plen, char *key
unsigned char *pstart = p; unsigned char *pstart = p;
if (!extract_name(header, plen, &p, daemon->workspacename, 1, 10)) if (!extract_name(header, plen, &p, daemon->workspacename, 1, 10))
return 0; return DNSSEC_FAIL_BADPACKET;
GETSHORT(type, p); GETSHORT(type, p);
GETSHORT(class, p); GETSHORT(class, p);
GETLONG(ttl, p); GETLONG(ttl, p);
GETSHORT(rdlen, p); GETSHORT(rdlen, p);
if (!CHECK_LEN(header, p, plen, rdlen))
return DNSSEC_FAIL_BADPACKET;
if (class == qclass && (type == T_NSEC || type == T_NSEC3)) if (class == qclass && (type == T_NSEC || type == T_NSEC3))
{ {
if (nsec_ttl) if (nsec_ttl)
@@ -1622,12 +1703,12 @@ static int prove_non_existence(struct dns_header *header, size_t plen, char *key
/* No mixed NSECing 'round here, thankyouverymuch */ /* No mixed NSECing 'round here, thankyouverymuch */
if (type_found != 0 && type_found != type) if (type_found != 0 && type_found != type)
return 0; return DNSSEC_FAIL_NONSEC;
type_found = type; type_found = type;
if (!expand_workspace(&nsecset, &nsecset_sz, nsecs_found)) if (!expand_workspace(&nsecset, &nsecset_sz, nsecs_found))
return 0; return DNSSEC_FAIL_BADPACKET;
if (type == T_NSEC) if (type == T_NSEC)
{ {
@@ -1642,30 +1723,33 @@ static int prove_non_existence(struct dns_header *header, size_t plen, char *key
int res, j, rdlen1, type1, class1; int res, j, rdlen1, type1, class1;
if (!expand_workspace(&rrsig_labels, &rrsig_labels_sz, nsecs_found)) if (!expand_workspace(&rrsig_labels, &rrsig_labels_sz, nsecs_found))
return 0; return DNSSEC_FAIL_BADPACKET;
rrsig_labels[nsecs_found] = NULL; rrsig_labels[nsecs_found] = NULL;
for (j = ntohs(header->nscount); j != 0; j--) for (j = ntohs(header->nscount); j != 0; j--)
{ {
unsigned char *psav;
if (!(res = extract_name(header, plen, &p1, daemon->workspacename, 0, 10))) if (!(res = extract_name(header, plen, &p1, daemon->workspacename, 0, 10)))
return 0; return DNSSEC_FAIL_BADPACKET;
GETSHORT(type1, p1); GETSHORT(type1, p1);
GETSHORT(class1, p1); GETSHORT(class1, p1);
p1 += 4; /* TTL */ p1 += 4; /* TTL */
GETSHORT(rdlen1, p1); GETSHORT(rdlen1, p1);
psav = p1;
if (!CHECK_LEN(header, p1, plen, rdlen1)) if (!CHECK_LEN(header, p1, plen, rdlen1))
return 0; return DNSSEC_FAIL_BADPACKET;
if (res == 1 && class1 == qclass && type1 == T_RRSIG) if (res == 1 && class1 == qclass && type1 == T_RRSIG)
{ {
int type_covered; int type_covered;
unsigned char *psav = p1;
if (rdlen1 < 18) if (rdlen1 < 18)
return 0; /* bad packet */ return DNSSEC_FAIL_BADPACKET; /* bad packet */
GETSHORT(type_covered, p1); GETSHORT(type_covered, p1);
@@ -1677,33 +1761,56 @@ static int prove_non_existence(struct dns_header *header, size_t plen, char *key
if (!rrsig_labels[nsecs_found]) if (!rrsig_labels[nsecs_found])
rrsig_labels[nsecs_found] = p1; rrsig_labels[nsecs_found] = p1;
else if (*rrsig_labels[nsecs_found] != *p1) /* algo */ else if (*rrsig_labels[nsecs_found] != *p1) /* algo */
return 0; return DNSSEC_FAIL_NONSEC;
} }
p1 = psav;
} }
if (!ADD_RDLEN(header, p1, plen, rdlen1)) p1 = psav + rdlen1;
return 0;
} }
/* Must have found at least one sig. */ /* Must have found at least one sig. */
if (!rrsig_labels[nsecs_found]) if (!rrsig_labels[nsecs_found])
return 0; return DNSSEC_FAIL_NONSEC;
}
else if (type == T_NSEC3)
{
/* Decode the packet structure enough to check that rdlen is big enough
to contain everything other than the type bitmap.
(packet checked to be long enough to contain rdlen above)
We don't need to do any further length checks in check_nes3_coverage()
or prove_non_existence_nsec3() */
int salt_len, hash_len;
unsigned char *psav = p;
if (rdlen < 5)
return DNSSEC_FAIL_BADPACKET;
p += 4; /* algo, flags, iterations */
salt_len = *p++; /* salt_len */
if (rdlen < (6 + salt_len))
return DNSSEC_FAIL_BADPACKET; /* check up to hash_length */
p += salt_len; /* salt */
hash_len = *p++;
if (rdlen < (6 + salt_len + hash_len))
return DNSSEC_FAIL_BADPACKET; /* check to end of next hashed name */
p = psav;
} }
nsecset[nsecs_found++] = pstart; nsecset[nsecs_found++] = pstart;
} }
if (!ADD_RDLEN(header, p, plen, rdlen)) p += rdlen;
return 0;
} }
if (type_found == T_NSEC) if (type_found == T_NSEC)
return prove_non_existence_nsec(header, plen, nsecset, rrsig_labels, 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) else if (type_found == T_NSEC3)
return prove_non_existence_nsec3(header, plen, nsecset, nsecs_found, daemon->workspacename, keyname, name, qtype, wildname, nons); return prove_non_existence_nsec3(header, plen, nsecset, nsecs_found, daemon->workspacename, keyname, name, qtype, wildname, nons, validate_counter);
else else
return 0; return DNSSEC_FAIL_NONSEC;
} }
/* Check signing status of name. /* Check signing status of name.
@@ -1798,16 +1905,17 @@ static int zone_status(char *name, int class, char *keyname, time_t now)
STAT_BOGUS signature is wrong, bad packet, no validation where there should be. STAT_BOGUS signature is wrong, bad packet, no validation where there should be.
STAT_NEED_KEY need DNSKEY to complete validation (name is returned in keyname, class in *class) STAT_NEED_KEY need DNSKEY to complete validation (name is returned in keyname, class in *class)
STAT_NEED_DS need DS to complete validation (name is returned in keyname) STAT_NEED_DS need DS to complete validation (name is returned in keyname)
STAT_ABANDONED resource exhaustion.
daemon->rr_status points to a char array which corressponds to the RRs in the daemon->rr_status points to a char array which corressponds to the RRs in the
answer and auth sections. This is set to >1 for each RR which is validated, and 0 for any which aren't. answer and auth sections. This is set to >1 for each RR which is validated, and 0 for any which aren't.
When validating replies to DS records, we're only interested in the NSEC{3} RRs in the auth section. When validating replies to DS records, we're only interested in the NSEC{3} RRs in the auth section.
Other RRs in that section missing sigs will not cause am INSECURE reply. We determine this mode Other RRs in that section missing sigs will not cause am INSECURE reply. We determine this mode
is the nons argument is non-NULL. if the nons argument is non-NULL.
*/ */
int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname, int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, char *name, char *keyname,
int *class, int check_unsigned, int *neganswer, int *nons, int *nsec_ttl) int *class, int check_unsigned, int *neganswer, int *nons, int *nsec_ttl, int *validate_counter)
{ {
static unsigned char **targets = NULL; static unsigned char **targets = NULL;
static int target_sz = 0; static int target_sz = 0;
@@ -1816,7 +1924,7 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch
int type1, class1, rdlen1 = 0, type2, class2, rdlen2, qclass, qtype, targetidx; int type1, class1, rdlen1 = 0, type2, class2, rdlen2, qclass, qtype, targetidx;
int i, j, rc = STAT_INSECURE; int i, j, rc = STAT_INSECURE;
int secure = STAT_SECURE; int secure = STAT_SECURE;
int rc_nsec;
/* extend rr_status if necessary */ /* extend rr_status if necessary */
if (daemon->rr_status_sz < ntohs(header->ancount) + ntohs(header->nscount)) if (daemon->rr_status_sz < ntohs(header->ancount) + ntohs(header->nscount))
{ {
@@ -1982,9 +2090,9 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch
{ {
unsigned long sig_ttl; unsigned long sig_ttl;
rc = validate_rrset(now, header, plen, class1, type1, sigcnt, rc = validate_rrset(now, header, plen, class1, type1, sigcnt,
rrcnt, name, keyname, &wildname, NULL, 0, 0, 0, &sig_ttl); rrcnt, name, keyname, &wildname, NULL, 0, 0, 0, &sig_ttl, validate_counter);
if (STAT_ISEQUAL(rc, STAT_BOGUS) || STAT_ISEQUAL(rc, STAT_NEED_KEY) || STAT_ISEQUAL(rc, STAT_NEED_DS)) if (STAT_ISEQUAL(rc, STAT_BOGUS) || STAT_ISEQUAL(rc, STAT_NEED_KEY) || STAT_ISEQUAL(rc, STAT_NEED_DS) || STAT_ISEQUAL(rc, STAT_ABANDONED))
{ {
if (class) if (class)
*class = class1; /* Class for DS or DNSKEY */ *class = class1; /* Class for DS or DNSKEY */
@@ -2018,8 +2126,8 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch
That's not a problem since if the RRsets later fail That's not a problem since if the RRsets later fail
we'll return BOGUS then. */ we'll return BOGUS then. */
if (STAT_ISEQUAL(rc, STAT_SECURE_WILDCARD) && if (STAT_ISEQUAL(rc, STAT_SECURE_WILDCARD) &&
!prove_non_existence(header, plen, keyname, name, type1, class1, wildname, NULL, NULL)) ((rc_nsec = prove_non_existence(header, plen, keyname, name, type1, class1, wildname, NULL, NULL, validate_counter))) != 0)
return STAT_BOGUS | DNSSEC_FAIL_NONSEC; return (rc_nsec & DNSSEC_FAIL_WORK) ? STAT_ABANDONED : (STAT_BOGUS | rc_nsec);
rc = STAT_SECURE; rc = STAT_SECURE;
} }
@@ -2044,20 +2152,24 @@ int dnssec_validate_reply(time_t now, struct dns_header *header, size_t plen, ch
/* For anything other than a DS record, this situation is OK if either /* For anything other than a DS record, this situation is OK if either
the answer is in an unsigned zone, or there's a NSEC records. */ the answer is in an unsigned zone, or there's a NSEC records. */
if (!prove_non_existence(header, plen, keyname, name, qtype, qclass, NULL, nons, nsec_ttl)) if ((rc_nsec = prove_non_existence(header, plen, keyname, name, qtype, qclass, NULL, nons, nsec_ttl, validate_counter)) != 0)
{ {
if (rc_nsec & DNSSEC_FAIL_WORK)
return STAT_ABANDONED;
/* Empty DS without NSECS */ /* Empty DS without NSECS */
if (qtype == T_DS) if (qtype == T_DS)
return STAT_BOGUS | DNSSEC_FAIL_NONSEC; return STAT_BOGUS | rc_nsec;
if (!STAT_ISEQUAL((rc = zone_status(name, qclass, keyname, now)), STAT_SECURE)) if ((rc_nsec & (DNSSEC_FAIL_NONSEC | DNSSEC_FAIL_NSEC3_ITERS)) &&
!STAT_ISEQUAL((rc = zone_status(name, qclass, keyname, now)), STAT_SECURE))
{ {
if (class) if (class)
*class = qclass; /* Class for NEED_DS or NEED_KEY */ *class = qclass; /* Class for NEED_DS or NEED_KEY */
return rc; return rc;
} }
return STAT_BOGUS | DNSSEC_FAIL_NONSEC; /* signed zone, no NSECs */ return STAT_BOGUS | rc_nsec; /* signed zone, no NSECs */
} }
} }
@@ -2139,6 +2251,8 @@ int errflags_to_ede(int status)
return EDE_NO_DNSKEY; return EDE_NO_DNSKEY;
else if (status & DNSSEC_FAIL_NODSSUP) else if (status & DNSSEC_FAIL_NODSSUP)
return EDE_USUPDS; return EDE_USUPDS;
else if (status & DNSSEC_FAIL_NSEC3_ITERS)
return EDE_UNS_NS3_ITER;
else if (status & DNSSEC_FAIL_NONSEC) else if (status & DNSSEC_FAIL_NONSEC)
return EDE_NO_NSEC; return EDE_NO_NSEC;
else if (status & DNSSEC_FAIL_INDET) else if (status & DNSSEC_FAIL_INDET)
+131 -79
View File
@@ -338,7 +338,8 @@ static int forward_query(int udpfd, union mysockaddr *udpaddr,
if (ad_reqd) if (ad_reqd)
forward->flags |= FREC_AD_QUESTION; forward->flags |= FREC_AD_QUESTION;
#ifdef HAVE_DNSSEC #ifdef HAVE_DNSSEC
forward->work_counter = DNSSEC_WORK; forward->work_counter = daemon->limit[LIMIT_WORK];
forward->validate_counter = daemon->limit[LIMIT_CRYPTO];
if (do_bit) if (do_bit)
forward->flags |= FREC_DO_QUESTION; forward->flags |= FREC_DO_QUESTION;
#endif #endif
@@ -687,13 +688,12 @@ static size_t process_reply(struct dns_header *header, time_t now, struct server
{ {
unsigned char *pheader, *sizep; unsigned char *pheader, *sizep;
struct ipsets *ipsets = NULL, *nftsets = NULL; struct ipsets *ipsets = NULL, *nftsets = NULL;
int munged = 0, is_sign; int is_sign;
unsigned int rcode = RCODE(header); unsigned int rcode = RCODE(header);
size_t plen; size_t plen;
(void)ad_reqd; (void)ad_reqd;
(void)do_bit; (void)do_bit;
(void)bogusanswer;
#ifdef HAVE_IPSET #ifdef HAVE_IPSET
if (daemon->ipsets && extract_request(header, n, daemon->namebuff, NULL)) if (daemon->ipsets && extract_request(header, n, daemon->namebuff, NULL))
@@ -782,10 +782,38 @@ static size_t process_reply(struct dns_header *header, time_t now, struct server
server->flags |= SERV_WARNED_RECURSIVE; server->flags |= SERV_WARNED_RECURSIVE;
} }
if (header->hb3 & HB3_TC)
{
log_query(F_UPSTREAM, NULL, NULL, "truncated", 0);
header->ancount = htons(0);
header->nscount = htons(0);
header->arcount = htons(0);
}
if (!(header->hb3 & HB3_TC) && (!bogusanswer || (header->hb4 & HB4_CD)))
{
if (rcode == NXDOMAIN && extract_request(header, n, daemon->namebuff, NULL) &&
(check_for_local_domain(daemon->namebuff, now) || lookup_domain(daemon->namebuff, F_CONFIG, NULL, NULL)))
{
/* if we forwarded a query for a locally known name (because it was for
an unknown type) and the answer is NXDOMAIN, convert that to NODATA,
since we know that the domain exists, even if upstream doesn't */
header->hb3 |= HB3_AA;
SET_RCODE(header, NOERROR);
cache_secure = 0;
}
if (daemon->doctors && do_doctor(header, n, daemon->namebuff))
cache_secure = 0;
/* check_for_bogus_wildcard() does it's own caching, so
don't call extract_addresses() if it triggers. */
if (daemon->bogus_addr && rcode != NXDOMAIN && if (daemon->bogus_addr && rcode != NXDOMAIN &&
check_for_bogus_wildcard(header, n, daemon->namebuff, now)) check_for_bogus_wildcard(header, n, daemon->namebuff, now))
{ {
munged = 1; header->ancount = htons(0);
header->nscount = htons(0);
header->arcount = htons(0);
SET_RCODE(header, NXDOMAIN); SET_RCODE(header, NXDOMAIN);
header->hb3 &= ~HB3_AA; header->hb3 &= ~HB3_AA;
cache_secure = 0; cache_secure = 0;
@@ -793,62 +821,50 @@ static size_t process_reply(struct dns_header *header, time_t now, struct server
} }
else else
{ {
int doctored = 0; int rc = extract_addresses(header, n, daemon->namebuff, now, ipsets, nftsets, is_sign, check_rebind, no_cache, cache_secure);
if (rcode == NXDOMAIN && if (rc != 0)
extract_request(header, n, daemon->namebuff, NULL))
{ {
if (check_for_local_domain(daemon->namebuff, now) || header->ancount = htons(0);
lookup_domain(daemon->namebuff, F_CONFIG, NULL, NULL)) header->nscount = htons(0);
{ header->arcount = htons(0);
/* if we forwarded a query for a locally known name (because it was for
an unknown type) and the answer is NXDOMAIN, convert that to NODATA,
since we know that the domain exists, even if upstream doesn't */
munged = 1;
header->hb3 |= HB3_AA;
SET_RCODE(header, NOERROR);
cache_secure = 0; cache_secure = 0;
} }
}
switch (extract_addresses(header, n, daemon->namebuff, now, ipsets, nftsets, is_sign, check_rebind, no_cache, cache_secure, &doctored)) if (rc == 1)
{ {
case 1:
my_syslog(LOG_WARNING, _("possible DNS-rebind attack detected: %s"), daemon->namebuff); my_syslog(LOG_WARNING, _("possible DNS-rebind attack detected: %s"), daemon->namebuff);
munged = 1;
cache_secure = 0;
ede = EDE_BLOCKED; ede = EDE_BLOCKED;
break;
/* extract_addresses() found a malformed answer. */
case 2:
munged = 1;
SET_RCODE(header, SERVFAIL);
cache_secure = 0;
ede = EDE_OTHER;
break;
} }
if (rcode == NOERROR && rrfilter(header, &n, RRFILTER_CONF) > 0) if (rc == 2)
ede = EDE_FILTERED; {
/* extract_addresses() found a malformed answer. */
SET_RCODE(header, SERVFAIL);
ede = EDE_OTHER;
}
}
if (doctored) if (RCODE(header) == NOERROR && rrfilter(header, &n, RRFILTER_CONF) > 0)
cache_secure = 0; ede = EDE_FILTERED;
} }
#ifdef HAVE_DNSSEC #ifdef HAVE_DNSSEC
if (bogusanswer && !(header->hb4 & HB4_CD) && !option_bool(OPT_DNSSEC_DEBUG)) if (option_bool(OPT_DNSSEC_VALID))
{
if (bogusanswer)
{
if (!(header->hb4 & HB4_CD) && !option_bool(OPT_DNSSEC_DEBUG))
{ {
/* Bogus reply, turn into SERVFAIL */ /* Bogus reply, turn into SERVFAIL */
SET_RCODE(header, SERVFAIL); SET_RCODE(header, SERVFAIL);
munged = 1; header->ancount = htons(0);
header->nscount = htons(0);
header->arcount = htons(0);
ede = EDE_DNSSEC_BOGUS;
} }
}
if (option_bool(OPT_DNSSEC_VALID)) else if (!(header->hb4 & HB4_CD) && ad_reqd && cache_secure)
{
header->hb4 &= ~HB4_AD;
if (!(header->hb4 & HB4_CD) && ad_reqd && cache_secure)
header->hb4 |= HB4_AD; header->hb4 |= HB4_AD;
/* If the requestor didn't set the DO bit, don't return DNSSEC info. */ /* If the requestor didn't set the DO bit, don't return DNSSEC info. */
@@ -857,19 +873,8 @@ static size_t process_reply(struct dns_header *header, time_t now, struct server
} }
#endif #endif
/* do this after extract_addresses. Ensure NODATA reply and remove /* the code above can elide sections of the packet. Find the new length here
nameserver info. */ and put back pseudoheader if it was removed. */
if (munged)
{
header->ancount = htons(0);
header->nscount = htons(0);
header->arcount = htons(0);
header->hb3 &= ~HB3_TC;
}
/* the bogus-nxdomain stuff, doctor and NXDOMAIN->NODATA munging can all elide
sections of the packet. Find the new length here and put back pseudoheader
if it was removed. */
n = resize_packet(header, n, pheader, plen); n = resize_packet(header, n, pheader, plen);
if (pheader && ede != EDE_UNSET) if (pheader && ede != EDE_UNSET)
@@ -888,6 +893,9 @@ static size_t process_reply(struct dns_header *header, time_t now, struct server
static void dnssec_validate(struct frec *forward, struct dns_header *header, static void dnssec_validate(struct frec *forward, struct dns_header *header,
ssize_t plen, int status, time_t now) ssize_t plen, int status, time_t now)
{ {
struct frec *orig;
int log_resource = 0;
daemon->log_display_id = forward->frec_src.log_id; daemon->log_display_id = forward->frec_src.log_id;
/* We've had a reply already, which we're validating. Ignore this duplicate */ /* We've had a reply already, which we're validating. Ignore this duplicate */
@@ -913,26 +921,27 @@ static void dnssec_validate(struct frec *forward, struct dns_header *header,
} }
} }
/* Find the original query that started it all.... */
for (orig = forward; orig->dependent; orig = orig->dependent);
/* As soon as anything returns BOGUS, we stop and unwind, to do otherwise /* As soon as anything returns BOGUS, we stop and unwind, to do otherwise
would invite infinite loops, since the answers to DNSKEY and DS queries would invite infinite loops, since the answers to DNSKEY and DS queries
will not be cached, so they'll be repeated. */ will not be cached, so they'll be repeated. */
if (!STAT_ISEQUAL(status, STAT_BOGUS) && !STAT_ISEQUAL(status, STAT_TRUNCATED) && !STAT_ISEQUAL(status, STAT_ABANDONED)) if (!STAT_ISEQUAL(status, STAT_BOGUS) && !STAT_ISEQUAL(status, STAT_TRUNCATED) && !STAT_ISEQUAL(status, STAT_ABANDONED))
{ {
if (forward->flags & FREC_DNSKEY_QUERY) if (forward->flags & FREC_DNSKEY_QUERY)
status = dnssec_validate_by_ds(now, header, plen, daemon->namebuff, daemon->keyname, forward->class); status = dnssec_validate_by_ds(now, header, plen, daemon->namebuff, daemon->keyname, forward->class, &orig->validate_counter);
else if (forward->flags & FREC_DS_QUERY) else if (forward->flags & FREC_DS_QUERY)
status = dnssec_validate_ds(now, header, plen, daemon->namebuff, daemon->keyname, forward->class); status = dnssec_validate_ds(now, header, plen, daemon->namebuff, daemon->keyname, forward->class, &orig->validate_counter);
else else
status = dnssec_validate_reply(now, header, plen, daemon->namebuff, daemon->keyname, &forward->class, status = dnssec_validate_reply(now, header, plen, daemon->namebuff, daemon->keyname, &forward->class,
!option_bool(OPT_DNSSEC_IGN_NS) && (forward->sentto->flags & SERV_DO_DNSSEC), !option_bool(OPT_DNSSEC_IGN_NS) && (forward->sentto->flags & SERV_DO_DNSSEC),
NULL, NULL, NULL); NULL, NULL, NULL, &orig->validate_counter);
#ifdef HAVE_DUMPFILE
if (STAT_ISEQUAL(status, STAT_BOGUS))
dump_packet_udp((forward->flags & (FREC_DNSKEY_QUERY | FREC_DS_QUERY)) ? DUMP_SEC_BOGUS : DUMP_BOGUS,
header, (size_t)plen, &forward->sentto->addr, NULL, -daemon->port);
#endif
} }
if (STAT_ISEQUAL(status, STAT_ABANDONED))
log_resource = 1;
/* Can't validate, as we're missing key data. Put this /* Can't validate, as we're missing key data. Put this
answer aside, whilst we get that. */ answer aside, whilst we get that. */
if (STAT_ISEQUAL(status, STAT_NEED_DS) || STAT_ISEQUAL(status, STAT_NEED_KEY)) if (STAT_ISEQUAL(status, STAT_NEED_DS) || STAT_ISEQUAL(status, STAT_NEED_KEY))
@@ -975,18 +984,19 @@ static void dnssec_validate(struct frec *forward, struct dns_header *header,
return; return;
} }
} }
else if (orig->work_counter-- == 0)
{
my_syslog(LOG_WARNING, _("limit exceeded: per-query subqueries"));
log_resource = 1;
}
else else
{ {
struct server *server; struct server *server;
struct frec *orig;
void *hash; void *hash;
size_t nn; size_t nn;
int serverind, fd; int serverind, fd;
struct randfd_list *rfds = NULL; struct randfd_list *rfds = NULL;
/* Find the original query that started it all.... */
for (orig = forward; orig->dependent; orig = orig->dependent);
/* Make sure we don't expire and free the orig frec during the /* Make sure we don't expire and free the orig frec during the
allocation of a new one: third arg of get_new_frec() does that. */ allocation of a new one: third arg of get_new_frec() does that. */
if ((serverind = dnssec_server(forward->sentto, daemon->keyname, NULL, NULL)) != -1 && if ((serverind = dnssec_server(forward->sentto, daemon->keyname, NULL, NULL)) != -1 &&
@@ -995,7 +1005,6 @@ static void dnssec_validate(struct frec *forward, struct dns_header *header,
daemon->keyname, forward->class, daemon->keyname, forward->class,
STAT_ISEQUAL(status, STAT_NEED_KEY) ? T_DNSKEY : T_DS, server->edns_pktsz)) && STAT_ISEQUAL(status, STAT_NEED_KEY) ? T_DNSKEY : T_DS, server->edns_pktsz)) &&
(hash = hash_questions(header, nn, daemon->namebuff)) && (hash = hash_questions(header, nn, daemon->namebuff)) &&
--orig->work_counter != 0 &&
(fd = allocate_rfd(&rfds, server)) != -1 && (fd = allocate_rfd(&rfds, server)) != -1 &&
(new = get_new_frec(now, server, 1))) (new = get_new_frec(now, server, 1)))
{ {
@@ -1061,6 +1070,21 @@ static void dnssec_validate(struct frec *forward, struct dns_header *header,
status = STAT_ABANDONED; status = STAT_ABANDONED;
} }
if (log_resource)
{
/* Log the actual validation that made us barf. */
unsigned char *p = (unsigned char *)(header+1);
if (extract_name(header, plen, &p, daemon->namebuff, 0, 4) == 1)
my_syslog(LOG_WARNING, _("validation of %s failed: resource limit exceeded."),
daemon->namebuff[0] ? daemon->namebuff : ".");
}
#ifdef HAVE_DUMPFILE
if (STAT_ISEQUAL(status, STAT_BOGUS) || STAT_ISEQUAL(status, STAT_ABANDONED))
dump_packet_udp((forward->flags & (FREC_DNSKEY_QUERY | FREC_DS_QUERY)) ? DUMP_SEC_BOGUS : DUMP_BOGUS,
header, (size_t)plen, &forward->sentto->addr, NULL, -daemon->port);
#endif
/* Validated original answer, all done. */ /* Validated original answer, all done. */
if (!forward->dependent) if (!forward->dependent)
return_reply(now, forward, header, plen, status); return_reply(now, forward, header, plen, status);
@@ -1333,6 +1357,12 @@ static void return_reply(time_t now, struct frec *forward, struct dns_header *he
log_query(F_SECSTAT, domain, &a, result, 0); log_query(F_SECSTAT, domain, &a, result, 0);
} }
} }
if ((daemon->limit[LIMIT_CRYPTO] - forward->validate_counter) > (int)daemon->metrics[METRIC_CRYPTO_HWM])
daemon->metrics[METRIC_CRYPTO_HWM] = daemon->limit[LIMIT_CRYPTO] - forward->validate_counter;
if ((daemon->limit[LIMIT_WORK] - forward->work_counter) > (int)daemon->metrics[METRIC_WORK_HWM])
daemon->metrics[METRIC_WORK_HWM] = daemon->limit[LIMIT_WORK] - forward->work_counter;
#endif #endif
if (option_bool(OPT_NO_REBIND)) if (option_bool(OPT_NO_REBIND))
@@ -2014,7 +2044,7 @@ static ssize_t tcp_talk(int first, int last, int start, unsigned char *packet,
/* Recurse down the key hierarchy */ /* Recurse down the key hierarchy */
static int tcp_key_recurse(time_t now, int status, struct dns_header *header, size_t n, static int tcp_key_recurse(time_t now, int status, struct dns_header *header, size_t n,
int class, char *name, char *keyname, struct server *server, int class, char *name, char *keyname, struct server *server,
int have_mark, unsigned int mark, int *keycount) int have_mark, unsigned int mark, int *keycount, int *validatecount)
{ {
int first, last, start, new_status; int first, last, start, new_status;
unsigned char *packet = NULL; unsigned char *packet = NULL;
@@ -2026,20 +2056,34 @@ static int tcp_key_recurse(time_t now, int status, struct dns_header *header, si
int log_save; int log_save;
/* limit the amount of work we do, to avoid cycling forever on loops in the DNS */ /* limit the amount of work we do, to avoid cycling forever on loops in the DNS */
if (--(*keycount) == 0) if (STAT_ISEQUAL(status, STAT_NEED_KEY))
new_status = STAT_ABANDONED; new_status = dnssec_validate_by_ds(now, header, n, name, keyname, class, validatecount);
else if (STAT_ISEQUAL(status, STAT_NEED_KEY))
new_status = dnssec_validate_by_ds(now, header, n, name, keyname, class);
else if (STAT_ISEQUAL(status, STAT_NEED_DS)) else if (STAT_ISEQUAL(status, STAT_NEED_DS))
new_status = dnssec_validate_ds(now, header, n, name, keyname, class); new_status = dnssec_validate_ds(now, header, n, name, keyname, class, validatecount);
else else
new_status = dnssec_validate_reply(now, header, n, name, keyname, &class, new_status = dnssec_validate_reply(now, header, n, name, keyname, &class,
!option_bool(OPT_DNSSEC_IGN_NS) && (server->flags & SERV_DO_DNSSEC), !option_bool(OPT_DNSSEC_IGN_NS) && (server->flags & SERV_DO_DNSSEC),
NULL, NULL, NULL); NULL, NULL, NULL, validatecount);
if (!STAT_ISEQUAL(new_status, STAT_NEED_DS) && !STAT_ISEQUAL(new_status, STAT_NEED_KEY)) if (!STAT_ISEQUAL(new_status, STAT_NEED_DS) && !STAT_ISEQUAL(new_status, STAT_NEED_KEY) && !STAT_ISEQUAL(new_status, STAT_ABANDONED))
break; break;
if ((*keycount)-- == 0)
{
my_syslog(LOG_WARNING, _("limit exceeded: per-query subqueries"));
new_status = STAT_ABANDONED;
}
if (STAT_ISEQUAL(new_status, STAT_ABANDONED))
{
/* Log the actual validation that made us barf. */
unsigned char *p = (unsigned char *)(header+1);
if (extract_name(header, n, &p, daemon->namebuff, 0, 4) == 1)
my_syslog(LOG_WARNING, _("validation of %s failed: resource limit exceeded."),
daemon->namebuff[0] ? daemon->namebuff : ".");
break;
}
/* Can't validate because we need a key/DS whose name now in keyname. /* Can't validate because we need a key/DS whose name now in keyname.
Make query for same, and recurse to validate */ Make query for same, and recurse to validate */
if (!packet) if (!packet)
@@ -2070,7 +2114,8 @@ static int tcp_key_recurse(time_t now, int status, struct dns_header *header, si
log_query_mysockaddr(F_NOEXTRA | F_DNSSEC | F_SERVER, keyname, &server->addr, log_query_mysockaddr(F_NOEXTRA | F_DNSSEC | F_SERVER, keyname, &server->addr,
STAT_ISEQUAL(new_status, STAT_NEED_KEY) ? "dnssec-query[DNSKEY]" : "dnssec-query[DS]", 0); STAT_ISEQUAL(new_status, STAT_NEED_KEY) ? "dnssec-query[DNSKEY]" : "dnssec-query[DS]", 0);
new_status = tcp_key_recurse(now, new_status, new_header, m, class, name, keyname, server, have_mark, mark, keycount); new_status = tcp_key_recurse(now, new_status, new_header, m, class, name, keyname, server,
have_mark, mark, keycount, validatecount);
daemon->log_display_id = log_save; daemon->log_display_id = log_save;
@@ -2375,9 +2420,10 @@ unsigned char *tcp_request(int confd, time_t now,
#ifdef HAVE_DNSSEC #ifdef HAVE_DNSSEC
if (option_bool(OPT_DNSSEC_VALID) && !checking_disabled && (master->flags & SERV_DO_DNSSEC)) if (option_bool(OPT_DNSSEC_VALID) && !checking_disabled && (master->flags & SERV_DO_DNSSEC))
{ {
int keycount = DNSSEC_WORK; /* Limit to number of DNSSEC questions, to catch loops and avoid filling cache. */ int keycount = daemon->limit[LIMIT_WORK]; /* Limit to number of DNSSEC questions, to catch loops and avoid filling cache. */
int validatecount = daemon->limit[LIMIT_CRYPTO];
int status = tcp_key_recurse(now, STAT_OK, header, m, 0, daemon->namebuff, daemon->keyname, int status = tcp_key_recurse(now, STAT_OK, header, m, 0, daemon->namebuff, daemon->keyname,
serv, have_mark, mark, &keycount); serv, have_mark, mark, &keycount, &validatecount);
char *result, *domain = "result"; char *result, *domain = "result";
union all_addr a; union all_addr a;
@@ -2403,6 +2449,12 @@ unsigned char *tcp_request(int confd, time_t now,
} }
log_query(F_SECSTAT, domain, &a, result, 0); log_query(F_SECSTAT, domain, &a, result, 0);
if ((daemon->limit[LIMIT_CRYPTO] - validatecount) > (int)daemon->metrics[METRIC_CRYPTO_HWM])
daemon->metrics[METRIC_CRYPTO_HWM] = daemon->limit[LIMIT_CRYPTO] - validatecount;
if ((daemon->limit[LIMIT_WORK] - keycount) > (int)daemon->metrics[METRIC_WORK_HWM])
daemon->metrics[METRIC_WORK_HWM] = daemon->limit[LIMIT_WORK] - keycount;
} }
#endif #endif
+1 -1
View File
@@ -94,7 +94,7 @@ void inotify_dnsmasq_init()
if (daemon->inotifyfd == -1) if (daemon->inotifyfd == -1)
die(_("failed to create inotify: %s"), NULL, EC_MISC); die(_("failed to create inotify: %s"), NULL, EC_MISC);
if (option_bool(OPT_NO_RESOLV)) if (daemon->port == 0 || option_bool(OPT_NO_RESOLV))
return; return;
for (res = daemon->resolv_files; res; res = res->next) for (res = daemon->resolv_files; res; res = res->next)
+3
View File
@@ -24,6 +24,9 @@ const char * metric_names[] = {
"dns_local_answered", "dns_local_answered",
"dns_stale_answered", "dns_stale_answered",
"dns_unanswered", "dns_unanswered",
"dnssec_max_crypto_use",
"dnssec_max_sig_fail",
"dnssec_max_work",
"bootp", "bootp",
"pxe", "pxe",
"dhcp_ack", "dhcp_ack",
+3
View File
@@ -23,6 +23,9 @@ enum {
METRIC_DNS_LOCAL_ANSWERED, METRIC_DNS_LOCAL_ANSWERED,
METRIC_DNS_STALE_ANSWERED, METRIC_DNS_STALE_ANSWERED,
METRIC_DNS_UNANSWERED_QUERY, METRIC_DNS_UNANSWERED_QUERY,
METRIC_CRYPTO_HWM,
METRIC_SIG_FAIL_HWM,
METRIC_WORK_HWM,
METRIC_BOOTP, METRIC_BOOTP,
METRIC_PXE, METRIC_PXE,
METRIC_DHCPACK, METRIC_DHCPACK,
+28 -1
View File
@@ -191,6 +191,7 @@ struct myoption {
#define LOPT_NO_DHCP6 382 #define LOPT_NO_DHCP6 382
#define LOPT_NO_DHCP4 383 #define LOPT_NO_DHCP4 383
#define LOPT_MAX_PROCS 384 #define LOPT_MAX_PROCS 384
#define LOPT_DNSSEC_LIMITS 385
#ifdef HAVE_GETOPT_LONG #ifdef HAVE_GETOPT_LONG
static const struct option opts[] = static const struct option opts[] =
@@ -364,6 +365,7 @@ static const struct myoption opts[] =
{ "dnssec-check-unsigned", 2, 0, LOPT_DNSSEC_CHECK }, { "dnssec-check-unsigned", 2, 0, LOPT_DNSSEC_CHECK },
{ "dnssec-no-timecheck", 0, 0, LOPT_DNSSEC_TIME }, { "dnssec-no-timecheck", 0, 0, LOPT_DNSSEC_TIME },
{ "dnssec-timestamp", 1, 0, LOPT_DNSSEC_STAMP }, { "dnssec-timestamp", 1, 0, LOPT_DNSSEC_STAMP },
{ "dnssec-limits", 1, 0, LOPT_DNSSEC_LIMITS },
{ "dhcp-relay", 1, 0, LOPT_RELAY }, { "dhcp-relay", 1, 0, LOPT_RELAY },
{ "ra-param", 1, 0, LOPT_RA_PARAM }, { "ra-param", 1, 0, LOPT_RA_PARAM },
{ "quiet-dhcp", 0, 0, LOPT_QUIET_DHCP }, { "quiet-dhcp", 0, 0, LOPT_QUIET_DHCP },
@@ -568,6 +570,7 @@ static struct {
{ LOPT_DNSSEC_CHECK, ARG_DUP, NULL, gettext_noop("Ensure answers without DNSSEC are in unsigned zones."), NULL }, { LOPT_DNSSEC_CHECK, ARG_DUP, NULL, gettext_noop("Ensure answers without DNSSEC are in unsigned zones."), NULL },
{ LOPT_DNSSEC_TIME, OPT_DNSSEC_TIME, NULL, gettext_noop("Don't check DNSSEC signature timestamps until first cache-reload"), NULL }, { LOPT_DNSSEC_TIME, OPT_DNSSEC_TIME, NULL, gettext_noop("Don't check DNSSEC signature timestamps until first cache-reload"), NULL },
{ LOPT_DNSSEC_STAMP, ARG_ONE, "<path>", gettext_noop("Timestamp file to verify system clock for DNSSEC"), NULL }, { LOPT_DNSSEC_STAMP, ARG_ONE, "<path>", gettext_noop("Timestamp file to verify system clock for DNSSEC"), NULL },
{ LOPT_DNSSEC_LIMITS, ARG_ONE, "<limit>,..", gettext_noop("Set resource limits for DNSSEC validation"), NULL },
{ LOPT_RA_PARAM, ARG_DUP, "<iface>,[mtu:<value>|<interface>|off,][<prio>,]<intval>[,<lifetime>]", gettext_noop("Set MTU, priority, resend-interval and router-lifetime"), NULL }, { LOPT_RA_PARAM, ARG_DUP, "<iface>,[mtu:<value>|<interface>|off,][<prio>,]<intval>[,<lifetime>]", gettext_noop("Set MTU, priority, resend-interval and router-lifetime"), NULL },
{ LOPT_QUIET_DHCP, OPT_QUIET_DHCP, NULL, gettext_noop("Do not log routine DHCP."), NULL }, { LOPT_QUIET_DHCP, OPT_QUIET_DHCP, NULL, gettext_noop("Do not log routine DHCP."), NULL },
{ LOPT_QUIET_DHCP6, OPT_QUIET_DHCP6, NULL, gettext_noop("Do not log routine DHCPv6."), NULL }, { LOPT_QUIET_DHCP6, OPT_QUIET_DHCP6, NULL, gettext_noop("Do not log routine DHCPv6."), NULL },
@@ -5258,6 +5261,24 @@ err:
} }
#ifdef HAVE_DNSSEC #ifdef HAVE_DNSSEC
case LOPT_DNSSEC_LIMITS:
{
int lim, val;
for (lim = LIMIT_SIG_FAIL; arg && lim < LIMIT_MAX ; lim++, arg = comma)
{
comma = split(arg);
if (!atoi_check(arg, &val))
ret_err(gen_err);
if (val != 0)
daemon->limit[lim] = val;
}
break;
}
case LOPT_DNSSEC_STAMP: /* --dnssec-timestamp */ case LOPT_DNSSEC_STAMP: /* --dnssec-timestamp */
daemon->timestamp_file = opt_string_alloc(arg); daemon->timestamp_file = opt_string_alloc(arg);
break; break;
@@ -5844,6 +5865,7 @@ void read_opts(int argc, char **argv, char *compile_opts)
daemon = opt_malloc(sizeof(struct daemon)); daemon = opt_malloc(sizeof(struct daemon));
memset(daemon, 0, sizeof(struct daemon)); memset(daemon, 0, sizeof(struct daemon));
daemon->namebuff = buff; daemon->namebuff = buff;
daemon->workspacename = safe_malloc((MAXDNAME * 2) + 1);
daemon->addrbuff = safe_malloc(ADDRSTRLEN); daemon->addrbuff = safe_malloc(ADDRSTRLEN);
/* Set defaults - everything else is zero or NULL */ /* Set defaults - everything else is zero or NULL */
@@ -5868,7 +5890,12 @@ void read_opts(int argc, char **argv, char *compile_opts)
daemon->randport_limit = 1; daemon->randport_limit = 1;
daemon->host_index = SRC_AH; daemon->host_index = SRC_AH;
daemon->max_procs = MAX_PROCS; daemon->max_procs = MAX_PROCS;
daemon->max_procs_used = 0; #ifdef HAVE_DNSSEC
daemon->limit[LIMIT_SIG_FAIL] = DNSSEC_LIMIT_SIG_FAIL;
daemon->limit[LIMIT_CRYPTO] = DNSSEC_LIMIT_CRYPTO;
daemon->limit[LIMIT_WORK] = DNSSEC_LIMIT_WORK;
daemon->limit[LIMIT_NSEC3_ITERS] = DNSSEC_LIMIT_NSEC3_ITERS;
#endif
/* See comment above make_servers(). Optimises server-read code. */ /* See comment above make_servers(). Optimises server-read code. */
mark_servers(0); mark_servers(0);
+241 -120
View File
@@ -384,14 +384,23 @@ static int private_net6(struct in6_addr *a, int ban_localhost)
((u32 *)a)[0] == htonl(0x20010db8); /* RFC 6303 4.6 */ ((u32 *)a)[0] == htonl(0x20010db8); /* RFC 6303 4.6 */
} }
static unsigned char *do_doctor(unsigned char *p, int count, struct dns_header *header, size_t qlen, int *doctored) int do_doctor(struct dns_header *header, size_t qlen, char *namebuff)
{ {
unsigned char *p;
int i, qtype, qclass, rdlen; int i, qtype, qclass, rdlen;
int done = 0;
for (i = count; i != 0; i--) if (!(p = skip_questions(header, qlen)))
return done;
for (i = 0; i < ntohs(header->ancount) + ntohs(header->arcount); i++)
{ {
if (!(p = skip_name(p, header, qlen, 10))) /* Skip over auth section */
return 0; /* bad packet */ if (i == ntohs(header->ancount) && !(p = skip_section(p, ntohs(header->nscount), header, qlen)))
return done;
if (!extract_name(header, qlen, &p, namebuff, 1, 10))
return done; /* bad packet */
GETSHORT(qtype, p); GETSHORT(qtype, p);
GETSHORT(qclass, p); GETSHORT(qclass, p);
@@ -401,57 +410,77 @@ static unsigned char *do_doctor(unsigned char *p, int count, struct dns_header *
if (qclass == C_IN && qtype == T_A) if (qclass == C_IN && qtype == T_A)
{ {
struct doctor *doctor; struct doctor *doctor;
struct in_addr addr; union all_addr addr;
if (!CHECK_LEN(header, p, qlen, INADDRSZ)) if (!CHECK_LEN(header, p, qlen, INADDRSZ))
return 0; return done;
/* alignment */ /* alignment */
memcpy(&addr, p, INADDRSZ); memcpy(&addr.addr4, p, INADDRSZ);
for (doctor = daemon->doctors; doctor; doctor = doctor->next) for (doctor = daemon->doctors; doctor; doctor = doctor->next)
{ {
if (doctor->end.s_addr == 0) if (doctor->end.s_addr == 0)
{ {
if (!is_same_net(doctor->in, addr, doctor->mask)) if (!is_same_net(doctor->in, addr.addr4, doctor->mask))
continue; continue;
} }
else if (ntohl(doctor->in.s_addr) > ntohl(addr.s_addr) || else if (ntohl(doctor->in.s_addr) > ntohl(addr.addr4.s_addr) ||
ntohl(doctor->end.s_addr) < ntohl(addr.s_addr)) ntohl(doctor->end.s_addr) < ntohl(addr.addr4.s_addr))
continue; continue;
addr.s_addr &= ~doctor->mask.s_addr; addr.addr4.s_addr &= ~doctor->mask.s_addr;
addr.s_addr |= (doctor->out.s_addr & doctor->mask.s_addr); addr.addr4.s_addr |= (doctor->out.s_addr & doctor->mask.s_addr);
/* Since we munged the data, the server it came from is no longer authoritative */ /* Since we munged the data, the server it came from is no longer authoritative */
header->hb3 &= ~HB3_AA; header->hb3 &= ~HB3_AA;
*doctored = 1; #ifdef HAVE_DNSSEC
memcpy(p, &addr, INADDRSZ); /* remove validated flag from this RR, since we changed it! */
if (option_bool(OPT_DNSSEC_VALID) && i < ntohs(header->ancount))
daemon->rr_status[i] = 0;
#endif
done = 1;
memcpy(p, &addr.addr4, INADDRSZ);
log_query(F_FORWARD | F_CONFIG | F_IPV4, namebuff, &addr, NULL, 0);
break; break;
} }
} }
if (!ADD_RDLEN(header, p, qlen, rdlen)) if (!ADD_RDLEN(header, p, qlen, rdlen))
return 0; /* bad packet */ return done; /* bad packet */
} }
return p; return done;
} }
static int find_soa(struct dns_header *header, size_t qlen, int *doctored) /* Find SOA RR in auth section to get TTL for negative caching of name.
Cache said SOA and return the difference in length between name and the name of the
SOA RR so we can look it up again.
*/
static int find_soa(struct dns_header *header, size_t qlen, char *name, int *substring, unsigned long *ttlp, int no_cache, time_t now)
{ {
unsigned char *p; unsigned char *p, *psave;
int qtype, qclass, rdlen; int qtype, qclass, rdlen;
unsigned long ttl, minttl = ULONG_MAX; unsigned long ttl, minttl;
int i, found_soa = 0; int i, j;
size_t name_len, soa_len, len;
union all_addr addr;
/* first move to NS section and find TTL from any SOA section */ /* first move to NS section and find TTL from SOA RR */
if (!(p = skip_questions(header, qlen)) || if (!(p = skip_questions(header, qlen)) ||
!(p = do_doctor(p, ntohs(header->ancount), header, qlen, doctored))) !(p = skip_section(p, ntohs(header->ancount), header, qlen)))
return 0; /* bad packet */ return 0; /* bad packet */
for (i = ntohs(header->nscount); i != 0; i--) name_len = strlen(name);
if (substring)
*substring = name_len;
if (ttlp)
*ttlp = daemon->neg_ttl;
for (i = 0; i < ntohs(header->nscount); i++)
{ {
if (!(p = skip_name(p, header, qlen, 10))) if (!extract_name(header, qlen, &p, daemon->workspacename, 1, 0))
return 0; /* bad packet */ return 0; /* bad packet */
GETSHORT(qtype, p); GETSHORT(qtype, p);
@@ -459,40 +488,112 @@ static int find_soa(struct dns_header *header, size_t qlen, int *doctored)
GETLONG(ttl, p); GETLONG(ttl, p);
GETSHORT(rdlen, p); GETSHORT(rdlen, p);
psave = p;
if ((qclass == C_IN) && (qtype == T_SOA)) if ((qclass == C_IN) && (qtype == T_SOA))
{ {
found_soa = 1; soa_len = strlen(daemon->workspacename);
if (ttl < minttl)
minttl = ttl;
/* MNAME */ /* SOA must be for the name we're interested in. */
if (!(p = skip_name(p, header, qlen, 0))) if (soa_len <= name_len && memcmp(daemon->workspacename, name + name_len - soa_len, soa_len) == 0)
{
int prefix = name_len - soa_len;
if (!no_cache)
{
if (!(addr.rrblock.rrdata = blockdata_alloc(NULL, 0)))
return 0; return 0;
/* RNAME */ addr.rrblock.rrtype = T_SOA;
if (!(p = skip_name(p, header, qlen, 20))) addr.rrblock.datalen = 0;
}
for (j = 0; j < 2; j++) /* MNAME, RNAME */
{
if (!extract_name(header, qlen, &p, daemon->workspacename, 1, 0))
{
if (!no_cache)
blockdata_free(addr.rrblock.rrdata);
return 0; return 0;
}
if (!no_cache)
{
len = to_wire(daemon->workspacename);
if (!blockdata_expand(addr.rrblock.rrdata, addr.rrblock.datalen, daemon->workspacename, len))
{
blockdata_free(addr.rrblock.rrdata);
return 0;
}
addr.rrblock.datalen += len;
}
}
if (!CHECK_LEN(header, p, qlen, 20))
{
if (!no_cache)
blockdata_free(addr.rrblock.rrdata);
return 0;
}
/* rest of RR */
if (!no_cache && !blockdata_expand(addr.rrblock.rrdata, addr.rrblock.datalen, (char *)p, 20))
{
blockdata_free(addr.rrblock.rrdata);
return 0;
}
addr.rrblock.datalen += 20;
if (!no_cache)
{
int secflag = 0;
#ifdef HAVE_DNSSEC
if (option_bool(OPT_DNSSEC_VALID) && daemon->rr_status[i + ntohs(header->ancount)] != 0)
{
secflag = F_DNSSECOK;
/* limit TTL based on signature. */
if (daemon->rr_status[i + ntohs(header->ancount)] < ttl)
ttl = daemon->rr_status[i + ntohs(header->ancount)];
}
#endif
if (!cache_insert(name + prefix, &addr, C_IN, now, ttl, F_FORWARD | F_RR | F_KEYTAG | secflag))
{
blockdata_free(addr.rrblock.rrdata);
return 0;
}
}
p += 16; /* SERIAL REFRESH RETRY EXPIRE */ p += 16; /* SERIAL REFRESH RETRY EXPIRE */
GETLONG(ttl, p); /* minTTL */ GETLONG(minttl, p); /* minTTL */
if (ttl < minttl) if (ttl < minttl)
minttl = ttl; minttl = ttl;
if (substring)
*substring = prefix;
if (ttlp)
*ttlp = minttl;
return 1;
} }
else if (!ADD_RDLEN(header, p, qlen, rdlen)) }
p = psave;
if (!ADD_RDLEN(header, p, qlen, rdlen))
return 0; /* bad packet */ return 0; /* bad packet */
} }
/* rewrite addresses in additional section too */
if (!do_doctor(p, ntohs(header->arcount), header, qlen, doctored))
return 0; return 0;
if (!found_soa)
minttl = daemon->neg_ttl;
return minttl;
} }
/* Print TXT reply to log */ /* Print TXT reply to log */
static int log_txt(char *name, unsigned char *p, const int ardlen, int secflag) static int log_txt(char *name, unsigned char *p, const int ardlen, int flag)
{ {
unsigned char *p1 = p; unsigned char *p1 = p;
@@ -514,7 +615,7 @@ static int log_txt(char *name, unsigned char *p, const int ardlen, int secflag)
} }
*p3 = 0; *p3 = 0;
log_query(secflag | F_FORWARD, name, NULL, (char*)p1, 0); log_query(flag, name, NULL, (char*)p1, 0);
/* restore */ /* restore */
memmove(p1 + 1, p1, i); memmove(p1 + 1, p1, i);
*p1 = len; *p1 = len;
@@ -531,10 +632,10 @@ static int log_txt(char *name, unsigned char *p, const int ardlen, int secflag)
*/ */
int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t now, int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t now,
struct ipsets *ipsets, struct ipsets *nftsets, int is_sign, int check_rebind, struct ipsets *ipsets, struct ipsets *nftsets, int is_sign, int check_rebind,
int no_cache_dnssec, int secure, int *doctored) int no_cache_dnssec, int secure)
{ {
unsigned char *p, *p1, *endrr, *namep; unsigned char *p, *p1, *endrr, *namep;
int j, qtype, qclass, aqtype, aqclass, ardlen, res, searched_soa = 0; int j, qtype, qclass, aqtype, aqclass, ardlen, res;
unsigned long ttl = 0; unsigned long ttl = 0;
union all_addr addr; union all_addr addr;
#ifdef HAVE_IPSET #ifdef HAVE_IPSET
@@ -557,25 +658,6 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
cache_start_insert(); cache_start_insert();
/* find_soa is needed for dns_doctor side effects, so don't call it lazily if there are any. */
if (daemon->doctors || option_bool(OPT_DNSSEC_VALID))
{
searched_soa = 1;
ttl = find_soa(header, qlen, doctored);
if (*doctored)
{
if (secure)
return 0;
#ifdef HAVE_DNSSEC
if (option_bool(OPT_DNSSEC_VALID))
for (j = 0; j < ntohs(header->ancount); j++)
if (daemon->rr_status[j] != 0)
return 0;
#endif
}
}
namep = p = (unsigned char *)(header+1); namep = p = (unsigned char *)(header+1);
if (ntohs(header->qdcount) != 1 || !extract_name(header, qlen, &p, name, 1, 4)) if (ntohs(header->qdcount) != 1 || !extract_name(header, qlen, &p, name, 1, 4))
@@ -624,7 +706,7 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
if (aqclass == C_IN && res != 2 && (aqtype == T_CNAME || aqtype == T_PTR)) if (aqclass == C_IN && res != 2 && (aqtype == T_CNAME || aqtype == T_PTR))
{ {
#ifdef HAVE_DNSSEC #ifdef HAVE_DNSSEC
if (option_bool(OPT_DNSSEC_VALID) && !no_cache_dnssec && daemon->rr_status[j] != 0) if (option_bool(OPT_DNSSEC_VALID) && daemon->rr_status[j] != 0)
{ {
/* validated RR anywhere in CNAME chain, don't cache. */ /* validated RR anywhere in CNAME chain, don't cache. */
if (cname_short || aqtype == T_CNAME) if (cname_short || aqtype == T_CNAME)
@@ -673,17 +755,16 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
if (!found && !option_bool(OPT_NO_NEG)) if (!found && !option_bool(OPT_NO_NEG))
{ {
if (!searched_soa) /* For reverse records, we use the name field to store the SOA name. */
{ int substring, have_soa = find_soa(header, qlen, name, &substring, &ttl, no_cache_dnssec, now);
searched_soa = 1;
ttl = find_soa(header, qlen, doctored);
}
flags |= F_NEG | (secure ? F_DNSSECOK : 0); flags |= F_NEG | (secure ? F_DNSSECOK : 0);
if (name_encoding && ttl) if (name_encoding && ttl)
{ {
flags |= F_REVERSE | name_encoding; flags |= F_REVERSE | name_encoding;
cache_insert(NULL, &addr, C_IN, now, ttl, flags); if (!have_soa)
flags |= F_NO_RR; /* Marks no SOA found. */
cache_insert(name + substring, &addr, C_IN, now, ttl, flags);
} }
log_query(flags | F_UPSTREAM, name, &addr, NULL, 0); log_query(flags | F_UPSTREAM, name, &addr, NULL, 0);
@@ -705,7 +786,8 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
addrlen = IN6ADDRSZ; addrlen = IN6ADDRSZ;
flags |= F_IPV6; flags |= F_IPV6;
} }
else if (qtype != T_CNAME && (qtype == T_SRV || rr_on_list(daemon->cache_rr, qtype))) else if (qtype != T_CNAME &&
(qtype == T_SRV || rr_on_list(daemon->cache_rr, qtype) || rr_on_list(daemon->cache_rr, T_ANY)))
flags |= F_RR; flags |= F_RR;
else else
insert = 0; /* NOTE: do not cache data from CNAME queries. */ insert = 0; /* NOTE: do not cache data from CNAME queries. */
@@ -732,17 +814,18 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
GETSHORT(ardlen, p1); GETSHORT(ardlen, p1);
endrr = p1+ardlen; endrr = p1+ardlen;
if (!CHECK_LEN(header, endrr, qlen, 0))
return 2; /* bad packet */
/* Not what we're looking for? */ /* Not what we're looking for? */
if (aqclass != C_IN || res == 2) if (aqclass != C_IN || res == 2)
{ {
p1 = endrr; p1 = endrr;
if (!CHECK_LEN(header, p1, qlen, 0))
return 2; /* bad packet */
continue; continue;
} }
#ifdef HAVE_DNSSEC #ifdef HAVE_DNSSEC
if (option_bool(OPT_DNSSEC_VALID) && !no_cache_dnssec && daemon->rr_status[j] != 0) if (option_bool(OPT_DNSSEC_VALID) && daemon->rr_status[j] != 0)
{ {
secflag = F_DNSSECOK; secflag = F_DNSSECOK;
@@ -787,7 +870,7 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
found = 1; found = 1;
} }
else if (aqtype != qtype) else if (qtype == T_ANY || aqtype != qtype)
{ {
#ifdef HAVE_DNSSEC #ifdef HAVE_DNSSEC
if (!option_bool(OPT_DNSSEC_VALID) || aqtype != T_RRSIG) if (!option_bool(OPT_DNSSEC_VALID) || aqtype != T_RRSIG)
@@ -838,7 +921,10 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
{ {
/* Copy the rest of the RR and end. */ /* Copy the rest of the RR and end. */
if (!blockdata_expand(addr.rrblock.rrdata, addr.rrblock.datalen, (char *)p1, endrr - p1)) if (!blockdata_expand(addr.rrblock.rrdata, addr.rrblock.datalen, (char *)p1, endrr - p1))
{
blockdata_free(addr.rrblock.rrdata);
return 0; return 0;
}
addr.rrblock.datalen += endrr - p1; addr.rrblock.datalen += endrr - p1;
} }
else if (desc == 0) else if (desc == 0)
@@ -935,26 +1021,17 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
blockdata_free(addr.rrblock.rrdata); blockdata_free(addr.rrblock.rrdata);
} }
if (aqtype == T_TXT)
{
if (!CHECK_LEN(header, p1, qlen, ardlen))
return 2;
log_txt(name, p1, ardlen, secflag | F_UPSTREAM);
}
else
{
int negflag = F_UPSTREAM;
/* We're filtering this RRtype. It will be removed from the /* We're filtering this RRtype. It will be removed from the
returned packet in process_reply() but gets cached here anyway returned packet in process_reply() but gets cached here anyway
and will be filtered again on the way out of the cache. Here, and will be filtered again on the way out of the cache. Here,
we just need to alter the logging. */ we just need to alter the logging. */
if (rr_on_list(daemon->filter_rr, qtype)) if (qtype != T_ANY && rr_on_list(daemon->filter_rr, qtype))
negflag = F_NEG | F_CONFIG; secflag = F_NEG | F_CONFIG;
log_query(negflag | flags | F_FORWARD | secflag, name, &addr, NULL, aqtype); if (aqtype == T_TXT)
} log_txt(name, p1, ardlen, flags | F_FORWARD | F_UPSTREAM | secflag);
else
log_query(flags | F_FORWARD | F_UPSTREAM | secflag, name, &addr, NULL, aqtype);
} }
p1 = endrr; p1 = endrr;
@@ -974,22 +1051,24 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
log_query(F_UPSTREAM | F_FORWARD | F_NEG | flags | (secure ? F_DNSSECOK : 0), name, NULL, NULL, 0); log_query(F_UPSTREAM | F_FORWARD | F_NEG | flags | (secure ? F_DNSSECOK : 0), name, NULL, NULL, 0);
if (!searched_soa) if (insert && !option_bool(OPT_NO_NEG))
{ {
searched_soa = 1; int substring, have_soa = find_soa(header, qlen, name, &substring, &ttl, no_cache_dnssec, now);
ttl = find_soa(header, qlen, doctored);
}
/* If there's no SOA to get the TTL from, but there is a CNAME /* If there's no SOA to get the TTL from, but there is a CNAME
pointing at this, inherit its TTL */ pointing at this, inherit its TTL */
if (insert && !option_bool(OPT_NO_NEG) && (ttl || cpp)) if (ttl || cpp)
{ {
if (ttl == 0) if (!ttl)
ttl = cttl; ttl = cttl;
if (flags & F_RR) addr.rrdata.datalen = substring;
addr.rrdata.rrtype = qtype; addr.rrdata.rrtype = qtype;
if (!have_soa)
flags |= F_NO_RR; /* Marks no SOA found. */
}
newc = cache_insert(name, &addr, C_IN, now, ttl, F_FORWARD | F_NEG | flags | (secure ? F_DNSSECOK : 0)); newc = cache_insert(name, &addr, C_IN, now, ttl, F_FORWARD | F_NEG | flags | (secure ? F_DNSSECOK : 0));
if (newc && cpp) if (newc && cpp)
{ {
@@ -1001,15 +1080,10 @@ int extract_addresses(struct dns_header *header, size_t qlen, char *name, time_t
} }
} }
if (header->hb3 & HB3_TC) /* Don't cache replies from non-recursive nameservers, since we may get a
log_query(F_UPSTREAM, NULL, NULL, "truncated", 0);
/* Don't put stuff from a truncated packet into the cache.
Don't cache replies from non-recursive nameservers, since we may get a
reply containing a CNAME but not its target, even though the target reply containing a CNAME but not its target, even though the target
does exist. */ does exist. */
if (!(header->hb3 & HB3_TC) && if (!(header->hb4 & HB4_CD) &&
!(header->hb4 & HB4_CD) &&
(header->hb4 & HB4_RA) && (header->hb4 & HB4_RA) &&
!no_cache_dnssec) !no_cache_dnssec)
cache_end_insert(); cache_end_insert();
@@ -1242,8 +1316,7 @@ static int check_bad_address(struct dns_header *header, size_t qlen, struct bogu
GETSHORT(qtype, p); GETSHORT(qtype, p);
GETSHORT(qclass, p); GETSHORT(qclass, p);
GETLONG(ttl, p); GETLONG(ttl, p);
GETSHORT(rdlen, p); GETSHORT(rdlen, p)
if (ttlp) if (ttlp)
*ttlp = ttl; *ttlp = ttl;
@@ -1296,8 +1369,9 @@ int check_for_bogus_wildcard(struct dns_header *header, size_t qlen, char *name,
/* Found a bogus address. Insert that info here, since there no SOA record /* Found a bogus address. Insert that info here, since there no SOA record
to get the ttl from in the normal processing */ to get the ttl from in the normal processing */
cache_start_insert(); cache_start_insert();
cache_insert(name, NULL, C_IN, now, ttl, F_IPV4 | F_FORWARD | F_NEG | F_NXDOMAIN); cache_insert(name, NULL, C_IN, now, ttl, F_FORWARD | F_NEG | F_NXDOMAIN);
cache_end_insert(); cache_end_insert();
log_query(F_CONFIG | F_FORWARD | F_NEG | F_NXDOMAIN, name, NULL, NULL, 0);
return 1; return 1;
} }
@@ -1500,12 +1574,13 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
union all_addr addr; union all_addr addr;
int nameoffset; int nameoffset;
unsigned short flag; unsigned short flag;
int q, ans, anscount = 0, addncount = 0; int ans, anscount = 0, nscount = 0, addncount = 0;
struct crec *crecp; struct crec *crecp, *soa_lookup = NULL;
int nxdomain = 0, notimp = 0, auth = 1, trunc = 0, sec_data = 1; int nxdomain = 0, notimp = 0, auth = 1, trunc = 0, sec_data = 1;
struct mx_srv_record *rec; struct mx_srv_record *rec;
size_t len; size_t len;
int rd_bit = (header->hb3 & HB3_RD); int rd_bit = (header->hb3 & HB3_RD);
int count = 255; /* catch loops */
if (stale) if (stale)
*stale = 0; *stale = 0;
@@ -1514,7 +1589,8 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
*filtered = 0; *filtered = 0;
/* never answer queries with RD unset, to avoid cache snooping. */ /* never answer queries with RD unset, to avoid cache snooping. */
if (ntohs(header->ancount) != 0 || if ( ntohs(header->qdcount) != 1 ||
ntohs(header->ancount) != 0 ||
ntohs(header->nscount) != 0 || ntohs(header->nscount) != 0 ||
ntohs(header->qdcount) == 0 || ntohs(header->qdcount) == 0 ||
OPCODE(header) != QUERY ) OPCODE(header) != QUERY )
@@ -1534,10 +1610,6 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
/* now process each question, answers go in RRs after the question */ /* now process each question, answers go in RRs after the question */
p = (unsigned char *)(header+1); p = (unsigned char *)(header+1);
for (q = ntohs(header->qdcount); q != 0; q--)
{
int count = 255; /* catch loops */
/* save pointer to name for copying into answers */ /* save pointer to name for copying into answers */
nameoffset = p - (unsigned char *)header; nameoffset = p - (unsigned char *)header;
@@ -1564,6 +1636,9 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
stale_flag = F_STALE; stale_flag = F_STALE;
} }
if (crecp->flags & F_NEG)
soa_lookup = crecp;
if (crecp->flags & F_NXDOMAIN) if (crecp->flags & F_NXDOMAIN)
{ {
if (qtype == T_CNAME) if (qtype == T_CNAME)
@@ -1768,6 +1843,7 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
if (crecp->flags & F_NXDOMAIN) if (crecp->flags & F_NXDOMAIN)
nxdomain = 1; nxdomain = 1;
log_query(stale_flag | (crecp->flags & ~F_FORWARD), name, &addr, NULL, 0); log_query(stale_flag | (crecp->flags & ~F_FORWARD), name, &addr, NULL, 0);
soa_lookup = crecp;
} }
else else
{ {
@@ -1872,13 +1948,13 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
continue; continue;
} }
if ((crecp = cache_find_by_name(NULL, name, now, flag | F_NXDOMAIN))) if ((crecp = cache_find_by_name(NULL, name, now, flag)))
{ {
int localise = 0; int localise = 0;
/* See if a putative address is on the network from which we received /* See if a putative address is on the network from which we received
the query, is so we'll filter other answers. */ the query, is so we'll filter other answers. */
if (local_addr.s_addr != 0 && option_bool(OPT_LOCALISE) && flag == F_IPV4) if (!(crecp->flags & F_NEG) && local_addr.s_addr != 0 && option_bool(OPT_LOCALISE) && flag == F_IPV4)
{ {
struct crec *save = crecp; struct crec *save = crecp;
do { do {
@@ -1918,7 +1994,7 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
if (!(crecp->flags & (F_HOSTS | F_DHCP))) if (!(crecp->flags & (F_HOSTS | F_DHCP)))
auth = 0; auth = 0;
if (rr_on_list(daemon->filter_rr, qtype) && if (qtype != T_ANY && rr_on_list(daemon->filter_rr, qtype) &&
!(crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG | F_NEG))) !(crecp->flags & (F_HOSTS | F_DHCP | F_CONFIG | F_NEG)))
{ {
/* We have a cached answer but we're filtering it. */ /* We have a cached answer but we're filtering it. */
@@ -1931,14 +2007,18 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
*filtered = 1; *filtered = 1;
} }
else if (crecp->flags & F_NEG) else if (crecp->flags & F_NEG)
{
if (qtype != T_ANY)
{ {
ans = 1; ans = 1;
auth = 0; auth = 0;
soa_lookup = crecp;
if (crecp->flags & F_NXDOMAIN) if (crecp->flags & F_NXDOMAIN)
nxdomain = 1; nxdomain = 1;
log_query(stale_flag | crecp->flags, name, NULL, NULL, 0); log_query(stale_flag | crecp->flags, name, NULL, NULL, 0);
} }
}
else else
{ {
/* If we are returning local answers depending on network, /* If we are returning local answers depending on network,
@@ -2102,12 +2182,15 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
if (flags & F_NXDOMAIN) if (flags & F_NXDOMAIN)
nxdomain = 1; nxdomain = 1;
else if (rr_on_list(daemon->filter_rr, qtype)) else if (qtype != T_ANY && rr_on_list(daemon->filter_rr, qtype))
flags |= F_NEG | F_CONFIG; flags |= F_NEG | F_CONFIG;
auth = 0; auth = 0;
ans = 1; ans = 1;
if (flags & F_NEG)
soa_lookup = crecp;
if (!(flags & F_NEG)) if (!(flags & F_NEG))
{ {
if (flags & F_KEYTAG) if (flags & F_KEYTAG)
@@ -2144,7 +2227,7 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
} }
if (!ans && rr_on_list(daemon->filter_rr, qtype)) if (qtype != T_ANY && !ans && rr_on_list(daemon->filter_rr, qtype))
{ {
/* We don't have a cached answer and when we get an answer from upstream we're going to /* We don't have a cached answer and when we get an answer from upstream we're going to
filter it anyway. If we have a cached answer for the domain for another RRtype then filter it anyway. If we have a cached answer for the domain for another RRtype then
@@ -2168,6 +2251,40 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
if (!ans) if (!ans)
return 0; /* failed to answer a question */ return 0; /* failed to answer a question */
/* We found a negative record. See if we have an SOA record to
return in the AUTH section.
For FORWARD NEG records, the addr.rrdata.datalen field of the othewise
empty addr is used to held an offset in to the name which yields the SOA
name. For REVERSE NEG records, the otherwise empty name field holds the
SOA name. If soa_name has zero length, then no SOA is known. soa_lookup
MUST be a neg record here.
If the F_NO_RR flag is set, there was no SOA record supplied with the RR. */
if (soa_lookup && !(soa_lookup->flags & F_NO_RR))
{
char *soa_name = soa_lookup->flags & F_REVERSE ? cache_get_name(soa_lookup) : name + soa_lookup->addr.rrdata.datalen;
crecp = NULL;
while ((crecp = cache_find_by_name(crecp, soa_name, now, F_RR)))
if (crecp->addr.rrblock.rrtype == T_SOA)
{
char *rrdata;
if (!(crecp->flags & F_NEG) &&
(rrdata = blockdata_retrieve(crecp->addr.rrblock.rrdata, crecp->addr.rrblock.datalen, NULL)) &&
add_resource_record(header, limit, &trunc, 0, &ansp,
crec_ttl(crecp, now), NULL, T_SOA, C_IN, "t",
soa_name, crecp->addr.rrblock.datalen, rrdata))
{
nscount++;
if (!(crecp->flags & F_DNSSECOK))
sec_data = 0;
}
break;
}
} }
/* create an additional data section, for stuff in SRV and MX record replies. */ /* create an additional data section, for stuff in SRV and MX record replies. */
@@ -2191,7 +2308,11 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
if (add_resource_record(header, limit, NULL, rec->offset, &ansp, if (add_resource_record(header, limit, NULL, rec->offset, &ansp,
crec_ttl(crecp, now), NULL, type, C_IN, crec_ttl(crecp, now), NULL, type, C_IN,
crecp->flags & F_IPV4 ? "4" : "6", &crecp->addr)) crecp->flags & F_IPV4 ? "4" : "6", &crecp->addr))
{
addncount++; addncount++;
if (!(crecp->flags & F_DNSSECOK))
sec_data = 0;
}
} }
} }
@@ -2216,7 +2337,7 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen,
else else
SET_RCODE(header, NOERROR); /* no error */ SET_RCODE(header, NOERROR); /* no error */
header->ancount = htons(anscount); header->ancount = htons(anscount);
header->nscount = htons(0); header->nscount = htons(nscount);
header->arcount = htons(addncount); header->arcount = htons(addncount);
len = ansp - (unsigned char *)header; len = ansp - (unsigned char *)header;
+8
View File
@@ -213,6 +213,14 @@ size_t rrfilter(struct dns_header *header, size_t *plen, int mode)
if (i < ntohs(header->ancount) && type == qtype && class == qclass) if (i < ntohs(header->ancount) && type == qtype && class == qclass)
continue; continue;
} }
else if (qtype == T_ANY && rr_on_list(daemon->filter_rr, T_ANY))
{
/* Filter replies to ANY queries in the spirit of
RFC RFC 8482 para 4.3 */
if (class != C_IN ||
type == T_A || type == T_AAAA || type == T_MX || type == T_CNAME)
continue;
}
else else
{ {
/* Only looking at answer section now. */ /* Only looking at answer section now. */
+1 -1
View File
@@ -119,7 +119,7 @@ int rr_on_list(struct rrlist *list, unsigned short rr)
{ {
while (list) while (list)
{ {
if (list->rr == rr || list->rr == T_ANY) if (list->rr == rr)
return 1; return 1;
list = list->next; list = list->next;