mDNS: Seven Implementations, One Very Old Flaw

Multicast DNS answers to anyone on the network, with no authentication and no handshake. We tested seven independent implementations and uncovered 24 defects. The single largest group comes from a name-compression feature inherited from DNS in 1987.

Multicast DNS is how devices find each other without a server. Printers, speakers, cameras, smart-home controllers, build tools and phones all announce themselves on UDP 5353, to 224.0.0.251 and ff02::fb, and all of them listen. It is the protocol behind service discovery on effectively every local network in existence.

It has no authentication, no handshake and no session. Anything that can send a multicast datagram on the link can reach every mDNS parser on it at once, and a device typically starts listening before it has been configured, paired or claimed.

We tested seven independent mDNS implementations and uncovered 24 defects. The single largest group comes from one feature, and that feature was designed in 1987.

The pointer that will not die

DNS name compression lets a message avoid repeating a domain name by pointing backwards to an earlier offset. It saves bytes, it predates everything, and mDNS inherited it unchanged. A parser has to follow those pointers, and a pointer can point anywhere the attacker likes.

Four of the seven implementations got this wrong, in four different ways.

Product B follows a compression pointer that forms a cycle and never terminates. Product C accepts a pointer to offset 0, which walks the cursor backwards through the packet it already parsed. Product E budgets the decompressor's work per label rather than per packet, so a datagram packed with short labels multiplies the work far beyond what its size suggests. Product A enforces a lookbehind invariant that bounds one name correctly, while leaving the packet as a whole unbounded.

The last of those is the instructive one. The code is not missing a check. It has a check, it is a reasonable check, and it constrains the wrong scope.

The attacker cost here is nothing. A compression pointer is two bytes.

Declared lengths that nothing compares against

The second group is the classic parser defect, in its usual form: a number in the packet that says how much data follows, trusted against the data that actually followed.

Product B walks off the receive buffer using the declared question count. Product C copies a declared rdata length with no size check. Product D copies six bytes of SRV record data without first confirming six bytes remain, and separately hands the declared RDLENGTH to the application without bounding it, which makes the defect the application's to trigger.

Product D also has the rarest shape in the set: a 16-bit parse cursor that wraps, so a single record can be parsed up to 65,535 times from one datagram.

Short datagrams nobody sized for

mDNS messages have a 12-byte header. Several parsers read it before establishing that 12 bytes arrived.

Product E reads four bytes past its receive buffer in two separate entry points, and at three further sites decodes the 12-byte header before anything checks that 12 bytes arrived, then branches on the result. Product B dereferences a NULL name on a 12-byte query. These are the cheapest cases in the whole set to generate and among the most likely to be missed by a test corpus built from valid traffic, because valid traffic never contains them.

State that only grows

Product F is a different kind of target and produced seven findings, none of them memory safety. Its discovery state grows without bound, once per PTR record observed, for the lifetime of the process. The cost of handling one SRV or TXT record is proportional to every PTR record ever seen. Every observed record is cached, and once the cache is at capacity each new record rescans the whole thing.

Because mDNS is multicast, none of that requires the attacker to be talking to the target. Records simply arriving on the link are enough.

Two more in the same implementation are correctness rather than exhaustion, and are arguably worse. Any PTR record on the link is delivered to the calling application as a service type, and one PTR record can permanently block discovery of a real service. A discovery event can name one host while carrying the address of another.

That last one is worth sitting with. The device does not crash. It resolves a name to the wrong address and reports success.

The protocol answers strangers, loudly

Product G emits one reply datagram per question, unicast back to the source address in the request. The source address of a UDP datagram on a local link is whatever the sender wrote in it.

Product E does the same thing with no aggregation, no rate limit and no source check, and the cost is measurable: 335 replies from a single datagram, 24.3 times traffic amplification. Its name decompressor is the matching CPU case, at roughly 120 times the honest parsing cost per datagram.

Product D has the matching defect on the other side of the same mechanism: a probe conflict cancels the only timer and reschedules nothing, so the responder stops responding.

mDNS is a protocol that volunteers answers. That is its entire purpose, and it is also why amplification and suppression are first-class attacks against it rather than edge cases.

Finding these with Penzzer

Select INET4 or INET6, since mDNS runs on both, and point the run at the device under test. No credentials, no pairing, no agent on the target.

A run fires six mDNS attack modules, each built from a specific defect shape and each reporting by name:

ModuleThe shape it provokes
Name decompression loopsCycles, backward pointers and per-label work budgets
Record count and RDLENGTH boundsDeclared counts and lengths against what actually follows
Short datagram header readsA header read before the header has arrived
Reply amplification and probe conflictAnswers to a spoofed source, and a responder that stops
Reply suppression above the output sizeKnown-answer handling that silences a legitimate responder
Unsolicited announce injectionRecords accepted and acted on without being asked for

Those six cover every group of defects described above. Each one asks a specific question about the device in front of it: does this parser follow a compression pointer into a loop, does it compare a declared length against the bytes that arrived, does it decode a header before the header is there, does it answer a spoofed source. A run reports the shape it provoked and what the device did about it, by name, so a result is a statement about the device rather than something to be reconstructed from a crash dump. No instrumentation, no firmware access and no source are needed to get it.

What this article is, and is not

The 24 defects above are a sample of what this kind of testing surfaces, not a description of Penzzer's mDNS coverage. They are what seven specific products happened to get wrong.

The attack library as a whole spans defect classes well beyond what this article shows: memory safety, information disclosure, resource exhaustion, availability, hangs and algorithmic complexity, input validation, protocol state logic, type and format handling, and error handling. Across the 107 protocols with a protocol-aware target, a run also generates its test cases from the structure of the protocol itself, walking each field through the boundaries where parsers break.

The same limit applies in reverse. What a test looks for is a property of the test, not a claim about your device. A module that reports nothing means that shape did not provoke anything.

If you ship something that speaks mDNS

It is probably listening right now, on a network you do not control, before anyone has logged into it.

Three questions. Does your name decompressor bound its total work per packet, rather than per name or per label? Does every declared length and count get compared against the bytes that actually arrived, including the 12-byte header itself? And does any discovery state in your implementation have a ceiling, given that a hostile link can fill it for free?

The implementations here are not careless. Several of them are careful code that bounds the wrong scope by one level. mDNS is unauthenticated, multicast, and listening on devices that nobody has configured yet, which means the distance between a parser defect and an unauthenticated remote one is zero.

Other Post
Uncover Hidden Vulnerabilities

Identify security flaws before attackers do, automatically and at scale with Penzzer's intelligent fuzzing engine.

Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.