Skip to content

Commit

Permalink
Merge pull request from GHSA-w4qg-3vf7-m9x5
Browse files Browse the repository at this point in the history
Fix GHSL-2023-117, GHSL-2023-118, GHSL-2023-119
  • Loading branch information
anticomputer committed Jul 12, 2023
2 parents a4cf959 + 580f021 commit 38d1cfe
Show file tree
Hide file tree
Showing 3 changed files with 74 additions and 27 deletions.
97 changes: 71 additions & 26 deletions extensions/table.c
Expand Up @@ -11,6 +11,9 @@
#include "table.h"
#include "cmark-gfm-core-extensions.h"

// Limit to prevent a malicious input from causing a denial of service.
#define MAX_AUTOCOMPLETED_CELLS 0x80000

// Custom node flag, initialized in `create_table_extension`.
static cmark_node_internal_flags CMARK_NODE__TABLE_VISITED;

Expand All @@ -31,6 +34,8 @@ typedef struct {
typedef struct {
uint16_t n_columns;
uint8_t *alignments;
int n_rows;
int n_nonempty_cells;
} node_table;

typedef struct {
Expand Down Expand Up @@ -83,6 +88,33 @@ static int set_n_table_columns(cmark_node *node, uint16_t n_columns) {
return 1;
}

// Increment the number of rows in the table. Also update n_nonempty_cells,
// which keeps track of the number of cells which were parsed from the
// input file. (If one of the rows is too short, then the trailing cells
// are autocompleted. Autocompleted cells are not counted in n_nonempty_cells.)
// The purpose of this is to prevent a malicious input from generating a very
// large number of autocompleted cells, which could cause a denial of service
// vulnerability.
static int incr_table_row_count(cmark_node *node, int i) {
if (!node || node->type != CMARK_NODE_TABLE) {
return 0;
}

((node_table *)node->as.opaque)->n_rows++;
((node_table *)node->as.opaque)->n_nonempty_cells += i;
return 1;
}

// Calculate the number of autocompleted cells.
static int get_n_autocompleted_cells(cmark_node *node) {
if (!node || node->type != CMARK_NODE_TABLE) {
return 0;
}

const node_table *nt = (node_table *)node->as.opaque;
return (nt->n_columns * nt->n_rows) - nt->n_nonempty_cells;
}

static uint8_t *get_table_alignments(cmark_node *node) {
if (!node || node->type != CMARK_NODE_TABLE)
return 0;
Expand All @@ -98,6 +130,23 @@ static int set_table_alignments(cmark_node *node, uint8_t *alignments) {
return 1;
}

static uint8_t get_cell_alignment(cmark_node *node) {
if (!node || node->type != CMARK_NODE_TABLE_CELL)
return 0;

const uint8_t *alignments = get_table_alignments(node->parent->parent);
int i = node->as.cell_index;
return alignments[i];
}

static int set_cell_index(cmark_node *node, int i) {
if (!node || node->type != CMARK_NODE_TABLE_CELL)
return 0;

node->as.cell_index = i;
return 1;
}

static cmark_strbuf *unescape_pipes(cmark_mem *mem, unsigned char *string, bufsize_t len)
{
cmark_strbuf *res = (cmark_strbuf *)mem->calloc(1, sizeof(cmark_strbuf));
Expand Down Expand Up @@ -353,19 +402,20 @@ static cmark_node *try_opening_table_header(cmark_syntax_extension *self,
table_header->as.opaque = ntr = (node_table_row *)parser->mem->calloc(1, sizeof(node_table_row));
ntr->is_header = true;

{
for (i = 0; i < header_row->n_columns; ++i) {
node_cell *cell = &header_row->cells[i];
cmark_node *header_cell = cmark_parser_add_child(parser, table_header,
CMARK_NODE_TABLE_CELL, parent_container->start_column + cell->start_offset);
header_cell->start_line = header_cell->end_line = parent_container->start_line;
header_cell->internal_offset = cell->internal_offset;
header_cell->end_column = parent_container->start_column + cell->end_offset;
cmark_node_set_string_content(header_cell, (char *) cell->buf->ptr);
cmark_node_set_syntax_extension(header_cell, self);
}
for (i = 0; i < header_row->n_columns; ++i) {
node_cell *cell = &header_row->cells[i];
cmark_node *header_cell = cmark_parser_add_child(parser, table_header,
CMARK_NODE_TABLE_CELL, parent_container->start_column + cell->start_offset);
header_cell->start_line = header_cell->end_line = parent_container->start_line;
header_cell->internal_offset = cell->internal_offset;
header_cell->end_column = parent_container->start_column + cell->end_offset;
cmark_node_set_string_content(header_cell, (char *) cell->buf->ptr);
cmark_node_set_syntax_extension(header_cell, self);
set_cell_index(header_cell, i);
}

incr_table_row_count(parent_container, i);

cmark_parser_advance_offset(
parser, (char *)input,
(int)strlen((char *)input) - 1 - cmark_parser_get_offset(parser), false);
Expand All @@ -385,6 +435,10 @@ static cmark_node *try_opening_table_row(cmark_syntax_extension *self,
if (cmark_parser_is_blank(parser))
return NULL;

if (get_n_autocompleted_cells(parent_container) > MAX_AUTOCOMPLETED_CELLS) {
return NULL;
}

table_row_block =
cmark_parser_add_child(parser, parent_container, CMARK_NODE_TABLE_ROW,
parent_container->start_column);
Expand Down Expand Up @@ -412,12 +466,16 @@ static cmark_node *try_opening_table_row(cmark_syntax_extension *self,
node->end_column = parent_container->start_column + cell->end_offset;
cmark_node_set_string_content(node, (char *) cell->buf->ptr);
cmark_node_set_syntax_extension(node, self);
set_cell_index(node, i);
}

incr_table_row_count(parent_container, i);

for (; i < table_columns; ++i) {
cmark_node *node = cmark_parser_add_child(
parser, table_row_block, CMARK_NODE_TABLE_CELL, 0);
cmark_node_set_syntax_extension(node, self);
set_cell_index(node, i);
}
}

Expand Down Expand Up @@ -602,13 +660,7 @@ static const char *xml_attr(cmark_syntax_extension *extension,
cmark_node *node) {
if (node->type == CMARK_NODE_TABLE_CELL) {
if (cmark_gfm_extensions_get_table_row_is_header(node->parent)) {
uint8_t *alignments = get_table_alignments(node->parent->parent);
int i = 0;
cmark_node *n;
for (n = node->parent->first_child; n; n = n->next, ++i)
if (n == node)
break;
switch (alignments[i]) {
switch (get_cell_alignment(node)) {
case 'l': return " align=\"left\"";
case 'c': return " align=\"center\"";
case 'r': return " align=\"right\"";
Expand Down Expand Up @@ -696,7 +748,6 @@ static void html_render(cmark_syntax_extension *extension,
cmark_event_type ev_type, int options) {
bool entering = (ev_type == CMARK_EVENT_ENTER);
cmark_strbuf *html = renderer->html;
cmark_node *n;

// XXX: we just monopolise renderer->opaque.
struct html_table_state *table_state =
Expand Down Expand Up @@ -745,7 +796,6 @@ static void html_render(cmark_syntax_extension *extension,
}
}
} else if (node->type == CMARK_NODE_TABLE_CELL) {
uint8_t *alignments = get_table_alignments(node->parent->parent);
if (entering) {
cmark_html_render_cr(html);
if (table_state->in_table_header) {
Expand All @@ -754,12 +804,7 @@ static void html_render(cmark_syntax_extension *extension,
cmark_strbuf_puts(html, "<td");
}

int i = 0;
for (n = node->parent->first_child; n; n = n->next, ++i)
if (n == node)
break;

switch (alignments[i]) {
switch (get_cell_alignment(node)) {
case 'l': html_table_add_align(html, "left", options); break;
case 'c': html_table_add_align(html, "center", options); break;
case 'r': html_table_add_align(html, "right", options); break;
Expand Down
3 changes: 2 additions & 1 deletion src/blocks.c
Expand Up @@ -1217,7 +1217,8 @@ static void open_new_blocks(cmark_parser *parser, cmark_node **container,
parser->first_nonspace + 1);
S_advance_offset(parser, input, input->len - 1 - parser->offset, false);
} else if (!indented &&
parser->options & CMARK_OPT_FOOTNOTES &&
(parser->options & CMARK_OPT_FOOTNOTES) &&
depth < MAX_LIST_DEPTH &&
(matched = scan_footnote_definition(input, parser->first_nonspace))) {
cmark_chunk c = cmark_chunk_dup(input, parser->first_nonspace + 2, matched - 2);
cmark_chunk_to_cstr(parser->mem, &c);
Expand Down
1 change: 1 addition & 0 deletions src/node.h
Expand Up @@ -105,6 +105,7 @@ struct cmark_node {
cmark_link link;
cmark_custom custom;
int html_block_type;
int cell_index; // For keeping track of TABLE_CELL table alignments
void *opaque;
} as;
};
Expand Down

0 comments on commit 38d1cfe

Please sign in to comment.