Skip to content

tc-dns: coverage-guided fuzzing and differential testing against hickory-proto #740

Description

@kasnder

Follow-up to #739, which routed all DNS response parsing/rewriting through the tc-dns crate. The PR ships with unit/golden tests (RFC 1035 wire-format core, CNAME chains, EDNS(0) OPT, RRSIG, blanked-message round-trip, TC-bit fail-open), FFI-boundary tests, and a host test for the DNS-over-TCP framing decision. Two heavier pieces of test infrastructure were deliberately deferred:

1. Coverage-guided fuzzing (cargo-fuzz)

The current fuzz coverage is a 50k-input random smoke test inside tests/message.rs. Random bytes rarely survive the header checks, so deep paths (compression-pointer chasing, answer walking, blanking) are barely exercised.

  • Add a cargo fuzz target for tcdns::process_response (and ideally one for tcdns_process_response through the C ABI with stub callbacks).
  • Seed corpus from real DNS responses (see below).
  • Run in CI on a schedule (e.g. nightly, time-boxed), not per-PR.

Since the crate forbids panicking ops by lint and uses checked arithmetic throughout, the fuzzer's main value is finding wrong policy outcomes and panics in unforeseen paths, not memory bugs.

2. Differential testing against an independent oracle

The golden tests encode our own understanding of the wire format; an author's blind spot is shared between code and test. Add a dev-dependency-only harness (nothing ships to users) that parses the same corpus with hickory-proto and asserts both implementations agree on qname, recorded A/AAAA answers, and addresses — flagging any message the two parsers interpret differently.

  • Corpus: pcap-derived real-world captures — DNSSEC-signed responses (RRSIG/NSEC in answers), long CDN CNAME chains, EDNS options/ECS, mixed-case names, qdcount>1 oddities.
  • The same corpus doubles as the fuzz seed corpus.
  • Keep tc-dns's runtime dependency-free status: hickory-proto under [dev-dependencies] only.

Context

  • The decision to hand-roll tc-dns rather than depend on a DNS library was deliberate (in-place rewrite semantics, fail-open contract, modified-UTF-8 for JNI, F-Droid reproducible-build and supply-chain surface, APK size). This issue is about importing a battle-tested implementation's judgement as a test oracle, not as a dependency.
  • Smaller test gaps already noted for later: case-normalization tests for is_domain_blocked qnames become relevant only if/when the domain-policy hook goes live (it currently returns false everywhere).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions