fix CVE-2022-30698 and CVE-2022-30699

This commit is contained in:
yangl777 2022-08-04 16:01:44 +08:00
parent 44fedbcff2
commit 730361b49f
3 changed files with 839 additions and 1 deletions

View File

@ -0,0 +1,206 @@
From 55ba863440dc5b1266b79f26ed92bbbdde5a2ebb Mon Sep 17 00:00:00 2001
From: "W.C.A. Wijngaards" <wouter@nlnetlabs.nl>
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 +++++++-
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 +++-
7 files changed, 42 insertions(+), 11 deletions(-)
diff --git a/cachedb/cachedb.c b/cachedb/cachedb.c
index eed4d5f..1f3ff98 100644
--- a/cachedb/cachedb.c
+++ b/cachedb/cachedb.c
@@ -616,12 +616,18 @@ cachedb_extcache_store(struct module_qstate* qstate, struct cachedb_env* ie)
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 &&
iter_qname_indicates_dnssec(qstate->env, &qstate->qinfo)) {
diff --git a/edns-subnet/subnetmod.c b/edns-subnet/subnetmod.c
index f1b401b..fcd6dc6 100644
--- a/edns-subnet/subnetmod.c
+++ b/edns-subnet/subnetmod.c
@@ -150,7 +150,7 @@ 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)) {
+ if(!iter_stub_fwd_no_cache(qstate, &qstate->qinfo, NULL, NULL)) {
qstate->no_cache_store = 0;
}
diff --git a/iterator/iter_utils.c b/iterator/iter_utils.c
index 7bc67da..260ebe6 100644
--- a/iterator/iter_utils.c
+++ b/iterator/iter_utils.c
@@ -1390,7 +1390,8 @@ int iter_dp_cangodown(struct query_info* qinfo, struct delegpt* dp)
}
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;
@@ -1419,6 +1420,10 @@ iter_stub_fwd_no_cache(struct module_qstate *qstate, struct query_info *qinf)
dname_str(stub->dp->name, dpname);
verbose(VERB_ALGO, "stub for %s %s has no_cache", qname, dpname);
}
+ if(retdpname) {
+ *retdpname = stub->dp->name;
+ *retdpnamelen = stub->dp->namelen;
+ }
return (stub->dp->no_cache);
}
@@ -1431,7 +1436,15 @@ iter_stub_fwd_no_cache(struct module_qstate *qstate, struct query_info *qinf)
dname_str(dp->name, dpname);
verbose(VERB_ALGO, "forward for %s %s has no_cache", qname, dpname);
}
+ if(retdpname) {
+ *retdpname = dp->name;
+ *retdpnamelen = dp->namelen;
+ }
return (dp->no_cache);
}
+ if(retdpname) {
+ *retdpname = NULL;
+ *retdpnamelen = 0;
+ }
return 0;
}
diff --git a/iterator/iter_utils.h b/iterator/iter_utils.h
index f771930..6d4d152 100644
--- a/iterator/iter_utils.h
+++ b/iterator/iter_utils.h
@@ -380,9 +380,14 @@ 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.
+ * @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 */
diff --git a/iterator/iterator.c b/iterator/iterator.c
index 23b07ea..3772235 100644
--- a/iterator/iterator.c
+++ b/iterator/iterator.c
@@ -1228,8 +1228,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);
@@ -1283,7 +1283,7 @@ processInitRequest(struct module_qstate* qstate, struct iter_qstate* iq,
/* This either results in a query restart (CNAME cache response), a
* terminating response (ANSWER), or a cache miss (null). */
- if (iter_stub_fwd_no_cache(qstate, &iq->qchase)) {
+ if (iter_stub_fwd_no_cache(qstate, &iq->qchase, &dpname, &dpnamelen)) {
/* Asked to not query cache. */
verbose(VERB_ALGO, "no-cache set, going to the network");
qstate->no_cache_lookup = 1;
@@ -1298,7 +1298,8 @@ processInitRequest(struct module_qstate* qstate, struct iter_qstate* iq,
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 &&
iter_qname_indicates_dnssec(qstate->env, &iq->qchase)) {
/* lookup in negative cache; may result in
@@ -2288,7 +2289,8 @@ processQueryTargets(struct module_qstate* qstate, struct iter_qstate* iq,
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
diff --git a/services/cache/dns.c b/services/cache/dns.c
index 7b6e142..8160b31 100644
--- a/services/cache/dns.c
+++ b/services/cache/dns.c
@@ -801,7 +801,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;
@@ -924,6 +924,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);
diff --git a/services/cache/dns.h b/services/cache/dns.h
index f1b77fb..bece837 100644
--- a/services/cache/dns.h
+++ b/services/cache/dns.h
@@ -164,6 +164,8 @@ struct dns_msg* tomsg(struct module_env* env, struct query_info* q,
* @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.
@@ -171,7 +173,7 @@ struct dns_msg* tomsg(struct module_env* env, struct query_info* q,
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
--
2.33.0

View File

@ -0,0 +1,624 @@
From f6753a0f1018133df552347a199e0362fc1dac68 Mon Sep 17 00:00:00 2001
From: "W.C.A. Wijngaards" <wouter@nlnetlabs.nl>
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 +-
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 +-
16 files changed, 157 insertions(+), 48 deletions(-)
diff --git a/cachedb/cachedb.c b/cachedb/cachedb.c
index af4ffe5..6e3dcfd 100644
--- a/cachedb/cachedb.c
+++ b/cachedb/cachedb.c
@@ -655,7 +655,7 @@ cachedb_intcache_store(struct module_qstate* qstate)
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);
}
/**
diff --git a/daemon/cachedump.c b/daemon/cachedump.c
index b1ce53b..c2f8207 100644
--- a/daemon/cachedump.c
+++ b/daemon/cachedump.c
@@ -677,7 +677,8 @@ load_msg(RES* ssl, sldns_buffer* buf, struct worker* worker)
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;
}
@@ -848,7 +849,7 @@ int print_deleg_lookup(RES* ssl, struct worker* worker, uint8_t* nm,
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");
diff --git a/daemon/worker.c b/daemon/worker.c
index 23e3244..47a3e36 100644
--- a/daemon/worker.c
+++ b/daemon/worker.c
@@ -491,7 +491,7 @@ answer_norec_from_cache(struct worker* worker, struct query_info* qinfo,
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;
}
diff --git a/dns64/dns64.c b/dns64/dns64.c
index 5c70119..4b6f54d 100644
--- a/dns64/dns64.c
+++ b/dns64/dns64.c
@@ -647,7 +647,7 @@ handle_event_moddone(struct module_qstate* qstate, int id)
if ( (!iq || !iq->started_no_cache_store) &&
qstate->return_msg && qstate->return_msg->rep &&
!dns_cache_store(qstate->env, &qstate->qinfo, qstate->return_msg->rep,
- 0, 0, 0, NULL, qstate->query_flags))
+ 0, 0, 0, NULL, qstate->query_flags, qstate->qstarttime))
log_err("out of memory");
/* do nothing */
@@ -981,7 +981,7 @@ dns64_inform_super(struct module_qstate* qstate, int id,
/* Store the generated response in cache. */
if ( (!super_dq || !super_dq->started_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");
}
diff --git a/ipsecmod/ipsecmod.c b/ipsecmod/ipsecmod.c
index a1f40a5..e7ae736 100644
--- a/ipsecmod/ipsecmod.c
+++ b/ipsecmod/ipsecmod.c
@@ -443,7 +443,7 @@ ipsecmod_handle_query(struct module_qstate* qstate,
/* 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;
diff --git a/iterator/iter_utils.c b/iterator/iter_utils.c
index 7bc67da..93fb859 100644
--- a/iterator/iter_utils.c
+++ b/iterator/iter_utils.c
@@ -653,10 +653,10 @@ dns_copy_msg(struct dns_msg* from, struct regional* region)
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");
}
diff --git a/iterator/iter_utils.h b/iterator/iter_utils.h
index f771930..2ceaec2 100644
--- a/iterator/iter_utils.h
+++ b/iterator/iter_utils.h
@@ -130,6 +130,7 @@ struct dns_msg* dns_copy_msg(struct dns_msg* from, struct regional* regional);
* 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.
@@ -138,7 +139,7 @@ struct dns_msg* dns_copy_msg(struct dns_msg* from, struct regional* regional);
*/
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.
diff --git a/iterator/iterator.c b/iterator/iterator.c
index 23b07ea..141d25b 100644
--- a/iterator/iterator.c
+++ b/iterator/iterator.c
@@ -370,7 +370,7 @@ error_response_cache(struct module_qstate* qstate, int id, int rcode)
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);
}
@@ -1422,7 +1422,8 @@ processInitRequest(struct module_qstate* qstate, struct iter_qstate* iq,
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
@@ -1744,7 +1745,8 @@ generate_parentside_target_query(struct module_qstate* qstate,
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(
@@ -2789,7 +2791,8 @@ processQueryResponse(struct module_qstate* qstate, struct iter_qstate* iq,
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;
@@ -2886,7 +2889,8 @@ processQueryResponse(struct module_qstate* qstate, struct iter_qstate* iq,
/* 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);
@@ -2997,7 +3001,7 @@ processQueryResponse(struct module_qstate* qstate, struct iter_qstate* iq,
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;
@@ -3566,7 +3570,8 @@ processFinished(struct module_qstate* qstate, struct iter_qstate* iq,
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;
diff --git a/pythonmod/interface.i b/pythonmod/interface.i
index c02ebaf..68064c5 100644
--- a/pythonmod/interface.i
+++ b/pythonmod/interface.i
@@ -1357,7 +1357,8 @@ int set_return_msg(struct module_qstate* 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,
@@ -1386,7 +1387,7 @@ struct delegpt* find_delegation(struct module_qstate* qstate, char *nm, size_t n
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)) {
diff --git a/pythonmod/pythonmod_utils.c b/pythonmod/pythonmod_utils.c
index 5d70f2b..4f5f674 100644
--- a/pythonmod/pythonmod_utils.c
+++ b/pythonmod/pythonmod_utils.c
@@ -68,7 +68,8 @@ int storeQueryInCache(struct module_qstate* qstate, struct query_info* qinfo, st
}
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 */
diff --git a/services/cache/dns.c b/services/cache/dns.c
index 7b6e142..6c836b5 100644
--- a/services/cache/dns.c
+++ b/services/cache/dns.c
@@ -68,11 +68,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. */
@@ -81,8 +86,8 @@ store_rrsets(struct module_env* env, struct reply_info* rep, time_t now,
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 */
@@ -155,7 +160,8 @@ msg_del_servfail(struct module_env* env, struct query_info* qinfo,
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;
@@ -170,7 +176,8 @@ dns_cache_store_msg(struct module_env* env, struct query_info* qinfo,
/* 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 */
@@ -194,10 +201,51 @@ dns_cache_store_msg(struct module_env* env, struct query_info* qinfo,
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;
@@ -212,8 +260,40 @@ find_closest_of_type(struct module_env* env, uint8_t* qname, size_t qnamelen,
/* 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;
@@ -461,7 +541,8 @@ dns_msg_ansadd(struct dns_msg* msg, struct regional* region,
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;
@@ -469,7 +550,7 @@ dns_cache_find_delegation(struct module_env* env, uint8_t* qname,
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;
@@ -835,7 +916,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 */
enum sec_status sec_status = sec_status_unchecked;
struct dns_msg* msg = synth_dname_msg(rrset, region, now, &k,
@@ -966,7 +1047,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) */
@@ -989,9 +1070,9 @@ dns_cache_store(struct module_env* env, struct query_info* msgqinf,
/*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;
@@ -1013,7 +1094,7 @@ dns_cache_store(struct module_env* env, struct query_info* msgqinf,
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);
diff --git a/services/cache/dns.h b/services/cache/dns.h
index f1b77fb..4422504 100644
--- 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* env, struct query_info* qinf,
* 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_env* env, struct query_info* qinfo,
* @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
diff --git a/services/mesh.c b/services/mesh.c
index 4b0c5db..8b4a59f 100644
--- a/services/mesh.c
+++ b/services/mesh.c
@@ -830,6 +830,7 @@ mesh_state_create(struct module_env* env, struct query_info* qinfo,
mstate->s.no_cache_store = 0;
mstate->s.need_refetch = 0;
mstate->s.was_ratelimited = 0;
+ mstate->s.qstarttime = *env->now;
/* init modules */
for(i=0; i<env->mesh->mods.num; i++) {
diff --git a/unbound-1.11.0/testdata/iter_prefetch_change.rpl b/unbound-1.11.0/testdata/iter_prefetch_change.rpl
index 007025a..1be9e6a 100644
--- a/testdata/iter_prefetch_change.rpl
+++ b/testdata/iter_prefetch_change.rpl
@@ -22,9 +22,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
@@ -34,9 +34,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
@@ -50,9 +50,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
@@ -78,9 +78,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
diff --git a/util/module.h b/util/module.h
index fa89c64..2f1a442 100644
--- a/util/module.h
+++ b/util/module.h
@@ -650,6 +650,12 @@ struct module_qstate {
int need_refetch;
/** whether the query (or a subquery) was ratelimited */
int was_ratelimited;
+ /** 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
diff --git a/validator/validator.c b/validator/validator.c
index c3ca0a2..bfc03fb 100644
--- a/validator/validator.c
+++ b/validator/validator.c
@@ -2302,7 +2302,7 @@ processFinished(struct module_qstate* qstate, struct val_qstate* vq,
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");
}
}
@@ -2311,7 +2311,7 @@ processFinished(struct module_qstate* qstate, struct val_qstate* vq,
/* 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");
}
}
--
2.27.0

View File

@ -2,7 +2,7 @@
Name: unbound
Version: 1.11.0
Release: 5
Release: 6
Summary: Unbound is a validating, recursive, caching DNS resolver
License: BSD
Url: https://nlnetlabs.nl/projects/unbound/about/
@ -26,6 +26,8 @@ Patch0: CVE-2020-28935.patch
Patch6000: backport-fix-610-undefine-shift-in-sldns_str2wire_hip_buf.patch
Patch6001: backport-fix-for-611-integer-overflow-in-sldns_wire2str_pkt_s.patch
Patch6002: backport-fix-q-doesnt-work-when-use-with-unbound-control-stats_shm.patch
Patch6003: backport-0001-CVE-2022-30698-and-CVE-2022-30699.patch
Patch6004: backport-0002-CVE-2022-30698-and-CVE-2022-30699.patch
BuildRequires: make flex swig pkgconfig systemd python-unversioned-command
BuildRequires: libevent-devel expat-devel openssl-devel python3-devel
@ -231,6 +233,12 @@ popd
%{_mandir}/man*
%changelog
* Thu Aug 04 2022 yanglu<yanglu72@h-partners.com> - 1.11.0-6
- Type:cves
- CVE:CVE-2022-30698 CVE-2022-30699
- SUG:NA
- DESC:fix CVE-2022-30698 and CVE-2022-30699
* Wed Mar 23 2022 zengweifeng<zwfeng@huawei.com> - 1.11.0-5
- Type:bugfix
- ID:NA