Skip to content

Commit 2cd664e

Browse files
Merge remote-tracking branch 'github/master' into QuietMisdreavus/sync-upstream
2 parents fce0dee + cf7577d commit 2cd664e

13 files changed

+424
-57
lines changed

.github/workflows/ci.yml

+83
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
name: CI tests
2+
3+
on: [push, workflow_dispatch]
4+
5+
jobs:
6+
linux:
7+
8+
runs-on: ubuntu-latest
9+
strategy:
10+
fail-fast: false
11+
matrix:
12+
cmake_opts:
13+
- '-DCMARK_SHARED=ON'
14+
- ''
15+
compiler:
16+
- c: 'clang'
17+
cpp: 'clang++'
18+
- c: 'gcc'
19+
cpp: 'g++'
20+
env:
21+
CMAKE_OPTIONS: ${{ matrix.cmake_opts }}
22+
CC: ${{ matrix.compiler.c }}
23+
CXX: ${{ matrix.compiler.cpp }}
24+
25+
steps:
26+
- uses: actions/checkout@v1
27+
- name: Install valgrind
28+
run: |
29+
sudo apt install -y valgrind
30+
- name: Build and test
31+
run: |
32+
make
33+
make test
34+
make leakcheck
35+
36+
macos:
37+
38+
runs-on: macOS-latest
39+
strategy:
40+
fail-fast: false
41+
matrix:
42+
cmake_opts:
43+
- '-DCMARK_SHARED=ON'
44+
- ''
45+
compiler:
46+
- c: 'clang'
47+
cpp: 'clang++'
48+
- c: 'gcc'
49+
cpp: 'g++'
50+
env:
51+
CMAKE_OPTIONS: ${{ matrix.cmake_opts }}
52+
CC: ${{ matrix.compiler.c }}
53+
CXX: ${{ matrix.compiler.cpp }}
54+
55+
steps:
56+
- uses: actions/checkout@v1
57+
- name: Build and test
58+
env:
59+
CMAKE_OPTIONS: -DCMARK_SHARED=OFF
60+
run: |
61+
make
62+
make test
63+
64+
windows:
65+
66+
runs-on: windows-latest
67+
strategy:
68+
fail-fast: false
69+
matrix:
70+
cmake_opts:
71+
- '-DCMARK_SHARED=ON'
72+
- ''
73+
env:
74+
CMAKE_OPTIONS: ${{ matrix.cmake_opts }}
75+
76+
steps:
77+
- uses: actions/checkout@v1
78+
- uses: ilammy/msvc-dev-cmd@v1
79+
- name: Build and test
80+
run: |
81+
chcp 65001
82+
nmake.exe /nologo /f Makefile.nmake test
83+
shell: cmd

CMakeLists.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ project(cmark-gfm)
44
set(PROJECT_VERSION_MAJOR 0)
55
set(PROJECT_VERSION_MINOR 29)
66
set(PROJECT_VERSION_PATCH 0)
7-
set(PROJECT_VERSION_GFM 0)
7+
set(PROJECT_VERSION_GFM 3)
88
set(PROJECT_VERSION ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}.gfm.${PROJECT_VERSION_GFM})
99

1010
include("FindAsan.cmake")

changelog.txt

+18
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,21 @@
1+
[0.29.0.gfm.3]
2+
* Fixed heap memory corruption vulnerabiliy via integer overflow per https://github.com/github/cmark-gfm/security/advisories/GHSA-mc3g-88wq-6f4x
3+
4+
[0.29.0.gfm.2]
5+
* Fixed issues with footnote rendering when used with the autolinker (#121),
6+
and when footnotes are adjacent (#139).
7+
* We now allow footnotes to be referenced from inside a footnote definition,
8+
we use the footnote label for the fnref href text when rendering html, and
9+
we insert multiple backrefs when a footnote has been referenced multiple
10+
times (#229, #230)
11+
* We added new data- attributes to footnote html rendering to make them
12+
easier to style (#234)
13+
14+
[0.29.0.gfm.1]
15+
16+
* Fixed denial of service bug in GFM's table extension
17+
per https://github.com/github/cmark-gfm/security/advisories/GHSA-7gc6-9qr5-hc85
18+
119
[0.29.0]
220

321
* Update spec to 0.29.

src/blocks.c

+13-2
Original file line numberDiff line numberDiff line change
@@ -494,7 +494,6 @@ static void process_footnotes(cmark_parser *parser) {
494494
while ((ev_type = cmark_iter_next(iter)) != CMARK_EVENT_DONE) {
495495
cur = cmark_iter_get_node(iter);
496496
if (ev_type == CMARK_EVENT_EXIT && cur->type == CMARK_NODE_FOOTNOTE_DEFINITION) {
497-
cmark_node_unlink(cur);
498497
cmark_footnote_create(map, cur);
499498
}
500499
}
@@ -511,6 +510,15 @@ static void process_footnotes(cmark_parser *parser) {
511510
if (!footnote->ix)
512511
footnote->ix = ++ix;
513512

513+
// store a reference to this footnote reference's footnote definition
514+
// this is used by renderers when generating label ids
515+
cur->parent_footnote_def = footnote->node;
516+
517+
// keep track of a) count of how many times this footnote def has been
518+
// referenced, and b) which reference index this footnote ref is at.
519+
// this is used by renderers when generating links and backreferences.
520+
cur->footnote.ref_ix = ++footnote->node->footnote.def_count;
521+
514522
char n[32];
515523
snprintf(n, sizeof(n), "%d", footnote->ix);
516524
cmark_chunk_free(parser->mem, &cur->as.literal);
@@ -541,13 +549,16 @@ static void process_footnotes(cmark_parser *parser) {
541549
qsort(map->sorted, map->size, sizeof(cmark_map_entry *), sort_footnote_by_ix);
542550
for (unsigned int i = 0; i < map->size; ++i) {
543551
cmark_footnote *footnote = (cmark_footnote *)map->sorted[i];
544-
if (!footnote->ix)
552+
if (!footnote->ix) {
553+
cmark_node_unlink(footnote->node);
545554
continue;
555+
}
546556
cmark_node_append_child(parser->root, footnote->node);
547557
footnote->node = NULL;
548558
}
549559
}
550560

561+
cmark_unlink_footnotes_map(map);
551562
cmark_map_free(map);
552563
}
553564

src/commonmark.c

+14-4
Original file line numberDiff line numberDiff line change
@@ -488,7 +488,13 @@ static int S_render_node(cmark_renderer *renderer, cmark_node *node,
488488
case CMARK_NODE_FOOTNOTE_REFERENCE:
489489
if (entering) {
490490
LIT("[^");
491-
OUT(cmark_chunk_to_cstr(renderer->mem, &node->as.literal), false, LITERAL);
491+
492+
char *footnote_label = renderer->mem->calloc(node->parent_footnote_def->as.literal.len + 1, sizeof(char));
493+
memmove(footnote_label, node->parent_footnote_def->as.literal.data, node->parent_footnote_def->as.literal.len);
494+
495+
OUT(footnote_label, false, LITERAL);
496+
renderer->mem->free(footnote_label);
497+
492498
LIT("]");
493499
}
494500
break;
@@ -497,9 +503,13 @@ static int S_render_node(cmark_renderer *renderer, cmark_node *node,
497503
if (entering) {
498504
renderer->footnote_ix += 1;
499505
LIT("[^");
500-
char n[32];
501-
snprintf(n, sizeof(n), "%d", renderer->footnote_ix);
502-
OUT(n, false, LITERAL);
506+
507+
char *footnote_label = renderer->mem->calloc(node->as.literal.len + 1, sizeof(char));
508+
memmove(footnote_label, node->as.literal.data, node->as.literal.len);
509+
510+
OUT(footnote_label, false, LITERAL);
511+
renderer->mem->free(footnote_label);
512+
503513
LIT("]:\n");
504514

505515
cmark_strbuf_puts(renderer->prefix, " ");

src/footnotes.c

+23
Original file line numberDiff line numberDiff line change
@@ -38,3 +38,26 @@ void cmark_footnote_create(cmark_map *map, cmark_node *node) {
3838
cmark_map *cmark_footnote_map_new(cmark_mem *mem) {
3939
return cmark_map_new(mem, footnote_free);
4040
}
41+
42+
// Before calling `cmark_map_free` on a map with `cmark_footnotes`, first
43+
// unlink all of the footnote nodes before freeing their memory.
44+
//
45+
// Sometimes, two (unused) footnote nodes can end up referencing each other,
46+
// which as they get freed up by calling `cmark_map_free` -> `footnote_free` ->
47+
// etc, can lead to a use-after-free error.
48+
//
49+
// Better to `unlink` every footnote node first, setting their next, prev, and
50+
// parent pointers to NULL, and only then walk thru & free them up.
51+
void cmark_unlink_footnotes_map(cmark_map *map) {
52+
cmark_map_entry *ref;
53+
cmark_map_entry *next;
54+
55+
ref = map->refs;
56+
while(ref) {
57+
next = ref->next;
58+
if (((cmark_footnote *)ref)->node) {
59+
cmark_node_unlink(((cmark_footnote *)ref)->node);
60+
}
61+
ref = next;
62+
}
63+
}

src/html.c

+40-19
Original file line numberDiff line numberDiff line change
@@ -59,16 +59,30 @@ static void filter_html_block(cmark_html_renderer *renderer, uint8_t *data, size
5959
cmark_strbuf_put(html, data, (bufsize_t)len);
6060
}
6161

62-
static bool S_put_footnote_backref(cmark_html_renderer *renderer, cmark_strbuf *html) {
62+
static bool S_put_footnote_backref(cmark_html_renderer *renderer, cmark_strbuf *html, cmark_node *node) {
6363
if (renderer->written_footnote_ix >= renderer->footnote_ix)
6464
return false;
6565
renderer->written_footnote_ix = renderer->footnote_ix;
6666

67-
cmark_strbuf_puts(html, "<a href=\"#fnref");
68-
char n[32];
69-
snprintf(n, sizeof(n), "%d", renderer->footnote_ix);
70-
cmark_strbuf_puts(html, n);
71-
cmark_strbuf_puts(html, "\" class=\"footnote-backref\">↩</a>");
67+
cmark_strbuf_puts(html, "<a href=\"#fnref-");
68+
houdini_escape_href(html, node->as.literal.data, node->as.literal.len);
69+
cmark_strbuf_puts(html, "\" class=\"footnote-backref\" data-footnote-backref aria-label=\"Back to content\">↩</a>");
70+
71+
if (node->footnote.def_count > 1)
72+
{
73+
for(int i = 2; i <= node->footnote.def_count; i++) {
74+
char n[32];
75+
snprintf(n, sizeof(n), "%d", i);
76+
77+
cmark_strbuf_puts(html, " <a href=\"#fnref-");
78+
houdini_escape_href(html, node->as.literal.data, node->as.literal.len);
79+
cmark_strbuf_puts(html, "-");
80+
cmark_strbuf_puts(html, n);
81+
cmark_strbuf_puts(html, "\" class=\"footnote-backref\" data-footnote-backref aria-label=\"Back to content\">↩<sup class=\"footnote-ref\">");
82+
cmark_strbuf_puts(html, n);
83+
cmark_strbuf_puts(html, "</sup></a>");
84+
}
85+
}
7286

7387
return true;
7488
}
@@ -273,7 +287,7 @@ static int S_render_node(cmark_html_renderer *renderer, cmark_node *node,
273287
} else {
274288
if (parent->type == CMARK_NODE_FOOTNOTE_DEFINITION && node->next == NULL) {
275289
cmark_strbuf_putc(html, ' ');
276-
S_put_footnote_backref(renderer, html);
290+
S_put_footnote_backref(renderer, html, parent);
277291
}
278292
cmark_strbuf_puts(html, "</p>\n");
279293
}
@@ -405,16 +419,15 @@ static int S_render_node(cmark_html_renderer *renderer, cmark_node *node,
405419
case CMARK_NODE_FOOTNOTE_DEFINITION:
406420
if (entering) {
407421
if (renderer->footnote_ix == 0) {
408-
cmark_strbuf_puts(html, "<section class=\"footnotes\">\n<ol>\n");
422+
cmark_strbuf_puts(html, "<section class=\"footnotes\" data-footnotes>\n<ol>\n");
409423
}
410424
++renderer->footnote_ix;
411-
cmark_strbuf_puts(html, "<li id=\"fn");
412-
char n[32];
413-
snprintf(n, sizeof(n), "%d", renderer->footnote_ix);
414-
cmark_strbuf_puts(html, n);
425+
426+
cmark_strbuf_puts(html, "<li id=\"fn-");
427+
houdini_escape_href(html, node->as.literal.data, node->as.literal.len);
415428
cmark_strbuf_puts(html, "\">\n");
416429
} else {
417-
if (S_put_footnote_backref(renderer, html)) {
430+
if (S_put_footnote_backref(renderer, html, node)) {
418431
cmark_strbuf_putc(html, '\n');
419432
}
420433
cmark_strbuf_puts(html, "</li>\n");
@@ -423,12 +436,20 @@ static int S_render_node(cmark_html_renderer *renderer, cmark_node *node,
423436

424437
case CMARK_NODE_FOOTNOTE_REFERENCE:
425438
if (entering) {
426-
cmark_strbuf_puts(html, "<sup class=\"footnote-ref\"><a href=\"#fn");
427-
cmark_strbuf_put(html, node->as.literal.data, node->as.literal.len);
428-
cmark_strbuf_puts(html, "\" id=\"fnref");
429-
cmark_strbuf_put(html, node->as.literal.data, node->as.literal.len);
430-
cmark_strbuf_puts(html, "\">");
431-
cmark_strbuf_put(html, node->as.literal.data, node->as.literal.len);
439+
cmark_strbuf_puts(html, "<sup class=\"footnote-ref\"><a href=\"#fn-");
440+
houdini_escape_href(html, node->parent_footnote_def->as.literal.data, node->parent_footnote_def->as.literal.len);
441+
cmark_strbuf_puts(html, "\" id=\"fnref-");
442+
houdini_escape_href(html, node->parent_footnote_def->as.literal.data, node->parent_footnote_def->as.literal.len);
443+
444+
if (node->footnote.ref_ix > 1) {
445+
char n[32];
446+
snprintf(n, sizeof(n), "%d", node->footnote.ref_ix);
447+
cmark_strbuf_puts(html, "-");
448+
cmark_strbuf_puts(html, n);
449+
}
450+
451+
cmark_strbuf_puts(html, "\" data-footnote-ref>");
452+
houdini_escape_href(html, node->as.literal.data, node->as.literal.len);
432453
cmark_strbuf_puts(html, "</a></sup>");
433454
}
434455
break;

src/include/footnotes.h

+2
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ typedef struct cmark_footnote cmark_footnote;
1818
void cmark_footnote_create(cmark_map *map, cmark_node *node);
1919
cmark_map *cmark_footnote_map_new(cmark_mem *mem);
2020

21+
void cmark_unlink_footnotes_map(cmark_map *map);
22+
2123
#ifdef __cplusplus
2224
}
2325
#endif

src/include/node.h

+7
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,13 @@ struct cmark_node {
8181

8282
cmark_syntax_extension *extension;
8383

84+
union {
85+
int ref_ix;
86+
int def_count;
87+
} footnote;
88+
89+
cmark_node *parent_footnote_def;
90+
8491
union {
8592
cmark_chunk literal;
8693
cmark_list list;

0 commit comments

Comments
 (0)