THE BUG
What happens when a DNS record claims to be shorter than the domain name packed inside it?
Read the fixOFFENSIVE LAB / PUBLICATION · DNSMASQ
A single crafted DNS record could crash dnsmasq while it validated DNSSEC.
OffensiveLab traced the flaw to get_rdata(); the upstream fix is a few lines of bounds-checking.
Find it, prove it, help fix it, write it down.
DNS forwarder & DHCP server
function get_rdata()
CWE-125, out-of-bounds read
only when validation is on
remote denial of service
one file · commit d823d8e9
Fixed upstream by Simon Kelley in dnsmasq. The commit message credits OffensiveLab's Tristan G. for spotting the problem. Commit d823d8e9 →
THE FINDING
dnsmasq parses every answer it validates. A single resource record, built to lie about its own length, was enough to take the process down.
THE BUG
What happens when a DNS record claims to be shorter than the domain name packed inside it?
Read the fixA resource record can claim a data length, its RDLEN, shorter than the domain name packed inside it.
Reachable only when DNSSEC validation is on. The malicious packets do not even need a valid signature.
The bytes left after the name turn negative, wrap to a giant unsigned value, and the parser reads off the end.
| 01SpottedOffensiveLab traces a reproducible crash in get_rdata() to a record whose name overruns its RDLEN. | Found |
| 02ReportedThe finding reaches the dnsmasq maintainer, Simon Kelley, with the case that triggers it. | Sent |
| 03Fixed upstreamA bounds check lands in src/dnssec.c, commit d823d8e9, 3 June 2026. | Patched |
MECHANISM
No exploit code here, just the shape of the bug and the check that now stops it.
Every DNS resource record carries RDLEN, the number of bytes of record data that follow it. Some record types also pack a domain name into that data.
A malicious server sends a record whose embedded name is longer than the RDLEN it claims: the name alone already runs past where the record is supposed to end.
After reading the name, get_rdata() works out how many bytes are left for the fields behind it: end minus position. Here that is negative, and it is held in an unsigned counter, so it wraps to a very large positive number.
The parser then copies that many bytes, reaching far past the record and the packet itself. dnsmasq hits unmapped memory and crashes with SIGSEGV.
if (d == (u16)0) { /* domain-name, canonicalise */ int len; if (!extract_name(…) || (len = to_wire(buff)) == 0) continue; state->c = len; /* malformed, or name runs past the RR end: abort, so a negative length never wraps to a huge one */ if (!extract_name(…) || state->ip > state->end) return 0; state->c = to_wire(buff); /* always > 0 */ state->op = (unsigned char *)buff; } else { /* short RR: clamp to bytes left, finish if none */ if ((state->end - state->ip) < d && (d = state->end - state->ip) == 0) return 0; state->op = state->ip;THE FIX
The patch makes get_rdata() refuse a record whose name does not fit, instead of trusting the arithmetic that follows.
| Change | In |
|---|---|
| Guard the name branchReturn instead of continuing when the name is malformed or reaches past the end of the record, so the leftover length can never go negative. | get_rdata() |
| Clamp the plain dataWhen fewer bytes remain than the record declares, clamp to what is left and return if nothing is, covering fixed-size fields a short record claims as zero bytes. | get_rdata() |
| Tidy a stray statementRemove a duplicated semicolon left over from earlier code. | dnssec.c |