- Fix to shorten RRSIG count in scrubber, this protects against
Some checks failed
ci / build (push) Has been cancelled

an overly large number of RRSIGs. It can be configured with
  `iter-scrub-rrsig: 8`, it has default 8. Thanks to Yuxiao Wu,
  Tsinghua University for the report.
This commit is contained in:
W.C.A. Wijngaards 2026-04-15 11:41:28 +02:00
parent f4f964f4fb
commit db1fe8b475
10 changed files with 170 additions and 0 deletions

View file

@ -6371,6 +6371,7 @@ fr_atomic_copy_cfg(struct config_file* oldcfg, struct config_file* cfg,
COPY_VAR_int(ede);
COPY_VAR_int(iter_scrub_ns);
COPY_VAR_int(iter_scrub_cname);
COPY_VAR_int(iter_scrub_rrsig);
COPY_VAR_int(max_global_quota);
COPY_VAR_int(iter_scrub_promiscuous);

View file

@ -2,6 +2,10 @@
- Fix RFC7766 compliance when client sends EOF over TCP. It stops
pending replies and closes. Thanks to Yuxiao Wu, Tsinghua
University for the report.
- Fix to shorten RRSIG count in scrubber, this protects against
an overly large number of RRSIGs. It can be configured with
`iter-scrub-rrsig: 8`, it has default 8. Thanks to Yuxiao Wu,
Tsinghua University for the report.
14 April 2026: Wouter
- Fix #1017: memory corruption related core dumps.

View file

@ -193,6 +193,9 @@ server:
# Limit on number of CNAME, DNAME records for incoming packets.
# iter-scrub-cname: 11
# Limit on number of RRSIGs for an RRset for incoming packets.
# iter-scrub-rrsig: 8
# Limit on upstream queries for an incoming query and its recursion.
# max-global-quota: 200

View file

@ -3297,6 +3297,15 @@ These options are part of the ``server:`` section.
Default: 11
@@UAHL@unbound.conf@iter-scrub-rrsig@@: *<number>*
Limit on the number of RRSIGs allowed for an RRset, from the iterator
scrubber.
This protects against an overly large number of RRSIGs.
Clips off the remainder of the RRSIG list at that point.
Default: 8
@@UAHL@unbound.conf@max-global-quota@@: *<number>*
Limit on the number of upstream queries sent out for an incoming query and
its subqueries from recursion.

View file

@ -419,6 +419,43 @@ shorten_rrset(sldns_buffer* pkt, struct rrset_parse* rrset, int count)
else rrset->rr_first = NULL;
}
/** Shorten RRSIGs list */
static void
shorten_rrsig(sldns_buffer* pkt, struct rrset_parse* rrset, int count)
{
/* The too large list of RRSIGs on the RRset is shortened.
* This is so that too large content does not overwhelm the cache.
* The validator does not validate more than a max number of
* RRSIGs as well. */
int i;
struct rr_parse* rr = rrset->rrsig_first, *prev = NULL;
if(!rr)
return;
for(i=0; i<count; i++) {
prev = rr;
rr = rr->next;
if(!rr)
return; /* The RRSIG list is already short. */
}
if(verbosity >= VERB_QUERY
&& rrset->dname_len <= LDNS_MAX_DOMAINLEN) {
uint8_t buf[LDNS_MAX_DOMAINLEN+1];
dname_pkt_copy(pkt, buf, rrset->dname);
log_nametypeclass(VERB_QUERY, "normalize: shorten RRSIGs:",
buf, rrset->type, ntohs(rrset->rrset_class));
}
/* remove further rrsigs */
rrset->rrsig_last = prev;
rrset->rrsig_count = count;
while(rr) {
rrset->size -= rr->size;
rr = rr->next;
}
if(rrset->rrsig_last)
rrset->rrsig_last->next = NULL;
else rrset->rrsig_first = NULL;
}
/**
* This routine normalizes a response. This includes removing "irrelevant"
* records from the answer and additional sections and (re)synthesizing
@ -456,6 +493,8 @@ scrub_normalize(sldns_buffer* pkt, struct msg_parse* msg,
prev = NULL;
rrset = msg->rrset_first;
while(rrset && rrset->section == LDNS_SECTION_ANSWER) {
if((int)rrset->rrsig_count > env->cfg->iter_scrub_rrsig)
shorten_rrsig(pkt, rrset, env->cfg->iter_scrub_rrsig);
if(cname_length > env->cfg->iter_scrub_cname) {
/* Too many CNAMEs, or DNAMEs, from the authority
* server, scrub down the length to something
@ -631,6 +670,8 @@ scrub_normalize(sldns_buffer* pkt, struct msg_parse* msg,
"RRset:", pkt, msg, prev, &rrset);
continue;
}
if((int)rrset->rrsig_count > env->cfg->iter_scrub_rrsig)
shorten_rrsig(pkt, rrset, env->cfg->iter_scrub_rrsig);
/* only one NS set allowed in authority section */
if(rrset->type==LDNS_RR_TYPE_NS) {
/* NS set must be pertinent to the query */
@ -773,6 +814,8 @@ scrub_normalize(sldns_buffer* pkt, struct msg_parse* msg,
"RRset:", pkt, msg, prev, &rrset);
continue;
}
if((int)rrset->rrsig_count > env->cfg->iter_scrub_rrsig)
shorten_rrsig(pkt, rrset, env->cfg->iter_scrub_rrsig);
prev = rrset;
rrset = rrset->rrset_all_next;
}

93
testdata/fwd_scrub_rrsig.rpl vendored Normal file
View file

@ -0,0 +1,93 @@
; This is a comment
server:
forward-zone: name: "." forward-addr: 216.0.0.1
CONFIG_END
SCENARIO_BEGIN Test scrub of RRSIG amount
RANGE_BEGIN 0 100
ADDRESS 216.0.0.1
ENTRY_BEGIN
MATCH opcode qtype qname
ADJUST copy_id
REPLY QR RD RA NOERROR
SECTION QUESTION
www.example.com. IN A
SECTION ANSWER
www.example.com. IN A 10.20.30.40
www.example.com. 300 IN RRSIG A 8 3 300 20330518033320 20010909014640 12345 . MQ== ;{id = 12345}
www.example.com. 300 IN RRSIG A 8 3 300 20330518033320 20010909014640 12345 . Mg== ;{id = 12345}
www.example.com. 300 IN RRSIG A 8 3 300 20330518033320 20010909014640 12345 . Mw== ;{id = 12345}
www.example.com. 300 IN RRSIG A 8 3 300 20330518033320 20010909014640 12345 . NA== ;{id = 12345}
www.example.com. 300 IN RRSIG A 8 3 300 20330518033320 20010909014640 12345 . NQ== ;{id = 12345}
www.example.com. 300 IN RRSIG A 8 3 300 20330518033320 20010909014640 12345 . Ng== ;{id = 12345}
www.example.com. 300 IN RRSIG A 8 3 300 20330518033320 20010909014640 12345 . Nw== ;{id = 12345}
www.example.com. 300 IN RRSIG A 8 3 300 20330518033320 20010909014640 12345 . OA== ;{id = 12345}
www.example.com. 300 IN RRSIG A 8 3 300 20330518033320 20010909014640 12345 . OQ== ;{id = 12345}
www.example.com. 300 IN RRSIG A 8 3 300 20330518033320 20010909014640 12345 . MTA= ;{id = 12345}
www.example.com. 300 IN RRSIG A 8 3 300 20330518033320 20010909014640 12345 . MTE= ;{id = 12345}
www.example.com. 300 IN RRSIG A 8 3 300 20330518033320 20010909014640 12345 . MTI= ;{id = 12345}
www.example.com. 300 IN RRSIG A 8 3 300 20330518033320 20010909014640 12345 . MTM= ;{id = 12345}
www.example.com. 300 IN RRSIG A 8 3 300 20330518033320 20010909014640 12345 . MTQ= ;{id = 12345}
www.example.com. 300 IN RRSIG A 8 3 300 20330518033320 20010909014640 12345 . MTU= ;{id = 12345}
www.example.com. 300 IN RRSIG A 8 3 300 20330518033320 20010909014640 12345 . MTY= ;{id = 12345}
www.example.com. 300 IN RRSIG A 8 3 300 20330518033320 20010909014640 12345 . MTc= ;{id = 12345}
www.example.com. 300 IN RRSIG A 8 3 300 20330518033320 20010909014640 12345 . MTg= ;{id = 12345}
www.example.com. 300 IN RRSIG A 8 3 300 20330518033320 20010909014640 12345 . MTk= ;{id = 12345}
www.example.com. 300 IN RRSIG A 8 3 300 20330518033320 20010909014640 12345 . MjA= ;{id = 12345}
www.example.com. 300 IN RRSIG A 8 3 300 20330518033320 20010909014640 12345 . MjE= ;{id = 12345}
www.example.com. 300 IN RRSIG A 8 3 300 20330518033320 20010909014640 12345 . MjI= ;{id = 12345}
www.example.com. 300 IN RRSIG A 8 3 300 20330518033320 20010909014640 12345 . MjM= ;{id = 12345}
www.example.com. 300 IN RRSIG A 8 3 300 20330518033320 20010909014640 12345 . MjQ= ;{id = 12345}
www.example.com. 300 IN RRSIG A 8 3 300 20330518033320 20010909014640 12345 . MjU= ;{id = 12345}
www.example.com. 300 IN RRSIG A 8 3 300 20330518033320 20010909014640 12345 . MjY= ;{id = 12345}
www.example.com. 300 IN RRSIG A 8 3 300 20330518033320 20010909014640 12345 . Mjc= ;{id = 12345}
www.example.com. 300 IN RRSIG A 8 3 300 20330518033320 20010909014640 12345 . Mjg= ;{id = 12345}
www.example.com. 300 IN RRSIG A 8 3 300 20330518033320 20010909014640 12345 . Mjk= ;{id = 12345}
www.example.com. 300 IN RRSIG A 8 3 300 20330518033320 20010909014640 12345 . MzA= ;{id = 12345}
www.example.com. 300 IN RRSIG A 8 3 300 20330518033320 20010909014640 12345 . MzE= ;{id = 12345}
www.example.com. 300 IN RRSIG A 8 3 300 20330518033320 20010909014640 12345 . MzI= ;{id = 12345}
www.example.com. 300 IN RRSIG A 8 3 300 20330518033320 20010909014640 12345 . MzM= ;{id = 12345}
www.example.com. 300 IN RRSIG A 8 3 300 20330518033320 20010909014640 12345 . MzQ= ;{id = 12345}
www.example.com. 300 IN RRSIG A 8 3 300 20330518033320 20010909014640 12345 . MzU= ;{id = 12345}
www.example.com. 300 IN RRSIG A 8 3 300 20330518033320 20010909014640 12345 . MzY= ;{id = 12345}
www.example.com. 300 IN RRSIG A 8 3 300 20330518033320 20010909014640 12345 . Mzc= ;{id = 12345}
www.example.com. 300 IN RRSIG A 8 3 300 20330518033320 20010909014640 12345 . Mzg= ;{id = 12345}
www.example.com. 300 IN RRSIG A 8 3 300 20330518033320 20010909014640 12345 . Mzk= ;{id = 12345}
www.example.com. 300 IN RRSIG A 8 3 300 20330518033320 20010909014640 12345 . NDA= ;{id = 12345}
www.example.com. 300 IN RRSIG A 8 3 300 20330518033320 20010909014640 12345 . NDE= ;{id = 12345}
www.example.com. 300 IN RRSIG A 8 3 300 20330518033320 20010909014640 12345 . NDI= ;{id = 12345}
www.example.com. 300 IN RRSIG A 8 3 300 20330518033320 20010909014640 12345 . NDM= ;{id = 12345}
www.example.com. 300 IN RRSIG A 8 3 300 20330518033320 20010909014640 12345 . NDQ= ;{id = 12345}
www.example.com. 300 IN RRSIG A 8 3 300 20330518033320 20010909014640 12345 . NDU= ;{id = 12345}
ENTRY_END
RANGE_END
STEP 1 QUERY
ENTRY_BEGIN
MATCH TCP
REPLY RD DO
SECTION QUESTION
www.example.com. IN A
ENTRY_END
STEP 4 CHECK_ANSWER
ENTRY_BEGIN
MATCH opcode qname qtype all
REPLY QR RD DO RA
SECTION QUESTION
www.example.com. IN A
SECTION ANSWER
www.example.com. IN A 10.20.30.40
www.example.com. 300 IN RRSIG A 8 3 300 20330518033320 20010909014640 12345 . MQ== ;{id = 12345}
www.example.com. 300 IN RRSIG A 8 3 300 20330518033320 20010909014640 12345 . Mg== ;{id = 12345}
www.example.com. 300 IN RRSIG A 8 3 300 20330518033320 20010909014640 12345 . Mw== ;{id = 12345}
www.example.com. 300 IN RRSIG A 8 3 300 20330518033320 20010909014640 12345 . NA== ;{id = 12345}
www.example.com. 300 IN RRSIG A 8 3 300 20330518033320 20010909014640 12345 . NQ== ;{id = 12345}
www.example.com. 300 IN RRSIG A 8 3 300 20330518033320 20010909014640 12345 . Ng== ;{id = 12345}
www.example.com. 300 IN RRSIG A 8 3 300 20330518033320 20010909014640 12345 . Nw== ;{id = 12345}
www.example.com. 300 IN RRSIG A 8 3 300 20330518033320 20010909014640 12345 . OA== ;{id = 12345}
ENTRY_END
SCENARIO_END

View file

@ -429,6 +429,7 @@ config_create(void)
cfg->dns_error_reporting = 0;
cfg->iter_scrub_ns = 20;
cfg->iter_scrub_cname = 11;
cfg->iter_scrub_rrsig = 8;
cfg->iter_scrub_promiscuous = 1;
cfg->max_global_quota = 200;
return cfg;
@ -776,6 +777,7 @@ int config_set_option(struct config_file* cfg, const char* opt,
else S_YNO("dns-error-reporting:", dns_error_reporting)
else S_NUMBER_OR_ZERO("iter-scrub-ns:", iter_scrub_ns)
else S_NUMBER_OR_ZERO("iter-scrub-cname:", iter_scrub_cname)
else S_NUMBER_OR_ZERO("iter-scrub-rrsig:", iter_scrub_rrsig)
else S_YNO("iter-scrub-promiscuous:", iter_scrub_promiscuous)
else S_NUMBER_OR_ZERO("max-global-quota:", max_global_quota)
else S_YNO("serve-original-ttl:", serve_original_ttl)
@ -1255,6 +1257,7 @@ config_get_option(struct config_file* cfg, const char* opt,
else O_YNO(opt, "dns-error-reporting", dns_error_reporting)
else O_DEC(opt, "iter-scrub-ns", iter_scrub_ns)
else O_DEC(opt, "iter-scrub-cname", iter_scrub_cname)
else O_DEC(opt, "iter-scrub-rrsig", iter_scrub_rrsig)
else O_YNO(opt, "iter-scrub-promiscuous", iter_scrub_promiscuous)
else O_DEC(opt, "max-global-quota", max_global_quota)
else O_YNO(opt, "serve-original-ttl", serve_original_ttl)

View file

@ -794,6 +794,8 @@ struct config_file {
size_t iter_scrub_ns;
/** limit on CNAME, DNAME RRs in answer for the iterator scrubber. */
int iter_scrub_cname;
/** limit on RRSIGs for an RRset for the iterator scrubber. */
int iter_scrub_rrsig;
/** limit on upstream queries for an incoming query and subqueries. */
int max_global_quota;
/** Should the iterator scrub promiscuous NS rrsets, from positive

View file

@ -607,6 +607,7 @@ dns-error-reporting{COLON} { YDVAR(1, VAR_DNS_ERROR_REPORTING ) }
proxy-protocol-port{COLON} { YDVAR(1, VAR_PROXY_PROTOCOL_PORT) }
iter-scrub-ns{COLON} { YDVAR(1, VAR_ITER_SCRUB_NS) }
iter-scrub-cname{COLON} { YDVAR(1, VAR_ITER_SCRUB_CNAME) }
iter-scrub-rrsig{COLON} { YDVAR(1, VAR_ITER_SCRUB_RRSIG) }
max-global-quota{COLON} { YDVAR(1, VAR_MAX_GLOBAL_QUOTA) }
iter-scrub-promiscuous{COLON} { YDVAR(1, VAR_ITER_SCRUB_PROMISCUOUS) }
<INITIAL,val>{NEWLINE} { LEXOUT(("NL\n")); cfg_parser->line++; }

View file

@ -215,6 +215,7 @@ extern struct config_parser_state* cfg_parser;
%token VAR_HARDEN_UNKNOWN_ADDITIONAL VAR_DISABLE_EDNS_DO VAR_CACHEDB_NO_STORE
%token VAR_LOG_DESTADDR VAR_CACHEDB_CHECK_WHEN_SERVE_EXPIRED
%token VAR_COOKIE_SECRET_FILE VAR_ITER_SCRUB_NS VAR_ITER_SCRUB_CNAME
%token VAR_ITER_SCRUB_RRSIG
%token VAR_MAX_GLOBAL_QUOTA VAR_HARDEN_UNVERIFIED_GLUE VAR_LOG_TIME_ISO
%token VAR_ITER_SCRUB_PROMISCUOUS VAR_LOG_THREAD_ID
@ -359,6 +360,7 @@ content_server: server_num_threads | server_verbosity | server_port |
server_harden_unknown_additional | server_disable_edns_do |
server_log_destaddr | server_cookie_secret_file |
server_iter_scrub_ns | server_iter_scrub_cname | server_max_global_quota |
server_iter_scrub_rrsig |
server_harden_unverified_glue | server_log_time_iso | server_iter_scrub_promiscuous
;
stub_clause: stubstart contents_stub
@ -4255,6 +4257,15 @@ server_iter_scrub_cname: VAR_ITER_SCRUB_CNAME STRING_ARG
free($2);
}
;
server_iter_scrub_rrsig: VAR_ITER_SCRUB_RRSIG STRING_ARG
{
OUTYY(("P(server_iter_scrub_rrsig:%s)\n", $2));
if(atoi($2) == 0 && strcmp($2, "0") != 0)
yyerror("number expected");
else cfg_parser->cfg->iter_scrub_rrsig = atoi($2);
free($2);
}
;
server_max_global_quota: VAR_MAX_GLOBAL_QUOTA STRING_ARG
{
OUTYY(("P(server_max_global_quota:%s)\n", $2));