]> git.ipfire.org Git - thirdparty/bind9.git/commitdiff
Detect extraneous NSEC3 RRs in responses
authorPetr Špaček <pspacek@isc.org>
Mon, 9 Jun 2025 09:01:27 +0000 (11:01 +0200)
committerPetr Špaček <pspacek@isc.org>
Tue, 29 Jul 2025 08:00:46 +0000 (10:00 +0200)
We expect minimal possible answers which prove what they have to
according to DNSSEC protocol.

bin/tests/system/dnssec/tests_nsec3.py

index efd6619e2a7418a91424c1b887cf0727b4899e9a..ae839059cd7051093d3251f26e667c217e2480b5 100755 (executable)
@@ -135,6 +135,7 @@ def check_wildcard_nodata(server, named_port: int, qname: dns.name.Name) -> None
     wname = ZONE.source_of_synthesis(qname)
     # expecting proof that wildcard owner does not have rdatatype requested
     nsec3check.prove_name_exists(wname)
+    nsec3check.check_extraneous_rrs()
 
 
 def check_nxdomain(server, named_port: int, qname: dns.name.Name) -> None:
@@ -147,6 +148,7 @@ def check_nxdomain(server, named_port: int, qname: dns.name.Name) -> None:
 
     wname = ZONE.source_of_synthesis(qname)
     nsec3check.prove_name_does_not_exist(wname)
+    nsec3check.check_extraneous_rrs()
 
 
 def check_wildcard_synthesis(server, named_port: int, qname: dns.name.Name) -> None:
@@ -185,6 +187,7 @@ def check_wildcard_synthesis(server, named_port: int, qname: dns.name.Name) -> N
     # ce is proven to exist by the RRSIG
     assert nce == qname.split(wildcard_parent_labels + 1)[1]
     nsec3check.prove_name_does_not_exist(nce)
+    nsec3check.check_extraneous_rrs()
 
 
 @dataclass(kw_only=True, frozen=True)
@@ -252,6 +255,8 @@ class NSEC3Checker:
         assert attrs_seen["algorithm"] is not None, f"no NSEC3 found\n{response}"
         self.params = NSEC3Params(**attrs_seen)  # type: NSEC3Params
         self.response = response  # type: dns.message.Message
+        self.owners_present = owners_seen
+        self.owners_used = set()
 
     @staticmethod
     def nsec3_covers(rrset: dns.rrset.RRset, hashed_name: dns.name.Name) -> bool:
@@ -300,6 +305,7 @@ class NSEC3Checker:
         for rrset in self.rrsets:
             name_is_covered = self.nsec3_covers(rrset, hashed_name)
             if name_is_covered:
+                self.owners_used.add(rrset.name)
                 return rrset
 
         assert (
@@ -313,7 +319,14 @@ class NSEC3Checker:
             if rrset.match(
                 nsec3_owner, dns.rdataclass.IN, dns.rdatatype.NSEC3, dns.rdatatype.NONE
             ):
+                self.owners_used.add(rrset.name)
                 return rrset
         assert (
             False
         ), f"Expected matching NSEC3 for {owner} (hash={nsec3_owner}) not found:\n{self.response}"
+
+    def check_extraneous_rrs(self):
+        """Check that all NSEC3 RRs present in the message were actually needed for proofs"""
+        assert (
+            self.owners_used == self.owners_present
+        ), f"extraneous NSEC3 RRs detected\n{self.response}"