diff -Nru unbound-1.6.7/debian/changelog unbound-1.6.7/debian/changelog --- unbound-1.6.7/debian/changelog 2021-05-05 11:38:50.000000000 +0000 +++ unbound-1.6.7/debian/changelog 2022-08-04 11:56:04.000000000 +0000 @@ -1,3 +1,29 @@ +unbound (1.6.7-1ubuntu2.5) bionic-security; urgency=medium + + * SECURITY UPDATE: Ghost domain names issues + - debian/patches/CVE-2022-3069x-pre1.patch: fix that cachedb could + return a partial CNAME chain in cachedb/cachedb.c, + iterator/iterator.c, services/cache/dns.c, services/cache/dns.h. + - debian/patches/CVE-2022-3069x-pre2.patch: backport a version of the + iter_stub_fwd_no_cache function in iterator/iter_utils.c, + iterator/iter_utils.h. + - debian/patches/CVE-2022-3069x-pre3.patch: fix that nxdomain synthesis + does not happen above the stub or forward definition in + cachedb/cachedb.c, iterator/iter_utils.c, iterator/iter_utils.h, + iterator/iterator.c, services/cache/dns.c, services/cache/dns.h. + - debian/patches/CVE-2022-3069x.patch: fix the novel ghost domain + issues in cachedb/cachedb.c, daemon/cachedump.c, daemon/worker.c, + dns64/dns64.c, ipsecmod/ipsecmod.c, iterator/iter_utils.c, + iterator/iter_utils.h, iterator/iterator.c, pythonmod/interface.i, + pythonmod/pythonmod_utils.c, services/cache/dns.c, + services/cache/dns.h, services/mesh.c, + testdata/iter_prefetch_change.rpl, util/module.h, + validator/validator.c. + - CVE-2022-30698 + - CVE-2022-30699 + + -- Marc Deslauriers Thu, 04 Aug 2022 07:56:04 -0400 + unbound (1.6.7-1ubuntu2.4) bionic-security; urgency=medium * SECURITY UPDATE: configuration injection via MITM diff -Nru unbound-1.6.7/debian/patches/CVE-2022-3069x.patch unbound-1.6.7/debian/patches/CVE-2022-3069x.patch --- unbound-1.6.7/debian/patches/CVE-2022-3069x.patch 1970-01-01 00:00:00.000000000 +0000 +++ unbound-1.6.7/debian/patches/CVE-2022-3069x.patch 2022-08-03 17:34:24.000000000 +0000 @@ -0,0 +1,583 @@ +Backport of: + +From f6753a0f1018133df552347a199e0362fc1dac68 Mon Sep 17 00:00:00 2001 +From: "W.C.A. Wijngaards" +Date: Mon, 1 Aug 2022 13:24:40 +0200 +Subject: [PATCH] - Fix the novel ghost domain issues CVE-2022-30698 and + CVE-2022-30699. + +--- + cachedb/cachedb.c | 2 +- + daemon/cachedump.c | 5 +- + daemon/worker.c | 2 +- + dns64/dns64.c | 4 +- + doc/Changelog | 3 + + ipsecmod/ipsecmod.c | 2 +- + iterator/iter_utils.c | 4 +- + iterator/iter_utils.h | 3 +- + iterator/iterator.c | 19 +++-- + pythonmod/interface.i | 5 +- + pythonmod/pythonmod_utils.c | 3 +- + services/cache/dns.c | 111 ++++++++++++++++++++++++++---- + services/cache/dns.h | 18 ++++- + services/mesh.c | 1 + + testdata/iter_prefetch_change.rpl | 16 ++--- + util/module.h | 6 ++ + validator/validator.c | 4 +- + 17 files changed, 160 insertions(+), 48 deletions(-) + +--- a/cachedb/cachedb.c ++++ b/cachedb/cachedb.c +@@ -607,7 +607,7 @@ cachedb_intcache_store(struct module_qst + return; + (void)dns_cache_store(qstate->env, &qstate->qinfo, + qstate->return_msg->rep, 0, qstate->prefetch_leeway, 0, +- qstate->region, store_flags); ++ qstate->region, store_flags, qstate->qstarttime); + } + + /** +--- a/daemon/cachedump.c ++++ b/daemon/cachedump.c +@@ -675,7 +675,8 @@ load_msg(SSL* ssl, sldns_buffer* buf, st + if(!go_on) + return 1; /* skip this one, not all references satisfied */ + +- if(!dns_cache_store(&worker->env, &qinf, &rep, 0, 0, 0, NULL, flags)) { ++ if(!dns_cache_store(&worker->env, &qinf, &rep, 0, 0, 0, NULL, flags, ++ *worker->env.now)) { + log_warn("error out of memory"); + return 0; + } +@@ -846,7 +847,7 @@ int print_deleg_lookup(SSL* ssl, struct + while(1) { + dp = dns_cache_find_delegation(&worker->env, nm, nmlen, + qinfo.qtype, qinfo.qclass, region, &msg, +- *worker->env.now); ++ *worker->env.now, 0, NULL, 0); + if(!dp) { + return ssl_printf(ssl, "no delegation from " + "cache; goes to configured roots\n"); +--- a/daemon/worker.c ++++ b/daemon/worker.c +@@ -483,7 +483,7 @@ answer_norec_from_cache(struct worker* w + + dp = dns_cache_find_delegation(&worker->env, qinfo->qname, + qinfo->qname_len, qinfo->qtype, qinfo->qclass, +- worker->scratchpad, &msg, timenow); ++ worker->scratchpad, &msg, timenow, 0, NULL, 0); + if(!dp) { /* no delegation, need to reprime */ + return 0; + } +--- a/dns64/dns64.c ++++ b/dns64/dns64.c +@@ -807,7 +807,7 @@ dns64_inform_super(struct module_qstate* + /* Store the generated response in cache. */ + if (!super->no_cache_store && + !dns_cache_store(super->env, &super->qinfo, super->return_msg->rep, +- 0, 0, 0, NULL, super->query_flags)) ++ 0, 0, 0, NULL, super->query_flags, qstate->qstarttime)) + log_err("out of memory"); + } + +--- a/ipsecmod/ipsecmod.c ++++ b/ipsecmod/ipsecmod.c +@@ -348,7 +348,7 @@ ipsecmod_handle_query(struct module_qsta + /* Store A/AAAA in cache. */ + if(!dns_cache_store(qstate->env, &qstate->qinfo, + qstate->return_msg->rep, 0, qstate->prefetch_leeway, +- 0, qstate->region, qstate->query_flags)) { ++ 0, qstate->region, qstate->query_flags, qstate->qstarttime)) { + log_err("ipsecmod: out of memory caching record"); + } + qstate->ext_state[id] = module_finished; +--- a/iterator/iter_utils.c ++++ b/iterator/iter_utils.c +@@ -503,10 +503,10 @@ dns_copy_msg(struct dns_msg* from, struc + void + iter_dns_store(struct module_env* env, struct query_info* msgqinf, + struct reply_info* msgrep, int is_referral, time_t leeway, int pside, +- struct regional* region, uint16_t flags) ++ struct regional* region, uint16_t flags, time_t qstarttime) + { + if(!dns_cache_store(env, msgqinf, msgrep, is_referral, leeway, +- pside, region, flags)) ++ pside, region, flags, qstarttime)) + log_err("out of memory: cannot store data in cache"); + } + +--- a/iterator/iter_utils.h ++++ b/iterator/iter_utils.h +@@ -125,6 +125,7 @@ struct dns_msg* dns_copy_msg(struct dns_ + * can be prefetch-updates. + * @param region: to copy modified (cache is better) rrs back to. + * @param flags: with BIT_CD for dns64 AAAA translated queries. ++ * @param qstarttime: time of query start. + * @return void, because we are not interested in alloc errors, + * the iterator and validator can operate on the results in their + * scratch space (the qstate.region) and are not dependent on the cache. +@@ -133,7 +134,7 @@ struct dns_msg* dns_copy_msg(struct dns_ + */ + void iter_dns_store(struct module_env* env, struct query_info* qinf, + struct reply_info* rep, int is_referral, time_t leeway, int pside, +- struct regional* region, uint16_t flags); ++ struct regional* region, uint16_t flags, time_t qstarttime); + + /** + * Select randomly with n/m probability. +--- a/iterator/iterator.c ++++ b/iterator/iterator.c +@@ -329,7 +329,7 @@ error_response_cache(struct module_qstat + err.security = sec_status_indeterminate; + verbose(VERB_ALGO, "store error response in message cache"); + iter_dns_store(qstate->env, &qstate->qinfo, &err, 0, 0, 0, NULL, +- qstate->query_flags); ++ qstate->query_flags, qstate->qstarttime); + } + return error_response(qstate, id, rcode); + } +@@ -1253,7 +1253,8 @@ processInitRequest(struct module_qstate* + iq->dp = dns_cache_find_delegation(qstate->env, delname, + delnamelen, iq->qchase.qtype, iq->qchase.qclass, + qstate->region, &iq->deleg_msg, +- *qstate->env->now+qstate->prefetch_leeway); ++ *qstate->env->now+qstate->prefetch_leeway, 1, ++ dpname, dpnamelen); + else iq->dp = NULL; + + /* If the cache has returned nothing, then we have a +@@ -1520,7 +1521,8 @@ generate_parentside_target_query(struct + subiq->dp = dns_cache_find_delegation(qstate->env, + name, namelen, qtype, qclass, subq->region, + &subiq->deleg_msg, +- *qstate->env->now+subq->prefetch_leeway); ++ *qstate->env->now+subq->prefetch_leeway, ++ 1, NULL, 0); + /* if no dp, then it's from root, refetch unneeded */ + if(subiq->dp) { + subiq->dnssec_expected = iter_indicates_dnssec( +@@ -2455,7 +2457,8 @@ processQueryResponse(struct module_qstat + iter_dns_store(qstate->env, &iq->response->qinfo, + iq->response->rep, 0, qstate->prefetch_leeway, + iq->dp&&iq->dp->has_parent_side_NS, +- qstate->region, qstate->query_flags); ++ qstate->region, qstate->query_flags, ++ qstate->qstarttime); + /* close down outstanding requests to be discarded */ + outbound_list_clear(&iq->outlist); + iq->num_current_queries = 0; +@@ -2544,7 +2547,8 @@ processQueryResponse(struct module_qstat + /* Store the referral under the current query */ + /* no prefetch-leeway, since its not the answer */ + iter_dns_store(qstate->env, &iq->response->qinfo, +- iq->response->rep, 1, 0, 0, NULL, 0); ++ iq->response->rep, 1, 0, 0, NULL, 0, ++ qstate->qstarttime); + if(iq->store_parent_NS) + iter_store_parentside_NS(qstate->env, + iq->response->rep); +@@ -2649,7 +2653,7 @@ processQueryResponse(struct module_qstat + iter_dns_store(qstate->env, &iq->response->qinfo, + iq->response->rep, 1, qstate->prefetch_leeway, + iq->dp&&iq->dp->has_parent_side_NS, NULL, +- qstate->query_flags); ++ qstate->query_flags, qstate->qstarttime); + /* set the current request's qname to the new value. */ + iq->qchase.qname = sname; + iq->qchase.qname_len = snamelen; +@@ -3186,7 +3190,8 @@ processFinished(struct module_qstate* qs + iter_dns_store(qstate->env, &qstate->qinfo, + iq->response->rep, 0, qstate->prefetch_leeway, + iq->dp&&iq->dp->has_parent_side_NS, +- qstate->region, qstate->query_flags); ++ qstate->region, qstate->query_flags, ++ qstate->qstarttime); + } + } + qstate->return_rcode = LDNS_RCODE_NOERROR; +--- a/pythonmod/interface.i ++++ b/pythonmod/interface.i +@@ -1221,7 +1221,8 @@ int set_return_msg(struct module_qstate* + /* Functions which we will need to lookup delegations */ + struct delegpt* dns_cache_find_delegation(struct module_env* env, + uint8_t* qname, size_t qnamelen, uint16_t qtype, uint16_t qclass, +- struct regional* region, struct dns_msg** msg, uint32_t timenow); ++ struct regional* region, struct dns_msg** msg, uint32_t timenow, ++ int noexpiredabove, uint8_t* expiretop, size_t expiretoplen); + int iter_dp_is_useless(struct query_info* qinfo, uint16_t qflags, + struct delegpt* dp); + struct iter_hints_stub* hints_lookup_stub(struct iter_hints* hints, +@@ -1250,7 +1251,7 @@ struct delegpt* find_delegation(struct m + qinfo.qclass = LDNS_RR_CLASS_IN; + + while(1) { +- dp = dns_cache_find_delegation(qstate->env, (uint8_t*)nm, nmlen, qinfo.qtype, qinfo.qclass, region, &msg, timenow); ++ dp = dns_cache_find_delegation(qstate->env, (uint8_t*)nm, nmlen, qinfo.qtype, qinfo.qclass, region, &msg, timenow, 0, NULL, 0); + if(!dp) + return NULL; + if(iter_dp_is_useless(&qinfo, BIT_RD, dp)) { +--- a/pythonmod/pythonmod_utils.c ++++ b/pythonmod/pythonmod_utils.c +@@ -68,7 +68,8 @@ int storeQueryInCache(struct module_qsta + } + + return dns_cache_store(qstate->env, qinfo, msgrep, is_referral, +- qstate->prefetch_leeway, 0, NULL, qstate->query_flags); ++ qstate->prefetch_leeway, 0, NULL, qstate->query_flags, ++ qstate->qstarttime); + } + + /* Invalidate the message associated with query_info stored in message cache */ +--- a/services/cache/dns.c ++++ b/services/cache/dns.c +@@ -66,11 +66,16 @@ + * in a prefetch situation to be updated (without becoming sticky). + * @param qrep: update rrsets here if cache is better + * @param region: for qrep allocs. ++ * @param qstarttime: time when delegations were looked up, this is perhaps ++ * earlier than the time in now. The time is used to determine if RRsets ++ * of type NS have expired, so that they can only be updated using ++ * lookups of delegation points that did not use them, since they had ++ * expired then. + */ + static void + store_rrsets(struct module_env* env, struct reply_info* rep, time_t now, + time_t leeway, int pside, struct reply_info* qrep, +- struct regional* region) ++ struct regional* region, time_t qstarttime) + { + size_t i; + /* see if rrset already exists in cache, if not insert it. */ +@@ -79,8 +84,8 @@ store_rrsets(struct module_env* env, str + rep->ref[i].id = rep->rrsets[i]->id; + /* update ref if it was in the cache */ + switch(rrset_cache_update(env->rrset_cache, &rep->ref[i], +- env->alloc, now + ((ntohs(rep->ref[i].key->rk.type)== +- LDNS_RR_TYPE_NS && !pside)?0:leeway))) { ++ env->alloc, ((ntohs(rep->ref[i].key->rk.type)== ++ LDNS_RR_TYPE_NS && !pside)?qstarttime:now + leeway))) { + case 0: /* ref unchanged, item inserted */ + break; + case 2: /* ref updated, cache is superior */ +@@ -111,7 +116,8 @@ store_rrsets(struct module_env* env, str + void + dns_cache_store_msg(struct module_env* env, struct query_info* qinfo, + hashvalue_type hash, struct reply_info* rep, time_t leeway, int pside, +- struct reply_info* qrep, uint32_t flags, struct regional* region) ++ struct reply_info* qrep, uint32_t flags, struct regional* region, ++ time_t qstarttime) + { + struct msgreply_entry* e; + time_t ttl = rep->ttl; +@@ -126,7 +132,8 @@ dns_cache_store_msg(struct module_env* e + /* there was a reply_info_sortref(rep) here but it seems to be + * unnecessary, because the cache gets locked per rrset. */ + reply_info_set_ttls(rep, *env->now); +- store_rrsets(env, rep, *env->now, leeway, pside, qrep, region); ++ store_rrsets(env, rep, *env->now, leeway, pside, qrep, region, ++ qstarttime); + if(ttl == 0 && !(flags & DNSCACHE_STORE_ZEROTTL)) { + /* we do not store the message, but we did store the RRs, + * which could be useful for delegation information */ +@@ -144,10 +151,51 @@ dns_cache_store_msg(struct module_env* e + slabhash_insert(env->msg_cache, hash, &e->entry, rep, env->alloc); + } + ++/** see if an rrset is expired above the qname, return upper qname. */ ++static int ++rrset_expired_above(struct module_env* env, uint8_t** qname, size_t* qnamelen, ++ uint16_t searchtype, uint16_t qclass, time_t now, uint8_t* expiretop, ++ size_t expiretoplen) ++{ ++ struct ub_packed_rrset_key *rrset; ++ uint8_t lablen; ++ ++ while(*qnamelen > 0) { ++ /* look one label higher */ ++ lablen = **qname; ++ *qname += lablen + 1; ++ *qnamelen -= lablen + 1; ++ if(*qnamelen <= 0) ++ break; ++ ++ /* looks up with a time of 0, to see expired entries */ ++ if((rrset = rrset_cache_lookup(env->rrset_cache, *qname, ++ *qnamelen, searchtype, qclass, 0, 0, 0))) { ++ struct packed_rrset_data* data = ++ (struct packed_rrset_data*)rrset->entry.data; ++ if(now > data->ttl) { ++ /* it is expired, this is not wanted */ ++ lock_rw_unlock(&rrset->entry.lock); ++ log_nametypeclass(VERB_ALGO, "this rrset is expired", *qname, searchtype, qclass); ++ return 1; ++ } ++ /* it is not expired, continue looking */ ++ lock_rw_unlock(&rrset->entry.lock); ++ } ++ ++ /* do not look above the expiretop. */ ++ if(expiretop && *qnamelen == expiretoplen && ++ query_dname_compare(*qname, expiretop)==0) ++ break; ++ } ++ return 0; ++} ++ + /** find closest NS or DNAME and returns the rrset (locked) */ + static struct ub_packed_rrset_key* + find_closest_of_type(struct module_env* env, uint8_t* qname, size_t qnamelen, +- uint16_t qclass, time_t now, uint16_t searchtype, int stripfront) ++ uint16_t qclass, time_t now, uint16_t searchtype, int stripfront, ++ int noexpiredabove, uint8_t* expiretop, size_t expiretoplen) + { + struct ub_packed_rrset_key *rrset; + uint8_t lablen; +@@ -162,8 +210,40 @@ find_closest_of_type(struct module_env* + /* snip off front part of qname until the type is found */ + while(qnamelen > 0) { + if((rrset = rrset_cache_lookup(env->rrset_cache, qname, +- qnamelen, searchtype, qclass, 0, now, 0))) +- return rrset; ++ qnamelen, searchtype, qclass, 0, now, 0))) { ++ uint8_t* origqname = qname; ++ size_t origqnamelen = qnamelen; ++ if(!noexpiredabove) ++ return rrset; ++ /* if expiretop set, do not look above it, but ++ * qname is equal, so the just found result is also ++ * the nonexpired above part. */ ++ if(expiretop && qnamelen == expiretoplen && ++ query_dname_compare(qname, expiretop)==0) ++ return rrset; ++ /* check for expiry, but we have to let go of the rrset ++ * for the lock ordering */ ++ lock_rw_unlock(&rrset->entry.lock); ++ /* the expired_above function always takes off one ++ * label (if qnamelen>0) and returns the final qname ++ * where it searched, so we can continue from there ++ * turning the O N*N search into O N. */ ++ if(!rrset_expired_above(env, &qname, &qnamelen, ++ searchtype, qclass, now, expiretop, ++ expiretoplen)) { ++ /* we want to return rrset, but it may be ++ * gone from cache, if so, just loop like ++ * it was not in the cache in the first place. ++ */ ++ if((rrset = rrset_cache_lookup(env-> ++ rrset_cache, origqname, origqnamelen, ++ searchtype, qclass, 0, now, 0))) { ++ return rrset; ++ } ++ } ++ log_nametypeclass(VERB_ALGO, "ignoring rrset because expired rrsets exist above it", origqname, searchtype, qclass); ++ continue; ++ } + + /* snip off front label */ + lablen = *qname; +@@ -412,7 +492,8 @@ dns_msg_ansadd(struct dns_msg* msg, stru + struct delegpt* + dns_cache_find_delegation(struct module_env* env, uint8_t* qname, + size_t qnamelen, uint16_t qtype, uint16_t qclass, +- struct regional* region, struct dns_msg** msg, time_t now) ++ struct regional* region, struct dns_msg** msg, time_t now, ++ int noexpiredabove, uint8_t* expiretop, size_t expiretoplen) + { + /* try to find closest NS rrset */ + struct ub_packed_rrset_key* nskey; +@@ -420,7 +501,7 @@ dns_cache_find_delegation(struct module_ + struct delegpt* dp; + + nskey = find_closest_of_type(env, qname, qnamelen, qclass, now, +- LDNS_RR_TYPE_NS, 0); ++ LDNS_RR_TYPE_NS, 0, noexpiredabove, expiretop, expiretoplen); + if(!nskey) /* hope the caller has hints to prime or something */ + return NULL; + nsdata = (struct packed_rrset_data*)nskey->entry.data; +@@ -748,7 +829,7 @@ dns_cache_lookup(struct module_env* env, + * consistent with the DNAME */ + if(!no_partial && + (rrset=find_closest_of_type(env, qname, qnamelen, qclass, now, +- LDNS_RR_TYPE_DNAME, 1))) { ++ LDNS_RR_TYPE_DNAME, 1, 0, NULL, 0))) { + /* synthesize a DNAME+CNAME message based on this */ + struct dns_msg* msg = synth_dname_msg(rrset, region, now, &k); + if(msg) { +@@ -852,7 +933,7 @@ dns_cache_lookup(struct module_env* env, + int + dns_cache_store(struct module_env* env, struct query_info* msgqinf, + struct reply_info* msgrep, int is_referral, time_t leeway, int pside, +- struct regional* region, uint32_t flags) ++ struct regional* region, uint32_t flags, time_t qstarttime) + { + struct reply_info* rep = NULL; + /* alloc, malloc properly (not in region, like msg is) */ +@@ -875,9 +956,9 @@ dns_cache_store(struct module_env* env, + /*ignore ret: it was in the cache, ref updated */ + /* no leeway for typeNS */ + (void)rrset_cache_update(env->rrset_cache, &ref, +- env->alloc, *env->now + ++ env->alloc, + ((ntohs(ref.key->rk.type)==LDNS_RR_TYPE_NS +- && !pside) ? 0:leeway)); ++ && !pside) ? qstarttime:*env->now + leeway)); + } + free(rep); + return 1; +@@ -899,7 +980,7 @@ dns_cache_store(struct module_env* env, + rep->flags &= ~(BIT_AA | BIT_CD); + h = query_info_hash(&qinf, (uint16_t)flags); + dns_cache_store_msg(env, &qinf, h, rep, leeway, pside, msgrep, +- flags, region); ++ flags, region, qstarttime); + /* qname is used inside query_info_entrysetup, and set to + * NULL. If it has not been used, free it. free(0) is safe. */ + free(qinf.qname); +--- a/services/cache/dns.h ++++ b/services/cache/dns.h +@@ -88,11 +88,13 @@ struct dns_msg { + * @param flags: flags with BIT_CD for AAAA queries in dns64 translation. + * The higher 16 bits are used internally to customize the cache policy. + * (See DNSCACHE_STORE_xxx flags). ++ * @param qstarttime: time when the query was started, and thus when the ++ * delegations were looked up. + * @return 0 on alloc error (out of memory). + */ + int dns_cache_store(struct module_env* env, struct query_info* qinf, + struct reply_info* rep, int is_referral, time_t leeway, int pside, +- struct regional* region, uint32_t flags); ++ struct regional* region, uint32_t flags, time_t qstarttime); + + /** + * Store message in the cache. Stores in message cache and rrset cache. +@@ -112,11 +114,14 @@ int dns_cache_store(struct module_env* e + * can be updated to full TTL even in prefetch situations. + * @param qrep: message that can be altered with better rrs from cache. + * @param flags: customization flags for the cache policy. ++ * @param qstarttime: time when the query was started, and thus when the ++ * delegations were looked up. + * @param region: to allocate into for qmsg. + */ + void dns_cache_store_msg(struct module_env* env, struct query_info* qinfo, + hashvalue_type hash, struct reply_info* rep, time_t leeway, int pside, +- struct reply_info* qrep, uint32_t flags, struct regional* region); ++ struct reply_info* qrep, uint32_t flags, struct regional* region, ++ time_t qstarttime); + + /** + * Find a delegation from the cache. +@@ -129,11 +134,18 @@ void dns_cache_store_msg(struct module_e + * @param msg: if not NULL, delegation message is returned here, synthesized + * from the cache. + * @param timenow: the time now, for checking if TTL on cache entries is OK. ++ * @param noexpiredabove: if set, no expired NS rrsets above the one found ++ * are tolerated. It only returns delegations where the delegations above ++ * it are valid. ++ * @param expiretop: if not NULL, name where check for expiry ends for ++ * noexpiredabove. ++ * @param expiretoplen: length of expiretop dname. + * @return new delegation or NULL on error or if not found in cache. + */ + struct delegpt* dns_cache_find_delegation(struct module_env* env, + uint8_t* qname, size_t qnamelen, uint16_t qtype, uint16_t qclass, +- struct regional* region, struct dns_msg** msg, time_t timenow); ++ struct regional* region, struct dns_msg** msg, time_t timenow, ++ int noexpiredabove, uint8_t* expiretop, size_t expiretoplen); + + /** + * generate dns_msg from cached message +--- a/services/mesh.c ++++ b/services/mesh.c +@@ -693,6 +693,7 @@ mesh_state_create(struct module_env* env + mstate->s.no_cache_lookup = 0; + mstate->s.no_cache_store = 0; + mstate->s.need_refetch = 0; ++ mstate->s.qstarttime = *env->now; + + /* init modules */ + for(i=0; imesh->mods.num; i++) { +--- a/testdata/iter_prefetch_change.rpl ++++ b/testdata/iter_prefetch_change.rpl +@@ -21,9 +21,9 @@ REPLY QR NOERROR + SECTION QUESTION + . IN NS + SECTION ANSWER +-. IN NS K.ROOT-SERVERS.NET. ++. 86400 IN NS K.ROOT-SERVERS.NET. + SECTION ADDITIONAL +-K.ROOT-SERVERS.NET. IN A 193.0.14.129 ++K.ROOT-SERVERS.NET. 86400 IN A 193.0.14.129 + ENTRY_END + + ENTRY_BEGIN +@@ -33,9 +33,9 @@ REPLY QR NOERROR + SECTION QUESTION + com. IN A + SECTION AUTHORITY +-com. IN NS a.gtld-servers.net. ++com. 86400 IN NS a.gtld-servers.net. + SECTION ADDITIONAL +-a.gtld-servers.net. IN A 192.5.6.30 ++a.gtld-servers.net. 86400 IN A 192.5.6.30 + ENTRY_END + RANGE_END + +@@ -49,9 +49,9 @@ REPLY QR NOERROR + SECTION QUESTION + com. IN NS + SECTION ANSWER +-com. IN NS a.gtld-servers.net. ++com. 86400 IN NS a.gtld-servers.net. + SECTION ADDITIONAL +-a.gtld-servers.net. IN A 192.5.6.30 ++a.gtld-servers.net. 86400 IN A 192.5.6.30 + ENTRY_END + + ENTRY_BEGIN +@@ -77,9 +77,9 @@ REPLY QR NOERROR + SECTION QUESTION + com. IN NS + SECTION ANSWER +-com. IN NS a.gtld-servers.net. ++com. 86400 IN NS a.gtld-servers.net. + SECTION ADDITIONAL +-a.gtld-servers.net. IN A 192.5.6.30 ++a.gtld-servers.net. 86400 IN A 192.5.6.30 + ENTRY_END + + ENTRY_BEGIN +--- a/util/module.h ++++ b/util/module.h +@@ -610,6 +610,12 @@ struct module_qstate { + int no_cache_store; + /** whether to refetch a fresh answer on finishing this state*/ + int need_refetch; ++ /** time when query was started. This is when the qstate is created. ++ * This is used so that type NS data cannot be overwritten by them ++ * expiring while the lookup is in progress, using data fetched from ++ * those servers. By comparing expiry time with qstarttime for type NS. ++ */ ++ time_t qstarttime; + + /** + * Attributes of clients that share the qstate that may affect IP-based +--- a/validator/validator.c ++++ b/validator/validator.c +@@ -2189,7 +2189,7 @@ processFinished(struct module_qstate* qs + if(!qstate->no_cache_store) { + if(!dns_cache_store(qstate->env, &vq->orig_msg->qinfo, + vq->orig_msg->rep, 0, qstate->prefetch_leeway, 0, NULL, +- qstate->query_flags)) { ++ qstate->query_flags, qstate->qstarttime)) { + log_err("out of memory caching validator results"); + } + } +@@ -2198,7 +2198,7 @@ processFinished(struct module_qstate* qs + /* and this does not get prefetched, so no leeway */ + if(!dns_cache_store(qstate->env, &vq->orig_msg->qinfo, + vq->orig_msg->rep, 1, 0, 0, NULL, +- qstate->query_flags)) { ++ qstate->query_flags, qstate->qstarttime)) { + log_err("out of memory caching validator results"); + } + } diff -Nru unbound-1.6.7/debian/patches/CVE-2022-3069x-pre1.patch unbound-1.6.7/debian/patches/CVE-2022-3069x-pre1.patch --- unbound-1.6.7/debian/patches/CVE-2022-3069x-pre1.patch 1970-01-01 00:00:00.000000000 +0000 +++ unbound-1.6.7/debian/patches/CVE-2022-3069x-pre1.patch 2022-08-02 17:43:55.000000000 +0000 @@ -0,0 +1,101 @@ +Backport of: + +From df6fbb82bec61aae9993851871d899b0586a4260 Mon Sep 17 00:00:00 2001 +From: Wouter Wijngaards +Date: Mon, 22 Jan 2018 13:54:20 +0000 +Subject: [PATCH] - Fix #3397: Fix that cachedb could return a partial CNAME + chain. + +git-svn-id: file:///svn/unbound/trunk@4445 be551aaa-1e26-0410-a405-d3ace91eadb9 +--- + cachedb/cachedb.c | 4 +++- + doc/Changelog | 1 + + iterator/iterator.c | 4 ++-- + services/cache/dns.c | 8 +++++--- + services/cache/dns.h | 5 ++++- + 5 files changed, 15 insertions(+), 7 deletions(-) + +--- a/cachedb/cachedb.c ++++ b/cachedb/cachedb.c +@@ -568,7 +568,9 @@ cachedb_intcache_lookup(struct module_qs + msg = dns_cache_lookup(qstate->env, qstate->qinfo.qname, + qstate->qinfo.qname_len, qstate->qinfo.qtype, + qstate->qinfo.qclass, qstate->query_flags, +- qstate->region, qstate->env->scratch); ++ qstate->region, qstate->env->scratch, ++ 1 /* no partial messages with only a CNAME */ ++ ); + if(!msg && qstate->env->neg_cache) { + /* lookup in negative cache; may result in + * NOERROR/NODATA or NXDOMAIN answers that need validation */ +--- a/iterator/iterator.c ++++ b/iterator/iterator.c +@@ -1138,7 +1138,7 @@ processInitRequest(struct module_qstate* + msg = dns_cache_lookup(qstate->env, iq->qchase.qname, + iq->qchase.qname_len, iq->qchase.qtype, + iq->qchase.qclass, qstate->query_flags, +- qstate->region, qstate->env->scratch); ++ qstate->region, qstate->env->scratch, 0); + if(!msg && qstate->env->neg_cache) { + /* lookup in negative cache; may result in + * NOERROR/NODATA or NXDOMAIN answers that need validation */ +@@ -2212,7 +2212,7 @@ processQueryTargets(struct module_qstate + iq->qinfo_out.qname, iq->qinfo_out.qname_len, + iq->qinfo_out.qtype, iq->qinfo_out.qclass, + qstate->query_flags, qstate->region, +- qstate->env->scratch); ++ qstate->env->scratch, 0); + if(msg && FLAGS_GET_RCODE(msg->rep->flags) == + LDNS_RCODE_NOERROR) + /* no need to send query if it is already +--- a/services/cache/dns.c ++++ b/services/cache/dns.c +@@ -713,7 +713,8 @@ fill_any(struct module_env* env, + struct dns_msg* + dns_cache_lookup(struct module_env* env, + uint8_t* qname, size_t qnamelen, uint16_t qtype, uint16_t qclass, +- uint16_t flags, struct regional* region, struct regional* scratch) ++ uint16_t flags, struct regional* region, struct regional* scratch, ++ int no_partial) + { + struct lruhash_entry* e; + struct query_info k; +@@ -745,7 +746,8 @@ dns_cache_lookup(struct module_env* env, + /* see if a DNAME exists. Checked for first, to enforce that DNAMEs + * are more important, the CNAME is resynthesized and thus + * consistent with the DNAME */ +- if( (rrset=find_closest_of_type(env, qname, qnamelen, qclass, now, ++ if(!no_partial && ++ (rrset=find_closest_of_type(env, qname, qnamelen, qclass, now, + LDNS_RR_TYPE_DNAME, 1))) { + /* synthesize a DNAME+CNAME message based on this */ + struct dns_msg* msg = synth_dname_msg(rrset, region, now, &k); +@@ -758,7 +760,7 @@ dns_cache_lookup(struct module_env* env, + + /* see if we have CNAME for this domain, + * but not for DS records (which are part of the parent) */ +- if( qtype != LDNS_RR_TYPE_DS && ++ if(!no_partial && qtype != LDNS_RR_TYPE_DS && + (rrset=rrset_cache_lookup(env->rrset_cache, qname, qnamelen, + LDNS_RR_TYPE_CNAME, qclass, 0, now, 0))) { + uint8_t* wc = NULL; +--- a/services/cache/dns.h ++++ b/services/cache/dns.h +@@ -159,13 +159,16 @@ struct dns_msg* tomsg(struct module_env* + * @param flags: flags with BIT_CD for AAAA queries in dns64 translation. + * @param region: where to allocate result. + * @param scratch: where to allocate temporary data. ++ * @param no_partial: if true, only complete messages and not a partial ++ * one (with only the start of the CNAME chain and not the rest). + * @return new response message (alloced in region, rrsets do not have IDs). + * or NULL on error or if not found in cache. + * TTLs are made relative to the current time. + */ + struct dns_msg* dns_cache_lookup(struct module_env* env, + uint8_t* qname, size_t qnamelen, uint16_t qtype, uint16_t qclass, +- uint16_t flags, struct regional* region, struct regional* scratch); ++ uint16_t flags, struct regional* region, struct regional* scratch, ++ int no_partial); + + /** + * find and add A and AAAA records for missing nameservers in delegpt diff -Nru unbound-1.6.7/debian/patches/CVE-2022-3069x-pre2.patch unbound-1.6.7/debian/patches/CVE-2022-3069x-pre2.patch --- unbound-1.6.7/debian/patches/CVE-2022-3069x-pre2.patch 1970-01-01 00:00:00.000000000 +0000 +++ unbound-1.6.7/debian/patches/CVE-2022-3069x-pre2.patch 2022-08-04 11:52:49.000000000 +0000 @@ -0,0 +1,77 @@ +Ubuntu: Partial Backport of the following commit just to get the +iter_stub_fwd_no_cache function in which we hardcode the return values. +This will be modified by the next commit which will add parameters to get +the deepest enclosing name of fwd or stub. + +From 0ff5c526572d002a50280a4e9442752d33002cac Mon Sep 17 00:00:00 2001 +From: Wouter Wijngaards +Date: Tue, 27 Nov 2018 10:29:14 +0000 +Subject: [PATCH] - Fix #4208: 'stub-no-cache' and 'forward-no-cache' not work. + +git-svn-id: file:///svn/unbound/trunk@4981 be551aaa-1e26-0410-a405-d3ace91eadb9 +--- + doc/Changelog | 1 + + edns-subnet/subnetmod.c | 19 +++++++--- + edns-subnet/subnetmod.h | 2 + + iterator/iter_utils.c | 47 +++++++++++++++++++++++ + iterator/iter_utils.h | 9 +++++ + iterator/iterator.c | 49 +----------------------- + testdata/fwd_no_cache.rpl | 78 +++++++++++++++++++++++++++++++++++++++ + 7 files changed, 152 insertions(+), 53 deletions(-) + create mode 100644 testdata/fwd_no_cache.rpl + +--- a/iterator/iter_utils.c ++++ b/iterator/iter_utils.c +@@ -1168,3 +1168,36 @@ int iter_dp_cangodown(struct query_info* + return 0; + return 1; + } ++ ++int ++iter_stub_fwd_no_cache(struct module_qstate *qstate, struct query_info *qinf) ++{ ++ struct iter_hints_stub *stub; ++ struct delegpt *dp; ++ ++ /* Check for stub. */ ++ stub = hints_lookup_stub(qstate->env->hints, qinf->qname, ++ qinf->qclass, NULL); ++ dp = forwards_lookup(qstate->env->fwds, qinf->qname, qinf->qclass); ++ ++ /* see if forward or stub is more pertinent */ ++ if(stub && stub->dp && dp) { ++ if(dname_strict_subdomain(dp->name, dp->namelabs, ++ stub->dp->name, stub->dp->namelabs)) { ++ stub = NULL; /* ignore stub, forward is lower */ ++ } else { ++ dp = NULL; /* ignore forward, stub is lower */ ++ } ++ } ++ ++ /* check stub */ ++ if (stub != NULL && stub->dp != NULL) { ++ return 0; ++ } ++ ++ /* Check for forward. */ ++ if (dp) { ++ return 0; ++ } ++ return 0; ++} +--- a/iterator/iter_utils.h ++++ b/iterator/iter_utils.h +@@ -365,4 +365,13 @@ int iter_ds_toolow(struct dns_msg* msg, + */ + int iter_dp_cangodown(struct query_info* qinfo, struct delegpt* dp); + ++/** ++ * Lookup if no_cache is set in stub or fwd. ++ * @param qstate: query state with env with hints and fwds. ++ * @param qinf: query name to lookup for. ++ * @return true if no_cache is set in stub or fwd. ++ */ ++int iter_stub_fwd_no_cache(struct module_qstate *qstate, ++ struct query_info *qinf); ++ + #endif /* ITERATOR_ITER_UTILS_H */ diff -Nru unbound-1.6.7/debian/patches/CVE-2022-3069x-pre3.patch unbound-1.6.7/debian/patches/CVE-2022-3069x-pre3.patch --- unbound-1.6.7/debian/patches/CVE-2022-3069x-pre3.patch 1970-01-01 00:00:00.000000000 +0000 +++ unbound-1.6.7/debian/patches/CVE-2022-3069x-pre3.patch 2022-08-03 17:34:12.000000000 +0000 @@ -0,0 +1,170 @@ +Partial backport of: + +From 55ba863440dc5b1266b79f26ed92bbbdde5a2ebb Mon Sep 17 00:00:00 2001 +From: "W.C.A. Wijngaards" +Date: Tue, 13 Apr 2021 13:52:57 +0200 +Subject: [PATCH] - Fix that nxdomain synthesis does not happen above the stub + or forward definition. + +--- + cachedb/cachedb.c | 8 +++++++- + doc/Changelog | 4 ++++ + edns-subnet/subnetmod.c | 2 +- + iterator/iter_utils.c | 15 ++++++++++++++- + iterator/iter_utils.h | 7 ++++++- + iterator/iterator.c | 12 +++++++----- + services/cache/dns.c | 5 ++++- + services/cache/dns.h | 4 +++- + 8 files changed, 46 insertions(+), 11 deletions(-) + +--- a/cachedb/cachedb.c ++++ b/cachedb/cachedb.c +@@ -564,12 +564,18 @@ cachedb_extcache_store(struct module_qst + static int + cachedb_intcache_lookup(struct module_qstate* qstate) + { ++ uint8_t* dpname=NULL; ++ size_t dpnamelen=0; + struct dns_msg* msg; ++ if(iter_stub_fwd_no_cache(qstate, &qstate->qinfo, ++ &dpname, &dpnamelen)) ++ return 0; /* no cache for these queries */ + msg = dns_cache_lookup(qstate->env, qstate->qinfo.qname, + qstate->qinfo.qname_len, qstate->qinfo.qtype, + qstate->qinfo.qclass, qstate->query_flags, + qstate->region, qstate->env->scratch, +- 1 /* no partial messages with only a CNAME */ ++ 1, /* no partial messages with only a CNAME */ ++ dpname, dpnamelen + ); + if(!msg && qstate->env->neg_cache) { + /* lookup in negative cache; may result in +--- a/iterator/iter_utils.c ++++ b/iterator/iter_utils.c +@@ -1170,7 +1170,8 @@ int iter_dp_cangodown(struct query_info* + } + + int +-iter_stub_fwd_no_cache(struct module_qstate *qstate, struct query_info *qinf) ++iter_stub_fwd_no_cache(struct module_qstate *qstate, struct query_info *qinf, ++ uint8_t** retdpname, size_t* retdpnamelen) + { + struct iter_hints_stub *stub; + struct delegpt *dp; +@@ -1192,12 +1193,24 @@ iter_stub_fwd_no_cache(struct module_qst + + /* check stub */ + if (stub != NULL && stub->dp != NULL) { ++ if(retdpname) { ++ *retdpname = stub->dp->name; ++ *retdpnamelen = stub->dp->namelen; ++ } + return 0; + } + + /* Check for forward. */ + if (dp) { ++ if(retdpname) { ++ *retdpname = dp->name; ++ *retdpnamelen = dp->namelen; ++ } + return 0; + } ++ if(retdpname) { ++ *retdpname = NULL; ++ *retdpnamelen = 0; ++ } + return 0; + } +--- a/iterator/iter_utils.h ++++ b/iterator/iter_utils.h +@@ -369,9 +369,14 @@ int iter_dp_cangodown(struct query_info* + * Lookup if no_cache is set in stub or fwd. + * @param qstate: query state with env with hints and fwds. + * @param qinf: query name to lookup for. ++ * @param retdpname: returns NULL or the deepest enclosing name of fwd or stub. ++ * This is the name under which the closest lookup is going to happen. ++ * Used for NXDOMAIN checks, above that it is an nxdomain from a ++ * different server and zone. You can pass NULL to not get it. ++ * @param retdpnamelen: returns the length of the dpname. + * @return true if no_cache is set in stub or fwd. + */ + int iter_stub_fwd_no_cache(struct module_qstate *qstate, +- struct query_info *qinf); ++ struct query_info *qinf, uint8_t** retdpname, size_t* retdpnamelen); + + #endif /* ITERATOR_ITER_UTILS_H */ +--- a/iterator/iterator.c ++++ b/iterator/iterator.c +@@ -1078,8 +1078,8 @@ static int + processInitRequest(struct module_qstate* qstate, struct iter_qstate* iq, + struct iter_env* ie, int id) + { +- uint8_t* delname; +- size_t delnamelen; ++ uint8_t* delname, *dpname=NULL; ++ size_t delnamelen, dpnamelen=0; + struct dns_msg* msg = NULL; + + log_query_info(VERB_DETAIL, "resolving", &qstate->qinfo); +@@ -1138,7 +1138,8 @@ processInitRequest(struct module_qstate* + msg = dns_cache_lookup(qstate->env, iq->qchase.qname, + iq->qchase.qname_len, iq->qchase.qtype, + iq->qchase.qclass, qstate->query_flags, +- qstate->region, qstate->env->scratch, 0); ++ qstate->region, qstate->env->scratch, 0, dpname, ++ dpnamelen); + if(!msg && qstate->env->neg_cache) { + /* lookup in negative cache; may result in + * NOERROR/NODATA or NXDOMAIN answers that need validation */ +@@ -2212,7 +2213,8 @@ processQueryTargets(struct module_qstate + iq->qinfo_out.qname, iq->qinfo_out.qname_len, + iq->qinfo_out.qtype, iq->qinfo_out.qclass, + qstate->query_flags, qstate->region, +- qstate->env->scratch, 0); ++ qstate->env->scratch, 0, iq->dp->name, ++ iq->dp->namelen); + if(msg && FLAGS_GET_RCODE(msg->rep->flags) == + LDNS_RCODE_NOERROR) + /* no need to send query if it is already +--- a/services/cache/dns.c ++++ b/services/cache/dns.c +@@ -714,7 +714,7 @@ struct dns_msg* + dns_cache_lookup(struct module_env* env, + uint8_t* qname, size_t qnamelen, uint16_t qtype, uint16_t qclass, + uint16_t flags, struct regional* region, struct regional* scratch, +- int no_partial) ++ int no_partial, uint8_t* dpname, size_t dpnamelen) + { + struct lruhash_entry* e; + struct query_info k; +@@ -811,6 +811,9 @@ dns_cache_lookup(struct module_env* env, + * the same. We search upwards for NXDOMAINs. */ + if(env->cfg->harden_below_nxdomain) + while(!dname_is_root(k.qname)) { ++ if(dpname && dpnamelen ++ && !dname_subdomain_c(k.qname, dpname)) ++ break; /* no synth nxdomain above the stub */ + dname_remove_label(&k.qname, &k.qname_len); + h = query_info_hash(&k, flags); + e = slabhash_lookup(env->msg_cache, h, &k, 0); +--- a/services/cache/dns.h ++++ b/services/cache/dns.h +@@ -161,6 +161,8 @@ struct dns_msg* tomsg(struct module_env* + * @param scratch: where to allocate temporary data. + * @param no_partial: if true, only complete messages and not a partial + * one (with only the start of the CNAME chain and not the rest). ++ * @param dpname: if not NULL, do not return NXDOMAIN above this name. ++ * @param dpnamelen: length of dpname. + * @return new response message (alloced in region, rrsets do not have IDs). + * or NULL on error or if not found in cache. + * TTLs are made relative to the current time. +@@ -168,7 +170,7 @@ struct dns_msg* tomsg(struct module_env* + struct dns_msg* dns_cache_lookup(struct module_env* env, + uint8_t* qname, size_t qnamelen, uint16_t qtype, uint16_t qclass, + uint16_t flags, struct regional* region, struct regional* scratch, +- int no_partial); ++ int no_partial, uint8_t* dpname, size_t dpnamelen); + + /** + * find and add A and AAAA records for missing nameservers in delegpt diff -Nru unbound-1.6.7/debian/patches/series unbound-1.6.7/debian/patches/series --- unbound-1.6.7/debian/patches/series 2021-05-05 11:35:19.000000000 +0000 +++ unbound-1.6.7/debian/patches/series 2022-08-03 17:35:19.000000000 +0000 @@ -12,3 +12,7 @@ CVE-2019-25040.patch CVE-2019-25042.patch CVE-2020-28935.patch +CVE-2022-3069x-pre1.patch +CVE-2022-3069x-pre2.patch +CVE-2022-3069x-pre3.patch +CVE-2022-3069x.patch