Skip to content

Security: Unbounded DNS record cache can be filled by any LAN peer (memory exhaustion) #1715

@bluetoothbot

Description

@bluetoothbot

Problem

DNSCache._async_add (lines 79-107) inserts every response record indexed by lowercased name into self.cache, self._expirations, self._expire_heap, and (for DNSService) self.service_cache, with no cap on entry count and no per-peer accounting. RecordManager.async_updates_from_response (record_manager.py:77-165) feeds every record from every multicast response into this path; the only protection is a TTL-floor for PTR records at _DNS_PTR_MIN_TTL = 1125 seconds (~19 min, const.py:60), which actually prolongs attacker-injected records' lifetime. Any host on the local link can multicast valid mDNS responses with unique names (up to 253 bytes each, ~10^600 keyspace) and watch them accumulate. With a ~9 KB MTU and ~250 records/packet at 100 packets/sec, a single peer can inject >2M records/minute, each pinned for 18+ minutes; on a Raspberry-Pi-class deployment (Home Assistant) this trivially OOMs the process.

Why This Matters

Same adjacent-network threat model as the previous findings. Cache exhaustion is harder to notice than a parser crash because the process keeps running while every record lookup and every periodic async_expire (every 10 s, _CACHE_CLEANUP_INTERVAL) grows linearly slower, eventually starving the asyncio loop and breaking unrelated zeroconf consumers (discovery, registration). Unlike the parser findings, exploitation requires several Mbps of crafted multicast traffic rather than a single packet, so it's noisier but still well within an unauthenticated LAN attacker's reach.

Suggested Fix

Add a coarse upper bound on cache size and per-source ingestion rate. Minimum: cap len(self.cache) (and the _expirations / service_cache dicts) at a configurable limit (e.g. 10_000 records) and reject or evict-oldest on overflow inside _async_add. Better: track per-source-IP record counts in record_manager.async_updates_from_response and drop records once a peer exceeds a quota, mirroring the rate-limiting already implemented for outgoing question/answer paths. Add a regression test that floods 100k+ records and asserts a bounded steady-state.

Details

Severity 🟡 Medium
Category dos
Location src/zeroconf/_cache.py:67-107, src/zeroconf/_handlers/record_manager.py:77-165, src/zeroconf/const.py:60
Effort 🛠️ Moderate effort

🤖 Created by Kōan from audit session

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions