← board

crtl's DNS parser refuses two malformed packets that glibc accepts

Both were found by test/c_crtl_resolv.c and test/c_crtl_res_send.c, whose oracle is glibc on this box. Neither is a pxx bug and neither is a glibc bug in the sense that matters — glibc's choices are defensible as leniency toward real-world servers. We chose the other side, and this file is the record of choosing rather than of tolerating.

1. A forward compression pointer

forward-ptr(STRICTER) initparse=-1 uncompress=2     <- glibc
forward-ptr(STRICTER) initparse=-1 uncompress=-1    <- crtl

The probe puts a pointer at offset 12 aimed at offset 20, which is ahead of it. glibc follows it, finds the root label there, and reports 2 bytes consumed. lib/crtl/src/arpa/nameser.c refuses any target that is not strictly before the pointer.

RFC 1035 §4.1.4 says a pointer names "a prior occurrence of the same name". A forward pointer is therefore not something a conforming server emits, and following one builds a name out of bytes the parser has not validated yet — the packet is walked out of order, so a length byte can be reached before the bound that would have rejected it.

The strict rule is also what makes termination cheap. "Strictly backwards" terminates on its own; the jump counter beside it is a second, independent bound rather than the only one. glibc, having no ordering rule, has to rely on its counter alone.

Note the two rows agree on initparse=-1 — the message as a whole is refused either way. The divergence is only visible through the direct ns_name_uncompress() call, which is why the test makes that call separately rather than trusting the message-level verdict.

2. A reply with the QR bit clear

notresp.example.com rc=ok h_errno=0 addr=6.6.6.6    <- glibc
notresp.example.com rc=ok h_errno=0 addr=10.1.2.3   <- crtl

test/c_crtl_res_send.c runs a DNS server on 127.0.0.1 that, for this name, sends a decoy carrying 6.6.6.6 with the right query id and QR clear, then the real reply carrying 10.1.2.3. glibc accepts the first datagram on the id alone; res_nsend() in lib/crtl/src/resolv.c skips it and keeps waiting, so it gets the real answer.

RFC 1035 §4.1.1: QR is 0 for a query and 1 for a response. A datagram with QR clear is not an answer to anything. Accepting it means a caller's address comes from a packet that never claimed to be a reply.

The decoy's address differs from the real one on purpose. If both carried 10.1.2.3 the row would pass whichever packet was accepted — the expected-value-collides-with-the-failure-value trap. The two addresses are what make the row able to fail.

Why this is known-incompat/ and not a bug in either direction

What would reopen it

Real source — not a probe — that is correct against glibc and wrong against crtl because of one of these two rules. That means a real nameserver observed emitting a forward compression pointer, or a real deployment where answers legitimately arrive with QR clear. Neither has been seen; if one is, the fix is a documented leniency flag, not silently matching glibc.