diff --git a/meta-networking/recipes-support/unbound/unbound/CVE-2026-80225.patch b/meta-networking/recipes-support/unbound/unbound/CVE-2026-80225.patch new file mode 100644 index 0000000000..fb7dda7a15 --- /dev/null +++ b/meta-networking/recipes-support/unbound/unbound/CVE-2026-80225.patch @@ -0,0 +1,286 @@ +From 5d92dbc699c7a347be74b3752b1c3ee690e4dec1 Mon Sep 17 00:00:00 2001 +From: "W.C.A. Wijngaards" +Date: Wed, 16 Sep 2026 09:16:29 +0200 +Subject: [PATCH] - Fix CVE-2026-80225, Possible degradation of service from + continuous queries on the same TCP/DoT connection. Thanks to Qifan Zhang + from Palo Alto Networks for the report. + +(cherry picked from commit e619ead2dbcde8fc47f03b2ac68987b3377da4c7) + +CVE: CVE-2026-80225 +Upstream-Status: Backport [https://github.com/NLnetLabs/unbound/commit/e619ead2dbcde8fc47f03b2ac68987b3377da4c7] + +Signed-off-by: Ankur Tyagi +--- + services/listen_dnsport.c | 12 ++++++- + services/listen_dnsport.h | 8 ++++- + testcode/testbound.c | 10 ++++++ + util/fptr_wlist.c | 2 ++ + util/netevent.c | 66 ++++++++++++++++++++++++++++++++++++++- + util/netevent.h | 9 ++++++ + 6 files changed, 104 insertions(+), 3 deletions(-) + +diff --git a/services/listen_dnsport.c b/services/listen_dnsport.c +index d49d4ad4c..9b6fb557c 100644 +--- a/services/listen_dnsport.c ++++ b/services/listen_dnsport.c +@@ -2134,7 +2134,7 @@ void listen_start_accept(struct listen_dnsport* listen) + } + + struct tcp_req_info* +-tcp_req_info_create(struct sldns_buffer* spoolbuf) ++tcp_req_info_create(struct comm_base* base, struct sldns_buffer* spoolbuf) + { + struct tcp_req_info* req = (struct tcp_req_info*)malloc(sizeof(*req)); + if(!req) { +@@ -2142,6 +2142,12 @@ tcp_req_info_create(struct sldns_buffer* spoolbuf) + return NULL; + } + memset(req, 0, sizeof(*req)); ++ req->read_again_timer = comm_timer_create(base, tcp_read_again_cb, req); ++ if(!req->read_again_timer) { ++ log_err("malloc failure"); ++ free(req); ++ return NULL; ++ } + req->spool_buffer = spoolbuf; + return req; + } +@@ -2151,6 +2157,7 @@ tcp_req_info_delete(struct tcp_req_info* req) + { + if(!req) return; + tcp_req_info_clear(req); ++ comm_timer_delete(req->read_again_timer); + /* cp is pointer back to commpoint that owns this struct and + * called delete on us */ + /* spool_buffer is shared udp buffer, not deleted here */ +@@ -2189,6 +2196,9 @@ void tcp_req_info_clear(struct tcp_req_info* req) + req->done_req_list = NULL; + req->num_done_req = 0; + req->read_is_closed = 0; ++ ++ if(comm_timer_is_set(req->read_again_timer)) ++ comm_timer_disable(req->read_again_timer); + } + + void +diff --git a/services/listen_dnsport.h b/services/listen_dnsport.h +index 963595a1c..79f46705f 100644 +--- a/services/listen_dnsport.h ++++ b/services/listen_dnsport.h +@@ -345,6 +345,10 @@ struct tcp_req_info { + int num_done_req; + /** list of pending writable result packets, malloced one at a time */ + struct tcp_req_done_item* done_req_list; ++ /** the read again timer, when the number of pipelined TCP queries ++ * is large, it waits, zero time, for a new event loop to service ++ * the remainder of the TCP traffic on the fd. */ ++ struct comm_timer* read_again_timer; + }; + + /** +@@ -375,10 +379,12 @@ struct tcp_req_done_item { + * Create tcp request info structure that keeps track of open + * requests on the TCP channel that are resolved at the same time, + * and the pending results that have to get written back to that client. ++ * @param base: comm base for read again timer. + * @param spoolbuf: shared buffer + * @return new structure or NULL on alloc failure. + */ +-struct tcp_req_info* tcp_req_info_create(struct sldns_buffer* spoolbuf); ++struct tcp_req_info* tcp_req_info_create(struct comm_base* base, ++ struct sldns_buffer* spoolbuf); + + /** + * Delete tcp request structure. Called by owning commpoint. +diff --git a/testcode/testbound.c b/testcode/testbound.c +index 063037df4..3a3bcaef6 100644 +--- a/testcode/testbound.c ++++ b/testcode/testbound.c +@@ -786,3 +786,13 @@ size_t doq_table_quic_size_get(struct doq_table* ATTR_UNUSED(table)) + return 0; + } + #endif ++ ++void tcp_read_again_cb(void* ATTR_UNUSED(arg)) ++{ ++ /* nothing */ ++} ++ ++void tcp_more_read_again_cb(void* ATTR_UNUSED(arg)) ++{ ++ /* nothing */ ++} +diff --git a/util/fptr_wlist.c b/util/fptr_wlist.c +index 3c863d5e1..2cf3d320e 100644 +--- a/util/fptr_wlist.c ++++ b/util/fptr_wlist.c +@@ -141,6 +141,8 @@ fptr_whitelist_comm_timer(void (*fptr)(void*)) + #ifdef UB_ON_WINDOWS + else if(fptr == &wsvc_cron_cb) return 1; + #endif ++ else if(fptr == &tcp_read_again_cb) return 1; ++ else if(fptr == &tcp_more_read_again_cb) return 1; + else if(fptr == &auth_xfer_timer) return 1; + else if(fptr == &auth_xfer_probe_timer_callback) return 1; + else if(fptr == &auth_xfer_transfer_timer_callback) return 1; +diff --git a/util/netevent.c b/util/netevent.c +index aedcb5e07..0dde9059c 100644 +--- a/util/netevent.c ++++ b/util/netevent.c +@@ -122,6 +122,10 @@ + #define NUM_UDP_PER_SELECT 1 + #endif + ++/** The number of TCP queries over a TCP connection, per read indication ++ * from select. */ ++#define NUM_TCP_PER_SELECT 100 ++ + /** timeout in millisec to wait for write to unblock, packets dropped after.*/ + #define SEND_BLOCKED_WAIT_TIMEOUT 200 + /** max number of times to wait for write to unblock, packets dropped after.*/ +@@ -4546,6 +4550,10 @@ comm_point_tcp_handle_write(int fd, struct comm_point* c) + static int + tcp_req_info_read_again(int fd, struct comm_point* c) + { ++ /* One event-loop visit drains at most this many pipelined queries; ++ * the rest is re-queued, so that other file descriptors get ++ * serviced in between. */ ++ int budget = NUM_TCP_PER_SELECT; + while(c->tcp_req_info->read_again) { + int r; + c->tcp_req_info->read_again = 0; +@@ -4562,6 +4570,16 @@ tcp_req_info_read_again(int fd, struct comm_point* c) + } + return 0; + } ++ if(--budget <= 0 && c->tcp_req_info->read_again) { ++ /* Defer the rest of the drain to the next loop turn. ++ * This uses a zero delay timer. For TLS the undrained ++ * remainder sits in OpenSSL's user-space buffer. */ ++ struct timeval tv; ++ memset(&tv, 0, sizeof(tv)); ++ verbose(VERB_ALGO, "Defer tcp_req_info read again"); ++ comm_timer_set(c->tcp_req_info->read_again_timer, &tv); ++ return 1; ++ } + } + return 1; + } +@@ -4575,6 +4593,7 @@ tcp_more_read_again(int fd, struct comm_point* c) + /* this continues until the read routines get EAGAIN or so, + * and thus does not call the callback, and the bool is 0 */ + int* moreread = c->tcp_more_read_again; ++ int budget = NUM_TCP_PER_SELECT; + while(moreread && *moreread) { + *moreread = 0; + if(!comm_point_tcp_handle_read(fd, c, 0)) { +@@ -4587,6 +4606,30 @@ tcp_more_read_again(int fd, struct comm_point* c) + } + return; + } ++ if(--budget <= 0 && *moreread) { ++ /* Defer the rest of the drain to the next loop turn. ++ * This uses a zero delay timer. For TLS the undrained ++ * remainder sits in OpenSSL's user-space buffer. */ ++ struct timeval tv; ++ memset(&tv, 0, sizeof(tv)); ++ if(!c->tcp_more_read_again_timer) { ++ c->tcp_more_read_again_timer = comm_timer_create(c->ev->base, tcp_more_read_again_cb, c); ++ if(!c->tcp_more_read_again_timer) { ++ log_err("out of memory for tcp more read again timer"); ++ reclaim_tcp_handler(c); ++ if(!c->tcp_do_close) { ++ fptr_ok(fptr_whitelist_comm_point( ++ c->callback)); ++ (void)(*c->callback)(c, c->cb_arg, ++ NETEVENT_CLOSED, NULL); ++ } ++ return; ++ } ++ } ++ verbose(VERB_ALGO, "Defer more read again"); ++ comm_timer_set(c->tcp_more_read_again_timer, &tv); ++ return; ++ } + } + } + +@@ -4614,6 +4657,23 @@ tcp_more_write_again(int fd, struct comm_point* c) + } + } + ++void ++tcp_read_again_cb(void* arg) ++{ ++ struct tcp_req_info* req = (struct tcp_req_info*)arg; ++ verbose(VERB_ALGO, "tcp_read_again_cb"); ++ if(!tcp_req_info_read_again(req->cp->fd, req->cp)) ++ return; ++} ++ ++void ++tcp_more_read_again_cb(void* arg) ++{ ++ struct comm_point* c = (struct comm_point*)arg; ++ verbose(VERB_ALGO, "tcp_more_read_again_cb"); ++ tcp_more_read_again(c->fd, c); ++} ++ + void + comm_point_tcp_handle_callback(int fd, short event, void* arg) + { +@@ -6025,7 +6085,7 @@ comm_point_create_tcp_handler(struct comm_base *base, + c->pp2_enabled = parent->pp2_enabled; + c->pp2_header_state = pp2_header_none; + if(spoolbuf) { +- c->tcp_req_info = tcp_req_info_create(spoolbuf); ++ c->tcp_req_info = tcp_req_info_create(base, spoolbuf); + if(!c->tcp_req_info) { + log_err("could not create tcp commpoint"); + sldns_buffer_free(c->buffer); +@@ -6584,6 +6644,9 @@ comm_point_close(struct comm_point* c) + *c->tcp_more_read_again = 0; + if(c->tcp_more_write_again && *c->tcp_more_write_again) + *c->tcp_more_write_again = 0; ++ if(c->tcp_more_read_again_timer && ++ comm_timer_is_set(c->tcp_more_read_again_timer)) ++ comm_timer_disable(c->tcp_more_read_again_timer); + + /* close fd after removing from event lists, or epoll.. is messed up */ + if(c->fd != -1 && !c->do_not_close) { +@@ -6623,6 +6686,7 @@ comm_point_delete(struct comm_point* c) + free(c->tcp_handlers); + } + free(c->timeout); ++ comm_timer_delete(c->tcp_more_read_again_timer); + if(c->type == comm_tcp || c->type == comm_local || c->type == comm_http) { + sldns_buffer_free(c->buffer); + #ifdef USE_DNSCRYPT +diff --git a/util/netevent.h b/util/netevent.h +index c5114bbbe..dae2c375a 100644 +--- a/util/netevent.h ++++ b/util/netevent.h +@@ -380,6 +380,9 @@ struct comm_point { + * Or leave NULL if it is not used at all. */ + int* tcp_more_write_again; + ++ /** resume timer for tcp_more_read_again */ ++ struct comm_timer* tcp_more_read_again_timer; ++ + /** if set, read/write completes: + read/write state of tcp is toggled. + buffer reset/bytecount reset. +@@ -1127,6 +1130,12 @@ void doq_send_pkt(struct comm_point* c, struct doq_pkt_addr* paddr, + /** doq timer callback function. */ + void doq_timer_cb(void* arg); + ++/** tcp read again callback function. For tcp req info listen. */ ++void tcp_read_again_cb(void* arg); ++ ++/** tcp more read again callback function. For outside network. */ ++void tcp_more_read_again_cb(void* arg); ++ + /** + * This routine is published for checks and tests, and is only used internally. + * handle libevent callback for timer comm. diff --git a/meta-networking/recipes-support/unbound/unbound_1.24.2.bb b/meta-networking/recipes-support/unbound/unbound_1.24.2.bb index faf315876c..0b59d7c9f9 100644 --- a/meta-networking/recipes-support/unbound/unbound_1.24.2.bb +++ b/meta-networking/recipes-support/unbound/unbound_1.24.2.bb @@ -43,6 +43,7 @@ SRC_URI = "git://github.com/NLnetLabs/unbound.git;protocol=https;branch=master;t file://CVE-2026-55991.patch \ file://CVE-2026-56416.patch \ file://CVE-2026-56444.patch \ + file://CVE-2026-80225.patch \ " SRCREV = "f6269baa605d31859f28770e01a24e3677e5f82c"