CVE-2024-33655 (Other, published 2024-06-06T17:15Z)
Description: value: The DNS protocol in RFC 1035 and updates allows remote attackers to cause a denial of service (resource consumption) by arranging for DNS queries to be accumulated for seconds, such that responses are later sent in a pulsing burst (which can be considered traffic amplification in some cases), aka the "DNSBomb" issue.
Severity: nan (CVSS v3 base score: )
Weakness: NVD-CWE-noinfo \u2014 Insufficient Information
There is insufficient information about the issue to classify it; details are unkown or unspecified.
Repository: https://github.com/NLnetLabs/unbound
Files changed:
doc/Changelog
doc/example.conf.in
doc/unbound.conf.5.in
services/cache/infra.c
services/cache/infra.h
services/mesh.c
testdata/cachedb_expired_client_timeout.crpl
testdata/cachedb_subnet_expired.crpl
testdata/doh_downstream.tdir/doh_downstream.conf
testdata/doh_downstream_notls.tdir/doh_downstream_notls.conf
testdata/doh_downstream_post.tdir/doh_downstream_post.conf
testdata/fwd_three_service.tdir/fwd_three_service.conf
testdata/iter_ghost_timewindow.rpl
testdata/ssl_req_order.tdir/ssl_req_order.conf
testdata/tcp_req_order.tdir/tcp_req_order.conf
testdata/tcp_sigpipe.tdir/tcp_sigpipe.conf
util/config_file.c
util/config_file.h
util/configlexer.lex
util/configparser.y
Commit message: - Fix for the DNSBomb vulnerability CVE-2024-33655. Thanks to Xiang Li
  from the Network and Information Security Lab of Tsinghua University
  for reporting it.


Vulnerable code:
	struct sockaddr_storage* addr, socklen_t addrlen, time_t timenow)
	infra_ip_create_ratedata(infra, addr, addrlen, timenow);
	verbosity: 2

Fixed code:
1 May 2024: Wouter
	- Fix for the DNSBomb vulnerability CVE-2024-33655. Thanks to Xiang Li
	  from the Network and Information Security Lab of Tsinghua University
	  for reporting it.

	# msec before recursion replies are dropped. The work item continues.
	# discard-timeout: 1900

	# Max number of replies waiting for recursion per IP address.
	# wait-limit: 1000

	# Max replies waiting for recursion for IP address with cookie.
	# wait-limit-cookie: 10000

	# Apart from the default, the wait limit can be set for a netblock.
	# wait-limit-netblock: 192.0.2.0/24 50000

	# Apart from the default, the wait limit with cookie can be adjusted.
	# wait-limit-cookie-netblock: 192.0.2.0/24 50000

.B discard\-timeout: \fI<msec>
The wait time in msec where recursion requests are dropped. This is
to stop a large number of replies from accumulating. They receive
no reply, the work item continues to recurse. It is nice to be a bit
larger than serve\-expired\-client\-timeout if that is enabled.
A value of 1900 msec is suggested. The value 0 disables it.
Default 1900 msec.
.TP
.B wait\-limit: \fI<number>
The number of replies that can wait for recursion, for an IP address.
This makes a ratelimit per IP address of waiting replies for recursion.
It stops very large amounts of queries waiting to be returned to one
destination. The value 0 disables wait limits. Default is 1000.
.TP
.B wait\-limit\-cookie: \fI<number>
The number of replies that can wait for recursion, for an IP address
that sent the query with a valid DNS cookie. Since the cookie validates
the client address, the limit can be higher. Default is 10000.
.TP
.B wait\-limit\-netblock: \fI<netblock> <number>
The wait limit for the netblock. If not given the wait\-limit value is
used. The most specific netblock is used to determine the limit. Useful for
overriding the default for a specific, group or individual, server.
The value -1 disables wait limits for the netblock.
.TP
.B wait\-limit\-cookie\-netblock: \fI<netblock> <number>
The wait limit for the netblock, when the query has a DNS cookie.
If not given, the wait\-limit\-cookie value is used.
The value -1 disables wait limits for the netblock.
.TP
/** find or create element in wait limit netblock tree */
static struct wait_limit_netblock_info*
wait_limit_netblock_findcreate(struct infra_cache* infra, char* str,
	int cookie)
{
	rbtree_type* tree;
	struct sockaddr_storage addr;
	int net;
	socklen_t addrlen;
	struct wait_limit_netblock_info* d;

	if(!netblockstrtoaddr(str, 0, &addr, &addrlen, &net)) {
		log_err("cannot parse wait limit netblock '%s'", str);
		return 0;
	}

	/* can we find it? */
	if(cookie)
		tree = &infra->wait_limits_cookie_netblock;
	else
		tree = &infra->wait_limits_netblock;
	d = (struct wait_limit_netblock_info*)addr_tree_find(tree, &addr,
		addrlen, net);
	if(d)
		return d;

	/* create it */
	d = (struct wait_limit_netblock_info*)calloc(1, sizeof(*d));
	if(!d)
		return NULL;
	d->limit = -1;
	if(!addr_tree_insert(tree, &d->node, &addr, addrlen, net)) {
		log_err("duplicate element in domainlimit tree");
		free(d);
		return NULL;
	}
	return d;
}


/** insert wait limit information into lookup tree */
static int
infra_wait_limit_netblock_insert(struct infra_cache* infra,
	struct config_file* cfg)
{
	struct config_str2list* p;
	struct wait_limit_netblock_info* d;
	for(p = cfg->wait_limit_netblock; p; p = p->next) {
		d = wait_limit_netblock_findcreate(infra, p->str, 0);
		if(!d)
			return 0;
		d->limit = atoi(p->str2);
	}
	for(p = cfg->wait_limit_cookie_netblock; p; p = p->next) {
		d = wait_limit_netblock_findcreate(infra, p->str, 1);
		if(!d)
			return 0;
		d->limit = atoi(p->str2);
	}
	return 1;
}

/** setup wait limits tree (0 on failure) */
static int
setup_wait_limits(struct infra_cache* infra, struct config_file* cfg)
{
	addr_tree_init(&infra->wait_limits_netblock);
	addr_tree_init(&infra->wait_limits_cookie_netblock);
	if(!infra_wait_limit_netblock_insert(infra, cfg))
		return 0;
	addr_tree_init_parents(&infra->wait_limits_netblock);
	addr_tree_init_parents(&infra->wait_limits_cookie_netblock);
	return 1;
}

	if(!setup_wait_limits(infra, cfg)) {
		infra_delete(infra);
		return NULL;
	}
/** delete wait_limit_netblock_info entries */
static void wait_limit_netblock_del(rbnode_type* n, void* ATTR_UNUSED(arg))
{
	free(n);
}

	traverse_postorder(&infra->wait_limits_netblock,
		wait_limit_netblock_del, NULL);
	traverse_postorder(&infra->wait_limits_cookie_netblock,
		wait_limit_netblock_del, NULL);
	struct sockaddr_storage* addr, socklen_t addrlen, time_t timenow,
	int mesh_wait)
	d->mesh_wait = mesh_wait;
	infra_ip_create_ratedata(infra, addr, addrlen, timenow, 0);

int infra_wait_limit_allowed(struct infra_cache* infra, struct comm_reply* rep,
	int cookie_valid, struct config_file* cfg)
{
	struct lruhash_entry* entry;
	if(cfg->wait_limit == 0)
		return 1;

	entry = infra_find_ip_ratedata(infra, &rep->client_addr,
		rep->client_addrlen, 0);
	if(entry) {
		rbtree_type* tree;
		struct wait_limit_netblock_info* w;
		struct rate_data* d = (struct rate_data*)entry->data;
		int mesh_wait = d->mesh_wait;
		lock_rw_unlock(&entry->lock);

		/* have the wait amount, check how much is allowed */
		if(cookie_valid)
			tree = &infra->wait_limits_cookie_netblock;
		else	tree = &infra->wait_limits_netblock;
		w = (struct wait_limit_netblock_info*)addr_tree_lookup(tree,
			&rep->client_addr, rep->client_addrlen);
		if(w) {
			if(w->limit != -1 && mesh_wait > w->limit)
				return 0;
		} else {
			/* if there is no IP netblock specific information,
			 * use the configured value. */
			if(mesh_wait > (cookie_valid?cfg->wait_limit_cookie:
				cfg->wait_limit))
				return 0;
		}
	}
	return 1;
}

void infra_wait_limit_inc(struct infra_cache* infra, struct comm_reply* rep,
	time_t timenow, struct config_file* cfg)
{
	struct lruhash_entry* entry;
	if(cfg->wait_limit == 0)
		return;

	/* Find it */
	entry = infra_find_ip_ratedata(infra, &rep->client_addr,
		rep->client_addrlen, 1);
	if(entry) {
		struct rate_data* d = (struct rate_data*)entry->data;
		d->mesh_wait++;
		lock_rw_unlock(&entry->lock);
		return;
	}

	/* Create it */
	infra_ip_create_ratedata(infra, &rep->client_addr,
		rep->client_addrlen, timenow, 1);
}

void infra_wait_limit_dec(struct infra_cache* infra, struct comm_reply* rep,
	struct config_file* cfg)
{
	struct lruhash_entry* entry;
	if(cfg->wait_limit == 0)
		return;

	entry = infra_find_ip_ratedata(infra, &rep->client_addr,
		rep->client_addrlen, 1);
	if(entry) {
		struct rate_data* d = (struct rate_data*)entry->data;
		if(d->mesh_wait > 0)
			d->mesh_wait--;
		lock_rw_unlock(&entry->lock);
	}
}
	/** tree of addr_tree_node, with wait_limit_netblock_info information */
	rbtree_type wait_limits_netblock;
	/** tree of addr_tree_node, with wait_limit_netblock_info information */
	rbtree_type wait_limits_cookie_netblock;
	/** the number of queries waiting in the mesh */
	int mesh_wait;
/**
 * Data to store the configuration per netblock for the wait limit
 */
struct wait_limit_netblock_info {
	/** The addr tree node, this must be first. */
	struct addr_tree_node node;
	/** the limit on the amount */
	int limit;
};

/** See if the IP address can have another reply in the wait limit */
int infra_wait_limit_allowed(struct infra_cache* infra, struct comm_reply* rep,
	int cookie_valid, struct config_file* cfg);

/** Increment number of waiting replies for IP */
void infra_wait_limit_inc(struct infra_cache* infra, struct comm_reply* rep,
	time_t timenow, struct config_file* cfg);

/** Decrement number of waiting replies for IP */
void infra_wait_limit_dec(struct infra_cache* infra, struct comm_reply* rep,
	struct config_file* cfg);

#include "services/cache/infra.h"
	if(!infra_wait_limit_allowed(mesh->env->infra_cache, rep,
		edns->cookie_valid, mesh->env->cfg)) {
		verbose(VERB_ALGO, "Too many queries waiting from the IP. "
			"dropping incoming query.");
		comm_point_drop_reply(rep);
		mesh->stats_dropped++;
		return;
	}
	infra_wait_limit_inc(mesh->env->infra_cache, rep, *mesh->env->now,
		mesh->env->cfg);
			infra_wait_limit_dec(mesh->env->infra_cache,
				&rep->query_reply, mesh->env->cfg);
	infra_wait_limit_dec(m->s.env->infra_cache, &r->query_reply,
		m->s.env->cfg);
		struct timeval old;
		timeval_subtract(&old, mstate->s.env->now_tv, &r->start_time);
		if(mstate->s.env->cfg->discard_timeout != 0 &&
			((int)old.tv_sec)*1000+((int)old.tv_usec)/1000 >
			mstate->s.env->cfg->discard_timeout) {
			/* Drop the reply, it is too old */
			/* briefly set the reply_list to NULL, so that the
			 * tcp req info cleanup routine that calls the mesh
			 * to deregister the meshstate for it is not done
			 * because the list is NULL and also accounting is not
			 * done there, but instead we do that here. */
			struct mesh_reply* reply_list = mstate->reply_list;
			verbose(VERB_ALGO, "drop reply, it is older than discard-timeout");
			infra_wait_limit_dec(mstate->s.env->infra_cache,
				&r->query_reply, mstate->s.env->cfg);
			mstate->reply_list = NULL;
			comm_point_drop_reply(&r->query_reply);
			mstate->reply_list = reply_list;
			mstate->s.env->mesh->stats_dropped++;
			continue;
		}

			infra_wait_limit_dec(mstate->s.env->infra_cache,
				&r->query_reply, mstate->s.env->cfg);
			infra_wait_limit_dec(mesh->env->infra_cache,
				&n->query_reply, mesh->env->cfg);
		struct timeval old;
		timeval_subtract(&old, mstate->s.env->now_tv, &r->start_time);
		if(mstate->s.env->cfg->discard_timeout != 0 &&
			((int)old.tv_sec)*1000+((int)old.tv_usec)/1000 >
			mstate->s.env->cfg->discard_timeout) {
			/* Drop the reply, it is too old */
			/* briefly set the reply_list to NULL, so that the
			 * tcp req info cleanup routine that calls the mesh
			 * to deregister the meshstate for it is not done
			 * because the list is NULL and also accounting is not
			 * done there, but instead we do that here. */
			struct mesh_reply* reply_list = mstate->reply_list;
			verbose(VERB_ALGO, "drop reply, it is older than discard-timeout");
			infra_wait_limit_dec(mstate->s.env->infra_cache,
				&r->query_reply, mstate->s.env->cfg);
			mstate->reply_list = NULL;
			comm_point_drop_reply(&r->query_reply);
			mstate->reply_list = reply_list;
			mstate->s.env->mesh->stats_dropped++;
			continue;
		}

		infra_wait_limit_dec(mstate->s.env->infra_cache,
			&r->query_reply, mstate->s.env->cfg);
	discard-timeout: 3000
	discard-timeout: 3000
	discard-timeout: 3000  # testns uses sleep=2
	discard-timeout: 3000  # testns uses sleep=2
	discard-timeout: 3000  # testns uses sleep=2
	discard-timeout: 3000  # testns uses sleep=2
	discard-timeout: 86400
	discard-timeout: 3000  # testns uses sleep=2
	discard-timeout: 3000  # testns uses sleep=2
	verbosity: 4
	discard-timeout: 3000  # testns uses sleep=2
	cfg->discard_timeout = 1900; /* msec */
	cfg->wait_limit = 1000;
	cfg->wait_limit_cookie = 10000;
	cfg->wait_limit_netblock = NULL;
	cfg->wait_limit_cookie_netblock = NULL;
	else S_NUMBER_OR_ZERO("discard-timeout:", discard_timeout)
	else S_NUMBER_OR_ZERO("wait-limit:", wait_limit)
	else S_NUMBER_OR_ZERO("wait-limit-cookie:", wait_limit_cookie)
	else O_DEC(opt, "discard-timeout", discard_timeout)
	else O_DEC(opt, "wait-limit", wait_limit)
	else O_DEC(opt, "wait-limit-cookie", wait_limit_cookie)
	else O_LS2(opt, "wait-limit-netblock", wait_limit_netblock)
	else O_LS2(opt, "wait-limit-cookie-netblock", wait_limit_cookie_netblock)
	config_deldblstrlist(cfg->wait_limit_netblock);
	config_deldblstrlist(cfg->wait_limit_cookie_netblock);
	/** Wait time to drop recursion replies */
	int discard_timeout;

	/** Wait limit for number of replies per IP address */
	int wait_limit;

	/** Wait limit for number of replies per IP address with cookie */
	int wait_limit_cookie;

	/** wait limit per netblock */
	struct config_str2list* wait_limit_netblock;

	/** wait limit with cookie per netblock */
	struct config_str2list* wait_limit_cookie_netblock;

discard-timeout{COLON}		{ YDVAR(1, VAR_DISCARD_TIMEOUT) }
wait-limit{COLON}		{ YDVAR(1, VAR_WAIT_LIMIT) }
wait-limit-cookie{COLON}	{ YDVAR(1, VAR_WAIT_LIMIT_COOKIE) }
wait-limit-netblock{COLON}	{ YDVAR(1, VAR_WAIT_LIMIT_NETBLOCK) }
wait-limit-cookie-netblock{COLON} { YDVAR(1, VAR_WAIT_LIMIT_COOKIE_NETBLOCK) }
%token VAR_DISCARD_TIMEOUT VAR_WAIT_LIMIT VAR_WAIT_LIMIT_COOKIE
%token VAR_WAIT_LIMIT_NETBLOCK VAR_WAIT_LIMIT_COOKIE_NETBLOCK
	server_discard_timeout | server_wait_limit | server_wait_limit_cookie |
	server_wait_limit_netblock | server_wait_limit_cookie_netblock |
server_discard_timeout: VAR_DISCARD_TIMEOUT STRING_ARG
	{
		OUTYY(("P(server_discard_timeout:%s)\n", $2));
		cfg_parser->cfg->discard_timeout = atoi($2);
		free($2);
	}
	;
server_wait_limit: VAR_WAIT_LIMIT STRING_ARG
	{
		OUTYY(("P(server_wait_limit:%s)\n", $2));
		cfg_parser->cfg->wait_limit = atoi($2);
		free($2);
	}
	;
server_wait_limit_cookie: VAR_WAIT_LIMIT_COOKIE STRING_ARG
	{
		OUTYY(("P(server_wait_limit_cookie:%s)\n", $2));
		cfg_parser->cfg->wait_limit_cookie = atoi($2);
		free($2);
	}
	;
server_wait_limit_netblock: VAR_WAIT_LIMIT_NETBLOCK STRING_ARG STRING_ARG
	{
		OUTYY(("P(server_wait_limit_netblock:%s %s)\n", $2, $3));
		if(atoi($3) == 0 && strcmp($3, "0") != 0) {
			yyerror("number expected");
			free($2);
			free($3);
		} else {
			if(!cfg_str2list_insert(&cfg_parser->cfg->
				wait_limit_netblock, $2, $3))
				fatal_exit("out of memory adding "
					"wait-limit-netblock");
		}
	}
	;
server_wait_limit_cookie_netblock: VAR_WAIT_LIMIT_COOKIE_NETBLOCK STRING_ARG STRING_ARG
	{
		OUTYY(("P(server_wait_limit_cookie_netblock:%s %s)\n", $2, $3));
		if(atoi($3) == 0 && strcmp($3, "0") != 0) {
			yyerror("number expected");
			free($2);
			free($3);
		} else {
			if(!cfg_str2list_insert(&cfg_parser->cfg->
				wait_limit_cookie_netblock, $2, $3))
				fatal_exit("out of memory adding "
					"wait-limit-cookie-netblock");
		}
	}
	;