Skip to content

Commit 62be38f

Browse files
authored
Merge pull request from GHSA-29g3-96g3-jg6c
Multiple vulnerability fixes and improvements
2 parents d84de00 + fae4863 commit 62be38f

File tree

17 files changed

+285
-134
lines changed

17 files changed

+285
-134
lines changed

api_test/main.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1133,6 +1133,7 @@ int main() {
11331133
int retval;
11341134
test_batch_runner *runner = test_batch_runner_new();
11351135

1136+
cmark_init_standard_node_flags();
11361137
version(runner);
11371138
constructor(runner);
11381139
accessors(runner);

extensions/autolink.c

Lines changed: 62 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -35,79 +35,78 @@ static int sd_autolink_issafe(const uint8_t *link, size_t link_len) {
3535
}
3636

3737
static size_t autolink_delim(uint8_t *data, size_t link_end) {
38-
uint8_t cclose, copen;
3938
size_t i;
39+
size_t closing = 0;
40+
size_t opening = 0;
4041

41-
for (i = 0; i < link_end; ++i)
42-
if (data[i] == '<') {
42+
for (i = 0; i < link_end; ++i) {
43+
const uint8_t c = data[i];
44+
if (c == '<') {
4345
link_end = i;
4446
break;
47+
} else if (c == '(') {
48+
opening++;
49+
} else if (c == ')') {
50+
closing++;
4551
}
52+
}
4653

4754
while (link_end > 0) {
48-
cclose = data[link_end - 1];
49-
50-
switch (cclose) {
55+
switch (data[link_end - 1]) {
5156
case ')':
52-
copen = '(';
53-
break;
54-
default:
55-
copen = 0;
56-
}
57-
58-
if (strchr("?!.,:*_~'\"", data[link_end - 1]) != NULL)
59-
link_end--;
60-
61-
else if (data[link_end - 1] == ';') {
62-
size_t new_end = link_end - 2;
63-
64-
while (new_end > 0 && cmark_isalpha(data[new_end]))
65-
new_end--;
66-
67-
if (new_end < link_end - 2 && data[new_end] == '&')
68-
link_end = new_end;
69-
else
70-
link_end--;
71-
} else if (copen != 0) {
72-
size_t closing = 0;
73-
size_t opening = 0;
74-
i = 0;
75-
7657
/* Allow any number of matching brackets (as recognised in copen/cclose)
7758
* at the end of the URL. If there is a greater number of closing
7859
* brackets than opening ones, we remove one character from the end of
7960
* the link.
8061
*
8162
* Examples (input text => output linked portion):
8263
*
83-
* http://www.pokemon.com/Pikachu_(Electric)
84-
* => http://www.pokemon.com/Pikachu_(Electric)
64+
* http://www.pokemon.com/Pikachu_(Electric)
65+
* => http://www.pokemon.com/Pikachu_(Electric)
8566
*
86-
* http://www.pokemon.com/Pikachu_((Electric)
87-
* => http://www.pokemon.com/Pikachu_((Electric)
67+
* http://www.pokemon.com/Pikachu_((Electric)
68+
* => http://www.pokemon.com/Pikachu_((Electric)
8869
*
89-
* http://www.pokemon.com/Pikachu_(Electric))
90-
* => http://www.pokemon.com/Pikachu_(Electric)
70+
* http://www.pokemon.com/Pikachu_(Electric))
71+
* => http://www.pokemon.com/Pikachu_(Electric)
9172
*
92-
* http://www.pokemon.com/Pikachu_((Electric))
93-
* => http://www.pokemon.com/Pikachu_((Electric))
73+
* http://www.pokemon.com/Pikachu_((Electric))
74+
* => http://www.pokemon.com/Pikachu_((Electric))
9475
*/
95-
96-
while (i < link_end) {
97-
if (data[i] == copen)
98-
opening++;
99-
else if (data[i] == cclose)
100-
closing++;
101-
102-
i++;
76+
if (closing <= opening) {
77+
return link_end;
10378
}
79+
closing--;
80+
link_end--;
81+
break;
82+
case '?':
83+
case '!':
84+
case '.':
85+
case ',':
86+
case ':':
87+
case '*':
88+
case '_':
89+
case '~':
90+
case '\'':
91+
case '"':
92+
link_end--;
93+
break;
94+
case ';': {
95+
size_t new_end = link_end - 2;
10496

105-
if (closing <= opening)
106-
break;
97+
while (new_end > 0 && cmark_isalpha(data[new_end]))
98+
new_end--;
10799

108-
link_end--;
109-
} else
100+
if (new_end < link_end - 2 && data[new_end] == '&')
101+
link_end = new_end;
102+
else
103+
link_end--;
110104
break;
105+
}
106+
107+
default:
108+
return link_end;
109+
}
111110
}
112111

113112
return link_end;
@@ -127,8 +126,17 @@ static size_t check_domain(uint8_t *data, size_t size, int allow_short) {
127126
break;
128127
}
129128

130-
if (uscore1 > 0 || uscore2 > 0)
131-
return 0;
129+
if (uscore1 > 0 || uscore2 > 0) {
130+
/* If the url is very long then accept it despite the underscores,
131+
* to avoid quadratic behavior causing a denial of service. See:
132+
* https://github.com/github/cmark-gfm/security/advisories/GHSA-29g3-96g3-jg6c
133+
* Reasonable urls are unlikely to have more than 10 segments, so
134+
* this extra condition shouldn't have any impact on normal usage.
135+
*/
136+
if (np <= 10) {
137+
return 0;
138+
}
139+
}
132140

133141
if (allow_short) {
134142
/* We don't need a valid domain in the strict sense (with
@@ -165,7 +173,7 @@ static cmark_node *www_match(cmark_parser *parser, cmark_node *parent,
165173
if (link_end == 0)
166174
return NULL;
167175

168-
while (link_end < size && !cmark_isspace(data[link_end]))
176+
while (link_end < size && !cmark_isspace(data[link_end]) && data[link_end] != '<')
169177
link_end++;
170178

171179
link_end = autolink_delim(data, link_end);
@@ -225,7 +233,7 @@ static cmark_node *url_match(cmark_parser *parser, cmark_node *parent,
225233
return 0;
226234

227235
link_end += domain_len;
228-
while (link_end < size && !cmark_isspace(data[link_end]))
236+
while (link_end < size && !cmark_isspace(data[link_end]) && data[link_end] != '<')
229237
link_end++;
230238

231239
link_end = autolink_delim(data, link_end);

extensions/strikethrough.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ static delimiter *insert(cmark_syntax_extension *self, cmark_parser *parser,
6767
strikethrough->end_column = closer->inl_text->start_column + closer->inl_text->as.literal.len - 1;
6868
cmark_node_free(closer->inl_text);
6969

70+
done:
7071
delim = closer;
7172
while (delim != NULL && delim != opener) {
7273
tmp_delim = delim->previous;
@@ -76,7 +77,6 @@ static delimiter *insert(cmark_syntax_extension *self, cmark_parser *parser,
7677

7778
cmark_inline_parser_remove_delimiter(inline_parser, opener);
7879

79-
done:
8080
return res;
8181
}
8282

extensions/table.c

Lines changed: 59 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,21 @@
1111
#include "table.h"
1212
#include "cmark-gfm-core-extensions.h"
1313

14+
// Custom node flag, initialized in `create_table_extension`.
15+
static cmark_node__internal_flags CMARK_NODE__TABLE_VISITED;
16+
1417
cmark_node_type CMARK_NODE_TABLE, CMARK_NODE_TABLE_ROW,
1518
CMARK_NODE_TABLE_CELL;
1619

20+
typedef struct {
21+
cmark_strbuf *buf;
22+
int start_offset, end_offset, internal_offset;
23+
} node_cell;
24+
1725
typedef struct {
1826
uint16_t n_columns;
1927
int paragraph_offset;
20-
cmark_llist *cells;
28+
node_cell *cells;
2129
} table_row;
2230

2331
typedef struct {
@@ -29,24 +37,24 @@ typedef struct {
2937
bool is_header;
3038
} node_table_row;
3139

32-
typedef struct {
33-
cmark_strbuf *buf;
34-
int start_offset, end_offset, internal_offset;
35-
} node_cell;
36-
37-
static void free_table_cell(cmark_mem *mem, void *data) {
38-
node_cell *cell = (node_cell *)data;
40+
static void free_table_cell(cmark_mem *mem, node_cell *cell) {
3941
cmark_strbuf_free((cmark_strbuf *)cell->buf);
4042
mem->free(cell->buf);
41-
mem->free(cell);
43+
}
44+
45+
static void free_row_cells(cmark_mem *mem, table_row *row) {
46+
while (row->n_columns > 0) {
47+
free_table_cell(mem, &row->cells[--row->n_columns]);
48+
}
49+
mem->free(row->cells);
50+
row->cells = NULL;
4251
}
4352

4453
static void free_table_row(cmark_mem *mem, table_row *row) {
4554
if (!row)
4655
return;
4756

48-
cmark_llist_free_full(mem, row->cells, (cmark_free_func)free_table_cell);
49-
57+
free_row_cells(mem, row);
5058
mem->free(row);
5159
}
5260

@@ -111,6 +119,24 @@ static cmark_strbuf *unescape_pipes(cmark_mem *mem, unsigned char *string, bufsi
111119
return res;
112120
}
113121

122+
// Adds a new cell to the end of the row. A pointer to the new cell is returned
123+
// for the caller to initialize.
124+
static node_cell* append_row_cell(cmark_mem *mem, table_row *row) {
125+
const uint32_t n_columns = row->n_columns + 1;
126+
// realloc when n_columns is a power of 2
127+
if ((n_columns & (n_columns-1)) == 0) {
128+
// make sure we never wrap row->n_columns
129+
// offset will != len and our exit will clean up as intended
130+
if (n_columns > UINT16_MAX) {
131+
return NULL;
132+
}
133+
// Use realloc to double the size of the buffer.
134+
row->cells = (node_cell *)mem->realloc(row->cells, (2 * n_columns - 1) * sizeof(node_cell));
135+
}
136+
row->n_columns = n_columns;
137+
return &row->cells[n_columns-1];
138+
}
139+
114140
static table_row *row_from_string(cmark_syntax_extension *self,
115141
cmark_parser *parser, unsigned char *string,
116142
int len) {
@@ -152,24 +178,22 @@ static table_row *row_from_string(cmark_syntax_extension *self,
152178
cell_matched);
153179
cmark_strbuf_trim(cell_buf);
154180

155-
node_cell *cell = (node_cell *)parser->mem->calloc(1, sizeof(*cell));
181+
node_cell *cell = append_row_cell(parser->mem, row);
182+
if (!cell) {
183+
int_overflow_abort = 1;
184+
cmark_strbuf_free(cell_buf);
185+
parser->mem->free(cell_buf);
186+
break;
187+
}
156188
cell->buf = cell_buf;
157189
cell->start_offset = offset;
158190
cell->end_offset = offset + cell_matched - 1;
191+
cell->internal_offset = 0;
159192

160-
while (cell->start_offset > 0 && string[cell->start_offset - 1] != '|') {
193+
while (cell->start_offset > row->paragraph_offset && string[cell->start_offset - 1] != '|') {
161194
--cell->start_offset;
162195
++cell->internal_offset;
163196
}
164-
165-
// make sure we never wrap row->n_columns
166-
// offset will != len and our exit will clean up as intended
167-
if (row->n_columns == UINT16_MAX) {
168-
int_overflow_abort = 1;
169-
break;
170-
}
171-
row->n_columns += 1;
172-
row->cells = cmark_llist_append(parser->mem, row->cells, cell);
173197
}
174198

175199
offset += cell_matched + pipe_matched;
@@ -187,9 +211,7 @@ static table_row *row_from_string(cmark_syntax_extension *self,
187211
if (row_end_offset && offset != len) {
188212
row->paragraph_offset = offset;
189213

190-
cmark_llist_free_full(parser->mem, row->cells, (cmark_free_func)free_table_cell);
191-
row->cells = NULL;
192-
row->n_columns = 0;
214+
free_row_cells(parser->mem, row);
193215

194216
// Scan past the (optional) leading pipe.
195217
offset += scan_table_cell_end(string, len, offset);
@@ -240,6 +262,10 @@ static cmark_node *try_opening_table_header(cmark_syntax_extension *self,
240262
const char *parent_string;
241263
uint16_t i;
242264

265+
if (parent_container->flags & CMARK_NODE__TABLE_VISITED) {
266+
return parent_container;
267+
}
268+
243269
if (!scan_table_start(input, len, cmark_parser_get_first_nonspace(parser))) {
244270
return parent_container;
245271
}
@@ -267,6 +293,7 @@ static cmark_node *try_opening_table_header(cmark_syntax_extension *self,
267293
free_table_row(parser->mem, marker_row);
268294
free_table_row(parser->mem, header_row);
269295
cmark_arena_pop();
296+
parent_container->flags |= CMARK_NODE__TABLE_VISITED;
270297
return parent_container;
271298
}
272299

@@ -303,9 +330,8 @@ static cmark_node *try_opening_table_header(cmark_syntax_extension *self,
303330
// since we populate the alignments array based on marker_row->cells
304331
uint8_t *alignments =
305332
(uint8_t *)parser->mem->calloc(marker_row->n_columns, sizeof(uint8_t));
306-
cmark_llist *it = marker_row->cells;
307-
for (i = 0; it; it = it->next, ++i) {
308-
node_cell *node = (node_cell *)it->data;
333+
for (i = 0; i < marker_row->n_columns; ++i) {
334+
node_cell *node = &marker_row->cells[i];
309335
bool left = node->buf->ptr[0] == ':', right = node->buf->ptr[node->buf->size - 1] == ':';
310336

311337
if (left && right)
@@ -328,10 +354,8 @@ static cmark_node *try_opening_table_header(cmark_syntax_extension *self,
328354
ntr->is_header = true;
329355

330356
{
331-
cmark_llist *tmp;
332-
333-
for (tmp = header_row->cells; tmp; tmp = tmp->next) {
334-
node_cell *cell = (node_cell *) tmp->data;
357+
for (i = 0; i < header_row->n_columns; ++i) {
358+
node_cell *cell = &header_row->cells[i];
335359
cmark_node *header_cell = cmark_parser_add_child(parser, table_header,
336360
CMARK_NODE_TABLE_CELL, parent_container->start_column + cell->start_offset);
337361
header_cell->start_line = header_cell->end_line = parent_container->start_line;
@@ -378,11 +402,10 @@ static cmark_node *try_opening_table_row(cmark_syntax_extension *self,
378402
}
379403

380404
{
381-
cmark_llist *tmp;
382405
int i, table_columns = get_n_table_columns(parent_container);
383406

384-
for (tmp = row->cells, i = 0; tmp && i < table_columns; tmp = tmp->next, ++i) {
385-
node_cell *cell = (node_cell *) tmp->data;
407+
for (i = 0; i < row->n_columns && i < table_columns; ++i) {
408+
node_cell *cell = &row->cells[i];
386409
cmark_node *node = cmark_parser_add_child(parser, table_row_block,
387410
CMARK_NODE_TABLE_CELL, parent_container->start_column + cell->start_offset);
388411
node->internal_offset = cell->internal_offset;
@@ -785,6 +808,7 @@ static int escape(cmark_syntax_extension *self, cmark_node *node, int c) {
785808
cmark_syntax_extension *create_table_extension(void) {
786809
cmark_syntax_extension *self = cmark_syntax_extension_new("table");
787810

811+
cmark_register_node_flag(&CMARK_NODE__TABLE_VISITED);
788812
cmark_syntax_extension_set_match_block_func(self, matches);
789813
cmark_syntax_extension_set_open_block_func(self, try_opening_table_block);
790814
cmark_syntax_extension_set_get_type_string_func(self, get_type_string);

0 commit comments

Comments
 (0)