Open
[sync] PR-358: fix CVE-2026-81563,CVE-2026-81736,CVE-2026-19662,CVE-2026-80274 #367
[sync] PR-358: fix CVE-2026-81563,CVE-2026-81736,CVE-2026-19662,CVE-2026-80274 #367
Open
openeuler-ci-botAdd on Oct 8
Total: 8 files changed+1440-1
@@ -0,0 +1,196 @@
1+From 7186f0b221109d4399df65383a6e1349d3ce1f39 Mon Sep 17 00:00:00 2001
2+From: =?UTF-8?q?Ond=C5=99ej=20Sur=C3=BD?= <ondrej@isc.org>
3+Date: Wed, 12 Aug 2026 21:57:05 +0200
4+Subject: [PATCH] Keep NOQNAME proof slabheaders alive
5+ 
6+Backport the proof-rdataset ownership model from d9701e1fae6 so that
7+proof views cannot outlive the cache slabheader that owns their data.
8+ 
9+Conflict:context adapte
10+Reference:https://gitlab.isc.org/isc-projects/bind9/-/commit/7186f0b221109d4399df65383a6e1349d3ce1f39
11+---
12+ lib/dns/include/dns/rdataset.h | 15 ++++++++
13+ lib/dns/rdataslab.c | 62 +++++++++++++++++++++++++++++-----
14+ 2 files changed, 69 insertions(+), 8 deletions(-)
15+ 
16+diff --git a/lib/dns/include/dns/rdataset.h b/lib/dns/include/dns/rdataset.h
17+index c52ff87..606a0fe 100644
18+--- a/lib/dns/include/dns/rdataset.h
19++++ b/lib/dns/include/dns/rdataset.h
20+@@ -197,6 +197,21 @@ struct dns_rdataset {
21+ dns_slabheader_proof_t *noqname, *closest;
22+ } slab;
23+
24++ /*
25++ * A proof rdataset is a view into a slabheader's noqname or
26++ * closest-encloser proof. The header reference keeps the
27++ * proof memory alive for as long as the view is associated.
28++ * Keep the fields shared with 'slab' at the same offsets.
29++ */
30++ struct {
31++ struct dns_db *db;
32++ dns_dbnode_t *node;
33++ unsigned char *raw;
34++ unsigned char *iter_pos;
35++ unsigned int iter_count;
36++ dns_slabheader_t *header;
37++ } proof;
38++
39+ /*
40+ * A simple rdatalist, plus an optional dbnode used by
41+ * builtin and sdlz.
42+diff --git a/lib/dns/rdataslab.c b/lib/dns/rdataslab.c
43+index 3d9f76b..30ca328 100644
44+--- a/lib/dns/rdataslab.c
45++++ b/lib/dns/rdataslab.c
46+@@ -15,6 +15,7 @@
47+
48+ #include <ctype.h>
49+ #include <stdbool.h>
50++#include <stddef.h>
51+ #include <stdlib.h>
52+
53+ #include <isc/ascii.h>
54+@@ -40,6 +41,16 @@
55+ ((atomic_load_acquire(&(header)->attributes) & \
56+ DNS_SLABHEADERATTR_NONEXISTENT) != 0)
57+
58++STATIC_ASSERT(offsetof(dns_rdataset_t, slab.raw) ==
59++ offsetof(dns_rdataset_t, proof.raw),
60++ "slab and proof raw pointers must have the same offset");
61++STATIC_ASSERT(offsetof(dns_rdataset_t, slab.iter_pos) ==
62++ offsetof(dns_rdataset_t, proof.iter_pos),
63++ "slab and proof iterators must have the same offset");
64++STATIC_ASSERT(offsetof(dns_rdataset_t, slab.iter_count) ==
65++ offsetof(dns_rdataset_t, proof.iter_count),
66++ "slab and proof iterator counts must have the same offset");
67++
68+ /*
69+ * The rdataslab structure allows iteration to occur in both load order
70+ * and DNSSEC order. The structure is as follows:
71+@@ -99,6 +110,8 @@ struct xrdata {
72+
73+ static void
74+ rdataset_disassociate(dns_rdataset_t *rdataset DNS__DB_FLARG);
75++static void
76++rdataproof_disassociate(dns_rdataset_t *rdataset DNS__DB_FLARG);
77+ static isc_result_t
78+ rdataset_first(dns_rdataset_t *rdataset);
79+ static isc_result_t
80+@@ -107,6 +120,8 @@ static void
81+ rdataset_current(dns_rdataset_t *rdataset, dns_rdata_t *rdata);
82+ static void
83+ rdataset_clone(dns_rdataset_t *source, dns_rdataset_t *target DNS__DB_FLARG);
84++static void
85++rdataproof_clone(dns_rdataset_t *source, dns_rdataset_t *target DNS__DB_FLARG);
86+ static unsigned int
87+ rdataset_count(dns_rdataset_t *rdataset);
88+ static isc_result_t
89+@@ -1159,11 +1174,11 @@ dns_rdatasetmethods_t dns_rdataslab_rdatasetmethods = {
90+ };
91+
92+ dns_rdatasetmethods_t dns_rdataproof_rdatasetmethods = {
93+- .disassociate = rdataset_disassociate,
94++ .disassociate = rdataproof_disassociate,
95+ .first = rdataset_first,
96+ .next = rdataset_next,
97+ .current = rdataset_current,
98+- .clone = rdataset_clone,
99++ .clone = rdataproof_clone,
100+ .count = rdataset_count,
101+ .getnoqname = rdataset_getnoqname,
102+ .getclosest = rdataset_getclosest,
103+@@ -1199,6 +1214,15 @@ rdataset_disassociate(dns_rdataset_t *rdataset DNS__DB_FLARG) {
104+ dns__db_detachnode(db, &node DNS__DB_FLARG_PASS);
105+ }
106+
107++static void
108++rdataproof_disassociate(dns_rdataset_t *rdataset DNS__DB_FLARG) {
109++ dns_db_t *db = rdataset->proof.db;
110++ dns_dbnode_t *node = rdataset->proof.node;
111++
112++ isc_refcount_decrement(&rdataset->proof.header->references);
113++ dns__db_detachnode(db, &node DNS__DB_FLARG_PASS);
114++}
115++
116+ static isc_result_t
117+ rdataset_first(dns_rdataset_t *rdataset) {
118+ unsigned char *raw = rdataset->slab.raw;
119+@@ -1315,6 +1339,23 @@ rdataset_clone(dns_rdataset_t *source, dns_rdataset_t *target DNS__DB_FLARG) {
120+ target->slab.iter_count = 0;
121+ }
122+
123++static void
124++rdataproof_clone(dns_rdataset_t *source, dns_rdataset_t *target DNS__DB_FLARG) {
125++ dns_db_t *db = source->proof.db;
126++ dns_dbnode_t *node = source->proof.node;
127++ dns_dbnode_t *cloned_node = NULL;
128++
129++ isc_refcount_increment(&source->proof.header->references);
130++
131++ dns__db_attachnode(db, node, &cloned_node DNS__DB_FLARG_PASS);
132++ INSIST(!ISC_LINK_LINKED(target, link));
133++ *target = *source;
134++ ISC_LINK_INIT(target, link);
135++
136++ target->proof.iter_pos = NULL;
137++ target->proof.iter_count = 0;
138++}
139++
140+ static unsigned int
141+ rdataset_count(dns_rdataset_t *rdataset) {
142+ unsigned char *raw = NULL;
143+@@ -1332,6 +1373,7 @@ rdataset_getnoqname(dns_rdataset_t *rdataset, dns_name_t *name,
144+ dns_rdataset_t *nsecsig DNS__DB_FLARG) {
145+ dns_db_t *db = rdataset->slab.db;
146+ dns_dbnode_t *node = rdataset->slab.node;
147++ dns_slabheader_t *header = dns_slabheader_fromrdataset(rdataset);
148+ const dns_slabheader_proof_t *noqname = rdataset->slab.noqname;
149+
150+ /*
151+@@ -1344,15 +1386,17 @@ rdataset_getnoqname(dns_rdataset_t *rdataset, dns_name_t *name,
152+ */
153+ dns__db_attachnode(db, node,
154+ &(dns_dbnode_t *){ NULL } DNS__DB_FLARG_PASS);
155++ isc_refcount_increment(&header->references);
156+ *nsec = (dns_rdataset_t){
157+ .methods = &dns_rdataproof_rdatasetmethods,
158+ .rdclass = db->rdclass,
159+ .type = noqname->type,
160+ .ttl = rdataset->ttl,
161+ .trust = rdataset->trust,
162+- .slab.db = db,
163+- .slab.node = node,
164+- .slab.raw = noqname->neg,
165++ .proof.db = db,
166++ .proof.node = node,
167++ .proof.raw = noqname->neg,
168++ .proof.header = header,
169+ .link = nsec->link,
170+ .count = nsec->count,
171+ .attributes = nsec->attributes | DNS_RDATASETATTR_KEEPCASE,
172+@@ -1361,6 +1405,7 @@ rdataset_getnoqname(dns_rdataset_t *rdataset, dns_name_t *name,
173+
174+ dns__db_attachnode(db, node,
175+ &(dns_dbnode_t *){ NULL } DNS__DB_FLARG_PASS);
176++ isc_refcount_increment(&header->references);
177+ *nsecsig = (dns_rdataset_t){
178+ .methods = &dns_rdataproof_rdatasetmethods,
179+ .rdclass = db->rdclass,
180+@@ -1368,9 +1413,10 @@ rdataset_getnoqname(dns_rdataset_t *rdataset, dns_name_t *name,
181+ .covers = noqname->type,
182+ .ttl = rdataset->ttl,
183+ .trust = rdataset->trust,
184+- .slab.db = db,
185+- .slab.node = node,
186+- .slab.raw = noqname->negsig,
187++ .proof.db = db,
188++ .proof.node = node,
189++ .proof.raw = noqname->negsig,
190++ .proof.header = header,
191+ .link = nsecsig->link,
192+ .count = nsecsig->count,
193+ .attributes = nsecsig->attributes | DNS_RDATASETATTR_KEEPCASE,
194+--
195+2.43.0
196+ 
@@ -0,0 +1,174 @@
1+From 919ae1f876f09a47c2e4ea3c1acc2d8f1f27314e Mon Sep 17 00:00:00 2001
2+From: =?UTF-8?q?Ond=C5=99ej=20Sur=C3=BD?= <ondrej@isc.org>
3+Date: Wed, 12 Aug 2026 21:57:43 +0200
4+Subject: [PATCH] Test NOQNAME proof slabheader lifetime
5+ 
6+Ensure cache replacement cannot reclaim a slabheader while NOQNAME or
7+closest-encloser proof views still reference its data.
8+ 
9+Assisted-by: codex:gpt-5
10+ 
11+Conflict:tests in rbtdb_test.c not merged bacause directory not exists
12+Reference:https://gitlab.isc.org/isc-projects/bind9/-/commit/919ae1f876f09a47c2e4ea3c1acc2d8f1f27314e
13+---
14+ tests/dns/qpdb_test.c | 130 ++++++++++++++++++++++++++++++++++++++++++
15+ 1 file changed, 130 insertions(+)
16+ 
17+diff --git a/tests/dns/qpdb_test.c b/tests/dns/qpdb_test.c
18+index 29b2fad..25fac72 100644
19+--- a/tests/dns/qpdb_test.c
20++++ b/tests/dns/qpdb_test.c
21+@@ -43,6 +43,28 @@
22+ /* Set to true (or use -v option) for verbose output */
23+ static bool verbose = false;
24+
25++static void
26++make_rdatalist(dns_rdatalist_t *rdatalist, dns_rdataset_t *rdataset,
27++ dns_rdata_t *rdata, dns_rdatatype_t type, dns_rdatatype_t covers,
28++ unsigned char *data, size_t length) {
29++ dns_rdata_init(rdata);
30++ rdata->data = data;
31++ rdata->length = length;
32++ rdata->rdclass = dns_rdataclass_in;
33++ rdata->type = type;
34++
35++ dns_rdatalist_init(rdatalist);
36++ rdatalist->rdclass = dns_rdataclass_in;
37++ rdatalist->type = type;
38++ rdatalist->covers = covers;
39++ rdatalist->ttl = 60;
40++ ISC_LIST_APPEND(rdatalist->rdata, rdata, link);
41++
42++ dns_rdataset_init(rdataset);
43++ dns_rdatalist_tordataset(rdatalist, rdataset);
44++ rdataset->trust = dns_trust_answer;
45++}
46++
47+ /*
48+ * Add to a cache DB 'db' an rdataset of type 'rtype' at a name
49+ * <idx>.example.com. The rdataset would contain one data, and rdata_len is
50+@@ -109,6 +131,112 @@ overmempurge_addrdataset(dns_db_t *db, isc_stdtime_t now, int idx,
51+ dns_db_detachnode(db, &node);
52+ }
53+
54++/*
55++ * A noqname or closest-encloser proof rdataset is a view into memory owned by
56++ * the slabheader of its parent rdataset. Replacing the parent must not free
57++ * that memory while a proof view or one of its clones remains associated.
58++ */
59++ISC_LOOP_TEST_IMPL(proof_rdataset_keeps_slabheader) {
60++ isc_result_t result;
61++ dns_db_t *db = NULL;
62++ isc_mem_t *dbmctx = NULL;
63++ isc_stdtime_t now = isc_stdtime_now();
64++ dns_fixedname_t fname, fproof, ffound;
65++ dns_name_t *name = NULL, *proofname = NULL;
66++ dns_dbnode_t *node = NULL;
67++ dns_slabheader_t *oldheader = NULL, *newheader = NULL;
68++ dns_rdatalist_t oldlist, newlist, nseclist, siglist;
69++ dns_rdataset_t oldset, newset, nsecset, sigset;
70++ dns_rdataset_t oldbound, newbound;
71++ dns_rdataset_t noqname, noqnamesig, noqnameclone;
72++ dns_rdata_t oldrdata, newrdata, nsecrdata, sigrdata;
73++ unsigned char olddata[] = { 192, 0, 2, 1 };
74++ unsigned char newdata[] = { 192, 0, 2, 2 };
75++ unsigned char nsecdata[] = { 0 };
76++ unsigned char sigdata[] = { 0 };
77++
78++ isc_mem_create(&dbmctx);
79++ result = dns_db_create(dbmctx, CACHEDB_DEFAULT, dns_rootname,
80++ dns_dbtype_cache, dns_rdataclass_in, 0, NULL,
81++ &db);
82++ assert_int_equal(result, ISC_R_SUCCESS);
83++
84++ dns_test_namefromstring("proof.example.", &fname);
85++ name = dns_fixedname_name(&fname);
86++ dns_test_namefromstring("nsec.example.", &fproof);
87++ proofname = dns_fixedname_name(&fproof);
88++
89++ make_rdatalist(&oldlist, &oldset, &oldrdata, dns_rdatatype_a, 0,
90++ olddata, sizeof(olddata));
91++ make_rdatalist(&newlist, &newset, &newrdata, dns_rdatatype_a, 0,
92++ newdata, sizeof(newdata));
93++ make_rdatalist(&nseclist, &nsecset, &nsecrdata, dns_rdatatype_nsec, 0,
94++ nsecdata, sizeof(nsecdata));
95++ make_rdatalist(&siglist, &sigset, &sigrdata, dns_rdatatype_rrsig,
96++ dns_rdatatype_nsec, sigdata, sizeof(sigdata));
97++
98++ ISC_LIST_APPEND(proofname->list, &nsecset, link);
99++ ISC_LIST_APPEND(proofname->list, &sigset, link);
100++ result = dns_rdataset_addnoqname(&oldset, proofname);
101++ assert_int_equal(result, ISC_R_SUCCESS);
102++
103++ result = dns_db_findnode(db, name, true, &node);
104++ assert_int_equal(result, ISC_R_SUCCESS);
105++
106++ dns_rdataset_init(&oldbound);
107++ result = dns_db_addrdataset(db, node, NULL, now, &oldset, 0, &oldbound);
108++ assert_int_equal(result, ISC_R_SUCCESS);
109++ oldheader = dns_slabheader_fromrdataset(&oldbound);
110++
111++ dns_rdataset_init(&noqname);
112++ dns_rdataset_init(&noqnamesig);
113++ result = dns_rdataset_getnoqname(&oldbound,
114++ dns_fixedname_initname(&ffound),
115++ &noqname, &noqnamesig);
116++ assert_int_equal(result, ISC_R_SUCCESS);
117++ assert_ptr_equal(noqname.proof.header, oldheader);
118++ assert_ptr_equal(noqnamesig.proof.header, oldheader);
119++
120++ dns_rdataset_init(&noqnameclone);
121++ dns_rdataset_clone(&noqname, &noqnameclone);
122++ assert_ptr_equal(noqnameclone.proof.header, oldheader);
123++
124++ /* Leave only the cache and proof views holding the old header. */
125++ dns_rdataset_disassociate(&oldbound);
126++ assert_int_equal(isc_refcount_current(&oldheader->references), 4);
127++
128++ /*
129++ * Replace the parent. The old header becomes ancient and loses its
130++ * cache-owned reference, but the five proof views keep it alive.
131++ */
132++ dns_rdataset_init(&newbound);
133++ result = dns_db_addrdataset(db, node, NULL, now, &newset, 0, &newbound);
134++ assert_int_equal(result, ISC_R_SUCCESS);
135++ newheader = dns_slabheader_fromrdataset(&newbound);
136++ assert_ptr_equal(newheader->down, oldheader);
137++ assert_int_equal(isc_refcount_current(&oldheader->references), 3);
138++
139++ clean_stale_headers(newheader);
140++ assert_ptr_equal(newheader->down, oldheader);
141++ assert_int_equal(dns_rdataset_count(&noqname), 1);
142++ assert_int_equal(dns_rdataset_count(&noqnamesig), 1);
143++ assert_int_equal(dns_rdataset_count(&noqnameclone), 1);
144++
145++ dns_rdataset_disassociate(&noqnameclone);
146++ dns_rdataset_disassociate(&noqname);
147++ dns_rdataset_disassociate(&noqnamesig);
148++ assert_int_equal(isc_refcount_current(&oldheader->references), 0);
149++
150++ clean_stale_headers(newheader);
151++ assert_null(newheader->down);
152++
153++ dns_rdataset_disassociate(&newbound);
154++ dns_db_detachnode(db, &node);
155++ dns_db_detach(&db);
156++ isc_mem_detach(&dbmctx);
157++ isc_loopmgr_shutdown(loopmgr);
158++}
159++
160+ ISC_LOOP_TEST_IMPL(overmempurge_bigrdata) {
161+ size_t maxcache = 2097152U; /* 2MB - same as DNS_CACHE_MINSIZE */
162+ size_t hiwater = maxcache - (maxcache >> 3); /* borrowed from cache.c */
163+@@ -212,6 +340,8 @@ ISC_LOOP_TEST_IMPL(overmempurge_longname) {
164+ ISC_TEST_LIST_START
165+ ISC_TEST_ENTRY_CUSTOM(overmempurge_bigrdata, setup_managers, teardown_managers)
166+ ISC_TEST_ENTRY_CUSTOM(overmempurge_longname, setup_managers, teardown_managers)
167++ISC_TEST_ENTRY_CUSTOM(proof_rdataset_keeps_slabheader, setup_managers,
168++ teardown_managers)
169+ ISC_TEST_LIST_END
170+
171+ ISC_TEST_MAIN
172+--
173+2.43.0
174+ 
@@ -0,0 +1,451 @@
1+From ce70d1950f162116402b7c375ec00937a0b8aab1 Mon Sep 17 00:00:00 2001
2+From: =?UTF-8?q?Ond=C5=99ej=20Sur=C3=BD?= <ondrej@isc.org>
3+Date: Wed, 26 Aug 2026 09:36:19 +0200
4+Subject: [PATCH] Keep NOQNAME proof selection consistent
5+ 
6+The add and get paths of the rdatalist noqname proof could select
7+different denial types when an owner carried both NSEC and NSEC3
8+records, so a proof accepted during validation could fail to be
9+cached. Worse, both paths picked the NSEC/NSEC3 and RRSIG pair by its
10+position in the message, with no knowledge of which type the validator
11+(or findnoqname()) had actually accepted, so the choice silently
12+depended on the order the authoritative server sent the records in and
13+could cache a proof that had been rejected.
14+ 
15+Pass the accepted type down through dns_rdataset_addnoqname() and
16+record it with the rdataset, so one lookup serves both paths and the
17+get path returns exactly the pair that was added.
18+ 
19+(cherry picked from commit 5c9ef5314cc4fea2cba10d4e30b01c4de0d3221e)
20+ 
21+Conflict:context adapte,rbtdb_test.c not exists and not merged
22+Reference:https://gitlab.isc.org/isc-projects/bind9/-/commit/ce70d1950f162116402b7c375ec00937a0b8aab1
23+---
24+ lib/dns/include/dns/rdatalist.h | 3 +-
25+ lib/dns/include/dns/rdataset.h | 27 ++++++---
26+ lib/dns/include/dns/validator.h | 4 ++
27+ lib/dns/rdatalist.c | 101 +++++++++++++-------------------
28+ lib/dns/rdataset.c | 5 +-
29+ lib/dns/resolver.c | 34 +++++++----
30+ lib/dns/validator.c | 3 +
31+ tests/dns/qpdb_test.c | 3 +-
32+ 8 files changed, 100 insertions(+), 80 deletions(-)
33+ 
34+diff --git a/lib/dns/include/dns/rdatalist.h b/lib/dns/include/dns/rdatalist.h
35+index 6e564b6..f711648 100644
36+--- a/lib/dns/include/dns/rdatalist.h
37++++ b/lib/dns/include/dns/rdatalist.h
38+@@ -131,7 +131,8 @@ unsigned int
39+ dns_rdatalist_count(dns_rdataset_t *rdataset);
40+
41+ isc_result_t
42+-dns_rdatalist_addnoqname(dns_rdataset_t *rdataset, const dns_name_t *name);
43++dns_rdatalist_addnoqname(dns_rdataset_t *rdataset, const dns_name_t *name,
44++ dns_rdatatype_t type);
45+
46+ isc_result_t
47+ dns_rdatalist_getnoqname(dns_rdataset_t *rdataset, dns_name_t *name,
48+diff --git a/lib/dns/include/dns/rdataset.h b/lib/dns/include/dns/rdataset.h
49+index 606a0fe..e82290f 100644
50+--- a/lib/dns/include/dns/rdataset.h
51++++ b/lib/dns/include/dns/rdataset.h
52+@@ -85,7 +85,8 @@ typedef struct dns_rdatasetmethods {
53+ dns_rdataset_t *target DNS__DB_FLARG);
54+ unsigned int (*count)(dns_rdataset_t *rdataset);
55+ isc_result_t (*addnoqname)(dns_rdataset_t *rdataset,
56+- const dns_name_t *name);
57++ const dns_name_t *name,
58++ dns_rdatatype_t type);
59+ isc_result_t (*getnoqname)(dns_rdataset_t *rdataset, dns_name_t *name,
60+ dns_rdataset_t *neg,
61+ dns_rdataset_t *negsig DNS__DB_FLARG);
62+@@ -222,9 +223,12 @@ struct dns_rdataset {
63+
64+ /*
65+ * These refer to names passed in by the caller of
66+- * dns_rdataset_addnoqname() and _addclosest()
67++ * dns_rdataset_addnoqname() and _addclosest(),
68++ * and the denial type
69++ * (NSEC or NSEC3) of the proof selected there.
70+ */
71+ const struct dns_name *noqname, *closest;
72++ dns_rdatatype_t noqnametype;
73+ dns_dbnode_t *node;
74+ } rdlist;
75+
76+@@ -598,17 +602,26 @@ dns__rdataset_getnoqname(dns_rdataset_t *rdataset, dns_name_t *name,
77+ */
78+
79+ isc_result_t
80+-dns_rdataset_addnoqname(dns_rdataset_t *rdataset, dns_name_t *name);
81++dns_rdataset_addnoqname(dns_rdataset_t *rdataset, dns_name_t *name,
82++ dns_rdatatype_t type);
83+ /*%<
84+- * Associate a noqname proof with this record.
85++ * Associate a noqname proof with this record: the rdataset of 'type'
86++ * (NSEC or NSEC3) at 'name' together with the RRSIG rdataset covering it.
87+ * Sets #DNS_RDATASETATTR_NOQNAME if successful.
88+ * Adjusts the 'rdataset->ttl' to minimum of the 'rdataset->ttl' and
89+ * the 'nsec'/'nsec3' and 'rrsig(nsec)'/'rrsig(nsec3)' ttl.
90+ *
91+ * Requires:
92+- *\li 'rdataset' to be valid and #DNS_RDATASETATTR_NOQNAME to be set.
93+- *\li 'name' to be valid and have NSEC or NSEC3 and associated RRSIG
94+- * rdatasets.
95++ *\li 'rdataset' to be valid.
96++ *\li 'name' to be valid.
97++ *\li 'type' to be dns_rdatatype_nsec or dns_rdatatype_nsec3.
98++ *
99++ * Returns:
100++ *\li #ISC_R_SUCCESS
101++ *\li #ISC_R_NOTFOUND if 'name' has no rdataset of 'type' or no RRSIG
102++ * rdataset covering it.
103++ *\li #ISC_R_NOTIMPLEMENTED if the rdataset implementation does not
104++ * support noqname proofs.
105+ */
106+
107+ #define dns_rdataset_getclosest(rdataset, name, nsec, nsecsig) \
108+diff --git a/lib/dns/include/dns/validator.h b/lib/dns/include/dns/validator.h
109+index 362e7e7..2047ae2 100644
110+--- a/lib/dns/include/dns/validator.h
111++++ b/lib/dns/include/dns/validator.h
112+@@ -113,6 +113,10 @@ struct dns_validator {
113+ * Proofs to be cached.
114+ */
115+ dns_name_t *proofs[4];
116++ /*
117++ * The denial type (NSEC or NSEC3) of the NOQNAME proof.
118++ */
119++ dns_rdatatype_t noqnametype;
120+ /*
121+ * Optout proof seen.
122+ */
123+diff --git a/lib/dns/rdatalist.c b/lib/dns/rdatalist.c
124+index 4fe11a2..532da34 100644
125+--- a/lib/dns/rdatalist.c
126++++ b/lib/dns/rdatalist.c
127+@@ -19,7 +19,6 @@
128+ #include <isc/util.h>
129+
130+ #include <dns/name.h>
131+-#include <dns/nsec3.h>
132+ #include <dns/rdata.h>
133+ #include <dns/rdatalist.h>
134+ #include <dns/rdataset.h>
135+@@ -173,56 +172,65 @@ dns_rdatalist_count(dns_rdataset_t *rdataset) {
136+ return count;
137+ }
138+
139+-isc_result_t
140+-dns_rdatalist_addnoqname(dns_rdataset_t *rdataset, const dns_name_t *name) {
141++/*
142++ * Find the denial of existence proof of the given type at 'name': the
143++ * NSEC or NSEC3 rdataset together with the RRSIG rdataset covering it.
144++ */
145++static bool
146++findproof(const dns_name_t *name, dns_rdataclass_t rdclass,
147++ dns_rdatatype_t type, dns_rdataset_t **negp,
148++ dns_rdataset_t **negsigp) {
149+ dns_rdataset_t *neg = NULL;
150+ dns_rdataset_t *negsig = NULL;
151+- dns_rdataset_t *rdset;
152+- dns_rdataset_t *sigset;
153+- dns_ttl_t ttl;
154++ dns_rdataset_t *rdset = NULL;
155+
156+- REQUIRE(rdataset != NULL);
157++ REQUIRE(type == dns_rdatatype_nsec || type == dns_rdatatype_nsec3);
158+
159+- for (rdset = ISC_LIST_HEAD(name->list); rdset != NULL;
160+- rdset = ISC_LIST_NEXT(rdset, link))
161+- {
162+- if (rdset->rdclass != rdataset->rdclass ||
163+- (rdset->type != dns_rdatatype_nsec &&
164+- rdset->type != dns_rdatatype_nsec3))
165+- {
166++ ISC_LIST_FOREACH(name->list, rdset, link) {
167++ if (rdset->rdclass != rdclass) {
168+ continue;
169+ }
170+
171+- for (sigset = ISC_LIST_HEAD(name->list); sigset != NULL;
172+- sigset = ISC_LIST_NEXT(sigset, link))
173++ if (rdset->type == type) {
174++ neg = rdset;
175++ } else if (rdset->type == dns_rdatatype_rrsig &&
176++ rdset->covers == type)
177+ {
178+- if (sigset->type == dns_rdatatype_rrsig &&
179+- sigset->covers == rdset->type)
180+- {
181+- neg = rdset;
182+- negsig = sigset;
183+- break;
184+- }
185++ negsig = rdset;
186++ }
187++
188++ if (neg != NULL && negsig != NULL) {
189++ *negp = neg;
190++ *negsigp = negsig;
191++ return true;
192+ }
193+ }
194+
195+- if (neg == NULL || negsig == NULL) {
196++ return false;
197++}
198++
199++isc_result_t
200++dns_rdatalist_addnoqname(dns_rdataset_t *rdataset, const dns_name_t *name,
201++ dns_rdatatype_t type) {
202++ dns_rdataset_t *neg = NULL;
203++ dns_rdataset_t *negsig = NULL;
204++ dns_ttl_t ttl;
205++
206++ REQUIRE(rdataset != NULL);
207++ REQUIRE(DNS_NAME_VALID(name));
208++
209++ if (!findproof(name, rdataset->rdclass, type, &neg, &negsig)) {
210+ return ISC_R_NOTFOUND;
211+ }
212+
213+ /*
214+ * Minimise ttl.
215+ */
216+- ttl = rdataset->ttl;
217+- if (neg->ttl < ttl) {
218+- ttl = neg->ttl;
219+- }
220+- if (negsig->ttl < ttl) {
221+- ttl = negsig->ttl;
222+- }
223++ ttl = ISC_MIN(rdataset->ttl, ISC_MIN(neg->ttl, negsig->ttl));
224+ rdataset->ttl = neg->ttl = negsig->ttl = ttl;
225+ rdataset->attributes |= DNS_RDATASETATTR_NOQNAME;
226+ rdataset->rdlist.noqname = name;
227++ rdataset->rdlist.noqnametype = type;
228+
229+ return ISC_R_SUCCESS;
230+ }
231+@@ -231,7 +239,6 @@ isc_result_t
232+ dns_rdatalist_getnoqname(dns_rdataset_t *rdataset, dns_name_t *name,
233+ dns_rdataset_t *neg,
234+ dns_rdataset_t *negsig DNS__DB_FLARG) {
235+- dns_rdataclass_t rdclass;
236+ dns_rdataset_t *tneg = NULL;
237+ dns_rdataset_t *tnegsig = NULL;
238+ const dns_name_t *noqname;
239+@@ -239,37 +246,13 @@ dns_rdatalist_getnoqname(dns_rdataset_t *rdataset, dns_name_t *name,
240+ REQUIRE(rdataset != NULL);
241+ REQUIRE((rdataset->attributes & DNS_RDATASETATTR_NOQNAME) != 0);
242+
243+- rdclass = rdataset->rdclass;
244+ noqname = rdataset->rdlist.noqname;
245+
246+- (void)dns_name_dynamic(noqname); /* Sanity Check. */
247++ REQUIRE(DNS_NAME_VALID(noqname));
248+
249+- for (rdataset = ISC_LIST_HEAD(noqname->list); rdataset != NULL;
250+- rdataset = ISC_LIST_NEXT(rdataset, link))
251++ if (!findproof(noqname, rdataset->rdclass, rdataset->rdlist.noqnametype,
252++ &tneg, &tnegsig))
253+ {
254+- if (rdataset->rdclass != rdclass) {
255+- continue;
256+- }
257+- if (rdataset->type == dns_rdatatype_nsec ||
258+- rdataset->type == dns_rdatatype_nsec3)
259+- {
260+- tneg = rdataset;
261+- }
262+- }
263+- if (tneg == NULL) {
264+- return ISC_R_NOTFOUND;
265+- }
266+-
267+- for (rdataset = ISC_LIST_HEAD(noqname->list); rdataset != NULL;
268+- rdataset = ISC_LIST_NEXT(rdataset, link))
269+- {
270+- if (rdataset->type == dns_rdatatype_rrsig &&
271+- rdataset->covers == tneg->type)
272+- {
273+- tnegsig = rdataset;
274+- }
275+- }
276+- if (tnegsig == NULL) {
277+ return ISC_R_NOTFOUND;
278+ }
279+
280+diff --git a/lib/dns/rdataset.c b/lib/dns/rdataset.c
281+index dadb1ae..2a2c8e4 100644
282+--- a/lib/dns/rdataset.c
283++++ b/lib/dns/rdataset.c
284+@@ -548,13 +548,14 @@ dns_rdataset_additionaldata(dns_rdataset_t *rdataset,
285+ }
286+
287+ isc_result_t
288+-dns_rdataset_addnoqname(dns_rdataset_t *rdataset, dns_name_t *name) {
289++dns_rdataset_addnoqname(dns_rdataset_t *rdataset, dns_name_t *name,
290++ dns_rdatatype_t type) {
291+ REQUIRE(DNS_RDATASET_VALID(rdataset));
292+ REQUIRE(rdataset->methods != NULL);
293+ if (rdataset->methods->addnoqname == NULL) {
294+ return ISC_R_NOTIMPLEMENTED;
295+ }
296+- return (rdataset->methods->addnoqname)(rdataset, name);
297++ return (rdataset->methods->addnoqname)(rdataset, name, type);
298+ }
299+
300+ isc_result_t
301+diff --git a/lib/dns/resolver.c b/lib/dns/resolver.c
302+index aa7ffc4..286b1fc 100644
303+--- a/lib/dns/resolver.c
304++++ b/lib/dns/resolver.c
305+@@ -688,7 +688,8 @@ add_bad(fetchctx_t *fctx, dns_message_t *rmessage, dns_adbaddrinfo_t *addrinfo,
306+ isc_result_t reason, badnstype_t badtype);
307+ static isc_result_t
308+ findnoqname(fetchctx_t *fctx, dns_message_t *message, dns_name_t *name,
309+- dns_rdatatype_t type, dns_name_t **noqname);
310++ dns_rdatatype_t type, dns_name_t **noqnamep,
311++ dns_rdatatype_t *noqnametypep);
312+
313+ #define fctx_done_detach(fctxp, result) \
314+ if (fctx__done(*fctxp, result, __func__, __FILE__, __LINE__)) { \
315+@@ -5584,7 +5585,8 @@ validated(void *arg) {
316+
317+ if (val->proofs[DNS_VALIDATOR_NOQNAMEPROOF] != NULL) {
318+ result = dns_rdataset_addnoqname(
319+- val->rdataset, val->proofs[DNS_VALIDATOR_NOQNAMEPROOF]);
320++ val->rdataset, val->proofs[DNS_VALIDATOR_NOQNAMEPROOF],
321++ val->noqnametype);
322+ if (result != ISC_R_SUCCESS) {
323+ goto noanswer_response;
324+ }
325+@@ -5601,11 +5603,13 @@ validated(void *arg) {
326+ {
327+ isc_result_t tresult;
328+ dns_name_t *noqname = NULL;
329++ dns_rdatatype_t noqnametype = dns_rdatatype_none;
330+ tresult = findnoqname(fctx, message, val->name,
331+- val->rdataset->type, &noqname);
332++ val->rdataset->type, &noqname,
333++ &noqnametype);
334+ if (tresult == ISC_R_SUCCESS && noqname != NULL) {
335+ tresult = dns_rdataset_addnoqname(val->rdataset,
336+- noqname);
337++ noqname, noqnametype);
338+ RUNTIME_CHECK(tresult == ISC_R_SUCCESS);
339+ }
340+ }
341+@@ -5878,7 +5882,8 @@ fctx_log(void *arg, int level, const char *fmt, ...) {
342+
343+ static isc_result_t
344+ findnoqname(fetchctx_t *fctx, dns_message_t *message, dns_name_t *name,
345+- dns_rdatatype_t type, dns_name_t **noqnamep) {
346++ dns_rdatatype_t type, dns_name_t **noqnamep,
347++ dns_rdatatype_t *noqnametypep) {
348+ dns_rdataset_t *nrdataset, *next, *sigrdataset;
349+ dns_rdata_rrsig_t rrsig;
350+ isc_result_t result;
351+@@ -5896,6 +5901,7 @@ findnoqname(fetchctx_t *fctx, dns_message_t *message, dns_name_t *name,
352+ FCTXTRACE("findnoqname");
353+
354+ REQUIRE(noqnamep != NULL && *noqnamep == NULL);
355++ REQUIRE(noqnametypep != NULL);
356+
357+ /*
358+ * Find the SIG for this rdataset, if we have it.
359+@@ -6006,6 +6012,7 @@ findnoqname(fetchctx_t *fctx, dns_message_t *message, dns_name_t *name,
360+ }
361+ if (sigrdataset != NULL) {
362+ *noqnamep = noqname;
363++ *noqnametypep = found;
364+ }
365+ }
366+ return result;
367+@@ -6244,14 +6251,18 @@ cache_name(fetchctx_t *fctx, dns_name_t *name, dns_message_t *message,
368+ {
369+ isc_result_t tresult;
370+ dns_name_t *noqname = NULL;
371++ dns_rdatatype_t noqnametype =
372++ dns_rdatatype_none;
373+ tresult = findnoqname(
374+ fctx, message, name,
375+- rdataset->type, &noqname);
376++ rdataset->type, &noqname,
377++ &noqnametype);
378+ if (tresult == ISC_R_SUCCESS &&
379+ noqname != NULL)
380+ {
381+ (void)dns_rdataset_addnoqname(
382+- rdataset, noqname);
383++ rdataset, noqname,
384++ noqnametype);
385+ }
386+ }
387+ if ((fctx->options & DNS_FETCHOPT_PREFETCH) !=
388+@@ -6417,12 +6428,15 @@ cache_name(fetchctx_t *fctx, dns_name_t *name, dns_message_t *message,
389+ {
390+ isc_result_t tresult;
391+ dns_name_t *noqname = NULL;
392++ dns_rdatatype_t noqnametype =
393++ dns_rdatatype_none;
394+ tresult = findnoqname(fctx, message, name,
395+- rdataset->type, &noqname);
396++ rdataset->type, &noqname,
397++ &noqnametype);
398+ if (tresult == ISC_R_SUCCESS && noqname != NULL)
399+ {
400+- (void)dns_rdataset_addnoqname(rdataset,
401+- noqname);
402++ (void)dns_rdataset_addnoqname(
403++ rdataset, noqname, noqnametype);
404+ }
405+ }
406+
407+diff --git a/lib/dns/validator.c b/lib/dns/validator.c
408+index 49a3a1a..f4027a3 100644
409+--- a/lib/dns/validator.c
410++++ b/lib/dns/validator.c
411+@@ -885,6 +885,7 @@ validator_callback_nsec(void *arg) {
412+ if (NEEDNOQNAME(val)) {
413+ proofs[DNS_VALIDATOR_NOQNAMEPROOF] =
414+ subvalidator->name;
415++ val->noqnametype = dns_rdatatype_nsec;
416+ }
417+ }
418+ }
419+@@ -2688,6 +2689,7 @@ findnsec3proofs(dns_validator_t *val) {
420+ proofs[DNS_VALIDATOR_NOQNAMEPROOF] == NULL)
421+ {
422+ proofs[DNS_VALIDATOR_NOQNAMEPROOF] = name;
423++ val->noqnametype = dns_rdatatype_nsec3;
424+ } else if (setclosest) {
425+ proofs[DNS_VALIDATOR_CLOSESTENCLOSER] = name;
426+ } else if (NEEDNODATA(val) &&
427+@@ -2718,6 +2720,7 @@ findnsec3proofs(dns_validator_t *val) {
428+ if (!exists && setnearest) {
429+ val->attributes |= VALATTR_FOUNDNOQNAME;
430+ proofs[DNS_VALIDATOR_NOQNAMEPROOF] = name;
431++ val->noqnametype = dns_rdatatype_nsec3;
432+ if (optout) {
433+ val->attributes |= VALATTR_FOUNDOPTOUT;
434+ }
435+diff --git a/tests/dns/qpdb_test.c b/tests/dns/qpdb_test.c
436+index 25fac72..8b778f2 100644
437+--- a/tests/dns/qpdb_test.c
438++++ b/tests/dns/qpdb_test.c
439+@@ -177,7 +177,8 @@ ISC_LOOP_TEST_IMPL(proof_rdataset_keeps_slabheader) {
440+
441+ ISC_LIST_APPEND(proofname->list, &nsecset, link);
442+ ISC_LIST_APPEND(proofname->list, &sigset, link);
443+- result = dns_rdataset_addnoqname(&oldset, proofname);
444++ result = dns_rdataset_addnoqname(&oldset, proofname,
445++ dns_rdatatype_nsec);
446+ assert_int_equal(result, ISC_R_SUCCESS);
447+
448+ result = dns_db_findnode(db, name, true, &node);
449+--
450+2.43.0
451+ 
@@ -0,0 +1,198 @@
1+From 15e0c8b0ae315296498ed2a82d00d18ba43c5db5 Mon Sep 17 00:00:00 2001
2+From: =?UTF-8?q?Ond=C5=99ej=20Sur=C3=BD?= <ondrej@isc.org>
3+Date: Wed, 26 Aug 2026 11:15:08 +0200
4+Subject: [PATCH] Test NOQNAME proof selection in the rdataset unit test
5+ 
6+Cover an owner carrying both denial types in either wire order, signed
7+or not, and check that dns_rdataset_getnoqname() returns the pair
8+selected by dns_rdataset_addnoqname() rather than the first one in
9+the message.
10+ 
11+Assisted-by: Claude:claude-fable-5
12+(cherry picked from commit cd96255d467f73ff18ec5f5c21021b3bf8cf082f)
13+ 
14+Conflict:no
15+Reference:https://gitlab.isc.org/isc-projects/bind9/-/commit/15e0c8b0ae315296498ed2a82d00d18ba43c5db5
16+---
17+ tests/dns/rdataset_test.c | 154 ++++++++++++++++++++++++++++++++++++++
18+ 1 file changed, 154 insertions(+)
19+ 
20+diff --git a/tests/dns/rdataset_test.c b/tests/dns/rdataset_test.c
21+index fc21b29f9d3..e0d2c4dc13b 100644
22+--- a/tests/dns/rdataset_test.c
23++++ b/tests/dns/rdataset_test.c
24+@@ -25,8 +25,13 @@
25+
26+ #include <isc/util.h>
27+
28++#include <dns/fixedname.h>
29++#include <dns/name.h>
30++#include <dns/rdataclass.h>
31++#include <dns/rdatalist.h>
32+ #include <dns/rdataset.h>
33+ #include <dns/rdatastruct.h>
34++#include <dns/rdatatype.h>
35+
36+ #include <tests/dns.h>
37+
38+@@ -101,8 +106,157 @@ ISC_RUN_TEST_IMPL(trimttl) {
39+ assert_int_equal(sigrdataset.ttl, 0);
40+ }
41+
42++/*
43++ * A rdataset at the NOQNAME proof owner, in wire order.
44++ */
45++typedef struct {
46++ dns_rdatatype_t type;
47++ dns_rdatatype_t covers;
48++ dns_ttl_t ttl;
49++} proofset_t;
50++
51++#define ANSWER_TTL 3600
52++#define MAXSETS 4
53++
54++static void
55++addset(dns_name_t *owner, dns_rdatalist_t *rdatalist, dns_rdataset_t *rdataset,
56++ dns_rdatatype_t type, dns_rdatatype_t covers, dns_ttl_t ttl) {
57++ dns_rdatalist_init(rdatalist);
58++ rdatalist->rdclass = dns_rdataclass_in;
59++ rdatalist->type = type;
60++ rdatalist->covers = covers;
61++ rdatalist->ttl = ttl;
62++
63++ dns_rdataset_init(rdataset);
64++ dns_rdatalist_tordataset(rdatalist, rdataset);
65++ if (owner != NULL) {
66++ ISC_LIST_APPEND(owner->list, rdataset, link);
67++ }
68++}
69++
70++/*
71++ * Check that dns_rdataset_addnoqname() selects the proof of 'type' at an
72++ * owner carrying 'sets' in wire order, and that dns_rdataset_getnoqname()
73++ * then returns that same proof rather than one chosen by wire order.
74++ */
75++static void
76++check_noqname(const proofset_t *sets, size_t nsets, dns_rdatatype_t type,
77++ isc_result_t expected) {
78++ dns_fixedname_t fowner;
79++ dns_name_t *owner = dns_fixedname_initname(&fowner);
80++ dns_rdatalist_t rdatalists[MAXSETS], answerlist;
81++ dns_rdataset_t rdatasets[MAXSETS], answer;
82++ dns_rdataset_t neg = DNS_RDATASET_INIT, negsig = DNS_RDATASET_INIT;
83++ dns_name_t found = DNS_NAME_INITEMPTY;
84++ dns_ttl_t ttl = ANSWER_TTL;
85++ isc_result_t result;
86++
87++ assert_true(nsets <= MAXSETS);
88++ result = dns_name_fromstring(owner, "proof.example.", dns_rootname, 0,
89++ NULL);
90++ assert_int_equal(result, ISC_R_SUCCESS);
91++
92++ for (size_t i = 0; i < nsets; i++) {
93++ addset(owner, &rdatalists[i], &rdatasets[i], sets[i].type,
94++ sets[i].covers, sets[i].ttl);
95++ if (sets[i].type == type ||
96++ (sets[i].type == dns_rdatatype_rrsig &&
97++ sets[i].covers == type))
98++ {
99++ ttl = ISC_MIN(ttl, sets[i].ttl);
100++ }
101++ }
102++ addset(NULL, &answerlist, &answer, dns_rdatatype_a, 0, ANSWER_TTL);
103++
104++ result = dns_rdataset_addnoqname(&answer, owner, type);
105++ assert_int_equal(result, expected);
106++ if (result != ISC_R_SUCCESS) {
107++ assert_false((answer.attributes & DNS_RDATASETATTR_NOQNAME) !=
108++ 0);
109++ assert_int_equal(answer.ttl, ANSWER_TTL);
110++ return;
111++ }
112++ assert_true((answer.attributes & DNS_RDATASETATTR_NOQNAME) != 0);
113++ assert_int_equal(answer.ttl, ttl);
114++
115++ result = dns_rdataset_getnoqname(&answer, &found, &neg, &negsig);
116++ assert_int_equal(result, ISC_R_SUCCESS);
117++ assert_true(dns_name_equal(&found, owner));
118++ assert_int_equal(neg.type, type);
119++ assert_int_equal(negsig.type, dns_rdatatype_rrsig);
120++ assert_int_equal(negsig.covers, type);
121++
122++ dns_rdataset_disassociate(&neg);
123++ dns_rdataset_disassociate(&negsig);
124++ dns_rdataset_disassociate(&answer);
125++}
126++
127++#define CHECK_NOQNAME(sets, type, expected) \
128++ check_noqname(sets, sizeof(sets) / sizeof((sets)[0]), type, expected)
129++
130++/* test NOQNAME proof selection by dns_rdataset_{add,get}noqname() */
131++ISC_RUN_TEST_IMPL(noqname) {
132++ /* Signed NSEC followed by an unsigned NSEC3 (#5985). */
133++ const proofset_t nsec_unsigned_nsec3[] = {
134++ { dns_rdatatype_nsec, 0, 300 },
135++ { dns_rdatatype_rrsig, dns_rdatatype_nsec, 200 },
136++ { dns_rdatatype_nsec3, 0, 100 },
137++ };
138++ /* Signed NSEC3 followed by an unsigned NSEC (#6369). */
139++ const proofset_t nsec3_unsigned_nsec[] = {
140++ { dns_rdatatype_nsec3, 0, 300 },
141++ { dns_rdatatype_rrsig, dns_rdatatype_nsec3, 200 },
142++ { dns_rdatatype_nsec, 0, 100 },
143++ };
144++ /* Both denial types signed, in both wire orders. */
145++ const proofset_t nsec_nsec3[] = {
146++ { dns_rdatatype_nsec, 0, 300 },
147++ { dns_rdatatype_rrsig, dns_rdatatype_nsec, 300 },
148++ { dns_rdatatype_nsec3, 0, 200 },
149++ { dns_rdatatype_rrsig, dns_rdatatype_nsec3, 200 },
150++ };
151++ const proofset_t nsec3_nsec[] = {
152++ { dns_rdatatype_nsec3, 0, 200 },
153++ { dns_rdatatype_rrsig, dns_rdatatype_nsec3, 200 },
154++ { dns_rdatatype_nsec, 0, 300 },
155++ { dns_rdatatype_rrsig, dns_rdatatype_nsec, 300 },
156++ };
157++ /* RRSIG preceding the rdataset it covers. */
158++ const proofset_t rrsig_first[] = {
159++ { dns_rdatatype_rrsig, dns_rdatatype_nsec, 300 },
160++ { dns_rdatatype_nsec, 0, 300 },
161++ };
162++ /* No signed denial at all. */
163++ const proofset_t unsigned_only[] = {
164++ { dns_rdatatype_nsec, 0, 300 },
165++ { dns_rdatatype_nsec3, 0, 300 },
166++ };
167++
168++ UNUSED(state);
169++
170++ CHECK_NOQNAME(nsec_unsigned_nsec3, dns_rdatatype_nsec, ISC_R_SUCCESS);
171++ CHECK_NOQNAME(nsec_unsigned_nsec3, dns_rdatatype_nsec3, ISC_R_NOTFOUND);
172++
173++ CHECK_NOQNAME(nsec3_unsigned_nsec, dns_rdatatype_nsec3, ISC_R_SUCCESS);
174++ CHECK_NOQNAME(nsec3_unsigned_nsec, dns_rdatatype_nsec, ISC_R_NOTFOUND);
175++
176++ /* The caller's choice wins regardless of wire order. */
177++ CHECK_NOQNAME(nsec_nsec3, dns_rdatatype_nsec, ISC_R_SUCCESS);
178++ CHECK_NOQNAME(nsec_nsec3, dns_rdatatype_nsec3, ISC_R_SUCCESS);
179++ CHECK_NOQNAME(nsec3_nsec, dns_rdatatype_nsec, ISC_R_SUCCESS);
180++ CHECK_NOQNAME(nsec3_nsec, dns_rdatatype_nsec3, ISC_R_SUCCESS);
181++
182++ CHECK_NOQNAME(rrsig_first, dns_rdatatype_nsec, ISC_R_SUCCESS);
183++
184++ CHECK_NOQNAME(unsigned_only, dns_rdatatype_nsec, ISC_R_NOTFOUND);
185++ CHECK_NOQNAME(unsigned_only, dns_rdatatype_nsec3, ISC_R_NOTFOUND);
186++
187++ check_noqname(NULL, 0, dns_rdatatype_nsec, ISC_R_NOTFOUND);
188++}
189++
190+ ISC_TEST_LIST_START
191+ ISC_TEST_ENTRY(trimttl)
192++ISC_TEST_ENTRY(noqname)
193+ ISC_TEST_LIST_END
194+
195+ ISC_TEST_MAIN
196+--
197+GitLab
198+ 
@@ -0,0 +1,88 @@
1+From 369d8c692441ed821182dafd91ea0f04ecd1b5ed Mon Sep 17 00:00:00 2001
2+From: =?UTF-8?q?Ond=C5=99ej=20Sur=C3=BD?= <ondrej@isc.org>
3+Date: Thu, 27 Aug 2026 07:46:57 +0200
4+Subject: [PATCH] Release the rdataset when SVCB additional-data lookups fail
5+ 
6+The additional-data callback can associate the caller's rdataset and
7+still return an error - query_additional_cb() clones the found RRset
8+before it recurses into that RRset's own additional data, which fails
9+with DNS_R_TOOMANYRECORDS for a target with more records than
10+DNS_RDATASET_MAXADDITIONAL. Returning the error straight away leaked the
11+clone, and with it a reference to the cache node and slab, so an
12+attacker who can make a resolver follow HTTPS/SVCB AliasMode records to
13+fresh oversized targets could pin cache memory without bound.
14+ 
15+Clean up the rdataset on both callback error paths, for the CNAME chain
16+as well as the alias target.
17+ 
18+(cherry picked from commit 36cab5b26e1350bad8046d792e7d4d6111684405)
19+ 
20+Conflict:no
21+Reference:https://gitlab.isc.org/isc-projects/bind9/-/commit/369d8c692441ed821182dafd91ea0f04ecd1b5ed
22+---
23+ lib/dns/rdata/in_1/svcb_64.c | 33 ++++++++++++++++++++++++++-------
24+ 1 file changed, 26 insertions(+), 7 deletions(-)
25+ 
26+diff --git a/lib/dns/rdata/in_1/svcb_64.c b/lib/dns/rdata/in_1/svcb_64.c
27+index 92adf0e493c..2ffb1ebb8ed 100644
28+--- a/lib/dns/rdata/in_1/svcb_64.c
29++++ b/lib/dns/rdata/in_1/svcb_64.c
30+@@ -1082,6 +1082,7 @@ generic_additionaldata_in_svcb(ARGS_ADDLDATA) {
31+ dns_rdataset_t rdataset;
32+ isc_region_t region;
33+ unsigned int cnames = 0;
34++ isc_result_t result;
35+
36+ dns_name_init(&name, offsets);
37+ dns_rdata_toregion(rdata, &region);
38+@@ -1110,10 +1111,16 @@ generic_additionaldata_in_svcb(ARGS_ADDLDATA) {
39+ dns_rdataset_init(&rdataset);
40+ fname = dns_fixedname_initname(&fixed);
41+ do {
42+- RETERR((add)(arg, &name, dns_rdatatype_cname,
43+- &rdataset DNS__DB_FILELINE));
44++ result = (add)(arg, &name, dns_rdatatype_cname,
45++ &rdataset DNS__DB_FILELINE);
46++ if (result != ISC_R_SUCCESS) {
47++ if (dns_rdataset_isassociated(&rdataset)) {
48++ dns__rdataset_disassociate(&rdataset);
49++ }
50++ return result;
51++ }
52++
53+ if (dns_rdataset_isassociated(&rdataset)) {
54+- isc_result_t result;
55+ result = dns_rdataset_first(&rdataset);
56+ if (result == ISC_R_SUCCESS) {
57+ dns_rdata_t current = DNS_RDATA_INIT;
58+@@ -1145,11 +1152,23 @@ generic_additionaldata_in_svcb(ARGS_ADDLDATA) {
59+ * Look up HTTPS/SVCB records when processing the alias form.
60+ */
61+ if (alias) {
62+- RETERR((add)(arg, &name, rdata->type,
63+- &rdataset DNS__DB_FILELINE));
64++ result = (add)(arg, &name, rdata->type,
65++ &rdataset DNS__DB_FILELINE);
66++ if (result != ISC_R_SUCCESS) {
67++ if (dns_rdataset_isassociated(&rdataset)) {
68++ dns_rdataset_disassociate(&rdataset);
69++ }
70++ return result;
71++ }
72++
73+ /*
74+- * Don't return A or AAAA if this is not the last element
75+- * in the HTTP / SVCB chain.
76++ * If the target has an HTTPS/SVCB RRset, the callback has
77++ * already added and followed it, and the client will use it
78++ * next; the target's own A/AAAA would only be dead weight.
79++ *
80++ * If it has none, the alias chain ends here and the client
81++ * resolves the target's A/AAAA directly (RFC 9460 section
82++ * 3), so look those up.
83+ */
84+ if (dns_rdataset_isassociated(&rdataset)) {
85+ dns_rdataset_disassociate(&rdataset);
86+--
87+GitLab
88+ 
@@ -0,0 +1,81 @@
1+From 2cd8f5b681fb70300120eeec12fce62ed6cdaf92 Mon Sep 17 00:00:00 2001
2+From: =?UTF-8?q?Ond=C5=99ej=20Sur=C3=BD?= <ondrej@isc.org>
3+Date: Thu, 27 Aug 2026 09:45:58 +0200
4+Subject: [PATCH] Bound the total additional processing for a single query
5+ 
6+Recursive additional-section processing for HTTPS and SVCB AliasMode
7+records follows the target chain into the cache. The per-RRset and
8+per-path depth limits each bound one path, but nothing limited the total
9+amount of work across the sibling paths, so a cached tree of AliasMode
10+records let a single query walk every cached descendant synchronously.
11+An attacker with an ordinary delegated zone could prime such a tree and
12+then spend one small query to consume a large amount of CPU, denying
13+service to other clients.
14+ 
15+Keep a per-request counter of the additional lookups performed and stop
16+once it is exceeded, bounding the aggregate work regardless of how the
17+cached records are arranged.
18+ 
19+(cherry picked from commit 39e9f5f06c185d0ba4cb3ad7a6faccbe4fa8b245)
20+ 
21+Conflict:context adapte
22+Reference:https://gitlab.isc.org/isc-projects/bind9/-/commit/2cd8f5b681fb70300120eeec12fce62ed6cdaf92
23+---
24+ lib/ns/client.c | 1 +
25+ lib/ns/include/ns/client.h | 1 +
26+ lib/ns/query.c | 8 +++++++-
27+ 3 files changed, 9 insertions(+), 1 deletion(-)
28+ 
29+diff --git a/lib/ns/client.c b/lib/ns/client.c
30+index c10b122..355cc14 100644
31+--- a/lib/ns/client.c
32++++ b/lib/ns/client.c
33+@@ -269,6 +269,7 @@ ns_client_endrequest(ns_client_t *client) {
34+ client->extflags = 0;
35+ client->ednsversion = -1;
36+ client->additionaldepth = 0;
37++ client->additionaltotal = 0;
38+ dns_ecs_init(&client->ecs);
39+ dns_message_reset(client->message, DNS_MESSAGE_INTENTPARSE);
40+
41+diff --git a/lib/ns/include/ns/client.h b/lib/ns/include/ns/client.h
42+index e813897..d2fcc58 100644
43+--- a/lib/ns/include/ns/client.h
44++++ b/lib/ns/include/ns/client.h
45+@@ -185,6 +185,7 @@ struct ns_client {
46+ uint16_t extflags;
47+ int16_t ednsversion; /* -1 noedns */
48+ uint16_t additionaldepth;
49++ uint16_t additionaltotal;
50+ void (*cleanup)(ns_client_t *);
51+ ns_query_t query;
52+ isc_time_t requesttime;
53+diff --git a/lib/ns/query.c b/lib/ns/query.c
54+index 03741dc..2e53b30 100644
55+--- a/lib/ns/query.c
56++++ b/lib/ns/query.c
57+@@ -1791,6 +1791,10 @@ query_additional_cb(void *arg, const dns_name_t *name, dns_rdatatype_t qtype,
58+
59+ CTRACE(ISC_LOG_DEBUG(3), "query_additional_cb");
60+
61++ if (client->additionaltotal++ >= DNS_RDATASET_MAXADDITIONAL * 2) {
62++ return DNS_R_TOOMANYRECORDS;
63++ }
64++
65+ dns_clientinfomethods_init(&cm, ns_client_sourceip);
66+ dns_clientinfo_init(&ci, client, NULL);
67+
68+@@ -2167,7 +2171,9 @@ addname:
69+
70+ cleanup:
71+ CTRACE(ISC_LOG_DEBUG(3), "query_additional_cb: cleanup");
72+- ns_client_putrdataset(client, &rdataset);
73++ if (rdataset != NULL) {
74++ ns_client_putrdataset(client, &rdataset);
75++ }
76+ if (sigrdataset != NULL) {
77+ ns_client_putrdataset(client, &sigrdataset);
78+ }
79+--
80+2.43.0
81+ 
@@ -0,0 +1,238 @@
1+From 12ba78ca6c69438b428f381cbcea308653e9ff04 Mon Sep 17 00:00:00 2001
2+From: =?UTF-8?q?Ond=C5=99ej=20Sur=C3=BD?= <ondrej@isc.org>
3+Date: Thu, 27 Aug 2026 09:28:14 +0200
4+Subject: [PATCH] Add a system test for SVCB/HTTPS AliasMode additional
5+ processing
6+ 
7+A recursive resolver that has cached a tree of HTTPS/SVCB AliasMode
8+records will, when answering a query for the root of that tree, follow
9+every cached descendant while building the additional section. The
10+per-RRset and per-path limits bound one path each but not the aggregate
11+work across sibling paths, so a modest cache plant turns a single query
12+into a large synchronous walk.
13+ 
14+The test plants a 13-way alias tree in the resolver cache and checks
15+that a query for its root does not return dramatically more additional
16+data than a single-level node, which fails until the aggregate amount of
17+additional processing is bounded.
18+ 
19+Assisted-by: Claude:claude-fable-5
20+(cherry picked from commit 222e8942c29c6dc972f50bc2f709f768972c8199)
21+ 
22+Conflict:remove tests in bin/tests/system/additional because directory not exists
23+Reference:https://gitlab.isc.org/isc-projects/bind9/-/commit/12ba78ca6c69438b428f381cbcea308653e9ff04
24+---
25+ bin/tests/system/svcb_alias/ns1/named.conf.j2 | 14 +++
26+ bin/tests/system/svcb_alias/ns1/root.hint | 2 +
27+ bin/tests/system/svcb_alias/ns2/named.conf.j2 | 16 ++++
28+ bin/tests/system/svcb_alias/ns2/root.db | 5 ++
29+ bin/tests/system/svcb_alias/setup.sh | 36 ++++++++
30+ .../system/svcb_alias/tests_svcb_alias.py | 88 +++++++++++++++++++
31+ 6 files changed, 161 insertions(+)
32+ create mode 100644 bin/tests/system/svcb_alias/ns1/named.conf.j2
33+ create mode 100644 bin/tests/system/svcb_alias/ns1/root.hint
34+ create mode 100644 bin/tests/system/svcb_alias/ns2/named.conf.j2
35+ create mode 100644 bin/tests/system/svcb_alias/ns2/root.db
36+ create mode 100755 bin/tests/system/svcb_alias/setup.sh
37+ create mode 100644 bin/tests/system/svcb_alias/tests_svcb_alias.py
38+ 
39+diff --git a/bin/tests/system/svcb_alias/ns1/named.conf.j2 b/bin/tests/system/svcb_alias/ns1/named.conf.j2
40+new file mode 100644
41+index 0000000..6678b7f
42+--- /dev/null
43++++ b/bin/tests/system/svcb_alias/ns1/named.conf.j2
44+@@ -0,0 +1,14 @@
45++{% include "_common/controls.conf.j2" %}
46++options {
47++ {% include_indented "_common/options.conf.j2" %}
48++ recursion yes;
49++ dnssec-validation no;
50++ allow-query { any; };
51++ allow-recursion { any; };
52++ // minimal-responses defaults to no-auth-recursive, the affected setting
53++};
54++
55++zone "." {
56++ type hint;
57++ file "root.hint";
58++};
59+diff --git a/bin/tests/system/svcb_alias/ns1/root.hint b/bin/tests/system/svcb_alias/ns1/root.hint
60+new file mode 100644
61+index 0000000..2a812d2
62+--- /dev/null
63++++ b/bin/tests/system/svcb_alias/ns1/root.hint
64+@@ -0,0 +1,2 @@
65++. IN NS ns2.
66++ns2. IN A 10.53.0.2
67+diff --git a/bin/tests/system/svcb_alias/ns2/named.conf.j2 b/bin/tests/system/svcb_alias/ns2/named.conf.j2
68+new file mode 100644
69+index 0000000..6fb0d95
70+--- /dev/null
71++++ b/bin/tests/system/svcb_alias/ns2/named.conf.j2
72+@@ -0,0 +1,16 @@
73++{% include "_common/controls.conf.j2" %}
74++options {
75++ {% include_indented "_common/options.conf.j2" %}
76++ recursion no;
77++ dnssec-validation no;
78++};
79++
80++zone "." {
81++ type primary;
82++ file "root.db";
83++};
84++
85++zone "tree.example" {
86++ type primary;
87++ file "tree.db";
88++};
89+diff --git a/bin/tests/system/svcb_alias/ns2/root.db b/bin/tests/system/svcb_alias/ns2/root.db
90+new file mode 100644
91+index 0000000..38c3798
92+--- /dev/null
93++++ b/bin/tests/system/svcb_alias/ns2/root.db
94+@@ -0,0 +1,5 @@
95++$TTL 86400
96++. IN SOA ns2. hostmaster.ns2. ( 1 3600 1800 604800 86400 )
97++. IN NS ns2.
98++ns2. IN A 10.53.0.2
99++tree.example. IN NS ns2.
100+diff --git a/bin/tests/system/svcb_alias/setup.sh b/bin/tests/system/svcb_alias/setup.sh
101+new file mode 100755
102+index 0000000..9f6215a
103+--- /dev/null
104++++ b/bin/tests/system/svcb_alias/setup.sh
105+@@ -0,0 +1,36 @@
106++#!/bin/sh
107++
108++# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
109++#
110++# SPDX-License-Identifier: MPL-2.0
111++#
112++# This Source Code Form is subject to the terms of the Mozilla Public
113++# License, v. 2.0. If a copy of the MPL was not distributed with this
114++# file, you can obtain one at https://mozilla.org/MPL/2.0/.
115++#
116++# See the COPYRIGHT file distributed with this work for additional
117++# information regarding copyright ownership.
118++
119++set -e
120++
121++. ../conf.sh
122++
123++# Branching factor equals DNS_RDATASET_MAXADDITIONAL so every RRset passes
124++# the per-RRset additional-processing limit. PARENTS is the highest node
125++# index that still owns an HTTPS RRset; its children are empty leaves.
126++BRANCHES=13
127++PARENTS=182
128++
129++{
130++ echo "\$TTL 43200"
131++ echo "@ IN SOA ns2. hostmaster.ns2. ( 1 3600 1800 604800 86400 )"
132++ echo "@ IN NS ns2."
133++} >ns2/tree.db
134++
135++awk -v b="$BRANCHES" -v p="$PARENTS" 'END {
136++ for (i = 0; i <= p; i++) {
137++ for (j = 1; j <= b; j++) {
138++ printf "n%d\tIN HTTPS\t0 n%d\n", i, b * i + j
139++ }
140++ }
141++}' </dev/null >>ns2/tree.db
142+diff --git a/bin/tests/system/svcb_alias/tests_svcb_alias.py b/bin/tests/system/svcb_alias/tests_svcb_alias.py
143+new file mode 100644
144+index 0000000..c507c60
145+--- /dev/null
146++++ b/bin/tests/system/svcb_alias/tests_svcb_alias.py
147+@@ -0,0 +1,88 @@
148++# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
149++#
150++# SPDX-License-Identifier: MPL-2.0
151++#
152++# This Source Code Form is subject to the terms of the Mozilla Public
153++# License, v. 2.0. If a copy of the MPL was not distributed with this
154++# file, you can obtain one at https://mozilla.org/MPL/2.0/.
155++#
156++# See the COPYRIGHT file distributed with this work for additional
157++# information regarding copyright ownership.
158++
159++import dns.rcode
160++import pytest
161++
162++import isctest
163++
164++pytestmark = pytest.mark.extra_artifacts(
165++ [
166++ "ns2/tree.db",
167++ ]
168++)
169++
170++# Highest node index that owns an HTTPS RRset in ns2/tree.db (setup.sh). Its
171++# children are empty leaves, so the tree served from cache is three levels
172++# deep: n0 (root), n1..n13, n14..n182.
173++PARENTS = 182
174++
175++# DNS_RDATASET_MAXADDITIONAL from lib/dns/include/dns/rdataset.h: the most
176++# records one RRset contributes to a single level of additional processing.
177++MAXADDITIONAL = 13
178++
179++
180++def _additional_rrs(res):
181++ return sum(len(rrset) for rrset in res.additional)
182++
183++
184++def _query_tcp(ns, index):
185++ msg = isctest.query.create(f"n{index}.tree.example.", "HTTPS")
186++ # Query over TCP so the additional section is not capped by the UDP buffer.
187++ res = isctest.query.tcp(msg, ns.ip)
188++ isctest.check.noerror(res)
189++ return res
190++
191++
192++def test_svcb_alias_tree_additional_is_bounded(ns1):
193++ """
194++ A cached tree of HTTPS AliasMode records must not let a single query walk
195++ the whole tree while building the additional section.
196++
197++ ns2 serves a 13-way HTTPS AliasMode tree. Priming the resolver cache
198++ top-down (ascending index, so every node's children are still cache misses
199++ while it is being cached) plants the tree without any single response
200++ walking it. A query for the root then follows every cached descendant:
201++ the per-RRset limit (DNS_RDATASET_MAXADDITIONAL) and the per-path depth
202++ limit (max-restarts) each bound one path, but neither bounds the aggregate
203++ number of records visited across the sibling paths.
204++ """
205++ # Prime the cache with every node that owns an HTTPS RRset.
206++ for i in range(PARENTS + 1):
207++ res = isctest.query.udp(
208++ isctest.query.create(f"n{i}.tree.example.", "HTTPS"),
209++ ns1.ip,
210++ expected_rcode=dns.rcode.NOERROR,
211++ )
212++ isctest.check.noerror(res)
213++
214++ # Root of the full three-level tree.
215++ root = _query_tcp(ns1, 0)
216++ # A node one level down: its grandchildren are empty leaves, so following
217++ # it touches only a single level of the tree. This is roughly the amount
218++ # of additional data a bounded resolver should also produce for the root.
219++ shallow = _query_tcp(ns1, 1)
220++
221++ isctest.log.info(
222++ "additional records: root=%d (names=%d) shallow=%d (names=%d)",
223++ _additional_rrs(root),
224++ len(root.additional),
225++ _additional_rrs(shallow),
226++ len(shallow.additional),
227++ )
228++
229++ # Without an aggregate limit, the root walk visits the whole planted tree
230++ # and its additional section grows with the tree. With the limit in place
231++ # the walk is cut to roughly one level, so the root must not carry more
232++ # than about one extra RRset's worth of additional names beyond a node
233++ # whose own subtree is a single level. This pins the behaviour without
234++ # depending on the limit's exact value.
235++ assert len(root.additional) <= len(shallow.additional) + MAXADDITIONAL
236+--
237+2.43.0
238+ 
Mbind.spec+14-1
@@ -29,7 +29,7 @@ Summary: The Berkeley Internet Name Domain (BIND) DNS (Domain Name System) serv
29Name: bind29Name: bind
30License: MPLv2.030License: MPLv2.0
31Version: 9.20.2131Version: 9.20.21
32-Release: 432+Release: 5
33Epoch: 3233Epoch: 32
34Url: https://www.isc.org/downloads/bind/34Url: https://www.isc.org/downloads/bind/
35#35#
@@ -94,6 +94,13 @@ Patch6030:backport-CVE-2026-13204-01.patch
94Patch6031:backport-CVE-2026-13204-02.patch94Patch6031:backport-CVE-2026-13204-02.patch
95Patch6032:backport-CVE-2026-13204-03.patch95Patch6032:backport-CVE-2026-13204-03.patch
96Patch6033:backport-CVE-2026-11622.patch96Patch6033:backport-CVE-2026-11622.patch
97+Patch6034:backport-CVE-2026-81563.patch
98+Patch6035:backport-CVE-2026-81736-1.patch
99+Patch6036:backport-CVE-2026-81736-2.patch
100+Patch6037:backport-CVE-2026-19662-1.patch
101+Patch6038:backport-CVE-2026-19662-2.patch
102+Patch6039:backport-CVE-2026-80274-1.patch
103+Patch6040:backport-CVE-2026-80274-2.patch
97# Common patches104# Common patches
98 105 
99%{?systemd_ordering}106%{?systemd_ordering}
@@ -923,6 +930,12 @@ fi;
923%endif930%endif
924 931 
925%changelog932%changelog
933+* Thu Sep 24 2026 tangce <tangce1@h-partners.com> - 32:9.20.21-5
934+- Type:CVE
935+- CVE:CVE-2026-81736,CVE-2026-81563,CVE-2026-19662,CVE-2026-80274
936+- SUG:NA
937+- DESC:fix CVE-2026-81736,CVE-2026-81563,CVE-2026-19662,CVE-2026-80274
938+ 
926* Sat Aug 01 2026 tangce <tangce1@h-partners.com> - 32:9.20.21-4939* Sat Aug 01 2026 tangce <tangce1@h-partners.com> - 32:9.20.21-4
927- Type:CVE940- Type:CVE
928- CVE:CVE-2026-10822,CVE-2026-11331,CVE-2026-12617,CVE-2026-13204,CVE-2026-11622941- CVE:CVE-2026-10822,CVE-2026-11331,CVE-2026-12617,CVE-2026-13204,CVE-2026-11622