From deeca182e392cecbbb59f439cf3bea77a550e644 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Men=C5=A1=C3=ADk?= Date: Mon, 12 Feb 2024 20:08:53 +0100 Subject: [PATCH] Prevent increased CPU load on large DNS messages 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. Resolves: RHEL-25342 ; Resolves: CVE-2023-4408 --- bind-9.16-CVE-2023-4408.patch | 1735 +++++++++++++++++++++++++++++++++ bind.spec | 7 +- 2 files changed, 1741 insertions(+), 1 deletion(-) create mode 100644 bind-9.16-CVE-2023-4408.patch diff --git a/bind-9.16-CVE-2023-4408.patch b/bind-9.16-CVE-2023-4408.patch new file mode 100644 index 0000000..f564235 --- /dev/null +++ b/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< - 32:9.16.23-16 +- Prevent increased CPU load on large DNS messages (CVE-2023-4408) + * Thu Dec 07 2023 Petr Menšík - 32:9.16.23-15 - Update addresses of b.root-servers.net (RHEL-18188)