Skip to content

fix(kernel): reject forward compression pointers in DNS name decoding - #183

Merged
kernalix7 merged 1 commit into
mainfrom
fix/dns-forward-pointer
Aug 13, 2026
Merged

fix(kernel): reject forward compression pointers in DNS name decoding#183
kernalix7 merged 1 commit into
mainfrom
fix/dns-forward-pointer

Conversation

@kernalix7

Copy link
Copy Markdown
Owner

Audit of crates/kernel/src/dns.rs ahead of wiring it into the UDP receive
path, per the crates/kernel/AGENTS.md rule that an unwired parser must be
audited before it becomes reachable from remote, pre-authentication input.

Audit result

The two classic DNS name-decompression hangs were already prevented: both
decode_domain_name and skip_name cap pointer indirection at MAX_POINTERS
(16), all offset arithmetic is saturating, label length is bounded at 63, and
rdlength / output copies are bounds-checked. Pointer cycles and self-pointers
are terminated by that hop cap. Overflow/underflow and unbounded-loop classes:
clean.

The defect

Missing was the RFC 1035 §4.1.4 prior-occurrence rule. A compression
pointer names a name that appeared earlier in the message, so its target must
be strictly before the pointer. decode_domain_name checked only
ptr < data.len() — never ptr < cur — so a pointer could point forward
at bytes that were never labels and reconstruct an attacker-controlled "name".
RFC 9267 explicitly lists this omission as a recurring parser anti-pattern, and
enforcing it is the BIND / tcpdump CVE-2017-12995 fix.

The hop cap does not cover this: a forward pointer consumes a hop but still
succeeds within 16 hops, so the integrity violation slips through even though
the loop terminates.

Fix

  • decode_domain_name: reject ptr >= cur, comparing against the position of
    the pointer just read so chained hops each move strictly backward. This makes
    cycles and self-pointers unreachable by construction rather than relying on
    the hop cap, and rejects forward pointers the cap never counted.
  • skip_name: a pointer at the final byte returned cur + 2 — one past the
    end of the message — without checking the second byte exists. Guard it.

Tests (TDD: written RED first)

Two fail without the fix:

test_decode_domain_name_rejects_forward_pointer   FAILED -> ok
test_skip_name_rejects_truncated_pointer          FAILED -> ok

Three already passed via the hop cap and are kept as guards so a future
relaxation of the backward rule turns them red:

test_decode_domain_name_rejects_self_pointer      ok
test_decode_domain_name_rejects_pointer_cycle     ok
test_decode_domain_name_hop_cap_on_legal_backward_chain  ok

test_decode_domain_name_pointer (a legitimate backward pointer, offset 13 →
0) still passes, confirming valid packets are unaffected.

Verification

  • cargo fmt --all -- --check · cargo clippy --workspace -- -D warnings · cargo build --workspace
  • bash scripts/run-tests.sh4601 passed, 0 skipped (kernel 359 → 367)

Follow-up

Wiring DNS validation into the UDP port-53 path is a separate PR (one concern
per PR). The parser is now safe to expose.

Audit of `dns.rs` ahead of wiring it into the UDP receive path.

The parser was already hardened against the two classic DNS name-decompression
hangs: both `decode_domain_name` and `skip_name` cap pointer indirection at
`MAX_POINTERS` (16), all offset arithmetic is saturating, label length is
bounded at 63, and `rdlength`/output copies are bounds-checked. Pointer cycles
and self-pointers are therefore already terminated by the hop cap.

What was missing is the RFC 1035 §4.1.4 prior-occurrence rule. A compression
pointer names a name that appeared *earlier* in the message, so its target must
be strictly before the pointer. `decode_domain_name` only checked
`ptr < data.len()`, never `ptr < cur`, so a pointer could point forward at
bytes that were never labels and reconstruct an attacker-controlled "name".
RFC 9267 calls out exactly this omission, and it is the BIND/tcpdump
CVE-2017-12995 fix. Enforce `ptr < cur`, comparing against the position of the
pointer just read so chained hops must each move strictly backward. This makes
cycles and self-pointers unreachable by construction instead of relying on the
hop cap, and rejects forward pointers the cap never counted.

`skip_name` returned `cur + 2` for a pointer without checking the second byte
exists; a pointer at the final byte yielded an offset one past the message end.
Guard it.

Five regression tests. Two fail without the fix
(`rejects_forward_pointer`, `rejects_truncated_pointer`); the self-pointer,
cycle, and legal-backward-chain-over-the-cap cases already passed via the hop
cap and are kept as guards so a future relaxation of the backward rule turns
them red. `test_decode_domain_name_pointer` (offset 13 → 0, a legitimate
backward pointer) still passes, confirming valid packets are unaffected.

Suite: 4601 passed, 0 skipped.
@kernalix7
kernalix7 merged commit 1ebf466 into main Aug 13, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant