diff --git a/SOURCES/bind-9.16-CVE-2023-2828.patch b/SOURCES/bind-9.16-CVE-2023-2828.patch new file mode 100644 index 0000000..466dc6e --- /dev/null +++ b/SOURCES/bind-9.16-CVE-2023-2828.patch @@ -0,0 +1,192 @@ +From ed920ea2ae1cc1214b42b82a5149758dbec941a5 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Ond=C5=99ej=20Sur=C3=BD?= +Date: Tue, 30 May 2023 08:46:17 +0200 +Subject: [PATCH] Improve RBT overmem cache cleaning + +When cache memory usage is over the configured cache size (overmem) and +we are cleaning unused entries, it might not be enough to clean just two +entries if the entries to be expired are smaller than the newly added +rdata. This could be abused by an attacker to cause a remote Denial of +Service by possibly running out of the operating system memory. + +Currently, the addrdataset() tries to do a single TTL-based cleaning +considering the serve-stale TTL and then optionally moves to overmem +cleaning if we are in that condition. Then the overmem_purge() tries to +do another single TTL based cleaning from the TTL heap and then continue +with LRU-based cleaning up to 2 entries cleaned. + +Squash the TTL-cleaning mechanism into single call from addrdataset(), +but ignore the serve-stale TTL if we are currently overmem. + +Then instead of having a fixed number of entries to clean, pass the size +of newly added rdatasetheader to the overmem_purge() function and +cleanup at least the size of the newly added data. This prevents the +cache going over the configured memory limit (`max-cache-size`). + +Additionally, refactor the overmem_purge() function to reduce for-loop +nesting for readability. + +(cherry picked from commit f1d9e9ee3859976f403914d20ad2a10855343702) +--- + lib/dns/rbtdb.c | 105 ++++++++++++++++++++++++++++++------------------ + 1 file changed, 65 insertions(+), 40 deletions(-) + +diff --git a/lib/dns/rbtdb.c b/lib/dns/rbtdb.c +index 51178cc877..75f97f5550 100644 +--- a/lib/dns/rbtdb.c ++++ b/lib/dns/rbtdb.c +@@ -599,7 +599,7 @@ static void + expire_header(dns_rbtdb_t *rbtdb, rdatasetheader_t *header, bool tree_locked, + expire_t reason); + static void +-overmem_purge(dns_rbtdb_t *rbtdb, unsigned int locknum_start, isc_stdtime_t now, ++overmem_purge(dns_rbtdb_t *rbtdb, unsigned int locknum_start, size_t purgesize, + bool tree_locked); + static isc_result_t + resign_insert(dns_rbtdb_t *rbtdb, int idx, rdatasetheader_t *newheader); +@@ -6802,6 +6802,16 @@ cleanup: + + static dns_dbmethods_t zone_methods; + ++static size_t ++rdataset_size(rdatasetheader_t *header) { ++ if (!NONEXISTENT(header)) { ++ return (dns_rdataslab_size((unsigned char *)header, ++ sizeof(*header))); ++ } ++ ++ return (sizeof(*header)); ++} ++ + static isc_result_t + addrdataset(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version, + isc_stdtime_t now, dns_rdataset_t *rdataset, unsigned int options, +@@ -6965,7 +6975,8 @@ addrdataset(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version, + } + + if (cache_is_overmem) { +- overmem_purge(rbtdb, rbtnode->locknum, now, tree_locked); ++ overmem_purge(rbtdb, rbtnode->locknum, rdataset_size(newheader), ++ tree_locked); + } + + NODE_LOCK(&rbtdb->node_locks[rbtnode->locknum].lock, +@@ -6984,10 +6995,18 @@ addrdataset(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version, + } + + header = isc_heap_element(rbtdb->heaps[rbtnode->locknum], 1); +- if (header != NULL && header->rdh_ttl + rbtdb->serve_stale_ttl < +- now - RBTDB_VIRTUAL) +- { +- expire_header(rbtdb, header, tree_locked, expire_ttl); ++ if (header != NULL) { ++ dns_ttl_t rdh_ttl = header->rdh_ttl; ++ ++ /* Only account for stale TTL if cache is not overmem */ ++ if (!cache_is_overmem) { ++ rdh_ttl += rbtdb->serve_stale_ttl; ++ } ++ ++ if (rdh_ttl < now - RBTDB_VIRTUAL) { ++ expire_header(rbtdb, header, tree_locked, ++ expire_ttl); ++ } + } + + /* +@@ -10531,52 +10550,58 @@ update_header(dns_rbtdb_t *rbtdb, rdatasetheader_t *header, isc_stdtime_t now) { + ISC_LIST_PREPEND(rbtdb->rdatasets[header->node->locknum], header, link); + } + ++static size_t ++expire_lru_headers(dns_rbtdb_t *rbtdb, unsigned int locknum, size_t purgesize, ++ bool tree_locked) { ++ rdatasetheader_t *header, *header_prev; ++ size_t purged = 0; ++ ++ for (header = ISC_LIST_TAIL(rbtdb->rdatasets[locknum]); ++ header != NULL && purged <= purgesize; header = header_prev) ++ { ++ header_prev = ISC_LIST_PREV(header, link); ++ /* ++ * Unlink the entry at this point to avoid checking it ++ * again even if it's currently used someone else and ++ * cannot be purged at this moment. This entry won't be ++ * referenced any more (so unlinking is safe) since the ++ * TTL was reset to 0. ++ */ ++ ISC_LIST_UNLINK(rbtdb->rdatasets[locknum], header, link); ++ size_t header_size = rdataset_size(header); ++ expire_header(rbtdb, header, tree_locked, expire_lru); ++ purged += header_size; ++ } ++ ++ return (purged); ++} ++ + /*% +- * Purge some expired and/or stale (i.e. unused for some period) cache entries +- * under an overmem condition. To recover from this condition quickly, up to +- * 2 entries will be purged. This process is triggered while adding a new +- * entry, and we specifically avoid purging entries in the same LRU bucket as +- * the one to which the new entry will belong. Otherwise, we might purge +- * entries of the same name of different RR types while adding RRsets from a +- * single response (consider the case where we're adding A and AAAA glue records +- * of the same NS name). ++ * Purge some stale (i.e. unused for some period - LRU based cleaning) cache ++ * entries under the overmem condition. To recover from this condition quickly, ++ * we cleanup entries up to the size of newly added rdata (passed as purgesize). ++ * ++ * This process is triggered while adding a new entry, and we specifically avoid ++ * purging entries in the same LRU bucket as the one to which the new entry will ++ * belong. Otherwise, we might purge entries of the same name of different RR ++ * types while adding RRsets from a single response (consider the case where ++ * we're adding A and AAAA glue records of the same NS name). + */ + static void +-overmem_purge(dns_rbtdb_t *rbtdb, unsigned int locknum_start, isc_stdtime_t now, ++overmem_purge(dns_rbtdb_t *rbtdb, unsigned int locknum_start, size_t purgesize, + bool tree_locked) { +- rdatasetheader_t *header, *header_prev; + unsigned int locknum; +- int purgecount = 2; ++ size_t purged = 0; + + for (locknum = (locknum_start + 1) % rbtdb->node_lock_count; +- locknum != locknum_start && purgecount > 0; ++ locknum != locknum_start && purged <= purgesize; + locknum = (locknum + 1) % rbtdb->node_lock_count) + { + NODE_LOCK(&rbtdb->node_locks[locknum].lock, + isc_rwlocktype_write); + +- header = isc_heap_element(rbtdb->heaps[locknum], 1); +- if (header && header->rdh_ttl < now - RBTDB_VIRTUAL) { +- expire_header(rbtdb, header, tree_locked, expire_ttl); +- purgecount--; +- } +- +- for (header = ISC_LIST_TAIL(rbtdb->rdatasets[locknum]); +- header != NULL && purgecount > 0; header = header_prev) +- { +- header_prev = ISC_LIST_PREV(header, link); +- /* +- * Unlink the entry at this point to avoid checking it +- * again even if it's currently used someone else and +- * cannot be purged at this moment. This entry won't be +- * referenced any more (so unlinking is safe) since the +- * TTL was reset to 0. +- */ +- ISC_LIST_UNLINK(rbtdb->rdatasets[locknum], header, +- link); +- expire_header(rbtdb, header, tree_locked, expire_lru); +- purgecount--; +- } ++ purged += expire_lru_headers(rbtdb, locknum, purgesize - purged, ++ tree_locked); + + NODE_UNLOCK(&rbtdb->node_locks[locknum].lock, + isc_rwlocktype_write); +-- +2.40.1 + diff --git a/SOURCES/bind-9.16-CVE-2023-3341.patch b/SOURCES/bind-9.16-CVE-2023-3341.patch new file mode 100644 index 0000000..97a3bf1 --- /dev/null +++ b/SOURCES/bind-9.16-CVE-2023-3341.patch @@ -0,0 +1,168 @@ +From b137e12dc8118cddee20e372e480a495585e72b6 Mon Sep 17 00:00:00 2001 +From: Petr Mensik +Date: Tue, 19 Sep 2023 12:44:31 +0200 +Subject: [PATCH] Fix CVE-2023-3341 + +6245. [security] Limit the amount of recursion that can be performed + by isccc_cc_fromwire. (CVE-2023-3341) [GL #4152] +--- + lib/isccc/cc.c | 39 ++++++++++++++++++++++++-------- + lib/isccc/include/isccc/result.h | 4 +++- + lib/isccc/result.c | 4 +++- + 3 files changed, 35 insertions(+), 12 deletions(-) + +diff --git a/lib/isccc/cc.c b/lib/isccc/cc.c +index 0be28b9057..3744d0f037 100644 +--- a/lib/isccc/cc.c ++++ b/lib/isccc/cc.c +@@ -50,6 +50,10 @@ + + #define MAX_TAGS 256 + #define DUP_LIFETIME 900 ++#ifndef ISCCC_MAXDEPTH ++#define ISCCC_MAXDEPTH \ ++ 10 /* Big enough for rndc which just sends a string each way. */ ++#endif + + typedef isccc_sexpr_t *sexpr_ptr; + +@@ -480,19 +484,25 @@ verify(isccc_sexpr_t *alist, unsigned char *data, unsigned int length, + + static isc_result_t + table_fromwire(isccc_region_t *source, isccc_region_t *secret, +- uint32_t algorithm, isccc_sexpr_t **alistp); ++ uint32_t algorithm, unsigned int depth, isccc_sexpr_t **alistp); + + static isc_result_t +-list_fromwire(isccc_region_t *source, isccc_sexpr_t **listp); ++list_fromwire(isccc_region_t *source, unsigned int depth, ++ isccc_sexpr_t **listp); + + static isc_result_t +-value_fromwire(isccc_region_t *source, isccc_sexpr_t **valuep) { ++value_fromwire(isccc_region_t *source, unsigned int depth, ++ isccc_sexpr_t **valuep) { + unsigned int msgtype; + uint32_t len; + isccc_sexpr_t *value; + isccc_region_t active; + isc_result_t result; + ++ if (depth > ISCCC_MAXDEPTH) { ++ return (ISCCC_R_MAXDEPTH); ++ } ++ + if (REGION_SIZE(*source) < 1 + 4) { + return (ISC_R_UNEXPECTEDEND); + } +@@ -513,9 +523,9 @@ value_fromwire(isccc_region_t *source, isccc_sexpr_t **valuep) { + result = ISC_R_NOMEMORY; + } + } else if (msgtype == ISCCC_CCMSGTYPE_TABLE) { +- result = table_fromwire(&active, NULL, 0, valuep); ++ result = table_fromwire(&active, NULL, 0, depth + 1, valuep); + } else if (msgtype == ISCCC_CCMSGTYPE_LIST) { +- result = list_fromwire(&active, valuep); ++ result = list_fromwire(&active, depth + 1, valuep); + } else { + result = ISCCC_R_SYNTAX; + } +@@ -525,7 +535,7 @@ value_fromwire(isccc_region_t *source, isccc_sexpr_t **valuep) { + + static isc_result_t + table_fromwire(isccc_region_t *source, isccc_region_t *secret, +- uint32_t algorithm, isccc_sexpr_t **alistp) { ++ uint32_t algorithm, unsigned int depth, isccc_sexpr_t **alistp) { + char key[256]; + uint32_t len; + isc_result_t result; +@@ -535,6 +545,10 @@ table_fromwire(isccc_region_t *source, isccc_region_t *secret, + + REQUIRE(alistp != NULL && *alistp == NULL); + ++ if (depth > ISCCC_MAXDEPTH) { ++ return (ISCCC_R_MAXDEPTH); ++ } ++ + checksum_rstart = NULL; + first_tag = true; + alist = isccc_alist_create(); +@@ -551,7 +565,7 @@ table_fromwire(isccc_region_t *source, isccc_region_t *secret, + GET_MEM(key, len, source->rstart); + key[len] = '\0'; /* Ensure NUL termination. */ + value = NULL; +- result = value_fromwire(source, &value); ++ result = value_fromwire(source, depth + 1, &value); + if (result != ISC_R_SUCCESS) { + goto bad; + } +@@ -589,14 +603,19 @@ bad: + } + + static isc_result_t +-list_fromwire(isccc_region_t *source, isccc_sexpr_t **listp) { ++list_fromwire(isccc_region_t *source, unsigned int depth, ++ isccc_sexpr_t **listp) { + isccc_sexpr_t *list, *value; + isc_result_t result; + ++ if (depth > ISCCC_MAXDEPTH) { ++ return (ISCCC_R_MAXDEPTH); ++ } ++ + list = NULL; + while (!REGION_EMPTY(*source)) { + value = NULL; +- result = value_fromwire(source, &value); ++ result = value_fromwire(source, depth + 1, &value); + if (result != ISC_R_SUCCESS) { + isccc_sexpr_free(&list); + return (result); +@@ -628,7 +647,7 @@ isccc_cc_fromwire(isccc_region_t *source, isccc_sexpr_t **alistp, + return (ISCCC_R_UNKNOWNVERSION); + } + +- return (table_fromwire(source, secret, algorithm, alistp)); ++ return (table_fromwire(source, secret, algorithm, 0, alistp)); + } + + static isc_result_t +diff --git a/lib/isccc/include/isccc/result.h b/lib/isccc/include/isccc/result.h +index 5346babefc..5b6a876d1c 100644 +--- a/lib/isccc/include/isccc/result.h ++++ b/lib/isccc/include/isccc/result.h +@@ -46,8 +46,10 @@ + #define ISCCC_R_CLOCKSKEW (ISC_RESULTCLASS_ISCCC + 4) + /*% Duplicate */ + #define ISCCC_R_DUPLICATE (ISC_RESULTCLASS_ISCCC + 5) ++/*% Maximum recursion depth */ ++#define ISCCC_R_MAXDEPTH (ISC_RESULTCLASS_ISCCC + 6) + +-#define ISCCC_R_NRESULTS 6 /*%< Number of results */ ++#define ISCCC_R_NRESULTS 7 /*%< Number of results */ + + ISC_LANG_BEGINDECLS + +diff --git a/lib/isccc/result.c b/lib/isccc/result.c +index 9285435209..1956cb1655 100644 +--- a/lib/isccc/result.c ++++ b/lib/isccc/result.c +@@ -36,12 +36,14 @@ static const char *text[ISCCC_R_NRESULTS] = { + "bad auth", /* 3 */ + "expired", /* 4 */ + "clock skew", /* 5 */ +- "duplicate" /* 6 */ ++ "duplicate", /* 6 */ ++ "max depth" /* 7 */ + }; + + static const char *ids[ISCCC_R_NRESULTS] = { + "ISCCC_R_UNKNOWNVERSION", "ISCCC_R_SYNTAX", "ISCCC_R_BADAUTH", + "ISCCC_R_EXPIRED", "ISCCC_R_CLOCKSKEW", "ISCCC_R_DUPLICATE", ++ "ISCCC_R_MAXDEPTH" + }; + + #define ISCCC_RESULT_RESULTSET 2 +-- +2.41.0 + diff --git a/SOURCES/bind-9.16-CVE-2023-4408-test1.patch b/SOURCES/bind-9.16-CVE-2023-4408-test1.patch new file mode 100644 index 0000000..53c42bb --- /dev/null +++ b/SOURCES/bind-9.16-CVE-2023-4408-test1.patch @@ -0,0 +1,88 @@ +From d258422d3e653621ce6340ba9af0153f8d4e8c07 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Ond=C5=99ej=20Sur=C3=BD?= +Date: Sun, 11 Feb 2024 00:49:32 +0100 +Subject: [PATCH] Test case insensitive matching in isc_ht hash table + implementation + +The case insensitive matching in isc_ht was basically completely broken +as only the hashvalue computation was case insensitive, but the key +comparison was always case sensitive. + +Import only test part from upstream. + +(cherry picked from commit 175655b771fd17b06dfb8cfb29eaadf0f3b6a8b5) +(cherry picked from upstream commit f493a8394102b0aeb101d5dc2f963004c8741175) +--- + lib/isc/tests/ht_test.c | 53 +++++++++++++++++++++++++++++++++++++++++ + 1 file changed, 53 insertions(+) + +diff --git a/lib/isc/tests/ht_test.c b/lib/isc/tests/ht_test.c +index 74d95c1..65502b5 100644 +--- a/lib/isc/tests/ht_test.c ++++ b/lib/isc/tests/ht_test.c +@@ -334,9 +334,62 @@ isc_ht_iterator_test(void **state) { + test_ht_iterator(); + } + ++static void ++isc_ht_case(void **state) { ++ UNUSED(state); ++ ++ isc_ht_t *ht = NULL; ++ void *f = NULL; ++ isc_result_t result = ISC_R_UNSET; ++ ++ unsigned char lower[16] = { "test case" }; ++ unsigned char same[16] = { "test case" }; ++ unsigned char upper[16] = { "TEST CASE" }; ++ unsigned char mixed[16] = { "tEsT CaSe" }; ++ ++ isc_ht_init(&ht, test_mctx, 8, ISC_HT_CASE_SENSITIVE); ++ assert_non_null(ht); ++ ++ result = isc_ht_add(ht, lower, 16, (void *)lower); ++ assert_int_equal(result, ISC_R_SUCCESS); ++ ++ result = isc_ht_add(ht, same, 16, (void *)same); ++ assert_int_equal(result, ISC_R_EXISTS); ++ ++ result = isc_ht_add(ht, upper, 16, (void *)upper); ++ assert_int_equal(result, ISC_R_SUCCESS); ++ ++ result = isc_ht_find(ht, mixed, 16, &f); ++ assert_int_equal(result, ISC_R_NOTFOUND); ++ assert_null(f); ++ ++ isc_ht_destroy(&ht); ++ assert_null(ht); ++ ++ isc_ht_init(&ht, test_mctx, 8, ISC_HT_CASE_INSENSITIVE); ++ assert_non_null(ht); ++ ++ result = isc_ht_add(ht, lower, 16, (void *)lower); ++ assert_int_equal(result, ISC_R_SUCCESS); ++ ++ result = isc_ht_add(ht, same, 16, (void *)same); ++ assert_int_equal(result, ISC_R_EXISTS); ++ ++ result = isc_ht_add(ht, upper, 16, (void *)upper); ++ assert_int_equal(result, ISC_R_EXISTS); ++ ++ result = isc_ht_find(ht, mixed, 16, &f); ++ assert_int_equal(result, ISC_R_SUCCESS); ++ assert_ptr_equal(f, &lower); ++ ++ isc_ht_destroy(&ht); ++ assert_null(ht); ++} ++ + int + main(void) { + const struct CMUnitTest tests[] = { ++ cmocka_unit_test(isc_ht_case), + cmocka_unit_test(isc_ht_20), + cmocka_unit_test(isc_ht_8), + cmocka_unit_test(isc_ht_1), +-- +2.43.0 + diff --git a/SOURCES/bind-9.16-CVE-2023-4408-test2.patch b/SOURCES/bind-9.16-CVE-2023-4408-test2.patch new file mode 100644 index 0000000..2fdc9cc --- /dev/null +++ b/SOURCES/bind-9.16-CVE-2023-4408-test2.patch @@ -0,0 +1,75 @@ +From aa1b0fc4b24d26233db30c85ae3609e54e9fa6d2 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Ond=C5=99ej=20Sur=C3=BD?= +Date: Sun, 11 Feb 2024 09:13:43 +0100 +Subject: [PATCH] Add a system test for mixed-case data for the same owner + +We were missing a test where a single owner name would have multiple +types with a different case. The generated RRSIGs and NSEC records will +then have different case than the signed records and message parser have +to cope with that and treat everything as the same owner. + +(cherry picked from commit a114042059ecbbc94ae0f604ca681323a75af480) +(cherry picked from upstream commit b9c10a194da3358204f5ba7d91e55332db435614) +--- + bin/tests/system/dnssec/ns3/secure.example.db.in | 5 +++++ + bin/tests/system/dnssec/ns3/sign.sh | 4 +++- + bin/tests/system/dnssec/tests.sh | 15 +++++++++++++++ + 3 files changed, 23 insertions(+), 1 deletion(-) + +diff --git a/bin/tests/system/dnssec/ns3/secure.example.db.in b/bin/tests/system/dnssec/ns3/secure.example.db.in +index 27f2b24..599566e 100644 +--- a/bin/tests/system/dnssec/ns3/secure.example.db.in ++++ b/bin/tests/system/dnssec/ns3/secure.example.db.in +@@ -45,3 +45,8 @@ rrsigonly A 10.0.0.29 + cnameandkey CNAME @ + cnamenokey CNAME @ + dnameandkey DNAME @ ++ ++mixedcase A 10.0.0.30 ++mixedCASE TXT "mixed case" ++MIXEDcase AAAA 2002:: ++mIxEdCaSe LOC 37 52 56.788 N 121 54 55.02 W 1120m 10m 100m 10m +diff --git a/bin/tests/system/dnssec/ns3/sign.sh b/bin/tests/system/dnssec/ns3/sign.sh +index 80d412e..d94f382 100644 +--- a/bin/tests/system/dnssec/ns3/sign.sh ++++ b/bin/tests/system/dnssec/ns3/sign.sh +@@ -86,7 +86,9 @@ keyname=$("$KEYGEN" -q -a "$DEFAULT_ALGORITHM" -b "$DEFAULT_BITS" -n zone "$zone + + cat "$infile" "$cnameandkey.key" "$dnameandkey.key" "$keyname.key" > "$zonefile" + +-"$SIGNER" -P -o "$zone" "$zonefile" > /dev/null ++"$SIGNER" -P -D -o "$zone" "$zonefile" >/dev/null ++cat "$zonefile" "$zonefile".signed >"$zonefile".tmp ++mv "$zonefile".tmp "$zonefile".signed + + zone=bogus.example. + infile=bogus.example.db.in +diff --git a/bin/tests/system/dnssec/tests.sh b/bin/tests/system/dnssec/tests.sh +index fe95c8d..0c03970 100644 +--- a/bin/tests/system/dnssec/tests.sh ++++ b/bin/tests/system/dnssec/tests.sh +@@ -762,6 +762,21 @@ n=$((n+1)) + test "$ret" -eq 0 || echo_i "failed" + status=$((status+ret)) + ++echo_i "checking mixed-case positive validation ($n)" ++ret=0 ++for type in a txt aaaa loc; do ++ dig_with_opts +noauth mixedcase.secure.example. \ ++ @10.53.0.3 $type >dig.out.$type.ns3.test$n || ret=1 ++ dig_with_opts +noauth mixedcase.secure.example. \ ++ @10.53.0.4 $type >dig.out.$type.ns4.test$n || ret=1 ++ digcomp --lc dig.out.$type.ns3.test$n dig.out.$type.ns4.test$n || ret=1 ++ grep "status: NOERROR" dig.out.$type.ns4.test$n >/dev/null || ret=1 ++ grep "flags:.*ad.*QUERY" dig.out.$type.ns4.test$n >/dev/null || ret=1 ++done ++n=$((n + 1)) ++test "$ret" -eq 0 || echo_i "failed" ++status=$((status + ret)) ++ + echo_i "checking multi-stage positive validation NSEC/NSEC3 ($n)" + ret=0 + dig_with_opts +noauth a.nsec3.example. \ +-- +2.43.0 + diff --git a/SOURCES/bind-9.16-CVE-2023-4408.patch b/SOURCES/bind-9.16-CVE-2023-4408.patch new file mode 100644 index 0000000..f564235 --- /dev/null +++ b/SOURCES/bind-9.16-CVE-2023-4408.patch @@ -0,0 +1,1735 @@ +From 8664429a745e73854486e1222d1f3a2a86bd3f7f Mon Sep 17 00:00:00 2001 +From: Petr Mensik +Date: Mon, 12 Feb 2024 19:34:45 +0100 +Subject: [PATCH] Fixes of CVE-2023-4408 + +6315. [security] Speed up parsing of DNS messages with many different + names. (CVE-2023-4408) [GL #4234] +6321. [security] Change 6315 inadvertently introduced regressions that + could cause named to crash. [GL #4234] +6343. [bug] Fix case insensitive setting for isc_ht hashtable. + [GL #4568] +--- + bin/plugins/filter-aaaa.c | 2 +- + lib/dns/catz.c | 8 +- + lib/dns/include/dns/message.h | 38 --- + lib/dns/include/dns/name.h | 37 ++- + lib/dns/message.c | 387 +++++++++++++++--------- + lib/dns/name.c | 1 + + lib/dns/rpz.c | 4 +- + lib/dns/win32/libdns.def.in | 2 - + lib/isc/ht.c | 550 +++++++++++++++++++++++++--------- + lib/isc/include/isc/ht.h | 28 +- + lib/isc/tests/ht_test.c | 4 +- + 11 files changed, 710 insertions(+), 351 deletions(-) + +diff --git a/bin/plugins/filter-aaaa.c b/bin/plugins/filter-aaaa.c +index 1db3ca2..f0327b6 100644 +--- a/bin/plugins/filter-aaaa.c ++++ b/bin/plugins/filter-aaaa.c +@@ -355,7 +355,7 @@ plugin_register(const char *parameters, const void *cfg, const char *cfg_file, + } + + isc_mempool_create(mctx, sizeof(filter_data_t), &inst->datapool); +- CHECK(isc_ht_init(&inst->ht, mctx, 16)); ++ CHECK(isc_ht_init(&inst->ht, mctx, 16, ISC_HT_CASE_SENSITIVE)); + isc_mutex_init(&inst->hlock); + + /* +diff --git a/lib/dns/catz.c b/lib/dns/catz.c +index 691c4aa..e7fac02 100644 +--- a/lib/dns/catz.c ++++ b/lib/dns/catz.c +@@ -404,12 +404,12 @@ dns_catz_zones_merge(dns_catz_zone_t *target, dns_catz_zone_t *newzone) { + + dns_name_format(&target->name, czname, DNS_NAME_FORMATSIZE); + +- result = isc_ht_init(&toadd, target->catzs->mctx, 16); ++ result = isc_ht_init(&toadd, target->catzs->mctx, 16, ISC_HT_CASE_SENSITIVE); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + +- result = isc_ht_init(&tomod, target->catzs->mctx, 16); ++ result = isc_ht_init(&tomod, target->catzs->mctx, 16, ISC_HT_CASE_SENSITIVE); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } +@@ -623,7 +623,7 @@ dns_catz_new_zones(dns_catz_zones_t **catzsp, dns_catz_zonemodmethods_t *zmm, + + isc_refcount_init(&new_zones->refs, 1); + +- result = isc_ht_init(&new_zones->zones, mctx, 4); ++ result = isc_ht_init(&new_zones->zones, mctx, 4, ISC_HT_CASE_SENSITIVE); + if (result != ISC_R_SUCCESS) { + goto cleanup_refcount; + } +@@ -679,7 +679,7 @@ dns_catz_new_zone(dns_catz_zones_t *catzs, dns_catz_zone_t **zonep, + dns_name_init(&new_zone->name, NULL); + dns_name_dup(name, catzs->mctx, &new_zone->name); + +- result = isc_ht_init(&new_zone->entries, catzs->mctx, 4); ++ result = isc_ht_init(&new_zone->entries, catzs->mctx, 4, ISC_HT_CASE_SENSITIVE); + if (result != ISC_R_SUCCESS) { + goto cleanup_name; + } +diff --git a/lib/dns/include/dns/message.h b/lib/dns/include/dns/message.h +index 1f406e5..68c13ee 100644 +--- a/lib/dns/include/dns/message.h ++++ b/lib/dns/include/dns/message.h +@@ -799,44 +799,6 @@ dns_message_findtype(const dns_name_t *name, dns_rdatatype_t type, + *\li #ISC_R_NOTFOUND -- the desired type does not exist. + */ + +-isc_result_t +-dns_message_find(const dns_name_t *name, dns_rdataclass_t rdclass, +- dns_rdatatype_t type, dns_rdatatype_t covers, +- dns_rdataset_t **rdataset); +-/*%< +- * Search the name for the specified rdclass and type. If it is found, +- * *rdataset is filled in with a pointer to that rdataset. +- * +- * Requires: +- *\li if '**rdataset' is non-NULL, *rdataset needs to be NULL. +- * +- *\li 'type' be a valid type, and NOT dns_rdatatype_any. +- * +- *\li If 'type' is dns_rdatatype_rrsig, 'covers' must be a valid type. +- * Otherwise it should be 0. +- * +- * Returns: +- *\li #ISC_R_SUCCESS -- all is well. +- *\li #ISC_R_NOTFOUND -- the desired type does not exist. +- */ +- +-void +-dns_message_movename(dns_message_t *msg, dns_name_t *name, +- dns_section_t fromsection, dns_section_t tosection); +-/*%< +- * Move a name from one section to another. +- * +- * Requires: +- * +- *\li 'msg' be valid. +- * +- *\li 'name' must be a name already in 'fromsection'. +- * +- *\li 'fromsection' must be a valid section. +- * +- *\li 'tosection' must be a valid section. +- */ +- + void + dns_message_addname(dns_message_t *msg, dns_name_t *name, + dns_section_t section); +diff --git a/lib/dns/include/dns/name.h b/lib/dns/include/dns/name.h +index 75bc020..4058b56 100644 +--- a/lib/dns/include/dns/name.h ++++ b/lib/dns/include/dns/name.h +@@ -67,6 +67,7 @@ + #include + #include + ++#include + #include + #include + #include /* Required for storage size of dns_label_t. */ +@@ -110,6 +111,7 @@ struct dns_name { + isc_buffer_t *buffer; + ISC_LINK(dns_name_t) link; + ISC_LIST(dns_rdataset_t) list; ++ isc_ht_t *ht; + }; + + #define DNS_NAME_MAGIC ISC_MAGIC('D', 'N', 'S', 'n') +@@ -165,30 +167,24 @@ LIBDNS_EXTERNAL_DATA extern const dns_name_t *dns_wildcardname; + * unsigned char offsets[] = { 0, 6 }; + * dns_name_t value = DNS_NAME_INITABSOLUTE(data, offsets); + */ +-#define DNS_NAME_INITNONABSOLUTE(A, B) \ +- { \ +- DNS_NAME_MAGIC, A, (sizeof(A) - 1), sizeof(B), \ +- DNS_NAMEATTR_READONLY, B, NULL, \ +- { (void *)-1, (void *)-1 }, { \ +- NULL, NULL \ +- } \ ++#define DNS_NAME_INITNONABSOLUTE(A, B) \ ++ { \ ++ DNS_NAME_MAGIC, A, (sizeof(A) - 1), sizeof(B), \ ++ DNS_NAMEATTR_READONLY, B, NULL, \ ++ { (void *)-1, (void *)-1 }, { NULL, NULL }, NULL \ + } + +-#define DNS_NAME_INITABSOLUTE(A, B) \ +- { \ +- DNS_NAME_MAGIC, A, sizeof(A), sizeof(B), \ +- DNS_NAMEATTR_READONLY | DNS_NAMEATTR_ABSOLUTE, B, \ +- NULL, { (void *)-1, (void *)-1 }, { \ +- NULL, NULL \ +- } \ ++#define DNS_NAME_INITABSOLUTE(A, B) \ ++ { \ ++ DNS_NAME_MAGIC, A, sizeof(A), sizeof(B), \ ++ DNS_NAMEATTR_READONLY | DNS_NAMEATTR_ABSOLUTE, B, \ ++ NULL, { (void *)-1, (void *)-1 }, { NULL, NULL }, NULL \ + } + +-#define DNS_NAME_INITEMPTY \ +- { \ +- DNS_NAME_MAGIC, NULL, 0, 0, 0, NULL, NULL, \ +- { (void *)-1, (void *)-1 }, { \ +- NULL, NULL \ +- } \ ++#define DNS_NAME_INITEMPTY \ ++ { \ ++ DNS_NAME_MAGIC, NULL, 0, 0, 0, NULL, NULL, \ ++ { (void *)-1, (void *)-1 }, { NULL, NULL }, NULL \ + } + + /*% +@@ -1355,6 +1351,7 @@ ISC_LANG_ENDDECLS + _n->buffer = NULL; \ + ISC_LINK_INIT(_n, link); \ + ISC_LIST_INIT(_n->list); \ ++ _n->ht = NULL; \ + } while (0) + + #define DNS_NAME_RESET(n) \ +diff --git a/lib/dns/message.c b/lib/dns/message.c +index e4f4338..1993b2e 100644 +--- a/lib/dns/message.c ++++ b/lib/dns/message.c +@@ -20,6 +20,8 @@ + #include + + #include ++#include ++#include + #include + #include + #include /* Required for HP/UX (and others?) */ +@@ -503,9 +505,11 @@ msgresetsigs(dns_message_t *msg, bool replying) { + } else { + dns_rdataset_disassociate(msg->tsig); + isc_mempool_put(msg->rdspool, msg->tsig); ++ msg->tsig = NULL; + if (msg->querytsig != NULL) { + dns_rdataset_disassociate(msg->querytsig); + isc_mempool_put(msg->rdspool, msg->querytsig); ++ msg->querytsig = NULL; + } + } + dns_message_puttempname(msg, &msg->tsigname); +@@ -794,6 +798,18 @@ dns_message_detach(dns_message_t **messagep) { + } + } + ++static isc_result_t ++name_hash_add(isc_ht_t *ht, dns_name_t *name, dns_name_t **foundp) { ++ isc_result_t result = isc_ht_find(ht, name->ndata, name->length, ++ (void **)foundp); ++ if (result == ISC_R_SUCCESS) { ++ return (ISC_R_EXISTS); ++ } ++ result = isc_ht_add(ht, name->ndata, name->length, (void *)name); ++ INSIST(result == ISC_R_SUCCESS); ++ return (ISC_R_SUCCESS); ++} ++ + static isc_result_t + findname(dns_name_t **foundname, const dns_name_t *target, + dns_namelist_t *section) { +@@ -813,28 +829,26 @@ findname(dns_name_t **foundname, const dns_name_t *target, + return (ISC_R_NOTFOUND); + } + +-isc_result_t +-dns_message_find(const dns_name_t *name, dns_rdataclass_t rdclass, +- dns_rdatatype_t type, dns_rdatatype_t covers, +- dns_rdataset_t **rdataset) { +- dns_rdataset_t *curr; +- +- REQUIRE(name != NULL); +- REQUIRE(rdataset == NULL || *rdataset == NULL); +- +- for (curr = ISC_LIST_TAIL(name->list); curr != NULL; +- curr = ISC_LIST_PREV(curr, link)) +- { +- if (curr->rdclass == rdclass && curr->type == type && +- curr->covers == covers) { +- if (rdataset != NULL) { +- *rdataset = curr; +- } +- return (ISC_R_SUCCESS); +- } +- } ++typedef struct __attribute__((__packed__)) rds_key { ++ dns_rdataclass_t rdclass; ++ dns_rdatatype_t type; ++ dns_rdatatype_t covers; ++} rds_key_t; + +- return (ISC_R_NOTFOUND); ++static isc_result_t ++rds_hash_add(isc_ht_t *ht, dns_rdataset_t *rds, dns_rdataset_t **foundp) { ++ rds_key_t key = { .rdclass = rds->rdclass, ++ .type = rds->type, ++ .covers = rds->covers }; ++ isc_result_t result = isc_ht_find(ht, (const unsigned char *)&key, ++ sizeof(key), (void **)foundp); ++ if (result == ISC_R_SUCCESS) { ++ return (ISC_R_EXISTS); ++ } ++ result = isc_ht_add(ht, (const unsigned char *)&key, sizeof(key), ++ (void *)rds); ++ INSIST(result == ISC_R_SUCCESS); ++ return (ISC_R_SUCCESS); + } + + isc_result_t +@@ -962,6 +976,18 @@ getrdata(isc_buffer_t *source, dns_message_t *msg, dns_decompress_t *dctx, + } \ + } while (0) + ++static void ++cleanup_name_hashmaps(dns_namelist_t *section) { ++ dns_name_t *name = NULL; ++ for (name = ISC_LIST_HEAD(*section); name != NULL; ++ name = ISC_LIST_NEXT(name, link)) ++ { ++ if (name->ht != NULL) { ++ isc_ht_destroy(&name->ht); ++ } ++ } ++} ++ + static isc_result_t + getquestions(isc_buffer_t *source, dns_message_t *msg, dns_decompress_t *dctx, + unsigned int options) { +@@ -971,13 +997,19 @@ getquestions(isc_buffer_t *source, dns_message_t *msg, dns_decompress_t *dctx, + dns_name_t *name2 = NULL; + dns_rdataset_t *rdataset = NULL; + dns_rdatalist_t *rdatalist = NULL; +- isc_result_t result; ++ isc_result_t result = ISC_R_SUCCESS; + dns_rdatatype_t rdtype; + dns_rdataclass_t rdclass; + dns_namelist_t *section = &msg->sections[DNS_SECTION_QUESTION]; + bool best_effort = ((options & DNS_MESSAGEPARSE_BESTEFFORT) != 0); + bool seen_problem = false; + bool free_name = false; ++ bool free_ht = false; ++ isc_ht_t *name_map = NULL; ++ ++ if (msg->counts[DNS_SECTION_QUESTION] > 1) { ++ isc_ht_init(&name_map, msg->mctx, 1, ISC_HT_CASE_INSENSITIVE); ++ } + + for (count = 0; count < msg->counts[DNS_SECTION_QUESTION]; count++) { + name = NULL; +@@ -998,13 +1030,19 @@ getquestions(isc_buffer_t *source, dns_message_t *msg, dns_decompress_t *dctx, + goto cleanup; + } + ++ /* If there is only one QNAME, skip the duplicity checks */ ++ if (name_map == NULL) { ++ result = ISC_R_SUCCESS; ++ goto skip_name_check; ++ } ++ + /* + * Run through the section, looking to see if this name + * is already there. If it is found, put back the allocated + * name since we no longer need it, and set our name pointer + * to point to the name we found. + */ +- result = findname(&name2, name, section); ++ result = name_hash_add(name_map, name, &name2); + + /* + * If it is the first name in the section, accept it. +@@ -1016,19 +1054,25 @@ getquestions(isc_buffer_t *source, dns_message_t *msg, dns_decompress_t *dctx, + * this should be legal or not. In either case we no longer + * need this name pointer. + */ +- if (result != ISC_R_SUCCESS) { ++ skip_name_check: ++ switch (result) { ++ case ISC_R_SUCCESS: + if (!ISC_LIST_EMPTY(*section)) { + DO_ERROR(DNS_R_FORMERR); + } + ISC_LIST_APPEND(*section, name, link); +- free_name = false; +- } else { ++ break; ++ case ISC_R_EXISTS: + dns_message_puttempname(msg, &name); + name = name2; + name2 = NULL; +- free_name = false; ++ break; ++ default: ++ ISC_UNREACHABLE(); + } + ++ free_name = false; ++ + /* + * Get type and class. + */ +@@ -1058,14 +1102,6 @@ getquestions(isc_buffer_t *source, dns_message_t *msg, dns_decompress_t *dctx, + msg->tkey = 1; + } + +- /* +- * Can't ask the same question twice. +- */ +- result = dns_message_find(name, rdclass, rdtype, 0, NULL); +- if (result == ISC_R_SUCCESS) { +- DO_ERROR(DNS_R_FORMERR); +- } +- + /* + * Allocate a new rdatalist. + */ +@@ -1079,6 +1115,7 @@ getquestions(isc_buffer_t *source, dns_message_t *msg, dns_decompress_t *dctx, + result = ISC_R_NOMEMORY; + goto cleanup; + } ++ dns_rdataset_init(rdataset); + + /* + * Convert rdatalist to rdataset, and attach the latter to +@@ -1087,32 +1124,71 @@ getquestions(isc_buffer_t *source, dns_message_t *msg, dns_decompress_t *dctx, + rdatalist->type = rdtype; + rdatalist->rdclass = rdclass; + +- dns_rdataset_init(rdataset); + result = dns_rdatalist_tordataset(rdatalist, rdataset); +- if (result != ISC_R_SUCCESS) { +- goto cleanup; +- } ++ RUNTIME_CHECK(result == ISC_R_SUCCESS); + + rdataset->attributes |= DNS_RDATASETATTR_QUESTION; + ++ /* ++ * Skip the duplicity check for first rdataset ++ */ ++ if (ISC_LIST_EMPTY(name->list)) { ++ result = ISC_R_SUCCESS; ++ goto skip_rds_check; ++ } ++ ++ /* ++ * Can't ask the same question twice. ++ */ ++ if (name->ht == NULL) { ++ isc_ht_init(&name->ht, msg->mctx, 1, ++ ISC_HT_CASE_SENSITIVE); ++ free_ht = true; ++ ++ INSIST(ISC_LIST_HEAD(name->list) == ++ ISC_LIST_TAIL(name->list)); ++ ++ dns_rdataset_t *old_rdataset = ++ ISC_LIST_HEAD(name->list); ++ ++ result = rds_hash_add(name->ht, old_rdataset, NULL); ++ ++ INSIST(result == ISC_R_SUCCESS); ++ } ++ result = rds_hash_add(name->ht, rdataset, NULL); ++ if (result == ISC_R_EXISTS) { ++ DO_ERROR(DNS_R_FORMERR); ++ } ++ ++ skip_rds_check: + ISC_LIST_APPEND(name->list, rdataset, link); ++ + rdataset = NULL; + } + + if (seen_problem) { +- return (DNS_R_RECOVERABLE); ++ result = DNS_R_RECOVERABLE; + } +- return (ISC_R_SUCCESS); + + cleanup: + if (rdataset != NULL) { +- INSIST(!dns_rdataset_isassociated(rdataset)); ++ if (dns_rdataset_isassociated(rdataset)) { ++ dns_rdataset_disassociate(rdataset); ++ } + isc_mempool_put(msg->rdspool, rdataset); + } + if (free_name) { + dns_message_puttempname(msg, &name); + } + ++ if (free_ht) { ++ cleanup_name_hashmaps(section); ++ } ++ ++ if (name_map != NULL) { ++ isc_ht_destroy(&name_map); ++ } ++ + return (result); + } + +@@ -1192,17 +1268,24 @@ getsection(isc_buffer_t *source, dns_message_t *msg, dns_decompress_t *dctx, + dns_name_t *name = NULL; + dns_name_t *name2 = NULL; + dns_rdataset_t *rdataset = NULL; ++ dns_rdataset_t *found_rdataset = NULL; + dns_rdatalist_t *rdatalist = NULL; +- isc_result_t result; ++ isc_result_t result = ISC_R_SUCCESS; + dns_rdatatype_t rdtype, covers; + dns_rdataclass_t rdclass; + dns_rdata_t *rdata = NULL; + dns_ttl_t ttl; + dns_namelist_t *section = &msg->sections[sectionid]; +- bool free_name = false, free_rdataset = false, seen_problem = false; ++ bool free_name = false, seen_problem = false; ++ bool free_ht = false; + bool preserve_order = ((options & DNS_MESSAGEPARSE_PRESERVEORDER) != 0); + bool best_effort = ((options & DNS_MESSAGEPARSE_BESTEFFORT) != 0); + bool isedns, issigzero, istsig; ++ isc_ht_t *name_map = NULL; ++ ++ if (msg->counts[sectionid] > 1) { ++ isc_ht_init(&name_map, msg->mctx, 1, ISC_HT_CASE_INSENSITIVE); ++ } + + for (count = 0; count < msg->counts[sectionid]; count++) { + int recstart = source->current; +@@ -1210,10 +1293,10 @@ getsection(isc_buffer_t *source, dns_message_t *msg, dns_decompress_t *dctx, + + skip_name_search = false; + skip_type_search = false; +- free_rdataset = false; + isedns = false; + issigzero = false; + istsig = false; ++ found_rdataset = NULL; + + name = NULL; + result = dns_message_gettempname(msg, &name); +@@ -1253,8 +1336,8 @@ getsection(isc_buffer_t *source, dns_message_t *msg, dns_decompress_t *dctx, + if (msg->rdclass_set == 0 && + rdtype != dns_rdatatype_opt && /* class is UDP SIZE */ + rdtype != dns_rdatatype_tsig && /* class is ANY */ +- rdtype != dns_rdatatype_tkey) +- { /* class is undefined */ ++ rdtype != dns_rdatatype_tkey) /* class is undefined */ ++ { + msg->rdclass = rdclass; + msg->rdclass_set = 1; + } +@@ -1360,10 +1443,6 @@ getsection(isc_buffer_t *source, dns_message_t *msg, dns_decompress_t *dctx, + * Then put the meta-class back into the finished rdata. + */ + rdata = newrdata(msg); +- if (rdata == NULL) { +- result = ISC_R_NOMEMORY; +- goto cleanup; +- } + if (msg->opcode == dns_opcode_update && + update(sectionid, rdclass)) { + if (rdatalen != 0) { +@@ -1446,33 +1525,70 @@ getsection(isc_buffer_t *source, dns_message_t *msg, dns_decompress_t *dctx, + free_name = false; + } + } else { ++ if (name_map == NULL) { ++ result = ISC_R_SUCCESS; ++ goto skip_name_check; ++ } ++ + /* + * Run through the section, looking to see if this name + * is already there. If it is found, put back the + * allocated name since we no longer need it, and set + * our name pointer to point to the name we found. + */ +- result = findname(&name2, name, section); ++ result = name_hash_add(name_map, name, &name2); + + /* + * If it is a new name, append to the section. + */ +- if (result == ISC_R_SUCCESS) { ++ skip_name_check: ++ switch (result) { ++ case ISC_R_SUCCESS: ++ ISC_LIST_APPEND(*section, name, link); ++ break; ++ case ISC_R_EXISTS: + dns_message_puttempname(msg, &name); + name = name2; +- } else { +- ISC_LIST_APPEND(*section, name, link); ++ name2 = NULL; ++ break; ++ default: ++ ISC_UNREACHABLE(); + } + free_name = false; + } + ++ rdatalist = newrdatalist(msg); ++ if (rdatalist == NULL) { ++ result = ISC_R_NOMEMORY; ++ goto cleanup; ++ } ++ dns_message_gettemprdataset(msg, &rdataset); ++ if (rdataset == NULL) { ++ result = ISC_R_NOMEMORY; ++ goto cleanup; ++ } ++ ++ rdatalist->type = rdtype; ++ rdatalist->covers = covers; ++ rdatalist->rdclass = rdclass; ++ rdatalist->ttl = ttl; ++ ++ RUNTIME_CHECK(dns_rdatalist_tordataset(rdatalist, rdataset) == ++ ISC_R_SUCCESS); ++ dns_rdataset_setownercase(rdataset, name); ++ rdatalist = NULL; ++ + /* + * Search name for the particular type and class. + * Skip this stage if in update mode or this is a meta-type. + */ +- if (preserve_order || msg->opcode == dns_opcode_update || ++ if (isedns || istsig || issigzero) { ++ /* Skip adding the rdataset to the tables */ ++ } else if (preserve_order || msg->opcode == dns_opcode_update || + skip_type_search) { +- result = ISC_R_NOTFOUND; ++ result = ISC_R_SUCCESS; ++ ++ ISC_LIST_APPEND(name->list, rdataset, link); + } else { + /* + * If this is a type that can only occur in +@@ -1482,63 +1598,71 @@ getsection(isc_buffer_t *source, dns_message_t *msg, dns_decompress_t *dctx, + DO_ERROR(DNS_R_FORMERR); + } + +- rdataset = NULL; +- result = dns_message_find(name, rdclass, rdtype, covers, +- &rdataset); +- } +- +- /* +- * If we found an rdataset that matches, we need to +- * append this rdata to that set. If we did not, we need +- * to create a new rdatalist, store the important bits there, +- * convert it to an rdataset, and link the latter to the name. +- * Yuck. When appending, make certain that the type isn't +- * a singleton type, such as SOA or CNAME. +- * +- * Note that this check will be bypassed when preserving order, +- * the opcode is an update, or the type search is skipped. +- */ +- if (result == ISC_R_SUCCESS) { +- if (dns_rdatatype_issingleton(rdtype)) { +- dns_rdata_t *first; +- dns_rdatalist_fromrdataset(rdataset, +- &rdatalist); +- first = ISC_LIST_HEAD(rdatalist->rdata); +- INSIST(first != NULL); +- if (dns_rdata_compare(rdata, first) != 0) { +- DO_ERROR(DNS_R_FORMERR); +- } ++ if (ISC_LIST_EMPTY(name->list)) { ++ result = ISC_R_SUCCESS; ++ goto skip_rds_check; + } +- } + +- if (result == ISC_R_NOTFOUND) { +- rdataset = isc_mempool_get(msg->rdspool); +- if (rdataset == NULL) { +- result = ISC_R_NOMEMORY; +- goto cleanup; +- } +- free_rdataset = true; ++ if (name->ht == NULL) { ++ isc_ht_init(&name->ht, msg->mctx, 1, ++ ISC_HT_CASE_SENSITIVE); ++ free_ht = true; + +- rdatalist = newrdatalist(msg); +- if (rdatalist == NULL) { +- result = ISC_R_NOMEMORY; +- goto cleanup; ++ INSIST(ISC_LIST_HEAD(name->list) == ++ ISC_LIST_TAIL(name->list)); ++ ++ dns_rdataset_t *old_rdataset = ++ ISC_LIST_HEAD(name->list); ++ ++ result = rds_hash_add(name->ht, old_rdataset, ++ NULL); ++ ++ INSIST(result == ISC_R_SUCCESS); + } ++ found_rdataset = NULL; ++ result = rds_hash_add(name->ht, rdataset, ++ &found_rdataset); + +- rdatalist->type = rdtype; +- rdatalist->covers = covers; +- rdatalist->rdclass = rdclass; +- rdatalist->ttl = ttl; ++ /* ++ * If we found an rdataset that matches, we need to ++ * append this rdata to that set. If we did not, we ++ * need to create a new rdatalist, store the important ++ * bits there, convert it to an rdataset, and link the ++ * latter to the name. Yuck. When appending, make ++ * certain that the type isn't a singleton type, such as ++ * SOA or CNAME. ++ * ++ * Note that this check will be bypassed when preserving ++ * order, the opcode is an update, or the type search is ++ * skipped. ++ */ ++ skip_rds_check: ++ switch (result) { ++ case ISC_R_EXISTS: ++ /* Free the rdataset we used as the key */ ++ dns_rdataset_disassociate(rdataset); ++ isc_mempool_put(msg->rdspool, rdataset); ++ result = ISC_R_SUCCESS; ++ rdataset = found_rdataset; + +- dns_rdataset_init(rdataset); +- RUNTIME_CHECK( +- dns_rdatalist_tordataset(rdatalist, rdataset) == +- ISC_R_SUCCESS); +- dns_rdataset_setownercase(rdataset, name); ++ if (!dns_rdatatype_issingleton(rdtype)) { ++ break; ++ } + +- if (!isedns && !istsig && !issigzero) { ++ dns_rdatalist_fromrdataset(rdataset, ++ &rdatalist); ++ dns_rdata_t *first = ++ ISC_LIST_HEAD(rdatalist->rdata); ++ INSIST(first != NULL); ++ if (dns_rdata_compare(rdata, first) != 0) { ++ DO_ERROR(DNS_R_FORMERR); ++ } ++ break; ++ case ISC_R_SUCCESS: + ISC_LIST_APPEND(name->list, rdataset, link); +- free_rdataset = false; ++ break; ++ default: ++ ISC_UNREACHABLE(); + } + } + +@@ -1573,8 +1697,6 @@ getsection(isc_buffer_t *source, dns_message_t *msg, dns_decompress_t *dctx, + dns_rcode_t ercode; + + msg->opt = rdataset; +- rdataset = NULL; +- free_rdataset = false; + ercode = (dns_rcode_t)((msg->opt->ttl & + DNS_MESSAGE_EDNSRCODE_MASK) >> + 20); +@@ -1585,8 +1707,6 @@ getsection(isc_buffer_t *source, dns_message_t *msg, dns_decompress_t *dctx, + msg->sig0 = rdataset; + msg->sig0name = name; + msg->sigstart = recstart; +- rdataset = NULL; +- free_rdataset = false; + free_name = false; + } else if (istsig) { + msg->tsig = rdataset; +@@ -1596,22 +1716,17 @@ getsection(isc_buffer_t *source, dns_message_t *msg, dns_decompress_t *dctx, + * Windows doesn't like TSIG names to be compressed. + */ + msg->tsigname->attributes |= DNS_NAMEATTR_NOCOMPRESS; +- rdataset = NULL; +- free_rdataset = false; + free_name = false; + } ++ rdataset = NULL; + + if (seen_problem) { + if (free_name) { + dns_message_puttempname(msg, &name); + } +- if (free_rdataset) { +- isc_mempool_put(msg->rdspool, rdataset); +- } +- free_name = free_rdataset = false; ++ free_name = false; + } + INSIST(!free_name); +- INSIST(!free_rdataset); + } + + /* +@@ -1629,16 +1744,24 @@ getsection(isc_buffer_t *source, dns_message_t *msg, dns_decompress_t *dctx, + } + + if (seen_problem) { +- return (DNS_R_RECOVERABLE); ++ result = DNS_R_RECOVERABLE; + } +- return (ISC_R_SUCCESS); + + cleanup: ++ if (rdataset != NULL && rdataset != found_rdataset) { ++ dns_rdataset_disassociate(rdataset); ++ isc_mempool_put(msg->rdspool, rdataset); ++ } + if (free_name) { + dns_message_puttempname(msg, &name); + } +- if (free_rdataset) { +- isc_mempool_put(msg->rdspool, rdataset); ++ ++ if (free_ht) { ++ cleanup_name_hashmaps(section); ++ } ++ ++ if (name_map != NULL) { ++ isc_ht_destroy(&name_map); + } + + return (result); +@@ -2432,7 +2555,7 @@ dns_message_findname(dns_message_t *msg, dns_section_t section, + const dns_name_t *target, dns_rdatatype_t type, + dns_rdatatype_t covers, dns_name_t **name, + dns_rdataset_t **rdataset) { +- dns_name_t *foundname; ++ dns_name_t *foundname = NULL; + isc_result_t result; + + /* +@@ -2479,22 +2602,6 @@ dns_message_findname(dns_message_t *msg, dns_section_t section, + return (result); + } + +-void +-dns_message_movename(dns_message_t *msg, dns_name_t *name, +- dns_section_t fromsection, dns_section_t tosection) { +- REQUIRE(msg != NULL); +- REQUIRE(msg->from_to_wire == DNS_MESSAGE_INTENTRENDER); +- REQUIRE(name != NULL); +- REQUIRE(VALID_NAMED_SECTION(fromsection)); +- REQUIRE(VALID_NAMED_SECTION(tosection)); +- +- /* +- * Unlink the name from the old section +- */ +- ISC_LIST_UNLINK(msg->sections[fromsection], name, link); +- ISC_LIST_APPEND(msg->sections[tosection], name, link); +-} +- + void + dns_message_addname(dns_message_t *msg, dns_name_t *name, + dns_section_t section) { +@@ -2586,6 +2693,10 @@ dns_message_puttempname(dns_message_t *msg, dns_name_t **itemp) { + REQUIRE(!ISC_LINK_LINKED(item, link)); + REQUIRE(ISC_LIST_HEAD(item->list) == NULL); + ++ if (item->ht != NULL) { ++ isc_ht_destroy(&item->ht); ++ } ++ + /* + * we need to check this in case dns_name_dup() was used. + */ +diff --git a/lib/dns/name.c b/lib/dns/name.c +index 6ca311d..cc1b872 100644 +--- a/lib/dns/name.c ++++ b/lib/dns/name.c +@@ -186,6 +186,7 @@ dns_name_invalidate(dns_name_t *name) { + name->offsets = NULL; + name->buffer = NULL; + ISC_LINK_INIT(name, link); ++ INSIST(name->ht == NULL); + } + + bool +diff --git a/lib/dns/rpz.c b/lib/dns/rpz.c +index d3baa71..fa93358 100644 +--- a/lib/dns/rpz.c ++++ b/lib/dns/rpz.c +@@ -1538,7 +1538,7 @@ dns_rpz_new_zone(dns_rpz_zones_t *rpzs, dns_rpz_zone_t **rpzp) { + * simplifies update_from_db + */ + +- result = isc_ht_init(&zone->nodes, rpzs->mctx, 1); ++ result = isc_ht_init(&zone->nodes, rpzs->mctx, 1, ISC_HT_CASE_SENSITIVE); + if (result != ISC_R_SUCCESS) { + goto cleanup_ht; + } +@@ -1721,7 +1721,7 @@ setup_update(dns_rpz_zone_t *rpz) { + ISC_LOG_DEBUG(1), "rpz: %s: using hashtable size %d", + domain, hashsize); + +- result = isc_ht_init(&rpz->newnodes, rpz->rpzs->mctx, hashsize); ++ result = isc_ht_init(&rpz->newnodes, rpz->rpzs->mctx, hashsize, ISC_HT_CASE_SENSITIVE); + if (result != ISC_R_SUCCESS) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, + DNS_LOGMODULE_MASTER, ISC_LOG_ERROR, +diff --git a/lib/dns/win32/libdns.def.in b/lib/dns/win32/libdns.def.in +index 31f5111..1c2ea3e 100644 +--- a/lib/dns/win32/libdns.def.in ++++ b/lib/dns/win32/libdns.def.in +@@ -534,7 +534,6 @@ dns_message_clonebuffer + dns_message_create + dns_message_currentname + dns_message_detach +-dns_message_find + dns_message_findname + dns_message_findtype + dns_message_firstname +@@ -553,7 +552,6 @@ dns_message_gettsigkey + dns_message_headertotext + dns_message_logfmtpacket + dns_message_logpacket +-dns_message_movename + dns_message_nextname + dns_message_parse + dns_message_peekheader +diff --git a/lib/isc/ht.c b/lib/isc/ht.c +index 82f8ac8..f88a32c 100644 +--- a/lib/isc/ht.c ++++ b/lib/isc/ht.c +@@ -25,51 +25,274 @@ typedef struct isc_ht_node isc_ht_node_t; + #define ISC_HT_MAGIC ISC_MAGIC('H', 'T', 'a', 'b') + #define ISC_HT_VALID(ht) ISC_MAGIC_VALID(ht, ISC_HT_MAGIC) + ++#define HT_NO_BITS 0 ++#define HT_MIN_BITS 1 ++#define HT_MAX_BITS 32 ++#define HT_OVERCOMMIT 3 ++ ++#define HT_NEXTTABLE(idx) ((idx == 0) ? 1 : 0) ++#define TRY_NEXTTABLE(idx, ht) (idx == ht->hindex && rehashing_in_progress(ht)) ++ ++#define GOLDEN_RATIO_32 0x61C88647 ++ ++#define HASHSIZE(bits) (UINT64_C(1) << (bits)) ++ + struct isc_ht_node { + void *value; + isc_ht_node_t *next; ++ uint32_t hashval; + size_t keysize; +- unsigned char key[FLEXIBLE_ARRAY_MEMBER]; ++ unsigned char key[]; + }; + + struct isc_ht { + unsigned int magic; + isc_mem_t *mctx; +- size_t size; +- size_t mask; +- unsigned int count; +- isc_ht_node_t **table; ++ size_t count; ++ bool case_sensitive; ++ size_t size[2]; ++ uint8_t hashbits[2]; ++ isc_ht_node_t **table[2]; ++ uint8_t hindex; ++ uint32_t hiter; /* rehashing iterator */ + }; + + struct isc_ht_iter { + isc_ht_t *ht; + size_t i; ++ uint8_t hindex; + isc_ht_node_t *cur; + }; + ++static isc_ht_node_t * ++isc__ht_find(const isc_ht_t *ht, const unsigned char *key, ++ const uint32_t keysize, const uint32_t hashval, const uint8_t idx); ++static void ++isc__ht_add(isc_ht_t *ht, const unsigned char *key, const uint32_t keysize, ++ const uint32_t hashval, const uint8_t idx, void *value); ++static isc_result_t ++isc__ht_delete(isc_ht_t *ht, const unsigned char *key, const uint32_t keysize, ++ const uint32_t hashval, const uint8_t idx); ++ ++static uint32_t ++rehash_bits(isc_ht_t *ht, size_t newcount); ++ ++static void ++hashtable_new(isc_ht_t *ht, const uint8_t idx, const uint8_t bits); ++static void ++hashtable_free(isc_ht_t *ht, const uint8_t idx); ++static void ++hashtable_rehash(isc_ht_t *ht, uint32_t newbits); ++static void ++hashtable_rehash_one(isc_ht_t *ht); ++static void ++maybe_rehash(isc_ht_t *ht, size_t newcount); ++ ++static isc_result_t ++isc__ht_iter_next(isc_ht_iter_t *it); ++ ++static uint8_t maptolower[] = { ++ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, ++ 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, ++ 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, ++ 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, ++ 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, ++ 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, ++ 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, ++ 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, ++ 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, ++ 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, ++ 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, 0x80, 0x81, 0x82, 0x83, ++ 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, ++ 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, ++ 0x9c, 0x9d, 0x9e, 0x9f, 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, ++ 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, 0xb0, 0xb1, 0xb2, 0xb3, ++ 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf, ++ 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xcb, ++ 0xcc, 0xcd, 0xce, 0xcf, 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, ++ 0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf, 0xe0, 0xe1, 0xe2, 0xe3, ++ 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, ++ 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, ++ 0xfc, 0xfd, 0xfe, 0xff ++}; ++ ++static int ++memcasecmp(const void *vs1, const void *vs2, size_t len) { ++ uint8_t const *s1 = vs1; ++ uint8_t const *s2 = vs2; ++ for (size_t i = 0; i < len; i++) { ++ uint8_t u1 = s1[i]; ++ uint8_t u2 = s2[i]; ++ int U1 = maptolower[u1]; ++ int U2 = maptolower[u2]; ++ int diff = U1 - U2; ++ if (diff) { ++ return diff; ++ } ++ } ++ return 0; ++} ++ ++static bool ++isc__ht_node_match(isc_ht_node_t *node, const uint32_t hashval, ++ const uint8_t *key, uint32_t keysize, bool case_sensitive) { ++ return (node->hashval == hashval && node->keysize == keysize && ++ (case_sensitive ? (memcmp(node->key, key, keysize) == 0) ++ : (memcasecmp(node->key, key, keysize) == 0))); ++} ++ ++static uint32_t ++hash_32(uint32_t val, unsigned int bits) { ++ REQUIRE(bits <= HT_MAX_BITS); ++ /* High bits are more random. */ ++ return (val * GOLDEN_RATIO_32 >> (32 - bits)); ++} ++ ++static bool ++rehashing_in_progress(const isc_ht_t *ht) { ++ return (ht->table[HT_NEXTTABLE(ht->hindex)] != NULL); ++} ++ ++static bool ++hashtable_is_overcommited(isc_ht_t *ht) { ++ return (ht->count >= (ht->size[ht->hindex] * HT_OVERCOMMIT)); ++} ++ ++static uint32_t ++rehash_bits(isc_ht_t *ht, size_t newcount) { ++ uint32_t newbits = ht->hashbits[ht->hindex]; ++ ++ while (newcount >= HASHSIZE(newbits) && newbits <= HT_MAX_BITS) { ++ newbits += 1; ++ } ++ ++ return (newbits); ++} ++ ++/* ++ * Rebuild the hashtable to reduce the load factor ++ */ ++static void ++hashtable_rehash(isc_ht_t *ht, uint32_t newbits) { ++ uint8_t oldindex = ht->hindex; ++ uint32_t oldbits = ht->hashbits[oldindex]; ++ uint8_t newindex = HT_NEXTTABLE(oldindex); ++ ++ REQUIRE(ht->hashbits[oldindex] >= HT_MIN_BITS); ++ REQUIRE(ht->hashbits[oldindex] <= HT_MAX_BITS); ++ REQUIRE(ht->table[oldindex] != NULL); ++ ++ REQUIRE(newbits <= HT_MAX_BITS); ++ REQUIRE(ht->hashbits[newindex] == HT_NO_BITS); ++ REQUIRE(ht->table[newindex] == NULL); ++ ++ REQUIRE(newbits > oldbits); ++ ++ hashtable_new(ht, newindex, newbits); ++ ++ ht->hindex = newindex; ++ ++ hashtable_rehash_one(ht); ++} ++ ++static void ++hashtable_rehash_one(isc_ht_t *ht) { ++ isc_ht_node_t **newtable = ht->table[ht->hindex]; ++ uint32_t oldsize = ht->size[HT_NEXTTABLE(ht->hindex)]; ++ isc_ht_node_t **oldtable = ht->table[HT_NEXTTABLE(ht->hindex)]; ++ isc_ht_node_t *node = NULL; ++ isc_ht_node_t *nextnode; ++ ++ /* Find first non-empty node */ ++ while (ht->hiter < oldsize && oldtable[ht->hiter] == NULL) { ++ ht->hiter++; ++ } ++ ++ /* Rehashing complete */ ++ if (ht->hiter == oldsize) { ++ hashtable_free(ht, HT_NEXTTABLE(ht->hindex)); ++ ht->hiter = 0; ++ return; ++ } ++ ++ /* Move the first non-empty node from old hashtable to new hashtable */ ++ for (node = oldtable[ht->hiter]; node != NULL; node = nextnode) { ++ uint32_t hash = hash_32(node->hashval, ++ ht->hashbits[ht->hindex]); ++ nextnode = node->next; ++ node->next = newtable[hash]; ++ newtable[hash] = node; ++ } ++ ++ oldtable[ht->hiter] = NULL; ++ ++ ht->hiter++; ++} ++ ++static void ++maybe_rehash(isc_ht_t *ht, size_t newcount) { ++ uint32_t newbits = rehash_bits(ht, newcount); ++ ++ if (ht->hashbits[ht->hindex] < newbits && newbits <= HT_MAX_BITS) { ++ hashtable_rehash(ht, newbits); ++ } ++} ++ ++static void ++hashtable_new(isc_ht_t *ht, const uint8_t idx, const uint8_t bits) { ++ size_t size; ++ REQUIRE(ht->hashbits[idx] == HT_NO_BITS); ++ REQUIRE(ht->table[idx] == NULL); ++ REQUIRE(bits >= HT_MIN_BITS); ++ REQUIRE(bits <= HT_MAX_BITS); ++ ++ ht->hashbits[idx] = bits; ++ ht->size[idx] = HASHSIZE(ht->hashbits[idx]); ++ ++ size = ht->size[idx] * sizeof(isc_ht_node_t *); ++ ++ ht->table[idx] = isc_mem_get(ht->mctx, size); ++ memset(ht->table[idx], 0, size); ++} ++ ++static void ++hashtable_free(isc_ht_t *ht, const uint8_t idx) { ++ size_t size = ht->size[idx] * sizeof(isc_ht_node_t *); ++ ++ for (size_t i = 0; i < ht->size[idx]; i++) { ++ isc_ht_node_t *node = ht->table[idx][i]; ++ while (node != NULL) { ++ isc_ht_node_t *next = node->next; ++ ht->count--; ++ isc_mem_put(ht->mctx, node, ++ sizeof(*node) + node->keysize); ++ node = next; ++ } ++ } ++ ++ isc_mem_put(ht->mctx, ht->table[idx], size); ++ ht->hashbits[idx] = HT_NO_BITS; ++ ht->table[idx] = NULL; ++} ++ + isc_result_t +-isc_ht_init(isc_ht_t **htp, isc_mem_t *mctx, uint8_t bits) { ++isc_ht_init(isc_ht_t **htp, isc_mem_t *mctx, uint8_t bits, ++ unsigned int options) { + isc_ht_t *ht = NULL; +- size_t i; ++ bool case_sensitive = ((options & ISC_HT_CASE_INSENSITIVE) == 0); + + REQUIRE(htp != NULL && *htp == NULL); + REQUIRE(mctx != NULL); +- REQUIRE(bits >= 1 && bits <= (sizeof(size_t) * 8 - 1)); ++ REQUIRE(bits >= 1 && bits <= HT_MAX_BITS); + +- ht = isc_mem_get(mctx, sizeof(struct isc_ht)); ++ ht = isc_mem_get(mctx, sizeof(*ht)); ++ *ht = (isc_ht_t){ ++ .case_sensitive = case_sensitive, ++ }; + +- ht->mctx = NULL; + isc_mem_attach(mctx, &ht->mctx); + +- ht->size = ((size_t)1 << bits); +- ht->mask = ((size_t)1 << bits) - 1; +- ht->count = 0; +- +- ht->table = isc_mem_get(ht->mctx, ht->size * sizeof(isc_ht_node_t *)); +- +- for (i = 0; i < ht->size; i++) { +- ht->table[i] = NULL; +- } ++ hashtable_new(ht, 0, bits); + + ht->magic = ISC_HT_MAGIC; + +@@ -83,122 +306,183 @@ isc_ht_destroy(isc_ht_t **htp) { + size_t i; + + REQUIRE(htp != NULL); ++ REQUIRE(ISC_HT_VALID(*htp)); + + ht = *htp; + *htp = NULL; + +- REQUIRE(ISC_HT_VALID(ht)); +- + ht->magic = 0; + +- for (i = 0; i < ht->size; i++) { +- isc_ht_node_t *node = ht->table[i]; +- while (node != NULL) { +- isc_ht_node_t *next = node->next; +- ht->count--; +- isc_mem_put(ht->mctx, node, +- offsetof(isc_ht_node_t, key) + +- node->keysize); +- node = next; ++ for (i = 0; i <= 1; i++) { ++ if (ht->table[i] != NULL) { ++ hashtable_free(ht, i); + } + } + + INSIST(ht->count == 0); + +- isc_mem_put(ht->mctx, ht->table, ht->size * sizeof(isc_ht_node_t *)); +- isc_mem_putanddetach(&ht->mctx, ht, sizeof(struct isc_ht)); ++ isc_mem_putanddetach(&ht->mctx, ht, sizeof(*ht)); + } + +-isc_result_t +-isc_ht_add(isc_ht_t *ht, const unsigned char *key, uint32_t keysize, +- void *value) { ++static void ++isc__ht_add(isc_ht_t *ht, const unsigned char *key, const uint32_t keysize, ++ const uint32_t hashval, const uint8_t idx, void *value) { + isc_ht_node_t *node; + uint32_t hash; + ++ hash = hash_32(hashval, ht->hashbits[idx]); ++ ++ node = isc_mem_get(ht->mctx, sizeof(*node) + keysize); ++ *node = (isc_ht_node_t){ ++ .keysize = keysize, ++ .hashval = hashval, ++ .next = ht->table[idx][hash], ++ .value = value, ++ }; ++ ++ memmove(node->key, key, keysize); ++ ++ ht->count++; ++ ht->table[idx][hash] = node; ++} ++ ++isc_result_t ++isc_ht_add(isc_ht_t *ht, const unsigned char *key, const uint32_t keysize, ++ void *value) { ++ uint32_t hashval; ++ + REQUIRE(ISC_HT_VALID(ht)); + REQUIRE(key != NULL && keysize > 0); + +- hash = isc_hash_function(key, keysize, true); +- node = ht->table[hash & ht->mask]; +- while (node != NULL) { +- if (keysize == node->keysize && +- memcmp(key, node->key, keysize) == 0) { +- return (ISC_R_EXISTS); +- } +- node = node->next; ++ if (rehashing_in_progress(ht)) { ++ /* Rehash in progress */ ++ hashtable_rehash_one(ht); ++ } else if (hashtable_is_overcommited(ht)) { ++ /* Rehash requested */ ++ maybe_rehash(ht, ht->count); + } + +- node = isc_mem_get(ht->mctx, offsetof(isc_ht_node_t, key) + keysize); ++ hashval = isc_hash32(key, keysize, ht->case_sensitive); + +- memmove(node->key, key, keysize); +- node->keysize = keysize; +- node->next = ht->table[hash & ht->mask]; +- node->value = value; ++ if (isc__ht_find(ht, key, keysize, hashval, ht->hindex) != NULL) { ++ return (ISC_R_EXISTS); ++ } ++ ++ isc__ht_add(ht, key, keysize, hashval, ht->hindex, value); + +- ht->count++; +- ht->table[hash & ht->mask] = node; + return (ISC_R_SUCCESS); + } + ++static isc_ht_node_t * ++isc__ht_find(const isc_ht_t *ht, const unsigned char *key, ++ const uint32_t keysize, const uint32_t hashval, ++ const uint8_t idx) { ++ uint32_t hash; ++ uint8_t findex = idx; ++ ++nexttable: ++ hash = hash_32(hashval, ht->hashbits[findex]); ++ for (isc_ht_node_t *node = ht->table[findex][hash]; node != NULL; ++ node = node->next) ++ { ++ if (isc__ht_node_match(node, hashval, key, keysize, ++ ht->case_sensitive)) ++ { ++ return (node); ++ } ++ } ++ if (TRY_NEXTTABLE(findex, ht)) { ++ /* ++ * Rehashing in progress, check the other table ++ */ ++ findex = HT_NEXTTABLE(findex); ++ goto nexttable; ++ } ++ ++ return (NULL); ++} ++ + isc_result_t +-isc_ht_find(const isc_ht_t *ht, const unsigned char *key, uint32_t keysize, +- void **valuep) { ++isc_ht_find(const isc_ht_t *ht, const unsigned char *key, ++ const uint32_t keysize, void **valuep) { ++ uint32_t hashval; + isc_ht_node_t *node; +- uint32_t hash; + + REQUIRE(ISC_HT_VALID(ht)); + REQUIRE(key != NULL && keysize > 0); + REQUIRE(valuep == NULL || *valuep == NULL); + +- hash = isc_hash_function(key, keysize, true); +- node = ht->table[hash & ht->mask]; +- while (node != NULL) { +- if (keysize == node->keysize && +- memcmp(key, node->key, keysize) == 0) { +- if (valuep != NULL) { +- *valuep = node->value; +- } +- return (ISC_R_SUCCESS); +- } +- node = node->next; ++ hashval = isc_hash32(key, keysize, ht->case_sensitive); ++ ++ node = isc__ht_find(ht, key, keysize, hashval, ht->hindex); ++ if (node == NULL) { ++ return (ISC_R_NOTFOUND); + } + +- return (ISC_R_NOTFOUND); ++ if (valuep != NULL) { ++ *valuep = node->value; ++ } ++ return (ISC_R_SUCCESS); + } + +-isc_result_t +-isc_ht_delete(isc_ht_t *ht, const unsigned char *key, uint32_t keysize) { +- isc_ht_node_t *node, *prev; ++static isc_result_t ++isc__ht_delete(isc_ht_t *ht, const unsigned char *key, const uint32_t keysize, ++ const uint32_t hashval, const uint8_t idx) { ++ isc_ht_node_t *prev = NULL; + uint32_t hash; + +- REQUIRE(ISC_HT_VALID(ht)); +- REQUIRE(key != NULL && keysize > 0); ++ hash = hash_32(hashval, ht->hashbits[idx]); + +- prev = NULL; +- hash = isc_hash_function(key, keysize, true); +- node = ht->table[hash & ht->mask]; +- while (node != NULL) { +- if (keysize == node->keysize && +- memcmp(key, node->key, keysize) == 0) { ++ for (isc_ht_node_t *node = ht->table[idx][hash]; node != NULL; ++ prev = node, node = node->next) ++ { ++ if (isc__ht_node_match(node, hashval, key, keysize, ++ ht->case_sensitive)) { + if (prev == NULL) { +- ht->table[hash & ht->mask] = node->next; ++ ht->table[idx][hash] = node->next; + } else { + prev->next = node->next; + } + isc_mem_put(ht->mctx, node, +- offsetof(isc_ht_node_t, key) + +- node->keysize); ++ sizeof(*node) + node->keysize); + ht->count--; + + return (ISC_R_SUCCESS); + } +- +- prev = node; +- node = node->next; + } + return (ISC_R_NOTFOUND); + } + ++isc_result_t ++isc_ht_delete(isc_ht_t *ht, const unsigned char *key, const uint32_t keysize) { ++ uint32_t hashval; ++ uint8_t hindex; ++ isc_result_t result; ++ ++ REQUIRE(ISC_HT_VALID(ht)); ++ REQUIRE(key != NULL && keysize > 0); ++ ++ if (rehashing_in_progress(ht)) { ++ /* Rehash in progress */ ++ hashtable_rehash_one(ht); ++ } ++ ++ hindex = ht->hindex; ++ hashval = isc_hash32(key, keysize, ht->case_sensitive); ++nexttable: ++ result = isc__ht_delete(ht, key, keysize, hashval, hindex); ++ ++ if (result == ISC_R_NOTFOUND && TRY_NEXTTABLE(hindex, ht)) { ++ /* ++ * Rehashing in progress, check the other table ++ */ ++ hindex = HT_NEXTTABLE(hindex); ++ goto nexttable; ++ } ++ ++ return (result); ++} ++ + isc_result_t + isc_ht_iter_create(isc_ht_t *ht, isc_ht_iter_t **itp) { + isc_ht_iter_t *it; +@@ -208,9 +492,10 @@ isc_ht_iter_create(isc_ht_t *ht, isc_ht_iter_t **itp) { + + it = isc_mem_get(ht->mctx, sizeof(isc_ht_iter_t)); + +- it->ht = ht; +- it->i = 0; +- it->cur = NULL; ++ *it = (isc_ht_iter_t){ ++ .ht = ht, ++ .hindex = ht->hindex, ++ }; + + *itp = it; + +@@ -227,25 +512,46 @@ isc_ht_iter_destroy(isc_ht_iter_t **itp) { + it = *itp; + *itp = NULL; + ht = it->ht; +- isc_mem_put(ht->mctx, it, sizeof(isc_ht_iter_t)); ++ isc_mem_put(ht->mctx, it, sizeof(*it)); + } + + isc_result_t + isc_ht_iter_first(isc_ht_iter_t *it) { ++ isc_ht_t *ht; ++ + REQUIRE(it != NULL); + ++ ht = it->ht; ++ ++ it->hindex = ht->hindex; + it->i = 0; +- while (it->i < it->ht->size && it->ht->table[it->i] == NULL) { ++ ++ return (isc__ht_iter_next(it)); ++} ++ ++static isc_result_t ++isc__ht_iter_next(isc_ht_iter_t *it) { ++ isc_ht_t *ht = it->ht; ++ ++ while (it->i < ht->size[it->hindex] && ++ ht->table[it->hindex][it->i] == NULL) ++ { + it->i++; + } + +- if (it->i == it->ht->size) { +- return (ISC_R_NOMORE); ++ if (it->i < ht->size[it->hindex]) { ++ it->cur = ht->table[it->hindex][it->i]; ++ ++ return (ISC_R_SUCCESS); + } + +- it->cur = it->ht->table[it->i]; ++ if (TRY_NEXTTABLE(it->hindex, ht)) { ++ it->hindex = HT_NEXTTABLE(it->hindex); ++ it->i = 0; ++ return (isc__ht_iter_next(it)); ++ } + +- return (ISC_R_SUCCESS); ++ return (ISC_R_NOMORE); + } + + isc_result_t +@@ -254,60 +560,36 @@ isc_ht_iter_next(isc_ht_iter_t *it) { + REQUIRE(it->cur != NULL); + + it->cur = it->cur->next; +- if (it->cur == NULL) { +- do { +- it->i++; +- } while (it->i < it->ht->size && it->ht->table[it->i] == NULL); +- if (it->i >= it->ht->size) { +- return (ISC_R_NOMORE); +- } +- it->cur = it->ht->table[it->i]; ++ ++ if (it->cur != NULL) { ++ return (ISC_R_SUCCESS); + } + +- return (ISC_R_SUCCESS); ++ it->i++; ++ ++ return (isc__ht_iter_next(it)); + } + + isc_result_t + isc_ht_iter_delcurrent_next(isc_ht_iter_t *it) { + isc_result_t result = ISC_R_SUCCESS; +- isc_ht_node_t *to_delete = NULL; +- isc_ht_node_t *prev = NULL; +- isc_ht_node_t *node = NULL; +- uint32_t hash; ++ isc_ht_node_t *dnode = NULL; ++ uint8_t dindex; + isc_ht_t *ht; ++ isc_result_t dresult; ++ + REQUIRE(it != NULL); + REQUIRE(it->cur != NULL); +- to_delete = it->cur; +- ht = it->ht; + +- it->cur = it->cur->next; +- if (it->cur == NULL) { +- do { +- it->i++; +- } while (it->i < ht->size && ht->table[it->i] == NULL); +- if (it->i >= ht->size) { +- result = ISC_R_NOMORE; +- } else { +- it->cur = ht->table[it->i]; +- } +- } ++ ht = it->ht; ++ dnode = it->cur; ++ dindex = it->hindex; + +- hash = isc_hash_function(to_delete->key, to_delete->keysize, true); +- node = ht->table[hash & ht->mask]; +- while (node != to_delete) { +- prev = node; +- node = node->next; +- INSIST(node != NULL); +- } ++ result = isc_ht_iter_next(it); + +- if (prev == NULL) { +- ht->table[hash & ht->mask] = node->next; +- } else { +- prev->next = node->next; +- } +- isc_mem_put(ht->mctx, node, +- offsetof(isc_ht_node_t, key) + node->keysize); +- ht->count--; ++ dresult = isc__ht_delete(ht, dnode->key, dnode->keysize, dnode->hashval, ++ dindex); ++ INSIST(dresult == ISC_R_SUCCESS); + + return (result); + } +@@ -332,8 +614,8 @@ isc_ht_iter_currentkey(isc_ht_iter_t *it, unsigned char **key, + *keysize = it->cur->keysize; + } + +-unsigned int +-isc_ht_count(isc_ht_t *ht) { ++size_t ++isc_ht_count(const isc_ht_t *ht) { + REQUIRE(ISC_HT_VALID(ht)); + + return (ht->count); +diff --git a/lib/isc/include/isc/ht.h b/lib/isc/include/isc/ht.h +index 9d5ab82..46cd32d 100644 +--- a/lib/isc/include/isc/ht.h ++++ b/lib/isc/include/isc/ht.h +@@ -11,8 +11,7 @@ + + /* ! \file */ + +-#ifndef ISC_HT_H +-#define ISC_HT_H 1 ++#pragma once + + #include + #include +@@ -23,9 +22,15 @@ + typedef struct isc_ht isc_ht_t; + typedef struct isc_ht_iter isc_ht_iter_t; + ++enum { ISC_HT_CASE_SENSITIVE = 0x00, ISC_HT_CASE_INSENSITIVE = 0x01 }; ++ + /*% + * Initialize hashtable at *htp, using memory context and size of (1< +Date: Mon, 12 Feb 2024 21:09:51 +0100 +Subject: [PATCH] Prevent increased CPU consumption in DNSSEC validator + +KeyTrap - Extreme CPU consumption in DNSSEC validator. Preparing an +NSEC3 closest encloser proof can exhaust CPU resources. + +6322. [security] Specific DNS answers could cause a denial-of-service + condition due to DNS validation taking a long time. + (CVE-2023-50387) [GL #4424] + +Resolves: CVE-2023-50387 CVE-2023-50868 +--- + lib/dns/dst_api.c | 27 +++++++++---- + lib/dns/include/dns/validator.h | 1 + + lib/dns/include/dst/dst.h | 4 ++ + lib/dns/resolver.c | 4 +- + lib/dns/validator.c | 67 +++++++++++++++------------------ + lib/isc/include/isc/netmgr.h | 3 ++ + lib/isc/netmgr/netmgr-int.h | 1 + + lib/isc/netmgr/netmgr.c | 36 +++++++++++------- + lib/isc/netmgr/tcp.c | 6 +-- + lib/isc/netmgr/tcpdns.c | 4 +- + lib/isc/netmgr/udp.c | 6 +-- + 11 files changed, 91 insertions(+), 68 deletions(-) + +diff --git a/lib/dns/dst_api.c b/lib/dns/dst_api.c +index 62600dd..3aafd7c 100644 +--- a/lib/dns/dst_api.c ++++ b/lib/dns/dst_api.c +@@ -160,7 +160,8 @@ computeid(dst_key_t *key); + static isc_result_t + frombuffer(const dns_name_t *name, unsigned int alg, unsigned int flags, + unsigned int protocol, dns_rdataclass_t rdclass, +- isc_buffer_t *source, isc_mem_t *mctx, dst_key_t **keyp); ++ isc_buffer_t *source, isc_mem_t *mctx, bool no_rdata, ++ dst_key_t **keyp); + + static isc_result_t + algorithm_status(unsigned int alg); +@@ -745,6 +746,13 @@ dst_key_todns(const dst_key_t *key, isc_buffer_t *target) { + isc_result_t + dst_key_fromdns(const dns_name_t *name, dns_rdataclass_t rdclass, + isc_buffer_t *source, isc_mem_t *mctx, dst_key_t **keyp) { ++ return (dst_key_fromdns_ex(name, rdclass, source, mctx, false, keyp)); ++} ++ ++isc_result_t ++dst_key_fromdns_ex(const dns_name_t *name, dns_rdataclass_t rdclass, ++ isc_buffer_t *source, isc_mem_t *mctx, bool no_rdata, ++ dst_key_t **keyp) { + uint8_t alg, proto; + uint32_t flags, extflags; + dst_key_t *key = NULL; +@@ -775,7 +783,7 @@ dst_key_fromdns(const dns_name_t *name, dns_rdataclass_t rdclass, + } + + result = frombuffer(name, alg, flags, proto, rdclass, source, mctx, +- &key); ++ no_rdata, &key); + if (result != ISC_R_SUCCESS) { + return (result); + } +@@ -796,7 +804,7 @@ dst_key_frombuffer(const dns_name_t *name, unsigned int alg, unsigned int flags, + REQUIRE(dst_initialized); + + result = frombuffer(name, alg, flags, protocol, rdclass, source, mctx, +- &key); ++ false, &key); + if (result != ISC_R_SUCCESS) { + return (result); + } +@@ -2288,7 +2296,8 @@ computeid(dst_key_t *key) { + static isc_result_t + frombuffer(const dns_name_t *name, unsigned int alg, unsigned int flags, + unsigned int protocol, dns_rdataclass_t rdclass, +- isc_buffer_t *source, isc_mem_t *mctx, dst_key_t **keyp) { ++ isc_buffer_t *source, isc_mem_t *mctx, bool no_rdata, ++ dst_key_t **keyp) { + dst_key_t *key; + isc_result_t ret; + +@@ -2313,10 +2322,12 @@ frombuffer(const dns_name_t *name, unsigned int alg, unsigned int flags, + return (DST_R_UNSUPPORTEDALG); + } + +- ret = key->func->fromdns(key, source); +- if (ret != ISC_R_SUCCESS) { +- dst_key_free(&key); +- return (ret); ++ if (!no_rdata) { ++ ret = key->func->fromdns(key, source); ++ if (ret != ISC_R_SUCCESS) { ++ dst_key_free(&key); ++ return (ret); ++ } + } + } + +diff --git a/lib/dns/include/dns/validator.h b/lib/dns/include/dns/validator.h +index 4744014..fe97e41 100644 +--- a/lib/dns/include/dns/validator.h ++++ b/lib/dns/include/dns/validator.h +@@ -148,6 +148,7 @@ struct dns_validator { + unsigned int authcount; + unsigned int authfail; + isc_stdtime_t start; ++ bool failed; + }; + + /*% +diff --git a/lib/dns/include/dst/dst.h b/lib/dns/include/dst/dst.h +index f454ebb..36770b5 100644 +--- a/lib/dns/include/dst/dst.h ++++ b/lib/dns/include/dst/dst.h +@@ -469,6 +469,10 @@ dst_key_tofile(const dst_key_t *key, int type, const char *directory); + */ + + isc_result_t ++dst_key_fromdns_ex(const dns_name_t *name, dns_rdataclass_t rdclass, ++ isc_buffer_t *source, isc_mem_t *mctx, bool no_rdata, ++ dst_key_t **keyp); ++isc_result_t + dst_key_fromdns(const dns_name_t *name, dns_rdataclass_t rdclass, + isc_buffer_t *source, isc_mem_t *mctx, dst_key_t **keyp); + /*%< +diff --git a/lib/dns/resolver.c b/lib/dns/resolver.c +index 7cbfbb2..be1d735 100644 +--- a/lib/dns/resolver.c ++++ b/lib/dns/resolver.c +@@ -10613,8 +10613,8 @@ dns_resolver_create(dns_view_t *view, isc_taskmgr_t *taskmgr, + * Since we have a pool of tasks we bind them to task queues + * to spread the load evenly + */ +- result = isc_task_create_bound(taskmgr, 0, +- &res->buckets[i].task, i); ++ result = isc_task_create_bound( ++ taskmgr, 0, &res->buckets[i].task, ISC_NM_TASK_SLOW(i)); + if (result != ISC_R_SUCCESS) { + isc_mutex_destroy(&res->buckets[i].lock); + goto cleanup_buckets; +diff --git a/lib/dns/validator.c b/lib/dns/validator.c +index e54fc70..e416cc9 100644 +--- a/lib/dns/validator.c ++++ b/lib/dns/validator.c +@@ -1098,8 +1098,8 @@ create_validator(dns_validator_t *val, dns_name_t *name, dns_rdatatype_t type, + * 'rdataset'. If found, build a dst_key_t for it and point val->key at + * it. + * +- * If val->key is already non-NULL, locate it in the rdataset and then +- * search past it for the *next* key that could have signed 'siginfo', then ++ * If val->key is already non-NULL, start searching from the next position in ++ * 'rdataset' to find the *next* key that could have signed 'siginfo', then + * set val->key to that. + * + * Returns ISC_R_SUCCESS if a possible matching key has been found, +@@ -1112,59 +1112,59 @@ select_signing_key(dns_validator_t *val, dns_rdataset_t *rdataset) { + isc_buffer_t b; + dns_rdata_t rdata = DNS_RDATA_INIT; + dst_key_t *oldkey = val->key; +- bool foundold; ++ bool no_rdata = false; + + if (oldkey == NULL) { +- foundold = true; ++ result = dns_rdataset_first(rdataset); + } else { +- foundold = false; ++ dst_key_free(&oldkey); + val->key = NULL; ++ result = dns_rdataset_next(rdataset); + } +- +- result = dns_rdataset_first(rdataset); + if (result != ISC_R_SUCCESS) { +- goto failure; ++ goto done; + } ++ + do { + dns_rdataset_current(rdataset, &rdata); + + isc_buffer_init(&b, rdata.data, rdata.length); + isc_buffer_add(&b, rdata.length); + INSIST(val->key == NULL); +- result = dst_key_fromdns(&siginfo->signer, rdata.rdclass, &b, +- val->view->mctx, &val->key); ++ result = dst_key_fromdns_ex(&siginfo->signer, rdata.rdclass, &b, ++ val->view->mctx, no_rdata, ++ &val->key); + if (result == ISC_R_SUCCESS) { + if (siginfo->algorithm == + (dns_secalg_t)dst_key_alg(val->key) && + siginfo->keyid == + (dns_keytag_t)dst_key_id(val->key) && ++ (dst_key_flags(val->key) & DNS_KEYFLAG_REVOKE) == ++ 0 && + dst_key_iszonekey(val->key)) + { +- if (foundold) { +- /* +- * This is the key we're looking for. +- */ +- return (ISC_R_SUCCESS); +- } else if (dst_key_compare(oldkey, val->key)) { +- foundold = true; +- dst_key_free(&oldkey); ++ if (no_rdata) { ++ /* Retry with full key */ ++ dns_rdata_reset(&rdata); ++ dst_key_free(&val->key); ++ no_rdata = false; ++ continue; + } ++ /* This is the key we're looking for. */ ++ goto done; + } + dst_key_free(&val->key); + } + dns_rdata_reset(&rdata); + result = dns_rdataset_next(rdataset); ++ no_rdata = true; + } while (result == ISC_R_SUCCESS); + ++done: + if (result == ISC_R_NOMORE) { + result = ISC_R_NOTFOUND; + } + +-failure: +- if (oldkey != NULL) { +- dst_key_free(&oldkey); +- } +- + return (result); + } + +@@ -1557,20 +1557,9 @@ validate_answer(dns_validator_t *val, bool resume) { + continue; + } + +- do { +- isc_result_t tresult; +- vresult = verify(val, val->key, &rdata, +- val->siginfo->keyid); +- if (vresult == ISC_R_SUCCESS) { +- break; +- } +- +- tresult = select_signing_key(val, val->keyset); +- if (tresult != ISC_R_SUCCESS) { +- break; +- } +- } while (1); ++ vresult = verify(val, val->key, &rdata, val->siginfo->keyid); + if (vresult != ISC_R_SUCCESS) { ++ val->failed = true; + validator_log(val, ISC_LOG_DEBUG(3), + "failed to verify rdataset"); + } else { +@@ -1607,9 +1596,13 @@ validate_answer(dns_validator_t *val, bool resume) { + } else { + validator_log(val, ISC_LOG_DEBUG(3), + "verify failure: %s", +- isc_result_totext(result)); ++ isc_result_totext(vresult)); + resume = false; + } ++ if (val->failed) { ++ result = ISC_R_NOMORE; ++ break; ++ } + } + if (result != ISC_R_NOMORE) { + validator_log(val, ISC_LOG_DEBUG(3), +diff --git a/lib/isc/include/isc/netmgr.h b/lib/isc/include/isc/netmgr.h +index be9fd56..dfabdc8 100644 +--- a/lib/isc/include/isc/netmgr.h ++++ b/lib/isc/include/isc/netmgr.h +@@ -455,6 +455,9 @@ isc_nm_tcpdnsconnect(isc_nm_t *mgr, isc_sockaddr_t *local, isc_sockaddr_t *peer, + * 'cb'. + */ + ++#define ISC_NM_TASK_SLOW_OFFSET -2 ++#define ISC_NM_TASK_SLOW(i) (ISC_NM_TASK_SLOW_OFFSET - 1 - i) ++ + void + isc_nm_task_enqueue(isc_nm_t *mgr, isc_task_t *task, int threadid); + /*%< +diff --git a/lib/isc/netmgr/netmgr-int.h b/lib/isc/netmgr/netmgr-int.h +index f7b54f9..70bb32d 100644 +--- a/lib/isc/netmgr/netmgr-int.h ++++ b/lib/isc/netmgr/netmgr-int.h +@@ -673,6 +673,7 @@ struct isc_nm { + #ifdef NETMGR_TRACE + ISC_LIST(isc_nmsocket_t) active_sockets; + #endif ++ int nlisteners; + }; + + typedef enum isc_nmsocket_type { +diff --git a/lib/isc/netmgr/netmgr.c b/lib/isc/netmgr/netmgr.c +index 0ed3182..898de41 100644 +--- a/lib/isc/netmgr/netmgr.c ++++ b/lib/isc/netmgr/netmgr.c +@@ -269,31 +269,34 @@ isc__nm_winsock_destroy(void) { + #endif /* WIN32 */ + + static void +-isc__nm_threadpool_initialize(uint32_t workers) { ++isc__nm_threadpool_initialize(uint32_t nworkers) { + char buf[11]; + int r = uv_os_getenv("UV_THREADPOOL_SIZE", buf, + &(size_t){ sizeof(buf) }); + if (r == UV_ENOENT) { +- snprintf(buf, sizeof(buf), "%" PRIu32, workers); ++ snprintf(buf, sizeof(buf), "%" PRIu32, nworkers); + uv_os_setenv("UV_THREADPOOL_SIZE", buf); + } + } + + void +-isc__netmgr_create(isc_mem_t *mctx, uint32_t workers, isc_nm_t **netmgrp) { ++isc__netmgr_create(isc_mem_t *mctx, uint32_t nworkers, isc_nm_t **netmgrp) { + isc_nm_t *mgr = NULL; + char name[32]; + +- REQUIRE(workers > 0); ++ REQUIRE(nworkers > 0); + + #ifdef WIN32 + isc__nm_winsock_initialize(); + #endif /* WIN32 */ + +- isc__nm_threadpool_initialize(workers); ++ isc__nm_threadpool_initialize(nworkers); + + mgr = isc_mem_get(mctx, sizeof(*mgr)); +- *mgr = (isc_nm_t){ .nworkers = workers }; ++ *mgr = (isc_nm_t){ ++ .nworkers = nworkers * 2, ++ .nlisteners = nworkers, ++ }; + + isc_mem_attach(mctx, &mgr->mctx); + isc_mutex_init(&mgr->lock); +@@ -334,11 +337,12 @@ isc__netmgr_create(isc_mem_t *mctx, uint32_t workers, isc_nm_t **netmgrp) { + isc_mempool_associatelock(mgr->evpool, &mgr->evlock); + isc_mempool_setfillcount(mgr->evpool, 32); + +- isc_barrier_init(&mgr->pausing, workers); +- isc_barrier_init(&mgr->resuming, workers); ++ isc_barrier_init(&mgr->pausing, mgr->nworkers); ++ isc_barrier_init(&mgr->resuming, mgr->nworkers); + +- mgr->workers = isc_mem_get(mctx, workers * sizeof(isc__networker_t)); +- for (size_t i = 0; i < workers; i++) { ++ mgr->workers = isc_mem_get(mctx, ++ mgr->nworkers * sizeof(isc__networker_t)); ++ for (int i = 0; i < mgr->nworkers; i++) { + int r; + isc__networker_t *worker = &mgr->workers[i]; + *worker = (isc__networker_t){ +@@ -373,7 +377,7 @@ isc__netmgr_create(isc_mem_t *mctx, uint32_t workers, isc_nm_t **netmgrp) { + mgr->workers_running++; + isc_thread_create(nm_thread, &mgr->workers[i], &worker->thread); + +- snprintf(name, sizeof(name), "isc-net-%04zu", i); ++ snprintf(name, sizeof(name), "isc-net-%04d", i); + isc_thread_setname(worker->thread, name); + } + +@@ -848,9 +852,15 @@ isc_nm_task_enqueue(isc_nm_t *nm, isc_task_t *task, int threadid) { + isc__networker_t *worker = NULL; + + if (threadid == -1) { +- tid = (int)isc_random_uniform(nm->nworkers); ++ tid = (int)isc_random_uniform(nm->nlisteners); ++ } else if (threadid == ISC_NM_TASK_SLOW_OFFSET) { ++ tid = nm->nlisteners + ++ (int)isc_random_uniform(nm->nworkers - nm->nlisteners); ++ } else if (threadid < ISC_NM_TASK_SLOW_OFFSET) { ++ tid = nm->nlisteners + (ISC_NM_TASK_SLOW(threadid) % ++ (nm->nworkers - nm->nlisteners)); + } else { +- tid = threadid % nm->nworkers; ++ tid = threadid % nm->nlisteners; + } + + worker = &nm->workers[tid]; +diff --git a/lib/isc/netmgr/tcp.c b/lib/isc/netmgr/tcp.c +index 5cca9f5..83bd2e2 100644 +--- a/lib/isc/netmgr/tcp.c ++++ b/lib/isc/netmgr/tcp.c +@@ -321,7 +321,7 @@ isc_nm_tcpconnect(isc_nm_t *mgr, isc_sockaddr_t *local, isc_sockaddr_t *peer, + isc__nm_connectcb(sock, req, result, false); + } else { + isc__nmsocket_clearcb(sock); +- sock->tid = isc_random_uniform(mgr->nworkers); ++ sock->tid = isc_random_uniform(mgr->nlisteners); + isc__nm_connectcb(sock, req, result, true); + } + atomic_store(&sock->closed, true); +@@ -339,7 +339,7 @@ isc_nm_tcpconnect(isc_nm_t *mgr, isc_sockaddr_t *local, isc_sockaddr_t *peer, + isc__nm_put_netievent_tcpconnect(mgr, ievent); + } else { + atomic_init(&sock->active, false); +- sock->tid = isc_random_uniform(mgr->nworkers); ++ sock->tid = isc_random_uniform(mgr->nlisteners); + isc__nm_enqueue_ievent(&mgr->workers[sock->tid], + (isc__netievent_t *)ievent); + } +@@ -435,7 +435,7 @@ isc_nm_listentcp(isc_nm_t *mgr, isc_sockaddr_t *iface, + #if defined(WIN32) + sock->nchildren = 1; + #else +- sock->nchildren = mgr->nworkers; ++ sock->nchildren = mgr->nlisteners; + #endif + children_size = sock->nchildren * sizeof(sock->children[0]); + sock->children = isc_mem_get(mgr->mctx, children_size); +diff --git a/lib/isc/netmgr/tcpdns.c b/lib/isc/netmgr/tcpdns.c +index 188790c..7f13ab2 100644 +--- a/lib/isc/netmgr/tcpdns.c ++++ b/lib/isc/netmgr/tcpdns.c +@@ -305,7 +305,7 @@ isc_nm_tcpdnsconnect(isc_nm_t *mgr, isc_sockaddr_t *local, isc_sockaddr_t *peer, + isc__nm_put_netievent_tcpdnsconnect(mgr, ievent); + } else { + atomic_init(&sock->active, false); +- sock->tid = isc_random_uniform(mgr->nworkers); ++ sock->tid = isc_random_uniform(mgr->nlisteners); + isc__nm_enqueue_ievent(&mgr->workers[sock->tid], + (isc__netievent_t *)ievent); + } +@@ -404,7 +404,7 @@ isc_nm_listentcpdns(isc_nm_t *mgr, isc_sockaddr_t *iface, + #if defined(WIN32) + sock->nchildren = 1; + #else +- sock->nchildren = mgr->nworkers; ++ sock->nchildren = mgr->nlisteners; + #endif + children_size = sock->nchildren * sizeof(sock->children[0]); + sock->children = isc_mem_get(mgr->mctx, children_size); +diff --git a/lib/isc/netmgr/udp.c b/lib/isc/netmgr/udp.c +index a91c425..f2e161c 100644 +--- a/lib/isc/netmgr/udp.c ++++ b/lib/isc/netmgr/udp.c +@@ -126,7 +126,7 @@ isc_nm_listenudp(isc_nm_t *mgr, isc_sockaddr_t *iface, isc_nm_recv_cb_t cb, + uv_os_sock_t fd = -1; + + /* +- * We are creating mgr->nworkers duplicated sockets, one ++ * We are creating mgr->nlisteners duplicated sockets, one + * socket for each worker thread. + */ + sock = isc_mem_get(mgr->mctx, sizeof(isc_nmsocket_t)); +@@ -136,7 +136,7 @@ isc_nm_listenudp(isc_nm_t *mgr, isc_sockaddr_t *iface, isc_nm_recv_cb_t cb, + #if defined(WIN32) + sock->nchildren = 1; + #else +- sock->nchildren = mgr->nworkers; ++ sock->nchildren = mgr->nlisteners; + #endif + + children_size = sock->nchildren * sizeof(sock->children[0]); +@@ -795,7 +795,7 @@ isc_nm_udpconnect(isc_nm_t *mgr, isc_sockaddr_t *local, isc_sockaddr_t *peer, + isc__nm_put_netievent_udpconnect(mgr, event); + } else { + atomic_init(&sock->active, false); +- sock->tid = isc_random_uniform(mgr->nworkers); ++ sock->tid = isc_random_uniform(mgr->nlisteners); + isc__nm_enqueue_ievent(&mgr->workers[sock->tid], + (isc__netievent_t *)event); + } +-- +2.43.0 + diff --git a/SOURCES/bind-9.16-CVE-2023-5517.patch b/SOURCES/bind-9.16-CVE-2023-5517.patch new file mode 100644 index 0000000..86a8cfd --- /dev/null +++ b/SOURCES/bind-9.16-CVE-2023-5517.patch @@ -0,0 +1,111 @@ +From bef141d5795429cab745f29f7d080d1e2ea8f164 Mon Sep 17 00:00:00 2001 +From: Petr Mensik +Date: Mon, 12 Feb 2024 20:33:41 +0100 +Subject: [PATCH] Prevent assertion failure when nxdomain-redirect is used with + RFC 1918 reverse zones + +6316. [security] Specific queries could trigger an assertion check with + nxdomain-redirect enabled. (CVE-2023-5517) [GL #4281] +--- + lib/ns/query.c | 25 ++++++++++++------------- + 1 file changed, 12 insertions(+), 13 deletions(-) + +diff --git a/lib/ns/query.c b/lib/ns/query.c +index 4fe3e30..cc1d179 100644 +--- a/lib/ns/query.c ++++ b/lib/ns/query.c +@@ -453,10 +453,10 @@ static void + query_addnxrrsetnsec(query_ctx_t *qctx); + + static isc_result_t +-query_nxdomain(query_ctx_t *qctx, bool empty_wild); ++query_nxdomain(query_ctx_t *qctx, isc_result_t result); + + static isc_result_t +-query_redirect(query_ctx_t *qctx); ++query_redirect(query_ctx_t *qctx, isc_result_t result); + + static isc_result_t + query_ncache(query_ctx_t *qctx, isc_result_t result); +@@ -7262,8 +7262,7 @@ query_usestale(query_ctx_t *qctx, isc_result_t result) { + * result from the search. + */ + static isc_result_t +-query_gotanswer(query_ctx_t *qctx, isc_result_t res) { +- isc_result_t result = res; ++query_gotanswer(query_ctx_t *qctx, isc_result_t result) { + char errmsg[256]; + + CCTRACE(ISC_LOG_DEBUG(3), "query_gotanswer"); +@@ -7333,16 +7332,16 @@ root_key_sentinel: + return (query_nodata(qctx, DNS_R_NXRRSET)); + + case DNS_R_EMPTYWILD: +- return (query_nxdomain(qctx, true)); ++ return (query_nxdomain(qctx, DNS_R_EMPTYWILD)); + + case DNS_R_NXDOMAIN: +- return (query_nxdomain(qctx, false)); ++ return (query_nxdomain(qctx, DNS_R_NXDOMAIN)); + + case DNS_R_COVERINGNSEC: + return (query_coveringnsec(qctx)); + + case DNS_R_NCACHENXDOMAIN: +- result = query_redirect(qctx); ++ result = query_redirect(qctx, result); + if (result != ISC_R_COMPLETE) { + return (result); + } +@@ -9155,10 +9154,10 @@ query_addnxrrsetnsec(query_ctx_t *qctx) { + * Handle NXDOMAIN and empty wildcard responses. + */ + static isc_result_t +-query_nxdomain(query_ctx_t *qctx, bool empty_wild) { ++query_nxdomain(query_ctx_t *qctx, isc_result_t result) { + dns_section_t section; + uint32_t ttl; +- isc_result_t result; ++ bool empty_wild = (result == DNS_R_EMPTYWILD); + + CCTRACE(ISC_LOG_DEBUG(3), "query_nxdomain"); + +@@ -9167,7 +9166,7 @@ query_nxdomain(query_ctx_t *qctx, bool empty_wild) { + INSIST(qctx->is_zone || REDIRECT(qctx->client)); + + if (!empty_wild) { +- result = query_redirect(qctx); ++ result = query_redirect(qctx, result); + if (result != ISC_R_COMPLETE) { + return (result); + } +@@ -9253,7 +9252,7 @@ cleanup: + * redirecting, so query processing should continue past it. + */ + static isc_result_t +-query_redirect(query_ctx_t *qctx) { ++query_redirect(query_ctx_t *qctx, isc_result_t saved_result) { + isc_result_t result; + + CCTRACE(ISC_LOG_DEBUG(3), "query_redirect"); +@@ -9294,7 +9293,7 @@ query_redirect(query_ctx_t *qctx) { + SAVE(qctx->client->query.redirect.rdataset, qctx->rdataset); + SAVE(qctx->client->query.redirect.sigrdataset, + qctx->sigrdataset); +- qctx->client->query.redirect.result = DNS_R_NCACHENXDOMAIN; ++ qctx->client->query.redirect.result = saved_result; + dns_name_copynf(qctx->fname, + qctx->client->query.redirect.fname); + qctx->client->query.redirect.authoritative = +@@ -9908,7 +9907,7 @@ query_coveringnsec(query_ctx_t *qctx) { + * We now have the proof that we have an NXDOMAIN. Apply + * NXDOMAIN redirection if configured. + */ +- result = query_redirect(qctx); ++ result = query_redirect(qctx, DNS_R_COVERINGNSEC); + if (result != ISC_R_COMPLETE) { + redirected = true; + goto cleanup; +-- +2.43.0 + diff --git a/SOURCES/bind-9.16-CVE-2023-5679.patch b/SOURCES/bind-9.16-CVE-2023-5679.patch new file mode 100644 index 0000000..0b05b9b --- /dev/null +++ b/SOURCES/bind-9.16-CVE-2023-5679.patch @@ -0,0 +1,37 @@ +From 61112d1ce39848e08ec133f280cf8f729cb70d16 Mon Sep 17 00:00:00 2001 +From: Petr Mensik +Date: Mon, 12 Feb 2024 20:41:43 +0100 +Subject: [PATCH] Prevent assertion failure if DNS64 and serve-stale is used + +Enabling both DNS64 and serve-stale may cause an assertion failure +during recursive resolution. + +6317. [security] Restore DNS64 state when handling a serve-stale timeout. + (CVE-2023-5679) [GL #4334] + +Resolves: CVE-2023-5679 +--- + lib/ns/query.c | 7 +++++++ + 1 file changed, 7 insertions(+) + +diff --git a/lib/ns/query.c b/lib/ns/query.c +index cc1d179..1993800 100644 +--- a/lib/ns/query.c ++++ b/lib/ns/query.c +@@ -5983,6 +5983,13 @@ query_lookup_stale(ns_client_t *client) { + query_ctx_t qctx; + + qctx_init(client, NULL, client->query.qtype, &qctx); ++ if (DNS64(client)) { ++ qctx.qtype = qctx.type = dns_rdatatype_a; ++ qctx.dns64 = true; ++ } ++ if (DNS64EXCLUDE(client)) { ++ qctx.dns64_exclude = true; ++ } + dns_db_attach(client->view->cachedb, &qctx.db); + client->query.attributes &= ~NS_QUERYATTR_RECURSIONOK; + client->query.dboptions |= DNS_DBFIND_STALETIMEOUT; +-- +2.43.0 + diff --git a/SOURCES/bind-9.16-CVE-2023-6516-test.patch b/SOURCES/bind-9.16-CVE-2023-6516-test.patch new file mode 100644 index 0000000..8ce8ec2 --- /dev/null +++ b/SOURCES/bind-9.16-CVE-2023-6516-test.patch @@ -0,0 +1,52 @@ +From e91ab7758bed0cf3dcf8ed745f91063d7ec4011c Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Micha=C5=82=20K=C4=99pie=C5=84?= +Date: Thu, 4 Jan 2024 13:39:27 +0100 +Subject: [PATCH] Fix map offsets in the "masterformat" system test + +The "masterformat" system test attempts to check named-checkzone +behavior when it is fed corrupt map-format zone files. However, despite +the RBTDB and RBT structures having evolved over the years, the offsets +at which a valid map-format zone file is malformed by the "masterformat" +test have not been updated accordingly, causing the relevant checks to +introduce a different type of corruption than they were originally meant +to cause: + + - the "bad node header" check originally mangled the 'type' member of + the rdatasetheader_t structure for cname.example.nil, + + - the "bad node data" check originally mangled the 'serial' and + 'rdh_ttl' members of the rdatasetheader_t structure for + aaaa.example.nil. + +Update the offsets at which the map-format zone file is malformed at by +the "masterformat" system test so that the relevant checks fulfill their +original purpose again. +--- + bin/tests/system/masterformat/tests.sh | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/bin/tests/system/masterformat/tests.sh b/bin/tests/system/masterformat/tests.sh +index 364a0d2..bb4e6ec 100755 +--- a/bin/tests/system/masterformat/tests.sh ++++ b/bin/tests/system/masterformat/tests.sh +@@ -295,7 +295,7 @@ status=$((status+ret)) + echo_i "checking corrupt map files fail to load (bad node header) ($n)" + ret=0 + cp map.5 badmap +-stomp badmap 2754 2 99 ++stomp badmap 3706 2 99 + $CHECKZONE -D -f map -F text -o text.5 example.nil badmap > /dev/null + [ $? = 1 ] || ret=1 + n=$((n+1)) +@@ -305,7 +305,7 @@ status=$((status+ret)) + echo_i "checking corrupt map files fail to load (bad node data) ($n)" + ret=0 + cp map.5 badmap +-stomp badmap 2897 5 127 ++stomp badmap 3137 5 127 + $CHECKZONE -D -f map -F text -o text.5 example.nil badmap > /dev/null + [ $? = 1 ] || ret=1 + n=$((n+1)) +-- +2.44.0 + diff --git a/SOURCES/bind-9.16-CVE-2023-6516.patch b/SOURCES/bind-9.16-CVE-2023-6516.patch new file mode 100644 index 0000000..4f7efa1 --- /dev/null +++ b/SOURCES/bind-9.16-CVE-2023-6516.patch @@ -0,0 +1,283 @@ +From 6e08fef24d7ba491228a4083ea0f0e33253a1043 Mon Sep 17 00:00:00 2001 +From: Petr Mensik +Date: Mon, 12 Feb 2024 20:48:57 +0100 +Subject: [PATCH] Specific recursive query patterns may lead to an + out-of-memory condition + +6319. [security] Query patterns that continuously triggered cache + database maintenance could exhaust all available memory + on the host running named. (CVE-2023-6516) [GL #4383] + +Resolves: CVE-2023-6516 +--- + lib/dns/include/dns/rbt.h | 6 ++ + lib/dns/mapapi | 2 +- + lib/dns/rbt.c | 1 + + lib/dns/rbtdb.c | 149 +++++++++++++++++++++++++------------- + 4 files changed, 107 insertions(+), 51 deletions(-) + +diff --git a/lib/dns/include/dns/rbt.h b/lib/dns/include/dns/rbt.h +index b67e602..69655b0 100644 +--- a/lib/dns/include/dns/rbt.h ++++ b/lib/dns/include/dns/rbt.h +@@ -164,6 +164,12 @@ struct dns_rbtnode { + uint16_t locknum; /* note that this is not in the bitfield */ + isc_refcount_t references; + /*@}*/ ++ ++ /*% ++ * This linked list is used to store nodes from which tree pruning can ++ * be started. ++ */ ++ ISC_LINK(dns_rbtnode_t) prunelink; + }; + + typedef isc_result_t (*dns_rbtfindcallback_t)(dns_rbtnode_t *node, +diff --git a/lib/dns/mapapi b/lib/dns/mapapi +index 1b502d3..a46e190 100644 +--- a/lib/dns/mapapi ++++ b/lib/dns/mapapi +@@ -13,4 +13,4 @@ + # Whenever releasing a new major release of BIND9, set this value + # back to 1.0 when releasing the first alpha. Map files are *never* + # compatible across major releases. +-MAPAPI=3.0 ++MAPAPI=4.0 +diff --git a/lib/dns/rbt.c b/lib/dns/rbt.c +index 7f2c2d2..a220368 100644 +--- a/lib/dns/rbt.c ++++ b/lib/dns/rbt.c +@@ -2283,6 +2283,7 @@ create_node(isc_mem_t *mctx, const dns_name_t *name, dns_rbtnode_t **nodep) { + HASHVAL(node) = 0; + + ISC_LINK_INIT(node, deadlink); ++ ISC_LINK_INIT(node, prunelink); + + LOCKNUM(node) = 0; + WILD(node) = 0; +diff --git a/lib/dns/rbtdb.c b/lib/dns/rbtdb.c +index 75f97f5..2707507 100644 +--- a/lib/dns/rbtdb.c ++++ b/lib/dns/rbtdb.c +@@ -515,6 +515,10 @@ struct dns_rbtdb { + */ + rbtnodelist_t *deadnodes; + ++ /* List of nodes from which recursive tree pruning can be started from. ++ * Locked by tree_lock. */ ++ rbtnodelist_t prunenodes; ++ + /* + * Heaps. These are used for TTL based expiry in a cache, + * or for zone resigning in a zone DB. hmctx is the memory +@@ -1060,6 +1064,7 @@ free_rbtdb(dns_rbtdb_t *rbtdb, bool log, isc_event_t *event) { + unsigned int i; + isc_result_t result; + char buf[DNS_NAME_FORMATSIZE]; ++ dns_rbtnode_t *node = NULL; + dns_rbt_t **treep; + isc_time_t start; + dns_dbonupdatelistener_t *listener, *listener_next; +@@ -1086,8 +1091,6 @@ free_rbtdb(dns_rbtdb_t *rbtdb, bool log, isc_event_t *event) { + * the overhead of unlinking all nodes here should be negligible. + */ + for (i = 0; i < rbtdb->node_lock_count; i++) { +- dns_rbtnode_t *node; +- + node = ISC_LIST_HEAD(rbtdb->deadnodes[i]); + while (node != NULL) { + ISC_LIST_UNLINK(rbtdb->deadnodes[i], node, deadlink); +@@ -1095,6 +1098,12 @@ free_rbtdb(dns_rbtdb_t *rbtdb, bool log, isc_event_t *event) { + } + } + ++ node = ISC_LIST_HEAD(rbtdb->prunenodes); ++ while (node != NULL) { ++ ISC_LIST_UNLINK(rbtdb->prunenodes, node, prunelink); ++ node = ISC_LIST_HEAD(rbtdb->prunenodes); ++ } ++ + if (event == NULL) { + rbtdb->quantum = (rbtdb->task != NULL) ? 100 : 0; + } +@@ -1934,19 +1943,32 @@ is_leaf(dns_rbtnode_t *node) { + node->left == NULL && node->right == NULL); + } + ++/*% ++ * The tree lock must be held when this function is called as it reads and ++ * updates rbtdb->prunenodes. ++ */ + static inline void + send_to_prune_tree(dns_rbtdb_t *rbtdb, dns_rbtnode_t *node, + isc_rwlocktype_t locktype) { +- isc_event_t *ev; +- dns_db_t *db; ++ bool pruning_queued = (ISC_LIST_HEAD(rbtdb->prunenodes) != NULL); ++ ++ INSIST(locktype == isc_rwlocktype_write); + +- ev = isc_event_allocate(rbtdb->common.mctx, NULL, DNS_EVENT_RBTPRUNE, +- prune_tree, node, sizeof(isc_event_t)); + new_reference(rbtdb, node, locktype); +- db = NULL; +- attach((dns_db_t *)rbtdb, &db); +- ev->ev_sender = db; +- isc_task_send(rbtdb->task, &ev); ++ INSIST(!ISC_LINK_LINKED(node, prunelink)); ++ ISC_LIST_APPEND(rbtdb->prunenodes, node, prunelink); ++ ++ if (!pruning_queued) { ++ isc_event_t *ev = NULL; ++ dns_db_t *db = NULL; ++ ++ attach((dns_db_t *)rbtdb, &db); ++ ++ ev = isc_event_allocate(rbtdb->common.mctx, NULL, ++ DNS_EVENT_RBTPRUNE, prune_tree, db, ++ sizeof(isc_event_t)); ++ isc_task_send(rbtdb->task, &ev); ++ } + } + + /*% +@@ -2220,17 +2242,26 @@ restore_locks: + } + + /* +- * Prune the tree by recursively cleaning-up single leaves. In the worst +- * case, the number of iteration is the number of tree levels, which is at +- * most the maximum number of domain name labels, i.e, 127. In practice, this +- * should be much smaller (only a few times), and even the worst case would be +- * acceptable for a single event. ++ * Prune the tree by recursively cleaning up single leaves. Go through all ++ * nodes stored in the rbtdb->prunenodes list; for each of them, in the worst ++ * case, it will be necessary to traverse a number of tree levels equal to the ++ * maximum legal number of domain name labels (127); in practice, the number of ++ * tree levels to traverse will virtually always be much smaller (a few levels ++ * at most). While holding the tree lock throughout this entire operation is ++ * less than ideal, so is splitting the latter up by queueing a separate ++ * prune_tree() run for each node to start pruning from (as queueing requires ++ * allocating memory and can therefore potentially be exploited to exhaust ++ * available memory). Also note that actually freeing up the memory used by ++ * RBTDB nodes (which is what this function does) is essential to keeping cache ++ * memory use in check, so since the tree lock needs to be acquired anyway, ++ * freeing as many nodes as possible before the tree lock gets released is ++ * prudent. + */ + static void + prune_tree(isc_task_t *task, isc_event_t *event) { +- dns_rbtdb_t *rbtdb = event->ev_sender; +- dns_rbtnode_t *node = event->ev_arg; +- dns_rbtnode_t *parent; ++ dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)event->ev_arg; ++ dns_rbtnode_t *node = NULL; ++ dns_rbtnode_t *parent = NULL; + unsigned int locknum; + + UNUSED(task); +@@ -2238,44 +2269,60 @@ prune_tree(isc_task_t *task, isc_event_t *event) { + isc_event_free(&event); + + RWLOCK(&rbtdb->tree_lock, isc_rwlocktype_write); +- locknum = node->locknum; +- NODE_LOCK(&rbtdb->node_locks[locknum].lock, isc_rwlocktype_write); +- do { +- parent = node->parent; +- decrement_reference(rbtdb, node, 0, isc_rwlocktype_write, +- isc_rwlocktype_write, true); + +- if (parent != NULL && parent->down == NULL) { +- /* +- * node was the only down child of the parent and has +- * just been removed. We'll then need to examine the +- * parent. Keep the lock if possible; otherwise, +- * release the old lock and acquire one for the parent. +- */ +- if (parent->locknum != locknum) { +- NODE_UNLOCK(&rbtdb->node_locks[locknum].lock, +- isc_rwlocktype_write); +- locknum = parent->locknum; +- NODE_LOCK(&rbtdb->node_locks[locknum].lock, +- isc_rwlocktype_write); ++ while ((node = ISC_LIST_HEAD(rbtdb->prunenodes)) != NULL) { ++ locknum = node->locknum; ++ NODE_LOCK(&rbtdb->node_locks[locknum].lock, ++ isc_rwlocktype_write); ++ do { ++ if (ISC_LINK_LINKED(node, prunelink)) { ++ ISC_LIST_UNLINK(rbtdb->prunenodes, node, ++ prunelink); + } + +- /* +- * We need to gain a reference to the node before +- * decrementing it in the next iteration. +- */ +- if (ISC_LINK_LINKED(parent, deadlink)) { +- ISC_LIST_UNLINK(rbtdb->deadnodes[locknum], ++ parent = node->parent; ++ decrement_reference(rbtdb, node, 0, ++ isc_rwlocktype_write, ++ isc_rwlocktype_write, true); ++ ++ if (parent != NULL && parent->down == NULL) { ++ /* ++ * node was the only down child of the parent ++ * and has just been removed. We'll then need ++ * to examine the parent. Keep the lock if ++ * possible; otherwise, release the old lock and ++ * acquire one for the parent. ++ */ ++ if (parent->locknum != locknum) { ++ NODE_UNLOCK( ++ &rbtdb->node_locks[locknum].lock, ++ isc_rwlocktype_write); ++ locknum = parent->locknum; ++ NODE_LOCK( ++ &rbtdb->node_locks[locknum].lock, ++ isc_rwlocktype_write); ++ } ++ ++ /* ++ * We need to gain a reference to the node ++ * before decrementing it in the next iteration. ++ */ ++ if (ISC_LINK_LINKED(parent, deadlink)) { ++ ISC_LIST_UNLINK( ++ rbtdb->deadnodes[locknum], + parent, deadlink); ++ } ++ new_reference(rbtdb, parent, ++ isc_rwlocktype_write); ++ } else { ++ parent = NULL; + } +- new_reference(rbtdb, parent, isc_rwlocktype_write); +- } else { +- parent = NULL; +- } + +- node = parent; +- } while (node != NULL); +- NODE_UNLOCK(&rbtdb->node_locks[locknum].lock, isc_rwlocktype_write); ++ node = parent; ++ } while (node != NULL); ++ NODE_UNLOCK(&rbtdb->node_locks[locknum].lock, ++ isc_rwlocktype_write); ++ } + RWUNLOCK(&rbtdb->tree_lock, isc_rwlocktype_write); + + detach((dns_db_t **)&rbtdb); +@@ -8726,6 +8773,8 @@ dns_rbtdb_create(isc_mem_t *mctx, const dns_name_t *origin, dns_dbtype_t type, + ISC_LIST_INIT(rbtdb->deadnodes[i]); + } + ++ ISC_LIST_INIT(rbtdb->prunenodes); ++ + rbtdb->active = rbtdb->node_lock_count; + + for (i = 0; i < (int)(rbtdb->node_lock_count); i++) { +-- +2.43.0 + diff --git a/SOURCES/bind-9.16-isc-mempool-attach.patch b/SOURCES/bind-9.16-isc-mempool-attach.patch new file mode 100644 index 0000000..f3cfcce --- /dev/null +++ b/SOURCES/bind-9.16-isc-mempool-attach.patch @@ -0,0 +1,40 @@ +From d249889a9c18df7792ca3cd8d97897e4fb5824b5 Mon Sep 17 00:00:00 2001 +From: Aram Sargsyan +Date: Wed, 31 Aug 2022 12:30:38 +0000 +Subject: [PATCH] Add mctx attach/detach when creating/destroying a memory pool + +This should make sure that the memory context is not destroyed +before the memory pool, which is using the context. + +(cherry picked from commit e97c3eea954e055634b72c21325d2611e960ee94) +--- + lib/isc/mem.c | 6 ++++-- + 1 file changed, 4 insertions(+), 2 deletions(-) + +diff --git a/lib/isc/mem.c b/lib/isc/mem.c +index f84d300..33ece7a 100644 +--- a/lib/isc/mem.c ++++ b/lib/isc/mem.c +@@ -1656,7 +1656,8 @@ isc_mempool_create(isc_mem_t *mctx0, size_t size, isc_mempool_t **mpctxp) { + mpctx->common.impmagic = MEMPOOL_MAGIC; + mpctx->common.magic = ISCAPI_MPOOL_MAGIC; + mpctx->lock = NULL; +- mpctx->mctx = mctx; ++ mpctx->mctx = NULL; ++ isc_mem_attach((isc_mem_t *)mctx, (isc_mem_t **)&mpctx->mctx); + /* + * Mempools are stored as a linked list of element. + */ +@@ -1765,7 +1766,8 @@ isc_mempool_destroy(isc_mempool_t **mpctxp) { + mpctx->common.impmagic = 0; + mpctx->common.magic = 0; + +- isc_mem_put((isc_mem_t *)mpctx->mctx, mpctx, sizeof(isc__mempool_t)); ++ isc_mem_putanddetach((isc_mem_t **)&mpctx->mctx, mpctx, ++ sizeof(isc__mempool_t)); + + if (lock != NULL) { + UNLOCK(lock); +-- +2.43.2 + diff --git a/SOURCES/bind-9.16-isc_hp-CVE-2023-50387.patch b/SOURCES/bind-9.16-isc_hp-CVE-2023-50387.patch new file mode 100644 index 0000000..806e29e --- /dev/null +++ b/SOURCES/bind-9.16-isc_hp-CVE-2023-50387.patch @@ -0,0 +1,66 @@ +From 103b09187466b2afbff7e204d166d21e2fbb057c Mon Sep 17 00:00:00 2001 +From: Petr Mensik +Date: Wed, 21 Feb 2024 11:54:27 +0100 +Subject: [PATCH] Downstream specific changes related to KeyTrap + +Fix for CVE-2023-50387 introduced new additional thread. But because +isc_hp functions were removed from later bind 9.16 release, their +changes did not contain increase of hazard pointers max thread limit. +To prevent obscure memory corruption increase thread max size. + +In addition place at least few INSISTs to check this is catched before +random memory overwrites begins. It would be quite difficult to track +without any check. +--- + lib/isc/hp.c | 3 +++ + lib/isc/managers.c | 5 +++-- + 2 files changed, 6 insertions(+), 2 deletions(-) + +diff --git a/lib/isc/hp.c b/lib/isc/hp.c +index 92d160b..5f9bbf7 100644 +--- a/lib/isc/hp.c ++++ b/lib/isc/hp.c +@@ -138,6 +138,7 @@ isc_hp_destroy(isc_hp_t *hp) { + + void + isc_hp_clear(isc_hp_t *hp) { ++ INSIST(tid() < isc__hp_max_threads); + for (int i = 0; i < hp->max_hps; i++) { + atomic_store_release(&hp->hp[tid()][i], 0); + } +@@ -152,6 +153,7 @@ uintptr_t + isc_hp_protect(isc_hp_t *hp, int ihp, atomic_uintptr_t *atom) { + uintptr_t n = 0; + uintptr_t ret; ++ INSIST(tid() < isc__hp_max_threads); + while ((ret = atomic_load(atom)) != n) { + atomic_store(&hp->hp[tid()][ihp], ret); + n = ret; +@@ -173,6 +175,7 @@ isc_hp_protect_release(isc_hp_t *hp, int ihp, atomic_uintptr_t ptr) { + + void + isc_hp_retire(isc_hp_t *hp, uintptr_t ptr) { ++ INSIST(tid() < isc__hp_max_threads); + hp->rl[tid()]->list[hp->rl[tid()]->size++] = ptr; + INSIST(hp->rl[tid()]->size < isc__hp_max_retired); + +diff --git a/lib/isc/managers.c b/lib/isc/managers.c +index c39a650..3bdca99 100644 +--- a/lib/isc/managers.c ++++ b/lib/isc/managers.c +@@ -25,9 +25,10 @@ isc_managers_create(isc_mem_t *mctx, size_t workers, size_t quantum, + + /* + * We have ncpus network threads, ncpus old network threads - make +- * it 4x just to be on the safe side. ++ * it 4x just to be on the safe side. One additional for slow netmgr ++ * thread. + */ +- isc_hp_init(4 * workers); ++ isc_hp_init(5 * workers); + + REQUIRE(netmgrp != NULL && *netmgrp == NULL); + isc__netmgr_create(mctx, workers, &netmgr); +-- +2.43.2 + diff --git a/SOURCES/bind-9.16-system-test-cds.patch b/SOURCES/bind-9.16-system-test-cds.patch new file mode 100644 index 0000000..cf8fcbb --- /dev/null +++ b/SOURCES/bind-9.16-system-test-cds.patch @@ -0,0 +1,33 @@ +From 7cc9fd1870e5264abd885ed2c419034945121d0f Mon Sep 17 00:00:00 2001 +From: Petr Mensik +Date: Mon, 19 Feb 2024 22:13:52 +0100 +Subject: [PATCH] Define variants to empty values + +DNSSEC_VARIANT and NAMED_VARIANT are special Red Hat modifications to +allow testing or alternative rebuilds, with support for pkcs11 or sdb. +But undefined value breaks some tests, so define them to empty values. +That means normal build variant. + +Required to pass upstream test suite cds test correctly. +--- + bin/tests/system/conf.sh.in | 4 ++++ + 1 file changed, 4 insertions(+) + +diff --git a/bin/tests/system/conf.sh.in b/bin/tests/system/conf.sh.in +index 7b2b309..c2d6526 100644 +--- a/bin/tests/system/conf.sh.in ++++ b/bin/tests/system/conf.sh.in +@@ -24,6 +24,10 @@ TMPDIR=${TMPDIR:-/tmp} + # This is not the windows build. + CYGWIN="" + ++# RH specific, allow variants testing ++: ${DNSSEC_VARIANT:=} ++: ${NAMED_VARIANT:=} ++ + # Load common values shared between windows and unix/linux. + . $TOP/bin/tests/system/conf.sh.common + +-- +2.43.2 + diff --git a/SPECS/bind9.16.spec b/SPECS/bind9.16.spec index 203e39b..809ad58 100644 --- a/SPECS/bind9.16.spec +++ b/SPECS/bind9.16.spec @@ -50,14 +50,14 @@ %global upname bind %define upname_compat() \ %if "%{name}" != "%{upname}" \ -Conflicts: %1 \ +Conflicts: %* \ %endif Summary: The Berkeley Internet Name Domain (BIND) DNS (Domain Name System) server Name: bind9.16 License: MPLv2.0 Version: 9.16.23 -Release: 0.14%{?dist} +Release: 0.19%{?dist} Epoch: 32 Url: https://www.isc.org/downloads/bind/ # @@ -126,6 +126,26 @@ Patch185: bind-9.16-CVE-2022-3094-test.patch Patch186: bind-9.16-CVE-2022-3736.patch # https://gitlab.isc.org/isc-projects/bind9/commit/b4a65aaea19762a3712932aa2270e8a833fbde22 Patch187: bind-9.16-CVE-2022-3924.patch +# https://gitlab.isc.org/isc-projects/bind9/commit/f1d9e9ee3859976f403914d20ad2a10855343702 +Patch188: bind-9.16-CVE-2023-2828.patch +Patch189: bind-9.16-CVE-2023-3341.patch +Patch194: bind-9.16-CVE-2023-4408.patch +Patch195: bind-9.16-CVE-2023-5517.patch +Patch196: bind-9.16-CVE-2023-5679.patch +Patch197: bind-9.16-CVE-2023-6516.patch +Patch198: bind-9.16-CVE-2023-50387.patch +# https://gitlab.isc.org/isc-projects/bind9/commit/f493a8394102b0aeb101d5dc2f963004c8741175 +Patch199: bind-9.16-CVE-2023-4408-test1.patch +# https://gitlab.isc.org/isc-projects/bind9/commit/b9c10a194da3358204f5ba7d91e55332db435614 +Patch200: bind-9.16-CVE-2023-4408-test2.patch +# Downstream only change, fixes patch 171 +Patch201: bind-9.16-system-test-cds.patch +# https://gitlab.isc.org/isc-projects/bind9/commit/32779aba8a0a5f852c611f44ecbeab5aab633e34 +Patch202: bind-9.16-isc-mempool-attach.patch +# Downstream only change, complements patch 198 +Patch203: bind-9.16-isc_hp-CVE-2023-50387.patch +# https://gitlab.isc.org/isc-projects/bind9/commit/1237d73cd1120b146ee699bbae7b2fe837cf2f98 +Patch204: bind-9.16-CVE-2023-6516-test.patch %{?systemd_ordering} Requires: coreutils @@ -167,6 +187,7 @@ BuildRequires: softhsm %if %{with SYSTEMTEST} # bin/tests/system dependencies BuildRequires: perl(Net::DNS) perl(Net::DNS::Nameserver) perl(Time::HiRes) perl(Getopt::Long) +BuildRequires: python-dns # manual configuration requires this tool BuildRequires: iproute %endif @@ -287,8 +308,8 @@ Requires: %{name}-libs%{?_isa} = %{epoch}:%{version}-%{release} Recommends: %{name}-utils Requires: python3-%{name} = %{epoch}:%{version}-%{release} Provides: %{name}-dnssec-doc = %{epoch}:%{version}-%{release} -%upname_compat %{upname}-dnssec-utils -%upname_compat %{upname}-pkcs11-utils +%upname_compat %{upname}-dnssec-utils %{upname}-pkcs11-utils +%upname_compat %{upname}-utils < 9.16 %description dnssec-utils Bind-dnssec-utils contains a collection of utilities for editing @@ -320,8 +341,7 @@ Requires: fstrm-devel%{?_isa} protobuf-c-devel%{?_isa} %if %{with GEOIP2} Requires: libmaxminddb-devel%{?_isa} %endif -%upname_compat %{upname}-devel -%upname_compat %{upname}-lite-devel +%upname_compat %{upname}-devel %{upname}-lite-devel %description devel The %{name}-devel package contains full version of the header files and libraries @@ -334,6 +354,7 @@ Prefix: %{chroot_prefix} # grep is required due to setup-named-chroot.sh script Requires: grep Requires: %{name}%{?_isa} = %{epoch}:%{version}-%{release} +%upname_compat %{upname}-chroot %{upname}-sdb-chroot %description chroot This package contains a tree of files which can be used as a @@ -437,6 +458,19 @@ in HTML and PDF format. %patch185 -p1 -b .CVE-2022-3094-test %patch186 -p1 -b .CVE-2022-3736 %patch187 -p1 -b .CVE-2022-3924 +%patch188 -p1 -b .CVE-2023-2828 +%patch189 -p1 -b .CVE-2023-3341 +%patch194 -p1 -b .CVE-2023-4408 +%patch195 -p1 -b .CVE-2023-5517 +%patch196 -p1 -b .CVE-2023-5679 +%patch197 -p1 -b .CVE-2023-6516 +%patch198 -p1 -b .CVE-2023-50387 +%patch199 -p1 +%patch200 -p1 +%patch201 -p1 -b .test-variant-def +%patch202 -p1 -b .mempool-attach +%patch203 -p1 -b .isc_hp-CVE-2023-50387 +%patch204 -p1 -b .CVE-2023-6516-test %if %{with PKCS11} %patch135 -p1 -b .config-pkcs11 @@ -537,6 +571,11 @@ export LIBDIR_SUFFIX --enable-fixed-rrset \ --enable-full-report \ ; + +%if 0%{?bind_skip_parsetab} + sed -e 's/^TARGETS =/& #/' -i bin/python/isc/Makefile +%endif + %if %{with DNSTAP} pushd lib SRCLIB="../../../lib" @@ -1156,6 +1195,31 @@ fi; %endif %changelog +* Wed May 15 2024 Petr Menšík - 32:9.16.23-0.19 +- Add few more explicit conflicts with bind subpackages (RHEL-2208) + +* Mon Mar 25 2024 Petr Menšík - 32:9.16.23-0.18 +- Prevent crashing at masterformat system test (CVE-2023-6516) + +* Mon Feb 12 2024 Petr Menšík - 32:9.16.23-0.17 +- Prevent increased CPU load on large DNS messages (CVE-2023-4408) +- Prevent assertion failure when nxdomain-redirect is used with + RFC 1918 reverse zones (CVE-2023-5517) +- Prevent assertion failure if DNS64 and serve-stale is used (CVE-2023-5679) +- Specific recursive query patterns may lead to an out-of-memory + condition (CVE-2023-6516) +- Prevent increased CPU consumption in DNSSEC validator (CVE-2023-50387 + CVE-2023-50868) +- Import tests for large DNS messages fix +- Add downstream change complementing CVE-2023-50387 + +* Wed Sep 20 2023 Petr Menšík - 32:9.16.23-0.16 +- Limit the amount of recursion possible in control channel (CVE-2023-3341) + +* Tue Jun 20 2023 Petr Menšík - 32:9.16.23-0.15 +- Strengten cache cleaning to prevent overflowing configured limit + (CVE-2023-2828) + * Sat Feb 25 2023 Petr Menšík - 32:9.16.23-0.14 - Handle subtle difference between upstream and rhel (CVE-2022-3094)