]> git.ipfire.org Git - thirdparty/unbound.git/commitdiff
- Fix RebirthDay Attack CVE-2025-5994, reported by Xiang Li from AOSP branch-1.23.1 release-1.23.1
authorW.C.A. Wijngaards <wouter@nlnetlabs.nl>
Wed, 16 Jul 2025 08:02:01 +0000 (10:02 +0200)
committerW.C.A. Wijngaards <wouter@nlnetlabs.nl>
Wed, 16 Jul 2025 08:02:01 +0000 (10:02 +0200)
  Lab Nankai University.

edns-subnet/subnetmod.c
edns-subnet/subnetmod.h

index ead720f340063d655e2e28e642bb97f6dba02557..c5e215b8b6848547489f9629ddb85196cae4b1cd 100644 (file)
@@ -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
index 1ff8a23ecdbaee80d1bbd4252f60c73b0c8fd4ec..3893820fabaf31c69892f2146cf0e8fe589ae576 100644 (file)
@@ -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));