From 26c83adc4815e46d64b982105d74968dd9796d2d Mon Sep 17 00:00:00 2001 From: Dominik Date: Mon, 6 Apr 2026 11:07:18 +0200 Subject: [PATCH] Allow expired RRSIGs when stale caching is enabled. When dnsmasq forwards to a caching validating resolver (e.g. unbound with serve-expired), the upstream may return responses from its cache whose RRSIGs have expired since being cached - the upstream validated them when they were fresh, but by the time dnsmasq re-validates, the signatures have passed their expiration timestamp. This causes intermittent DNSSEC BOGUS failures (EDE: "signature expired") that clear up once the upstream refreshes its cache. Pi-hole enables use-stale-cache by default to reduce DNS latency for clients - they get an instant stale answer while dnsmasq refreshes in the background. Combined with DNSSEC validation and a caching upstream like unbound, this creates a window where every query for a DNSSEC- signed domain fails BOGUS until the upstream cache refreshes. Popular resolvers like unbound enable serve-expired with no time limit by default, so RRSIGs can be days past expiration by the time dnsmasq sees them. When use-stale-cache is enabled, skip the RRSIG expiration timestamp check entirely. The signature still receives full cryptographic verification - only the timestamp gate is relaxed. This is consistent with the existing stale cache design: is_expired() in cache.c already serves stale data records past their TTL, so enforcing strict RRSIG freshness on top of deliberately stale data is contradictory. When stale caching is disabled (cache_max_expiry == 0), behaviour is completely unchanged. Signed-off-by: Dominik --- src/dnssec.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/dnssec.c b/src/dnssec.c index b6e5502..c575713 100644 --- a/src/dnssec.c +++ b/src/dnssec.c @@ -514,7 +514,17 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in failflags &= ~DNSSEC_FAIL_NYV; if (serial_compare_32(curtime, sig_expiration) == SERIAL_GT) - continue; + { + /* When stale caching is enabled, skip the RRSIG expiration check. + A caching forwarder (e.g. unbound with serve-expired) may return + responses whose RRSIGs expired long ago but were valid when + originally cached upstream. The signature still gets full + cryptographic verification - only the timestamp gate is relaxed. */ + if (daemon->cache_max_expiry != 0) + failflags &= ~DNSSEC_FAIL_EXP; + else + continue; + } else failflags &= ~DNSSEC_FAIL_EXP; } -- 2.43.0