Skip to content

Commit a9a9b9b

Browse files
committed
Add recursion limit
1 parent be32160 commit a9a9b9b

File tree

5 files changed

+78
-14
lines changed

5 files changed

+78
-14
lines changed

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ license = "MIT OR Apache-2.0"
1515
name = "serde-json-wasm"
1616
readme = "README.md"
1717
repository = "https://github.com/CosmWasm/serde-json-wasm"
18-
version = "0.5.1"
18+
version = "0.5.2"
1919
exclude = [
2020
".cargo/",
2121
".github/",

src/de/errors.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,9 @@ pub enum Error {
6868
/// JSON has a comma after the last value in an array or map.
6969
TrailingComma,
7070

71+
/// JSON is nested too deeply, exceeeded the recursion limit.
72+
RecursionLimitExceeded,
73+
7174
/// Custom error message from serde
7275
Custom(String),
7376
}
@@ -132,6 +135,7 @@ impl fmt::Display for Error {
132135
value."
133136
}
134137
Error::TrailingComma => "JSON has a comma after the last value in an array or map.",
138+
Error::RecursionLimitExceeded => "JSON is nested too deeply, exceeeded the recursion limit.",
135139
Error::Custom(msg) => msg,
136140
}
137141
)

src/de/mod.rs

Lines changed: 47 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ use std::str::from_utf8;
2020
pub struct Deserializer<'b> {
2121
slice: &'b [u8],
2222
index: usize,
23+
24+
/// Remaining depth until we hit the recursion limit
25+
remaining_depth: u8,
2326
}
2427

2528
enum StringLike<'a> {
@@ -29,7 +32,11 @@ enum StringLike<'a> {
2932

3033
impl<'a> Deserializer<'a> {
3134
fn new(slice: &'a [u8]) -> Deserializer<'_> {
32-
Deserializer { slice, index: 0 }
35+
Deserializer {
36+
slice,
37+
index: 0,
38+
remaining_depth: 128,
39+
}
3340
}
3441

3542
fn eat_char(&mut self) {
@@ -286,16 +293,22 @@ impl<'a, 'de> de::Deserializer<'de> for &'a mut Deserializer<'de> {
286293
}
287294
}
288295
b'[' => {
289-
self.eat_char();
290-
let ret = visitor.visit_seq(SeqAccess::new(self))?;
296+
check_recursion! {
297+
self.eat_char();
298+
let ret = visitor.visit_seq(SeqAccess::new(self));
299+
}
300+
let ret = ret?;
291301

292302
self.end_seq()?;
293303

294304
Ok(ret)
295305
}
296306
b'{' => {
297-
self.eat_char();
298-
let ret = visitor.visit_map(MapAccess::new(self))?;
307+
check_recursion! {
308+
self.eat_char();
309+
let ret = visitor.visit_map(MapAccess::new(self));
310+
}
311+
let ret = ret?;
299312

300313
self.end_map()?;
301314

@@ -548,8 +561,11 @@ impl<'a, 'de> de::Deserializer<'de> for &'a mut Deserializer<'de> {
548561
{
549562
match self.parse_whitespace().ok_or(Error::EofWhileParsingValue)? {
550563
b'[' => {
551-
self.eat_char();
552-
let ret = visitor.visit_seq(SeqAccess::new(self))?;
564+
check_recursion! {
565+
self.eat_char();
566+
let ret = visitor.visit_seq(SeqAccess::new(self));
567+
}
568+
let ret = ret?;
553569

554570
self.end_seq()?;
555571

@@ -585,9 +601,11 @@ impl<'a, 'de> de::Deserializer<'de> for &'a mut Deserializer<'de> {
585601
let peek = self.parse_whitespace().ok_or(Error::EofWhileParsingValue)?;
586602

587603
if peek == b'{' {
588-
self.eat_char();
589-
590-
let ret = visitor.visit_map(MapAccess::new(self))?;
604+
check_recursion! {
605+
self.eat_char();
606+
let ret = visitor.visit_map(MapAccess::new(self));
607+
}
608+
let ret = ret?;
591609

592610
self.end_map()?;
593611

@@ -623,8 +641,11 @@ impl<'a, 'de> de::Deserializer<'de> for &'a mut Deserializer<'de> {
623641
b'"' => visitor.visit_enum(UnitVariantAccess::new(self)),
624642
// if it is a struct enum
625643
b'{' => {
626-
self.eat_char();
627-
visitor.visit_enum(StructVariantAccess::new(self))
644+
check_recursion! {
645+
self.eat_char();
646+
let value = visitor.visit_enum(StructVariantAccess::new(self));
647+
}
648+
value
628649
}
629650
_ => Err(Error::ExpectedSomeIdent),
630651
}
@@ -684,6 +705,20 @@ where
684705
from_slice(s.as_bytes())
685706
}
686707

708+
macro_rules! check_recursion {
709+
($this:ident $($body:tt)*) => {
710+
$this.remaining_depth -= 1;
711+
if $this.remaining_depth == 0 {
712+
return Err($crate::de::Error::RecursionLimitExceeded);
713+
}
714+
715+
$this $($body)*
716+
717+
$this.remaining_depth += 1;
718+
};
719+
}
720+
pub(crate) use check_recursion;
721+
687722
#[cfg(test)]
688723
mod tests {
689724
use super::from_str;

src/lib.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,4 +214,29 @@ mod test {
214214
item
215215
);
216216
}
217+
218+
#[test]
219+
fn no_stack_overflow() {
220+
const AMOUNT: usize = 2000;
221+
let mut json = String::from(r#"{"":"#);
222+
223+
#[derive(Debug, Deserialize, Serialize)]
224+
pub struct Person {
225+
name: String,
226+
age: u8,
227+
phones: Vec<String>,
228+
}
229+
230+
for _ in 0..AMOUNT {
231+
json.push('[');
232+
}
233+
for _ in 0..AMOUNT {
234+
json.push(']');
235+
}
236+
237+
json.push_str(r#"] }[[[[[[[[[[[[[[[[[[[[[ ""","age":35,"phones":["#);
238+
239+
let err = from_str::<Person>(&json).unwrap_err();
240+
assert_eq!(err, crate::de::Error::RecursionLimitExceeded);
241+
}
217242
}

0 commit comments

Comments
 (0)