2
0
mirror of https://gitlab.isc.org/isc-projects/bind9 synced 2025-08-30 22:15:20 +00:00

Merge branch '4234-confidential-use-hashmap-when-parsing' into 'v9.19.20-release'

[CVE-2023-4408] Use hashmap when parsing DNS messages

See merge request isc-private/bind9!560
This commit is contained in:
Michał Kępień
2024-01-05 10:43:31 +00:00
5 changed files with 251 additions and 145 deletions

View File

@@ -6,7 +6,8 @@
6316. [placeholder]
6315. [placeholder]
6315. [security] Speed up parsing of DNS messages with many different
names. (CVE-2023-4408) [GL #4234]
6314. [bug] Address race conditions in dns_tsigkey_find().
[GL #4182]

View File

@@ -15,7 +15,13 @@ Notes for BIND 9.19.20
Security Fixes
~~~~~~~~~~~~~~
- None.
- Parsing DNS messages with many different names could cause excessive
CPU load. This has been fixed. :cve:`2023-4408`
ISC would like to thank Shoham Danino from Reichman University, Anat
Bremler-Barr from Tel-Aviv University, Yehuda Afek from Tel-Aviv
University, and Yuval Shavitt from Tel-Aviv University for bringing
this vulnerability to our attention. :gl:`#4234`
New Features
~~~~~~~~~~~~

View File

@@ -859,27 +859,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_addname(dns_message_t *msg, dns_name_t *name,
dns_section_t section);

View File

@@ -68,6 +68,7 @@
#include <stdio.h>
#include <isc/buffer.h>
#include <isc/hashmap.h>
#include <isc/lang.h>
#include <isc/magic.h>
#include <isc/region.h> /* Required for storage size of dns_label_t. */
@@ -119,6 +120,7 @@ struct dns_name {
isc_buffer_t *buffer;
ISC_LINK(dns_name_t) link;
ISC_LIST(dns_rdataset_t) list;
isc_hashmap_t *hashmap;
};
#define DNS_NAME_MAGIC ISC_MAGIC('D', 'N', 'S', 'n')

View File

@@ -22,6 +22,8 @@
#include <stdbool.h>
#include <isc/buffer.h>
#include <isc/hash.h>
#include <isc/hashmap.h>
#include <isc/mem.h>
#include <isc/result.h>
#include <isc/string.h>
@@ -779,6 +781,11 @@ ISC_REFCOUNT_TRACE_IMPL(dns_message, dns__message_destroy);
ISC_REFCOUNT_IMPL(dns_message, dns__message_destroy);
#endif
static bool
name_match(void *node, const void *key) {
return (dns_name_equal(node, key));
}
static isc_result_t
findname(dns_name_t **foundname, const dns_name_t *target,
dns_namelist_t *section) {
@@ -796,26 +803,25 @@ 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 **rdatasetp) {
dns_rdataset_t *rds = NULL;
static uint32_t
rds_hash(dns_rdataset_t *rds) {
isc_hash32_t state;
REQUIRE(name != NULL);
REQUIRE(rdatasetp == NULL || *rdatasetp == NULL);
isc_hash32_init(&state);
isc_hash32_hash(&state, &rds->rdclass, sizeof(rds->rdclass), true);
isc_hash32_hash(&state, &rds->type, sizeof(rds->type), true);
isc_hash32_hash(&state, &rds->covers, sizeof(rds->covers), true);
ISC_LIST_FOREACH_REV (name->list, rds, link) {
if (rds->rdclass == rdclass && rds->type == type &&
rds->covers == covers)
{
SET_IF_NOT_NULL(rdatasetp, rds);
return (isc_hash32_finalize(&state));
}
return (ISC_R_SUCCESS);
}
}
static bool
rds_match(void *node, const void *key0) {
const dns_rdataset_t *rds = node;
const dns_rdataset_t *key = key0;
return (ISC_R_NOTFOUND);
return (rds->rdclass == key->rdclass && rds->type == key->type &&
rds->covers == key->covers);
}
isc_result_t
@@ -939,22 +945,38 @@ 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;
ISC_LIST_FOREACH (*section, name, link) {
if (name->hashmap != NULL) {
isc_hashmap_destroy(&name->hashmap);
}
}
}
static isc_result_t
getquestions(isc_buffer_t *source, dns_message_t *msg, dns_decompress_t dctx,
unsigned int options) {
isc_region_t r;
unsigned int count;
dns_name_t *name = NULL;
dns_name_t *name2 = NULL;
dns_name_t *found_name = 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_hashmaps = false;
isc_hashmap_t *name_map = NULL;
if (msg->counts[DNS_SECTION_QUESTION] > 1) {
isc_hashmap_create(msg->mctx, 1, &name_map);
}
for (count = 0; count < msg->counts[DNS_SECTION_QUESTION]; count++) {
name = NULL;
@@ -972,13 +994,21 @@ 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 = isc_hashmap_add(name_map, dns_name_hash(name),
name_match, name, name,
(void **)&found_name);
/*
* If it is the first name in the section, accept it.
@@ -990,19 +1020,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;
name = found_name;
found_name = NULL;
break;
default:
UNREACHABLE();
}
free_name = false;
/*
* Get type and class.
*/
@@ -1032,53 +1068,83 @@ 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.
*/
rdatalist = newrdatalist(msg);
if (rdatalist == NULL) {
result = ISC_R_NOMEMORY;
goto cleanup;
}
rdataset = isc_mempool_get(msg->rdspool);
rdatalist->type = rdtype;
rdatalist->rdclass = rdclass;
rdatalist->covers = 0;
/*
* Convert rdatalist to rdataset, and attach the latter to
* the name.
*/
rdatalist->type = rdtype;
rdatalist->rdclass = rdclass;
dns_rdataset_init(rdataset);
dns_message_gettemprdataset(msg, &rdataset);
dns_rdatalist_tordataset(rdatalist, rdataset);
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->hashmap == NULL) {
isc_hashmap_create(msg->mctx, 1, &name->hashmap);
free_hashmaps = true;
INSIST(ISC_LIST_HEAD(name->list) ==
ISC_LIST_TAIL(name->list));
dns_rdataset_t *old_rdataset =
ISC_LIST_HEAD(name->list);
result = isc_hashmap_add(
name->hashmap, rds_hash(old_rdataset),
rds_match, old_rdataset, old_rdataset, NULL);
INSIST(result == ISC_R_SUCCESS);
}
result = isc_hashmap_add(name->hashmap, rds_hash(rdataset),
rds_match, rdataset, 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) {
dns_message_puttemprdataset(msg, &rdataset);
}
if (free_name) {
dns_message_puttempname(msg, &name);
}
if (free_hashmaps) {
cleanup_name_hashmaps(section);
}
if (name_map != NULL) {
isc_hashmap_destroy(&name_map);
}
return (result);
}
@@ -1105,7 +1171,6 @@ auth_signed(dns_namelist_t *section) {
ISC_LIST_FOREACH (*section, name, link) {
int auth_dnssec = 0, auth_rrsig = 0;
dns_rdataset_t *rds = NULL;
ISC_LIST_FOREACH (name->list, rds, link) {
switch (rds->type) {
case dns_rdatatype_ds:
@@ -1152,19 +1217,26 @@ getsection(isc_buffer_t *source, dns_message_t *msg, dns_decompress_t dctx,
isc_region_t r;
unsigned int count, rdatalen;
dns_name_t *name = NULL;
dns_name_t *name2 = NULL;
dns_name_t *found_name = 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_hashmaps = false;
bool preserve_order = ((options & DNS_MESSAGEPARSE_PRESERVEORDER) != 0);
bool best_effort = ((options & DNS_MESSAGEPARSE_BESTEFFORT) != 0);
bool isedns, issigzero, istsig;
isc_hashmap_t *name_map = NULL;
if (msg->counts[sectionid] > 1) {
isc_hashmap_create(msg->mctx, 1, &name_map);
}
for (count = 0; count < msg->counts[sectionid]; count++) {
int recstart = source->current;
@@ -1172,7 +1244,6 @@ 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;
@@ -1212,8 +1283,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;
}
@@ -1412,61 +1483,128 @@ 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 = isc_hashmap_add(name_map, dns_name_hash(name),
name_match, name, name,
(void **)&found_name);
/*
* If it is a new name, append to the section.
*/
if (result == ISC_R_SUCCESS) {
dns_message_puttempname(msg, &name);
name = name2;
} else {
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 = found_name;
found_name = NULL;
break;
default:
UNREACHABLE();
}
free_name = false;
}
rdatalist = newrdatalist(msg);
rdatalist->type = rdtype;
rdatalist->covers = covers;
rdatalist->rdclass = rdclass;
rdatalist->ttl = ttl;
dns_message_gettemprdataset(msg, &rdataset);
dns_rdatalist_tordataset(rdatalist, rdataset);
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 ||
skip_type_search)
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
* the question section, fail.
*/
if (dns_rdatatype_questiononly(rdtype)) {
dns_message_puttemprdataset(msg, &rdataset);
DO_ERROR(DNS_R_FORMERR);
}
rdataset = NULL;
result = dns_message_find(name, rdclass, rdtype, covers,
&rdataset);
}
if (ISC_LIST_EMPTY(name->list)) {
result = ISC_R_SUCCESS;
goto skip_rds_check;
}
if (name->hashmap == NULL) {
isc_hashmap_create(msg->mctx, 1,
&name->hashmap);
free_hashmaps = true;
INSIST(ISC_LIST_HEAD(name->list) ==
ISC_LIST_TAIL(name->list));
dns_rdataset_t *old_rdataset =
ISC_LIST_HEAD(name->list);
result = isc_hashmap_add(
name->hashmap, rds_hash(old_rdataset),
rds_match, old_rdataset, old_rdataset,
NULL);
INSIST(result == ISC_R_SUCCESS);
}
result = isc_hashmap_add(
name->hashmap, rds_hash(rdataset), rds_match,
rdataset, rdataset, (void **)&found_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.
*/
skip_rds_check:
switch (result) {
case ISC_R_EXISTS:
/* Free the rdataset we used as the key */
dns__message_putassociatedrdataset(msg,
&rdataset);
rdataset = found_rdataset;
result = ISC_R_SUCCESS;
if (!dns_rdatatype_issingleton(rdtype)) {
break;
}
/*
* 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);
@@ -1475,31 +1613,12 @@ getsection(isc_buffer_t *source, dns_message_t *msg, dns_decompress_t dctx,
if (dns_rdata_compare(rdata, first) != 0) {
DO_ERROR(DNS_R_FORMERR);
}
}
}
if (result == ISC_R_NOTFOUND) {
rdataset = isc_mempool_get(msg->rdspool);
free_rdataset = true;
rdatalist = newrdatalist(msg);
if (rdatalist == NULL) {
result = ISC_R_NOMEMORY;
goto cleanup;
}
rdatalist->type = rdtype;
rdatalist->covers = covers;
rdatalist->rdclass = rdclass;
rdatalist->ttl = ttl;
dns_rdataset_init(rdataset);
dns_rdatalist_tordataset(rdatalist, rdataset);
dns_rdataset_setownercase(rdataset, name);
if (!isedns && !istsig && !issigzero) {
break;
case ISC_R_SUCCESS:
ISC_LIST_APPEND(name->list, rdataset, link);
free_rdataset = false;
break;
default:
UNREACHABLE();
}
}
@@ -1535,7 +1654,6 @@ getsection(isc_buffer_t *source, dns_message_t *msg, dns_decompress_t dctx,
msg->opt = rdataset;
rdataset = NULL;
free_rdataset = false;
ercode = (dns_rcode_t)((msg->opt->ttl &
DNS_MESSAGE_EDNSRCODE_MASK) >>
20);
@@ -1547,7 +1665,6 @@ getsection(isc_buffer_t *source, dns_message_t *msg, dns_decompress_t dctx,
msg->sig0name = name;
msg->sigstart = recstart;
rdataset = NULL;
free_rdataset = false;
free_name = false;
} else if (istsig) {
msg->tsig = rdataset;
@@ -1558,7 +1675,6 @@ getsection(isc_buffer_t *source, dns_message_t *msg, dns_decompress_t dctx,
*/
msg->tsigname->attributes.nocompress = true;
rdataset = NULL;
free_rdataset = false;
free_name = false;
}
@@ -1566,14 +1682,11 @@ getsection(isc_buffer_t *source, dns_message_t *msg, dns_decompress_t dctx,
if (free_name) {
dns_message_puttempname(msg, &name);
}
if (free_rdataset) {
dns__message_putassociatedrdataset(msg,
&rdataset);
}
free_name = free_rdataset = false;
free_name = false;
}
INSIST(!free_name);
INSIST(!free_rdataset);
rdataset = NULL;
}
/*
@@ -1591,16 +1704,20 @@ 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 (free_name) {
dns_message_puttempname(msg, &name);
}
if (free_rdataset) {
dns__message_putassociatedrdataset(msg, &rdataset);
if (free_hashmaps) {
cleanup_name_hashmaps(section);
}
if (name_map != NULL) {
isc_hashmap_destroy(&name_map);
}
return (result);
@@ -1661,6 +1778,7 @@ dns_message_parse(dns_message_t *msg, isc_buffer_t *source,
dctx = DNS_DECOMPRESS_ALWAYS;
ret = getquestions(source, msg, dctx, options);
if (ret == ISC_R_UNEXPECTEDEND && ignore_tc) {
goto truncated;
}
@@ -2349,15 +2467,13 @@ dns_message_renderreset(dns_message_t *msg) {
dns_message_puttempname(msg, &msg->tsigname);
}
if (msg->tsig != NULL) {
dns_rdataset_disassociate(msg->tsig);
dns_message_puttemprdataset(msg, &msg->tsig);
dns__message_putassociatedrdataset(msg, &msg->tsig);
}
if (msg->sig0name != NULL) {
dns_message_puttempname(msg, &msg->sig0name);
}
if (msg->sig0 != NULL) {
dns_rdataset_disassociate(msg->sig0);
dns_message_puttemprdataset(msg, &msg->sig0);
dns__message_putassociatedrdataset(msg, &msg->sig0);
}
}
@@ -2406,7 +2522,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;
/*
@@ -2524,6 +2640,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->hashmap != NULL) {
isc_hashmap_destroy(&item->hashmap);
}
/*
* we need to check this in case dns_name_dup() was used.
*/
@@ -2551,8 +2671,7 @@ dns_message_puttemprdata(dns_message_t *msg, dns_rdata_t **item) {
static void
dns__message_putassociatedrdataset(dns_message_t *msg, dns_rdataset_t **item) {
dns_rdataset_disassociate(*item);
isc_mempool_put(msg->rdspool, *item);
*item = NULL;
dns_message_puttemprdataset(msg, item);
}
void
@@ -2730,8 +2849,7 @@ dns_message_setopt(dns_message_t *msg, dns_rdataset_t *opt) {
return (ISC_R_SUCCESS);
cleanup:
dns_rdataset_disassociate(opt);
dns_message_puttemprdataset(msg, &opt);
dns__message_putassociatedrdataset(msg, &opt);
return (result);
}