Recheck DNSSEC lookups that hickory wrongly calls bogus #72

Merged
jcoffey-dev merged 1 commits from fix/dnssec-insecure-fallback into main 2026-09-28 05:33:21 +00:00
Owner

Outbound delivery kept failing with DNSSEC validation failed for hosts that are perfectly valid. The cause is two bugs in hickory 0.26.3's validator. Both are still present on hickory's main branch.

1. A zone delegated beneath an unsigned zone. l.google.com is its own zone under google.com, and google.com is unsigned. To prove l.google.com insecure, hickory wants an SOA record in the DS reply ("Case 4" in fetch_ds_records). 1.1.1.1 and 8.8.8.8 send none, so the answer counts as bogus. As a result, every Google MX host (aspmx.l.google.com, gmail-smtp-in.l.google.com, …) was unreachable whenever the recipient domain's MX record is signed, because a signed MX sends address lookups to the validating resolver.

2. A signed CNAME to a signed name without the queried type. verify_nsec checks the denial of existence against the original name instead of the CNAME target, finds the target zone's SOA "for the wrong zone", and rejects it. This broke MX lookups for names that are CNAMEs.

Fix

When the validating resolver returns DnssecBogus, validated_lookup checks the answer again using lookups hickory gets right:

  • If the name has a CNAME that validates as signed, repeat the lookup at its target (up to 8 hops).
  • Otherwise, look up the SOA of the name's zone and each parent zone, nearest first. If one validates as insecure, nothing below it can be signed, so the plain resolver answers and the result is marked Insecure. If one validates as secure first, the bogus verdict stands.

Both steps rely only on answers hickory itself validated, so a genuinely broken or spoofed answer under a signed zone is still refused. The MX, A and AAAA DNSSEC lookups all go through it. TLSA is left alone, because an insecure host is never looked up for DANE.

Tests

  • Unit tests for secure_alias and apex_status.
  • An #[ignore] network test (validated_lookup_proves_delegation_below_unsigned_zone) resolves aspmx.l.google.com through the recheck. Run it with --include-ignored after a hickory upgrade to see whether the workaround is still needed.
  • cargo test -p smtp --features test_mode --lib outbound::dane::dnssec -- --include-ignored: 11 passed.
  • Checked against live DNS with a scratch copy of the same logic: Google hosts resolve as insecure, the CNAME case follows to its target and gets a validated NODATA, and signed hosts stay secure.

Found while tracing DMARC reports stuck in the queue.

Outbound delivery kept failing with `DNSSEC validation failed` for hosts that are perfectly valid. The cause is two bugs in hickory 0.26.3's validator. Both are still present on hickory's main branch. **1. A zone delegated beneath an unsigned zone.** `l.google.com` is its own zone under `google.com`, and `google.com` is unsigned. To prove `l.google.com` insecure, hickory wants an SOA record in the DS reply ("Case 4" in `fetch_ds_records`). 1.1.1.1 and 8.8.8.8 send none, so the answer counts as bogus. As a result, every Google MX host (`aspmx.l.google.com`, `gmail-smtp-in.l.google.com`, …) was unreachable whenever the recipient domain's MX record is signed, because a signed MX sends address lookups to the validating resolver. **2. A signed CNAME to a signed name without the queried type.** `verify_nsec` checks the denial of existence against the original name instead of the CNAME target, finds the target zone's SOA "for the wrong zone", and rejects it. This broke MX lookups for names that are CNAMEs. ### Fix When the validating resolver returns `DnssecBogus`, `validated_lookup` checks the answer again using lookups hickory gets right: - If the name has a CNAME that validates as signed, repeat the lookup at its target (up to 8 hops). - Otherwise, look up the SOA of the name's zone and each parent zone, nearest first. If one validates as **insecure**, nothing below it can be signed, so the plain resolver answers and the result is marked `Insecure`. If one validates as **secure** first, the bogus verdict stands. Both steps rely only on answers hickory itself validated, so a genuinely broken or spoofed answer under a signed zone is still refused. The MX, A and AAAA DNSSEC lookups all go through it. TLSA is left alone, because an insecure host is never looked up for DANE. ### Tests - Unit tests for `secure_alias` and `apex_status`. - An `#[ignore]` network test (`validated_lookup_proves_delegation_below_unsigned_zone`) resolves `aspmx.l.google.com` through the recheck. Run it with `--include-ignored` after a hickory upgrade to see whether the workaround is still needed. - `cargo test -p smtp --features test_mode --lib outbound::dane::dnssec -- --include-ignored`: 11 passed. - Checked against live DNS with a scratch copy of the same logic: Google hosts resolve as insecure, the CNAME case follows to its target and gets a validated NODATA, and signed hosts stay secure. Found while tracing DMARC reports stuck in the queue.
jcoffey-dev added 1 commit 2026-09-28 04:45:34 +00:00
Recheck DNSSEC lookups that hickory wrongly calls bogus
ci / fork-checks (pull_request) Successful in 14s
ci / build (pull_request) Successful in 7m34s
b1bc5ed6e0
hickory 0.26.3 rejects two kinds of valid answers, and outbound
delivery then retries those hosts until the message expires:

- A zone delegated beneath an unsigned zone (l.google.com under
  google.com). Proving the delegation insecure needs an SOA record in
  the DS reply, and public resolvers often leave it out. Every Google
  MX host behind a signed MX record was unreachable.
- A signed CNAME to a signed name that lacks the queried type. The
  NSEC denial is checked against the original name, not the target's.

On a bogus verdict, follow a signed CNAME and repeat the lookup at its
target; otherwise look up the name's zone and its parents, nearest
first. A zone that validates as unsigned means nothing below it can be
signed, so the plain resolver answers and the result is insecure. A
zone that validates as signed first leaves the verdict standing.
jcoffey-dev merged commit 4c07779c16 into main 2026-09-28 05:33:21 +00:00
jcoffey-dev deleted branch fix/dnssec-insecure-fallback 2026-09-28 05:33:21 +00:00
Sign in to join this conversation.
No Reviewers
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: inbuxa/inbuxa-server#72