new file mode 100644
@@ -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));
@@ -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"
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