Reject NSEC records with next field with \000 label

A number of DNS implementation produce NSEC records with bad type
maps that don't contain types that exist at the name leading to
NODATA responses being synthesize instead of the records in the
zone.  NSEC records with these bad type maps often have the NSEC
NSEC field set to '\000.QNAME'.  We look for the first label of
this pattern.

e.g.
	example.com NSEC \000.example.com SOA NS NSEC RRSIG
	example.com RRRSIG NSEC ...
	example.com SOA ...
	example.com RRRSIG SOA ...
	example.com NS ...
	example.com RRRSIG NS ...
	example.com A ...
	example.com RRRSIG A ...

	A is missing from the type map.

This introduces a temporary option 'reject-000-label' to control
this behaviour.
This commit is contained in:
Mark Andrews 2021-12-02 00:34:38 +11:00 committed by Petr Špaček
parent 8878adcd61
commit 0aaaa8768f
No known key found for this signature in database
GPG key ID: ABD587CDF06581AE
12 changed files with 75 additions and 0 deletions

View file

@ -177,6 +177,7 @@ options {\n\
query-source address *;\n\
query-source-v6 address *;\n\
recursion true;\n\
reject-000-label yes;\n\
request-expire true;\n\
request-ixfr true;\n\
require-server-cookie no;\n\

View file

@ -388,6 +388,7 @@ OPTIONS
recursing-file quoted_string;
recursion boolean;
recursive-clients integer;
reject-000-label boolean;// deprecated
request-expire boolean;
request-ixfr boolean;
request-nsid boolean;
@ -793,6 +794,7 @@ VIEW
window integer;
};
recursion boolean;
reject-000-label boolean;// deprecated
request-expire boolean;
request-ixfr boolean;
request-nsid boolean;

View file

@ -4456,6 +4456,11 @@ configure_view(dns_view_t *view, dns_viewlist_t *viewlist, cfg_obj_t *config,
INSIST(result == ISC_R_SUCCESS);
view->acceptexpired = cfg_obj_asboolean(obj);
obj = NULL;
result = named_config_get(maps, "reject-000-label", &obj);
INSIST(result == ISC_R_SUCCESS);
view->reject_000_label = cfg_obj_asboolean(obj);
obj = NULL;
/* 'optionmaps', not 'maps': don't check named_g_defaults yet */
(void)named_config_get(optionmaps, "dnssec-validation", &obj);

View file

@ -2100,6 +2100,16 @@ Boolean Options
default is ``no``. Setting this option to ``yes`` leaves ``named``
vulnerable to replay attacks.
``reject-000-label``
This can be used to control whether NSEC records which have the
``next`` field starting with the ``\\000`` label are cached for
``synth-from-dnssec``. There are a number of DNSSEC implementations
that generate bad NSEC type maps where the ``next`` field starts with
the ``\\000`` label and between BIND 9.18 and BIND 9.20 there will be
a campaign to get these servers corrected. In BIND 9.18 this defaults
to ``yes``. In BIND 9.20 (BIND 9.19) this will default to ``no`` and
in BIND 9.22 (BIND 9.21) this option will be removed.
``querylog``
Query logging provides a complete log of all incoming queries and all query
errors. This provides more insight into the server's activity, but with a
@ -2247,6 +2257,16 @@ Boolean Options
named caching broken NSEC records from negative responses from servers
that emit broken NSEC records with missing types that actually exist.
``reject-000-label`` can be used to control whether NSEC records
which have the ``next`` field starting with the ``\\000`` label
are cached for ``synth-from-dnssec``. There are a number of
DNSSEC implementations that generate bad NSEC type maps where
the ``next`` field starts with the ``\\000`` label and between
BIND 9.18 and BIND 9.20 there will be a campaign to get these
servers corrected. In BIND 9.18 this defaults to ``yes``. In
BIND 9.20 (BIND 9.19) this will default to ``no`` and in BIND 9.22
(BIND 9.21) this option will be removed.
.. note:: DNSSEC validation must be enabled for this option to be effective.
This initial implementation only covers synthesis of answers from
NSEC records; synthesis from NSEC3 is planned for the future. This

View file

@ -455,6 +455,7 @@ options {
recursing\-file quoted_string;
recursion boolean;
recursive\-clients integer;
reject\-000\-label boolean;// deprecated
request\-expire boolean;
request\-ixfr boolean;
request\-nsid boolean;
@ -896,6 +897,7 @@ view string [ class ] {
window integer;
};
recursion boolean;
reject\-000\-label boolean;// deprecated
request\-expire boolean;
request\-ixfr boolean;
request\-nsid boolean;

View file

@ -313,6 +313,7 @@ options {
recursing-file <quoted_string>;
recursion <boolean>;
recursive-clients <integer>;
reject-000-label <boolean>; // deprecated
request-expire <boolean>;
request-ixfr <boolean>;
request-nsid <boolean>;
@ -673,6 +674,7 @@ view <string> [ <class> ] {
window <integer>;
};
recursion <boolean>;
reject-000-label <boolean>; // deprecated
request-expire <boolean>;
request-ixfr <boolean>;
request-nsid <boolean>;

View file

@ -311,6 +311,7 @@ options {
recursing-file <quoted_string>;
recursion <boolean>;
recursive-clients <integer>;
reject-000-label <boolean>; // deprecated
request-expire <boolean>;
request-ixfr <boolean>;
request-nsid <boolean>;
@ -669,6 +670,7 @@ view <string> [ <class> ] {
window <integer>;
};
recursion <boolean>;
reject-000-label <boolean>; // deprecated
request-expire <boolean>;
request-ixfr <boolean>;
request-nsid <boolean>;

View file

@ -231,6 +231,7 @@
recursing-file <quoted_string>;
recursion <boolean>;
recursive-clients <integer>;
reject-000-label <boolean>; // deprecated
request-expire <boolean>;
request-ixfr <boolean>;
request-nsid <boolean>;

View file

@ -130,6 +130,7 @@ struct dns_view {
bool synthfromdnssec;
bool trust_anchor_telemetry;
bool root_key_sentinel;
bool reject_000_label;
dns_transfer_format_t transfer_format;
dns_acl_t *cacheacl;
dns_acl_t *cacheonacl;

View file

@ -5210,6 +5210,32 @@ check_soa_and_dnskey(dns_rdataset_t *nsecset) {
return (true);
}
/*
* Look for NSEC next name that starts with the label '\000'.
*/
static bool
has_000_label(dns_rdataset_t *nsecset) {
dns_rdataset_t rdataset;
isc_result_t result;
dns_rdataset_init(&rdataset);
dns_rdataset_clone(nsecset, &rdataset);
for (result = dns_rdataset_first(&rdataset); result == ISC_R_SUCCESS;
result = dns_rdataset_next(&rdataset))
{
dns_rdata_t rdata = DNS_RDATA_INIT;
dns_rdataset_current(&rdataset, &rdata);
if (rdata.length > 1 && rdata.data[0] == 1 &&
rdata.data[1] == 0) {
dns_rdataset_disassociate(&rdataset);
return (true);
}
}
dns_rdataset_disassociate(&rdataset);
return (false);
}
/*
* The validator has finished.
*/
@ -5645,6 +5671,17 @@ answer_response:
!check_soa_and_dnskey(rdataset)) {
continue;
}
/*
* Look for \000 label in next name.
*/
if (rdataset->type == dns_rdatatype_nsec &&
fctx->res->view->reject_000_label &&
has_000_label(rdataset))
{
continue;
}
result = dns_db_findnode(fctx->cache, name, true,
&nsnode);
if (result != ISC_R_SUCCESS) {

View file

@ -240,6 +240,7 @@ dns_view_create(isc_mem_t *mctx, dns_rdataclass_t rdclass, const char *name,
view->synthfromdnssec = true;
view->trust_anchor_telemetry = true;
view->root_key_sentinel = true;
view->reject_000_label = true;
view->new_zone_dir = NULL;
view->new_zone_file = NULL;
view->new_zone_db = NULL;

View file

@ -2118,6 +2118,7 @@ static cfg_clausedef_t view_clauses[] = {
{ "queryport-pool-updateinterval", NULL, CFG_CLAUSEFLAG_ANCIENT },
{ "rate-limit", &cfg_type_rrl, 0 },
{ "recursion", &cfg_type_boolean, 0 },
{ "reject-000-label", &cfg_type_boolean, CFG_CLAUSEFLAG_DEPRECATED },
{ "request-nsid", &cfg_type_boolean, 0 },
{ "request-sit", NULL, CFG_CLAUSEFLAG_ANCIENT },
{ "require-server-cookie", &cfg_type_boolean, 0 },