Home | History | Annotate | Line # | Download | only in nsec
      1 #!/usr/bin/python3
      2 
      3 # Copyright (C) Internet Systems Consortium, Inc. ("ISC")
      4 #
      5 # SPDX-License-Identifier: MPL-2.0
      6 #
      7 # This Source Code Form is subject to the terms of the Mozilla Public
      8 # License, v. 2.0.  If a copy of the MPL was not distributed with this
      9 # file, you can obtain one at https://mozilla.org/MPL/2.0/.
     10 #
     11 # See the COPYRIGHT file distributed with this work for additional
     12 # information regarding copyright ownership.
     13 
     14 import dns.rdataclass
     15 import dns.rdatatype
     16 import dns.rdtypes.ANY.RRSIG
     17 import dns.zone
     18 
     19 from isctest.run import EnvCmd
     20 
     21 import isctest
     22 
     23 
     24 def duplicate_rrsig(rdata, i):
     25     return dns.rdtypes.ANY.RRSIG.RRSIG(
     26         rdclass=rdata.rdclass,
     27         rdtype=rdata.rdtype,
     28         type_covered=rdata.type_covered,
     29         algorithm=rdata.algorithm,
     30         labels=rdata.labels,
     31         # increment orig TTL so the rdataset isn't treated as identical record by dnspython
     32         original_ttl=rdata.original_ttl + i,
     33         expiration=rdata.expiration,
     34         inception=rdata.inception,
     35         key_tag=rdata.key_tag,
     36         signer=rdata.signer,
     37         signature=rdata.signature,
     38     )
     39 
     40 
     41 def bootstrap():
     42     keygen = EnvCmd("KEYGEN", "-a ECDSA256 -Kns2 -q")
     43     signer = EnvCmd("SIGNER", "-S -g")
     44 
     45     zone = "excessive-nsec-rrsigs"
     46     infile = f"{zone}.db.in"
     47     signedfile = f"{zone}.db.signed"
     48 
     49     isctest.log.info(f"{zone}: generate ksk and zsk")
     50     ksk_name = keygen(f"-f KSK {zone}").out.strip()
     51     keygen(f"{zone}").out.strip()
     52     ksk = isctest.kasp.Key(ksk_name, keydir="ns2")
     53 
     54     isctest.log.info(f"{zone}: sign zone")
     55     signer(f"-P -x -O full -o {zone} -f {signedfile} {infile}", cwd="ns2")
     56 
     57     isctest.log.info(
     58         f"{zone}: duplicate the RRSIG(NSEC) to exceed max-records-per-type"
     59     )
     60     zone = dns.zone.from_file(f"ns2/{signedfile}", origin=zone)
     61     for node in zone.values():
     62         rrsig_rdataset = node.find_rdataset(
     63             dns.rdataclass.IN, dns.rdatatype.RRSIG, dns.rdatatype.NSEC
     64         )
     65         orig = rrsig_rdataset[0]
     66         rrsig_rdataset.add(duplicate_rrsig(orig, 1))
     67         rrsig_rdataset.add(duplicate_rrsig(orig, 2))
     68     zone.to_file(f"ns2/{signedfile}", sorted=True)
     69 
     70     return {
     71         "trust_anchors": [
     72             ksk.into_ta("static-key"),
     73         ],
     74     }
     75 
     76 
     77 # reproducer for CVE-2026-3104 [GL#5742]
     78 def test_excessive_rrsigs(ns3):
     79     # the real test is that there is no crash on shutdown - checked by the test
     80     # framework when the test finishes
     81 
     82     # multiple queries seem more reliable to reproduce the memory leak, using a
     83     # single query sometimes didn't cause a crash on shutdown
     84     for i in range(10):
     85         msg = isctest.query.create(f"x{i}.excessive-nsec-rrsigs", "A")
     86         res = isctest.query.udp(msg, ns3.ip, attempts=1)
     87         isctest.check.servfail(res)
     88