On 7/21/26 20:29, Michael Tokarev wrote:
On 7/21/26 19:00, Ondřej Surý wrote:
Hi Michael,
I believe it is this one: https://github.com/NLnetLabs/unbound/issues/1174
and the fix is:
https://github.com/NLnetLabs/unbound/commit/fff9f62a1ec008185868a369207b0d56ffb7dcb3
(Just confirmed by Yorgos.)
Note this commit is based on
https://github.com/NLnetLabs/unbound/commit/d5e91d181
(at least contextually - it uses extra parameter is_valrec to iter_dns_store(),
added by d5e91d181.
With so much quite sensitive changes for such a fragile area, I'm not confident
to just cherry-pick these two commits (without the Changelog parts) and hope the
whole thing will work correctly.
The debdiff is attached. But I haven't verified if the result fixes the issue.
At the very least, regular operations of unbond seems to be unaffected by the
two patches, the thing continue to work.
As stated by Salvatore already, this is not a security material for debian,
but if we're to release a real security update, this change can be squashed
into that one.
Thanks,
/mjt
diff -Nru unbound-1.22.0/debian/changelog unbound-1.22.0/debian/changelog
--- unbound-1.22.0/debian/changelog 2026-05-25 12:05:38.000000000 +0300
+++ unbound-1.22.0/debian/changelog 2026-07-22 09:53:15.000000000 +0300
@@ -1,3 +1,12 @@
+unbound (1.22.0-2+deb13u4) UNRELEASED; urgency=medium
+
+ * two patches for upstream to avoid amplifying traffic to root NSes:
+ fix-serve-expired-DNSSEC-information.patch (preparation),
+ 1174-serve-expired-cache-update-fixes.patch
+ (Closes: #1142539)
+
+ -- Michael Tokarev <[email protected]> Wed, 22 Jul 2026 09:53:15 +0300
+
unbound (1.22.0-2+deb13u3) trixie-security; urgency=medium
* May-2026 security updates in debian/patches/26-05/ subdir:
diff -Nru
unbound-1.22.0/debian/patches/1174-serve-expired-cache-update-fixes.patch
unbound-1.22.0/debian/patches/1174-serve-expired-cache-update-fixes.patch
--- unbound-1.22.0/debian/patches/1174-serve-expired-cache-update-fixes.patch
1970-01-01 03:00:00.000000000 +0300
+++ unbound-1.22.0/debian/patches/1174-serve-expired-cache-update-fixes.patch
2026-07-22 09:53:15.000000000 +0300
@@ -0,0 +1,461 @@
+From fff9f62a1ec008185868a369207b0d56ffb7dcb3 Mon Sep 17 00:00:00 2001
+From: Yorgos Thessalonikefs <[email protected]>
+Date: Tue, 31 Dec 2024 16:28:12 +0100
+Subject: [PATCH] Serve expired cache update fixes (#1174)
+Bug: https://github.com/NLnetLabs/unbound/issues/1174
+Bug-Debian: https://bugs.debian.org/1142539
+Origin: upstream,
https://github.com/NLnetLabs/unbound/commit/fff9f62a1ec008185868a369207b0d56ffb7dcb3
+Forwarded: not-needed
+
+- Fixes a regression bug with serve-expired that appeared in 1.22.0
+ and would not allow the iterator to update the cache with
+ not-yet-validated entries resulting in increased outgoing traffic.
+
+- Treat serve_expired_norec_ttl as a backoff timer for failed updates of
expired records.
+- Try to use expired answers instead of SERVFAIL if serve-expired is
+ enabled even without serve-expired-client-timeout.
+- Add suggestion to refresh the cached norec_ttl and expired_ttl when a
+ response cannot update the usable expired entry.
+---
+ daemon/worker.c | 6 +-
+ services/cache/dns.c | 41 +++++++++-
+ services/mesh.c | 6 +-
+ .../serve_expired_client_timeout_servfail.rpl | 48 +++++++++---
+ ...serve_expired_client_timeout_val_bogus.rpl | 18 +++--
+ testdata/serve_expired_ttl_reset.rpl | 22 +++---
+ testdata/serve_expired_val_bogus.rpl | 75 +++++++++++++++++--
+ 7 files changed, 170 insertions(+), 46 deletions(-)
+
+diff --git a/daemon/worker.c b/daemon/worker.c
+index 413abdb87..3985d1108 100644
+--- a/daemon/worker.c
++++ b/daemon/worker.c
+@@ -1845,10 +1845,10 @@ lookup_cache:
+ * its qname must be that used for cache
+ * lookup. */
+ if((worker->env.cfg->prefetch &&
+- *worker->env.now >= rep->prefetch_ttl)
||
++ rep->prefetch_ttl <= *worker->env.now)
||
+ (worker->env.cfg->serve_expired &&
+- *worker->env.now > rep->ttl)) {
+-
++ rep->ttl < *worker->env.now &&
++ !(*worker->env.now <
rep->serve_expired_norec_ttl))) {
+ time_t leeway = rep->ttl -
*worker->env.now;
+ if(rep->ttl < *worker->env.now)
+ leeway = 0;
+diff --git a/services/cache/dns.c b/services/cache/dns.c
+index d9536c0e7..351b3568c 100644
+--- a/services/cache/dns.c
++++ b/services/cache/dns.c
+@@ -1056,7 +1056,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 reply_info* msgrep, int is_referral, time_t leeway, int pside,
+ struct regional* region, uint32_t flags, time_t qstarttime,
+ int is_valrec)
+ {
+@@ -1066,7 +1066,7 @@ dns_cache_store(struct module_env* env, struct
query_info* msgqinf,
+ * useful expired record exists. */
+ struct msgreply_entry* e = msg_cache_lookup(env,
+ msgqinf->qname, msgqinf->qname_len, msgqinf->qtype,
+- msgqinf->qclass, flags, 0, 0);
++ msgqinf->qclass, flags, 0, 1);
+ if(e) {
+ struct reply_info* cached = e->entry.data;
+ if(cached->ttl < *env->now
+@@ -1081,7 +1081,42 @@ dns_cache_store(struct module_env* env, struct
query_info* msgqinf,
+ && cached->security != sec_status_bogus
+ && (env->need_to_validate &&
+ msgrep->security == sec_status_unchecked)
+- && !is_valrec) {
++ /* Exceptions to that rule are:
++ * o recursions that don't need validation but
++ * need to update the cache for coherence
++ * (delegation information while iterating,
++ * DNSKEY and DS lookups from validator)
++ * o explicit RRSIG queries that are not
++ * validated. */
++ && !is_valrec
++ && msgqinf->qtype != LDNS_RR_TYPE_RRSIG) {
++ if((int)FLAGS_GET_RCODE(msgrep->flags) !=
++ LDNS_RCODE_NOERROR &&
++ (int)FLAGS_GET_RCODE(msgrep->flags) !=
++ LDNS_RCODE_NXDOMAIN) {
++ /* The current response has an
++ * erroneous rcode. Adjust norec time
++ * so that additional lookups are not
++ * performed for some time. */
++ verbose(VERB_ALGO, "set "
++ "serve-expired-norec-ttl for "
++ "response in cache");
++ cached->serve_expired_norec_ttl =
++ NORR_TTL + *env->now;
++ if(env->cfg->serve_expired_ttl_reset &&
++ cached->serve_expired_ttl
++ < *env->now +
++ env->cfg->serve_expired_ttl) {
++ /* Reset serve-expired-ttl for
++ * valid response in cache. */
++ verbose(VERB_ALGO, "reset "
++ "serve-expired-ttl "
++ "for response in
cache");
++ cached->serve_expired_ttl =
++ *env->now +
++ env->cfg->serve_expired_ttl;
++ }
++ }
+ verbose(VERB_ALGO, "a validated expired entry "
+ "could be overwritten, skip caching "
+ "the new message at this stage");
+diff --git a/services/mesh.c b/services/mesh.c
+index a25094d12..2289277ea 100644
+--- a/services/mesh.c
++++ b/services/mesh.c
+@@ -1512,8 +1512,10 @@ void mesh_query_done(struct mesh_state* mstate)
+ }
+ if(mstate->s.return_rcode == LDNS_RCODE_SERVFAIL ||
+ (rep && FLAGS_GET_RCODE(rep->flags) == LDNS_RCODE_SERVFAIL)) {
+- /* we are SERVFAILing; check for expired answer here */
+- mesh_serve_expired_callback(mstate);
++ if(mstate->s.env->cfg->serve_expired) {
++ /* we are SERVFAILing; check for expired answer here */
++ mesh_respond_serve_expired(mstate);
++ }
+ if((mstate->reply_list || mstate->cb_list)
+ && mstate->s.env->cfg->log_servfail
+ && !mstate->s.env->cfg->val_log_squelch) {
+diff --git a/testdata/serve_expired_client_timeout_servfail.rpl
b/testdata/serve_expired_client_timeout_servfail.rpl
+index 13d03da6c..3c5b35e17 100644
+--- a/testdata/serve_expired_client_timeout_servfail.rpl
++++ b/testdata/serve_expired_client_timeout_servfail.rpl
+@@ -9,7 +9,6 @@ server:
+ ede: yes
+ ede-serve-expired: yes
+
+-
+ stub-zone:
+ name: "example.com"
+ stub-addr: 1.2.3.4
+@@ -25,8 +24,10 @@ SCENARIO_BEGIN Test serve-expired with client-timeout and a
SERVFAIL upstream re
+ ; - check that we get the expired cached answer
+ ; - query again (the answer is available on the upstream server now)
+ ; - check that we get the immediate expired answer back instead
+-; - (the upstream query does happen after the expired reply and updates the
cache)
+-; - query again (the upstream has no answer)
++; - query again (the answer is available on the upstream server now)
++; - check that we *still* get the immediate expired answer back instead,
recursion is blocked for NORR_TTL(5)
++; - wait for NORR_TTL(5) to expire
++; - query again
+ ; - check that we get the freshly cached answer
+
+ ; ns.example.com.
+@@ -73,7 +74,7 @@ RANGE_BEGIN 30 40
+ RANGE_END
+
+ ; ns.example.com.
+-RANGE_BEGIN 50 60
++RANGE_BEGIN 50 100
+ ADDRESS 1.2.3.4
+ ; response to A query
+ ENTRY_BEGIN
+@@ -148,12 +149,8 @@ ENTRY_BEGIN
+ example.com. IN A
+ ENTRY_END
+
+-; Allow for upstream query to resolve.
+-STEP 51 TRAFFIC
+-
+ ; Check that we got an immediate stale answer because of the previous failure,
+-; regardless if upstream has the answer already in this range. The query will
+-; be resolved after the immediate cached answer and will cache the result.
++; regardless if upstream has the answer already in this range.
+ STEP 60 CHECK_ANSWER
+ ENTRY_BEGIN
+ MATCH all ttl ede=3
+@@ -171,14 +168,41 @@ ENTRY_END
+ ; Query again
+ STEP 70 QUERY
+ ENTRY_BEGIN
+- REPLY RD
++ REPLY RD DO
+ SECTION QUESTION
+ example.com. IN A
+ ENTRY_END
+
+-; Check that we got the cached updated answer from the previous step since
+-; there is no upstream in this range.
++; Check that we still get the immediate stale answer because of the previous
failure,
++; regardless if upstream has the answer already in this range. NORR_TTL(5)
blocks us from
++; recursion.
+ STEP 80 CHECK_ANSWER
++ENTRY_BEGIN
++ MATCH all ttl ede=3
++ REPLY QR RD RA DO NOERROR
++ SECTION QUESTION
++ example.com. IN A
++ SECTION ANSWER
++ example.com. 123 IN A 5.6.7.8
++ SECTION AUTHORITY
++ example.com. 123 IN NS ns.example.com.
++ SECTION ADDITIONAL
++ ns.example.com. 123 IN A 1.2.3.4
++ENTRY_END
++
++; Let NORR_TTL(5) expire
++STEP 81 TIME_PASSES ELAPSE 5
++
++; Query again
++STEP 90 QUERY
++ENTRY_BEGIN
++ REPLY RD
++ SECTION QUESTION
++ example.com. IN A
++ENTRY_END
++
++; Check fresh reply
++STEP 100 CHECK_ANSWER
+ ENTRY_BEGIN
+ MATCH all ttl
+ REPLY QR RD RA NOERROR
+diff --git a/testdata/serve_expired_client_timeout_val_bogus.rpl
b/testdata/serve_expired_client_timeout_val_bogus.rpl
+index 4334168cd..47a6545a4 100644
+--- a/testdata/serve_expired_client_timeout_val_bogus.rpl
++++ b/testdata/serve_expired_client_timeout_val_bogus.rpl
+@@ -30,13 +30,13 @@ SCENARIO_BEGIN Test serve-expired with client-timeout and
bogus answer
+ ; - wait for the record to expire
+ ; - (upstream now has a bogus response)
+ ; - query again for www.example.com. IN A
+-; - check that we get the expired valid response instead
+-; - query once more
++; - check that we get the expired valid response instead; recursion is
blocked for NORR_TTL(5) because of the failure
+ ; - (upstream has the valid response again)
++; - query once more
+ ; - check that we get the immediate expired valid response
+-; - (the prefetch query updates the cache with the valid response)
++; - let NORR_TTL(5) expire
+ ; - query one last time
+-; - check that we get the immediate valid cache response; upstream does not
have an answer at this moment
++; - check that we get the immediate valid cache response
+
+ ; The example.com NS and ns.example.com A record are commented out.
+ ; This to make the test succeed. It then keeps the dnssec valid lookup.
+@@ -199,7 +199,7 @@ RANGE_END
+ ;;
+ ;; ns.example.com. with valid data again
+ ;;
+-RANGE_BEGIN 40 60
++RANGE_BEGIN 40 70
+ ADDRESS 1.2.3.4
+ ; response to query of interest
+ ENTRY_BEGIN
+@@ -279,7 +279,7 @@ SECTION QUESTION
+ www.example.com. IN A
+ ENTRY_END
+
+-; immediate cached answer because upstream is valid again
++; immediate cached answer; although upstream is valid again
+ STEP 50 CHECK_ANSWER
+ ENTRY_BEGIN
+ MATCH all ttl ede=3
+@@ -297,7 +297,9 @@ SECTION ADDITIONAL
+ ;ns.example.com. 123 IN RRSIG A 3 3 3600 20070926134150
20070829134150 2854 example.com.
MC0CFQCQMyTjn7WWwpwAR1LlVeLpRgZGuQIUCcJDEkwAuzytTDRlYK7nIMwH1CM= ;{id = 2854}
+ ENTRY_END
+
+-; upstream query is resolved before this query comes in
++STEP 51 TIME_PASSES ELAPSE 5
++
++; query one last time
+ STEP 60 QUERY
+ ENTRY_BEGIN
+ REPLY RD DO
+@@ -305,7 +307,7 @@ SECTION QUESTION
+ www.example.com. IN A
+ ENTRY_END
+
+-; prefetch query updated the cache, since there is no upstream response in
this range
++; this is the fresh valid response
+ STEP 70 CHECK_ANSWER
+ ENTRY_BEGIN
+ MATCH all ttl
+diff --git a/testdata/serve_expired_ttl_reset.rpl
b/testdata/serve_expired_ttl_reset.rpl
+index faedb1cfc..9f215c96b 100644
+--- a/testdata/serve_expired_ttl_reset.rpl
++++ b/testdata/serve_expired_ttl_reset.rpl
+@@ -16,11 +16,11 @@ SCENARIO_BEGIN Serve expired ttl with reset on forwarder
with a timeout on upstr
+ ; - Wait for it to expire (+ serve-expired-ttl)
+ ; - Send query again
+ ; - Upstream timeouts
+-; - Error response from iterator SERVFAIL, resets expired-ttl on cache
+-; - Check we are getting the SERVFAIL response
++; - Error response from iterator SERVFAIL, resets expired-ttl on cache and
sets norec_ttl blocking recursion
++; - Check we are getting the cached response because it was expired-ttl-reset
+ ; - Query again
+-; - Check we are getting the expired answer
+-; - Upstream still timeouts
++; - Check we are getting the expired answer; prefetching is blocked by
norec_ttl
++; - If there was prefetching the test would fail with the pending upstream
query
+
+ STEP 1 QUERY
+ ENTRY_BEGIN
+@@ -54,7 +54,7 @@ STEP 4 TIME_PASSES ELAPSE 12
+
+ STEP 5 QUERY
+ ENTRY_BEGIN
+-REPLY RD
++REPLY RD DO
+ SECTION QUESTION
+ www.example.com. IN A
+ ENTRY_END
+@@ -67,14 +67,16 @@ STEP 8 TIMEOUT
+ STEP 9 TIMEOUT
+ STEP 10 TIMEOUT
+
+-; Returns servfail
++; Returns
+ ; but error response from iterator resets the expired ttl
+ STEP 11 CHECK_ANSWER
+ ENTRY_BEGIN
+-MATCH all ttl
+-REPLY QR RA RD SERVFAIL
++MATCH all ttl ede=3
++REPLY QR RA RD DO NOERROR
+ SECTION QUESTION
+ www.example.com. IN A
++SECTION ANSWER
++www.example.com. 123 IN A 0.0.0.0
+ ENTRY_END
+
+ ; Query again
+@@ -96,8 +98,4 @@ SECTION ANSWER
+ www.example.com. 123 IN A 0.0.0.0
+ ENTRY_END
+
+-; But the pending query times out!
+-; Only one because RTT reached the limit.
+-STEP 16 TIMEOUT
+-
+ SCENARIO_END
+diff --git a/testdata/serve_expired_val_bogus.rpl
b/testdata/serve_expired_val_bogus.rpl
+index 6c28aa9a6..54b66fe98 100644
+--- a/testdata/serve_expired_val_bogus.rpl
++++ b/testdata/serve_expired_val_bogus.rpl
+@@ -31,12 +31,18 @@ SCENARIO_BEGIN Test serve-expired with client-timeout and
bogus answer
+ ; - (upstream now has a bogus response)
+ ; - query again for www.example.com. IN A
+ ; - check that we get the immediate expired valid response
+-; - (prefetch response is bogus and is not cached)
++; - (prefetch response is bogus and is not cached; recursion is blocked for
NORR_TTL(5) because of the failure)
++; - (upstream has a valid response again)
+ ; - query once more
+-; - check that we still get the immediate expired valid response and not the
fresh bogus one
+-; - (upstream has a valid response again; prefetch will update the cache)
++; - check that we still get the immediate expired valid response (prefetch
will not trigger because of NORR_TTL(5))
++; - query and check that cache was not updated
++; - let NORR_TTL(5) expire
++; - query once more
++; - check that we still get the immediate expired valid response
++; - (prefetch should be allowed to refresh the record at this point)
++; - (upstream does not have the answer anymore)
+ ; - query one last time
+-; - check that we get an immediate valid cache response
++; - check that we get the immediate valid cache response
+
+ ; The example.com NS and ns.example.com A record are commented out.
+ ; This to make the test succeed. It then keeps the dnssec valid lookup.
+@@ -273,6 +279,7 @@ SECTION ADDITIONAL
+ ;ns.example.com. 123 IN RRSIG A 3 3 3600 20070926134150
20070829134150 2854 example.com.
MC0CFQCQMyTjn7WWwpwAR1LlVeLpRgZGuQIUCcJDEkwAuzytTDRlYK7nIMwH1CM= ;{id = 2854}
+ ENTRY_END
+
++; query with response available on the server
+ STEP 40 QUERY
+ ENTRY_BEGIN
+ REPLY RD DO
+@@ -280,7 +287,8 @@ SECTION QUESTION
+ www.example.com. IN A
+ ENTRY_END
+
+-; this is still the immediate cache response because the previous upstream
response was bogus
++; this is still the immediate expired cache response because the previous
upstream response was bogus
++; upstream query did not go out because of the previous failure NORR_TTL(5).
+ STEP 50 CHECK_ANSWER
+ ENTRY_BEGIN
+ MATCH all ttl ede=3
+@@ -298,6 +306,7 @@ SECTION ADDITIONAL
+ ;ns.example.com. 123 IN RRSIG A 3 3 3600 20070926134150
20070829134150 2854 example.com.
MC0CFQCQMyTjn7WWwpwAR1LlVeLpRgZGuQIUCcJDEkwAuzytTDRlYK7nIMwH1CM= ;{id = 2854}
+ ENTRY_END
+
++; query with response available
+ STEP 60 QUERY
+ ENTRY_BEGIN
+ REPLY RD DO
+@@ -305,9 +314,63 @@ SECTION QUESTION
+ www.example.com. IN A
+ ENTRY_END
+
+-; this is the immediate cache response because the previous upstream response
was valid
++; this is still the immediate expired cache response because resolution is
blocked for NORR_TTL(5)
+ STEP 70 CHECK_ANSWER
+ ENTRY_BEGIN
++MATCH all ttl ede=3
++REPLY QR RD RA AD DO NOERROR
++SECTION QUESTION
++www.example.com. IN A
++SECTION ANSWER
++www.example.com. 123 IN A 10.20.30.40
++www.example.com. 123 IN RRSIG A 3 3 3600 20070926134150
20070829134150 2854 example.com.
MC0CFC99iE9K5y2WNgI0gFvBWaTi9wm6AhUAoUqOpDtG5Zct+Qr9F3mSdnbc6V4= ;{id = 2854}
++SECTION AUTHORITY
++;example.com. 123 IN NS ns.example.com.
++;example.com. 123 IN RRSIG NS 3 2 3600 20070926134150
20070829134150 2854 example.com.
MC0CFQCN+qHdJxoI/2tNKwsb08pra/G7aAIUAWA5sDdJTbrXA1/3OaesGBAO3sI= ;{id = 2854}
++SECTION ADDITIONAL
++;ns.example.com. 123 IN A 1.2.3.4
++;ns.example.com. 123 IN RRSIG A 3 3 3600 20070926134150
20070829134150 2854 example.com.
MC0CFQCQMyTjn7WWwpwAR1LlVeLpRgZGuQIUCcJDEkwAuzytTDRlYK7nIMwH1CM= ;{id = 2854}
++ENTRY_END
++
++; expire NORR_TTL(5)
++STEP 71 TIME_PASSES ELAPSE 5
++
++; query again
++STEP 80 QUERY
++ENTRY_BEGIN
++REPLY RD DO
++SECTION QUESTION
++www.example.com. IN A
++ENTRY_END
++
++; this is still the immediate expired cache response but prefetching will be
allowed to update the cache
++STEP 90 CHECK_ANSWER
++ENTRY_BEGIN
++MATCH all ttl ede=3
++REPLY QR RD RA AD DO NOERROR
++SECTION QUESTION
++www.example.com. IN A
++SECTION ANSWER
++www.example.com. 123 IN A 10.20.30.40
++www.example.com. 123 IN RRSIG A 3 3 3600 20070926134150
20070829134150 2854 example.com.
MC0CFC99iE9K5y2WNgI0gFvBWaTi9wm6AhUAoUqOpDtG5Zct+Qr9F3mSdnbc6V4= ;{id = 2854}
++SECTION AUTHORITY
++;example.com. 123 IN NS ns.example.com.
++;example.com. 123 IN RRSIG NS 3 2 3600 20070926134150
20070829134150 2854 example.com.
MC0CFQCN+qHdJxoI/2tNKwsb08pra/G7aAIUAWA5sDdJTbrXA1/3OaesGBAO3sI= ;{id = 2854}
++SECTION ADDITIONAL
++;ns.example.com. 123 IN A 1.2.3.4
++;ns.example.com. 123 IN RRSIG A 3 3 3600 20070926134150
20070829134150 2854 example.com.
MC0CFQCQMyTjn7WWwpwAR1LlVeLpRgZGuQIUCcJDEkwAuzytTDRlYK7nIMwH1CM= ;{id = 2854}
++ENTRY_END
++
++STEP 100 QUERY
++ENTRY_BEGIN
++REPLY RD DO
++SECTION QUESTION
++www.example.com. IN A
++ENTRY_END
++
++; this is the immediate cache response because the previous upstream response
was valid
++STEP 110 CHECK_ANSWER
++ENTRY_BEGIN
+ MATCH all ttl
+ REPLY QR RD RA AD DO NOERROR
+ SECTION QUESTION
+--
+2.47.3
+
diff -Nru
unbound-1.22.0/debian/patches/fix-serve-expired-DNSSEC-information.patch
unbound-1.22.0/debian/patches/fix-serve-expired-DNSSEC-information.patch
--- unbound-1.22.0/debian/patches/fix-serve-expired-DNSSEC-information.patch
1970-01-01 03:00:00.000000000 +0300
+++ unbound-1.22.0/debian/patches/fix-serve-expired-DNSSEC-information.patch
2026-07-22 09:53:15.000000000 +0300
@@ -0,0 +1,580 @@
+From d5e91d181b12bee480b5ff862aedbeab756623cb Mon Sep 17 00:00:00 2001
+From: "W.C.A. Wijngaards" <[email protected]>
+Date: Tue, 5 Nov 2024 10:39:27 +0100
+Subject: [PATCH] - Fix for the serve expired DNSSEC information fix, it would
not allow
+ current delegation information be updated in cache. The fix allows
+ current delegation and validation recursion information to be
+ updated, but as a consequence no longer has certain expired
+ information around for later dnssec valid expired responses.
+Origin: upstream,
https://github.com/NLnetLabs/unbound/commit/d5e91d181b12bee480b5ff862aedbeab756623cb
+Forwarded: not-needed
+Comment: without the doc/Changelog changes
+Comment: preparation for 1174-serve-expired-cache-update-fixes.patch
+
+---
+ cachedb/cachedb.c | 3 +-
+ daemon/cachedump.c | 2 +-
+ dns64/dns64.c | 6 +-
+ doc/Changelog | 7 ++
+ ipsecmod/ipsecmod.c | 3 +-
+ iterator/iter_utils.c | 14 +++-
+ iterator/iter_utils.h | 11 +++-
+ iterator/iterator.c | 21 ++++--
+ pythonmod/pythonmod_utils.c | 2 +-
+ services/cache/dns.c | 6 +-
+ services/cache/dns.h | 5 +-
+ ...serve_expired_client_timeout_val_bogus.rpl | 65 +++++++++++--------
+ testdata/serve_expired_val_bogus.rpl | 65 +++++++++++--------
+ validator/validator.c | 5 +-
+ 14 files changed, 140 insertions(+), 75 deletions(-)
+
+diff --git a/cachedb/cachedb.c b/cachedb/cachedb.c
+index 0329f8458..eca3b7cb7 100644
+--- a/cachedb/cachedb.c
++++ b/cachedb/cachedb.c
+@@ -755,7 +755,8 @@ cachedb_intcache_store(struct module_qstate* qstate, int
msg_expired)
+ }
+ (void)dns_cache_store(qstate->env, &qstate->qinfo,
+ qstate->return_msg->rep, 0, qstate->prefetch_leeway, 0,
+- qstate->region, store_flags, qstate->qstarttime);
++ qstate->region, store_flags, qstate->qstarttime,
++ qstate->is_valrec);
+ if(serve_expired && msg_expired) {
+ if(qstate->env->cfg->serve_expired_client_timeout) {
+ /* No expired response from the query state, the
+diff --git a/daemon/cachedump.c b/daemon/cachedump.c
+index c4f55d8c9..a04b24e56 100644
+--- a/daemon/cachedump.c
++++ b/daemon/cachedump.c
+@@ -692,7 +692,7 @@ load_msg(RES* ssl, sldns_buffer* buf, struct worker*
worker)
+ return 1; /* skip this one, not all references satisfied */
+
+ if(!dns_cache_store(&worker->env, &qinf, &rep, 0, 0, 0, NULL, flags,
+- *worker->env.now)) {
++ *worker->env.now, 1)) {
+ log_warn("error out of memory");
+ return 0;
+ }
+diff --git a/dns64/dns64.c b/dns64/dns64.c
+index 0db174ba9..f028cd28a 100644
+--- a/dns64/dns64.c
++++ b/dns64/dns64.c
+@@ -658,7 +658,8 @@ handle_event_moddone(struct module_qstate* qstate, int id)
+ !dns_cache_store(
+ qstate->env, &qstate->qinfo, qstate->return_msg->rep,
+ 0, qstate->prefetch_leeway, 0, NULL,
+- qstate->query_flags, qstate->qstarttime))
++ qstate->query_flags, qstate->qstarttime,
++ qstate->is_valrec))
+ log_err("out of memory");
+
+ /* do nothing */
+@@ -1008,7 +1009,8 @@ 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, super->prefetch_leeway, 0, NULL, super->query_flags,
qstate->qstarttime))
++ 0, super->prefetch_leeway, 0, NULL, super->query_flags,
++ qstate->qstarttime, qstate->is_valrec))
+ log_err("out of memory");
+ }
+
+diff --git a/ipsecmod/ipsecmod.c b/ipsecmod/ipsecmod.c
+index 76f9b1965..1c9e6e6a5 100644
+--- a/ipsecmod/ipsecmod.c
++++ b/ipsecmod/ipsecmod.c
+@@ -456,7 +456,8 @@ 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, qstate->qstarttime)) {
++ 0, qstate->region, qstate->query_flags, qstate->qstarttime,
++ qstate->is_valrec)) {
+ 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 cacba420e..5198e9ef2 100644
+--- a/iterator/iter_utils.c
++++ b/iterator/iter_utils.c
+@@ -693,10 +693,11 @@ 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, time_t qstarttime)
++ struct regional* region, uint16_t flags, time_t qstarttime,
++ int is_valrec)
+ {
+ if(!dns_cache_store(env, msgqinf, msgrep, is_referral, leeway,
+- pside, region, flags, qstarttime))
++ pside, region, flags, qstarttime, is_valrec))
+ log_err("out of memory: cannot store data in cache");
+ }
+
+@@ -1606,3 +1607,12 @@ limit_nsec_ttl(struct dns_msg* msg)
+ }
+ }
+ }
++
++void
++iter_make_minimal(struct reply_info* rep)
++{
++ size_t rem = rep->ns_numrrsets + rep->ar_numrrsets;
++ rep->ns_numrrsets = 0;
++ rep->ar_numrrsets = 0;
++ rep->rrset_count -= rem;
++}
+diff --git a/iterator/iter_utils.h b/iterator/iter_utils.h
+index 0361e4377..30b3dbe5f 100644
+--- a/iterator/iter_utils.h
++++ b/iterator/iter_utils.h
+@@ -142,6 +142,7 @@ struct dns_msg* dns_copy_msg(struct dns_msg* from, struct
regional* regional);
+ * @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.
++ * @param is_valrec: if the query is validation recursion and does not get
+ * 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.
+@@ -150,7 +151,8 @@ 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, time_t qstarttime);
++ struct regional* region, uint16_t flags, time_t qstarttime,
++ int is_valrec);
+
+ /**
+ * Select randomly with n/m probability.
+@@ -435,4 +437,11 @@ void iterator_set_ip46_support(struct module_stack* mods,
+ */
+ void limit_nsec_ttl(struct dns_msg* msg);
+
++/**
++ * Make the response minimal. Removed authority and additional section,
++ * that works when there is an answer in the answer section.
++ * @param rep: reply to modify.
++ */
++void iter_make_minimal(struct reply_info* rep);
++
+ #endif /* ITERATOR_ITER_UTILS_H */
+diff --git a/iterator/iterator.c b/iterator/iterator.c
+index 59e4b36ce..562d2771e 100644
+--- a/iterator/iterator.c
++++ b/iterator/iterator.c
+@@ -368,7 +368,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->qstarttime);
++ qstate->query_flags, qstate->qstarttime, qstate->is_valrec);
+ return error_response(qstate, id, rcode);
+ }
+
+@@ -3296,6 +3296,16 @@ processQueryResponse(struct module_qstate* qstate,
struct iter_qstate* iq,
+ iq->num_target_queries = 0;
+ return processDSNSFind(qstate, iq, id);
+ }
++ if(iq->qchase.qtype == LDNS_RR_TYPE_DNSKEY && SERVE_EXPIRED
++ && qstate->is_valrec &&
++ reply_find_answer_rrset(&iq->qchase, iq->response->rep)
!= NULL) {
++ /* clean out the authority section, if any, so it
++ * does not overwrite dnssec valid data in the
++ * validation recursion lookup. */
++ verbose(VERB_ALGO, "make DNSKEY minimal for serve "
++ "expired");
++ iter_make_minimal(iq->response->rep);
++ }
+ if(!qstate->no_cache_store)
+ iter_dns_store(qstate->env, &iq->response->qinfo,
+ iq->response->rep,
+@@ -3303,7 +3313,7 @@ processQueryResponse(struct module_qstate* qstate,
struct iter_qstate* iq,
+ qstate->prefetch_leeway,
+ iq->dp&&iq->dp->has_parent_side_NS,
+ qstate->region, qstate->query_flags,
+- qstate->qstarttime);
++ qstate->qstarttime, qstate->is_valrec);
+ /* close down outstanding requests to be discarded */
+ outbound_list_clear(&iq->outlist);
+ iq->num_current_queries = 0;
+@@ -3397,7 +3407,7 @@ processQueryResponse(struct module_qstate* qstate,
struct iter_qstate* iq,
+ /* no prefetch-leeway, since its not the answer */
+ iter_dns_store(qstate->env, &iq->response->qinfo,
+ iq->response->rep, 1, 0, 0, NULL, 0,
+- qstate->qstarttime);
++ qstate->qstarttime, qstate->is_valrec);
+ if(iq->store_parent_NS)
+ iter_store_parentside_NS(qstate->env,
+ iq->response->rep);
+@@ -3527,7 +3537,8 @@ 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->qstarttime);
++ qstate->query_flags, qstate->qstarttime,
++ qstate->is_valrec);
+ /* set the current request's qname to the new value. */
+ iq->qchase.qname = sname;
+ iq->qchase.qname_len = snamelen;
+@@ -4154,7 +4165,7 @@ processFinished(struct module_qstate* qstate, struct
iter_qstate* iq,
+ iq->response->rep, 0, qstate->prefetch_leeway,
+ iq->dp&&iq->dp->has_parent_side_NS,
+ qstate->region, qstate->query_flags,
+- qstate->qstarttime);
++ qstate->qstarttime, qstate->is_valrec);
+ }
+ }
+ qstate->return_rcode = LDNS_RCODE_NOERROR;
+diff --git a/pythonmod/pythonmod_utils.c b/pythonmod/pythonmod_utils.c
+index aebe4d2bb..fc9921aa1 100644
+--- a/pythonmod/pythonmod_utils.c
++++ b/pythonmod/pythonmod_utils.c
+@@ -73,7 +73,7 @@ int storeQueryInCache(struct module_qstate* qstate, struct
query_info* qinfo,
+
+ return dns_cache_store(qstate->env, qinfo, msgrep, is_referral,
+ qstate->prefetch_leeway, 0, NULL, qstate->query_flags,
+- qstate->qstarttime);
++ qstate->qstarttime, qstate->is_valrec);
+ }
+
+ /* Invalidate the message associated with query_info stored in message cache
*/
+diff --git a/services/cache/dns.c b/services/cache/dns.c
+index 7ab63bacf..d9536c0e7 100644
+--- a/services/cache/dns.c
++++ b/services/cache/dns.c
+@@ -1057,7 +1057,8 @@ 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, time_t qstarttime)
++ struct regional* region, uint32_t flags, time_t qstarttime,
++ int is_valrec)
+ {
+ struct reply_info* rep = NULL;
+ if(SERVE_EXPIRED) {
+@@ -1079,7 +1080,8 @@ dns_cache_store(struct module_env* env, struct
query_info* msgqinf,
+ * one and let the validator manage caching. */
+ && cached->security != sec_status_bogus
+ && (env->need_to_validate &&
+- msgrep->security == sec_status_unchecked)) {
++ msgrep->security == sec_status_unchecked)
++ && !is_valrec) {
+ verbose(VERB_ALGO, "a validated expired entry "
+ "could be overwritten, skip caching "
+ "the new message at this stage");
+diff --git a/services/cache/dns.h b/services/cache/dns.h
+index 1dd537d2b..8aa6b44bc 100644
+--- a/services/cache/dns.h
++++ b/services/cache/dns.h
+@@ -90,11 +90,14 @@ struct dns_msg {
+ * (See DNSCACHE_STORE_xxx flags).
+ * @param qstarttime: time when the query was started, and thus when the
+ * delegations were looked up.
++ * @param is_valrec: if the query is validation recursion and does not get
++ * dnssec validation itself.
+ * @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, time_t qstarttime);
++ struct regional* region, uint32_t flags, time_t qstarttime,
++ int is_valrec);
+
+ /**
+ * Store message in the cache. Stores in message cache and rrset cache.
+diff --git a/testdata/serve_expired_client_timeout_val_bogus.rpl
b/testdata/serve_expired_client_timeout_val_bogus.rpl
+index f4937a16c..4334168cd 100644
+--- a/testdata/serve_expired_client_timeout_val_bogus.rpl
++++ b/testdata/serve_expired_client_timeout_val_bogus.rpl
+@@ -38,6 +38,15 @@ SCENARIO_BEGIN Test serve-expired with client-timeout and
bogus answer
+ ; - query one last time
+ ; - check that we get the immediate valid cache response; upstream does not
have an answer at this moment
+
++; The example.com NS and ns.example.com A record are commented out.
++; This to make the test succeed. It then keeps the dnssec valid lookup.
++; Otherwise, the relookup of the referral would overwrite the example.com NS
++; the serve expired response would no longer be valid. But this record must
++; be cached, for keeping the current delegation information.
++; Also the DNSKEY lookup authority and additional are cleaned to stop
overwrite
++; of the NS and A record. This is more likely to keep the serve expired
++; information intact.
++
+ ;;
+ ;; K.ROOT-SERVERS.NET.
+ ;;
+@@ -150,12 +159,12 @@ RANGE_BEGIN 0 10
+ www.example.com. IN A
+ SECTION ANSWER
+ www.example.com. IN A 10.20.30.40
+- ns.example.com. 3600 IN RRSIG A 3 3 3600 20070926134150
20070829134150 2854 example.com.
MC0CFQCQMyTjn7WWwpwAR1LlVeLpRgZGuQIUCcJDEkwAuzytTDRlYK7nIMwH1CM= ;{id = 2854}
++ ;ns.example.com. 3600 IN RRSIG A 3 3 3600 20070926134150
20070829134150 2854 example.com.
MC0CFQCQMyTjn7WWwpwAR1LlVeLpRgZGuQIUCcJDEkwAuzytTDRlYK7nIMwH1CM= ;{id = 2854}
+ SECTION AUTHORITY
+- example.com. IN NS ns.example.com.
+- example.com. 3600 IN RRSIG NS 3 2 3600 20070926134150
20070829134150 2854 example.com.
MC0CFQCN+qHdJxoI/2tNKwsb08pra/G7aAIUAWA5sDdJTbrXA1/3OaesGBAO3sI= ;{id = 2854}
++ ;example.com. IN NS ns.example.com.
++ ;example.com. 3600 IN RRSIG NS 3 2 3600 20070926134150
20070829134150 2854 example.com.
MC0CFQCN+qHdJxoI/2tNKwsb08pra/G7aAIUAWA5sDdJTbrXA1/3OaesGBAO3sI= ;{id = 2854}
+ SECTION ADDITIONAL
+- ns.example.com. IN A 1.2.3.4
++ ;ns.example.com. IN A 1.2.3.4
+ www.example.com. 3600 IN RRSIG A 3 3 3600 20070926134150
20070829134150 2854 example.com.
MC0CFC99iE9K5y2WNgI0gFvBWaTi9wm6AhUAoUqOpDtG5Zct+Qr9F3mSdnbc6V4= ;{id = 2854}
+ ENTRY_END
+ RANGE_END
+@@ -174,12 +183,12 @@ RANGE_BEGIN 20 30
+ www.example.com. IN A
+ SECTION ANSWER
+ www.example.com. IN A 10.20.30.40
+- ns.example.com. 3600 IN RRSIG A 3 3 3600 20070926134150
20070829134150 2854 example.com.
MC0CFQCQMyTjn7WWwpwAR1LlVeLpRgZGuQIUCcJDEkwAuzytTDRlYK7nIMwH1CM= ;{id = 2854}
++ ;ns.example.com. 3600 IN RRSIG A 3 3 3600 20070926134150
20070829134150 2854 example.com.
MC0CFQCQMyTjn7WWwpwAR1LlVeLpRgZGuQIUCcJDEkwAuzytTDRlYK7nIMwH1CM= ;{id = 2854}
+ SECTION AUTHORITY
+- example.com. IN NS ns.example.com.
+- example.com. 3600 IN RRSIG NS 3 2 3600 20070926134150
20070829134150 2854 example.com.
MC0CFQCN+qHdJxoI/2tNKwsb08pra/G7aAIUAWA5sDdJTbrXA1/3OaesGBAO3sI= ;{id = 2854}
++ ;example.com. IN NS ns.example.com.
++ ;example.com. 3600 IN RRSIG NS 3 2 3600 20070926134150
20070829134150 2854 example.com.
MC0CFQCN+qHdJxoI/2tNKwsb08pra/G7aAIUAWA5sDdJTbrXA1/3OaesGBAO3sI= ;{id = 2854}
+ SECTION ADDITIONAL
+- ns.example.com. IN A 1.2.3.4
++ ;ns.example.com. IN A 1.2.3.4
+ ;; (valid signature)
+ ;; www.example.com. 3600 IN RRSIG A 3 3 3600
20070926134150 20070829134150 2854 example.com.
MC0CFC99iE9K5y2WNgI0gFvBWaTi9wm6AhUAoUqOpDtG5Zct+Qr9F3mSdnbc6V4= ;{id = 2854}
+ ;; (bogus signature)
+@@ -201,12 +210,12 @@ RANGE_BEGIN 40 60
+ www.example.com. IN A
+ SECTION ANSWER
+ www.example.com. IN A 10.20.30.40
+- ns.example.com. 3600 IN RRSIG A 3 3 3600 20070926134150
20070829134150 2854 example.com.
MC0CFQCQMyTjn7WWwpwAR1LlVeLpRgZGuQIUCcJDEkwAuzytTDRlYK7nIMwH1CM= ;{id = 2854}
++ ;ns.example.com. 3600 IN RRSIG A 3 3 3600 20070926134150
20070829134150 2854 example.com.
MC0CFQCQMyTjn7WWwpwAR1LlVeLpRgZGuQIUCcJDEkwAuzytTDRlYK7nIMwH1CM= ;{id = 2854}
+ SECTION AUTHORITY
+- example.com. IN NS ns.example.com.
+- example.com. 3600 IN RRSIG NS 3 2 3600 20070926134150
20070829134150 2854 example.com.
MC0CFQCN+qHdJxoI/2tNKwsb08pra/G7aAIUAWA5sDdJTbrXA1/3OaesGBAO3sI= ;{id = 2854}
++ ;example.com. IN NS ns.example.com.
++ ;example.com. 3600 IN RRSIG NS 3 2 3600 20070926134150
20070829134150 2854 example.com.
MC0CFQCN+qHdJxoI/2tNKwsb08pra/G7aAIUAWA5sDdJTbrXA1/3OaesGBAO3sI= ;{id = 2854}
+ SECTION ADDITIONAL
+- ns.example.com. IN A 1.2.3.4
++ ;ns.example.com. IN A 1.2.3.4
+ www.example.com. 3600 IN RRSIG A 3 3 3600 20070926134150
20070829134150 2854 example.com.
MC0CFC99iE9K5y2WNgI0gFvBWaTi9wm6AhUAoUqOpDtG5Zct+Qr9F3mSdnbc6V4= ;{id = 2854}
+ ENTRY_END
+ RANGE_END
+@@ -229,11 +238,11 @@ SECTION ANSWER
+ www.example.com. IN A 10.20.30.40
+ www.example.com. 3600 IN RRSIG A 3 3 3600 20070926134150
20070829134150 2854 example.com.
MC0CFC99iE9K5y2WNgI0gFvBWaTi9wm6AhUAoUqOpDtG5Zct+Qr9F3mSdnbc6V4= ;{id = 2854}
+ SECTION AUTHORITY
+-example.com. IN NS ns.example.com.
+-example.com. 3600 IN RRSIG NS 3 2 3600 20070926134150
20070829134150 2854 example.com.
MC0CFQCN+qHdJxoI/2tNKwsb08pra/G7aAIUAWA5sDdJTbrXA1/3OaesGBAO3sI= ;{id = 2854}
++;example.com. IN NS ns.example.com.
++;example.com. 3600 IN RRSIG NS 3 2 3600 20070926134150
20070829134150 2854 example.com.
MC0CFQCN+qHdJxoI/2tNKwsb08pra/G7aAIUAWA5sDdJTbrXA1/3OaesGBAO3sI= ;{id = 2854}
+ SECTION ADDITIONAL
+-ns.example.com. IN A 1.2.3.4
+-ns.example.com. 3600 IN RRSIG A 3 3 3600 20070926134150
20070829134150 2854 example.com.
MC0CFQCQMyTjn7WWwpwAR1LlVeLpRgZGuQIUCcJDEkwAuzytTDRlYK7nIMwH1CM= ;{id = 2854}
++;ns.example.com. IN A 1.2.3.4
++;ns.example.com. 3600 IN RRSIG A 3 3 3600 20070926134150
20070829134150 2854 example.com.
MC0CFQCQMyTjn7WWwpwAR1LlVeLpRgZGuQIUCcJDEkwAuzytTDRlYK7nIMwH1CM= ;{id = 2854}
+ ENTRY_END
+
+ STEP 11 TIME_PASSES ELAPSE 3601
+@@ -256,11 +265,11 @@ SECTION ANSWER
+ www.example.com. 123 IN A 10.20.30.40
+ www.example.com. 123 IN RRSIG A 3 3 3600 20070926134150
20070829134150 2854 example.com.
MC0CFC99iE9K5y2WNgI0gFvBWaTi9wm6AhUAoUqOpDtG5Zct+Qr9F3mSdnbc6V4= ;{id = 2854}
+ SECTION AUTHORITY
+-example.com. 123 IN NS ns.example.com.
+-example.com. 123 IN RRSIG NS 3 2 3600 20070926134150
20070829134150 2854 example.com.
MC0CFQCN+qHdJxoI/2tNKwsb08pra/G7aAIUAWA5sDdJTbrXA1/3OaesGBAO3sI= ;{id = 2854}
++;example.com. 123 IN NS ns.example.com.
++;example.com. 123 IN RRSIG NS 3 2 3600 20070926134150
20070829134150 2854 example.com.
MC0CFQCN+qHdJxoI/2tNKwsb08pra/G7aAIUAWA5sDdJTbrXA1/3OaesGBAO3sI= ;{id = 2854}
+ SECTION ADDITIONAL
+-ns.example.com. 123 IN A 1.2.3.4
+-ns.example.com. 123 IN RRSIG A 3 3 3600 20070926134150
20070829134150 2854 example.com.
MC0CFQCQMyTjn7WWwpwAR1LlVeLpRgZGuQIUCcJDEkwAuzytTDRlYK7nIMwH1CM= ;{id = 2854}
++;ns.example.com. 123 IN A 1.2.3.4
++;ns.example.com. 123 IN RRSIG A 3 3 3600 20070926134150
20070829134150 2854 example.com.
MC0CFQCQMyTjn7WWwpwAR1LlVeLpRgZGuQIUCcJDEkwAuzytTDRlYK7nIMwH1CM= ;{id = 2854}
+ ENTRY_END
+
+ STEP 40 QUERY
+@@ -281,11 +290,11 @@ SECTION ANSWER
+ www.example.com. 123 IN A 10.20.30.40
+ www.example.com. 123 IN RRSIG A 3 3 3600 20070926134150
20070829134150 2854 example.com.
MC0CFC99iE9K5y2WNgI0gFvBWaTi9wm6AhUAoUqOpDtG5Zct+Qr9F3mSdnbc6V4= ;{id = 2854}
+ SECTION AUTHORITY
+-example.com. 123 IN NS ns.example.com.
+-example.com. 123 IN RRSIG NS 3 2 3600 20070926134150
20070829134150 2854 example.com.
MC0CFQCN+qHdJxoI/2tNKwsb08pra/G7aAIUAWA5sDdJTbrXA1/3OaesGBAO3sI= ;{id = 2854}
++;example.com. 123 IN NS ns.example.com.
++;example.com. 123 IN RRSIG NS 3 2 3600 20070926134150
20070829134150 2854 example.com.
MC0CFQCN+qHdJxoI/2tNKwsb08pra/G7aAIUAWA5sDdJTbrXA1/3OaesGBAO3sI= ;{id = 2854}
+ SECTION ADDITIONAL
+-ns.example.com. 123 IN A 1.2.3.4
+-ns.example.com. 123 IN RRSIG A 3 3 3600 20070926134150
20070829134150 2854 example.com.
MC0CFQCQMyTjn7WWwpwAR1LlVeLpRgZGuQIUCcJDEkwAuzytTDRlYK7nIMwH1CM= ;{id = 2854}
++;ns.example.com. 123 IN A 1.2.3.4
++;ns.example.com. 123 IN RRSIG A 3 3 3600 20070926134150
20070829134150 2854 example.com.
MC0CFQCQMyTjn7WWwpwAR1LlVeLpRgZGuQIUCcJDEkwAuzytTDRlYK7nIMwH1CM= ;{id = 2854}
+ ENTRY_END
+
+ ; upstream query is resolved before this query comes in
+@@ -307,11 +316,11 @@ SECTION ANSWER
+ www.example.com. IN A 10.20.30.40
+ www.example.com. 3600 IN RRSIG A 3 3 3600 20070926134150
20070829134150 2854 example.com.
MC0CFC99iE9K5y2WNgI0gFvBWaTi9wm6AhUAoUqOpDtG5Zct+Qr9F3mSdnbc6V4= ;{id = 2854}
+ SECTION AUTHORITY
+-example.com. IN NS ns.example.com.
+-example.com. 3600 IN RRSIG NS 3 2 3600 20070926134150
20070829134150 2854 example.com.
MC0CFQCN+qHdJxoI/2tNKwsb08pra/G7aAIUAWA5sDdJTbrXA1/3OaesGBAO3sI= ;{id = 2854}
++;example.com. IN NS ns.example.com.
++;example.com. 3600 IN RRSIG NS 3 2 3600 20070926134150
20070829134150 2854 example.com.
MC0CFQCN+qHdJxoI/2tNKwsb08pra/G7aAIUAWA5sDdJTbrXA1/3OaesGBAO3sI= ;{id = 2854}
+ SECTION ADDITIONAL
+-ns.example.com. IN A 1.2.3.4
+-ns.example.com. 3600 IN RRSIG A 3 3 3600 20070926134150
20070829134150 2854 example.com.
MC0CFQCQMyTjn7WWwpwAR1LlVeLpRgZGuQIUCcJDEkwAuzytTDRlYK7nIMwH1CM= ;{id = 2854}
++;ns.example.com. IN A 1.2.3.4
++;ns.example.com. 3600 IN RRSIG A 3 3 3600 20070926134150
20070829134150 2854 example.com.
MC0CFQCQMyTjn7WWwpwAR1LlVeLpRgZGuQIUCcJDEkwAuzytTDRlYK7nIMwH1CM= ;{id = 2854}
+ ENTRY_END
+
+ SCENARIO_END
+diff --git a/testdata/serve_expired_val_bogus.rpl
b/testdata/serve_expired_val_bogus.rpl
+index 35365beef..49afcc0eb 100644
+--- a/testdata/serve_expired_val_bogus.rpl
++++ b/testdata/serve_expired_val_bogus.rpl
+@@ -37,6 +37,15 @@ SCENARIO_BEGIN Test serve-expired with client-timeout and
bogus answer
+ ; - query one last time
+ ; - check that we get an immediate valid cache response
+
++; The example.com NS and ns.example.com A record are commented out.
++; This to make the test succeed. It then keeps the dnssec valid lookup.
++; Otherwise, the relookup of the referral would overwrite the example.com NS
++; the serve expired response would no longer be valid. But this record must
++; be cached, for keeping the current delegation information.
++; Also the DNSKEY lookup authority and additional are cleaned to stop
overwrite
++; of the NS and A record. This is more likely to keep the serve expired
++; information intact.
++
+ ;;
+ ;; K.ROOT-SERVERS.NET.
+ ;;
+@@ -149,12 +158,12 @@ RANGE_BEGIN 0 10
+ www.example.com. IN A
+ SECTION ANSWER
+ www.example.com. IN A 10.20.30.40
+- ns.example.com. 3600 IN RRSIG A 3 3 3600 20070926134150
20070829134150 2854 example.com.
MC0CFQCQMyTjn7WWwpwAR1LlVeLpRgZGuQIUCcJDEkwAuzytTDRlYK7nIMwH1CM= ;{id = 2854}
++ ;ns.example.com. 3600 IN RRSIG A 3 3 3600 20070926134150
20070829134150 2854 example.com.
MC0CFQCQMyTjn7WWwpwAR1LlVeLpRgZGuQIUCcJDEkwAuzytTDRlYK7nIMwH1CM= ;{id = 2854}
+ SECTION AUTHORITY
+- example.com. IN NS ns.example.com.
+- example.com. 3600 IN RRSIG NS 3 2 3600 20070926134150
20070829134150 2854 example.com.
MC0CFQCN+qHdJxoI/2tNKwsb08pra/G7aAIUAWA5sDdJTbrXA1/3OaesGBAO3sI= ;{id = 2854}
++ ;example.com. IN NS ns.example.com.
++ ;example.com. 3600 IN RRSIG NS 3 2 3600 20070926134150
20070829134150 2854 example.com.
MC0CFQCN+qHdJxoI/2tNKwsb08pra/G7aAIUAWA5sDdJTbrXA1/3OaesGBAO3sI= ;{id = 2854}
+ SECTION ADDITIONAL
+- ns.example.com. IN A 1.2.3.4
++ ;ns.example.com. IN A 1.2.3.4
+ www.example.com. 3600 IN RRSIG A 3 3 3600 20070926134150
20070829134150 2854 example.com.
MC0CFC99iE9K5y2WNgI0gFvBWaTi9wm6AhUAoUqOpDtG5Zct+Qr9F3mSdnbc6V4= ;{id = 2854}
+ ENTRY_END
+ RANGE_END
+@@ -173,12 +182,12 @@ RANGE_BEGIN 20 40
+ www.example.com. IN A
+ SECTION ANSWER
+ www.example.com. IN A 10.20.30.40
+- ns.example.com. 3600 IN RRSIG A 3 3 3600 20070926134150
20070829134150 2854 example.com.
MC0CFQCQMyTjn7WWwpwAR1LlVeLpRgZGuQIUCcJDEkwAuzytTDRlYK7nIMwH1CM= ;{id = 2854}
++ ;ns.example.com. 3600 IN RRSIG A 3 3 3600 20070926134150
20070829134150 2854 example.com.
MC0CFQCQMyTjn7WWwpwAR1LlVeLpRgZGuQIUCcJDEkwAuzytTDRlYK7nIMwH1CM= ;{id = 2854}
+ SECTION AUTHORITY
+- example.com. IN NS ns.example.com.
+- example.com. 3600 IN RRSIG NS 3 2 3600 20070926134150
20070829134150 2854 example.com.
MC0CFQCN+qHdJxoI/2tNKwsb08pra/G7aAIUAWA5sDdJTbrXA1/3OaesGBAO3sI= ;{id = 2854}
++ ;example.com. IN NS ns.example.com.
++ ;example.com. 3600 IN RRSIG NS 3 2 3600 20070926134150
20070829134150 2854 example.com.
MC0CFQCN+qHdJxoI/2tNKwsb08pra/G7aAIUAWA5sDdJTbrXA1/3OaesGBAO3sI= ;{id = 2854}
+ SECTION ADDITIONAL
+- ns.example.com. IN A 1.2.3.4
++ ;ns.example.com. IN A 1.2.3.4
+ ;; (valid signature)
+ ;; www.example.com. 3600 IN RRSIG A 3 3 3600
20070926134150 20070829134150 2854 example.com.
MC0CFC99iE9K5y2WNgI0gFvBWaTi9wm6AhUAoUqOpDtG5Zct+Qr9F3mSdnbc6V4= ;{id = 2854}
+ ;; (bogus signature)
+@@ -200,12 +209,12 @@ RANGE_BEGIN 50 100
+ www.example.com. IN A
+ SECTION ANSWER
+ www.example.com. IN A 10.20.30.40
+- ns.example.com. 3600 IN RRSIG A 3 3 3600 20070926134150
20070829134150 2854 example.com.
MC0CFQCQMyTjn7WWwpwAR1LlVeLpRgZGuQIUCcJDEkwAuzytTDRlYK7nIMwH1CM= ;{id = 2854}
++ ;ns.example.com. 3600 IN RRSIG A 3 3 3600 20070926134150
20070829134150 2854 example.com.
MC0CFQCQMyTjn7WWwpwAR1LlVeLpRgZGuQIUCcJDEkwAuzytTDRlYK7nIMwH1CM= ;{id = 2854}
+ SECTION AUTHORITY
+- example.com. IN NS ns.example.com.
+- example.com. 3600 IN RRSIG NS 3 2 3600 20070926134150
20070829134150 2854 example.com.
MC0CFQCN+qHdJxoI/2tNKwsb08pra/G7aAIUAWA5sDdJTbrXA1/3OaesGBAO3sI= ;{id = 2854}
++ ;example.com. IN NS ns.example.com.
++ ;example.com. 3600 IN RRSIG NS 3 2 3600 20070926134150
20070829134150 2854 example.com.
MC0CFQCN+qHdJxoI/2tNKwsb08pra/G7aAIUAWA5sDdJTbrXA1/3OaesGBAO3sI= ;{id = 2854}
+ SECTION ADDITIONAL
+- ns.example.com. IN A 1.2.3.4
++ ;ns.example.com. IN A 1.2.3.4
+ www.example.com. 3600 IN RRSIG A 3 3 3600 20070926134150
20070829134150 2854 example.com.
MC0CFC99iE9K5y2WNgI0gFvBWaTi9wm6AhUAoUqOpDtG5Zct+Qr9F3mSdnbc6V4= ;{id = 2854}
+ ENTRY_END
+ RANGE_END
+@@ -229,11 +238,11 @@ SECTION ANSWER
+ www.example.com. IN A 10.20.30.40
+ www.example.com. 3600 IN RRSIG A 3 3 3600 20070926134150
20070829134150 2854 example.com.
MC0CFC99iE9K5y2WNgI0gFvBWaTi9wm6AhUAoUqOpDtG5Zct+Qr9F3mSdnbc6V4= ;{id = 2854}
+ SECTION AUTHORITY
+-example.com. IN NS ns.example.com.
+-example.com. 3600 IN RRSIG NS 3 2 3600 20070926134150
20070829134150 2854 example.com.
MC0CFQCN+qHdJxoI/2tNKwsb08pra/G7aAIUAWA5sDdJTbrXA1/3OaesGBAO3sI= ;{id = 2854}
++;example.com. IN NS ns.example.com.
++;example.com. 3600 IN RRSIG NS 3 2 3600 20070926134150
20070829134150 2854 example.com.
MC0CFQCN+qHdJxoI/2tNKwsb08pra/G7aAIUAWA5sDdJTbrXA1/3OaesGBAO3sI= ;{id = 2854}
+ SECTION ADDITIONAL
+-ns.example.com. IN A 1.2.3.4
+-ns.example.com. 3600 IN RRSIG A 3 3 3600 20070926134150
20070829134150 2854 example.com.
MC0CFQCQMyTjn7WWwpwAR1LlVeLpRgZGuQIUCcJDEkwAuzytTDRlYK7nIMwH1CM= ;{id = 2854}
++;ns.example.com. IN A 1.2.3.4
++;ns.example.com. 3600 IN RRSIG A 3 3 3600 20070926134150
20070829134150 2854 example.com.
MC0CFQCQMyTjn7WWwpwAR1LlVeLpRgZGuQIUCcJDEkwAuzytTDRlYK7nIMwH1CM= ;{id = 2854}
+ ENTRY_END
+
+ STEP 11 TIME_PASSES ELAPSE 3601
+@@ -256,11 +265,11 @@ SECTION ANSWER
+ www.example.com. 123 IN A 10.20.30.40
+ www.example.com. 123 IN RRSIG A 3 3 3600 20070926134150
20070829134150 2854 example.com.
MC0CFC99iE9K5y2WNgI0gFvBWaTi9wm6AhUAoUqOpDtG5Zct+Qr9F3mSdnbc6V4= ;{id = 2854}
+ SECTION AUTHORITY
+-example.com. 123 IN NS ns.example.com.
+-example.com. 123 IN RRSIG NS 3 2 3600 20070926134150
20070829134150 2854 example.com.
MC0CFQCN+qHdJxoI/2tNKwsb08pra/G7aAIUAWA5sDdJTbrXA1/3OaesGBAO3sI= ;{id = 2854}
++;example.com. 123 IN NS ns.example.com.
++;example.com. 123 IN RRSIG NS 3 2 3600 20070926134150
20070829134150 2854 example.com.
MC0CFQCN+qHdJxoI/2tNKwsb08pra/G7aAIUAWA5sDdJTbrXA1/3OaesGBAO3sI= ;{id = 2854}
+ SECTION ADDITIONAL
+-ns.example.com. 123 IN A 1.2.3.4
+-ns.example.com. 123 IN RRSIG A 3 3 3600 20070926134150
20070829134150 2854 example.com.
MC0CFQCQMyTjn7WWwpwAR1LlVeLpRgZGuQIUCcJDEkwAuzytTDRlYK7nIMwH1CM= ;{id = 2854}
++;ns.example.com. 123 IN A 1.2.3.4
++;ns.example.com. 123 IN RRSIG A 3 3 3600 20070926134150
20070829134150 2854 example.com.
MC0CFQCQMyTjn7WWwpwAR1LlVeLpRgZGuQIUCcJDEkwAuzytTDRlYK7nIMwH1CM= ;{id = 2854}
+ ENTRY_END
+
+ STEP 40 QUERY
+@@ -281,11 +290,11 @@ SECTION ANSWER
+ www.example.com. 123 IN A 10.20.30.40
+ www.example.com. 123 IN RRSIG A 3 3 3600 20070926134150
20070829134150 2854 example.com.
MC0CFC99iE9K5y2WNgI0gFvBWaTi9wm6AhUAoUqOpDtG5Zct+Qr9F3mSdnbc6V4= ;{id = 2854}
+ SECTION AUTHORITY
+-example.com. 123 IN NS ns.example.com.
+-example.com. 123 IN RRSIG NS 3 2 3600 20070926134150
20070829134150 2854 example.com.
MC0CFQCN+qHdJxoI/2tNKwsb08pra/G7aAIUAWA5sDdJTbrXA1/3OaesGBAO3sI= ;{id = 2854}
++;example.com. 123 IN NS ns.example.com.
++;example.com. 123 IN RRSIG NS 3 2 3600 20070926134150
20070829134150 2854 example.com.
MC0CFQCN+qHdJxoI/2tNKwsb08pra/G7aAIUAWA5sDdJTbrXA1/3OaesGBAO3sI= ;{id = 2854}
+ SECTION ADDITIONAL
+-ns.example.com. 123 IN A 1.2.3.4
+-ns.example.com. 123 IN RRSIG A 3 3 3600 20070926134150
20070829134150 2854 example.com.
MC0CFQCQMyTjn7WWwpwAR1LlVeLpRgZGuQIUCcJDEkwAuzytTDRlYK7nIMwH1CM= ;{id = 2854}
++;ns.example.com. 123 IN A 1.2.3.4
++;ns.example.com. 123 IN RRSIG A 3 3 3600 20070926134150
20070829134150 2854 example.com.
MC0CFQCQMyTjn7WWwpwAR1LlVeLpRgZGuQIUCcJDEkwAuzytTDRlYK7nIMwH1CM= ;{id = 2854}
+ ENTRY_END
+
+ STEP 60 QUERY
+@@ -306,11 +315,11 @@ SECTION ANSWER
+ www.example.com. IN A 10.20.30.40
+ www.example.com. 3600 IN RRSIG A 3 3 3600 20070926134150
20070829134150 2854 example.com.
MC0CFC99iE9K5y2WNgI0gFvBWaTi9wm6AhUAoUqOpDtG5Zct+Qr9F3mSdnbc6V4= ;{id = 2854}
+ SECTION AUTHORITY
+-example.com. IN NS ns.example.com.
+-example.com. 3600 IN RRSIG NS 3 2 3600 20070926134150
20070829134150 2854 example.com.
MC0CFQCN+qHdJxoI/2tNKwsb08pra/G7aAIUAWA5sDdJTbrXA1/3OaesGBAO3sI= ;{id = 2854}
++;example.com. IN NS ns.example.com.
++;example.com. 3600 IN RRSIG NS 3 2 3600 20070926134150
20070829134150 2854 example.com.
MC0CFQCN+qHdJxoI/2tNKwsb08pra/G7aAIUAWA5sDdJTbrXA1/3OaesGBAO3sI= ;{id = 2854}
+ SECTION ADDITIONAL
+-ns.example.com. IN A 1.2.3.4
+-ns.example.com. 3600 IN RRSIG A 3 3 3600 20070926134150
20070829134150 2854 example.com.
MC0CFQCQMyTjn7WWwpwAR1LlVeLpRgZGuQIUCcJDEkwAuzytTDRlYK7nIMwH1CM= ;{id = 2854}
++;ns.example.com. IN A 1.2.3.4
++;ns.example.com. 3600 IN RRSIG A 3 3 3600 20070926134150
20070829134150 2854 example.com.
MC0CFQCQMyTjn7WWwpwAR1LlVeLpRgZGuQIUCcJDEkwAuzytTDRlYK7nIMwH1CM= ;{id = 2854}
+ ENTRY_END
+
+ SCENARIO_END
+diff --git a/validator/validator.c b/validator/validator.c
+index 857510b65..1a51dc270 100644
+--- a/validator/validator.c
++++ b/validator/validator.c
+@@ -2563,7 +2563,7 @@ processFinished(struct module_qstate* qstate, struct
val_qstate* vq,
+ if(!dns_cache_store(qstate->env, &vq->orig_msg->qinfo,
+ vq->orig_msg->rep, 0, qstate->prefetch_leeway,
+ 0, qstate->region, qstate->query_flags,
+- qstate->qstarttime)) {
++ qstate->qstarttime, qstate->is_valrec)) {
+ log_err("out of memory caching validator
results");
+ }
+ }
+@@ -2572,7 +2572,8 @@ 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, qstate->region,
+- qstate->query_flags, qstate->qstarttime)) {
++ qstate->query_flags, qstate->qstarttime,
++ qstate->is_valrec)) {
+ log_err("out of memory caching validator results");
+ }
+ }
+--
+2.47.3
+
diff -Nru unbound-1.22.0/debian/patches/series
unbound-1.22.0/debian/patches/series
--- unbound-1.22.0/debian/patches/series 2026-05-25 12:05:38.000000000
+0300
+++ unbound-1.22.0/debian/patches/series 2026-07-22 09:53:15.000000000
+0300
@@ -21,3 +21,5 @@
26-05/15-Unit-test-for-CVE-2026-42959.patch
26-05/16-Unit-test-for-CVE-2026-40622.patch
26-05/17-Unit-test-for-CVE-2026-42960.patch
+fix-serve-expired-DNSSEC-information.patch
+1174-serve-expired-cache-update-fixes.patch