Skip to content

Unbounded nested components can trigger RecursionError in repr() / str() #1370

@gistrec

Description

@gistrec

Summary

Deeply nested iCalendar components can be parsed successfully, but formatting the resulting object with repr(), str(), or an f-string can raise RecursionError.

The recursive path appears to be Component.__repr__():

# src/icalendar/cal/component.py
def __repr__(self):
    """String representation of class with all of it's subcomponents."""
    subs = ", ".join(str(it) for it in self.subcomponents)
    return f"{self.name or type(self).__name__}({dict(self)}{', ' + subs if subs else ''})"

This can cause a denial-of-service in applications that parse untrusted .ics files and later log or format the parsed calendar object.

Reproducer

import icalendar

ics = (
    "BEGIN:VCALENDAR\r\n"
    "VERSION:2.0\r\n"
    "PRODID:-//x//x//EN\r\n"
    + "BEGIN:VEVENT\r\n" * 500
    + "UID:t@e\r\n"
    "DTSTAMP:20260101T000000Z\r\n"
    + "END:VEVENT\r\n" * 500
    + "END:VCALENDAR\r\n"
)

cal = icalendar.Calendar.from_ical(ics)  # succeeds
str(cal)  # RecursionError

Observed behavior

Operation Result
Calendar.from_ical(ics) succeeds
repr(cal) / str(cal) / f"{cal}" raises RecursionError
cal.to_ical() succeeds
cal.walk() succeeds

The payload is small, around 13 KB.

Impact

This is a denial-of-service / robustness issue. Example scenarios:

  • a CalDAV server or calendar import service logs a parsed calendar object;
  • an email invitation parser includes the object in diagnostics;
  • error reporting formats local variables containing the parsed calendar.

The crash is not in parsing itself, but in later diagnostic/logging paths.

Possible fixes

Any of these should mitigate the issue:

  1. Reject excessive nesting during parsing, for example with a component nesting-depth limit.
  2. Make __repr__ iterative or depth-limited.
  3. Truncate subcomponent output in __repr__ after a safe depth.

A parser-level limit would prevent this class of issue more generally, but a bounded __repr__ may be less invasive if compatibility is a concern.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No fields configured for Bug.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions