Open
[sync] PR-358: fix CVE-2026-81563,CVE-2026-81736,CVE-2026-19662,CVE-2026-80274 #367
openeuler-ci-botAdd on Oct 8
[sync] PR-358: fix CVE-2026-81563,CVE-2026-81736,CVE-2026-19662,CVE-2026-80274 #367
Open
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, ®ion); | ||
| 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 | + | ||
| @@ -29,7 +29,7 @@ Summary: The Berkeley Internet Name Domain (BIND) DNS (Domain Name System) serv | |||
| 29 | Name: bind | 29 | Name: bind |
| 30 | License: MPLv2.0 | 30 | License: MPLv2.0 |
| 31 | Version: 9.20.21 | 31 | Version: 9.20.21 |
| 32 | -Release: 4 | 32 | +Release: 5 |
| 33 | Epoch: 32 | 33 | Epoch: 32 |
| 34 | Url: https://www.isc.org/downloads/bind/ | 34 | Url: https://www.isc.org/downloads/bind/ |
| 35 | # | 35 | # |
| @@ -94,6 +94,13 @@ Patch6030:backport-CVE-2026-13204-01.patch | |||
| 94 | Patch6031:backport-CVE-2026-13204-02.patch | 94 | Patch6031:backport-CVE-2026-13204-02.patch |
| 95 | Patch6032:backport-CVE-2026-13204-03.patch | 95 | Patch6032:backport-CVE-2026-13204-03.patch |
| 96 | Patch6033:backport-CVE-2026-11622.patch | 96 | Patch6033: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 patches | 104 | # Common patches |
| 98 | 105 | ||
| 99 | %{?systemd_ordering} | 106 | %{?systemd_ordering} |
| @@ -923,6 +930,12 @@ fi; | |||
| 923 | %endif | 930 | %endif |
| 924 | 931 | ||
| 925 | %changelog | 932 | %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-4 | 939 | * Sat Aug 01 2026 tangce <tangce1@h-partners.com> - 32:9.20.21-4 |
| 927 | - Type:CVE | 940 | - Type:CVE |
| 928 | - CVE:CVE-2026-10822,CVE-2026-11331,CVE-2026-12617,CVE-2026-13204,CVE-2026-11622 | 941 | - CVE:CVE-2026-10822,CVE-2026-11331,CVE-2026-12617,CVE-2026-13204,CVE-2026-11622 |