{"record":{"id":"0d1e50cdb4ff5345","repo":"stamparm/maltrail","slug":"question","errorCode":null,"errorMessage":"question","messagePattern":"question","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"sensor/src/protocols/dns.rs","lineNumber":160,"sourceCode":"    /// Named, deterministic offset-overflow contract.\n    ///\n    /// The fuzz suite catches this class, but only by chance of input; these pin the exact boundary\n    /// values forever. Both functions take a caller-supplied `name_end` and must return `None`\n    /// rather than overflow — a debug build panicked on `start + 4` here, and release wrapped it\n    /// into an empty range, which is a correctness property that must not depend on the profile.\n    #[test]\n    fn hostile_name_end_offsets_never_overflow() {\n        let data = [0u8; 64];\n        for offset in [usize::MAX, usize::MAX - 1, usize::MAX - 4, usize::MAX - 5, usize::MAX - 6] {\n            assert_eq!(question_type_class(&data, offset), None, \"question_type_class({offset})\");\n            assert_eq!(first_a_record(&data, offset), None, \"first_a_record({offset})\");\n        }\n        // A sane offset past the end is also None, not a panic.\n        assert_eq!(question_type_class(&data, 1_000), None);\n        assert_eq!(first_a_record(&data, 1_000), None);\n        // ... and a valid offset still works, so the guards did not break the happy path.\n        let query =\n            super::super::dns::question(&crate::testkit::dns_query(\"evil.com\", 1, 1, 0x0100)).expect(\"question\");\n        assert_eq!(query.name, \"evil.com\");\n    }\n\n    fn query_message(name: &str, qtype: u16, qclass: u16, flags: u16) -> Vec<u8> {\n        let mut v = Vec::new();\n        v.extend_from_slice(&0x1234u16.to_be_bytes());\n        v.extend_from_slice(&flags.to_be_bytes());\n        v.extend_from_slice(&1u16.to_be_bytes());\n        v.extend_from_slice(&0u16.to_be_bytes());\n        v.extend_from_slice(&0u16.to_be_bytes());\n        v.extend_from_slice(&0u16.to_be_bytes());\n        for label in name.split('.') {\n            v.push(label.len() as u8);\n            v.extend_from_slice(label.as_bytes());\n        }\n        v.push(0);\n        v.extend_from_slice(&qtype.to_be_bytes());\n        v.extend_from_slice(&qclass.to_be_bytes());","sourceCodeStart":142,"sourceCodeEnd":178,"githubUrl":"https://github.com/stamparm/maltrail/blob/77cfb06d7606506d101bbcec0786c77166c4255e/sensor/src/protocols/dns.rs#L142-L178","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","solutions":["Re-run the test and inspect the produced query bytes with a hex dump; confirm `testkit::dns_query` still emits a valid question section.","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.","If testkit was changed, restore a correctly encoded query (correct name pointer/labels, qtype/qclass present) or update the expectation accordingly.","Add a few more happy-path fixtures (multiple labels, long names) to catch over-strict guards earlier."],"exampleFix":"// before (test fails because question() returns None)\nlet query = super::super::dns::question(crate::testkit::dns_query(\"evil.com\", 1, 1, 0x0100)).expect(\"question\");\n// after (diagnose instead of a bare panic)\nlet bytes = crate::testkit::dns_query(\"evil.com\", 1, 1, 0x0100);\nlet query = super::super::dns::question(&bytes)\n    .unwrap_or_else(|| panic!(\"question() rejected valid query: {:02x?}\", bytes));","handlingStrategy":"validation","validationCode":"let bytes = crate::testkit::dns_query(\"evil.com\", 1, 1, 0x0100);\nassert!(!bytes.is_empty() && bytes.len() > 12, \"testkit emitted a malformed query\");","typeGuard":null,"tryCatchPattern":"let query = dns::question(&bytes)\n    .unwrap_or_else(|| panic!(\"question() rejected valid query {:02x?}\", bytes));","preventionTips":["Keep happy-path fixtures alongside hostile-input tests","Hex-dump testkit packets when parser tests fail","After tightening bounds guards, always re-run happy-path tests","Return descriptive errors from parsers instead of bare None during debugging"],"tags":["rust","test","dns","parser","expect"],"backgroundTag":"internal-invariant-violation","analyzedSha":"77cfb06d7606506d101bbcec0786c77166c4255e","analyzedAt":"2026-09-13T03:50:16.010Z","contentChangedAt":"2026-09-13T03:50:16.010Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}