diff mbox series

[meta-oe,scarthgap] unbound: Fix CVE-2025-5994

Message ID 20251104095256.3402683-1-namanj1@kpit.com
State Accepted, archived
Delegated to: Anuj Mittal
Headers show
Series [meta-oe,scarthgap] unbound: Fix CVE-2025-5994 | expand

Commit Message

Naman Jain Nov. 4, 2025, 9:52 a.m. UTC
A multi-vendor cache poisoning vulnerability named 'Rebirthday Attack' has been
discovered in caching resolvers that support EDNS Client Subnet (ECS). Unbound is
also vulnerable when compiled with ECS support, i.e., '--enable-subnet', AND
configured to send ECS information along with queries to upstream name servers

CVE: CVE-2025-5994

Signed-off-by: Naman Jain <namanj1@kpit.com>
---
 .../unbound/unbound/CVE-2025-5994.patch       | 275 ++++++++++++++++++
 .../recipes-support/unbound/unbound_1.19.3.bb |   1 +
 2 files changed, 276 insertions(+)
 create mode 100644 meta-networking/recipes-support/unbound/unbound/CVE-2025-5994.patch

Comments

Naman Jain Jan. 28, 2026, 5:47 a.m. UTC | #1
Hello,
Can you please check this.

On Tue, 4 Nov, 2025, 3:23 pm Naman Jain, <nmjain23@gmail.com> wrote:

> A multi-vendor cache poisoning vulnerability named 'Rebirthday Attack' has
> been
> discovered in caching resolvers that support EDNS Client Subnet (ECS).
> Unbound is
> also vulnerable when compiled with ECS support, i.e., '--enable-subnet',
> AND
> configured to send ECS information along with queries to upstream name
> servers
>
> CVE: CVE-2025-5994
>
> Signed-off-by: Naman Jain <namanj1@kpit.com>
> ---
>  .../unbound/unbound/CVE-2025-5994.patch       | 275 ++++++++++++++++++
>  .../recipes-support/unbound/unbound_1.19.3.bb |   1 +
>  2 files changed, 276 insertions(+)
>  create mode 100644
> meta-networking/recipes-support/unbound/unbound/CVE-2025-5994.patch
>
> diff --git
> a/meta-networking/recipes-support/unbound/unbound/CVE-2025-5994.patch
> b/meta-networking/recipes-support/unbound/unbound/CVE-2025-5994.patch
> new file mode 100644
> index 0000000000..b6c0e37e43
> --- /dev/null
> +++ b/meta-networking/recipes-support/unbound/unbound/CVE-2025-5994.patch
> @@ -0,0 +1,275 @@
> +Backport of:
> +
> +From 5bf82f246481098a6473f296b21fc1229d276c0f Mon Sep 17 00:00:00 2001
> +From: "W.C.A. Wijngaards" <wouter@nlnetlabs.nl>
> +Date: Wed, 16 Jul 2025 10:02:01 +0200
> +Subject: [PATCH] - Fix RebirthDay Attack CVE-2025-5994, reported by Xiang
> Li
> + from AOSP   Lab Nankai University.
> +
> +CVE: CVE-2025-5994
> +Upstream-Status: Backport [
> https://github.com/NLnetLabs/unbound/commit/5bf82f246481098a6473f296b21fc1229d276c0f
> ]
> +Signed-off-by: Naman Jain <namanj1@kpit.com>
> +
> +---
> + edns-subnet/subnetmod.c | 152 ++++++++++++++++++++++++++++++++++++----
> + edns-subnet/subnetmod.h |   4 ++
> + 2 files changed, 142 insertions(+), 14 deletions(-)
> +
> +--- 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"
> +@@ -152,7 +153,8 @@ int ecs_whitelist_check(struct query_inf
> +
> +       /* Cache by default, might be disabled after parsing EDNS option
> +        * received from nameserver. */
> +-      if(!iter_stub_fwd_no_cache(qstate, &qstate->qinfo, NULL, NULL)) {
> ++      if(!iter_stub_fwd_no_cache(qstate, &qstate->qinfo, NULL, NULL)
> ++              && sq->ecs_client_in.subnet_validdata) {
> +               qstate->no_cache_store = 0;
> +       }
> +
> +@@ -504,6 +506,69 @@ common_prefix(uint8_t *a, uint8_t *b, ui
> +       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)
> + {
> +@@ -539,14 +604,7 @@ eval_response(struct module_qstate *qsta
> +                * 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
> +@@ -571,14 +629,14 @@ eval_response(struct module_qstate *qsta
> +               !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);
> +@@ -763,6 +821,9 @@ ecs_edns_back_parsed(struct module_qstat
> +       } 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;
> +@@ -780,6 +841,32 @@ subnetmod_operate(struct module_qstate *
> +               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;
> +@@ -790,6 +877,8 @@ subnetmod_operate(struct module_qstate *
> +               }
> +
> +               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,
> +@@ -819,6 +908,14 @@ subnetmod_operate(struct module_qstate *
> +                       /* 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;
> +@@ -859,6 +956,14 @@ subnetmod_operate(struct module_qstate *
> +                       }
> +                       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;
> +@@ -895,6 +1000,8 @@ subnetmod_operate(struct module_qstate *
> +               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);
> +@@ -943,10 +1050,27 @@ subnetmod_clear(struct module_qstate *AT
> + }
> +
> + 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
> +--- 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));
> diff --git a/meta-networking/recipes-support/unbound/unbound_1.19.3.bb
> b/meta-networking/recipes-support/unbound/unbound_1.19.3.bb
> index 6f54038c6c..6c04ed5840 100644
> --- a/meta-networking/recipes-support/unbound/unbound_1.19.3.bb
> +++ b/meta-networking/recipes-support/unbound/unbound_1.19.3.bb
> @@ -11,6 +11,7 @@ LIC_FILES_CHKSUM =
> "file://LICENSE;md5=5308494bc0590c0cb036afd781d78f06"
>
>  SRC_URI = "git://
> github.com/NLnetLabs/unbound.git;protocol=https;branch=branch-1.19.3 \
>             file://CVE-2024-8508.patch \
> +           file://CVE-2025-5994.patch \
>             "
>  SRCREV = "48b6c60a24e9a5d6d369a7a37c9fe2a767f26abd"
>
> --
> 2.34.1
>
>
Gyorgy Sarvari Jan. 28, 2026, 8:15 a.m. UTC | #2
On 1/28/26 06:47, Naman Jain via lists.openembedded.org wrote:
> Hello, 
> Can you please check this.
>

This has been merged not so long ago:
https://git.openembedded.org/meta-openembedded/commit/?h=scarthgap&id=30dafc39583af496a300155620efd0275c79f25b

> On Tue, 4 Nov, 2025, 3:23 pm Naman Jain, <nmjain23@gmail.com> wrote:
>
>     A multi-vendor cache poisoning vulnerability named 'Rebirthday
>     Attack' has been
>     discovered in caching resolvers that support EDNS Client Subnet
>     (ECS). Unbound is
>     also vulnerable when compiled with ECS support, i.e.,
>     '--enable-subnet', AND
>     configured to send ECS information along with queries to upstream
>     name servers
>
>     CVE: CVE-2025-5994
>
>     Signed-off-by: Naman Jain <namanj1@kpit.com>
>     ---
>      .../unbound/unbound/CVE-2025-5994.patch       | 275
>     ++++++++++++++++++
>      .../recipes-support/unbound/unbound_1.19.3.bb
>     <http://unbound_1.19.3.bb> |   1 +
>      2 files changed, 276 insertions(+)
>      create mode 100644
>     meta-networking/recipes-support/unbound/unbound/CVE-2025-5994.patch
>
>     diff --git
>     a/meta-networking/recipes-support/unbound/unbound/CVE-2025-5994.patch
>     b/meta-networking/recipes-support/unbound/unbound/CVE-2025-5994.patch
>     new file mode 100644
>     index 0000000000..b6c0e37e43
>     --- /dev/null
>     +++
>     b/meta-networking/recipes-support/unbound/unbound/CVE-2025-5994.patch
>     @@ -0,0 +1,275 @@
>     +Backport of:
>     +
>     +From 5bf82f246481098a6473f296b21fc1229d276c0f Mon Sep 17 00:00:00
>     2001
>     +From: "W.C.A. Wijngaards" <wouter@nlnetlabs.nl>
>     +Date: Wed, 16 Jul 2025 10:02:01 +0200
>     +Subject: [PATCH] - Fix RebirthDay Attack CVE-2025-5994, reported
>     by Xiang Li
>     + from AOSP   Lab Nankai University.
>     +
>     +CVE: CVE-2025-5994
>     +Upstream-Status: Backport
>     [https://github.com/NLnetLabs/unbound/commit/5bf82f246481098a6473f296b21fc1229d276c0f]
>     +Signed-off-by: Naman Jain <namanj1@kpit.com>
>     +
>     +---
>     + edns-subnet/subnetmod.c | 152
>     ++++++++++++++++++++++++++++++++++++----
>     + edns-subnet/subnetmod.h |   4 ++
>     + 2 files changed, 142 insertions(+), 14 deletions(-)
>     +
>     +--- 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"
>     +@@ -152,7 +153,8 @@ int ecs_whitelist_check(struct query_inf
>     +
>     +       /* Cache by default, might be disabled after parsing EDNS
>     option
>     +        * received from nameserver. */
>     +-      if(!iter_stub_fwd_no_cache(qstate, &qstate->qinfo, NULL,
>     NULL)) {
>     ++      if(!iter_stub_fwd_no_cache(qstate, &qstate->qinfo, NULL, NULL)
>     ++              && sq->ecs_client_in.subnet_validdata) {
>     +               qstate->no_cache_store = 0;
>     +       }
>     +
>     +@@ -504,6 +506,69 @@ common_prefix(uint8_t *a, uint8_t *b, ui
>     +       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)
>     + {
>     +@@ -539,14 +604,7 @@ eval_response(struct module_qstate *qsta
>     +                * 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
>     +@@ -571,14 +629,14 @@ eval_response(struct module_qstate *qsta
>     +               !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);
>     +@@ -763,6 +821,9 @@ ecs_edns_back_parsed(struct module_qstat
>     +       } 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;
>     +@@ -780,6 +841,32 @@ subnetmod_operate(struct module_qstate *
>     +               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;
>     +@@ -790,6 +877,8 @@ subnetmod_operate(struct module_qstate *
>     +               }
>     +
>     +               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,
>     +@@ -819,6 +908,14 @@ subnetmod_operate(struct module_qstate *
>     +                       /* 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;
>     +@@ -859,6 +956,14 @@ subnetmod_operate(struct module_qstate *
>     +                       }
>     +                       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;
>     +@@ -895,6 +1000,8 @@ subnetmod_operate(struct module_qstate *
>     +               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);
>     +@@ -943,10 +1050,27 @@ subnetmod_clear(struct module_qstate *AT
>     + }
>     +
>     + 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
>     +--- 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));
>     diff --git
>     a/meta-networking/recipes-support/unbound/unbound_1.19.3.bb
>     <http://unbound_1.19.3.bb>
>     b/meta-networking/recipes-support/unbound/unbound_1.19.3.bb
>     <http://unbound_1.19.3.bb>
>     index 6f54038c6c..6c04ed5840 100644
>     --- a/meta-networking/recipes-support/unbound/unbound_1.19.3.bb
>     <http://unbound_1.19.3.bb>
>     +++ b/meta-networking/recipes-support/unbound/unbound_1.19.3.bb
>     <http://unbound_1.19.3.bb>
>     @@ -11,6 +11,7 @@ LIC_FILES_CHKSUM =
>     "file://LICENSE;md5=5308494bc0590c0cb036afd781d78f06"
>
>      SRC_URI =
>     "git://github.com/NLnetLabs/unbound.git;protocol=https;branch=branch-1.19.3
>     <http://github.com/NLnetLabs/unbound.git;protocol=https;branch=branch-1.19.3>
>     \
>                 file://CVE-2024-8508.patch \
>     +           file://CVE-2025-5994.patch \
>                 "
>      SRCREV = "48b6c60a24e9a5d6d369a7a37c9fe2a767f26abd"
>
>     -- 
>     2.34.1
>
>
> -=-=-=-=-=-=-=-=-=-=-=-
> Links: You receive all messages sent to this group.
> View/Reply Online (#123971): https://lists.openembedded.org/g/openembedded-devel/message/123971
> Mute This Topic: https://lists.openembedded.org/mt/116115060/6084445
> Group Owner: openembedded-devel+owner@lists.openembedded.org
> Unsubscribe: https://lists.openembedded.org/g/openembedded-devel/unsub [skandigraun@gmail.com]
> -=-=-=-=-=-=-=-=-=-=-=-
>
diff mbox series

Patch

diff --git a/meta-networking/recipes-support/unbound/unbound/CVE-2025-5994.patch b/meta-networking/recipes-support/unbound/unbound/CVE-2025-5994.patch
new file mode 100644
index 0000000000..b6c0e37e43
--- /dev/null
+++ b/meta-networking/recipes-support/unbound/unbound/CVE-2025-5994.patch
@@ -0,0 +1,275 @@ 
+Backport of:
+
+From 5bf82f246481098a6473f296b21fc1229d276c0f Mon Sep 17 00:00:00 2001
+From: "W.C.A. Wijngaards" <wouter@nlnetlabs.nl>
+Date: Wed, 16 Jul 2025 10:02:01 +0200
+Subject: [PATCH] - Fix RebirthDay Attack CVE-2025-5994, reported by Xiang Li
+ from AOSP   Lab Nankai University.
+
+CVE: CVE-2025-5994
+Upstream-Status: Backport [https://github.com/NLnetLabs/unbound/commit/5bf82f246481098a6473f296b21fc1229d276c0f]
+Signed-off-by: Naman Jain <namanj1@kpit.com> 
+
+---
+ edns-subnet/subnetmod.c | 152 ++++++++++++++++++++++++++++++++++++----
+ edns-subnet/subnetmod.h |   4 ++
+ 2 files changed, 142 insertions(+), 14 deletions(-)
+
+--- 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"
+@@ -152,7 +153,8 @@ int ecs_whitelist_check(struct query_inf
+ 
+ 	/* Cache by default, might be disabled after parsing EDNS option
+ 	 * received from nameserver. */
+-	if(!iter_stub_fwd_no_cache(qstate, &qstate->qinfo, NULL, NULL)) {
++	if(!iter_stub_fwd_no_cache(qstate, &qstate->qinfo, NULL, NULL)
++		&& sq->ecs_client_in.subnet_validdata) {
+ 		qstate->no_cache_store = 0;
+ 	}
+ 
+@@ -504,6 +506,69 @@ common_prefix(uint8_t *a, uint8_t *b, ui
+ 	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)
+ {
+@@ -539,14 +604,7 @@ eval_response(struct module_qstate *qsta
+ 		 * 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
+@@ -571,14 +629,14 @@ eval_response(struct module_qstate *qsta
+ 		!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);
+@@ -763,6 +821,9 @@ ecs_edns_back_parsed(struct module_qstat
+ 	} 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;
+@@ -780,6 +841,32 @@ subnetmod_operate(struct module_qstate *
+ 		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;
+@@ -790,6 +877,8 @@ subnetmod_operate(struct module_qstate *
+ 		}
+ 
+ 		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,
+@@ -819,6 +908,14 @@ subnetmod_operate(struct module_qstate *
+ 			/* 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;
+@@ -859,6 +956,14 @@ subnetmod_operate(struct module_qstate *
+ 			}
+ 			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;
+@@ -895,6 +1000,8 @@ subnetmod_operate(struct module_qstate *
+ 		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);
+@@ -943,10 +1050,27 @@ subnetmod_clear(struct module_qstate *AT
+ }
+ 
+ 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
+--- 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));
diff --git a/meta-networking/recipes-support/unbound/unbound_1.19.3.bb b/meta-networking/recipes-support/unbound/unbound_1.19.3.bb
index 6f54038c6c..6c04ed5840 100644
--- a/meta-networking/recipes-support/unbound/unbound_1.19.3.bb
+++ b/meta-networking/recipes-support/unbound/unbound_1.19.3.bb
@@ -11,6 +11,7 @@  LIC_FILES_CHKSUM = "file://LICENSE;md5=5308494bc0590c0cb036afd781d78f06"
 
 SRC_URI = "git://github.com/NLnetLabs/unbound.git;protocol=https;branch=branch-1.19.3 \
            file://CVE-2024-8508.patch \
+           file://CVE-2025-5994.patch \
            "
 SRCREV = "48b6c60a24e9a5d6d369a7a37c9fe2a767f26abd"