Fix catastrophic backtracking (ReDoS) in inline link parsing#27
Fix catastrophic backtracking (ReDoS) in inline link parsing#27thistehneisen wants to merge 1 commit into
Conversation
The inline._inside grammar used by the link and reflink rules backtracks catastrophically on balanced nested brackets. A ~4 KB message of the form [[[[ ... x ... ]]]] blocked the parser for ~2.6s, scaling super-linearly (~20s at 8 KB). Consumers call marked() synchronously on the render thread, so a single such message froze every viewer's UI - an unprivileged, no-interaction, persistent denial of service. The bracket-pair branch \[[^\]]*\] used [^\]]* which also matches '[', making it overlap the outer repetition. Restricting it to [^\[\]]* removes the ambiguity while still allowing one level of [..] nesting and preserving the stray-']' behaviour; the sibling nolink rule already uses the equivalent non-overlapping form. Adds a timing regression test and a correctness fixture.
|
Warning Rate limit exceeded
You’ve run out of usage credits. Purchase more in the billing tab. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (4)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Backtracking (ReDoS) in inline link parsing
Summary
The
inline._insidegrammar used by the inlinelinkandreflinkrulesbacktracks catastrophically on balanced nested brackets. A single chat-message
sized input of the form
[[[[ … x … ]]]]blocks the parser for seconds andscales super-linearly:
marked()is invoked synchronously on the render thread by consumers (e.g. theMattermost web and desktop clients call it from
formatTextwith no timeout orworker). A single stored message of ~4 KB — well within the default 4,000
character post limit — therefore freezes the UI of every user who renders
that channel, thread, search result, or notification preview. Posting several
such messages produces a persistent, unprivileged, no-interaction
denial-of-service for all channel members. This is an availability issue and is
independent of
sanitize/ renderer configuration, since the cost is incurredin the tokenizer before any escaping or sanitization runs.
Root cause
The bracket-pair branch
\[[^\]]*\]uses[^\]]*, which also matches[.That makes this branch overlap the outer
(?:…)*repetition: for balancednested brackets there are exponentially many ways the engine can partition the
input across the alternation, and when the overall match ultimately fails (a
link requires
]( … )/][ref]to follow) the engine explores all of them.Note
inline.nolink(defined a few lines above) already uses thenon-overlapping form
(?:\[[^\]]*\]|[^\[\]])*and is not affected.Fix
Restrict the bracket-pair branch to
[^\[\]]*so a single pair cannot itselfcontain a
[. This removes the ambiguity that drives the backtracking whilestill allowing one level of
[..]nesting in link text, and the]-lookaheadbranch is kept (re-ordered ahead of the catch-all) so the existing
"stray
]inside link text" behaviour is preserved.One-character-class change in effect; no API or output change for valid input.
Validation
(
node test— 78/78, 1 skipped), includingnested_square_link(
[the \]` character](/url)`) and the existing nested-link / referencefixtures, confirming link parsing semantics are unchanged.
test/tests/mm_redos_nested_brackets.{text,html}asserts a one-level nested link still renders and that a bracket bomb renders
as literal text. It also serves as a hang canary — a future regression would
make
node testtime out.test/redos.jsasserts the previously-pathologicalpayloads (balanced brackets, reflink form,
a][alternation, repeated nestedpairs) each render in well under 250 ms.
Files changed
lib/marked.js— the_insideregex (one line) + explanatory comment.test/redos.js— timing regression test (node test/redos.js).test/tests/mm_redos_nested_brackets.text/.html— correctness fixture.Nils Putnins / OffSeq Cybersecurity
npu@offseq.com / https://offseq.com / https://radar.offseq.com