VYPR
High severityOSV Advisory· Published Jan 4, 2024· Updated Nov 29, 2025

CommonMarker Integer Overflow Vulnerability

CVE-2024-22051

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.

PackageAffected versionsPatched versions
commonmarkerRubyGems
< 0.23.40.23.4

Affected products

2

Patches

1
ab4504fd1746

Merge pull request from GHSA-fmx4-26r3-wxpf

https://github.com/gjtorikian/commonmarkerGaren TorikianMar 2, 2022via ghsa
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

News mentions

0

No linked articles in our index yet.