GHSA-9hj4-r449-hfvc Ruby JSON: JSON::ResumableParser#partial_value dereferences a freed input buffer and crashes on truncated duplicate-key streams
SummaryRuby'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.0JSON 2.21.0JSON 2.21.1The 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.DetailsThe affected source is:The vulnerable sequence in JSON 2.21.1 is:`cResumableParser_parse` reaches the end of the current input buffer.It calls `json_str_clear(parser->buffer)`.It sets `parser->buffer = Qfalse`.The parser-state pointers into the released buffer are not reset.`partial_value` makes a shallow copy of the parser state.Reconstructing an incomplete object containing duplicate keys reaches the duplicate-key warning path.`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-L2569Parser-state copy:
https://github.com/ruby/json/blob/fd61def38b9bb859fee7eec8e7d3143600e5b347/ext/json/ext/parser/parser.c#L2647-L2654Stale-pointer read in `cursor_position`:
https://github.com/ruby/json/blob/fd61def38b9bb859fee7eec8e7d3143600e5b347/ext/json/ext/parser/parser.c#L590-L628Duplicate-key handling path:
https://github.com/ruby/json/blob/fd61def38b9bb859fee7eec8e7d3143600e5b347/ext/json/ext/parser/parser.c#L1196-L1255When input is supplied to the resumable parser, the parser state stores direct pointers into the backing Ruby string:After the current buffer has been consumed, `cResumableParser_parse` clears the string and removes the parser's reference to it:This path does not invalidate or replace:`JSON::ResumableParser#partial_value` subsequently makes a shallow copy of the parser structure:When the partial object contains duplicate keys, reconstruction follows this call path:`cursor_position` then reads through pointers that may refer to released storage.AddressSanitizer reports:The reproducer follows the normal resumable-parser API sequence: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.PoCThe complete evidence bundle is attached as:SHA-256:RequirementsLinuxRuby with development headersC compiler`make`RubyGemsRelease-build, network, and differential reproductionExtract the attachment:Run the official JSON 2.21.1 release-build proof, loopback network proof, and differential controls:Expected primary results:The following four differential controls must also report `result=PASS`:The release-build crash stack includes:AddressSanitizer reproductionRun:Expected vulnerable result:Expected patched-control result:Affected-version matrixThe release-build crash was reproduced three times for each of the following official RubyGems releases:Additional evidence is included in:ImpactThis 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:uses `JSON::ResumableParser`;processes attacker-controlled streaming input;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:No confidentiality impact, integrity impact, arbitrary code execution, or information disclosure is claimed.Suggested remediationBefore 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.