Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update to 0.29.0.gfm.13 #247

Merged
merged 3 commits into from
Jul 31, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# Changelog

## [v0.23.10] (2023-07-31)

- Update GFM release to [`0.29.0.gfm.12`](https://github.com/github/cmark-gfm/releases/tag/0.29.0.gfm.12) and [`0.29.0.gfm.13`](https://github.com/github/cmark-gfm/releases/tag/0.29.0.gfm.13), thereby [fixing a polynomial time complexity security vulnerability](https://github.com/github/cmark-gfm/security/advisories/GHSA-w4qg-3vf7-m9x5).
- Of note to users of this library, GFM releases `0.29.0.gfm.12` and `0.29.0.gfm.13` also:
- Normalized marker row vs. delimiter row nomenclature ([#273](https://github.com/github/cmark-gfm/pull/273))
- Exposed CMARK_NODE_FOOTNOTE_DEFINITION literal value ([#336](https://github.com/github/cmark-gfm/pull/336))


## [v0.23.4](https://github.com/gjtorikian/commonmarker/tree/v0.23.4) (2022-03-03)

[Full Changelog](https://github.com/gjtorikian/commonmarker/compare/v0.23.2...v0.23.4)
Expand Down
2 changes: 1 addition & 1 deletion ext/commonmarker/autolink.c
Original file line number Diff line number Diff line change
Expand Up @@ -296,7 +296,7 @@ static cmark_node *match(cmark_syntax_extension *ext, cmark_parser *parser,
// inline was finished in inlines.c.
}

static bool validate_protocol(char protocol[], uint8_t *data, size_t rewind, size_t max_rewind) {
static bool validate_protocol(const char protocol[], uint8_t *data, size_t rewind, size_t max_rewind) {
size_t len = strlen(protocol);

if (len > (max_rewind - rewind)) {
Expand Down
6 changes: 4 additions & 2 deletions ext/commonmarker/blocks.c
Original file line number Diff line number Diff line change
Expand Up @@ -1217,15 +1217,17 @@ 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);

while (c.data[c.len - 1] != ']')
--c.len;
--c.len;

cmark_chunk_to_cstr(parser->mem, &c);

S_advance_offset(parser, input, parser->first_nonspace + matched - parser->offset, false);
*container = add_child(parser, *container, CMARK_NODE_FOOTNOTE_DEFINITION, parser->first_nonspace + matched + 1);
(*container)->as.literal = c;
Expand Down
2 changes: 1 addition & 1 deletion ext/commonmarker/commonmark.c
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ static int S_render_node(cmark_renderer *renderer, cmark_node *node,
char fencechar[2] = {'\0', '\0'};
size_t info_len, code_len;
char listmarker[LISTMARKER_SIZE];
char *emph_delim;
const char *emph_delim;
bool first_in_list_item;
bufsize_t marker_width;
bool allow_wrap = renderer->width > 0 && !(CMARK_OPT_NOBREAKS & options) &&
Expand Down
1 change: 1 addition & 0 deletions ext/commonmarker/node.c
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,7 @@ const char *cmark_node_get_literal(cmark_node *node) {
case CMARK_NODE_HTML_INLINE:
case CMARK_NODE_CODE:
case CMARK_NODE_FOOTNOTE_REFERENCE:
case CMARK_NODE_FOOTNOTE_DEFINITION:
return cmark_chunk_to_cstr(NODE_MEM(node), &node->as.literal);

case CMARK_NODE_CODE_BLOCK:
Expand Down
1 change: 1 addition & 0 deletions ext/commonmarker/node.h
Original file line number Diff line number Diff line change
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
137 changes: 91 additions & 46 deletions ext/commonmarker/table.c
Original file line number Diff line number Diff line change
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 @@ -257,7 +306,7 @@ static cmark_node *try_opening_table_header(cmark_syntax_extension *self,
unsigned char *input, int len) {
cmark_node *table_header;
table_row *header_row = NULL;
table_row *marker_row = NULL;
table_row *delimiter_row = NULL;
node_table_row *ntr;
const char *parent_string;
uint16_t i;
Expand All @@ -270,16 +319,16 @@ static cmark_node *try_opening_table_header(cmark_syntax_extension *self,
return parent_container;
}

// Since scan_table_start was successful, we must have a marker row.
marker_row = row_from_string(self, parser,
input + cmark_parser_get_first_nonspace(parser),
len - cmark_parser_get_first_nonspace(parser));
// Since scan_table_start was successful, we must have a delimiter row.
delimiter_row = row_from_string(
self, parser, input + cmark_parser_get_first_nonspace(parser),
len - cmark_parser_get_first_nonspace(parser));
// assert may be optimized out, don't rely on it for security boundaries
if (!marker_row) {
if (!delimiter_row) {
return parent_container;
}
assert(marker_row);

assert(delimiter_row);

cmark_arena_push();

Expand All @@ -289,31 +338,31 @@ static cmark_node *try_opening_table_header(cmark_syntax_extension *self,
parent_string = cmark_node_get_string_content(parent_container);
header_row = row_from_string(self, parser, (unsigned char *)parent_string,
(int)strlen(parent_string));
if (!header_row || header_row->n_columns != marker_row->n_columns) {
free_table_row(parser->mem, marker_row);
if (!header_row || header_row->n_columns != delimiter_row->n_columns) {
free_table_row(parser->mem, delimiter_row);
free_table_row(parser->mem, header_row);
cmark_arena_pop();
parent_container->flags |= CMARK_NODE__TABLE_VISITED;
return parent_container;
}

if (cmark_arena_pop()) {
marker_row = row_from_string(
delimiter_row = row_from_string(
self, parser, input + cmark_parser_get_first_nonspace(parser),
len - cmark_parser_get_first_nonspace(parser));
header_row = row_from_string(self, parser, (unsigned char *)parent_string,
(int)strlen(parent_string));
// row_from_string can return NULL, add additional check to ensure n_columns match
if (!marker_row || !header_row || header_row->n_columns != marker_row->n_columns) {
free_table_row(parser->mem, marker_row);
if (!delimiter_row || !header_row || header_row->n_columns != delimiter_row->n_columns) {
free_table_row(parser->mem, delimiter_row);
free_table_row(parser->mem, header_row);
return parent_container;
}
}

if (!cmark_node_set_type(parent_container, CMARK_NODE_TABLE)) {
free_table_row(parser->mem, header_row);
free_table_row(parser->mem, marker_row);
free_table_row(parser->mem, delimiter_row);
return parent_container;
}

Expand All @@ -326,12 +375,12 @@ static cmark_node *try_opening_table_header(cmark_syntax_extension *self,
parent_container->as.opaque = parser->mem->calloc(1, sizeof(node_table));
set_n_table_columns(parent_container, header_row->n_columns);

// allocate alignments based on marker_row->n_columns
// since we populate the alignments array based on marker_row->cells
// allocate alignments based on delimiter_row->n_columns
// since we populate the alignments array based on delimiter_row->cells
uint8_t *alignments =
(uint8_t *)parser->mem->calloc(marker_row->n_columns, sizeof(uint8_t));
for (i = 0; i < marker_row->n_columns; ++i) {
node_cell *node = &marker_row->cells[i];
(uint8_t *)parser->mem->calloc(delimiter_row->n_columns, sizeof(uint8_t));
for (i = 0; i < delimiter_row->n_columns; ++i) {
node_cell *node = &delimiter_row->cells[i];
bool left = node->buf->ptr[0] == ':', right = node->buf->ptr[node->buf->size - 1] == ':';

if (left && right)
Expand All @@ -353,25 +402,26 @@ 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);

free_table_row(parser->mem, header_row);
free_table_row(parser->mem, marker_row);
free_table_row(parser->mem, delimiter_row);
return parent_container;
}

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
2 changes: 1 addition & 1 deletion lib/commonmarker/version.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# frozen_string_literal: true

module CommonMarker
VERSION = "0.23.9"
VERSION = "0.23.10"
end
7 changes: 5 additions & 2 deletions script/update_submodules
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#!/bin/bash

set -e
set -euo pipefail

if [ -z "$1" ]; then
BRANCH="main"
Expand All @@ -14,7 +14,10 @@ echo "Checking out cmark-upstream"
echo "---------------------"
cd ext/commonmarker/cmark-upstream
git fetch origin
git checkout $BRANCH && git pull
# when checking out a tag, for whatever reason the git pull step fails
# so we comment it out for now:
# git checkout $BRANCH && git pull
git checkout $BRANCH
sha=`git rev-parse HEAD`
cd ../../..
make
Expand Down