Skip to content

Commit

Permalink
Add recursion limit
Browse files Browse the repository at this point in the history
  • Loading branch information
chipshort committed Jan 23, 2024
1 parent be32160 commit a9a9b9b
Show file tree
Hide file tree
Showing 5 changed files with 78 additions and 14 deletions.
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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/",
Expand Down
4 changes: 4 additions & 0 deletions src/de/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
}
Expand Down Expand Up @@ -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,
}
)
Expand Down
59 changes: 47 additions & 12 deletions src/de/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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> {
Expand All @@ -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) {
Expand Down Expand Up @@ -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()?;

Expand Down Expand Up @@ -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()?;

Expand Down Expand Up @@ -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()?;

Expand Down Expand Up @@ -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),
}
Expand Down Expand Up @@ -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;
Expand Down
25 changes: 25 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}

0 comments on commit a9a9b9b

Please sign in to comment.