From 329c53c51c2a3b0b0a1ee2c7f4bec7612e5ab27b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Men=C5=A1=C3=ADk?= Date: Mon, 12 Feb 2024 21:19:31 +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: RHEL-25403 RHEL-25392 ; Resolves: CVE-2023-50868 CVE-2023-50387 --- bind-9.16-CVE-2023-50387.patch | 478 +++++++++++++++++++++++++++++++++ bind9.16.spec | 4 + 2 files changed, 482 insertions(+) create mode 100644 bind-9.16-CVE-2023-50387.patch diff --git a/bind-9.16-CVE-2023-50387.patch b/bind-9.16-CVE-2023-50387.patch new file mode 100644 index 0000000..3f4b9ca --- /dev/null +++ b/bind-9.16-CVE-2023-50387.patch @@ -0,0 +1,478 @@ +From c6e05ffc5fb784514ab54938867abaab41126c65 Mon Sep 17 00:00:00 2001 +From: Petr Mensik +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/bind9.16.spec b/bind9.16.spec index 5a8ce94..f81c0d9 100644 --- a/bind9.16.spec +++ b/bind9.16.spec @@ -133,6 +133,7 @@ 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 %{?systemd_ordering} Requires: coreutils @@ -450,6 +451,7 @@ in HTML and PDF format. %patch195 -p1 -b .CVE-2023-5517 %patch196 -p1 -b .CVE-2023-5679 %patch197 -p1 -b .CVE-2023-6516 +%patch198 -p1 -b .CVE-2023-50387 %if %{with PKCS11} %patch135 -p1 -b .config-pkcs11 @@ -1176,6 +1178,8 @@ fi; - 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) * 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)