[Security] ConnMan DNS proxy get_name() out-of-bounds read
R4mbb <[email protected]> Mon, 1 Jun 2026 10:57:35 +0900
| Newsgroups | dev.linux.lists.connman |
|---|---|
| Message-ID | <CA+8xMBxLOfL7CutDYEeFDjZPJorTjcxFn7O4zsSVcGT90=4eig@mail.gmail.com> |
Hello ConnMan maintainers,
I would like to report an out-of-bounds read in ConnMan's built-in DNS proxy,
reachable when parsing an attacker-controlled upstream DNS response. I have not
publicly disclosed this issue before this report.
Summary
=======
get_name() in src/dnsproxy.c performs an ineffective bounds check while parsing
DNS names. The per-label bounds check compares the packet start pointer (`pkt`)
instead of the current read cursor (`p`) against the buffer end (`max`). As a
result, the check does not protect the following memcpy() from reading past the
end of the received DNS message buffer.
For a syntactically valid DNS label, this can read up to about 63 bytes past the
packet. Because the code uses `*p` directly and only treats `0xc0..0xff` as DNS
compression pointers, reserved label bytes in the `0x40..0xbf` range can be
interpreted as plain lengths as well. In that malformed-label case,
the attempted
copy length can be larger, up to roughly 191 bytes.
The issue is reachable through ConnMan's DNS response parsing path:
parse_response() -> parse_rr() -> get_name()
when ConnMan's built-in DNS proxy processes a malicious or MITM-controlled
upstream DNS response.
Affected version / commit
=========================
Confirmed on ConnMan git HEAD:
9e46e4a021cd8566b942d1a72b41b14a0a58f506
from the canonical git.kernel.org repository.
The `pkt + label_len > max` source-side check appears to have been present
for a long time, and this issue is still present at HEAD. It is closely related
to previously fixed ConnMan DNS-proxy bugs, but it does not appear to be covered
by them.
In particular, CVE-2017-12865 affected this same get_name() helper and hardened
its output-side bounds checking for the `name` buffer, but the
source-side cursor
check before this memcpy() remained unchanged. This report is the read-side
instance in get_name(): the parser validates the packet start pointer (`pkt`),
not the current source cursor (`p`), before copying from `p + 1`.
This issue also appears distinct from CVE-2022-23097, which involved a similar
`ptr + 1 + len` over-read pattern in a different DNS-proxy path, and from
CVE-2021-26675 / CVE-2021-33833 (uncompress()), CVE-2022-23096 / CVE-2022-23098
(TCP framing), and CVE-2025-32366 / CVE-2025-32743.
Prerequisites
=============
- ConnMan is running with its built-in DNS proxy enabled.
- The attacker can control a DNS response, for example by operating a malicious
upstream resolver or by acting as an on-path/MITM attacker on the ConnMan ->
upstream DNS path.
- No authentication is required.
Root cause
==========
src/dnsproxy.c, get_name(), label branch:
unsigned label_len = *p;
if (pkt + label_len > max) /* BUG: pkt = packet start, not p */
return -ENOBUFS;
...
name[(*name_len)++] = label_len;
memcpy(name + *name_len, p + 1, label_len + 1); /* OOB read source */
*name_len += label_len;
...
p += label_len + 1;
...
if (p >= max) /* checked only AFTER the memcpy */
return -ENOBUFS;
`pkt` is the constant packet start. Therefore `pkt + label_len > max` does not
validate whether the current cursor `p` has enough remaining bytes for
`label_len + 1` bytes to be copied from `p + 1`.
For a normal DNS label, `label_len` is at most 63, so the existing check is
ineffective for ordinary packet sizes. In addition, the code uses `*p` directly
without masking and only handles the `0xc0..0xff` compression-pointer range
specially. Reserved label bytes in the `0x40..0xbf` range are not
rejected before
the copy, so malformed label bytes can produce a larger attempted copy length.
The cursor `p` is only checked after the memcpy(). If `p` is near `max`, the
memcpy() reads past the DNS message buffer before the later `if (p >=
max)` check
can reject the name.
One reachable construction is to use a compression pointer in an answer RR name
that points near the final bytes of the packet. The compression offset check
allows offsets below the packet length. A label-length byte near the end of the
message then causes get_name() to copy from beyond `max`.
Relevant reachable sites:
- get_name() used for the RR owner name in parse_rr()
- get_name() used for names inside RDATA, such as CNAME/NS/PTR targets
Buffer type differs by transport path:
- UDP path: stack receive buffer
- TCP path: heap receive buffer, which makes the allocation-boundary OOB read
straightforward to detect with AddressSanitizer
Reproduction
============
I reproduced this in two ways with AddressSanitizer:
1. A standalone harness using the exact get_name() function copied from HEAD,
driven with a crafted packet buffer ending in a maximum-length label byte.
2. An end-to-end parser harness using the verbatim parse_response(), parse_rr(),
and get_name() bodies from HEAD, driving:
parse_response() -> parse_rr() -> get_name()
with a crafted DNS response whose answer name uses a compression pointer to
the final bytes of the packet. This is still a standalone harness rather than
a live connmand integration test, but it exercises the relevant parser chain.
Observed ASan result:
AddressSanitizer: heap-buffer-overflow
READ of size 64
#1 get_name src/dnsproxy.c (the memcpy)
#2 parse_rr
#3 parse_response
I can provide the standalone PoC and the end-to-end reproduction harness on
request.
Impact
======
This is an out-of-bounds read (CWE-125) past the DNS message buffer,
triggered by
an attacker-controlled DNS response.
The realistic impact appears limited to potential denial of service:
- ASan builds reliably detect the OOB read.
- In non-instrumented builds, a crash depends on whether the over-read
crosses an
inaccessible memory region.
- If the over-read does not fault, the malformed RR is rejected
immediately after
the copy when the cursor check runs.
I do not believe this leads to information disclosure or RCE on its own. After
the over-reading memcpy(), `p += label_len + 1` makes the cursor reach or exceed
`max`; the next `if (p >= max) return -ENOBUFS;` aborts get_name(), and the
enclosing RR is discarded. The out-of-bounds bytes are therefore not expected to
be cached, reflected, or otherwise used.
Suggested CVSS v3.1:
AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:L = 3.7 Low
If scored specifically for a malicious configured/upstream resolver
with reliable
response delivery, AC:L may be arguable, giving:
AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L = 5.3 Medium
Suggested fix
=============
Validate the current cursor before copying the label data:
- if (pkt + label_len > max)
+ if (label_len + 1 > max - p)
return -ENOBUFS;
It would also be safer to reject reserved DNS label encodings in this branch
instead of treating every non-compression byte as a plain label length.
In addition, consider adding a `ptr < buf + buflen` guard at the top of the
answer loop before calling parse_rr().
Disclosure
==========
I have not published the PoC or report elsewhere. Please let me know whether you
would like the standalone PoC, the end-to-end ASan harness, or a proposed patch.
Thank you.