# Arbitrary heap exposure in JSON.generate

JSON.generate with a NUL-containing space option causes strdup/memccpy to truncate the string while the stored length stays large, leaking arbitrary heap memory into output.

- Payout: hidden
- Severity: high
- Program: Ruby
- Found by: ahmadsherif
- Disclosed: 2017-09-25
- CVE: CVE-2017-14064
- Write-up quality: 8/10
- Original: https://hackerone.com/reports/209949
- Library page: https://bugbountylibrary.com/r/h1%3A209949
- Tags: area:oss domain:oss impact:data-leak kind:bounty platform:hackerone sev:high tech:ruby technique:source-review vuln:info-disclosure vuln:memory-corruption

**Target:** json gem C extension (generator.c)
**Root cause:** The space option is duplicated with strdup/fstrndup (memccpy), which stop at a NUL byte and return a shorter string, but space_len retains the original length; fbuffer_append then copies space_len bytes from the truncated buffer, reading past it into adjacent heap memory.
**Steps:**
1. Build Ruby with the json extension.
2. Run `require 'json'; state = JSON.state.new; state.space = "\0" * 1024; puts JSON.generate({a: :b}, state)`.
3. Observe heap contents (e.g. psych/handlers/recorder.rb, gem spec text) emitted between the JSON tokens.
4. Alternatively `JSON.generate({foo: "bar"}, space: "\0" * 1024)` triggers the same via fstrndup/memccpy.
**How found:** Code reading of generator.c: noticed strdup and memccpy both terminate at NUL while space_len is computed from the original string length, creating a length/content mismatch later consumed by fbuffer_append.
**Exploitation:** Any caller passing attacker-influenced space/indent configuration to JSON.generate can cause the generator to append uninitialized/adjacent heap bytes to the output, disclosing process memory (potentially secrets or pointers) to whoever receives the JSON.
**Insight:** When a length is stored separately from a duplicated string, check whether the duplication routine (strdup, memccpy, strlcpy) can truncate on NUL or embedded terminators while the length stays authoritative — the mismatch becomes an over-read in any append/copy sink.

## Proof of concept (copied exactly from the original report)

**Ruby snippet exposing heap via JSON.state space**

```
require 'json'

state = JSON.state.new
state.space = "\0" * 1024

puts JSON.generate({a: :b}, state)
```

