Skip to content

fix(massdns): guard ptrToIp against out-of-bounds read for short names - #3414

Closed
toor11 wants to merge 1 commit into
nmap:masterfrom
toor11:fix/dns-ptrtoip-short-name-guard
Closed

fix(massdns): guard ptrToIp against out-of-bounds read for short names#3414
toor11 wants to merge 1 commit into
nmap:masterfrom
toor11:fix/dns-ptrtoip-short-name-guard

Conversation

@toor11

@toor11 toor11 commented Jul 20, 2026

Copy link
Copy Markdown

What

DNS::Factory::ptrToIp() (in libnetutil/massdns.cc) finds the reverse-DNS suffix by starting a strcasestr at a fixed offset from the end of the name:

strcasestr(cptr + ptr.length() + 1 - sizeof(C_IPV4_PTR_DOMAIN), C_IPV4_PTR_DOMAIN)

sizeof(".in-addr.arpa") is 14, so the search starts at cptr + len - 13. When len < 13 this pointer is before the start of the name, and strcasestr reads from it. The IPv6 branch has the same issue with sizeof(".ip6.arpa") = 10 (cptr + len - 9).

Reachability

The argument is the answer/CNAME owner name decoded straight off the wire — process_result(a.name, ...) and the CNAME branch — with no length check on either path. A root label decodes to "." (length 1) since 8769ab35f "Correctly handle root domain label", so a DNS server can return a short owner name and trigger this during a normal reverse-DNS lookup.

Impact

Low, but real on libc++ targets. On libc++ (the default C++ library on macOS and the BSDs) the short-string buffer sits near the start of the std::string object, so the computed pointer lands outside it. Building the function against libc++ with AddressSanitizer, exercised through the real std::string API:

$ clang++ -stdlib=libc++ -fsanitize=address,undefined ...
ERROR: AddressSanitizer: stack-buffer-overflow
READ of size 1 ...
    [256, 280) 's' <== Memory access ... underflows this variable

With the guards in this PR, the same inputs are clean. On libstdc++ (the default on Linux) the inline buffer sits high enough in the object that the read stays inside it, so the access is inert there — but it is still undefined pointer arithmetic.

It is a bounded (≤ 12 bytes), read-only access whose decoded value is only compared against the address that was requested and rejected on mismatch, so there is no write and no code execution.

The fix

Guard each strcasestr on the name being at least as long as the suffix it is compared against. A name shorter than the suffix cannot end with it, so results for all valid inputs are unchanged:

if (ptr.length() >= sizeof(C_IPV4_PTR_DOMAIN) - 1
    && NULL != (p = strcasestr(cptr + ptr.length() + 1 - sizeof(C_IPV4_PTR_DOMAIN), C_IPV4_PTR_DOMAIN)))

Tests

Added short-name (including the root label ".") and uppercase-suffix cases to tests/nmap_dns_test.cc. make check-nmap goes from 43 to 53 assertions, 0 failures. (The in-tree suite builds with libstdc++, where the pre-fix read is inert, so these serve as regression guards; the libc++ ASan run above is the reproduction of the fault itself.)

DNS::Factory::ptrToIp() locates the ".in-addr.arpa" / ".ip6.arpa" suffix
with:

    strcasestr(cptr + ptr.length() + 1 - sizeof(C_IPV4_PTR_DOMAIN), ...)

When the name is shorter than the suffix, that start pointer is computed
before the beginning of the name and strcasestr reads from it. The name
is the answer/CNAME owner name taken straight off the wire, and a root
label decodes to "." (length 1) since commit "Correctly handle root
domain label", so a DNS server can trigger this on a reverse-DNS lookup.

On libc++ (the default on macOS and the BSDs) the short-string buffer
sits near the start of the std::string object, so the read lands outside
it. Building the function against libc++ with -fsanitize=address shows a
stack-buffer-overflow READ for a "." name; with the length guards below,
the same input is clean. On libstdc++ the inline buffer sits high enough
in the object that the read stays inside it, so the access is inert there
but is still undefined pointer arithmetic.

Guard each strcasestr call on the name being at least as long as the
suffix it is compared against; a name shorter than the suffix cannot end
with it, so the result is unchanged. Add short-name and uppercase-suffix
cases to tests/nmap_dns_test.cc.
@nmap-bot nmap-bot closed this in a2db276 Jul 22, 2026
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.

2 participants