mirror of
https://github.com/NLnetLabs/unbound.git
synced 2026-09-25 11:14:58 +02:00
- 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.
This commit is contained in:
@@ -2133,7 +2133,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) {
|
||||
@@ -2141,6 +2141,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;
|
||||
}
|
||||
@@ -2150,6 +2156,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
|
||||
|
||||
@@ -347,6 +347,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;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -377,10 +381,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.
|
||||
|
||||
@@ -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 */
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
+65
-1
@@ -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.*/
|
||||
@@ -4621,6 +4625,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;
|
||||
@@ -4637,6 +4645,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;
|
||||
}
|
||||
@@ -4650,6 +4668,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)) {
|
||||
@@ -4662,6 +4681,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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4689,6 +4732,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)
|
||||
{
|
||||
@@ -6131,7 +6191,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);
|
||||
@@ -6693,6 +6753,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) {
|
||||
@@ -6732,6 +6795,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
|
||||
|
||||
@@ -384,6 +384,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.
|
||||
@@ -1133,6 +1136,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.
|
||||
|
||||
Reference in New Issue
Block a user