stamparm/maltrail · warning

question

Error message

question

What it means

A test-side `Option::expect` in the DNS module: after verifying that out-of-bounds offsets return `None`, the test builds a well-formed DNS query via `testkit::dns_query` and asserts `question(...)` parses it, panicking with 'question' if parsing fails. It is the test's way of saying 'the happy path must still work after the bounds guards were added'; a panic means the parser now rejects a valid query.

Solutions

  1. Re-run the test and inspect the produced query bytes with a hex dump; confirm `testkit::dns_query` still emits a valid question section.
  2. Step through `dns::question` with the testkit bytes to find which guard returns `None` for this valid input, and fix the over-tight bound check.
  3. If testkit was changed, restore a correctly encoded query (correct name pointer/labels, qtype/qclass present) or update the expectation accordingly.
  4. Add a few more happy-path fixtures (multiple labels, long names) to catch over-strict guards earlier.

Example fix

// before (test fails because question() returns None)
let query = super::super::dns::question(crate::testkit::dns_query("evil.com", 1, 1, 0x0100)).expect("question");
// after (diagnose instead of a bare panic)
let bytes = crate::testkit::dns_query("evil.com", 1, 1, 0x0100);
let query = super::super::dns::question(&bytes)
    .unwrap_or_else(|| panic!("question() rejected valid query: {:02x?}", bytes));
Defensive patterns

Strategy: validation

Validate before calling

let bytes = crate::testkit::dns_query("evil.com", 1, 1, 0x0100);
assert!(!bytes.is_empty() && bytes.len() > 12, "testkit emitted a malformed query");

Try / catch

let query = dns::question(&bytes)
    .unwrap_or_else(|| panic!("question() rejected valid query {:02x?}", bytes));

Prevention

When it happens

Trigger: Running `hostile_name_end_offsets_never_overflow` after a change to `dns::question` (or the bounds guards it relies on) that makes it return `None` for a valid single-question query built by `testkit::dns_query("evil.com", 1, 1, 0x0100)`. Also fires if the testkit helper starts emitting malformed packets (bad offsets, wrong counts, truncated name encoding).

Common situations: Refactoring the DNS name parser or question bounds-checking; tightening offset validation so the happy path is accidentally rejected; changing `testkit::dns_query` wire format (e.g. flags or compression handling); inconsistent qdcount/name encoding.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


AI-assisted analysis of stamparm/maltrail@77cfb06d76 (2026-09-13). Data as JSON: /api/errors/0d1e50cdb4ff5345. Report an issue: GitHub.

Appendix: source

Thrown at sensor/src/protocols/dns.rs:160

    /// Named, deterministic offset-overflow contract.
    ///
    /// The fuzz suite catches this class, but only by chance of input; these pin the exact boundary
    /// values forever. Both functions take a caller-supplied `name_end` and must return `None`
    /// rather than overflow — a debug build panicked on `start + 4` here, and release wrapped it
    /// into an empty range, which is a correctness property that must not depend on the profile.
    #[test]
    fn hostile_name_end_offsets_never_overflow() {
        let data = [0u8; 64];
        for offset in [usize::MAX, usize::MAX - 1, usize::MAX - 4, usize::MAX - 5, usize::MAX - 6] {
            assert_eq!(question_type_class(&data, offset), None, "question_type_class({offset})");
            assert_eq!(first_a_record(&data, offset), None, "first_a_record({offset})");
        }
        // A sane offset past the end is also None, not a panic.
        assert_eq!(question_type_class(&data, 1_000), None);
        assert_eq!(first_a_record(&data, 1_000), None);
        // ... and a valid offset still works, so the guards did not break the happy path.
        let query =
            super::super::dns::question(&crate::testkit::dns_query("evil.com", 1, 1, 0x0100)).expect("question");
        assert_eq!(query.name, "evil.com");
    }

    fn query_message(name: &str, qtype: u16, qclass: u16, flags: u16) -> Vec<u8> {
        let mut v = Vec::new();
        v.extend_from_slice(&0x1234u16.to_be_bytes());
        v.extend_from_slice(&flags.to_be_bytes());
        v.extend_from_slice(&1u16.to_be_bytes());
        v.extend_from_slice(&0u16.to_be_bytes());
        v.extend_from_slice(&0u16.to_be_bytes());
        v.extend_from_slice(&0u16.to_be_bytes());
        for label in name.split('.') {
            v.push(label.len() as u8);
            v.extend_from_slice(label.as_bytes());
        }
        v.push(0);
        v.extend_from_slice(&qtype.to_be_bytes());
        v.extend_from_slice(&qclass.to_be_bytes());

View on GitHub (pinned to 77cfb06d76)