You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Uncaught RecursionError (DoS) on deeply nested lists; blockquotes are guarded but lists are not #1679
markdown.markdown() raises an uncaught RecursionError on a deeply nested list. The input is small and plausible as user-submitted content, so any service that renders untrusted Markdown (comments, wikis, issue trackers) can be crashed by it. This is the same class as CVE-2025-69534 (#1534) — an uncaught exception reached while parsing untrusted input — with a different trigger.
Reproduce
>>> import markdown
>>> doc ="".join(""* i +"- x\n"for i inrange(600)) # ~6 KB, 600 nested bullets
>>> markdown.markdown(doc)
Traceback (most recent call last):
...
File ".../markdown/blockprocessors.py", line 411, in run
self.parser.parseBlocks(lst[-1], [item])
File ".../markdown/blockprocessors.py", line 197, in run
self.parser.parseBlocks(parent[-1], [block])
...
RecursionError: maximum recursion depth exceeded
It crashes from a depth of roughly 500 on CPython's default limit. Ordered lists (1. x) trigger it the same way. The recursion alternates between OListProcessor.run and ListIndentProcessor.run, one frame pair per level of indentation.
Why this looks like a gap rather than an inherent limit
The project already has a mechanism for exactly this, and already applies it to the other block type that recurses per level — blockquotes:
With that guard, a 2000-deep blockquote renders fine instead of crashing:
>>> len(markdown.markdown(">"*2000)) # guarded: no crash
14861
#799 (the blockquote version of this report) was fixed this way. The list processors — ListIndentProcessor, OListProcessor, UListProcessor — all recurse through parseBlocks/parseChunk but none calls nearing_recursion_limit(), so the list path still overflows.
A note for whoever fixes it
The obvious one-line change — adding not util.nearing_recursion_limit() to ListIndentProcessor.test() — does stop the RecursionError, but I found it then exposes a slow degraded path: a 2000-deep indented block takes many seconds rather than rendering promptly, where the blockquote guard degrades cleanly. So the list case seems to need a little more than copying the guard verbatim, which is why I am reporting it rather than sending a patch I am not confident in. Happy to help test a fix.
Environment
Python-Markdown master (3.11.x) and 3.11.2 on PyPI
CPython 3.12, Ubuntu 24.04
Severity
Denial of service: a small, plausible input (a few KB of nested list markers) crashes the parser with an uncaught exception, affecting any caller that renders untrusted Markdown.
Activity