From: W.C.A. Wijngaards Date: Wed, 15 Apr 2026 09:41:28 +0000 (+0200) Subject: - Fix to shorten RRSIG count in scrubber, this protects against X-Git-Tag: release-1.25.0rc1~33 X-Git-Url: http://git.ipfire.org/gitweb/?a=commitdiff_plain;h=db1fe8b475a8a291791a52d1f618fb86ef9ada24;p=thirdparty%2Funbound.git - 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. --- diff --git a/daemon/remote.c b/daemon/remote.c index 6275ccb23..6a5b1ad12 100644 --- a/daemon/remote.c +++ b/daemon/remote.c @@ -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); diff --git a/doc/Changelog b/doc/Changelog index 9c96a9fc9..f8d418edd 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -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. diff --git a/doc/example.conf.in b/doc/example.conf.in index 68b78b9d2..11f0458ce 100644 --- a/doc/example.conf.in +++ b/doc/example.conf.in @@ -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 diff --git a/doc/unbound.conf.rst b/doc/unbound.conf.rst index a0daeb236..bb7c9e067 100644 --- a/doc/unbound.conf.rst +++ b/doc/unbound.conf.rst @@ -3297,6 +3297,15 @@ These options are part of the ``server:`` section. Default: 11 +@@UAHL@unbound.conf@iter-scrub-rrsig@@: ** + 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@@: ** Limit on the number of upstream queries sent out for an incoming query and its subqueries from recursion. diff --git a/iterator/iter_scrub.c b/iterator/iter_scrub.c index 757d95a3d..37c4150cd 100644 --- a/iterator/iter_scrub.c +++ b/iterator/iter_scrub.c @@ -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; inext; + 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; } diff --git a/testdata/fwd_scrub_rrsig.rpl b/testdata/fwd_scrub_rrsig.rpl new file mode 100644 index 000000000..cc9e426d6 --- /dev/null +++ b/testdata/fwd_scrub_rrsig.rpl @@ -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 diff --git a/util/config_file.c b/util/config_file.c index b1d89f093..af8e3c310 100644 --- a/util/config_file.c +++ b/util/config_file.c @@ -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) diff --git a/util/config_file.h b/util/config_file.h index f214d3361..23468cb09 100644 --- a/util/config_file.h +++ b/util/config_file.h @@ -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 diff --git a/util/configlexer.lex b/util/configlexer.lex index 566de49ab..4540913a9 100644 --- a/util/configlexer.lex +++ b/util/configlexer.lex @@ -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) } {NEWLINE} { LEXOUT(("NL\n")); cfg_parser->line++; } diff --git a/util/configparser.y b/util/configparser.y index aa787fdce..9b0ef9e72 100644 --- a/util/configparser.y +++ b/util/configparser.y @@ -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));