Skip to content

Avoid quadratic-time parsing for inline HTML #447

Description

@fromVeeko

Summary

Every < in a document makes the inline parser look for the end of the construct it might start, and the scan is thrown away when that end is not there. On input made of < whose construct never ends, each one walks to the end of the input, so parsing is quadratic in the size of the input. Roughly 800 KB of x <!-- > keeps a core busy for over a minute, so a single request can stall a worker in any application that renders untrusted Markdown (comments, chat, wikis).

Details

HtmlInlineParser is triggered by < and has four scans of this shape:

Construct Looks for Code
processing instruction ?> L153
comment --> L177
CDATA section ]]> L196
declaration > L215

Each of them ends up in Scanner.find, which advances one character at a time and only gives up at the end of the input:

while (scanner.find('-') >= 0) {   // no `-->` ahead, so this walks to the end of the input
    if (scanner.next("-->")) {
        return true;
    } else {
        scanner.next();
    }
}

The candidate is then rejected, the position is reset, and the next < starts the same scan over again. With n openers that is n scans of average length n/2. Plain paragraph text is enough to reach it:

Input, repeated Size at 100,000 25,000 50,000 100,000 100,000 with the patch
x <? > 586 KB 1.7 s 6.6 s 26.7 s 0.08 s
x <!-- > 781 KB 4.6 s 19.2 s 76.7 s 0.07 s
x <![CDATA[ > 1270 KB 3.5 s 15.3 s 63.0 s 0.13 s

Measured with Parser.builder().build() and HtmlRenderer.builder().build() on JDK 25, current main (7a68e920). Times roughly quadruple when the input doubles.

Each payload deliberately contains a > close to every <, so that the separately reported autolink scan for > stays cheap and only the inline HTML scanning is measured. It also starts with text so the line doesn't begin with <, which would parse as an HTML block instead.

A PR will shortly follow.

Credits

Found by Team Atlanta.
Collected and verified by OSTIF using AI with their own harness.
Manually verified, fixed, and reported by Shielder.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions