
Automated defect verification tool for 6 dnsmasq CVEs (CVE-2026-2291, 4890, 4891, 4892, 4893, 5172)
Automated black-box tool for verifying 6 dnsmasq vulnerabilities (May 2026). Sends attack packets to a live DUT and reports PASS/FAIL — no source code access needed.
# Set DUT DNS to your laptop's WAN IP via GUI first, then:
sudo python3 dnsmasq_cve_verify.py --laptop <YOUR_WAN_IP> --dut <DUT_LAN_IP> --dut-pass <SSH_PASS>
# Example:
sudo python3 dnsmasq_cve_verify.py --laptop 10.0.0.211 --dut 192.168.1.1 --dut-pass '12345Asdf@'
| CVE | CVSS | Type | Attack Vector | Affected Feature |
|---|---|---|---|---|
| CVE-2026-2291 | 9.2 | Heap buffer overflow | Remote | extract_name() — always active |
| CVE-2026-5172 | 7.5 | OOB read / crash | Remote | extract_addresses() — always active |
| CVE-2026-4890 | 7.5 | Infinite loop DoS | Remote | NSEC bitmap parsing (--dnssec) |
| CVE-2026-4891 | 5.3 | Heap OOB read | Remote | RRSIG validation (--dnssec) |
| CVE-2026-4892 | 8.4 | Heap overflow → root | Local/Adjacent | DHCPv6 CLID (--dhcp-script + DHCPv6) |
| CVE-2026-4893 | 5.3 | Validation bypass | Remote | ECS source check (--add-subnet) |
Root cause: union bigname declares char name[MAXDNAME] but escaped characters can expand a name to 2*MAXDNAME+1 bytes, causing heap overflow.
Test method: Sends DNS queries containing domain names with high-bit characters (0x80+) that get \DDD escaped internally (4 bytes per input byte). If dnsmasq crashes or stops responding, it's vulnerable.
Patched behavior: Rejects oversized names gracefully (FORMERR/REFUSED) or uses enlarged buffer.
Root cause: Falsified rdlen field lets extract_name() advance pointer past record end. Remaining-bytes underflow produces a huge value → massive OOB read → crash.
Test method: Sends DNS responses with CNAME records where rdlen is smaller than the actual encoded name. If dnsmasq crashes, it's vulnerable.
Patched behavior: Validates that pointer stays within declared rdlen boundary after extract_name().
Root cause: NSEC type bitmap parsing advances by p[1] instead of p[1]+2 (missing window header size). With bitmap_length=0, pointer never advances → infinite loop.
Test method: Sends a crafted NSEC record with window=0, bitmap_length=0. If dnsmasq stops responding to ALL queries (hangs, not crashes), it's vulnerable. Exploitable BEFORE RRSIG validation.
Patched behavior: Advances by p[1]+2 and skips zero-length bitmaps.
Root cause: rdlen in RRSIG not validated against minimum size (18 + signer name). Calculated signature length underflows negative → treated as huge → OOB read.
Test method: Sends RRSIG records with rdlen=10 (way below minimum 31+ bytes). Crash = vulnerable.
Patched behavior: Validates rdlen >= fixed_fields + signer_name_length before computing signature length.
Root cause: DHCPv6 CLIDs (up to 65535 bytes) get hex-encoded via sprintf("%.2x") into daemon->packet (5131 bytes). 3000-byte CLID → 6000-byte hex string → overflow. Helper process runs as root.
Test method: Sends DHCPv6 SOLICIT with 3000-byte Client Identifier. Requires IPv6 adjacency and --dhcp-script configured. Helper crash = vulnerable.
Patched behavior: Truncates or validates CLID length before hex encoding.
Note: Some builds compile with -DNO_DHCP6 and are NOT affected by this CVE.
Root cause: process_reply() passes OPT record length (~23 bytes) instead of full packet length to check_source(). All bounds checks fail → function always returns 1 (valid).
Test method: Sends DNS queries with EDNS Client Subnet option containing spoofed source prefixes. If dnsmasq echoes ECS back without validation, it's vulnerable.
Patched behavior: Passes full packet length to check_source(), enabling proper bounds checks per RFC 7871 Section 9.2.
Upgrade to dnsmasq 2.92rel2 (recommended)
dnsmasq_cve_verify.py)The primary QA tool. Runs on the testing laptop, sends attack packets to the DUT, and reports clear PASS/FAIL for each CVE. No modification of the DUT is needed beyond read-only SSH access for state inspection.
┌─────────────────────────────────────────────────────────────────────┐
│ Testing Laptop │
│ │
│ LAN interface WAN interface │
│ <LAPTOP_LAN_IP> <LAPTOP_WAN_IP> │
│ │ │ │
│ │ ┌────┴──────────────┐ │
│ │ │ Malicious DNS │ │
│ │ │ Server (port 53) │ │
│ │ └────┬──────────────┘ │
│ │ │ │
└────────┼───────────────────────────────┼────────────────────────────┘
│ LAN subnet │ WAN subnet
│ │
┌────────┼───────────────────────────────┼────────────────────────────┐
│ │ │ │
│ LAN: <DUT_LAN_IP> WAN: <DUT_WAN_IP> │
│ (LAN gateway) (WAN uplink) │
│ │
│ DUT (Linksys Router) │
│ dnsmasq (any version < 2.92rel2) │
│ │
│ resolv-file=/etc/resolv.conf │
│ → nameserver <LAPTOP_WAN_IP> ← set via GUI, forwards to us │
│ │
└─────────────────────────────────────────────────────────────────────┘
Data flow:
1. Tool sends DNS query to DUT LAN IP (port 53)
2. DUT's dnsmasq can't resolve locally → forwards upstream to LAPTOP_WAN_IP
3. Our malicious server on WAN interface replies with exploit payload
4. DUT's dnsmasq processes the malicious response → crash/hang/survive
5. Tool checks DUT state via SSH (read-only)
Example setup (your IPs will differ):
| Role | IP (example) |
|---|---|
| Laptop LAN | 192.168.1.254 |
| Laptop WAN | 10.0.0.211 |
| DUT LAN | 192.168.1.1 |
| DUT WAN | 10.0.0.214 |
The key requirement: Laptop WAN IP and DUT WAN IP must be on the same subnet, so the DUT can reach the laptop as an upstream DNS server.