mirror of
https://github.com/isc-projects/bind9.git
synced 2026-07-15 01:00:57 -04:00
fix: usr: Check that an NSEC signer is at or above the name to be validated
Add a check that an NSEC record being used as a proof of nonexistence for a given name is not signed by a name lower in the DNS hierarchy than the one in question. Closes #5876 Merge branch '5876-nsec-signer-above-name' into 'main' See merge request isc-projects/bind9!12272
This commit is contained in:
commit
89e57b45e0
6 changed files with 236 additions and 0 deletions
34
bin/tests/system/nsec_synthesis/ns2/named.conf.j2
Normal file
34
bin/tests/system/nsec_synthesis/ns2/named.conf.j2
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
// validating resolver
|
||||
|
||||
options {
|
||||
query-source address 10.53.0.2;
|
||||
notify-source 10.53.0.2;
|
||||
transfer-source 10.53.0.2;
|
||||
port @PORT@;
|
||||
pid-file "named.pid";
|
||||
listen-on { 10.53.0.2; };
|
||||
listen-on-v6 { none; };
|
||||
recursion yes;
|
||||
dnssec-validation yes;
|
||||
synth-from-dnssec yes;
|
||||
};
|
||||
|
||||
controls {
|
||||
inet 10.53.0.2 port @CONTROLPORT@ allow { any; } keys { rndc_key; };
|
||||
};
|
||||
|
||||
include "../../_common/rndc.key";
|
||||
|
||||
zone "." {
|
||||
type hint;
|
||||
file "../../_common/root.hint";
|
||||
};
|
||||
|
||||
zone "f007.test" {
|
||||
type static-stub;
|
||||
server-addresses { 10.53.0.3; };
|
||||
};
|
||||
|
||||
trust-anchors {
|
||||
f007.test. static-key 257 3 13 "@DNSKEY@";
|
||||
};
|
||||
34
bin/tests/system/nsec_synthesis/ns3/named.conf.j2
Normal file
34
bin/tests/system/nsec_synthesis/ns3/named.conf.j2
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
options {
|
||||
query-source address 10.53.0.3;
|
||||
notify-source 10.53.0.3;
|
||||
transfer-source 10.53.0.3;
|
||||
port @PORT@;
|
||||
pid-file "named.pid";
|
||||
listen-on { 10.53.0.3; };
|
||||
listen-on-v6 { none; };
|
||||
recursion no;
|
||||
dnssec-validation no;
|
||||
notify no;
|
||||
};
|
||||
|
||||
controls {
|
||||
inet 10.53.0.3 port @CONTROLPORT@ allow { any; } keys { rndc_key; };
|
||||
};
|
||||
|
||||
include "../../_common/rndc.key";
|
||||
|
||||
zone "." {
|
||||
type hint;
|
||||
file "../../_common/root.hint";
|
||||
};
|
||||
|
||||
zone "f007.test" {
|
||||
type primary;
|
||||
file "f007.test.db.signed";
|
||||
};
|
||||
|
||||
zone "!.f007.test" {
|
||||
type primary;
|
||||
file "child.f007.test.db.signed";
|
||||
};
|
||||
|
||||
147
bin/tests/system/nsec_synthesis/tests_nsec_selfloop.py
Normal file
147
bin/tests/system/nsec_synthesis/tests_nsec_selfloop.py
Normal file
|
|
@ -0,0 +1,147 @@
|
|||
#!/usr/bin/python3
|
||||
|
||||
# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
|
||||
#
|
||||
# SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import dns.flags
|
||||
import dns.name
|
||||
import dns.rdataclass
|
||||
import dns.rdatatype
|
||||
import pytest
|
||||
|
||||
from isctest.run import EnvCmd
|
||||
|
||||
import isctest
|
||||
|
||||
PARENT = "f007.test."
|
||||
CHILD = "!.f007.test."
|
||||
PROBE = f"probe.{CHILD}"
|
||||
VICTIM = f"victim.{PARENT}"
|
||||
VICTIM_A = "203.0.113.7"
|
||||
AUTH = "10.53.0.3"
|
||||
RESOLVER = "10.53.0.2"
|
||||
|
||||
pytestmark = pytest.mark.extra_artifacts(
|
||||
[
|
||||
"ans1/ans.run",
|
||||
"ns3/K*",
|
||||
"ns3/child.f007.test.db",
|
||||
"ns3/child.f007.test.db.signed",
|
||||
"ns3/dsset-*",
|
||||
"ns3/f007.test.db",
|
||||
"ns3/f007.test.db.signed",
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
def _write_zone(path, text):
|
||||
Path(path).write_text(text, encoding="ascii")
|
||||
|
||||
|
||||
def bootstrap():
|
||||
keygen = EnvCmd("KEYGEN", "-a ECDSA256 -q")
|
||||
dsfromkey = EnvCmd("DSFROMKEY", "-2")
|
||||
signer = EnvCmd("SIGNER", "-S -g")
|
||||
|
||||
parent_ksk = keygen(f"-f KSK {PARENT}", cwd="ns3").out.strip()
|
||||
keygen(PARENT, cwd="ns3")
|
||||
child_ksk = keygen(f"-f KSK {CHILD}", cwd="ns3").out.strip()
|
||||
keygen(CHILD, cwd="ns3")
|
||||
|
||||
child_ds = dsfromkey(f"{child_ksk}.key", cwd="ns3").out
|
||||
|
||||
_write_zone(
|
||||
"ns3/child.f007.test.db",
|
||||
f"""$TTL 300
|
||||
$ORIGIN {CHILD}
|
||||
@ SOA ns.f007.test. hostmaster.f007.test. 1 3600 600 86400 300
|
||||
@ NS ns.f007.test.
|
||||
""",
|
||||
)
|
||||
_write_zone(
|
||||
"ns3/f007.test.db",
|
||||
f"""$TTL 300
|
||||
$ORIGIN {PARENT}
|
||||
@ SOA ns hostmaster 1 3600 600 86400 300
|
||||
@ NS ns
|
||||
ns A 10.53.0.3
|
||||
victim A {VICTIM_A}
|
||||
! NS ns.f007.test.
|
||||
{child_ds}""",
|
||||
)
|
||||
|
||||
signer(f"-o {CHILD} -f child.f007.test.db.signed child.f007.test.db", cwd="ns3")
|
||||
signer(f"-o {PARENT} -f f007.test.db.signed f007.test.db", cwd="ns3")
|
||||
|
||||
dnskey = str(isctest.kasp.Key(parent_ksk, keydir="ns3").dnskey).split()
|
||||
ta = "".join(dnskey[7:])
|
||||
|
||||
return {
|
||||
"DNSKEY": ta,
|
||||
}
|
||||
|
||||
|
||||
def _query(server, qname, qtype):
|
||||
query = isctest.query.create(qname, qtype)
|
||||
return isctest.query.tcp(query, server)
|
||||
|
||||
|
||||
def _rrset(response, section, owner, rdtype, covers=None):
|
||||
if covers is None:
|
||||
return response.get_rrset(
|
||||
section,
|
||||
dns.name.from_text(owner),
|
||||
dns.rdataclass.IN,
|
||||
rdtype,
|
||||
)
|
||||
return response.get_rrset(
|
||||
section,
|
||||
dns.name.from_text(owner),
|
||||
dns.rdataclass.IN,
|
||||
rdtype,
|
||||
covers=covers,
|
||||
)
|
||||
|
||||
|
||||
def _has_a(response, owner, address):
|
||||
rrset = _rrset(response, response.answer, owner, dns.rdatatype.A)
|
||||
return rrset is not None and any(rdata.address == address for rdata in rrset)
|
||||
|
||||
|
||||
def _check_child_self_loop_nsec(response):
|
||||
nsec = _rrset(response, response.authority, CHILD, dns.rdatatype.NSEC)
|
||||
assert nsec is not None, response.to_text()
|
||||
assert nsec[0].next == dns.name.from_text(CHILD), response.to_text()
|
||||
|
||||
rrsig = _rrset(
|
||||
response,
|
||||
response.authority,
|
||||
CHILD,
|
||||
dns.rdatatype.RRSIG,
|
||||
covers=dns.rdatatype.NSEC,
|
||||
)
|
||||
assert rrsig is not None, response.to_text()
|
||||
assert rrsig[0].signer == dns.name.from_text(CHILD), response.to_text()
|
||||
|
||||
|
||||
def test_direct_child_self_loop_nsec_fixture():
|
||||
response = _query(AUTH, PROBE, "A")
|
||||
isctest.check.nxdomain(response)
|
||||
assert response.flags & dns.flags.AA
|
||||
_check_child_self_loop_nsec(response)
|
||||
|
||||
|
||||
def test_resolver_does_not_synth_parent_nxdomain_from_child_nsec():
|
||||
poison = _query(RESOLVER, PROBE, "A")
|
||||
isctest.check.nxdomain(poison)
|
||||
isctest.check.adflag(poison)
|
||||
_check_child_self_loop_nsec(poison)
|
||||
|
||||
response = _query(RESOLVER, VICTIM, "A")
|
||||
isctest.check.noerror(response)
|
||||
assert _has_a(response, VICTIM, VICTIM_A), response.to_text()
|
||||
isctest.check.adflag(response)
|
||||
assert _rrset(response, response.authority, CHILD, dns.rdatatype.NSEC) is None
|
||||
|
|
@ -416,6 +416,18 @@ dns_nsec_noexistnodata(dns_rdatatype_t type, const dns_name_t *name,
|
|||
return DNS_R_DNAME;
|
||||
}
|
||||
|
||||
if (relation != dns_namereln_subdomain &&
|
||||
dns_nsec_typepresent(&rdata, dns_rdatatype_soa))
|
||||
{
|
||||
/*
|
||||
* An NSEC with an SOA in the bitmap can only cover
|
||||
* names that are subdomains of the owner.
|
||||
*/
|
||||
(*logit)(arg, ISC_LOG_DEBUG(3),
|
||||
"ignoring nsec with SOA covering non-subdomain");
|
||||
return ISC_R_IGNORE;
|
||||
}
|
||||
|
||||
RETERR(dns_rdata_tostruct(&rdata, &nsec, NULL));
|
||||
relation = dns_name_fullcompare(&nsec.next, name, &order, &nlabels);
|
||||
if (order == 0) {
|
||||
|
|
|
|||
|
|
@ -851,6 +851,8 @@ validator_callback_nsec(void *arg) {
|
|||
rdataset->trust == dns_trust_secure &&
|
||||
(NEEDNODATA(val) || NEEDNOQNAME(val)) &&
|
||||
!FOUNDNODATA(val) && !FOUNDNOQNAME(val) &&
|
||||
dns_name_issubdomain(val->name,
|
||||
&subvalidator->siginfo->signer) &&
|
||||
dns_nsec_noexistnodata(val->type, val->name,
|
||||
subvalidator->name, rdataset,
|
||||
&exists, &data, wild, validator_log,
|
||||
|
|
|
|||
|
|
@ -9522,6 +9522,13 @@ query_coveringnsec(query_ctx_t *qctx) {
|
|||
goto cleanup;
|
||||
}
|
||||
|
||||
/*
|
||||
* The query name can't be above the signer of the NSEC.
|
||||
*/
|
||||
if (!dns_name_issubdomain(qctx->client->query.qname, signer)) {
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
/*
|
||||
* If NSEC or RRSIG are missing from the type map
|
||||
* reject the NSEC RRset.
|
||||
|
|
|
|||
Loading…
Reference in a new issue