]> git.ipfire.org Git - thirdparty/unbound.git/commitdiff
- Fix to shorten RRSIG count in scrubber, this protects against
authorW.C.A. Wijngaards <wouter@nlnetlabs.nl>
Wed, 15 Apr 2026 09:41:28 +0000 (11:41 +0200)
committerW.C.A. Wijngaards <wouter@nlnetlabs.nl>
Wed, 15 Apr 2026 09:41:28 +0000 (11:41 +0200)
  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.

daemon/remote.c
doc/Changelog
doc/example.conf.in
doc/unbound.conf.rst
iterator/iter_scrub.c
testdata/fwd_scrub_rrsig.rpl [new file with mode: 0644]
util/config_file.c
util/config_file.h
util/configlexer.lex
util/configparser.y

index 6275ccb237ee8ed7869c4ec4a61edde534939672..6a5b1ad12e12218880f1b03d73a45006c36e1f07 100644 (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);
 
index 9c96a9fc977eda04bac94b00100f8e6d06e21511..f8d418edd1d0dece9ec8e8807af5c8ad25418ca6 100644 (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.
index 68b78b9d2dd6faffb9b95abf15bdcf0949dc1479..11f0458ce31fbcf1e2371dc2612e998d7abcc03e 100644 (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
 
index a0daeb236cb97233fc92232e8a816b55e554c3e2..bb7c9e067257c8c5ed3ba91ee1286149d7de29af 100644 (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.
index 757d95a3d744163570d16745473fef2848b51b24..37c4150cd2ddf82b833c8e27068ca0583bd8b11c 100644 (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;
        }
diff --git a/testdata/fwd_scrub_rrsig.rpl b/testdata/fwd_scrub_rrsig.rpl
new file mode 100644 (file)
index 0000000..cc9e426
--- /dev/null
@@ -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
index b1d89f0932f265425a349f8f3c09f33af2f5ccea..af8e3c310c3a23a9b4b55217c57c7e959d48f117 100644 (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)
index f214d3361b075fadc7401c7ad644bac8c12a9750..23468cb0974bc3eadf55a8558fb3c7ef6f46a8d1 100644 (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
index 566de49abc73462342d4a0ba3c3ca62424280087..4540913a9e0d724229f941d3a17840f838ae92fb 100644 (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++; }
index aa787fdcefdfaa34316b6df29653c765a8196160..9b0ef9e72be31d6e1507a319419e8044ef42654d 100644 (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));