Skip to content

Commit a693918

Browse files
committed
blocks: Fix quadratic behavior in check_open_blocks
If we find consecutive blank lines inside a list item, abort early in check_open_blocks by returning NULL. This makes S_process_line skip the calls to open_new_blocks and add_text_to_container. open_new_blocks is a no-op for blank lines. add_text_to_container would add an empty line only for code and HTML blocks which we account for in check_open_blocks. Fixes part of GHSA-66g8-4hjf-77xh. Obsoletes commonmark#475.
1 parent b7a7273 commit a693918

3 files changed

Lines changed: 29 additions & 0 deletions

File tree

src/blocks.c

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -904,6 +904,31 @@ static cmark_node *check_open_blocks(cmark_parser *parser, cmark_chunk *input,
904904
if (!parse_block_quote_prefix(parser, input))
905905
goto done;
906906
break;
907+
case CMARK_NODE_LIST:
908+
// Avoid quadratic behavior caused by iterating deeply nested lists
909+
// for each blank line.
910+
if (parser->blank) {
911+
if (container->flags & CMARK_NODE__LIST_LAST_LINE_BLANK &&
912+
parser->indent == 0) {
913+
// Abort early if we encounter multiple blank lines. Returning
914+
// NULL will cause S_process_line to skip the calls to
915+
// open_new_blocks and add_text_to_container. open_new_blocks
916+
// is a no-op for blank lines. add_text_to_container closes
917+
// remaining open nodes, but since we have a second blank
918+
// line, all open nodes have already been closed when the
919+
// first blank line was processed. Certain block types accept
920+
// empty lines as content, so add them here.
921+
if (parser->current->type == CMARK_NODE_CODE_BLOCK ||
922+
parser->current->type == CMARK_NODE_HTML_BLOCK) {
923+
add_line(input, parser);
924+
}
925+
return NULL;
926+
}
927+
container->flags |= CMARK_NODE__LIST_LAST_LINE_BLANK;
928+
} else {
929+
container->flags &= ~CMARK_NODE__LIST_LAST_LINE_BLANK;
930+
}
931+
break;
907932
case CMARK_NODE_ITEM:
908933
if (!parse_node_item_prefix(parser, input, container))
909934
goto done;

src/node.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ enum cmark_node__internal_flags {
5050
CMARK_NODE__OPEN = (1 << 0),
5151
CMARK_NODE__LAST_LINE_BLANK = (1 << 1),
5252
CMARK_NODE__LAST_LINE_CHECKED = (1 << 2),
53+
CMARK_NODE__LIST_LAST_LINE_BLANK = (1 << 3),
5354
};
5455

5556
struct cmark_node {

test/pathological_tests.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,9 @@ def badhash(ref):
110110
"unclosed <!--":
111111
("</" + "<!--" * 300000,
112112
re.compile("\\&lt;\\/(\\&lt;!--){300000}")),
113+
"empty lines in deeply nested lists":
114+
("- " * 30000 + "x" + "\n" * 30000,
115+
re.compile(r"^(<\w+>\n?)+x(</\w+>\n)+$")),
113116
"reference collisions": hash_collisions()
114117
# "many references":
115118
# ("".join(map(lambda x: ("[" + str(x) + "]: u\n"), range(1,5000 * 16))) + "[0] " * 5000,

0 commit comments

Comments
 (0)