Jul 23, 2026·6 min read·3 visits
A dynamic buffer capacity calculation error in the Ruby json gem's C extension allows attackers to trigger a heap-based buffer overflow and crash the application by sending strings close to 16 KB during IO-based streaming serialization.
A heap-based buffer overflow vulnerability exists in the native C extension of the Ruby json gem (versions 2.9.0 through 2.19.8) during IO-based streaming serialization. An incorrect buffer size calculation can lead to memory corruption and process termination when processing large strings.
The Ruby json gem relies on a native C extension for performance-critical serialization operations. Within this extension, the library manages memory through a custom dynamic buffer structure named FBuffer. This structure is designed to aggregate small chunks of serialized JSON output before writing them to an IO stream, minimizing system call overhead during serialization tasks.
This dynamic buffering process exposes an attack surface when applications stream untrusted input or serialize database fields containing user-provided strings. The vulnerability manifests when the serialized payload contains a string element close to the internal buffer's threshold, specifically near 16 KB.
The underlying flaw is a heap-based buffer overflow (CWE-122) arising from a size calculation failure (CWE-131). When triggered, the vulnerability results in memory corruption within the application heap. Under standard runtime conditions, this corruption causes immediate process termination, leading to a Denial of Service.
The technical root cause of CVE-2026-54696 lies within the capacity management logic defined in ext/json/ext/fbuffer/fbuffer.h. Specifically, the function fbuffer_do_inc_capa(FBuffer *fb, size_t requested) ensures that the buffer has sufficient space to hold an upcoming write of size requested. When writing to an IO destination, the generator flushes the existing buffer when its capacity exceeds FBUFFER_IO_BUFFER_SIZE (16 KB) and resets the length.
The logical error occurs during the validation check executed immediately after checking the IO stream status. The vulnerable code executes a conditional comparison: if (RB_LIKELY(requested < fb->capa)) { return; }. This statement assumes that if the incoming request size is smaller than the total capacity of the buffer, the data will fit safely within the remaining buffer limits.
This assumption is flawed because it ignores the current utilization of the buffer, represented by fb->len. If the buffer already contains serialized data (fb->len > 0), the actual available capacity is the total capacity minus the written length (fb->capa - fb->len). If the requested write size is smaller than fb->capa but larger than the remaining free space, the function returns prematurely without expanding the buffer or flushing the existing data.
The vulnerability can be traced by analyzing the vulnerable implementation of the dynamic capacity adjustment function. Below is the vulnerable code segment:
static void fbuffer_do_inc_capa(FBuffer *fb, size_t requested)
{
if (RB_UNLIKELY(fb->io)) {
if (fb->capa < FBUFFER_IO_BUFFER_SIZE) {
fbuffer_realloc(fb, FBUFFER_IO_BUFFER_SIZE);
} else {
fbuffer_flush(fb);
}
// VULNERABLE CHECK: Only compares requested size against total capacity
// Ignores the remaining space available (fb->capa - fb->len)
if (RB_LIKELY(requested < fb->capa)) {
return;
}
}
// ... standard resizing code ...
}The remediation commit (996bac686d64e4e3aaeae03b14a7f9ee9695ebdb) refactors this logic to ensure that memory is flushed cleanly and capacity calculations are robust. The patched version operates as follows:
static void fbuffer_do_inc_capa(FBuffer *fb, size_t requested)
{
if (RB_UNLIKELY(fb->io)) {
if (fb->capa != 0) {
fbuffer_flush(fb); // Unconditionally flush existing buffer contents
if (RB_LIKELY(requested < fb->capa)) {
return; // Safe because fb->len is now guaranteed to be 0
}
}
}
size_t new_capa = fb->capa ? fb->capa : fb->initial_length;
size_t needed_capa = requested + fb->len; // Correctly includes existing length
while (new_capa < needed_capa) {
new_capa *= 2;
}
fbuffer_realloc(fb, new_capa);
}By ensuring that fbuffer_flush is executed and sets fb->len to 0, the check requested < fb->capa becomes safe. Additionally, the subsequent logic calculates needed_capa using the sum of the requested size and the current length, preventing sizing omissions during non-IO operations.
Exploitation of CVE-2026-54696 requires specific operational prerequisites. The target application must serialize data directly to an IO object, such as a file descriptor, network socket, or StringIO wrapper, using the native C extension engine. This pattern is commonly implemented via JSON.dump(obj, io) or when setting up custom generator states with stream destinations.
The attacker must supply a payload string that is processed and serialized by the application. To trigger the vulnerability, the payload must contain a string element designed to match the boundary state of the dynamic buffer. Specifically, the string must be large enough to trigger the capacity calculation mismatch (near 16 KB) without triggering an automatic flush via alternative logic blocks.
When the serialization engine invokes the flawed check, the capacity validation logic returns without resizing the heap allocation. The subsequent copy instruction executes a MEMCPY operation that writes beyond the bounds of the allocated buffer. This action corrupts adjacent heap metadata, typically causing the interpreter process to abort immediately.
The primary security impact of this vulnerability is a Denial of Service (DoS) condition. Because the memory corruption occurs on the heap, the Ruby interpreter's internal state becomes unstable, leading to an immediate crash. In web applications or background workers processing queue messages, repeated exploit attempts can result in a complete disruption of service availability.
The CVSS v3.1 base score is 3.7 (Low Severity), reflecting the high complexity of the attack vector and the restricted impact. The vector string is CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:L. This indicates that while the vulnerability can be reached remotely without authentication, triggering the precise memory layout conditions requires specific configurations and execution paths.
At present, there are no weaponized public exploits or documented instances of active exploitation in the wild. The vulnerability does not appear in the CISA Known Exploited Vulnerabilities (KEV) catalog, and its Exploit Prediction Scoring System (EPSS) score remains low at 0.00301 (22.29th percentile), indicating a low probability of imminent widespread exploitation.
The definitive mitigation is upgrading the json gem to version 2.19.9 or higher. This release integrates the corrected capacity check implementation and enforces clean buffer flushes. Systems relying on vendored Ruby gems or pre-packaged distribution libraries should verify their package configurations to ensure the patched versions are utilized.
When immediate patching of the native extension is not feasible due to environment constraints, applications can apply a temporary code-level workaround. By serializing the target object entirely in memory before writing it to the IO destination, developers can bypass the vulnerable streaming code path.
# Vulnerable dynamic streaming pattern
JSON.dump(user_data, io_stream)
# Temporary mitigated pattern (bypasses dynamic streaming buffer)
serialized_json = JSON.fast_generate(user_data)
io_stream.write(serialized_json)This workaround shifts the serialization burden to the standard Ruby virtual machine heap allocation, which does not utilize the vulnerable FBuffer IO-stream capacity management code. However, this may result in elevated transient memory utilization for very large objects.
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:L| Product | Affected Versions | Fixed Version |
|---|---|---|
json gem Ruby | >= 2.9.0, <= 2.19.8 | 2.19.9 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-122 |
| Attack Vector | Network (AV:N) |
| CVSS v3.1 Score | 3.7 |
| EPSS Score | 0.00301 (22.29th percentile) |
| Impact | Low (Denial of Service via process crash) |
| Exploit Status | None |
| KEV Status | Not Listed |
A heap-based buffer overflow condition is a buffer overflow where the buffer that can be overwritten is allocated in the heap portion of memory, which generally means that the buffer was allocated using a function such as malloc().
An uncontrolled memory allocation vulnerability (CWE-789) exists in pypdf prior to version 6.14.0. The library blindly trusted user-controlled image dimensions (/Width and /Height) from PDF metadata, allowing attackers to trigger physical memory exhaustion and an Out-of-Memory crash via tiny, malformed files.
An infinite loop vulnerability exists in the pure-python PDF library pypdf prior to version 6.14.1. This vulnerability occurs during the processing of malformed or truncated page content streams containing inline images. Specifically, the parser fails to validate the End-of-Stream condition during inline image dictionary parsing. This results in continuous backward stream-seeking and an infinite loop that consumes 100% CPU on the processing thread.
An infinite loop vulnerability exists in the pypdf library prior to version 6.14.2 when processing malformed PDF inline images that utilize ASCII85 or ASCIIHex decoders. Attackers can exploit this vulnerability by submitting crafted PDF files containing incomplete streams, causing the parsing thread to consume 100% CPU resource indefinitely and leading to Denial of Service.
A critical CPU exhaustion vulnerability exists in the pypdf library before version 6.14.0. When parsing PDF files with malformed or corrupt standard cross-reference (xref) table entries, the library falls back to sequentially scanning the entire file buffer via regular expression search. An attacker can exploit this algorithmic bottleneck by supplying a crafted PDF with numerous malformed xref entries, leading to denial of service.
In Auth.js (specifically next-auth version 5), server-side configuration errors or backend exceptions can cause existence-based authentication and authorization checks to fail open. When the underlying core engine (@auth/core) encounters a configuration error or runtime exception, it returns an HTTP 500 Internal Server Error response containing a JSON-formatted error description payload. Prior to the fix, the next-auth wrappers read this response body using response.json() without validating the HTTP status code. Because any parsed JavaScript object evaluates to truthy, common existence-based checks incorrectly evaluated to true, letting unauthenticated requests pass.
A host/authority desynchronization vulnerability exists in Eclipse Jetty's handling of HTTP/2 and HTTP/3 requests. When both an ':authority' pseudo-header and a standard 'Host' header are present in a request, Jetty fails to validate that they represent the same target host. This desynchronization creates a host-confusion or 'split-brain' state, enabling attackers to bypass access control lists, escape virtual host isolation, poison web caches, or manipulate redirect targets in backend applications.