Skip to content

Critical: long TXT/SPF (>255B) rdata not chunked on serve path — 2048-bit DKIM returns no response (SERVFAIL) #52

Description

@joyider

Summary

The query answer path does not split TXT/SPF rdata into ≤255-byte DNS character-strings. Any TXT (or SPF) record whose rdata exceeds 255 bytes fails to serialize, and go53 returns no response at all (UDP and TCP) for that qname. Resolvers therefore report SERVFAIL.

Real-world impact: every 2048-bit DKIM record is unreachable (a 2048-bit v=DKIM1;...;p=<base64> value is ~400+ bytes). 1024-bit DKIM (<255 bytes) resolves fine, which is why the bug went unnoticed. Long SPF/verification TXT values are affected too.

Observed on v0.79.0.

Severity

Critical. A single short, valid record silently disables answering for that name across all transports, breaking email authentication (DKIM) for affected domains. Because the failure is a silent drop of an otherwise valid, common record, it is also a latent availability footgun for any zone hosting long TXT values.

Root cause

chunkTXT() (which correctly splits long strings into ≤255-byte character-strings) lives in internal/rrbuilder.go and is used by the zone-build / AXFR / signing RR construction — but it is never called on the live query answer path.

The answer path builds the RR with the full string as a single character-string:

  • zone/rtypes/txt.go (Lookup) → &dns.TXT{ ..., Txt: []string{rec.Text} }
  • zone/rtypes/spf.go (Lookup) → &dns.SPF{ ..., Txt: []string{rec.Text} }

Call chain for a normal query:

dns/handler.go        zone.LookupRecord(q.Qtype, q.Name)
  -> zone/zone.go      LookupRecord: rtypes.Get(rrtype).Lookup(name)
  -> zone/rtypes/txt.go Lookup: &dns.TXT{ Txt: []string{rec.Text} }   // <-- single unchunked string

When rec.Text is > 255 bytes, miekg/dns refuses to pack the character-string (255-byte limit), the whole message fails to serialize, and the handler writes nothing back to the client. This is independent of DNSSEC — the failure is in packing the TXT/SPF RR itself.

grep -rn chunkTXT zone/rtypes/ → no hits. chunkTXT is only referenced in internal/rrbuilder.go.

Note: the earlier "TXT chunking" fix (v0.77.3, commit 3c1269d) added chunkTXT to internal/rrbuilder.go only and missed the serve path, so the bug persists on the actual query hot path.

Reproduction

  1. Create a zone and add a TXT record with rdata > 255 bytes, e.g. a 2048-bit DKIM key:
    <selector>._domainkey TXT "v=DKIM1;k=rsa;p=<~392 base64 chars>"
  2. Query it: dig @<go53> TXT <selector>._domainkey.<zone> — also try +tcp and +dnssec.
  3. Observed: no answer on UDP or TCP (client times out / SERVFAIL upstream).
    Expected: the TXT RRset returned with the value split into multiple ≤255-byte character-strings (which resolvers concatenate).
  4. A short TXT at the same name (e.g. _dmarc) answers normally — confirming it is length-related, not zone/record-existence.

Proposed fix

Apply chunkTXT() on the answer path so serve and zone-build agree:

  • zone/rtypes/txt.go: Txt: chunkTXT(rec.Text) instead of Txt: []string{rec.Text}
  • zone/rtypes/spf.go: same for the SPF RR

chunkTXT is safe for short values (returns the string unchanged when ≤255 bytes), so this is a drop-in. It would be worth centralizing the TXT/SPF RR construction so internal/rrbuilder.go and zone/rtypes/* cannot diverge again, and adding a regression test that queries a >255-byte TXT end-to-end (pack + wire round-trip), asserting multiple character-strings are emitted.

Workaround (until a release)

Rotate affected domains to a 1024-bit DKIM key so the rdata packs under 255 bytes.


Filed from downstream operations after a production DKIM outage; root cause traced to the serve path. Happy to open a PR for the chunkTXT serve-path fix + regression test.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingpriority: criticalHighest priority — production-impacting, fix ASAP

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions