I found several out-of-bounds read bugs in getdns's DNS message parsing while doing differential fuzzing against Unbound, LDNS, c-ares, and Knot DNS. They're all in the wire-to-dict parsing path. Tested on v1.7.3.
1. Heap-buffer-overflow in gldns_read_uint32() — truncated RR TTL (CWE-125)
When a DNS message header claims more RRs than actually exist in the data (e.g. ANCOUNT=1 but the answer section is truncated before the TTL field), getdns_wire2msg_dict() reads past the buffer in gldns_read_uint32() at gbuffer.h:59.
Call chain: getdns_wire2msg_dict() → _getdns_wire2msg_dict_scan() → _getdns_rr_iter2rr_dict() → _getdns_rr_iter2rr_dict_canonical() → gldns_read_uint32() — reads 4 bytes at i->rr_type + 4 which may be past the buffer end.
ASAN trace:
READ of size 4 at 0x...
gldns_read_uint32 (gbuffer.h:59)
_getdns_rr_iter2rr_dict_canonical (util-internal.c:230)
2. OOB read via compression pointer to offset 0
When a DNS name contains a compression pointer pointing to offset 0 (the beginning of the message), getdns's dname decompression follows it and can read out of bounds depending on the message structure.
3. RDLENGTH exceeds remaining message — silently accepted
rr_iter_find_nxt() in src/rr-iter.c clamps the next-RR pointer to pkt_end instead of rejecting the parse when rr_type + 10 + RDLENGTH > pkt_end:
i->nxt = ... i->rr_type + 10 + gldns_read_uint16(i->rr_type + 8) > i->pkt_end
? i->pkt_end /* should reject */
: i->rr_type + 10 + gldns_read_uint16(i->rr_type + 8);
So getdns returns GETDNS_RETURN_GOOD for packets where RDLENGTH claims more data than exists. c-ares, LDNS, Knot, and Unbound all reject the same wire bytes.
4. Label type validation missing
The dname decompression loop doesn't properly validate label type bytes, allowing OOB reads via unexpected label types.
Cross-implementation comparison (across all findings):
Every one of these malformed inputs is rejected by the other 4 implementations I tested. getdns is consistently the only one that accepts them.
Reproduction:
# Build with ASAN
clang -fsanitize=address -g -O0 \
-I/path/to/getdns-src/build-clangfuzz/include \
-I/path/to/getdns-src/src \
h_getdns.c -L/path/to/getdns-src/build -lgetdns -o h_getdns
# Crash on truncated RR
./h_getdns crash_truncated_ttl.bin
# ASAN: heap-buffer-overflow in gldns_read_uint32
# Accept invalid RDLENGTH (other impls reject)
./h_getdns seed_71_ptr_rdlen_oob.bin
# Returns GETDNS_RETURN_GOOD instead of error
Suggested fixes:
- In
rr_iter_find_nxt(): return an error when rr_type + 10 + RDLENGTH > pkt_end instead of clamping
- In the RR iterator: validate that enough bytes remain before reading TTL/RDLENGTH fields
- In dname decompression: validate compression pointer targets and label types
Found through differential fuzzing (PathDiff v22-v23) against Unbound, LDNS, c-ares, Knot DNS.
I found several out-of-bounds read bugs in getdns's DNS message parsing while doing differential fuzzing against Unbound, LDNS, c-ares, and Knot DNS. They're all in the wire-to-dict parsing path. Tested on v1.7.3.
1. Heap-buffer-overflow in gldns_read_uint32() — truncated RR TTL (CWE-125)
When a DNS message header claims more RRs than actually exist in the data (e.g. ANCOUNT=1 but the answer section is truncated before the TTL field),
getdns_wire2msg_dict()reads past the buffer ingldns_read_uint32()atgbuffer.h:59.Call chain:
getdns_wire2msg_dict()→_getdns_wire2msg_dict_scan()→_getdns_rr_iter2rr_dict()→_getdns_rr_iter2rr_dict_canonical()→gldns_read_uint32()— reads 4 bytes ati->rr_type + 4which may be past the buffer end.ASAN trace:
2. OOB read via compression pointer to offset 0
When a DNS name contains a compression pointer pointing to offset 0 (the beginning of the message), getdns's dname decompression follows it and can read out of bounds depending on the message structure.
3. RDLENGTH exceeds remaining message — silently accepted
rr_iter_find_nxt()insrc/rr-iter.cclamps the next-RR pointer topkt_endinstead of rejecting the parse whenrr_type + 10 + RDLENGTH > pkt_end:So getdns returns GETDNS_RETURN_GOOD for packets where RDLENGTH claims more data than exists. c-ares, LDNS, Knot, and Unbound all reject the same wire bytes.
4. Label type validation missing
The dname decompression loop doesn't properly validate label type bytes, allowing OOB reads via unexpected label types.
Cross-implementation comparison (across all findings):
Every one of these malformed inputs is rejected by the other 4 implementations I tested. getdns is consistently the only one that accepts them.
Reproduction:
Suggested fixes:
rr_iter_find_nxt(): return an error whenrr_type + 10 + RDLENGTH > pkt_endinstead of clampingFound through differential fuzzing (PathDiff v22-v23) against Unbound, LDNS, c-ares, Knot DNS.