Summary
protocol/dns.c has multiple memory-safety bugs in DNS name encode/decode used by dns_rr_unpack / dns_unpack / nslookup / dns_query.
I am reporting these as original findings against current libhv master (lab verification 2026-07-22). I did not find a prior issue/CVE specifically for dns_name_decode / dns_name_encode bounds.
Bug 1 — dns_name_decode unbounded write (CWE-120 / CWE-787)
// protocol/dns.c
int dns_name_decode(const char* buf, char* domain) {
const char* p = buf;
int len = *p++;
int buflen = 1;
while (*p != '\0') {
if (len-- == 0) {
len = *p;
*domain = '.';
} else {
*domain = *p;
}
++p;
++domain; // never checks remaining space in domain[]
++buflen;
}
*domain = '\0';
...
}
Callers pass rr->name which is only DNS_NAME_MAXLEN (256) bytes (dns_rr_t.name[256]), but the decoder never enforces that limit. A crafted wire name that expands to >255 bytes overwrites past name[].
There is also no remaining-input length parameter: a missing terminating 0x00 causes unbounded read of buf.
Local confirmation: canary after a 256-byte domain buffer is smashed (buflen returned 513).
Bug 2 — compression-pointer path leaves name[] uninitialized (CWE-457 / CWE-125)
int dns_rr_unpack(...) {
if (*(uint8_t*)p >= 192) {
namelen = 2;
// name is never written / NUL-terminated
} else {
namelen = dns_name_decode(buf, rr->name);
}
...
}
Any RR using a compression pointer leaves rr->name untouched. Later strlen / %s use is an OOB read.
Bug 3 — dns_name_encode unbounded write (CWE-787)
int dns_name_encode(const char* domain, char* buf) {
...
while (*p != '\0') {
...
++buf; // no capacity check
++buflen;
}
dns_rr_pack uses a fixed char encoded_name[256] and calls encode without checking domain length. A long rr->name overruns encoded_name.
Local confirmation: encode of a long name returned length 502 past a 256-byte buffer canary.
Attack surface
Any application using libhv DNS helpers (dns_unpack, dns_query, nslookup, examples under protocol DNS) that processes untrusted DNS responses (or packs attacker-influenced names) can hit these paths.
Not the default HTTP-only build path, but enabled with protocol/DNS (WITH_PROTOCOL / equivalent).
Suggested fix
- Change APIs to pass capacity / remaining wire length, e.g.
dns_name_decode(const char* buf, size_t buflen, char* domain, size_t domaincap)
- Reject expanded names
>= 256 and individual labels > 63.
- On compression pointer, either fully resolve into
name[] with bounds, or set name[0] = '\0' / explicit flag and never treat as C-string until resolved.
- Cap
dns_name_encode output to provided buffer size; return error if domain too long.
- Add unit tests + ASan fuzz of
dns_unpack.
Reproduction (lab)
Standalone canary harnesses (same algorithm as tree):
- long wire name →
dns_name_decode smashes canary after 256-byte domain
- compression pointer path →
name[] never written
- long domain string →
dns_name_encode smashes 256-byte encode buffer
Happy to attach minimal C files or open a PR with a patch if useful.
Researcher
LL-V
GitHub: https://github.com/LL-V
Please credit if a CVE is requested. Testing was local-only against source / standalone reimplementation of the vulnerable functions.
Summary
protocol/dns.chas multiple memory-safety bugs in DNS name encode/decode used bydns_rr_unpack/dns_unpack/nslookup/dns_query.I am reporting these as original findings against current libhv
master(lab verification 2026-07-22). I did not find a prior issue/CVE specifically fordns_name_decode/dns_name_encodebounds.Bug 1 —
dns_name_decodeunbounded write (CWE-120 / CWE-787)Callers pass
rr->namewhich is onlyDNS_NAME_MAXLEN(256) bytes (dns_rr_t.name[256]), but the decoder never enforces that limit. A crafted wire name that expands to >255 bytes overwrites pastname[].There is also no remaining-input length parameter: a missing terminating
0x00causes unbounded read ofbuf.Local confirmation: canary after a 256-byte domain buffer is smashed (
buflenreturned 513).Bug 2 — compression-pointer path leaves
name[]uninitialized (CWE-457 / CWE-125)Any RR using a compression pointer leaves
rr->nameuntouched. Laterstrlen/%suse is an OOB read.Bug 3 —
dns_name_encodeunbounded write (CWE-787)dns_rr_packuses a fixedchar encoded_name[256]and calls encode without checking domain length. A longrr->nameoverrunsencoded_name.Local confirmation: encode of a long name returned length 502 past a 256-byte buffer canary.
Attack surface
Any application using libhv DNS helpers (
dns_unpack,dns_query,nslookup, examples under protocol DNS) that processes untrusted DNS responses (or packs attacker-influenced names) can hit these paths.Not the default HTTP-only build path, but enabled with protocol/DNS (
WITH_PROTOCOL/ equivalent).Suggested fix
dns_name_decode(const char* buf, size_t buflen, char* domain, size_t domaincap)>= 256and individual labels> 63.name[]with bounds, or setname[0] = '\0'/ explicit flag and never treat as C-string until resolved.dns_name_encodeoutput to provided buffer size; return error if domain too long.dns_unpack.Reproduction (lab)
Standalone canary harnesses (same algorithm as tree):
dns_name_decodesmashes canary after 256-byte domainname[]never writtendns_name_encodesmashes 256-byte encode bufferHappy to attach minimal C files or open a PR with a patch if useful.
Researcher
LL-V
GitHub: https://github.com/LL-V
Please credit if a CVE is requested. Testing was local-only against source / standalone reimplementation of the vulnerable functions.