Bug 2439088 (CVE-2026-2291)

Summary: CVE-2026-2291 dnsmasq: dnsmasq: heap buffer overflow in cache via NAME_ESCAPE expansion
Product: [Other] Security Response Reporter: OSIDB Bzimport <bzimport>
Component: vulnerabilityAssignee: Product Security DevOps Team <prodsec-dev>
Status: NEW --- QA Contact:
Severity: medium Docs Contact:
Priority: medium    
Version: unspecifiedCC: rhel-process-autobot, security-response-team, watson-tool-maintainers
Target Milestone: ---Keywords: Security
Target Release: ---   
Hardware: All   
OS: Linux   
Whiteboard:
Fixed In Version: Doc Type: ---
Doc Text:
A heap buffer overflow was discovered in dnsmasq's DNS cache. When processing DNS responses, dnsmasq expands certain characters into longer escape sequences, but the cache buffer is not sized to hold the expanded result. A specially crafted DNS response can overflow this buffer, potentially crashing the dnsmasq process or poisoning DNS cache records.
Story Points: ---
Clone Of: Environment:
Last Closed: Type: ---
Regression: --- Mount Type: ---
Documentation: --- CRM:
Verified Versions: Category: ---
oVirt Team: --- RHEL 7.3 requirements from Atomic Host:
Cloudforms Team: --- Target Upstream Version:
Embargoed:
Deadline: 2026-05-09   

Description OSIDB Bzimport 2026-02-11 20:06:20 UTC
Summary:
`extract_name()` tracks name length in wire-format bytes and enforces `namelen < MAXDNAME`. But three byte values (0x00, 0x2E, 0x01) undergo 2-byte NAME_ESCAPE expansion in the presentation format. A wire-format name at the 1024-byte limit can expand to 2031 bytes in presentation format.

The code accounted for this correctly when sizing `namebuff` -- the comment at `option.c:5942` explains the `(MAXDNAME * 2) + 1` allocation. But `union bigname` in `dnsmasq.h` is still sized at `MAXDNAME`:

```c
// dnsmasq.h:481
union bigname {
char name[MAXDNAME]; // 1025 bytes
union bigname *next;
};
```

So the `strcpy` at `cache.c:760` can write up to 2031 bytes into a 1025-byte buffer:

```c
strcpy(cache_get_name(new), name); // name comes from daemon->namebuff (2051 bytes)
```

A DNS response containing 16 labels of 63 NUL bytes (wire length 1024, passes the check) produces a presentation-format string of 2031 bytes -- a 1006-byte overflow.

## Trigger
A PTR or CNAME response with NUL-byte-filled labels triggers it. NUL bytes in DNS labels are legal per RFC 2181 Section 11, and I confirmed Unbound and BIND forward them without sanitization. No special dnsmasq configuration is needed.

## Impact
- **Crash:** A single malicious DNS response corrupts heap metadata. ASAN reports `WRITE of size 2032` at `cache.c:760`. Release builds crash on subsequent operations (`malloc(): corrupted top size`).

- **Cache poisoning:** The overflow can overwrite an adjacent `bigname.name[]` with a target domain string, causing `cache_find_by_name()` to match the target domain against an entry with the attacker's IP. The `crec` metadata is untouched -- only the name changes. This bypasses TXID/port randomization since the attacker is the legitimate authoritative server for their own zone.

## Affected Versions
The NAME_ESCAPE expansion was introduced in commit `cbe379a` (2015-04-21, "Handle domain names with '.' or /000 within labels"), first released in **v2.73**. That same commit upsized `namebuff` to `MAXDNAME * 2` with an explicit comment about the 2x expansion -- but `union bigname` was not updated. From v2.73 through v2.89, the expansion only ran in DNSSEC-enabled builds.

In commit `638c7c4` (2023-03-23, "Add --cache-rr to enable caching of arbitrary RR types"), released in **v2.90**, the NAME_ESCAPE expansion was made **unconditional** -- it now runs in all builds regardless of DNSSEC configuration. The `namebuff` allocation was moved to `read_opts()` and unconditionally sized at `(MAXDNAME * 2) + 1`.

- **v2.73 -- v2.89:** Vulnerable in DNSSEC-enabled builds only.
- **v2.90 -- v2.92 (current):** Vulnerable in all builds, all configurations.

## ASAN Output
Built v2.92 at `d8f66f4` with `-fsanitize=address -g -O0`. Triggered via a single PTR response containing 16 labels of 63 NUL bytes:

```
==3387188==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x519000001888
WRITE of size 2032 at 0x519000001888 thread T0
#0 strcpy
#1 really_insert cache.c:760
#2 cache_insert cache.c:626
#3 extract_addresses rfc1035.c:770
#4 process_reply forward.c:824
#5 return_reply forward.c:1426
#6 reply_query forward.c:1298
#7 check_dns_listeners dnsmasq.c:1905
#8 main dnsmasq.c:1297

0x519000001888 is located 0 bytes after 1032-byte region [0x519000001480,0x519000001888)
allocated by thread T0 here:
#0 calloc
#1 whine_malloc util.c:346
#2 really_insert cache.c:731
```

## Patch
Tested against v2.92 (`d8f66f4`): ASAN build survives the same payload that previously triggered the overflow. Applicable with `git am`: