Aug 8, 2026·6 min read·29 visits
A Use-After-Free (UAF) bug in the native C extension of the Ruby json gem allows remote attackers to trigger a process crash and denial of service via malformed streaming JSON data with duplicate keys.
A technical analysis of the use-after-free (UAF) vulnerability in the Ruby JSON gem (CVE-2026-71847) that impacts versions 2.20.0 through 2.21.1. This vulnerability occurs when parsing incomplete stream data containing duplicate keys.
CVE-2026-71847 is a high-severity use-after-free vulnerability identified within the native C extension of the Ruby json gem. The vulnerability resides specifically within the JSON::ResumableParser class, which handles chunk-by-chunk stream parsing. Under conditions involving incomplete stream inputs containing duplicate keys, the parser invokes memory-unsafe operations on deallocated storage.
The vulnerability is categorized under CWE-416 (Use After Free). In multi-threaded or memory-sensitive Ruby environments, dereferencing these stale pointers can lead to memory leakage, immediate process crash via segmentation fault, or potential memory corruption. This compromise directly affects system availability and potentially system integrity.
The issue impacts versions of the json gem starting from 2.20.0 up to, but excluding, 2.21.2. Applications that rely on streaming parsers to handle large, unauthenticated user payloads are particularly exposed. Remediation requires an immediate update to version 2.21.2.
The root cause of this vulnerability lies in the memory management of the native C extension inside ext/json/ext/parser/parser.c. During the streaming parse cycle, when a chunk of data is completed, the function cResumableParser_parse clears the associated input buffer by calling json_str_clear(parser->buffer). While the buffer's Ruby object reference is updated, the internal parsing state (parser->state) retains dangling pointers.
Specifically, the structure fields state.start, state.cursor, and state.end are not re-initialized to null. These fields continue pointing to the memory address of the newly freed heap allocation. This creates a classic dangling pointer condition where subsequent execution flows can access invalid locations.
The execution path triggers when a consumer calls JSON::ResumableParser#partial_value to extract the current parsing state of an incomplete stream. If the parsed stream contains duplicate object keys, the native parser triggers a warning path via emit_parse_warning to issue a deprecation alert. This warning path invokes cursor_position which reads and dereferences the stale pointer values to calculate the line and column numbers of the duplicate key.
Here is a sequence diagram illustrating the lifecycle of the dangling pointers during resumable parsing:
To understand the mechanism of the vulnerability, we inspect the vulnerable version of the source code in ext/json/ext/parser/parser.c. In the vulnerable implementation, the clearing of the buffer is done without safety checks or pointer nullification:
// Vulnerable implementation in cResumableParser_parse
if (eos(&parser->state)) {
json_str_clear(parser->buffer);
parser->buffer = Qfalse;
// state.start, state.cursor, and state.end are left pointing to the freed buffer
}This vulnerability is resolved in commit 2c332bfe2bfb0e754da07e2a0310ef106bf46482 by explicitly nullifying these pointers upon freeing the buffer. The patch also prevents coordinate calculations for warnings when a resumable parser context is active:
// Patched implementation in cResumableParser_parse
if (eos(&parser->state)) {
json_str_clear(parser->buffer);
parser->buffer = Qfalse;
// Nullify dangling pointers to prevent Use-After-Free
parser->state.start = parser->state.cursor = parser->state.end = 0;
}Additionally, the patch alters emit_parse_warning to avoid calling cursor_position when the parsing context runs in resumable mode, since tracking absolute positions on incomplete streams is unreliable:
// Patched warning emission path
static void emit_parse_warning(const char *message, JSON_ParserState *state)
{
VALUE warning;
if (state->parser) {
// Avoid calculation entirely; use plain message to bypass cursor_position
warning = rb_utf8_str_new_cstr(message);
} else {
long line, column;
cursor_position(state, &line, &column);
warning = rb_sprintf("%s at line %ld column %ld", message, line, column);
}
rb_funcall(mJSON, rb_intern("deprecation_warning"), 1, warning);
}An attack targeting this vulnerability relies on sending malformed, streaming JSON inputs to an application that processes data using the JSON::ResumableParser class. The payload must satisfy two specific structural properties to trigger the vulnerable path: duplicate object keys and a truncated or incomplete structure.
First, the attacker must initiate a stream with duplicate keys, such as {"key": 1, "key": 2, ...}. The presence of duplicate keys guarantees that the native extension executes the warning generation path. Second, the JSON payload must be cut off prematurely, ensuring that the stream remains incomplete and the application calls #partial_value to recover the partial state.
When the application reads the incomplete stream, it processes the initial chunk, reaches the end of the input segment, and frees the buffer while retaining pointers. The subsequent invocation of #partial_value forces the C extension to attempt pointer arithmetic on these freed segments to calculate line offsets. This attempt results in a read access violation and terminates the executing Ruby worker process.
The following script structure illustrates how a regression payload can be constructed to trigger the vulnerability when sent over a stream:
# Trigger sequence in Ruby test suite
parser = JSON::ResumableParser.new
parser << '{"a":1,"a":2,"pad":"' + ('x' * 4194304)
parser.parse
parser << '",'
parser.parse
parser.partial_value # Dereferences freed pointer in cursor_positionThe main outcome of successful exploitation is a persistent denial-of-service (DoS) condition. Because the memory dereference happens within a native C library, the Ruby VM cannot catch the resulting segmentation fault through standard exception handlers like rescue. The entire parent process or worker thread terminates immediately.
In multi-threaded web application servers (such as Puma or Passenger), a repeated execution of this payload against the server endpoints will deplete available workers. This leads to a denial of service for legitimate users. If the application environment lacks automatic worker recovery, manual intervention is required to restore the service.
The primary CVSS score for this vulnerability is assessed at 8.7 under the CVSS v4.0 standard. While the impact is primarily centered on system availability (VA:H), vulnerability researchers must recognize that heap-use-after-free vulnerabilities can occasionally be combined with other heap-grooming techniques to achieve remote code execution (RCE) in scenarios where memory allocators do not randomize allocations.
Remediation requires upgrading the json gem to version 2.21.2 or higher. The patch fully mitigates the vulnerability by clearing the dangling pointers inside cResumableParser_parse and short-circuiting the warning generation path to bypass the unsafe memory scanner when resumable parsing is active.
For legacy systems that cannot immediately update the gem, a structural workaround involves implementing input validation filters prior to passing stream data to the resumable parser. Specifically, a lightweight Ruby-based filter can check incoming payloads for duplicate key signatures or verify formatting before deep parsing.
Additionally, system administrators should configure containerized runtimes to automatically restart application processes that exit abnormally. Enforcing maximum memory limits per worker process can also prevent heap-grooming activities that facilitate memory exploitation.
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N| Product | Affected Versions | Fixed Version |
|---|---|---|
json (gem) Ruby | >= 2.20.0, < 2.21.2 | 2.21.2 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-416 (Use After Free) |
| Attack Vector | Network |
| CVSS v4.0 | 8.7 (High) |
| Exploit Status | PoC (Proof of Concept) |
| Impact | Denial of Service (Process Crash) |
| Affected Gem Versions | >= 2.20.0, < 2.21.2 |
The product uses a pointer after it has been freed, which can lead to a crash, unexpected behavior, or execution of arbitrary code.
A DOM-based Cross-Site Scripting (XSS) vulnerability exists in the Laravel exception debug page rendering pipeline when APP_DEBUG=true is active. This flaw allows an attacker to execute arbitrary client-side JavaScript in the security context of an authenticated user's session when they hover over interactive code-trace tooltips handled by Tippy.js.
A critical denial-of-service vulnerability in the adm-zip npm package allows attackers to bypass decompression-bomb protections introduced in version 0.5.18. By declaring the uncompressed file size as exactly 0, an attacker can disable the maxOutputLength constraint in the Node.js zlib wrapper, leading to complete system memory exhaustion and process crashes.
An uncontrolled resource consumption vulnerability in the logs SDK of OpenTelemetry-Go allows remote attackers to trigger a denial of service. Under conditions of downstream exporter backpressure, the BatchingProcessor enters a tight loop, exhausting CPU resources. This occurs because the processor immediately schedules retry attempts without waiting for its ticker interval, spinning continuously when the internal queue remains filled above the batch size. The issue affects all versions prior to v0.21.0 of the go.opentelemetry.io/otel/sdk/log package.
CVE-2026-76844 is a high-severity path traversal vulnerability in webpack-dev-middleware affecting multiple version branches. It stems from an incomplete fix for CVE-2024-29180 when serving files via a physical filesystem with a non-slash-terminated publicPath configuration. Attackers can bypass directory validation to access files situated one level above the intended output directory.
A Server-Side Request Forgery (SSRF) vulnerability exists in FasterXML jackson-databind before versions 2.18.9, 2.21.5, 2.22.1, 3.1.5, and 3.2.1. The flaw occurs during the deserialization of java.net.InetAddress fields, where the library implicitly triggers eager DNS lookups. Unauthenticated remote attackers can exploit this behavior by passing arbitrary hostnames in JSON fields, forcing target servers to make outbound DNS lookup requests.
An insecure deserialization vulnerability exists in FasterXML jackson-databind due to improper validation of URI schemes when resolving java.nio.file.Path properties. When binding untrusted JSON input to a Path field, the deserializer resolves attacker-supplied URIs without restriction. If the scheme is unrecognized by the default filesystem, the application falls back to querying registered SPI FileSystemProvider instances, causing class loading and potential side effects in environments with custom providers.