Feed/CVE-2026-71847
CVE-2026-71847LOWCVSS 0.0

Ruby JSON: JSON::ResumableParser#partial_value dereferences a freed input buffer and crashes on truncated duplicate-key streams

Published Aug 7, 2026·Updated Aug 7, 2026

NVD Description

### Summary Ruby's JSON native C extension clears the consumed `JSON::ResumableParser` input buffer but leaves `state.start`, `state.cursor`, and `state.end` pointing into released storage. When `partial_value` reconstructs an incomplete object containing duplicate keys, the duplicate-key warning path calls `cursor_position`, which dereferences those stale pointers. This results in a heap-use-after-free and can terminate the Ruby process. An attacker who can supply JSON stream data to an application using `JSON::ResumableParser` may cause process termination when the application calls `partial_value` on incomplete attacker-controlled input containing duplicate object keys. The issue was reproduced in the native C extension from the official RubyGems releases: * JSON 2.20.0 * JSON 2.21.0 * JSON 2.21.1 The attached evidence demonstrates: * an AddressSanitizer-confirmed heap-use-after-free; * a native `SIGSEGV` using the official JSON 2.21.1 RubyGem; * an end-to-end loopback TCP attacker/victim reproduction; * four differential controls; * successful execution after applying a tested patch control. This was originally reported privately through Ruby's HackerOne program as report `#3867755`. A Ruby maintainer independently confirmed reproduction of the ASan failure and requested that further coordination continue through this private advisory. No code execution or information disclosure is claimed. ### Details The affected source is: ```text ext/json/ext/parser/parser.c ``` The vulnerable sequence in JSON 2.21.1 is: 1. `cResumableParser_parse` reaches the end of the current input buffer. 2. It calls `json_str_clear(parser->buffer)`. 3. It sets `parser->buffer = Qfalse`. 4. The parser-state pointers into the released buffer are not reset. 5. `partial_value` makes a shallow copy of the parser state. 6. Reconstructing an incomplete object containing duplicate keys reaches the duplicate-key warning path. 7. `cursor_position` walks through the stale input pointers and reads released memory. Relevant source locations: * Buffer release: https://github.com/ruby/json/blob/fd61def38b9bb859fee7eec8e7d3143600e5b347/ext/json/ext/parser/parser.c#L2562-L2569 * Parser-state copy: https://github.com/ruby/json/blob/fd61def38b9bb859fee7eec8e7d3143600e5b347/ext/json/ext/parser/parser.c#L2647-L2654 * Stale-pointer read in `cursor_position`: https://github.com/ruby/json/blob/fd61def38b9bb859fee7eec8e7d3143600e5b347/ext/json/ext/parser/parser.c#L590-L628 * Duplicate-key handling path: https://github.com/ruby/json/blob/fd61def38b9bb859fee7eec8e7d3143600e5b347/ext/json/ext/parser/parser.c#L1196-L1255 When input is supplied to the resumable parser, the parser state stores direct pointers into the backing Ruby string: ```c RSTRING_GETMEM(parser->buffer, start, len); parser->state.start = start; parser->state.end = start + len; parser->state.cursor = parser->state.start + offset; ``` After the current buffer has been consumed, `cResumableParser_parse` clears the string and removes the parser's reference to it: ```c if (eos(&parser->state)) { json_str_clear(parser->buffer); parser->buffer = Qfalse; } ``` This path does not invalidate or replace: ```text parser->state.start parser->state.cursor parser->state.end ``` `JSON::ResumableParser#partial_value` subsequently makes a shallow copy of the parser structure: ```c JSON_ResumableParser *original_parser = cResumableParser_get(self); JSON_ResumableParser parser = *original_parser; ``` When the partial object contains duplicate keys, reconstruction follows this call path: ```text cResumableParser_partial_value_body -> json_decode_object -> json_on_duplicate_key -> emit_duplicate_key_warning -> emit_parse_warning -> cursor_position ``` `cursor_position` then reads through pointers that may refer to released storage. AddressSanitizer reports: ```text ERROR: AddressSanitizer: heap-use-after-free cursor_position at parser.c:604 freed by cResumableParser_parse at parser.c:2567 ``` The reproducer follows the normal resumable-parser API sequence: ```ruby parser << chunk parser.parse parser << next_chunk parser.parse parser.partial_value ``` The issue does not require: * an application-defined callback; * explicit garbage collection; * parser reentrancy; * custom parser options; * an attacker-supplied Ruby object; * manual modification of native parser state. The release-build crash reproduced on JSON 2.20.0, 2.21.0, and 2.21.1. This report covers the native C-extension implementation. The separate Java-platform implementation was not tested and is not claimed to be affected. ### PoC The complete evidence bundle is attached as: ```text ruby-json-resumable-partial-value-uaf-evidence-20260716.zip ``` SHA-256: ```text 07bf8d47b115e45d6145d0447ab6c1c0255e4a7e9b2fb55c3c9a0e24406134ac ``` #### Requirements * Linux * Ruby with development headers * C compiler * `make` * RubyGems #### Release-build, network, and differential reproduction Extract the attachment: ```sh unzip ruby-json-resumable-partial-value-uaf-evidence-20260716.zip cd ruby-json-resumable-partial-value-uaf-evidence-20260716 ``` Run the official JSON 2.21.1 release-build proof, loopback network proof, and differential controls: ```sh ./run_exact_2211.sh ``` Expected primary results: ```text release_exit=139 network_victim_exit=139 network_result=PASS result=PASS ``` The following four differential controls must also report `result=PASS`: ```text unique_key duplicate_allowed no_partial complete_document ``` The release-build crash stack includes: ```text cursor_position emit_parse_warning emit_duplicate_key_warning json_decode_object cResumableParser_partial_value_body ``` #### AddressSanitizer reproduction Run: ```sh ./run_asan.sh ``` Expected vulnerable result: ```text asan_vulnerable_exit=134 ERROR: AddressSanitizer: heap-use-after-free cursor_position at parser.c:604 freed by cResumableParser_parse at parser.c:2567 ``` Expected patched-control result: ```text asan_patched_exit=0 asan_result=PASS ``` #### Affected-version matrix The release-build crash was reproduced three times for each of the following official RubyGems releases: ```text json 2.20.0 json 2.21.0 json 2.21.1 ``` Additional evidence is included in: ```text artifacts/exact-2211-e2e.txt artifacts/asan-and-patched-control.txt artifacts/version-matrix.txt artifacts/source-and-release-verification.txt source-slices.txt prior-art.md patch-control.diff ``` ### Impact This is a use-after-free that can result in native Ruby process termination. An attacker must be able to supply JSON stream data to an application that: 1. uses `JSON::ResumableParser`; 2. processes attacker-controlled streaming input; 3. calls `partial_value` after parsing an incomplete document containing duplicate object keys. In network-facing deployments meeting these conditions, an attacker can cause process termination and denial of service. The release-build crash was reproduced consistently in the tested Linux environment. The AddressSanitizer result confirms the underlying heap-use-after-free independently of normal allocator behavior. The demonstrated impact is: ```text Denial of service through native process termination ``` No confidentiality impact, integrity impact, arbitrary code execution, or information disclosure is claimed. ### Suggested remediation Before clearing or releasing the resumable parser's input buffer, invalidate or replace every parser-state pointer that refers to the buffer's backing storage. Delayed code paths such as duplicate-key warning generation must not calculate cursor positions using pointers after the corresponding buffer has been released. The attached `patch-control.diff` demonstrates one tested control and is provided for validation rather than as a required final implementation.

Affected Packages (1)

jsonGEM
From 2.20.0
Fixed in = 2.21.1

References

View on NVD Search GitHub Search Google

Get alerted for CVEs like this

Register your stack and get notified within minutes when a matching CVE drops.

Start monitoring free