CVE-2026-35201 is a medium-severity (CVSS 5.9) Out-of-bounds Read vulnerability in rdiscount. A fix is available for rdiscount — see the affected versions and patch details below.
Discount has an Out-of-bounds Read in rdiscount
Exploitation Status
Proof-of-concept exploit code exists
- CISA’s SSVC triage found public proof-of-concept exploit code for this CVE, though no confirmed active exploitation.
Exploitation and automatability from CISA’s SSVC triage for CVE-2026-35201.
EPSS Exploitation Probability
EPSS (Exploit Prediction Scoring System) is a daily probability model maintained by FIRST.org. It estimates the likelihood a CVE will be exploited in production environments within the next 30 days, derived from real-world threat intelligence signals.
How urgent is this, really
CVE-2026-35201 plotted by exploitation likelihood (EPSS) against impact (CVSS). The shaded corner — EPSS 50%+ and CVSS 7.0+ — is where this CVE doesn't sit, though severity or exploitability alone can still warrant action.
Where this sits among everything scored
Of 377,636 CVEs with a current EPSS score, this one falls in the < 10% band (highlighted). Real counts from FIRST.org, not a sample — log-scaled since the landscape is heavily right-skewed.
Real-World Exposure
rdiscountReal-time download stats are indexed for npm and PyPI packages. This vulnerability affects RubyGems packages — download data is not available via public APIs for these ecosystems.
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:97ext/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, &about, flags & 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->size ) return EOF;
--(in->size);
return *(in->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_htmlRDiscount.new(input).toc_content
PoC
Crash via to_html:
RUBYLIB=lib:ext ruby -e 'require "rdiscount"; n=2_200_000_000; s = "a" * n; warn "built=#{s.bytesize}"; RDiscount.new(s).to_html"'
result:
built=2200000000- Ruby terminates with
[BUG] Segmentation fault - top control frame:
CFUNC :to_html
same result with 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:97ext/rdiscount.c:136ex:
VALUE text = rb_funcall(self, rb_intern("text"), 0);
long text_len = RSTRING_LEN(text);
VALUE buf = rb_str_buf_new(1024);
Check_Type(text, T_STRING);
if (text_len > INT_MAX) {
rb_raise(rb_eArgError, "markdown input too large");
}
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.
Affected Packages
| Ecosystem | Package | Vulnerable range | Fix |
|---|---|---|---|
| 💎RubyGems | rdiscount | ≥ 1.3.1.1&&< 2.2.7.4 | 2.2.7.4bundle update rdiscount --conservative |
Detection & mitigation playbook
Open-source dependencyDetect
Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for rdiscount, including transitive dependencies — a direct dependency you never call can still pull in a vulnerable version.
Fix
Update rdiscount to 2.2.7.4 or later, then make sure no transitive (indirect) dependency still pins the vulnerable range — O3 confirms CVE-2026-35201 is resolved across your whole dependency graph.
Workarounds
If you can't upgrade right away: gate or disable the affected feature, validate untrusted input at the boundary, and avoid passing attacker-controlled data into the vulnerable path. O3's runtime protection blocks exploitation in production as an interim safeguard until the upgrade lands.
How O3 protects you
O3 Security's impact-aware SCA analyses which vulnerable code paths your application actually calls, so a match like CVE-2026-35201 can be triaged on real exposure rather than presence alone.
Tailored to CVE-2026-35201. Runtime protection reduces exposure until a permanent patch is applied and verified — it complements patching, it doesn't replace it.
Frequently Asked Questions
Is CVE-2026-35201 in your dependencies?
O3 Security finds CVE-2026-35201 across RubyGems dependencies, including transitive ones, and its impact-aware SCA ranks findings by whether your code actually calls the vulnerable path.