Skip to content

Commit

Permalink
Avoid quadratic output growth with reference links
Browse files Browse the repository at this point in the history
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 github#354.
  • Loading branch information
nwellnhof authored and kevinbackhouse committed Nov 7, 2022
1 parent 6a6e335 commit b4f0106
Show file tree
Hide file tree
Showing 5 changed files with 29 additions and 3 deletions.
14 changes: 14 additions & 0 deletions src/blocks.c
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include <stdlib.h>
#include <assert.h>
#include <stdio.h>
#include <limits.h>

#include "cmark_ctype.h"
#include "syntax_extension.h"
Expand Down Expand Up @@ -639,6 +640,14 @@ static cmark_node *finalize_document(cmark_parser *parser) {
}

finalize(parser, parser->root);

// Limit total size of extra content created from reference links to
// document size to avoid superlinear growth. Always allow 100KB.
if (parser->total_size > 100000)
parser->refmap->max_ref_size = parser->total_size;
else
parser->refmap->max_ref_size = 100000;

process_inlines(parser, parser->refmap, parser->options);
if (parser->options & CMARK_OPT_FOOTNOTES)
process_footnotes(parser);
Expand Down Expand Up @@ -698,6 +707,11 @@ static void S_parser_feed(cmark_parser *parser, const unsigned char *buffer,
const unsigned char *end = buffer + len;
static const uint8_t repl[] = {239, 191, 189};

if (len > UINT_MAX - parser->total_size)
parser->total_size = UINT_MAX;
else
parser->total_size += len;

if (parser->last_buffer_ended_with_cr && *buffer == '\n') {
// skip NL if last buffer ended with CR ; see #117
buffer++;
Expand Down
13 changes: 10 additions & 3 deletions src/map.c
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ static void sort_map(cmark_map *map) {

cmark_map_entry *cmark_map_lookup(cmark_map *map, cmark_chunk *label) {
cmark_map_entry **ref = NULL;
cmark_map_entry *r = NULL;
unsigned char *norm;

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

if (!ref)
return NULL;
if (ref != NULL) {
r = ref[0];
/* Check for expansion limit */
if (r->size > map->max_ref_size - map->ref_size)
return NULL;
map->ref_size += r->size;
}

return ref[0];
return r;
}

void cmark_map_free(cmark_map *map) {
Expand All @@ -118,5 +124,6 @@ cmark_map *cmark_map_new(cmark_mem *mem, cmark_map_free_f free) {
cmark_map *map = (cmark_map *)mem->calloc(1, sizeof(cmark_map));
map->mem = mem;
map->free = free;
map->max_ref_size = UINT_MAX;
return map;
}
3 changes: 3 additions & 0 deletions src/map.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ struct cmark_map_entry {
struct cmark_map_entry *next;
unsigned char *label;
unsigned int age;
unsigned int size;
};

typedef struct cmark_map_entry cmark_map_entry;
Expand All @@ -24,6 +25,8 @@ struct cmark_map {
cmark_map_entry *refs;
cmark_map_entry **sorted;
unsigned int size;
unsigned int ref_size;
unsigned int max_ref_size;
cmark_map_free_f free;
};

Expand Down
1 change: 1 addition & 0 deletions src/parser.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ struct cmark_parser {
/* Options set by the user, see the Options section in cmark.h */
int options;
bool last_buffer_ended_with_cr;
unsigned int total_size;
cmark_llist *syntax_extensions;
cmark_llist *inline_syntax_extensions;
cmark_ispunct_func backslash_ispunct;
Expand Down
1 change: 1 addition & 0 deletions src/references.c
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ void cmark_reference_create(cmark_map *map, cmark_chunk *label,
ref->title = cmark_clean_title(map->mem, title);
ref->entry.age = map->size;
ref->entry.next = map->refs;
ref->entry.size = ref->url.len + ref->title.len;

map->refs = (cmark_map_entry *)ref;
map->size++;
Expand Down

0 comments on commit b4f0106

Please sign in to comment.