diff --git a/debian/changelog b/debian/changelog index 3a4f88b3..14564868 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,57 @@ +bind9 (1:9.20.23-1~deb13u1deepin10) unstable; urgency=medium + + * fix(cve): CVE-2026-11721 + + -- deepin-ci-robot Fri, 24 Jul 2026 13:43:09 +0800 + +bind9 (1:9.20.23-1~deb13u1deepin9) unstable; urgency=medium + + * fix(cve): CVE-2026-10723 + + -- deepin-ci-robot Fri, 24 Jul 2026 13:19:22 +0800 + +bind9 (1:9.20.23-1~deb13u1deepin8) unstable; urgency=medium + + * fix(cve): CVE-2026-10822 + + -- deepin-ci-robot Fri, 24 Jul 2026 10:18:02 +0800 + +bind9 (1:9.20.23-1~deb13u1deepin7) unstable; urgency=medium + + * fix(cve): CVE-2026-11331 + + -- deepin-ci-robot Fri, 24 Jul 2026 10:10:36 +0800 + +bind9 (1:9.20.23-1~deb13u1deepin6) unstable; urgency=medium + + * fix(cve): CVE-2026-11605 + + -- deepin-ci-robot Fri, 24 Jul 2026 10:05:17 +0800 + +bind9 (1:9.20.23-1~deb13u1deepin5) unstable; urgency=medium + + * fix(cve): CVE-2026-13321 + + -- deepin-ci-robot Fri, 24 Jul 2026 09:52:54 +0800 + +bind9 (1:9.20.23-1~deb13u1deepin4) unstable; urgency=medium + + * fix(cve): CVE-2026-11622 + + -- deepin-ci-robot Fri, 24 Jul 2026 09:43:15 +0800 + +bind9 (1:9.20.23-1~deb13u1deepin3) unstable; urgency=medium + + * fix(cve): CVE-2026-12617 + + -- deepin-ci-robot Fri, 24 Jul 2026 09:37:32 +0800 + +bind9 (1:9.20.23-1~deb13u1deepin2) unstable; urgency=medium + + * fix(cve): CVE-2026-13204 + + -- deepin-ci-robot Fri, 24 Jul 2026 09:26:55 +0800 + bind9 (1:9.20.23-1~deb13u1deepin1) unstable; urgency=medium [ lichenggang ] diff --git a/debian/patches/CVE-2026-10723.patch b/debian/patches/CVE-2026-10723.patch new file mode 100644 index 00000000..696c06c6 --- /dev/null +++ b/debian/patches/CVE-2026-10723.patch @@ -0,0 +1,51 @@ +Description: CVE-2026-10723 - 安全修复 +Author: Evan Hunt +Origin: https://gitlab.isc.org/isc-projects/bind9/commit/6965fa47edd3b45538db2b16488dbb6b4ad8066a +Bug: https://nvd.nist.gov/vuln/detail/CVE-2026-10723 +Last-Update: Wed Jul 1 23:24:09 2026 -0700 +--- +diff --git a/lib/dns/dnssec.c b/lib/dns/dnssec.c +index cf95f53622..3bd7069e8a 100644 +--- a/lib/dns/dnssec.c ++++ b/lib/dns/dnssec.c +@@ -402,10 +402,25 @@ dns_dnssec_verify(const dns_name_t *name, dns_rdataset_t *set, dst_key_t *key, + } + + /* +- * NS, SOA and DNSKEY records are signed by their owner. +- * DS records are signed by the parent. ++ * NS, SOA and DNSKEY records are signed by their owners. ++ * NSEC3 records are signed by the apex, exactly one level up ++ * from their owner names. ++ * DS records are signed by the parent zone. + */ + switch (set->type) { ++ case dns_rdatatype_nsec3: { ++ dns_name_t apex = DNS_NAME_INITEMPTY; ++ labels = dns_name_countlabels(name); ++ if (labels <= 1) { ++ inc_stat(dns_dnssecstats_fail); ++ return DNS_R_INVALIDNSEC3; ++ } ++ dns_name_split(name, labels - 1, NULL, &apex); ++ if (!dns_name_equal(&apex, &sig.signer)) { ++ inc_stat(dns_dnssecstats_fail); ++ return DNS_R_SIGINVALID; ++ } ++ } break; + case dns_rdatatype_ns: + case dns_rdatatype_soa: + case dns_rdatatype_dnskey: +diff --git a/lib/isc/result.c b/lib/isc/result.c +index 29b8bc65f4..3bce804809 100644 +--- a/lib/isc/result.c ++++ b/lib/isc/result.c +@@ -200,7 +200,7 @@ static const char *description[ISC_R_NRESULTS] = { + [DNS_R_COVERINGNSEC] = "covering NSEC record returned", + [DNS_R_MXISADDRESS] = "MX is an address", + [DNS_R_DUPLICATE] = "duplicate query", +- [DNS_R_INVALIDNSEC3] = "invalid NSEC3 owner name (wildcard)", ++ [DNS_R_INVALIDNSEC3] = "invalid NSEC3 owner name", + [DNS_R_NOTPRIMARY] = "not primary", + [DNS_R_BROKENCHAIN] = "broken trust chain", + [DNS_R_EXPIRED] = "expired", diff --git a/debian/patches/CVE-2026-10822.patch b/debian/patches/CVE-2026-10822.patch new file mode 100644 index 00000000..f920c979 --- /dev/null +++ b/debian/patches/CVE-2026-10822.patch @@ -0,0 +1,156 @@ +Description: CVE-2026-10822 - 安全修复 +Author: Mark Andrews +Origin: https://github.com/isc-projects/bind9/commit/9a82af76e8b7249f512dd50a9a7925270475a22f +Bug: https://nvd.nist.gov/vuln/detail/CVE-2026-10822 +Last-Update: 2026-07-10 09:33:40 +0200 +--- +diff --git a/bin/tests/system/xfer/ans9/ans.py b/bin/tests/system/xfer/ans9/ans.py +index 25da6b7..a9e7395 100644 +--- a/bin/tests/system/xfer/ans9/ans.py ++++ b/bin/tests/system/xfer/ans9/ans.py +@@ -13,6 +13,7 @@ information regarding copyright ownership. + + from collections.abc import AsyncGenerator + ++import dns.name + import dns.rcode + import dns.rdatatype + import dns.rrset +@@ -33,7 +34,7 @@ class AXFRServer(DomainHandler): + version. + """ + +- domains = ["xfr-and-reconfig"] ++ domains = ["xfr-and-reconfig", "private-dns-overrun"] + + def __init__(self) -> None: + super().__init__() +@@ -97,6 +98,34 @@ class AXFRServer(DomainHandler): + + yield DnsResponseSend(txt_message) + ++ if qctx.qname == dns.name.from_text("private-dns-overrun"): ++ # A message where the malformed DNSKEY algorithm identifier ++ # finishes on a 00 byte in the next record. Assumes the ++ # next record starts with a compression pointer which is ++ # followed by the type which starts with 00. ++ ++ # Generate malformed PRIVATE DNS DNSKEY ++ dnskey_message = qctx.prepare_new_response() ++ dnskey_rrset = dns.rrset.from_text( ++ qctx.qname, ++ 300, ++ qctx.qclass, ++ dns.rdatatype.DNSKEY, ++ "\\# 12 00 00 00 fd 09 00 00 00 00 00 00 00", ++ ) ++ dnskey_message.answer.append(dnskey_rrset) ++ # Generate well formed PRIVATE DNS DNSKEY ++ dnskey_rrset = dns.rrset.from_text( ++ qctx.qname, ++ 300, ++ qctx.qclass, ++ dns.rdatatype.DNSKEY, ++ "\\# 12 00 00 00 fd 06 00 00 00 00 00 00 00", ++ ) ++ dnskey_message.answer.append(dnskey_rrset) ++ ++ yield DnsResponseSend(dnskey_message) ++ + # Finish the AXFR transaction by sending the second SOA RRset. + yield DnsResponseSend(soa_message) + +diff --git a/bin/tests/system/xfer/ns6/named.conf.j2 b/bin/tests/system/xfer/ns6/named.conf.j2 +index 80f4281..c262f9d 100644 +--- a/bin/tests/system/xfer/ns6/named.conf.j2 ++++ b/bin/tests/system/xfer/ns6/named.conf.j2 +@@ -118,3 +118,12 @@ zone "ixfr-race" { + primaries { 10.53.0.11; }; + file "ixfr-race.bk"; + }; ++ ++# GL#6004 ++zone "private-dns-overrun" { ++ type secondary; ++ primaries { 10.53.0.9; }; ++ file "private-dns-overrun.bk"; ++ masterfile-format text; # force bug to be exercised ++ request-ixfr no; # ans9 supports only axfr ++}; +diff --git a/bin/tests/system/xfer/tests_xfer.py b/bin/tests/system/xfer/tests_xfer.py +index b87a75f..1060a70 100644 +--- a/bin/tests/system/xfer/tests_xfer.py ++++ b/bin/tests/system/xfer/tests_xfer.py +@@ -544,6 +544,16 @@ def test_reconfiguration_when_zone_transfer_is_in_the_middle_of_soa_query(ns6): + watcher_transfer_started.wait_for_line("Transfer started") + + ++def test_malformed_private_dns_identifier_overrun(ns6): ++ isctest.log.info( ++ "Check that a malformed PRIVATEDNS DNSKEY which overruns the record is rejected" ++ ) ++ with ns6.watch_log_from_start(timeout=60) as watcher_transfer_completed: ++ watcher_transfer_completed.wait_for_line( ++ "zone private-dns-overrun/IN: zone transfer finished: unexpected end of input" ++ ) ++ ++ + # See #5767 + def test_ixfr_race(ns6): + isctest.log.info( +diff --git a/lib/dns/name.c b/lib/dns/name.c +index aede7da..9436220 100644 +--- a/lib/dns/name.c ++++ b/lib/dns/name.c +@@ -1492,7 +1492,7 @@ dns_name_fromwire(dns_name_t *const name, isc_buffer_t *const source, + * The amount of the source we consumed is set once. + */ + const uint8_t *const source_buf = isc_buffer_base(source); +- const uint8_t *const source_max = isc_buffer_used(source); ++ const uint8_t *const source_max = isc_buffer_active(source); + const uint8_t *const start = isc_buffer_current(source); + const uint8_t *marker = start; + const uint8_t *cursor = start; +diff --git a/lib/dns/rdata.c b/lib/dns/rdata.c +index 42549ac..bd18777 100644 +--- a/lib/dns/rdata.c ++++ b/lib/dns/rdata.c +@@ -591,7 +591,7 @@ check_private(isc_buffer_t *source, dns_secalg_t alg) { + dns_fixedname_t fixed; + + RETERR(dns_name_fromwire(dns_fixedname_initname(&fixed), source, +- DNS_DECOMPRESS_DEFAULT, NULL)); ++ DNS_DECOMPRESS_NEVER, NULL)); + } else if (alg == DNS_KEYALG_PRIVATEOID) { + /* + * Check that we can extract the OID from the start of the +diff --git a/tests/dns/rdata_test.c b/tests/dns/rdata_test.c +index 6dd6e0c..61f3a41 100644 +--- a/tests/dns/rdata_test.c ++++ b/tests/dns/rdata_test.c +@@ -2276,6 +2276,25 @@ ISC_RUN_TEST_IMPL(key) { + + check_rdata(text_ok, wire_ok, NULL, false, dns_rdataclass_in, + dns_rdatatype_key, sizeof(dns_rdata_key_t)); ++ ++ /* ++ * A valid PRIVATEDNS record with an active region shorter than the ++ * actual record length. A bug in dns_name_fromwire meant that this ++ * was previously accepted. ++ */ ++ unsigned char key[] = { 0x00, 0x00, 0x00, 253, 0x07, 'e', 'x', ++ 'a', 'm', 'p', 'l', 'e', 0x00 }; ++ unsigned char buf[sizeof(key)]; ++ isc_buffer_t source, target; ++ isc_result_t result; ++ ++ isc_buffer_init(&source, key, sizeof(key)); ++ isc_buffer_add(&source, sizeof(key)); ++ isc_buffer_setactive(&source, sizeof(key) - 1); ++ isc_buffer_init(&target, buf, sizeof(buf)); ++ result = dns_rdata_fromwire(NULL, dns_rdataclass_in, dns_rdatatype_key, ++ &source, DNS_DECOMPRESS_ALWAYS, &target); ++ assert_int_not_equal(result, ISC_R_SUCCESS); + } + + /* diff --git a/debian/patches/CVE-2026-11331.patch b/debian/patches/CVE-2026-11331.patch new file mode 100644 index 00000000..6d47d454 --- /dev/null +++ b/debian/patches/CVE-2026-11331.patch @@ -0,0 +1,20 @@ +Description: CVE-2026-11331 - 安全修复 +Author: Mark Andrews +Origin: https://github.com/isc-projects/bind9/commit/b950da5f625488e2b84575f75052c5f366a4cb06 +Bug: https://nvd.nist.gov/vuln/detail/CVE-2026-11331 +Last-Update: 2026-06-12 +--- +diff --git a/lib/ns/query.c b/lib/ns/query.c +index bd763c6..08f0a1a 100644 +--- a/lib/ns/query.c ++++ b/lib/ns/query.c +@@ -7544,7 +7544,8 @@ query_rpzcname(query_ctx_t *qctx, dns_name_t *cname) { + qctx->fname, NULL); + if (result == DNS_R_NAMETOOLONG) { + client->message->rcode = dns_rcode_yxdomain; +- } else if (result != ISC_R_SUCCESS) { ++ } ++ if (result != ISC_R_SUCCESS) { + return result; + } + } else { diff --git a/debian/patches/CVE-2026-11605.patch b/debian/patches/CVE-2026-11605.patch new file mode 100644 index 00000000..1c160387 --- /dev/null +++ b/debian/patches/CVE-2026-11605.patch @@ -0,0 +1,21 @@ +Description: CVE-2026-11605 - 安全修复 +Author: Ondřej Surý +Origin: https://github.com/isc-projects/bind9/commit/c3388378a7699c956603bc405f73e38d2c344dc8 +Bug: https://nvd.nist.gov/vuln/detail/CVE-2026-11605 +Last-Update: 2026-06-07 10:19:44 +0200 +--- +diff --git a/lib/dns/validator.c b/lib/dns/validator.c +index dc7b990..b8267b2 100644 +--- a/lib/dns/validator.c ++++ b/lib/dns/validator.c +@@ -898,6 +898,10 @@ validator_callback_nsec(void *arg) { + switch (eresult) { + case ISC_R_CANCELED: + case ISC_R_SHUTTINGDOWN: ++ case ISC_R_QUOTA: ++ val->attributes |= subvalidator->attributes & ++ (VALATTR_MAXVALIDATIONS | ++ VALATTR_MAXVALIDATIONFAILS); + result = eresult; + break; + case DNS_R_BROKENCHAIN: diff --git a/debian/patches/CVE-2026-11622.patch b/debian/patches/CVE-2026-11622.patch new file mode 100644 index 00000000..1de9f7b9 --- /dev/null +++ b/debian/patches/CVE-2026-11622.patch @@ -0,0 +1,581 @@ +Description: CVE-2026-11622 - 安全修复 +Author: Ondřej Surý +Origin: https://github.com/isc-projects/bind9/commit/2dabf117e1264fd13fb33096f87e78a039fd1c6c +Bug: https://nvd.nist.gov/vuln/detail/CVE-2026-11622 +Last-Update: Tue, 23 Jun 2026 10:59:38 +0200 +--- +diff --git a/bin/tests/system/reclimit/tests.sh b/bin/tests/system/reclimit/tests.sh +index 10ef4c3..94dce0c 100644 +--- a/bin/tests/system/reclimit/tests.sh ++++ b/bin/tests/system/reclimit/tests.sh +@@ -340,13 +340,13 @@ echo_i "checking that NXDOMAIN names over the max-types-per-name limit don't get + + # Query for 10 NXDOMAIN types + for ntype in $(seq 65270 65279); do +- check_manytypes 1 manytypes.big "TYPE${ntype}" NOERROR big SOA 0 || ret=1 ++ check_manytypes 1 manytypes.big "TYPE${ntype}" NOERROR big SOA 120 || ret=1 + done + # Wait at least 1 second + sleep 1 + # Query for 10 NXDOMAIN types again - these should not be cached + for ntype in $(seq 65270 65279); do +- check_manytypes 2 manytypes.big "TYPE${ntype}" NOERROR big SOA 0 || ret=1 ++ check_manytypes 2 manytypes.big "TYPE${ntype}" NOERROR big SOA 120 || ret=1 + done + + if [ $ret -ne 0 ]; then echo_i "failed"; fi +diff --git a/lib/dns/include/dns/rdataslab.h b/lib/dns/include/dns/rdataslab.h +index 8fc6832..6eeb55d 100644 +--- a/lib/dns/include/dns/rdataslab.h ++++ b/lib/dns/include/dns/rdataslab.h +@@ -46,6 +46,7 @@ + #include + #include + #include ++#include + #include + #include + +@@ -95,6 +96,8 @@ struct dns_slabheader { + isc_stdtime_t last_used; + _Atomic(uint32_t) last_refresh_fail_ts; + ++ isc_refcount_t references; ++ + dns_slabheader_proof_t *noqname; + dns_slabheader_proof_t *closest; + /*%< +diff --git a/lib/dns/qpcache.c b/lib/dns/qpcache.c +index f138678..24b2f2c 100644 +--- a/lib/dns/qpcache.c ++++ b/lib/dns/qpcache.c +@@ -584,13 +584,19 @@ update_header(qpcache_t *qpdb, dns_slabheader_t *header, isc_stdtime_t now) { + + static void + clean_stale_headers(dns_slabheader_t *top) { +- dns_slabheader_t *d = NULL, *down_next = NULL; ++ dns_slabheader_t *d = NULL, *down_next = NULL, *down_parent = top; + + for (d = top->down; d != NULL; d = down_next) { + down_next = d->down; +- dns_slabheader_destroy(&d); ++ d->next = down_parent; ++ ++ if (isc_refcount_current(&d->references) == 0) { ++ dns_slabheader_destroy(&d); ++ down_parent->down = down_next; ++ } else { ++ down_parent = d; ++ } + } +- top->down = NULL; + } + + static void +@@ -604,6 +610,7 @@ clean_cache_node(qpcache_t *qpdb, qpcnode_t *node) { + for (current = node->data; current != NULL; current = top_next) { + top_next = current->next; + clean_stale_headers(current); ++ INSIST(current->down == NULL); + /* + * If current is nonexistent, ancient, or stale and + * we are not keeping stale, we can clean it up. +@@ -974,9 +981,13 @@ setttl(dns_slabheader_t *header, dns_ttl_t newttl) { + + static void + mark_ancient(dns_slabheader_t *header) { +- setttl(header, 0); +- mark(header, DNS_SLABHEADERATTR_ANCIENT); +- HEADERNODE(header)->dirty = 1; ++ if (!ANCIENT(header)) { ++ setttl(header, 0); ++ mark(header, DNS_SLABHEADERATTR_ANCIENT); ++ HEADERNODE(header)->dirty = 1; ++ isc_refcount_decrement(&header->references); ++ clean_stale_headers(header); ++ } + } + + /* +@@ -1065,6 +1076,8 @@ bindrdataset(qpcache_t *qpdb, qpcnode_t *node, dns_slabheader_t *header, + return; + } + ++ isc_refcount_increment(&header->references); ++ + qpcnode_acquire(qpdb, node, nlocktype, tlocktype DNS__DB_FLARG_PASS); + + INSIST(rdataset->methods == NULL); /* We must be disassociated. */ +@@ -2835,6 +2848,7 @@ add(qpcache_t *qpdb, qpcnode_t *qpnode, + dns_slabheader_t *prioheader = NULL, *expireheader = NULL; + bool header_nx; + bool newheader_nx; ++ bool do_expireheader = false; + dns_typepair_t negtype = 0; + dns_trust_t trust; + int idx; +@@ -3239,6 +3253,8 @@ find_header: + } + + if (overmaxtype(qpdb, ntypes)) { ++ do_expireheader = true; ++ + if (expireheader == NULL) { + expireheader = newheader; + } +@@ -3252,14 +3268,6 @@ find_header: + */ + expireheader = newheader; + } +- +- mark_ancient(expireheader); +- /* +- * FIXME: In theory, we should mark the RRSIG +- * and the header at the same time, but there is +- * no direct link between those two header, so +- * we would have to check the whole list again. +- */ + } + } + } +@@ -3269,6 +3277,14 @@ find_header: + addedrdataset DNS__DB_FLARG_PASS); + } + ++ /* ++ * We need to delay the expiration of the header until we are bound to ++ * it to prevent decrement-then-increment on the header references. ++ */ ++ if (do_expireheader) { ++ mark_ancient(expireheader); ++ } ++ + return ISC_R_SUCCESS; + } + +@@ -3721,6 +3737,11 @@ rdatasetiter_destroy(dns_rdatasetiter_t **iteratorp DNS__DB_FLARG) { + + iterator = (qpc_rditer_t *)(*iteratorp); + ++ if (iterator->current != NULL) { ++ isc_refcount_decrement(&iterator->current->references); ++ iterator->current = NULL; ++ } ++ + dns__db_detachnode(iterator->common.db, + &iterator->common.node DNS__DB_FLARG_PASS); + isc_mem_put(iterator->common.db->mctx, iterator, sizeof(*iterator)); +@@ -3790,8 +3811,17 @@ rdatasetiter_first(dns_rdatasetiter_t *it DNS__DB_FLARG) { + } + } + ++ if (header != NULL) { ++ isc_refcount_increment0(&header->references); ++ } ++ + NODE_UNLOCK(nlock, &nlocktype); + ++ if (iterator->current != NULL) { ++ isc_refcount_decrement(&iterator->current->references); ++ iterator->current = NULL; ++ } ++ + iterator->current = header; + + if (header == NULL) { +@@ -3879,8 +3909,17 @@ rdatasetiter_next(dns_rdatasetiter_t *it DNS__DB_FLARG) { + } + } + ++ if (header != NULL) { ++ isc_refcount_increment0(&header->references); ++ } ++ + NODE_UNLOCK(nlock, &nlocktype); + ++ if (iterator->current != NULL) { ++ isc_refcount_decrement(&iterator->current->references); ++ iterator->current = NULL; ++ } ++ + iterator->current = header; + + if (header == NULL) { +diff --git a/lib/dns/qpzone.c b/lib/dns/qpzone.c +index 1b69587..69954ae 100644 +--- a/lib/dns/qpzone.c ++++ b/lib/dns/qpzone.c +@@ -918,6 +918,8 @@ bindrdataset(qpzonedb_t *qpdb, qpznode_t *node, dns_slabheader_t *header, + return; + } + ++ isc_refcount_increment0(&header->references); ++ + qpznode_acquire(qpdb, node DNS__DB_FLARG_PASS); + + INSIST(rdataset->methods == NULL); /* We must be disassociated. */ +@@ -4050,6 +4052,11 @@ rdatasetiter_destroy(dns_rdatasetiter_t **iteratorp DNS__DB_FLARG) { + + qrditer = (qpdb_rdatasetiter_t *)(*iteratorp); + ++ if (qrditer->current != NULL) { ++ isc_refcount_decrement(&qrditer->current->references); ++ qrditer->current = NULL; ++ } ++ + if (qrditer->common.version != NULL) { + closeversion(qrditer->common.db, &qrditer->common.version, + false DNS__DB_FLARG_PASS); +@@ -4092,8 +4099,17 @@ rdatasetiter_first(dns_rdatasetiter_t *iterator DNS__DB_FLARG) { + } + } + ++ if (header != NULL) { ++ isc_refcount_increment0(&header->references); ++ } ++ + NODE_UNLOCK(nlock, &nlocktype); + ++ if (qrditer->current != NULL) { ++ isc_refcount_decrement(&qrditer->current->references); ++ qrditer->current = NULL; ++ } ++ + qrditer->current = header; + + if (header == NULL) { +@@ -4164,8 +4180,17 @@ rdatasetiter_next(dns_rdatasetiter_t *iterator DNS__DB_FLARG) { + } + } + ++ if (header != NULL) { ++ isc_refcount_increment0(&header->references); ++ } ++ + NODE_UNLOCK(nlock, &nlocktype); + ++ if (qrditer->current != NULL) { ++ isc_refcount_decrement(&qrditer->current->references); ++ qrditer->current = NULL; ++ } ++ + qrditer->current = header; + + if (header == NULL) { +diff --git a/lib/dns/rbt-cachedb.c b/lib/dns/rbt-cachedb.c +index 09fc281..91a9ad8 100644 +--- a/lib/dns/rbt-cachedb.c ++++ b/lib/dns/rbt-cachedb.c +@@ -242,17 +242,6 @@ update_cachestats(dns_rbtdb_t *rbtdb, isc_result_t result) { + } + } + +-static void +-clean_stale_headers(dns_slabheader_t *top) { +- dns_slabheader_t *d = NULL, *down_next = NULL; +- +- for (d = top->down; d != NULL; d = down_next) { +- down_next = d->down; +- dns_slabheader_destroy(&d); +- } +- top->down = NULL; +-} +- + static isc_result_t + setup_delegation(rbtdb_search_t *search, dns_dbnode_t **nodep, + dns_name_t *foundname, dns_rdataset_t *rdataset, +@@ -403,7 +392,7 @@ check_stale_header(dns_rbtnode_t *node, dns_slabheader_t *header, + * which case we need to purge the stale + * headers first. + */ +- clean_stale_headers(header); ++ dns__rbtdb_clean_stale_headers(header); + if (*header_prev != NULL) { + (*header_prev)->next = header->next; + } else { +@@ -412,6 +401,7 @@ check_stale_header(dns_rbtnode_t *node, dns_slabheader_t *header, + dns_slabheader_destroy(&header); + } else { + dns__rbtdb_mark_ancient(header); ++ dns__rbtdb_clean_stale_headers(header); + *header_prev = header; + } + } else { +@@ -1394,6 +1384,7 @@ cache_findrdataset(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version, + * argument to the function. + */ + dns__rbtdb_mark_ancient(header); ++ dns__rbtdb_clean_stale_headers(header); + } + } else if (EXISTS(header) && !ANCIENT(header)) { + if (header->type == matchtype) { +@@ -1580,6 +1571,7 @@ dns__cacherbt_expireheader(dns_slabheader_t *header, + isc_rwlocktype_t *tlocktypep, + dns_expire_t reason DNS__DB_FLARG) { + dns__rbtdb_mark_ancient(header); ++ dns__rbtdb_clean_stale_headers(header); + + if (isc_refcount_current(&RBTDB_HEADERNODE(header)->references) == 0) { + isc_rwlocktype_t nlocktype = isc_rwlocktype_write; +diff --git a/lib/dns/rbtdb.c b/lib/dns/rbtdb.c +index 9b904de..e5c1db2 100644 +--- a/lib/dns/rbtdb.c ++++ b/lib/dns/rbtdb.c +@@ -856,20 +856,29 @@ dns__rbtdb_mark(dns_slabheader_t *header, uint_least16_t flag) { + + void + dns__rbtdb_mark_ancient(dns_slabheader_t *header) { +- dns__rbtdb_setttl(header, 0); +- dns__rbtdb_mark(header, DNS_SLABHEADERATTR_ANCIENT); +- RBTDB_HEADERNODE(header)->dirty = 1; ++ if (!ANCIENT(header)) { ++ dns__rbtdb_setttl(header, 0); ++ dns__rbtdb_mark(header, DNS_SLABHEADERATTR_ANCIENT); ++ RBTDB_HEADERNODE(header)->dirty = 1; ++ isc_refcount_decrement(&header->references); ++ } + } + +-static void +-clean_stale_headers(dns_slabheader_t *top) { +- dns_slabheader_t *d = NULL, *down_next = NULL; ++void ++dns__rbtdb_clean_stale_headers(dns_slabheader_t *top) { ++ dns_slabheader_t *d = NULL, *down_next = NULL, *down_parent = top; + + for (d = top->down; d != NULL; d = down_next) { + down_next = d->down; +- dns_slabheader_destroy(&d); ++ d->next = down_parent; ++ ++ if (isc_refcount_current(&d->references) == 0) { ++ dns_slabheader_destroy(&d); ++ down_parent->down = down_next; ++ } else { ++ down_parent = d; ++ } + } +- top->down = NULL; + } + + static void +@@ -882,7 +891,8 @@ clean_cache_node(dns_rbtdb_t *rbtdb, dns_rbtnode_t *node) { + + for (current = node->data; current != NULL; current = top_next) { + top_next = current->next; +- clean_stale_headers(current); ++ dns__rbtdb_clean_stale_headers(current); ++ INSIST(current->down == NULL); + /* + * If current is nonexistent, ancient, or stale and + * we are not keeping stale, we can clean it up. +@@ -2131,6 +2141,8 @@ dns__rbtdb_bindrdataset(dns_rbtdb_t *rbtdb, dns_rbtnode_t *node, + return; + } + ++ isc_refcount_increment(&header->references); ++ + dns__rbtnode_acquire(rbtdb, node, locktype DNS__DB_FLARG_PASS); + + INSIST(rdataset->methods == NULL); /* We must be disassociated. */ +@@ -2549,6 +2561,7 @@ dns__rbtdb_add(dns_rbtdb_t *rbtdb, dns_rbtnode_t *rbtnode, + bool header_nx; + bool newheader_nx; + bool merge; ++ bool do_expireheader = false; + dns_rdatatype_t rdtype, covers; + dns_typepair_t negtype = 0, sigtype; + dns_trust_t trust; +@@ -3108,6 +3121,7 @@ find_header: + } + + if (IS_CACHE(rbtdb) && overmaxtype(rbtdb, ntypes)) { ++ do_expireheader = true; + if (expireheader == NULL) { + expireheader = newheader; + } +@@ -3121,14 +3135,6 @@ find_header: + */ + expireheader = newheader; + } +- +- dns__rbtdb_mark_ancient(expireheader); +- /* +- * FIXME: In theory, we should mark the RRSIG +- * and the header at the same time, but there is +- * no direct link between those two header, so +- * we would have to check the whole list again. +- */ + } + } + } +@@ -3153,6 +3159,14 @@ find_header: + addedrdataset DNS__DB_FLARG_PASS); + } + ++ /* ++ * We need to delay the expiration of the header until we are bound to ++ * it to prevent decrement-then-increment on the header references. ++ */ ++ if (do_expireheader) { ++ dns__rbtdb_mark_ancient(expireheader); ++ } ++ + return ISC_R_SUCCESS; + } + +@@ -4131,6 +4145,11 @@ rdatasetiter_destroy(dns_rdatasetiter_t **iteratorp DNS__DB_FLARG) { + + rbtiterator = (rbtdb_rdatasetiter_t *)(*iteratorp); + ++ if (rbtiterator->current != NULL) { ++ isc_refcount_decrement(&rbtiterator->current->references); ++ rbtiterator->current = NULL; ++ } ++ + if (rbtiterator->common.version != NULL) { + dns__rbtdb_closeversion(rbtiterator->common.db, + &rbtiterator->common.version, +@@ -4210,8 +4229,17 @@ rdatasetiter_first(dns_rdatasetiter_t *iterator DNS__DB_FLARG) { + } + } + ++ if (header != NULL) { ++ isc_refcount_increment0(&header->references); ++ } ++ + NODE_UNLOCK(&rbtdb->node_locks[rbtnode->locknum].lock, &nlocktype); + ++ if (rbtiterator->current != NULL) { ++ isc_refcount_decrement(&rbtiterator->current->references); ++ rbtiterator->current = NULL; ++ } ++ + rbtiterator->current = header; + + if (header == NULL) { +@@ -4303,8 +4331,17 @@ rdatasetiter_next(dns_rdatasetiter_t *iterator DNS__DB_FLARG) { + } + } + ++ if (header != NULL) { ++ isc_refcount_increment0(&header->references); ++ } ++ + NODE_UNLOCK(&rbtdb->node_locks[rbtnode->locknum].lock, &nlocktype); + ++ if (rbtiterator->current != NULL) { ++ isc_refcount_decrement(&rbtiterator->current->references); ++ rbtiterator->current = NULL; ++ } ++ + rbtiterator->current = header; + + if (header == NULL) { +diff --git a/lib/dns/rbtdb_p.h b/lib/dns/rbtdb_p.h +index 1b2c7dc..7a71836 100644 +--- a/lib/dns/rbtdb_p.h ++++ b/lib/dns/rbtdb_p.h +@@ -494,5 +494,7 @@ dns__cacherbt_expireheader(dns_slabheader_t *header, + void + dns__cacherbt_overmem(dns_rbtdb_t *rbtdb, dns_slabheader_t *newheader, + isc_rwlocktype_t *tlocktypep DNS__DB_FLARG); ++void ++dns__rbtdb_clean_stale_headers(dns_slabheader_t *top); + + ISC_LANG_ENDDECLS +diff --git a/lib/dns/rdataslab.c b/lib/dns/rdataslab.c +index ea2aa92..f059c6a 100644 +--- a/lib/dns/rdataslab.c ++++ b/lib/dns/rdataslab.c +@@ -1101,6 +1101,7 @@ dns_slabheader_reset(dns_slabheader_t *h, dns_db_t *db, dns_dbnode_t *node) { + + atomic_init(&h->attributes, 0); + atomic_init(&h->last_refresh_fail_ts, 0); ++ isc_refcount_init(&h->references, 1); + + STATIC_ASSERT(sizeof(h->attributes) == 2, + "The .attributes field of dns_slabheader_t needs to be " +@@ -1173,6 +1174,22 @@ dns_rdatasetmethods_t dns_rdataslab_rdatasetmethods = { + .getownercase = rdataset_getownercase, + }; + ++dns_rdatasetmethods_t dns_rdataproof_rdatasetmethods = { ++ .disassociate = rdataset_disassociate, ++ .first = rdataset_first, ++ .next = rdataset_next, ++ .current = rdataset_current, ++ .clone = rdataset_clone, ++ .count = rdataset_count, ++ .getnoqname = rdataset_getnoqname, ++ .getclosest = rdataset_getclosest, ++ .settrust = rdataset_settrust, ++ .expire = rdataset_expire, ++ .clearprefetch = rdataset_clearprefetch, ++ .setownercase = rdataset_setownercase, ++ .getownercase = rdataset_getownercase, ++}; ++ + /* Fixed RRSet helper macros */ + + #define DNS_RDATASET_LENGTH 2; +@@ -1190,6 +1207,11 @@ rdataset_disassociate(dns_rdataset_t *rdataset DNS__DB_FLARG) { + dns_db_t *db = rdataset->slab.db; + dns_dbnode_t *node = rdataset->slab.node; + ++ if (rdataset->methods == &dns_rdataslab_rdatasetmethods) { ++ dns_slabheader_t *header = ++ dns_slabheader_fromrdataset(rdataset); ++ isc_refcount_decrement(&header->references); ++ } + dns__db_detachnode(db, &node DNS__DB_FLARG_PASS); + } + +@@ -1295,6 +1317,11 @@ rdataset_clone(dns_rdataset_t *source, dns_rdataset_t *target DNS__DB_FLARG) { + dns_dbnode_t *node = source->slab.node; + dns_dbnode_t *cloned_node = NULL; + ++ if (source->methods == &dns_rdataslab_rdatasetmethods) { ++ dns_slabheader_t *header = dns_slabheader_fromrdataset(source); ++ isc_refcount_increment(&header->references); ++ } ++ + dns__db_attachnode(db, node, &cloned_node DNS__DB_FLARG_PASS); + INSIST(!ISC_LINK_LINKED(target, link)); + *target = *source; +@@ -1334,7 +1361,7 @@ rdataset_getnoqname(dns_rdataset_t *rdataset, dns_name_t *name, + dns__db_attachnode(db, node, + &(dns_dbnode_t *){ NULL } DNS__DB_FLARG_PASS); + *nsec = (dns_rdataset_t){ +- .methods = &dns_rdataslab_rdatasetmethods, ++ .methods = &dns_rdataproof_rdatasetmethods, + .rdclass = db->rdclass, + .type = noqname->type, + .ttl = rdataset->ttl, +@@ -1351,7 +1378,7 @@ rdataset_getnoqname(dns_rdataset_t *rdataset, dns_name_t *name, + dns__db_attachnode(db, node, + &(dns_dbnode_t *){ NULL } DNS__DB_FLARG_PASS); + *nsecsig = (dns_rdataset_t){ +- .methods = &dns_rdataslab_rdatasetmethods, ++ .methods = &dns_rdataproof_rdatasetmethods, + .rdclass = db->rdclass, + .type = dns_rdatatype_rrsig, + .covers = noqname->type, +@@ -1387,7 +1414,7 @@ rdataset_getclosest(dns_rdataset_t *rdataset, dns_name_t *name, + dns__db_attachnode(db, node, + &(dns_dbnode_t *){ NULL } DNS__DB_FLARG_PASS); + *nsec = (dns_rdataset_t){ +- .methods = &dns_rdataslab_rdatasetmethods, ++ .methods = &dns_rdataproof_rdatasetmethods, + .rdclass = db->rdclass, + .type = closest->type, + .ttl = rdataset->ttl, +@@ -1404,7 +1431,7 @@ rdataset_getclosest(dns_rdataset_t *rdataset, dns_name_t *name, + dns__db_attachnode(db, node, + &(dns_dbnode_t *){ NULL } DNS__DB_FLARG_PASS); + *nsecsig = (dns_rdataset_t){ +- .methods = &dns_rdataslab_rdatasetmethods, ++ .methods = &dns_rdataproof_rdatasetmethods, + .rdclass = db->rdclass, + .type = dns_rdatatype_rrsig, + .covers = closest->type, diff --git a/debian/patches/CVE-2026-11721.patch b/debian/patches/CVE-2026-11721.patch new file mode 100644 index 00000000..4fab2672 --- /dev/null +++ b/debian/patches/CVE-2026-11721.patch @@ -0,0 +1,198 @@ +Description: CVE-2026-11721 - 安全修复 +Author: Mark Andrews +Origin: https://github.com/isc-projects/bind9/commit/8a46533cbeab78c215ee035d73967a3a59925375 +Bug: https://nvd.nist.gov/vuln/detail/CVE-2026-11721 +Last-Update: 2026-04-14 +--- +diff --git a/lib/dns/dnssec.c b/lib/dns/dnssec.c +index 3bd7069e8a..9f7e869971 100644 +--- a/lib/dns/dnssec.c ++++ b/lib/dns/dnssec.c +@@ -129,7 +129,7 @@ dns_dnssec_keyfromrdata(const dns_name_t *name, const dns_rdata_t *rdata, + isc_buffer_t b; + isc_region_t r; + +- INSIST(name != NULL); ++ INSIST(DNS_NAME_VALID(name)); + INSIST(rdata != NULL); + INSIST(mctx != NULL); + INSIST(key != NULL); +@@ -186,12 +186,14 @@ dns_dnssec_sign(const dns_name_t *name, dns_rdataset_t *set, dst_key_t *key, + isc_result_t ret; + isc_buffer_t *databuf = NULL; + char data[256 + 8]; ++ unsigned int labels; + unsigned int sigsize; + dns_fixedname_t fnewname; + dns_fixedname_t fsigner; + +- REQUIRE(name != NULL); +- REQUIRE(dns_name_countlabels(name) <= 255); ++ REQUIRE(DNS_NAME_VALID(name)); ++ labels = dns_name_countlabels(name); ++ REQUIRE(labels <= 255 && labels > 0); + REQUIRE(set != NULL); + REQUIRE(key != NULL); + REQUIRE(inception != NULL); +@@ -220,7 +222,7 @@ dns_dnssec_sign(const dns_name_t *name, dns_rdataset_t *set, dst_key_t *key, + + sig.covered = set->type; + sig.algorithm = dst_key_alg(key); +- sig.labels = dns_name_countlabels(name) - 1; ++ sig.labels = labels - 1; + if (dns_name_iswildcard(name)) { + sig.labels--; + } +@@ -363,10 +365,13 @@ dns_dnssec_verify(const dns_name_t *name, dns_rdataset_t *set, dst_key_t *key, + isc_result_t ret; + unsigned char data[300]; + dst_context_t *ctx = NULL; +- int labels = 0; ++ unsigned int labels; ++ unsigned int siglabels; + bool downcase = false; + +- REQUIRE(name != NULL); ++ REQUIRE(DNS_NAME_VALID(name)); ++ labels = dns_name_countlabels(name); ++ REQUIRE(labels > 0); + REQUIRE(set != NULL); + REQUIRE(key != NULL); + REQUIRE(mctx != NULL); +@@ -381,6 +386,21 @@ dns_dnssec_verify(const dns_name_t *name, dns_rdataset_t *set, dst_key_t *key, + return DNS_R_SIGINVALID; + } + ++ /* ++ * The RRSIG labels field can't indicate fewer labels than the ++ * signer. Also the labels shouldn't be greater than that of ++ * the owner name. ++ * ++ * sig.labels doesn't include the root label, so add 1 to account ++ * for it. ++ */ ++ siglabels = sig.labels + 1; ++ if (siglabels < dns_name_countlabels(&sig.signer) || siglabels > labels) ++ { ++ inc_stat(dns_dnssecstats_fail); ++ return DNS_R_SIGINVALID; ++ } ++ + if (isc_serial_lt(sig.timeexpire, sig.timesigned)) { + inc_stat(dns_dnssecstats_fail); + return DNS_R_SIGINVALID; +@@ -462,10 +482,9 @@ again: + * If the name is an expanded wildcard, use the wildcard name. + */ + dns_fixedname_init(&fnewname); +- labels = dns_name_countlabels(name) - 1; + RUNTIME_CHECK(dns_name_downcase(name, dns_fixedname_name(&fnewname), + NULL) == ISC_R_SUCCESS); +- if (labels - sig.labels > 0) { ++ if (labels > siglabels) { + dns_name_split(dns_fixedname_name(&fnewname), sig.labels + 1, + NULL, dns_fixedname_name(&fnewname)); + } +@@ -476,7 +495,7 @@ again: + * Create an envelope for each rdata: . + */ + isc_buffer_init(&envbuf, data, sizeof(data)); +- if (labels - sig.labels > 0) { ++ if (labels > siglabels) { + isc_buffer_putuint8(&envbuf, 1); + isc_buffer_putuint8(&envbuf, '*'); + memmove(data + 2, r.base, r.length); +@@ -571,7 +590,7 @@ cleanup_struct: + inc_stat(dns_dnssecstats_fail); + } + +- if (ret == ISC_R_SUCCESS && labels - sig.labels > 0) { ++ if (ret == ISC_R_SUCCESS && labels > siglabels) { + if (wild != NULL) { + RUNTIME_CHECK(dns_name_concatenate( + dns_wildcardname, +diff --git a/lib/dns/rdata/generic/rrsig_46.c b/lib/dns/rdata/generic/rrsig_46.c +index 80b6fbc711..75ea2e8e90 100644 +--- a/lib/dns/rdata/generic/rrsig_46.c ++++ b/lib/dns/rdata/generic/rrsig_46.c +@@ -23,12 +23,12 @@ + static isc_result_t + fromtext_rrsig(ARGS_FROMTEXT) { + isc_token_t token; +- unsigned char alg, c; ++ unsigned char alg, labels; + long i; + dns_rdatatype_t covered; +- char *e; ++ char *e = NULL; + isc_result_t result; +- dns_name_t name; ++ dns_name_t signer; + isc_buffer_t buffer; + uint32_t time_signed, time_expire; + unsigned int used; +@@ -73,8 +73,8 @@ fromtext_rrsig(ARGS_FROMTEXT) { + if (token.value.as_ulong > 0xffU) { + RETTOK(ISC_R_RANGE); + } +- c = (unsigned char)token.value.as_ulong; +- RETERR(mem_tobuffer(target, &c, 1)); ++ labels = (unsigned char)token.value.as_ulong; ++ RETERR(mem_tobuffer(target, &labels, 1)); + + /* + * Original ttl. +@@ -145,12 +145,20 @@ fromtext_rrsig(ARGS_FROMTEXT) { + */ + RETERR(isc_lex_getmastertoken(lexer, &token, isc_tokentype_string, + false)); +- dns_name_init(&name, NULL); ++ dns_name_init(&signer, NULL); + buffer_fromregion(&buffer, &token.value.as_region); + if (origin == NULL) { + origin = dns_rootname; + } +- RETTOK(dns_name_fromtext(&name, &buffer, origin, options, target)); ++ RETTOK(dns_name_fromtext(&signer, &buffer, origin, options, target)); ++ ++ /* ++ * (RRSIG labels doesn't include the root label, so add one ++ * to normalize it before checking against the signer.) ++ */ ++ if ((unsigned int)(labels + 1) < dns_name_countlabels(&signer)) { ++ RETTOK(ISC_R_RANGE); ++ } + + /* + * Sig. +@@ -297,6 +305,7 @@ fromwire_rrsig(ARGS_FROMWIRE) { + isc_region_t sr; + dns_name_t name; + unsigned char algorithm; ++ unsigned char labels; + + REQUIRE(type == dns_rdatatype_rrsig); + +@@ -320,6 +329,7 @@ fromwire_rrsig(ARGS_FROMWIRE) { + } + + algorithm = sr.base[2]; ++ labels = sr.base[3]; + + isc_buffer_forward(source, 18); + RETERR(mem_tobuffer(target, sr.base, 18)); +@@ -330,6 +340,14 @@ fromwire_rrsig(ARGS_FROMWIRE) { + dns_name_init(&name, NULL); + RETERR(dns_name_fromwire(&name, source, dctx, target)); + ++ /* ++ * (RRSIG labels doesn't include the root label, so add one ++ * to normalize it before checking against the signer.) ++ */ ++ if ((unsigned int)(labels + 1) < dns_name_countlabels(&name)) { ++ RETERR(DNS_R_FORMERR); ++ } ++ + /* + * Sig. + */ diff --git a/debian/patches/CVE-2026-12617.patch b/debian/patches/CVE-2026-12617.patch new file mode 100644 index 00000000..631fa086 --- /dev/null +++ b/debian/patches/CVE-2026-12617.patch @@ -0,0 +1,206 @@ +Description: CVE-2026-12617 - 安全修复 +Author: Colin Vidal +Origin: https://github.com/isc-projects/bind9/commit/773d46d58c693047a5945c8fe40512edd0ac214e +Bug: https://nvd.nist.gov/vuln/detail/CVE-2026-12617 +Last-Update: 2026-06-18 18:17:05 +0200 +--- +diff --git a/lib/dns/resolver.c b/lib/dns/resolver.c +index b46df77..4fe0282 100644 +--- a/lib/dns/resolver.c ++++ b/lib/dns/resolver.c +@@ -675,10 +675,10 @@ fctx_minimize_qname(fetchctx_t *fctx); + static void + fctx_destroy(fetchctx_t *fctx); + static isc_result_t +-ncache_adderesult(dns_message_t *message, dns_db_t *cache, dns_dbnode_t *node, +- dns_rdatatype_t covers, isc_stdtime_t now, dns_ttl_t minttl, +- dns_ttl_t maxttl, bool optout, bool secure, +- dns_rdataset_t *ardataset, isc_result_t *eresultp); ++ncache_adderesult(fetchctx_t *fctx, dns_message_t *message, dns_dbnode_t *node, ++ dns_rdatatype_t covers, isc_stdtime_t now, dns_ttl_t maxttl, ++ bool optout, bool secure, dns_rdataset_t *ardataset, ++ isc_result_t *eresultp); + static void + validated(void *arg); + static void +@@ -5254,6 +5254,46 @@ has_000_label(dns_rdataset_t *nsecset) { + return false; + } + ++/* ++ * After a (non-error) negative-cache add, 'rdataset' is bound to whatever ++ * rdataset the cache authoritatively holds for the queried name and type. ++ * Map that to the result code the fetch should report: ++ * ++ * - A negative cache entry (the one we just added, or a pre-existing one): ++ * DNS_R_NCACHENXDOMAIN or DNS_R_NCACHENXRRSET, depending on NXDOMAIN vs ++ * NODATA. ++ * ++ * - A positive rdataset that was already cached at higher trust, which ++ * caused our negative entry to be discarded (e.g. a CNAME or DNAME cached ++ * by a concurrent query): ISC_R_SUCCESS, because that cached positive ++ * answer is what gets returned. Note the specific case for CNAME and ++ * DNAME *if* the query type is not the same as the rdataset type. There ++ * is a chain to follow *only* if the query type doesn't ask for the CNAME ++ * or the DNAME. ++ */ ++static isc_result_t ++fctx_setresult(fetchctx_t *fctx, dns_rdataset_t *rdataset) { ++ isc_result_t result = ISC_R_SUCCESS; ++ ++ if (NEGATIVE(rdataset)) { ++ result = NXDOMAIN(rdataset) ? DNS_R_NCACHENXDOMAIN ++ : DNS_R_NCACHENXRRSET; ++ } else if (result == ISC_R_SUCCESS && rdataset->type != fctx->type) { ++ switch (rdataset->type) { ++ case dns_rdatatype_cname: ++ result = DNS_R_CNAME; ++ break; ++ case dns_rdatatype_dname: ++ result = DNS_R_DNAME; ++ break; ++ default: ++ break; ++ } ++ } ++ ++ return result; ++} ++ + /* + * The validator has finished. + */ +@@ -5499,8 +5539,7 @@ validated(void *arg) { + ttl = 0; + } + +- result = ncache_adderesult(message, fctx->cache, node, covers, +- now, fctx->res->view->minncachettl, ++ result = ncache_adderesult(fctx, message, node, covers, now, + ttl, val->optout, val->secure, + ardataset, &eresult); + if (result != ISC_R_SUCCESS) { +@@ -5748,23 +5787,7 @@ answer_response: + */ + INSIST(hresp->rdataset != NULL); + if (dns_rdataset_isassociated(hresp->rdataset)) { +- if (NEGATIVE(hresp->rdataset)) { +- INSIST(eresult == DNS_R_NCACHENXDOMAIN || +- eresult == DNS_R_NCACHENXRRSET); +- } else if (eresult == ISC_R_SUCCESS && +- hresp->rdataset->type != fctx->type) +- { +- switch (hresp->rdataset->type) { +- case dns_rdatatype_cname: +- eresult = DNS_R_CNAME; +- break; +- case dns_rdatatype_dname: +- eresult = DNS_R_DNAME; +- break; +- default: +- break; +- } +- } ++ eresult = fctx_setresult(fctx, hresp->rdataset); + } + + hresp->result = eresult; +@@ -6416,24 +6439,7 @@ cache_name(fetchctx_t *fctx, dns_name_t *name, dns_message_t *message, + * resp->result. + */ + if (dns_rdataset_isassociated(resp->rdataset)) { +- if (NEGATIVE(resp->rdataset)) { +- INSIST(eresult == +- DNS_R_NCACHENXDOMAIN || +- eresult == DNS_R_NCACHENXRRSET); +- } else if (eresult == ISC_R_SUCCESS && +- resp->rdataset->type != fctx->type) +- { +- switch (resp->rdataset->type) { +- case dns_rdatatype_cname: +- eresult = DNS_R_CNAME; +- break; +- case dns_rdatatype_dname: +- eresult = DNS_R_DNAME; +- break; +- default: +- break; +- } +- } ++ eresult = fctx_setresult(fctx, resp->rdataset); + } + resp->result = eresult; + if (adbp != NULL && *adbp != NULL) { +@@ -6502,12 +6508,14 @@ cache_message(fetchctx_t *fctx, dns_message_t *message, + * eresult. + */ + static isc_result_t +-ncache_adderesult(dns_message_t *message, dns_db_t *cache, dns_dbnode_t *node, +- dns_rdatatype_t covers, isc_stdtime_t now, dns_ttl_t minttl, +- dns_ttl_t maxttl, bool optout, bool secure, +- dns_rdataset_t *ardataset, isc_result_t *eresultp) { ++ncache_adderesult(fetchctx_t *fctx, dns_message_t *message, dns_dbnode_t *node, ++ dns_rdatatype_t covers, isc_stdtime_t now, dns_ttl_t maxttl, ++ bool optout, bool secure, dns_rdataset_t *ardataset, ++ isc_result_t *eresultp) { + isc_result_t result; + dns_rdataset_t rdataset; ++ dns_db_t *cache = fctx->cache; ++ dns_ttl_t minttl = fctx->res->view->minncachettl; + + if (ardataset == NULL) { + dns_rdataset_init(&rdataset); +@@ -6523,37 +6531,13 @@ ncache_adderesult(dns_message_t *message, dns_db_t *cache, dns_dbnode_t *node, + } + if (result == DNS_R_UNCHANGED || result == ISC_R_SUCCESS) { + /* +- * If the cache now contains a negative entry and we +- * care about whether it is DNS_R_NCACHENXDOMAIN or +- * DNS_R_NCACHENXRRSET then extract it. ++ * The cache settled successfully (DNS_R_UNCHANGED means our ++ * negative entry was discarded in favour of existing ++ * higher-trust data). Either way 'ardataset' is now bound to ++ * the rdataset the cache holds for this name and type; derive ++ * the result code from it. + */ +- if (NEGATIVE(ardataset)) { +- /* +- * The cache data is a negative cache entry. +- */ +- if (NXDOMAIN(ardataset)) { +- *eresultp = DNS_R_NCACHENXDOMAIN; +- } else { +- *eresultp = DNS_R_NCACHENXRRSET; +- } +- } else { +- /* +- * The attempt to add a negative cache entry +- * was rejected. Set *eresultp to reflect +- * the type of the dataset being returned. +- */ +- switch (ardataset->type) { +- case dns_rdatatype_cname: +- *eresultp = DNS_R_CNAME; +- break; +- case dns_rdatatype_dname: +- *eresultp = DNS_R_DNAME; +- break; +- default: +- *eresultp = ISC_R_SUCCESS; +- break; +- } +- } ++ *eresultp = fctx_setresult(fctx, ardataset); + result = ISC_R_SUCCESS; + } + if (ardataset == &rdataset && dns_rdataset_isassociated(ardataset)) { +@@ -6697,8 +6681,7 @@ ncache_message(fetchctx_t *fctx, dns_message_t *message, + ttl = 0; + } + +- result = ncache_adderesult(message, fctx->cache, node, covers, now, +- fctx->res->view->minncachettl, ttl, false, ++ result = ncache_adderesult(fctx, message, node, covers, now, ttl, false, + false, ardataset, &eresult); + if (result != ISC_R_SUCCESS) { + goto unlock; diff --git a/debian/patches/CVE-2026-13204.patch b/debian/patches/CVE-2026-13204.patch new file mode 100644 index 00000000..f41f614c --- /dev/null +++ b/debian/patches/CVE-2026-13204.patch @@ -0,0 +1,132 @@ +Description: CVE-2026-13204 - 安全修复 +Author: Evan Hunt +Origin: https://github.com/isc-projects/bind9/commit/4730903dca9827c748c8f1555d0c99beb5696cfc +Bug: https://nvd.nist.gov/vuln/detail/CVE-2026-13204 +Last-Update: 2026-05-13 20:45:57 -0700 +--- +diff --git a/lib/dns/qpcache.c b/lib/dns/qpcache.c +index 1969d21..f138678 100644 +--- a/lib/dns/qpcache.c ++++ b/lib/dns/qpcache.c +@@ -3281,8 +3281,7 @@ addnoqname(isc_mem_t *mctx, dns_slabheader_t *newheader, uint32_t maxrrperset, + dns_rdataset_t neg = DNS_RDATASET_INIT, negsig = DNS_RDATASET_INIT; + isc_region_t r1 = { .base = NULL }, r2 = { .base = NULL }; + +- result = dns_rdataset_getnoqname(rdataset, &name, &neg, &negsig); +- RUNTIME_CHECK(result == ISC_R_SUCCESS); ++ CHECK(dns_rdataset_getnoqname(rdataset, &name, &neg, &negsig)); + + result = dns_rdataslab_fromrdataset(&neg, mctx, &r1, 0, maxrrperset); + if (result != ISC_R_SUCCESS) { +diff --git a/lib/dns/rbtdb.c b/lib/dns/rbtdb.c +index 386b862..9b904de 100644 +--- a/lib/dns/rbtdb.c ++++ b/lib/dns/rbtdb.c +@@ -3183,7 +3183,9 @@ addnoqname(isc_mem_t *mctx, dns_slabheader_t *newheader, uint32_t maxrrperset, + isc_region_t r1 = { .base = NULL }, r2 = { .base = NULL }; + + result = dns_rdataset_getnoqname(rdataset, &name, &neg, &negsig); +- RUNTIME_CHECK(result == ISC_R_SUCCESS); ++ if (result != ISC_R_SUCCESS) { ++ goto cleanup; ++ } + + result = dns_rdataslab_fromrdataset(&neg, mctx, &r1, 0, maxrrperset); + if (result != ISC_R_SUCCESS) { +diff --git a/lib/dns/rdatalist.c b/lib/dns/rdatalist.c +index 6320f60..4fe11a2 100644 +--- a/lib/dns/rdatalist.c ++++ b/lib/dns/rdatalist.c +@@ -178,6 +178,7 @@ dns_rdatalist_addnoqname(dns_rdataset_t *rdataset, const dns_name_t *name) { + dns_rdataset_t *neg = NULL; + dns_rdataset_t *negsig = NULL; + dns_rdataset_t *rdset; ++ dns_rdataset_t *sigset; + dns_ttl_t ttl; + + REQUIRE(rdataset != NULL); +@@ -185,32 +186,30 @@ dns_rdatalist_addnoqname(dns_rdataset_t *rdataset, const dns_name_t *name) { + for (rdset = ISC_LIST_HEAD(name->list); rdset != NULL; + rdset = ISC_LIST_NEXT(rdset, link)) + { +- if (rdset->rdclass != rdataset->rdclass) { +- continue; +- } +- if (rdset->type == dns_rdatatype_nsec || +- rdset->type == dns_rdatatype_nsec3) ++ if (rdset->rdclass != rdataset->rdclass || ++ (rdset->type != dns_rdatatype_nsec && ++ rdset->type != dns_rdatatype_nsec3)) + { +- neg = rdset; ++ continue; + } +- } +- if (neg == NULL) { +- return ISC_R_NOTFOUND; +- } + +- for (rdset = ISC_LIST_HEAD(name->list); rdset != NULL; +- rdset = ISC_LIST_NEXT(rdset, link)) +- { +- if (rdset->type == dns_rdatatype_rrsig && +- rdset->covers == neg->type) ++ for (sigset = ISC_LIST_HEAD(name->list); sigset != NULL; ++ sigset = ISC_LIST_NEXT(sigset, link)) + { +- negsig = rdset; ++ if (sigset->type == dns_rdatatype_rrsig && ++ sigset->covers == rdset->type) ++ { ++ neg = rdset; ++ negsig = sigset; ++ break; ++ } + } + } + +- if (negsig == NULL) { ++ if (neg == NULL || negsig == NULL) { + return ISC_R_NOTFOUND; + } ++ + /* + * Minimise ttl. + */ +@@ -224,6 +223,7 @@ dns_rdatalist_addnoqname(dns_rdataset_t *rdataset, const dns_name_t *name) { + rdataset->ttl = neg->ttl = negsig->ttl = ttl; + rdataset->attributes |= DNS_RDATASETATTR_NOQNAME; + rdataset->rdlist.noqname = name; ++ + return ISC_R_SUCCESS; + } + +diff --git a/lib/dns/resolver.c b/lib/dns/resolver.c +index 4b70963..b46df77 100644 +--- a/lib/dns/resolver.c ++++ b/lib/dns/resolver.c +@@ -5516,7 +5516,9 @@ validated(void *arg) { + if (val->proofs[DNS_VALIDATOR_NOQNAMEPROOF] != NULL) { + result = dns_rdataset_addnoqname( + val->rdataset, val->proofs[DNS_VALIDATOR_NOQNAMEPROOF]); +- RUNTIME_CHECK(result == ISC_R_SUCCESS); ++ if (result != ISC_R_SUCCESS) { ++ goto noanswer_response; ++ } + INSIST(val->sigrdataset != NULL); + val->sigrdataset->ttl = val->rdataset->ttl; + if (val->proofs[DNS_VALIDATOR_CLOSESTENCLOSER] != NULL) { +diff --git a/lib/ns/query.c b/lib/ns/query.c +index 231ad9f..8517b99 100644 +--- a/lib/ns/query.c ++++ b/lib/ns/query.c +@@ -7884,8 +7884,7 @@ query_addnoqnameproof(query_ctx_t *qctx) { + neg = ns_client_newrdataset(client); + negsig = ns_client_newrdataset(client); + +- result = dns_rdataset_getnoqname(qctx->noqname, fname, neg, negsig); +- RUNTIME_CHECK(result == ISC_R_SUCCESS); ++ CHECK(dns_rdataset_getnoqname(qctx->noqname, fname, neg, negsig)); + + query_addrrset(qctx, &fname, &neg, &negsig, dbuf, + DNS_SECTION_AUTHORITY); diff --git a/debian/patches/CVE-2026-13321.patch b/debian/patches/CVE-2026-13321.patch new file mode 100644 index 00000000..2c123773 --- /dev/null +++ b/debian/patches/CVE-2026-13321.patch @@ -0,0 +1,184 @@ +Description: CVE-2026-13321 - 安全修复 +Author: Aydın Mercan +Origin: https://github.com/isc-projects/bind9/commit/27ceccfe778d974840bc5267162bbac58049fe98 +Bug: https://nvd.nist.gov/vuln/detail/CVE-2026-13321 +Last-Update: 2026-05-12 +--- +diff --git a/lib/dns/include/dns/nsec.h b/lib/dns/include/dns/nsec.h +index 50df8e4..1e71bf1 100644 +--- a/lib/dns/include/dns/nsec.h ++++ b/lib/dns/include/dns/nsec.h +@@ -119,13 +119,21 @@ dns_nsec_noexistnodata(dns_rdatatype_t type, const dns_name_t *name, + */ + + bool +-dns_nsec_requiredtypespresent(dns_rdataset_t *rdataset); +-/* +- * Return true if all the NSEC records in rdataset have both +- * NSEC and RRSIG present. ++dns_nsec_is_legal(dns_rdataset_t *rdataset, const dns_name_t *name); ++/**< ++ * \brief ++ * Validates a rdataset of type NSEC. + * +- * Requires: ++ * This functions checks for the following in the given rdataset: ++ * \li All NSEC records have both NSEC and RRSIG present ++ * \li All NSEC entries are under the `name` ++ * ++ * \par Requires: + * \li rdataset to be a NSEC rdataset. ++ * \li `name` is a valid dns_name_t ++ * ++ * \retval true if all the checks pass ++ * \retval false otherwise + */ + + ISC_LANG_ENDDECLS +diff --git a/lib/dns/nsec.c b/lib/dns/nsec.c +index 255b9e9..dc48995 100644 +--- a/lib/dns/nsec.c ++++ b/lib/dns/nsec.c +@@ -21,6 +21,7 @@ + #include + + #include ++#include + #include + #include + #include +@@ -499,8 +500,9 @@ dns_nsec_noexistnodata(dns_rdatatype_t type, const dns_name_t *name, + } + + bool +-dns_nsec_requiredtypespresent(dns_rdataset_t *nsecset) { +- dns_rdataset_t rdataset; ++dns_nsec_is_legal(dns_rdataset_t *nsecset, const dns_name_t *name) { ++ dns_rdataset_t rdataset = DNS_RDATASET_INIT; ++ dns_rdata_nsec_t nsec; + isc_result_t result; + bool found = false; + +@@ -515,12 +517,19 @@ dns_nsec_requiredtypespresent(dns_rdataset_t *nsecset) { + { + dns_rdata_t rdata = DNS_RDATA_INIT; + dns_rdataset_current(&rdataset, &rdata); +- if (!dns_nsec_typepresent(&rdata, dns_rdatatype_nsec) || +- !dns_nsec_typepresent(&rdata, dns_rdatatype_rrsig)) ++ ++ /* must never fail */ ++ result = dns_rdata_tostruct(&rdata, &nsec, NULL); ++ INSIST(result == ISC_R_SUCCESS); ++ ++ if (!dns_name_issubdomain(&nsec.next, name) || ++ !dns_nsec_typepresent(&rdata, dns_rdatatype_rrsig) || ++ !dns_nsec_typepresent(&rdata, dns_rdatatype_nsec)) + { + dns_rdataset_disassociate(&rdataset); + return false; + } ++ + found = true; + } + dns_rdataset_disassociate(&rdataset); +diff --git a/lib/dns/resolver.c b/lib/dns/resolver.c +index 4fe0282..f819ed8 100644 +--- a/lib/dns/resolver.c ++++ b/lib/dns/resolver.c +@@ -73,7 +73,9 @@ + #include + #include + #include ++#include + #include ++#include + #include + + #ifdef WANT_QUERYTRACE +@@ -5294,6 +5296,36 @@ fctx_setresult(fetchctx_t *fctx, dns_rdataset_t *rdataset) { + return result; + } + ++static bool ++get_and_check_signer_name(dns_name_t *signer, dns_rdataset_t *sigrdataset) { ++ dns_rdata_rrsig_t rrsig; ++ isc_result_t result; ++ dns_rdata_t rdata; ++ ++ if (dns_rdataset_first(sigrdataset) != ISC_R_SUCCESS) { ++ return false; ++ } ++ ++ rdata = (dns_rdata_t)DNS_RDATA_INIT; ++ dns_rdataset_current(sigrdataset, &rdata); ++ result = dns_rdata_tostruct(&rdata, &rrsig, NULL); ++ INSIST(result == ISC_R_SUCCESS); ++ dns_name_copy(&rrsig.signer, signer); ++ ++ while (dns_rdataset_next(sigrdataset) == ISC_R_SUCCESS) { ++ rdata = (dns_rdata_t)DNS_RDATA_INIT; ++ dns_rdataset_current(sigrdataset, &rdata); ++ result = dns_rdata_tostruct(&rdata, &rrsig, NULL); ++ INSIST(result == ISC_R_SUCCESS); ++ ++ if (!dns_name_equal(signer, &rrsig.signer)) { ++ return false; ++ } ++ } ++ ++ return true; ++} ++ + /* + * The validator has finished. + */ +@@ -5323,6 +5355,8 @@ validated(void *arg) { + dns_fixedname_t fwild; + dns_name_t *wild = NULL; + dns_message_t *message = NULL; ++ dns_fixedname_t fsigner; ++ dns_name_t *signer = NULL; + bool done = false; + + valarg = val->arg; +@@ -5687,10 +5721,20 @@ answer_response: + } + + /* +- * Don't cache NSEC if missing NSEC or RRSIG types. ++ * Don't cache if all the RRSIGs don't have the same ++ * signer. ++ */ ++ signer = dns_fixedname_initname(&fsigner); ++ if (!get_and_check_signer_name(signer, sigrdataset)) { ++ continue; ++ } ++ ++ /* ++ * Don't cache NSEC if missing NSEC or RRSIG ++ * types. + */ + if (rdataset->type == dns_rdatatype_nsec && +- !dns_nsec_requiredtypespresent(rdataset)) ++ !dns_nsec_is_legal(rdataset, signer)) + { + continue; + } +diff --git a/lib/ns/query.c b/lib/ns/query.c +index 8517b99..bd763c6 100644 +--- a/lib/ns/query.c ++++ b/lib/ns/query.c +@@ -10166,10 +10166,10 @@ query_coveringnsec(query_ctx_t *qctx) { + } + + /* +- * If NSEC or RRSIG are missing from the type map +- * reject the NSEC RRset. ++ * Check that the NSEC entry is legal. ++ * (NSEC + RRSIG present and the entry isn't out-of-zone) + */ +- if (!dns_nsec_requiredtypespresent(qctx->rdataset)) { ++ if (!dns_nsec_is_legal(qctx->rdataset, signer)) { + goto cleanup; + } + diff --git a/debian/patches/series b/debian/patches/series index e2806182..649a1e8c 100644 --- a/debian/patches/series +++ b/debian/patches/series @@ -1,2 +1,11 @@ 0001-Disable-treat-warnings-as-errors-in-sphinx-build.patch 0002-Disable-RTLD_DEEPBIND-in-Samba-DLZ-module.patch +CVE-2026-13204.patch +CVE-2026-12617.patch +CVE-2026-11622.patch +CVE-2026-13321.patch +CVE-2026-11605.patch +CVE-2026-11331.patch +CVE-2026-10822.patch +CVE-2026-10723.patch +CVE-2026-11721.patch