Skip to content

Security: Unbounded _seen_logs module dict retains tracebacks (and packet bytes) for every unique decode error #1714

@bluetoothbot

Description

@bluetoothbot

Problem

_seen_logs: dict[str, int | tuple] = {} is a module-level dict that is written but never read-from-empty and never expired. _log_exception_debug keys it by exc_str = str(exc_info[1]) and stores the full sys.exc_info() triple as the value. The exception messages produced by the decoder embed attacker-controlled values: self.source (peer IP and ephemeral source port, which varies per packet), the byte offset, and the pointer link — for example f"DNS compression pointer at {off} points to {link} beyond packet from {self.source}" (line 446) and similar at lines 417, 437, 450, 454, 465, 469. Each distinct combination yields a new key, and the stored traceback keeps frame references whose locals include self.data (the entire raw packet, up to 8966 bytes). A LAN attacker sending malformed packets that vary in offset/link/source-port can cause the parser to retain a slow-growing memory footprint of ~9 KB per unique exception string — easily millions of distinct strings, MBs-to-GBs over time — and the dict is never bounded or trimmed.

Why This Matters

The same multicast attack surface as the recursion finding (any host on the local link) lets a low-rate attacker grow long-running zeroconf processes' memory until OOM, with no observable error-rate spike on the wire. Particularly impactful for embedded / always-on consumers (Home Assistant on a Raspberry Pi) where memory headroom is small.

Suggested Fix

Either (a) bound the dict (e.g. if len(_seen_logs) > 128: _seen_logs.clear() before insertion, or back it with functools.lru_cache semantics keyed on a stable signature), or (b) avoid keeping exc_info at all — store a sentinel like True instead so the bytes/traceback aren't retained:

if exc_str not in _seen_logs:
    _seen_logs[exc_str] = True   # do not retain exc_info / traceback
    log_exc_info = True

Combine with (c) normalizing the exception strings so peer IP/port and packet offsets are not part of the dedup key — e.g. format messages with a fixed template and pass dynamic values as logger args.

Details

Severity 🟡 Medium
Category dos
Location src/zeroconf/_protocol/incoming.py:66-67, 183-192
Effort ⚡ Quick fix

🤖 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