From d0e81e2392861ad3f6f27e895080cdfc6a8e72e7 Mon Sep 17 00:00:00 2001 From: Phill MV Date: Mon, 31 Jul 2023 14:53:48 -0400 Subject: [PATCH 1/3] I've used this version of the update_submodules script for several releases, so here I am committing it for posterity. --- script/update_submodules | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/script/update_submodules b/script/update_submodules index 69a43e88..876622b7 100755 --- a/script/update_submodules +++ b/script/update_submodules @@ -1,6 +1,6 @@ #!/bin/bash -set -e +set -euo pipefail if [ -z "$1" ]; then BRANCH="main" @@ -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 From 08b7c4b96c2835edcc2f14e978f758f6ac58b158 Mon Sep 17 00:00:00 2001 From: Phill MV Date: Mon, 31 Jul 2023 14:54:13 -0400 Subject: [PATCH 2/3] Update cmark-upstream to https://github.com/github/cmark-gfm/commit/587a12bb54d95ac37241377e6ddc93ea0e45439b --- ext/commonmarker/autolink.c | 2 +- ext/commonmarker/blocks.c | 6 +- ext/commonmarker/cmark-upstream | 2 +- ext/commonmarker/commonmark.c | 2 +- ext/commonmarker/node.c | 1 + ext/commonmarker/node.h | 1 + ext/commonmarker/table.c | 137 +++++++++++++++++++++----------- 7 files changed, 100 insertions(+), 51 deletions(-) diff --git a/ext/commonmarker/autolink.c b/ext/commonmarker/autolink.c index 491d96c3..d898cd2d 100644 --- a/ext/commonmarker/autolink.c +++ b/ext/commonmarker/autolink.c @@ -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)) { diff --git a/ext/commonmarker/blocks.c b/ext/commonmarker/blocks.c index 03a58748..3b5da56b 100644 --- a/ext/commonmarker/blocks.c +++ b/ext/commonmarker/blocks.c @@ -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; diff --git a/ext/commonmarker/cmark-upstream b/ext/commonmarker/cmark-upstream index 1e230827..587a12bb 160000 --- a/ext/commonmarker/cmark-upstream +++ b/ext/commonmarker/cmark-upstream @@ -1 +1 @@ -Subproject commit 1e230827a584ebc9938c3eadc5059c55ef3c9abf +Subproject commit 587a12bb54d95ac37241377e6ddc93ea0e45439b diff --git a/ext/commonmarker/commonmark.c b/ext/commonmarker/commonmark.c index 4815bfc3..987b4731 100644 --- a/ext/commonmarker/commonmark.c +++ b/ext/commonmarker/commonmark.c @@ -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) && diff --git a/ext/commonmarker/node.c b/ext/commonmarker/node.c index 67f657d8..e7a9606d 100644 --- a/ext/commonmarker/node.c +++ b/ext/commonmarker/node.c @@ -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: diff --git a/ext/commonmarker/node.h b/ext/commonmarker/node.h index 38ac4a6f..73ca7605 100644 --- a/ext/commonmarker/node.h +++ b/ext/commonmarker/node.h @@ -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; }; diff --git a/ext/commonmarker/table.c b/ext/commonmarker/table.c index e53ea315..e8359f25 100644 --- a/ext/commonmarker/table.c +++ b/ext/commonmarker/table.c @@ -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; @@ -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 { @@ -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; @@ -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)); @@ -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; @@ -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(); @@ -289,8 +338,8 @@ 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; @@ -298,14 +347,14 @@ static cmark_node *try_opening_table_header(cmark_syntax_extension *self, } 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; } @@ -313,7 +362,7 @@ static cmark_node *try_opening_table_header(cmark_syntax_extension *self, 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; } @@ -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) @@ -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; } @@ -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); @@ -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); } } @@ -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\""; @@ -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 = @@ -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) { @@ -754,12 +804,7 @@ static void html_render(cmark_syntax_extension *extension, cmark_strbuf_puts(html, "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; From e1e450c381e1fac5021a08bdc5f72bbac9cf6038 Mon Sep 17 00:00:00 2001 From: Phill MV Date: Mon, 31 Jul 2023 14:56:18 -0400 Subject: [PATCH 3/3] :gem: release 0.23.10 --- CHANGELOG.md | 8 ++++++++ lib/commonmarker/version.rb | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e1cc41a0..2da27c89 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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) diff --git a/lib/commonmarker/version.rb b/lib/commonmarker/version.rb index 77032fd8..0c4de08a 100644 --- a/lib/commonmarker/version.rb +++ b/lib/commonmarker/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module CommonMarker - VERSION = "0.23.9" + VERSION = "0.23.10" end