Skip to content

Fix quadratic-time backtracking in attr_list attribute regex - #1656

Open
naaz234 wants to merge 1 commit into
Python-Markdown:masterfrom
naaz234:attr-list-quadratic-backtracking
Open

naaz234 wants to merge 1 commit into
Python-Markdown:masterfrom
naaz234:attr-list-quadratic-backtracking

Conversation

@naaz234

@naaz234 naaz234 commented Oct 14, 2026

Copy link
Copy Markdown

When the attr_list extension is enabled, AttrListTreeprocessor.BASE_RE matches the attribute content with ([^}\n ][^\n]*) and then allows trailing spaces before the closing brace with a separate [ ]*. [^\n]* already matches spaces, so both groups cover the same run of trailing spaces. When a block ends with an unclosed attribute list the closing brace is never found, and for a run of n spaces the engine retries the match from each of the n+1 ways to split the spaces between the two groups, which is O(n^2).

Markdown is routinely used to render untrusted input server-side, so this is reachable pre-auth as an algorithmic-complexity DoS (CWE-1333/CWE-407). A two-line block such as text {a followed by a long run of spaces hits BLOCK_RE, which is built from BASE_RE; HEADER_RE and INLINE_RE share the same shape.

Measured with markdown.markdown(text, extensions=['attr_list']) on 'text {a' + ' ' * n:

trailing spaces before after
25,000 300 ms 3 ms
50,000 1,213 ms 6 ms
100,000 3,565 ms 11 ms
200,000 10,680 ms 22 ms

The trailing [ ]* can only ever match zero characters in a successful match, because the greedy [^\n]* consumes the trailing spaces first and then gives back exactly enough for \} to match. Dropping it removes the ambiguity and makes the match linear. I verified the old and new patterns produce identical spans and capture groups across ~16k inputs (valid attribute lists, the existing test cases, and random brace/space/tab strings), so valid input is unaffected. Added a regression test and ran the full test suite and flake8.

This is the same class of defect fixed for ReferenceProcessor in #798.

`AttrListTreeprocessor.BASE_RE` matched the attribute content with
`([^\}\n ][^\n]*)` and then allowed trailing spaces before the closing
brace with a separate `[ ]*`. Because `[^\n]*` already matches spaces,
the two groups both cover the same run of trailing spaces. When a block
ends with an unclosed attribute list, the closing brace is never found,
so for a run of n spaces the engine retries the match from each of the
n+1 ways to split the spaces between the two groups, which is O(n^2).

The trailing `[ ]*` can only ever match zero characters in a successful
match (the greedy `[^\n]*` consumes the spaces first), so dropping it
leaves every match and capture group unchanged while removing the
ambiguity. A two-line block such as `text\n{a` followed by 200k spaces
drops from ~10s to ~20ms, and the cost is now linear in the input size.
@waylan

waylan commented Oct 14, 2026

Copy link
Copy Markdown
Member

Please review our AI Policy. Specifically, the following:

Contributors must be able to explain all changes upon request during review.

... The use of an AI agent to submit a pull request, issue, or comment autonomously is not permitted.

You description reads like it was written by AI. Also, as the PR template was deleted, I am assuming you had AI autonomously submit the PR. This presents 3 problems:

  1. Because you didn't write the description yourself, we don't know if you are able to explain the changes.
  2. Because the PR template was deleted, you did not disclose AI use.
  3. Having AI submit the PR is expressly forbidden and while this is a simple change to the code which is relatively easy to review, the policy requires that we close the request.

That said, it is possible that this was not AI generated. If you can honestly confirm that, then we will proceed. Otherwise, this will be closed.

PS to other maintainers: this is going to be my standard response to AI submitted PRs/issues from now on. Feel free to respond in kind if you see a new PR first.

@naaz234

naaz234 commented Oct 15, 2026

Copy link
Copy Markdown
Author

Hi @waylan, I apologize for this, I used AI for the wordings and stuff and pasted it without noticing.
Since I have gone through the change myself:The regex has two pieces in a row that can both eat spaces. When there’s a { with lots of spaces after it and no closing }, the regex engine tries every possible way of dividing the spaces between those two pieces before it gives up, and it repeats that from many starting points. That’s why it gets so slow. The second piece, [ ]*, never actually does anything useful, because the first piece already grabs all the spaces. Removing it leaves only one way to match, so the engine gives up quickly.

If the policy means this has to be closed, I understand. I’m happy to resubmit with the template filled in and the AI use disclosed, or to open an issue instead so a maintainer can make the fix.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants