CommonMarker Integer Overflow Vulnerability
Description
CommonMarker versions prior to 0.23.4 are at risk of an integer overflow vulnerability. This vulnerability can result in possibly unauthenticated remote attackers to cause heap memory corruption, potentially leading to an information leak or remote code execution, via parsing tables with marker rows that contain more than UINT16_MAX columns.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
Integer overflow in CommonMarker <0.23.4 allows unauthenticated attackers to cause heap memory corruption via crafted Markdown tables with over 65,535 marker row columns.
Vulnerability
Overview
CommonMarker, a Ruby wrapper for the Rust-based comrak CommonMark parser, is vulnerable to an integer overflow in versions prior to 0.23.4 [1]. The vulnerability resides in the row_from_string function used during table parsing [3]. When a table's marker row (the row containing dashes and pipes that defines column alignment) contains more than 65,535 columns (UINT16_MAX), the row->n_columns counter wraps around, leading to an undersized allocation and subsequent heap memory corruption [2][3].
Exploitation
An unauthenticated remote attacker can exploit this vulnerability by providing a specially crafted Markdown document that includes a table with a marker row exceeding the 65,535 column limit. The parsing process will trigger the integer overflow, causing the parser to write beyond allocated heap buffers [2]. No authentication or special privileges are required; the attacker only needs to submit the malicious input to an application that uses the vulnerable CommonMarker library to render Markdown [1].
Impact
Successful exploitation can result in heap memory corruption, which may allow an attacker to leak sensitive information (information disclosure) or achieve remote code execution (RCE) in the context of the application process [2]. The severity is reflected in the CVSS score, and the vulnerability has been assigned a CVE ID [2].
Mitigation
The issue was fixed in CommonMarker version 0.23.4, released on January 4, 2024 [1]. The fix introduces a check for row->n_columns == UINT16_MAX and returns a null pointer to abort parsing, preventing the overflow [3]. Users are strongly advised to upgrade to the latest version. No workarounds are available other than updating [4].
AI Insight generated on May 20, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
commonmarkerRubyGems | < 0.23.4 | 0.23.4 |
Affected products
2Patches
1ab4504fd1746Merge pull request from GHSA-fmx4-26r3-wxpf
4 files changed · +32 −6
ext/commonmarker/cmark-gfm_version.h+2 −2 modified@@ -1,7 +1,7 @@ #ifndef CMARK_GFM_VERSION_H #define CMARK_GFM_VERSION_H -#define CMARK_GFM_VERSION ((0 << 24) | (29 << 16) | (0 << 8) | 2) -#define CMARK_GFM_VERSION_STRING "0.29.0.gfm.2" +#define CMARK_GFM_VERSION ((0 << 24) | (29 << 16) | (0 << 8) | 3) +#define CMARK_GFM_VERSION_STRING "0.29.0.gfm.3" #endif
ext/commonmarker/cmark-upstream+1 −1 modified@@ -1 +1 @@ -Subproject commit 766f161ef6d61019acf3a69f5099489e7d14cd49 +Subproject commit ff164f188bc1eb23391c85436ab418463e7a030f
ext/commonmarker/table.c+28 −2 modified@@ -129,6 +129,7 @@ static table_row *row_from_string(cmark_syntax_extension *self, bufsize_t cell_matched = 1, pipe_matched = 1, offset; int expect_more_cells = 1; int row_end_offset = 0; + int int_overflow_abort = 0; row = (table_row *)parser->mem->calloc(1, sizeof(table_row)); row->n_columns = 0; @@ -161,6 +162,12 @@ static table_row *row_from_string(cmark_syntax_extension *self, ++cell->internal_offset; } + // make sure we never wrap row->n_columns + // offset will != len and our exit will clean up as intended + if (row->n_columns == UINT16_MAX) { + int_overflow_abort = 1; + break; + } row->n_columns += 1; row->cells = cmark_llist_append(parser->mem, row->cells, cell); } @@ -194,7 +201,7 @@ static table_row *row_from_string(cmark_syntax_extension *self, } } - if (offset != len || row->n_columns == 0) { + if (offset != len || row->n_columns == 0 || int_overflow_abort) { free_table_row(parser->mem, row); row = NULL; } @@ -241,6 +248,11 @@ static cmark_node *try_opening_table_header(cmark_syntax_extension *self, marker_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) { + return parent_container; + } + assert(marker_row); cmark_arena_push(); @@ -264,6 +276,12 @@ static cmark_node *try_opening_table_header(cmark_syntax_extension *self, 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); + free_table_row(parser->mem, header_row); + return parent_container; + } } if (!cmark_node_set_type(parent_container, CMARK_NODE_TABLE)) { @@ -281,8 +299,10 @@ 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 uint8_t *alignments = - (uint8_t *)parser->mem->calloc(header_row->n_columns, sizeof(uint8_t)); + (uint8_t *)parser->mem->calloc(marker_row->n_columns, sizeof(uint8_t)); cmark_llist *it = marker_row->cells; for (i = 0; it; it = it->next, ++i) { node_cell *node = (node_cell *)it->data; @@ -351,6 +371,12 @@ static cmark_node *try_opening_table_row(cmark_syntax_extension *self, row = row_from_string(self, parser, input + cmark_parser_get_first_nonspace(parser), len - cmark_parser_get_first_nonspace(parser)); + if (!row) { + // clean up the dangling node + cmark_node_free(table_row_block); + return NULL; + } + { cmark_llist *tmp; int i, table_columns = get_n_table_columns(parent_container);
lib/commonmarker/version.rb+1 −1 modified@@ -1,5 +1,5 @@ # frozen_string_literal: true module CommonMarker - VERSION = '0.23.2' + VERSION = '0.23.3' end
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
7- github.com/gjtorikian/commonmarker/commit/ab4504fd17460627a6ab255bc3c63e8e5fc6aed3ghsapatchWEB
- github.com/advisories/GHSA-fmx4-26r3-wxpfghsathird-party-advisoryADVISORY
- github.com/gjtorikian/commonmarker/security/advisories/GHSA-fmx4-26r3-wxpfghsavendor-advisoryWEB
- nvd.nist.gov/vuln/detail/CVE-2024-22051ghsaADVISORY
- vulncheck.com/advisories/vc-advisory-GHSA-fmx4-26r3-wxpfmitrethird-party-advisory
- github.com/github/cmark-gfm/security/advisories/GHSA-mc3g-88wq-6f4xghsarelatedWEB
- github.com/rubysec/ruby-advisory-db/blob/master/gems/commonmarker/CVE-2024-22051.ymlghsaWEB
News mentions
0No linked articles in our index yet.