Ruby json: JSON generator heap buffer overflow when streaming to an IOGHSA-x2f5-4prf-w687
LOWFix: ruby/json@996bac6GHSA-x2f5-4prf-w687 is a low-severity (CVSS 3.7) CWE-122 vulnerability in json. A fix is available for json — see the affected versions and patch details below.
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 GHSA-x2f5-4prf-w687.
EPSS Exploitation Probability
Probability of exploitation in the next 30 days, from FIRST.org EPSS.
How urgent is this, really
GHSA-x2f5-4prf-w687 by exploitation likelihood (EPSS) against impact (CVSS). Outside the shaded patch-first corner.
Where this sits among everything scored
Of 395,249 CVEs with a current EPSS score, this one falls in the < 10% band (highlighted). Counts from FIRST.org, log-scaled.
Real-World Exposure
jsonReal-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
JSON.dump(obj, io) and JSON::State#generate(obj, io) can write past the
internal JSON generator buffer when a streamed object contains an
attacker-controlled string near 16 KB. The issue is a heap out-of-bounds write
in the IO-streaming path and is demonstrated as a reliable process crash /
denial of service.
This was triaged on HackerOne as report #3785370. The issue was confirmed there and I was asked to open it here.
Details
Root cause is in ext/json/fbuffer/fbuffer.h, fbuffer_do_inc_capa().
On the IO path, the buffer is grown to FBUFFER_IO_BUFFER_SIZE (16383), but the
early return checks total capacity instead of remaining capacity:
if (RB_UNLIKELY(fb->io)) {
if (fb->capa < FBUFFER_IO_BUFFER_SIZE) {
fbuffer_realloc(fb, FBUFFER_IO_BUFFER_SIZE);
} else {
fbuffer_flush(fb);
}
if (RB_LIKELY(requested < fb->capa)) {
return;
}
}
If fb->len already contains JSON syntax bytes, and a string flush has
16383 - fb->len <= requested < 16383, this check returns even though there is
not enough space left. fbuffer_append_reserved() then writes past the buffer:
MEMCPY(fb->ptr + fb->len, newstr, char, len);
The minimal fix is to compare against the remaining capacity:
- if (RB_LIKELY(requested < fb->capa)) {
+ if (RB_LIKELY(requested <= fb->capa - fb->len)) {
return;
}
PoC
require "json"
require "stringio"
io = StringIO.new
big = "a" * 16385
big[16382] = '"' # escapable byte near the buffer boundary
JSON.dump([big], io)
Verified results:
Ruby 4.0.5 / bundled json 2.18.0:
malloc(): invalid size (unsorted)
.../json/common.rb:956: [BUG] Aborted
ruby/ruby master c78418b7a0 / json 2.19.8 / ASan:
heap-buffer-overflow WRITE of size 16382
fbuffer_append_reserved ext/json/fbuffer/fbuffer.h:145
search_flush ext/json/generator/generator.c:139
convert_UTF8_to_JSON ext/json/generator/generator.c:231
raw_generate_json_string ext/json/generator/generator.c:922
cState_m_generate ext/json/generator/generator.c:1891
Control: the same data through JSON.dump([big]) without an IO argument returns
normally. The bug is specific to the IO-streaming path.
Impact
A remote attacker can trigger a heap out-of-bounds write if they control a
string field that an application serializes through JSON.dump(obj, io) or
JSON::State#generate(obj, io). The demonstrated impact is reliable denial of
service. I am not claiming code execution or information disclosure.
Affected Packages
| Ecosystem | Package | Vulnerable range | Fix |
|---|---|---|---|
| 💎RubyGems | json | ≥ 2.9.0&&< 2.19.9 | 2.19.9bundle update json --conservative |
Detection & mitigation playbook
Open-source dependencyDetect
Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for json, including transitive dependencies — a direct dependency you never call can still pull in a vulnerable version.
Fix
Update json to 2.19.9 or later, then make sure no transitive (indirect) dependency still pins the vulnerable range — O3 confirms GHSA-x2f5-4prf-w687 is resolved across your whole dependency graph.
Workarounds
Stop feeding it untrusted input: reject or quarantine files and payloads from unverified sources until you can upgrade, restrict accepted formats to the ones you actually need, and run the parsing or decoding step in a least-privileged sandbox or short-lived worker so a crash or corrupted read cannot reach the rest of the process.
Fixing This On Your OS
If you run this on a Linux distribution, patch through your package manager against the distro's own security advisory below — it tracks the exact backported fix for your release, which can ship on a different timeline (and sometimes a different severity) than the upstream project.
This Low impact flaw in Ruby JSON can lead to a denial of service in applications that process untrusted, oversized streamed JSON objects. The vulnerability arises from a heap buffer overflow when the JSON generator attempts to write past its internal buffer with an attacker-controlled string near 16 KB, resulting in…
| Product | Fixed in | Advisory |
|---|---|---|
| Red Hat Hardened Images | ruby4-0-main-0.3.2-37.1.hum1 | RHSA-2026:47932 |
| Red Hat Hardened Images | ruby3-4-main-3.4.10-31.5.hum1 | RHSA-2026:48482 |
Frequently Asked Questions
Is GHSA-x2f5-4prf-w687 in your dependencies?
Find it across RubyGems, including transitive dependencies.