[Dnsmasq-discuss] [PATCH] Restore INSECURE result for empty DS replies without NSEC records

Dominik Derigs dl6er at dl6er.de
Sun Jul 12 09:12:56 UTC 2026


Hi Simon, all,

we have a DNSSEC regression in v2.93 that a Pi-hole user ran into [1], and I
think the attached patch is the right fix - but I would like your eyes 
on it,
since it touches the DS/CNAME logic you reworked in 707bc2d25.

The symptom: reverse (PTR) lookups for RFC-1918 space that are forwarded 
to a
local resolver via a domain-specific server (i.e., our rev-server) return
SERVFAIL as soon as DNSSEC validation is on. It worked in 2.91 and breaks in
2.93. The user's log shows the validator walking up to the private reverse
zone and then abandoning:

     dnssec-query[DS] 168.192.in-addr.arpa to 1.1.1.1
     validation 22.0.168.192.in-addr.arpa is ABANDONED

and the familiar "insecure reply received for DS ... RFC-1918 address" line
never shows up anymore.

The cause is that 707bc2d25 ("Fix DNSSEC fail with CNAME replies to DS
queries") dropped the

     /* Empty DS without NSECS */
     if (qtype == T_DS)
       return STAT_INSECURE;

shortcut from dnssec_validate_reply(). That matters here because the public
resolvers people forward to (1.1.1.1, 8.8.8.8, 9.9.9.9, ...) serve the 
private
reverse zones as empty zones per RFC 6303, and answer the DS query with a
*bare* NXDOMAIN - no SOA, no NSEC, nothing to prove non-existence with:

     $ dig +dnssec DS 168.192.in-addr.arpa @1.1.1.1
     ;; ->>HEADER<<- opcode: QUERY, status: NXDOMAIN
     ;; flags: qr ra; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 1

Without the shortcut, prove_non_existence() returns DNSSEC_FAIL_NONSEC, 
and we
then call zone_status() for the very name whose DS we are in the middle of
resolving. That name is not cached (it cannot be yet), so zone_status()
returns STAT_NEED_DS for it, dnssec_validate_ds() passes that back up, we
re-issue the identical DS query, the self-dependency trips the loop 
detector,
and the whole validation is abandoned - hence the SERVFAIL. The bogus-priv
escape hatch never gets a look-in, as it only fires on rc == STAT_INSECURE.

I did not simply revert the shortcut, since your CNAME-to-DS change 
relies on
the code below it. Instead I gated the restored return on the primary query
(j == 0), which is exactly the "the DS query itself came back empty" 
case; the
CNAME targets are j > 0 and keep going through prim_ok as before. That 
way we
retain both behaviours: a signed CNAME answer to a DS query is still 
accepted
as proof of no-DS, and an empty DS answer for the queried name itself is 
once
more reported as INSECURE, so the bogus-priv / domain-specific-server
heuristics can decide instead of looping into ABANDONED.

Patch attached, against current master. Reproduction, in case it helps:

     dnsmasq --no-daemon --log-queries=extra --dnssec --bogus-priv \
             --trust-anchor=<root DS> \
             --rev-server=192.168.0.0/24,<local-auth> --server=1.1.1.1
     dig @127.0.0.1 22.0.168.192.in-addr.arpa PTR
     # 2.93:    SERVFAIL, "validation ... is ABANDONED"
     # patched: NOERROR,  "validation result is INSECURE"

(the local authoritative server answers the PTR from an unsigned zone; 
1.1.1.1
answers the DS for 168.192.in-addr.arpa with the bare NXDOMAIN shown above.)

Thanks,
Dominik

[1] https://github.com/pi-hole/FTL/issues/2942
-------------- next part --------------
A non-text attachment was scrubbed...
Name: dnsmasq-empty-ds-INSECURE.patch
Type: text/x-patch
Size: 3389 bytes
Desc: not available
URL: <http://lists.thekelleys.org.uk/pipermail/dnsmasq-discuss/attachments/20260712/a2dbfe50/attachment-0001.bin>


More information about the Dnsmasq-discuss mailing list