Package: release.debian.org
Severity: normal
X-Debbugs-Cc: unbo...@packages.debian.org
Control: affects -1 + src:unbound
User: release.debian....@packages.debian.org
Usertags: unblock

Please unblock package unbound

[ Reason ]
There's a single fix from upstream for a security issue,
i#1109427, CVE-2025-5994.

[ Tests ]
I haven't done much testing, though the new package seems
to be working (it is already used in production for our
networks), and it looks like there were no issues found
by other users either.

[ Checklist ]
  [x] all changes are documented in the d/changelog
  [x] I reviewed all changes and I approve them
  [x] attach debdiff against the package in testing

Debdiff is below.

Thanks,

/mjt

unblock unbound/1.22.0-2

diff -Nru unbound-1.22.0/debian/changelog unbound-1.22.0/debian/changelog
--- unbound-1.22.0/debian/changelog     2024-10-19 13:11:14.000000000 +0300
+++ unbound-1.22.0/debian/changelog     2025-07-17 19:05:22.000000000 +0300
@@ -1,3 +1,10 @@
+unbound (1.22.0-2) unstable; urgency=medium
+
+  * add Fix-RebirthDay-Attack-CVE-2025-5994.patch from upstream
+    (Closes: #1109427, CVE-2025-5994)
+
+ -- Michael Tokarev <m...@tls.msk.ru>  Thu, 17 Jul 2025 19:05:22 +0300
+
 unbound (1.22.0-1) unstable; urgency=medium
 
   * new upstream release (1.22.0)
diff -Nru 
unbound-1.22.0/debian/patches/Fix-RebirthDay-Attack-CVE-2025-5994.patch 
unbound-1.22.0/debian/patches/Fix-RebirthDay-Attack-CVE-2025-5994.patch
--- unbound-1.22.0/debian/patches/Fix-RebirthDay-Attack-CVE-2025-5994.patch     
1970-01-01 03:00:00.000000000 +0300
+++ unbound-1.22.0/debian/patches/Fix-RebirthDay-Attack-CVE-2025-5994.patch     
2025-07-17 19:03:46.000000000 +0300
@@ -0,0 +1,278 @@
+From: "W.C.A. Wijngaards" <wou...@nlnetlabs.nl>
+Date: Wed, 16 Jul 2025 10:02:01 +0200
+Subject: Fix RebirthDay Attack CVE-2025-5994, reported by Xiang Li
+ from AOSP Lab Nankai University
+Bug-Debian: https://bugs.debian.org/1109427
+Origin: upstream, 
https://github.com/NLnetLabs/unbound/commit/5bf82f246481098a6473f296b21fc1229d276c0f
+Forwarded: not-needed
+
+---
+ edns-subnet/subnetmod.c | 152 ++++++++++++++++++++++++++++++++++++----
+ edns-subnet/subnetmod.h |   4 ++
+ 2 files changed, 142 insertions(+), 14 deletions(-)
+
+diff --git a/edns-subnet/subnetmod.c b/edns-subnet/subnetmod.c
+index ead720f34..c5e215b8b 100644
+--- a/edns-subnet/subnetmod.c
++++ b/edns-subnet/subnetmod.c
+@@ -51,6 +51,7 @@
+ #include "services/cache/dns.h"
+ #include "util/module.h"
+ #include "util/regional.h"
++#include "util/fptr_wlist.h"
+ #include "util/storage/slabhash.h"
+ #include "util/config_file.h"
+ #include "util/data/msgreply.h"
+@@ -155,7 +156,8 @@ int ecs_whitelist_check(struct query_info* qinfo,
+ 
+       /* Cache by default, might be disabled after parsing EDNS option
+        * received from nameserver. */
+-      if(!iter_stub_fwd_no_cache(qstate, &qstate->qinfo, NULL, NULL, NULL, 
0)) {
++      if(!iter_stub_fwd_no_cache(qstate, &qstate->qinfo, NULL, NULL, NULL, 0)
++              && sq->ecs_client_in.subnet_validdata) {
+               qstate->no_cache_store = 0;
+       }
+ 
+@@ -522,6 +524,69 @@ common_prefix(uint8_t *a, uint8_t *b, uint8_t net)
+       return !memcmp(a, b, n) && ((net % 8) == 0 || a[n] == b[n]);
+ }
+ 
++/**
++ * Create sub request that looks up the query.
++ * @param qstate: query state
++ * @param sq: subnet qstate
++ * @return false on failure.
++ */
++static int
++generate_sub_request(struct module_qstate *qstate, struct subnet_qstate* sq)
++{
++      struct module_qstate* subq = NULL;
++      uint16_t qflags = 0; /* OPCODE QUERY, no flags */
++      int prime = 0;
++      int valrec = 0;
++      struct query_info qinf;
++      qinf.qname = qstate->qinfo.qname;
++      qinf.qname_len = qstate->qinfo.qname_len;
++      qinf.qtype = qstate->qinfo.qtype;
++      qinf.qclass = qstate->qinfo.qclass;
++      qinf.local_alias = NULL;
++
++      qflags |= BIT_RD;
++      if((qstate->query_flags & BIT_CD)!=0) {
++              qflags |= BIT_CD;
++              valrec = 1;
++      }
++
++      fptr_ok(fptr_whitelist_modenv_attach_sub(qstate->env->attach_sub));
++      if(!(*qstate->env->attach_sub)(qstate, &qinf, qflags, prime, valrec,
++              &subq)) {
++              return 0;
++      }
++      if(subq) {
++              /* It is possible to access the subquery module state. */
++              if(sq->ecs_client_in.subnet_source_mask == 0 &&
++                      edns_opt_list_find(qstate->edns_opts_front_in,
++                              qstate->env->cfg->client_subnet_opcode)) {
++                      subq->no_cache_store = 1;
++              }
++      }
++      return 1;
++}
++
++/**
++ * Perform the query without subnet
++ * @param qstate: query state
++ * @param sq: subnet qstate
++ * @return module state
++ */
++static enum module_ext_state
++generate_lookup_without_subnet(struct module_qstate *qstate,
++      struct subnet_qstate* sq)
++{
++      verbose(VERB_ALGO, "subnetcache: make subquery to look up without 
subnet");
++      if(!generate_sub_request(qstate, sq)) {
++              verbose(VERB_ALGO, "Could not generate sub query");
++              qstate->return_rcode = LDNS_RCODE_FORMERR;
++              qstate->return_msg = NULL;
++              return module_finished;
++      }
++      sq->wait_subquery = 1;
++      return module_wait_subquery;
++}
++
+ static enum module_ext_state
+ eval_response(struct module_qstate *qstate, int id, struct subnet_qstate *sq)
+ {
+@@ -557,14 +622,7 @@ eval_response(struct module_qstate *qstate, int id, 
struct subnet_qstate *sq)
+                * is still useful to put it in the edns subnet cache for
+                * when a client explicitly asks for subnet specific answer. */
+               verbose(VERB_QUERY, "subnetcache: Authority indicates no 
support");
+-              if(!sq->started_no_cache_store) {
+-                      lock_rw_wrlock(&sne->biglock);
+-                      update_cache(qstate, id);
+-                      lock_rw_unlock(&sne->biglock);
+-              }
+-              if (sq->subnet_downstream)
+-                      cp_edns_bad_response(c_out, c_in);
+-              return module_finished;
++              return generate_lookup_without_subnet(qstate, sq);
+       }
+ 
+       /* Purposefully there was no sent subnet, and there is consequently
+@@ -589,14 +647,14 @@ eval_response(struct module_qstate *qstate, int id, 
struct subnet_qstate *sq)
+               !common_prefix(s_out->subnet_addr, s_in->subnet_addr, 
+                       s_out->subnet_source_mask))
+       {
+-              /* we can not accept, restart query without option */
++              /* we can not accept, perform query without option */
+               verbose(VERB_QUERY, "subnetcache: forged data");
+               s_out->subnet_validdata = 0;
+               (void)edns_opt_list_remove(&qstate->edns_opts_back_out,
+                       qstate->env->cfg->client_subnet_opcode);
+               sq->subnet_sent = 0;
+               sq->subnet_sent_no_subnet = 0;
+-              return module_restart_next;
++              return generate_lookup_without_subnet(qstate, sq);
+       }
+ 
+       lock_rw_wrlock(&sne->biglock);
+@@ -795,6 +853,9 @@ ecs_edns_back_parsed(struct module_qstate* qstate, int id,
+       } else if(sq->subnet_sent_no_subnet) {
+               /* The answer can be stored as scope 0, not in global cache. */
+               qstate->no_cache_store = 1;
++      } else if(sq->subnet_sent) {
++              /* Need another query to be able to store in global cache. */
++              qstate->no_cache_store = 1;
+       }
+ 
+       return 1;
+@@ -812,6 +873,32 @@ subnetmod_operate(struct module_qstate *qstate, enum 
module_ev event,
+               strmodulevent(event));
+       log_query_info(VERB_QUERY, "subnetcache operate: query", 
&qstate->qinfo);
+ 
++      if(sq && sq->wait_subquery_done) {
++              /* The subquery lookup returned. */
++              if(sq->ecs_client_in.subnet_source_mask == 0 &&
++                      edns_opt_list_find(qstate->edns_opts_front_in,
++                              qstate->env->cfg->client_subnet_opcode)) {
++                      if(!sq->started_no_cache_store &&
++                              qstate->return_msg) {
++                              lock_rw_wrlock(&sne->biglock);
++                              update_cache(qstate, id);
++                              lock_rw_unlock(&sne->biglock);
++                      }
++                      if (sq->subnet_downstream)
++                              cp_edns_bad_response(&sq->ecs_client_out,
++                                      &sq->ecs_client_in);
++                      /* It is a scope zero lookup, append edns subnet
++                       * option to the querier. */
++                      subnet_ecs_opt_list_append(&sq->ecs_client_out,
++                              &qstate->edns_opts_front_out, qstate,
++                              qstate->region);
++              }
++              sq->wait_subquery_done = 0;
++              qstate->ext_state[id] = module_finished;
++              qstate->no_cache_store = sq->started_no_cache_store;
++              qstate->no_cache_lookup = sq->started_no_cache_lookup;
++              return;
++      }
+       if((event == module_event_new || event == module_event_pass) &&
+               sq == NULL) {
+               struct edns_option* ecs_opt;
+@@ -822,6 +909,8 @@ subnetmod_operate(struct module_qstate *qstate, enum 
module_ev event,
+               }
+ 
+               sq = (struct subnet_qstate*)qstate->minfo[id];
++              if(sq->wait_subquery)
++                      return; /* Wait for that subquery to return */
+ 
+               if((ecs_opt = edns_opt_list_find(
+                       qstate->edns_opts_front_in,
+@@ -851,6 +940,14 @@ subnetmod_operate(struct module_qstate *qstate, enum 
module_ev event,
+                       /* No clients are interested in result or we could not
+                        * parse it, we don't do client subnet */
+                       sq->ecs_server_out.subnet_validdata = 0;
++                      if(edns_opt_list_find(qstate->edns_opts_front_in,
++                              qstate->env->cfg->client_subnet_opcode)) {
++                              /* aggregated this deaggregated state */
++                              qstate->ext_state[id] =
++                                      generate_lookup_without_subnet(
++                                      qstate, sq);
++                              return;
++                      }
+                       verbose(VERB_ALGO, "subnetcache: pass to next module");
+                       qstate->ext_state[id] = module_wait_module;
+                       return;
+@@ -891,6 +988,14 @@ subnetmod_operate(struct module_qstate *qstate, enum 
module_ev event,
+                       }
+                       lock_rw_unlock(&sne->biglock);
+               }
++              if(sq->ecs_client_in.subnet_source_mask == 0 &&
++                      edns_opt_list_find(qstate->edns_opts_front_in,
++                              qstate->env->cfg->client_subnet_opcode)) {
++                      /* client asked for resolution without edns subnet */
++                      qstate->ext_state[id] = generate_lookup_without_subnet(
++                              qstate, sq);
++                      return;
++              }
+               
+               sq->ecs_server_out.subnet_addr_fam =
+                       sq->ecs_client_in.subnet_addr_fam;
+@@ -927,6 +1032,8 @@ subnetmod_operate(struct module_qstate *qstate, enum 
module_ev event,
+               qstate->ext_state[id] = module_wait_module;
+               return;
+       }
++      if(sq && sq->wait_subquery)
++              return; /* Wait for that subquery to return */
+       /* Query handed back by next module, we have a 'final' answer */
+       if(sq && event == module_event_moddone) {
+               qstate->ext_state[id] = eval_response(qstate, id, sq);
+@@ -975,10 +1082,27 @@ subnetmod_clear(struct module_qstate 
*ATTR_UNUSED(qstate),
+ }
+ 
+ void
+-subnetmod_inform_super(struct module_qstate *ATTR_UNUSED(qstate),
+-      int ATTR_UNUSED(id), struct module_qstate *ATTR_UNUSED(super))
++subnetmod_inform_super(struct module_qstate *qstate, int id,
++      struct module_qstate *super)
+ {
+-      /* Not used */
++      struct subnet_qstate* super_sq =
++              (struct subnet_qstate*)super->minfo[id];
++      log_query_info(VERB_ALGO, "subnetcache inform_super: query",
++              &super->qinfo);
++      super_sq->wait_subquery = 0;
++      super_sq->wait_subquery_done = 1;
++      if(qstate->return_rcode != LDNS_RCODE_NOERROR ||
++              !qstate->return_msg) {
++              super->return_msg = NULL;
++              super->return_rcode = LDNS_RCODE_SERVFAIL;
++              return;
++      }
++      super->return_rcode = LDNS_RCODE_NOERROR;
++      super->return_msg = dns_copy_msg(qstate->return_msg, super->region);
++      if(!super->return_msg) {
++              log_err("subnetcache: copy response, out of memory");
++              super->return_rcode = LDNS_RCODE_SERVFAIL;
++      }
+ }
+ 
+ size_t
+diff --git a/edns-subnet/subnetmod.h b/edns-subnet/subnetmod.h
+index 1ff8a23ec..3893820fa 100644
+--- a/edns-subnet/subnetmod.h
++++ b/edns-subnet/subnetmod.h
+@@ -102,6 +102,10 @@ struct subnet_qstate {
+       int started_no_cache_store;
+       /** has the subnet module been started with no_cache_lookup? */
+       int started_no_cache_lookup;
++      /** Wait for subquery that has been started for nonsubnet lookup. */
++      int wait_subquery;
++      /** The subquery waited for is done. */
++      int wait_subquery_done;
+ };
+ 
+ void subnet_data_delete(void* d, void* ATTR_UNUSED(arg));
+-- 
+2.47.2
+
diff -Nru unbound-1.22.0/debian/patches/series 
unbound-1.22.0/debian/patches/series
--- unbound-1.22.0/debian/patches/series        2024-10-04 17:55:17.000000000 
+0300
+++ unbound-1.22.0/debian/patches/series        2025-07-17 19:02:37.000000000 
+0300
@@ -1 +1,2 @@
 do-not-chown-control-socket.patch
+Fix-RebirthDay-Attack-CVE-2025-5994.patch

Reply via email to