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
Problem
DNSCache._async_add(lines 79-107) inserts every response record indexed by lowercased name intoself.cache,self._expirations,self._expire_heap, and (forDNSService)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 = 1125seconds (~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_cachedicts) 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 inrecord_manager.async_updates_from_responseand 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
src/zeroconf/_cache.py:67-107, src/zeroconf/_handlers/record_manager.py:77-165, src/zeroconf/const.py:60🤖 Created by Kōan from audit session