[Pdns-users] Recursor 5.4.0: RPZ matches on '.' root query, breaks DNSSEC validation (Indeterminate instead of Bogus)

Otto Moerbeek otto at drijf.net
Mon Apr 20 09:58:51 UTC 2026


On Mon, Apr 20, 2026 at 11:45:25AM +0200, Otto Moerbeek via Pdns-users wrote:

> On Sun, Apr 19, 2026 at 10:48:09PM +0100, Chris Brough via Pdns-users wrote:
> 
> > Hi all,
> >  
> > I'm running into behaviour I can't explain and I'd appreciate a second pair of eyes before I assume it's a bug. I could easily be doing something obvious and wrong.
> >  
> > Setup
> >     * PowerDNS Recursor 5.4.0 in Docker (image: powerdns/pdns-recursor-54:latest)
> >     * Host: Debian 13 VM (UmbrelOS - using the Portainer app to manage Docker), Docker host network mode
> >     * Full recursion, no forwarders
> >     * `dnssec.validation: validate`, built-in root trust anchor
> >     * Root zone loaded via `zonetocaches` from Internic (ZONEMD validated Secure at startup)
> >     * 7 RPZ zones loaded via `rpzPrimary` from IPFire DBL (xfr.dbl.ipfire.org) — ads, gambling, malware, phishing, piracy, smart-tv, violence. Open AXFR, no TSIG.
> >     * `extended_resolution_errors: true`, per-RPZ `extendedErrorCode: 15` and `extendedErrorExtra` set
> > Full YAML config and docker-compose.yml available on request.
> >  
> > Symptom
> > DNSSEC-bogus domains return NOERROR with data instead of SERVFAIL:
> >     $ dig @<host_ip> dnssec-failed.org +dnssec
> >     ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 18515
> >     ;; flags: qr rd ra; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1
> >     ;; ANSWER SECTION:
> >     dnssec-failed.org.  218  IN  A  96.99.227.255
> >     dnssec-failed.org.  218  IN  RRSIG  A 13 2 300 ...
> >  
> > Confirmed validation mode is actually loaded:
> >     $ rec_control get-parameter dnssec
> >     dnssec:
> >       validation: validate
> >       disabled_algorithms:
> >       - '1'
> >       - '3'
> >       - '5'
> >       - '6'
> >       - '7'
> >       - '12'
> >       log_bogus: true
> >  
> > Query log shows `validationState="Indeterminate"` for the query, not Bogus.
> >  
> > Root cause (as far as I can tell)
> > A rec_control trace-regex 'dnssec-failed.org' trace shows validation tries to fetch the root DNSKEY and hits the first RPZ in the config list:
> >     .|DNSKEY:: RPZ Hit; PolicyName=ads.rpz.ipfire.org; Trigger=.; Hit=; Type=QName; Kind=Local Data
> >     .: Retrieved 0 DNSKeys, state is Indeterminate
> >     org: Updating validation state with cache content for org to Indeterminate
> >     dnssec-failed.org: Updating validation state with cache content for dnssec-failed.org to Indeterminate
> >     dnssec-failed.org: Validation status is Indeterminate
> >  
> > Confirming this from a client:
> >     $ dig @<host_ip> . NS
> >     ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 14776
> >     ;; flags: qr rd ra; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 1
> >     ; EDE: 15 (Blocked): (Blocked: advertising)
> >     ;; QUESTION SECTION:
> >     ;.    IN    NS
> >  
> > Zero-answer NOERROR on `. NS`, tagged by the first RPZ. This is not specific to the ads list — I tested by removing ads.rpz.ipfire.org from config, and whichever RPZ moved into first position then shows the same behaviour on root queries. So every IPFire RPZ I'm loading exhibits this, which makes me think it's something about the way they're structured or the way PDNS loads/evaluates them, rather than a specific zone being poisoned.
> >  
> > What dump-rpz shows:
> >     $ rec_control dump-rpz ads.rpz.ipfire.org /tmp/ads.txt
> >     $ head -5 /tmp/ads.txt
> >     ads.rpz.ipfire.org. IN SOA primary.dbl.ipfire.org. hostmaster.ipfire.org. 1776630606 3600 600 3600000 60
> >     stbg.stanbicbank.co.zw.ads.rpz.ipfire.org. 60 IN CNAME .
> >     stats.zpl.zone.ads.rpz.ipfire.org. 60 IN CNAME .
> >     *.a.userscript.zone.ads.rpz.ipfire.org. 60 IN CNAME .
> >     a.userscript.zone.ads.rpz.ipfire.org. 60 IN CNAME .
> >  
> > Apex has only SOA (NS stripped). Nothing in the dumped zone should produce a `.` QName trigger as far as I can read it — but the trace clearly shows one matching, with `Hit=` empty.
> >  
> > The IPFire source zone at xfr.dbl.ipfire.org contains at its apex: SOA, NS, and an `_info IN TXT` record one label below the apex. Nothing else unusual that I can see.
> >  
> > RPZ config (abbreviated)
> >     recursor:
> >       rpzs:
> >         - name: ads.rpz.ipfire.org
> >           addresses: [ 'xfr.dbl.ipfire.org:53' ]
> >           policyName: ads.rpz.ipfire.org
> >           extendedErrorCode: 15
> >           extendedErrorExtra: 'Blocked: advertising'
> >         - name: gambling.rpz.ipfire.org
> >           addresses: [ 'xfr.dbl.ipfire.org:53' ]
> >           policyName: gambling.rpz.ipfire.org
> >           extendedErrorCode: 15
> >           extendedErrorExtra: 'Blocked: gambling'
> >         # ... 5 more in same pattern
> >       system_resolver_ttl: 300
> >       extended_resolution_errors: true
> >  
> > No `defpol` is set on any zone.
> >  
> > Am I configuring these RPZs wrong in a way that causes `.` to match as a QName trigger? I can't find anything in docs or the RFC that would point there.
> >  
> > Happy to provide the full YAML, full trace log, full zone dump, or anything else useful. I didn't attach them here to keep the email readable.
> >  
> > Thanks,
> > Chris
> 
> 
> I can repreduce, the issue is inclusion of a ZONEMD record on the
> apex:
> 
> ads.rpz.ipfire.org. ZONEMD 1776657606 1 1 eb5a6cb0c3bf2d1ab1f54d7614c8eda74c2bdfe5a7474506b9c5a2baa41d9e01842ecad7cf14a0a4d7b789f67b629ed5
> 
> Which gets interpreted as custom data for root, causing all other
> qtypes to return NODATA. NS records are skipped
> for that reason, ZONEMD should as well (or validated and then skipped).
> 
> Would it be possible to instruct your RPZ provider to not include
> ZONEMD records?
> 

BTW, there are also some TXT record under the _info.rpzname name:

_info.ads.rpz.ipfire.org. 60    IN      TXT     "license=CC BY-SA 4.0"
_info.ads.rpz.ipfire.org. 60    IN      TXT     "total-domains=150639"
_info.ads.rpz.ipfire.org. 60    IN      TXT     "updated-at=2026-04-20T04:00:05.299879+00:00"
_info.ads.rpz.ipfire.org. 60    IN      TXT     "description=Blocks domains used for ads, tracking, an

which could lead to unexpected matches as well.  I have to think if it
is possible to skip those as well, though this is dangerous territory,
as afaik we have no defined way to distinguish actual custom records
from these kind of "meta" records.

	-Otto


More information about the Pdns-users mailing list