Jul 16, 2026·7 min read·2 visits
A vulnerability in the Rust crate `serde_with` allows remote attackers to trigger process termination via an integer underflow or uncontrolled memory allocation when deserializing crafted payloads.
A Denial of Service (DoS) vulnerability in the Rust crate `serde_with` arises from an integer underflow and uncontrolled memory allocation during the processing of empty collections using the `KeyValueMap` helper. Depending on the build profile, this flaw leads to an immediate thread panic (debug) or process abort due to an out-of-memory condition (release).
The Rust crate serde_with provides custom de/serialization helpers for use with the serde framework. Among these helpers is the KeyValueMap attribute, which allows developers to serialize map-like structures as sequences of key-value tuples or structures. This normalization is frequently employed to guarantee stable output ordering or to adapt data layouts to specific external API formats.
The vulnerability exists in the serialization and deserialization implementations of the KeyValueMap helper. When processing structured input, the helper calculates the initial memory allocation needed to hold deserialized collections based on size hints or actual item counts. Under specific conditions, this preallocation logic fails to handle small or empty inputs safely.
The vulnerability exhibits two primary behaviors based on the target compilation profile. In debug profiles, the application panics immediately upon receiving an empty sequence due to an integer underflow check. In release profiles, where overflow checking is disabled by default, the underflow results in an extremely large value that is passed directly to the memory allocator, causing a fatal process abort.
The root cause of the vulnerability resides in the preallocation arithmetic within serde_with/src/key_value_map.rs. When preparing to serialize or deserialize collections using KeyValueMap, the crate obtains a length hint len to optimize vector instantiation. To compensate for structural boundaries, the code calculated the capacity using the expression len - 1.
When an application processes an empty collection where len evaluates to 0, the calculation 0 - 1 is performed. Because len is of type usize (an unsigned integer matching the target architecture pointer size), subtracting one from zero results in an arithmetic underflow. The consequences of this underflow diverge depending on the compilation profile configured for the target binary.
Under a debug profile, the Rust compiler emits code with explicit integer overflow and underflow checks enabled. Consequently, the calculation of 0 - 1 triggers an immediate panic, causing the execution thread to terminate with an attempt to subtract with overflow error. If the thread is not isolated, or if the panic occurs in a critical processing thread, this can degrade application stability.
Under a release profile, where compiler-inserted arithmetic boundary checks are omitted by default, the unsigned subtraction wraps around. On standard 64-bit platforms, 0 - 1 wraps to the maximum value representable by usize, which is usize::MAX or 18,446,744,073,709,551,615. This value is then directly supplied to the standard library function Vec::with_capacity(), forcing the allocator to attempt an immediate multi-exabyte allocation. The system allocator fails to satisfy this request, triggering a fatal out-of-memory (OOM) runtime abort that kills the entire process.
To trace the vulnerable execution path, consider the code pattern present in serde_with/src/key_value_map.rs prior to the fix. The preallocation logic did not validate whether the incoming length hint was zero before subtracting:
// Vulnerable implementation
content: Vec::with_capacity(len.unwrap_or(17) - 1),If len evaluates to 0, the subtraction underflows immediately. The official fix resolved this by replacing the subtraction operator with the saturating_sub method, ensuring that subtraction below the zero boundary simply returns 0:
// Patched implementation
content: utils::vec_with_capacity_cautious(Some(len.unwrap_or(17).saturating_sub(1))),In addition to correcting the underflow, the maintainers introduced a cautious vector allocation utility vec_with_capacity_cautious in serde_with/src/utils.rs. This function utilizes a capping mechanism to restrict preallocations to a reasonable, safe threshold. This protection prevents malicious actors from spoofing very large size hints in JSON or binary input streams to cause high memory allocation spikes:
pub(crate) fn size_hint_cautious<Element>(hint: Option<usize>) -> usize {
const MAX_PREALLOCATION: usize = 4096;
let value = hint.unwrap_or(0);
if value > MAX_PREALLOCATION {
MAX_PREALLOCATION
} else {
value
}
}
#[cfg(feature = "alloc")]
#[inline]
pub(crate) fn vec_with_capacity_cautious<Element>(hint: Option<usize>) -> Vec<Element> {
Vec::with_capacity(size_hint_cautious::<Element>(hint))
}To enforce this safe allocation pattern across the codebase, a Clippy lint configuration was added to disallow future direct usage of standard Vec::with_capacity calls. While this static analysis check prevents new direct calls on vectors, other collection types, such as custom maps or string buffers, are not automatically covered by the rule and require manual validation during ongoing development.
Exploitation of this vulnerability is straightforward and requires no prior authentication or administrative privileges. It is sufficient for an attacker to locate an endpoint that accepts external input and deserializes it into a Rust structure utilizing the #[serde_as(as = "KeyValueMap")] attribute.
The following proof-of-concept demonstrates the vulnerability. By feeding an empty JSON array payload {"settings": []} to the vulnerable deserializer, the system crashes immediately upon processing:
use serde::{Serialize, Deserialize};
use serde_with::{serde_as, KeyValueMap};
#[serde_as]
#[derive(Debug, Serialize, Deserialize)]
struct UserConfig {
#[serde_as(as = "KeyValueMap")]
pub settings: Vec<(String, String)>,
}
fn main() {
let payload = r#"{"settings": []}"#;
let config: UserConfig = serde_json::from_str(payload).unwrap();
println!("Serializing empty configuration...");
let _serialized = serde_json::to_string(&config).unwrap();
}When running this program in debug mode, the output shows a clean underflow panic. However, running the program with the --release flag yields a fatal allocator crash, terminating the process without leaving a standard Rust backtrace:
fatal runtime error: allocator memory exhausted
Aborted (core dumped)The following flowchart outlines the entire decision tree and execution path from initial payload submission down to the final system crash:
The security impact of GHSA-7GCF-G7XR-8HXJ is rated High, with an estimated CVSS score of 7.5. The primary security consequence is a complete loss of service availability. An unauthenticated attacker can remotely crash any application parsing crafted payloads containing empty maps or custom size metadata.
Because deserialization is often performed early in the request lifecycle (frequently before routing, authentication checks, or payload signature validation), this vulnerability presents a highly accessible vector for Denial of Service. In a typical web service architecture, a single malformed request can terminate the entire backend worker process.
If the hosting application is configured with a process supervisor (such as systemd or Kubernetes pod managers) that restarts the application upon failure, repeated exploit payloads can still induce a persistent crash loop. This constant process teardown and initialization cycle degrades server CPU resources, exhausts networking connection queues, and renders the service permanently unavailable to legitimate users.
The recommended remediation path is to upgrade the serde_with dependency to version 3.21.0 or higher. This update replaces unsafe direct subtractions with saturating operations and clamps vector preallocations to a safe upper bound. Update the project dependency inside Cargo.toml:
[dependencies]
serde_with = "3.21.0"To identify vulnerable versions of serde_with in your deployment pipeline or local dependency tree, execute cargo audit or inspect the generated Cargo.lock file. These static analysis checks will identify if any transitive dependencies pull in vulnerable versions of the crate.
For environments where upgrading is not immediately possible, implement input size filtering at the reverse proxy or Web Application Firewall (WAF) layer. Restricting the maximum payload length can prevent large or deep nested JSON trees from reaching the parsing logic, although it does not fully mitigate crashes caused by simple empty arrays.
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H| Product | Affected Versions | Fixed Version |
|---|---|---|
serde_with jonasbb | < 3.21.0 | 3.21.0 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-191, CWE-400, CWE-248 |
| Attack Vector | Network |
| CVSS v3.1 Score | 7.5 (High) |
| Impact | Denial of Service (DoS) |
| Exploit Status | Proof-of-Concept (PoC) |
| KEV Status | Not Listed |
| Ransomware Use | No |
The product subtracts one value from another, causing an underflow and wrapping around to a very large positive number when using unsigned integers.
A high-severity vulnerability exists in the adawolfa/isdoc PHP library before versions 1.4.1 and 2.0.0. The library fails to restrict or validate the sizes of files extracted from untrusted Zip archives (.isdocx container files) or PDF embedded streams. This allows remote attackers to perform decompression bomb attacks, causing denial of service via memory or disk exhaustion.
A critical remote code execution vulnerability exists in django-haystack prior to version 3.4.0. The vulnerability stems from the Elasticsearch 1.x search backend incorrectly processing aliased search result fields, leading to the unsafe execution of user-supplied strings using Python's built-in eval() function.
The obsidian-local-rest-api plugin prior to version 4.1.3 is vulnerable to an authenticated path traversal flaw in its /vault/{path} endpoints. An authenticated attacker can bypass the vault root boundary using URL-encoded directory traversal sequences to perform unauthorized operations on the host filesystem.
An authenticated Server-Side Request Forgery (SSRF) vulnerability in Koel, an open-source personal music streaming server, allows remote attackers to probe internal hosts and loopback addresses. The vulnerability arises due to a missing 'bail' validation rule in the Laravel-based form validation pipeline, which permits downstream HTTP checks to execute even after a URL has failed security verification.
A Server-Side Request Forgery (SSRF) vulnerability in the open-source personal music streaming server Koel allows authenticated Subsonic API users to perform unauthorized network queries. This flaw resides in both the Subsonic podcast feed import routine and the subsequent redirect handling inside the podcast streaming helper, exposing private local networks and internal loopback systems to unauthorized reconnaissance and interaction.
CVE-2026-50661 is a security feature bypass vulnerability in Microsoft Windows BitLocker full-disk encryption. A physical attacker can exploit this flaw to bypass encryption controls, permitting unauthorized access to sensitive local storage and the modification of offline system configurations.