CVE-2024-58264
Description
The serde-json-wasm crate before 1.0.1 for Rust allows stack consumption via deeply nested JSON data.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
serde-json-wasm before 1.0.1 and 0.5.2 allows stack overflow via deeply nested JSON due to missing recursion limit.
Vulnerability
Description
The serde-json-wasm crate for Rust, used for JSON serialization/deserialization in WebAssembly environments, is vulnerable to stack consumption caused by unbounded recursion. Prior to versions 1.0.1 and 0.5.2, the deserializer did not enforce a recursion depth limit. An attacker could craft a deeply nested JSON structure (e.g., arrays containing arrays) that triggers recursive parsing, leading to excessive stack usage and a potential stack overflow [1][2].
Exploitation
Exploitation requires the ability to supply a JSON input to an application that uses the vulnerable crate. No authentication or special network position is needed beyond the ability to send the malicious payload. The attack is trivial to execute by simply providing a JSON document with deeply nested objects or arrays (e.g., thousands of levels of nesting). The deserializer's recursive descent parser will recurse until the call stack is exhausted, causing a crash or denial of service [1][2].
Impact
Successful exploitation results in a denial of service (DoS) condition, as the application process terminates due to stack overflow. In environments where robustness is critical (e.g., smart contract execution in CosmWasm), this could disrupt service availability or be used to crash validator nodes. The vulnerability does not allow arbitrary code execution but can be used to repeatedly crash services [1][2].
Mitigation
The issue is fixed in versions 1.0.1 and 0.5.2 of the crate. The fix introduces a recursion limit of 128 and adds a new Error::RecursionLimitExceeded variant. Users should update to the latest patched version. No workarounds are available other than upgrading [1][2]. The CVE is not known to be on the CISA KEV list as of publication.
AI Insight generated on May 19, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
serde-json-wasmcrates.io | >= 1.0.0, < 1.0.1 | 1.0.1 |
serde-json-wasmcrates.io | < 0.5.2 | 0.5.2 |
Affected products
2- Range: <1.0.1
- CosmWasm/serde-json-wasmv5Range: 0
Patches
2a9a9b9bf2438Add recursion limit
5 files changed · +78 −14
Cargo.lock+1 −1 modified@@ -40,7 +40,7 @@ checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789" [[package]] name = "serde-json-wasm" -version = "0.5.1" +version = "0.5.2" dependencies = [ "serde", "serde_derive",
Cargo.toml+1 −1 modified@@ -15,7 +15,7 @@ license = "MIT OR Apache-2.0" name = "serde-json-wasm" readme = "README.md" repository = "https://github.com/CosmWasm/serde-json-wasm" -version = "0.5.1" +version = "0.5.2" exclude = [ ".cargo/", ".github/",
src/de/errors.rs+4 −0 modified@@ -68,6 +68,9 @@ pub enum Error { /// JSON has a comma after the last value in an array or map. TrailingComma, + /// JSON is nested too deeply, exceeeded the recursion limit. + RecursionLimitExceeded, + /// Custom error message from serde Custom(String), } @@ -132,6 +135,7 @@ impl fmt::Display for Error { value." } Error::TrailingComma => "JSON has a comma after the last value in an array or map.", + Error::RecursionLimitExceeded => "JSON is nested too deeply, exceeeded the recursion limit.", Error::Custom(msg) => msg, } )
src/de/mod.rs+47 −12 modified@@ -20,6 +20,9 @@ use std::str::from_utf8; pub struct Deserializer<'b> { slice: &'b [u8], index: usize, + + /// Remaining depth until we hit the recursion limit + remaining_depth: u8, } enum StringLike<'a> { @@ -29,7 +32,11 @@ enum StringLike<'a> { impl<'a> Deserializer<'a> { fn new(slice: &'a [u8]) -> Deserializer<'_> { - Deserializer { slice, index: 0 } + Deserializer { + slice, + index: 0, + remaining_depth: 128, + } } fn eat_char(&mut self) { @@ -286,16 +293,22 @@ impl<'a, 'de> de::Deserializer<'de> for &'a mut Deserializer<'de> { } } b'[' => { - self.eat_char(); - let ret = visitor.visit_seq(SeqAccess::new(self))?; + check_recursion! { + self.eat_char(); + let ret = visitor.visit_seq(SeqAccess::new(self)); + } + let ret = ret?; self.end_seq()?; Ok(ret) } b'{' => { - self.eat_char(); - let ret = visitor.visit_map(MapAccess::new(self))?; + check_recursion! { + self.eat_char(); + let ret = visitor.visit_map(MapAccess::new(self)); + } + let ret = ret?; self.end_map()?; @@ -548,8 +561,11 @@ impl<'a, 'de> de::Deserializer<'de> for &'a mut Deserializer<'de> { { match self.parse_whitespace().ok_or(Error::EofWhileParsingValue)? { b'[' => { - self.eat_char(); - let ret = visitor.visit_seq(SeqAccess::new(self))?; + check_recursion! { + self.eat_char(); + let ret = visitor.visit_seq(SeqAccess::new(self)); + } + let ret = ret?; self.end_seq()?; @@ -585,9 +601,11 @@ impl<'a, 'de> de::Deserializer<'de> for &'a mut Deserializer<'de> { let peek = self.parse_whitespace().ok_or(Error::EofWhileParsingValue)?; if peek == b'{' { - self.eat_char(); - - let ret = visitor.visit_map(MapAccess::new(self))?; + check_recursion! { + self.eat_char(); + let ret = visitor.visit_map(MapAccess::new(self)); + } + let ret = ret?; self.end_map()?; @@ -623,8 +641,11 @@ impl<'a, 'de> de::Deserializer<'de> for &'a mut Deserializer<'de> { b'"' => visitor.visit_enum(UnitVariantAccess::new(self)), // if it is a struct enum b'{' => { - self.eat_char(); - visitor.visit_enum(StructVariantAccess::new(self)) + check_recursion! { + self.eat_char(); + let value = visitor.visit_enum(StructVariantAccess::new(self)); + } + value } _ => Err(Error::ExpectedSomeIdent), } @@ -684,6 +705,20 @@ where from_slice(s.as_bytes()) } +macro_rules! check_recursion { + ($this:ident $($body:tt)*) => { + $this.remaining_depth -= 1; + if $this.remaining_depth == 0 { + return Err($crate::de::Error::RecursionLimitExceeded); + } + + $this $($body)* + + $this.remaining_depth += 1; + }; +} +pub(crate) use check_recursion; + #[cfg(test)] mod tests { use super::from_str;
src/lib.rs+25 −0 modified@@ -214,4 +214,29 @@ mod test { item ); } + + #[test] + fn no_stack_overflow() { + const AMOUNT: usize = 2000; + let mut json = String::from(r#"{"":"#); + + #[derive(Debug, Deserialize, Serialize)] + pub struct Person { + name: String, + age: u8, + phones: Vec<String>, + } + + for _ in 0..AMOUNT { + json.push('['); + } + for _ in 0..AMOUNT { + json.push(']'); + } + + json.push_str(r#"] }[[[[[[[[[[[[[[[[[[[[[ ""","age":35,"phones":["#); + + let err = from_str::<Person>(&json).unwrap_err(); + assert_eq!(err, crate::de::Error::RecursionLimitExceeded); + } }
e78f9e28b3a2Add recursion limit
5 files changed · +78 −14
Cargo.lock+1 −1 modified@@ -40,7 +40,7 @@ checksum = "6d3e73c93c3240c0bda063c239298e633114c69a888c3e37ca8bb33f343e9890" [[package]] name = "serde-json-wasm" -version = "1.0.0" +version = "1.0.1" dependencies = [ "serde", "serde_derive",
Cargo.toml+1 −1 modified@@ -15,7 +15,7 @@ license = "MIT OR Apache-2.0" name = "serde-json-wasm" readme = "README.md" repository = "https://github.com/CosmWasm/serde-json-wasm" -version = "1.0.0" +version = "1.0.1" exclude = [ ".cargo/", ".github/",
src/de/errors.rs+4 −0 modified@@ -75,6 +75,9 @@ pub enum Error { /// JSON has a comma after the last value in an array or map. TrailingComma, + /// JSON is nested too deeply, exceeeded the recursion limit. + RecursionLimitExceeded, + /// Custom error message from serde Custom(String), } @@ -128,6 +131,7 @@ impl core::fmt::Display for Error { value." } Error::TrailingComma => "JSON has a comma after the last value in an array or map.", + Error::RecursionLimitExceeded => "JSON is nested too deeply, exceeeded the recursion limit.", Error::Custom(msg) => msg, } )
src/de/mod.rs+47 −12 modified@@ -21,6 +21,9 @@ use self::seq::SeqAccess; pub struct Deserializer<'b> { slice: &'b [u8], index: usize, + + /// Remaining depth until we hit the recursion limit + remaining_depth: u8, } enum StringLike<'a> { @@ -30,7 +33,11 @@ enum StringLike<'a> { impl<'a> Deserializer<'a> { fn new(slice: &'a [u8]) -> Deserializer<'_> { - Deserializer { slice, index: 0 } + Deserializer { + slice, + index: 0, + remaining_depth: 128, + } } fn eat_char(&mut self) { @@ -287,16 +294,22 @@ impl<'a, 'de> de::Deserializer<'de> for &'a mut Deserializer<'de> { } } b'[' => { - self.eat_char(); - let ret = visitor.visit_seq(SeqAccess::new(self))?; + check_recursion! { + self.eat_char(); + let ret = visitor.visit_seq(SeqAccess::new(self)); + } + let ret = ret?; self.end_seq()?; Ok(ret) } b'{' => { - self.eat_char(); - let ret = visitor.visit_map(MapAccess::new(self))?; + check_recursion! { + self.eat_char(); + let ret = visitor.visit_map(MapAccess::new(self)); + } + let ret = ret?; self.end_map()?; @@ -513,8 +526,11 @@ impl<'a, 'de> de::Deserializer<'de> for &'a mut Deserializer<'de> { { match self.parse_whitespace().ok_or(Error::EofWhileParsingValue)? { b'[' => { - self.eat_char(); - let ret = visitor.visit_seq(SeqAccess::new(self))?; + check_recursion! { + self.eat_char(); + let ret = visitor.visit_seq(SeqAccess::new(self)); + } + let ret = ret?; self.end_seq()?; @@ -550,9 +566,11 @@ impl<'a, 'de> de::Deserializer<'de> for &'a mut Deserializer<'de> { let peek = self.parse_whitespace().ok_or(Error::EofWhileParsingValue)?; if peek == b'{' { - self.eat_char(); - - let ret = visitor.visit_map(MapAccess::new(self))?; + check_recursion! { + self.eat_char(); + let ret = visitor.visit_map(MapAccess::new(self)); + } + let ret = ret?; self.end_map()?; @@ -588,8 +606,11 @@ impl<'a, 'de> de::Deserializer<'de> for &'a mut Deserializer<'de> { b'"' => visitor.visit_enum(UnitVariantAccess::new(self)), // if it is a struct enum b'{' => { - self.eat_char(); - visitor.visit_enum(StructVariantAccess::new(self)) + check_recursion! { + self.eat_char(); + let value = visitor.visit_enum(StructVariantAccess::new(self)); + } + value } _ => Err(Error::ExpectedSomeIdent), } @@ -649,6 +670,20 @@ where from_slice(s.as_bytes()) } +macro_rules! check_recursion { + ($this:ident $($body:tt)*) => { + $this.remaining_depth -= 1; + if $this.remaining_depth == 0 { + return Err($crate::de::Error::RecursionLimitExceeded); + } + + $this $($body)* + + $this.remaining_depth += 1; + }; +} +pub(crate) use check_recursion; + #[cfg(test)] mod tests { use super::from_str;
src/lib.rs+25 −0 modified@@ -227,4 +227,29 @@ mod test { item ); } + + #[test] + fn no_stack_overflow() { + const AMOUNT: usize = 2000; + let mut json = String::from(r#"{"":"#); + + #[derive(Debug, Deserialize, Serialize)] + pub struct Person { + name: String, + age: u8, + phones: Vec<String>, + } + + for _ in 0..AMOUNT { + json.push('['); + } + for _ in 0..AMOUNT { + json.push(']'); + } + + json.push_str(r#"] }[[[[[[[[[[[[[[[[[[[[[ ""","age":35,"phones":["#); + + let err = from_str::<Person>(&json).unwrap_err(); + assert_eq!(err, crate::de::Error::RecursionLimitExceeded); + } }
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
6- github.com/advisories/GHSA-rr69-rxr6-8qwfghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2024-58264ghsaADVISORY
- github.com/CosmWasm/serde-json-wasm/commit/a9a9b9bf243862bd2afbf6853fca97f30dc4f620ghsaWEB
- github.com/CosmWasm/serde-json-wasm/commit/e78f9e28b3a2151d3175ee88ab2a001bf9515429ghsaWEB
- rustsec.org/advisories/RUSTSEC-2024-0012.htmlghsaWEB
- crates.io/crates/serde-json-wasmmitre
News mentions
0No linked articles in our index yet.