RubySec

Providing security resources for the Ruby community

CVE-2026-35201 (rdiscount): rdiscount has an Out-of-bounds Read

rdiscount has an Out-of-bounds Read

Published: April 06, 2026

SECURITY IDENTIFIERS

GEM

rdiscount

SEVERITY

CVSS v3.x: 5.9 (Medium)

UNAFFECTED VERSIONS

< 1.3.1.1

PATCHED VERSIONS

>= 2.2.7.4

DESCRIPTION

Summary

A signed length truncation bug causes an out-of-bounds read in the default Markdown parse path. Inputs larger than INT_MAX are truncated to a signed int before entering the native parser, allowing the parser to read past the end of the supplied buffer and crash the process.

Details

In both public entry points:

  • ext/rdiscount.c:97
  • ext/rdiscount.c:136

RSTRING_LEN(text) is passed directly into mkd_string():

MMIOT *doc = mkd_string(RSTRING_PTR(text),
RSTRING_LEN(text), flags);

mkd_string() accepts int len:

  • ext/mkdio.c:174
Document
* mkd_string(const char *buf, int len, mkd_flag_t flags)
{
    struct string_stream about;

    about.data = buf;
    about.size = len;

    return populate((getc_func)__mkd_io_strget, &amp;about, flags &amp; INPUT_MASK);
}

The parser stores the remaining input length in a signed int:

  • ext/markdown.h:205
struct string_stream {
    const
char *data;
    int   size;
};

The read loop stops only when size == 0:

  • ext/mkdio.c:161
int __mkd_io_strget(struct string_stream *in)
{
    if ( !in-&gt;size ) return EOF;

    --(in-&gt;size);

    return *(in-&gt;data)++;
}

If the Ruby string length exceeds INT_MAX, the value can truncate to a negative int. In that state, the parser continues incrementing data and reading past the end of the original Ruby string, causing an out-of-bounds read and native crash.

Affected APIs:

  • RDiscount.new(input).to_html
  • RDiscount.new(input).toc_content

Impact

This is an out-of-bounds read with the main issue being reliable denial-of-service. Impacted is limited to deployments parses attacker-controlled Markdown and permits multi-GB inputs.

Fix

just add a checked length guard before the mkd_string() call in both public entry points:

  • ext/rdiscount.c:97
  • ext/rdiscount.c:136 ex:
VALUE text = rb_funcall(self, rb_intern(\&quot;text\&quot;), 0);
long text_len = RSTRING_LEN(text);
VALUE buf = rb_str_buf_new(1024);
Check_Type(text, T_STRING);

if (text_len &gt; INT_MAX) {
    rb_raise(rb_eArgError, \&quot;markdown input too large\&quot;);
}

MMIOT *doc = mkd_string(RSTRING_PTR(text), (int)text_len, flags);

The same guard should be applied in rb_rdiscount_toc_content() before its mkd_string() call.

RELATED