Skip to content

Commit b4f0106

Browse files
nwellnhofkevinbackhouse
authored andcommitted
Avoid quadratic output growth with reference links
Keep track of the number bytes added through expansion of reference links and limit the total to the size of the input document. Always allow a minimum of 100KB. Unfortunately, cmark has no error handling, so all we can do is to stop expanding reference links without returning an error. This should never be an issue in practice though. The 100KB minimum alone should cover all real-world cases. See issue commonmark#354.
1 parent 6a6e335 commit b4f0106

File tree

5 files changed

+29
-3
lines changed

5 files changed

+29
-3
lines changed

src/blocks.c

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#include <stdlib.h>
99
#include <assert.h>
1010
#include <stdio.h>
11+
#include <limits.h>
1112

1213
#include "cmark_ctype.h"
1314
#include "syntax_extension.h"
@@ -639,6 +640,14 @@ static cmark_node *finalize_document(cmark_parser *parser) {
639640
}
640641

641642
finalize(parser, parser->root);
643+
644+
// Limit total size of extra content created from reference links to
645+
// document size to avoid superlinear growth. Always allow 100KB.
646+
if (parser->total_size > 100000)
647+
parser->refmap->max_ref_size = parser->total_size;
648+
else
649+
parser->refmap->max_ref_size = 100000;
650+
642651
process_inlines(parser, parser->refmap, parser->options);
643652
if (parser->options & CMARK_OPT_FOOTNOTES)
644653
process_footnotes(parser);
@@ -698,6 +707,11 @@ static void S_parser_feed(cmark_parser *parser, const unsigned char *buffer,
698707
const unsigned char *end = buffer + len;
699708
static const uint8_t repl[] = {239, 191, 189};
700709

710+
if (len > UINT_MAX - parser->total_size)
711+
parser->total_size = UINT_MAX;
712+
else
713+
parser->total_size += len;
714+
701715
if (parser->last_buffer_ended_with_cr && *buffer == '\n') {
702716
// skip NL if last buffer ended with CR ; see #117
703717
buffer++;

src/map.c

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ static void sort_map(cmark_map *map) {
7373

7474
cmark_map_entry *cmark_map_lookup(cmark_map *map, cmark_chunk *label) {
7575
cmark_map_entry **ref = NULL;
76+
cmark_map_entry *r = NULL;
7677
unsigned char *norm;
7778

7879
if (label->len < 1 || label->len > MAX_LINK_LABEL_LENGTH)
@@ -91,10 +92,15 @@ cmark_map_entry *cmark_map_lookup(cmark_map *map, cmark_chunk *label) {
9192
ref = (cmark_map_entry **)bsearch(norm, map->sorted, map->size, sizeof(cmark_map_entry *), refsearch);
9293
map->mem->free(norm);
9394

94-
if (!ref)
95-
return NULL;
95+
if (ref != NULL) {
96+
r = ref[0];
97+
/* Check for expansion limit */
98+
if (r->size > map->max_ref_size - map->ref_size)
99+
return NULL;
100+
map->ref_size += r->size;
101+
}
96102

97-
return ref[0];
103+
return r;
98104
}
99105

100106
void cmark_map_free(cmark_map *map) {
@@ -118,5 +124,6 @@ cmark_map *cmark_map_new(cmark_mem *mem, cmark_map_free_f free) {
118124
cmark_map *map = (cmark_map *)mem->calloc(1, sizeof(cmark_map));
119125
map->mem = mem;
120126
map->free = free;
127+
map->max_ref_size = UINT_MAX;
121128
return map;
122129
}

src/map.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ struct cmark_map_entry {
1111
struct cmark_map_entry *next;
1212
unsigned char *label;
1313
unsigned int age;
14+
unsigned int size;
1415
};
1516

1617
typedef struct cmark_map_entry cmark_map_entry;
@@ -24,6 +25,8 @@ struct cmark_map {
2425
cmark_map_entry *refs;
2526
cmark_map_entry **sorted;
2627
unsigned int size;
28+
unsigned int ref_size;
29+
unsigned int max_ref_size;
2730
cmark_map_free_f free;
2831
};
2932

src/parser.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ struct cmark_parser {
4646
/* Options set by the user, see the Options section in cmark.h */
4747
int options;
4848
bool last_buffer_ended_with_cr;
49+
unsigned int total_size;
4950
cmark_llist *syntax_extensions;
5051
cmark_llist *inline_syntax_extensions;
5152
cmark_ispunct_func backslash_ispunct;

src/references.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ void cmark_reference_create(cmark_map *map, cmark_chunk *label,
3232
ref->title = cmark_clean_title(map->mem, title);
3333
ref->entry.age = map->size;
3434
ref->entry.next = map->refs;
35+
ref->entry.size = ref->url.len + ref->title.len;
3536

3637
map->refs = (cmark_map_entry *)ref;
3738
map->size++;

0 commit comments

Comments
 (0)