Skip to content

Commit ff265a5

Browse files
authored
Merge pull request #65 from CosmWasm/recursion-limit
Add recursion limit for 1.0.1
2 parents 05c08e6 + 249825a commit ff265a5

File tree

5 files changed

+78
-14
lines changed

5 files changed

+78
-14
lines changed

Cargo.lock

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+1-1
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 = "1.0.0"
18+
version = "1.0.1"
1919
exclude = [
2020
".cargo/",
2121
".github/",

src/de/errors.rs

+4
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,9 @@ pub enum Error {
7575
/// JSON has a comma after the last value in an array or map.
7676
TrailingComma,
7777

78+
/// JSON is nested too deeply, exceeded the recursion limit.
79+
RecursionLimitExceeded,
80+
7881
/// Custom error message from serde
7982
Custom(String),
8083
}
@@ -128,6 +131,7 @@ impl core::fmt::Display for Error {
128131
value."
129132
}
130133
Error::TrailingComma => "JSON has a comma after the last value in an array or map.",
134+
Error::RecursionLimitExceeded => "JSON is nested too deeply, exceeded the recursion limit.",
131135
Error::Custom(msg) => msg,
132136
}
133137
)

src/de/mod.rs

+47-12
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ use self::seq::SeqAccess;
2121
pub struct Deserializer<'b> {
2222
slice: &'b [u8],
2323
index: usize,
24+
25+
/// Remaining depth until we hit the recursion limit
26+
remaining_depth: u8,
2427
}
2528

2629
enum StringLike<'a> {
@@ -30,7 +33,11 @@ enum StringLike<'a> {
3033

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

3643
fn eat_char(&mut self) {
@@ -287,16 +294,22 @@ impl<'a, 'de> de::Deserializer<'de> for &'a mut Deserializer<'de> {
287294
}
288295
}
289296
b'[' => {
290-
self.eat_char();
291-
let ret = visitor.visit_seq(SeqAccess::new(self))?;
297+
check_recursion! {
298+
self.eat_char();
299+
let ret = visitor.visit_seq(SeqAccess::new(self));
300+
}
301+
let ret = ret?;
292302

293303
self.end_seq()?;
294304

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

301314
self.end_map()?;
302315

@@ -513,8 +526,11 @@ impl<'a, 'de> de::Deserializer<'de> for &'a mut Deserializer<'de> {
513526
{
514527
match self.parse_whitespace().ok_or(Error::EofWhileParsingValue)? {
515528
b'[' => {
516-
self.eat_char();
517-
let ret = visitor.visit_seq(SeqAccess::new(self))?;
529+
check_recursion! {
530+
self.eat_char();
531+
let ret = visitor.visit_seq(SeqAccess::new(self));
532+
}
533+
let ret = ret?;
518534

519535
self.end_seq()?;
520536

@@ -550,9 +566,11 @@ impl<'a, 'de> de::Deserializer<'de> for &'a mut Deserializer<'de> {
550566
let peek = self.parse_whitespace().ok_or(Error::EofWhileParsingValue)?;
551567

552568
if peek == b'{' {
553-
self.eat_char();
554-
555-
let ret = visitor.visit_map(MapAccess::new(self))?;
569+
check_recursion! {
570+
self.eat_char();
571+
let ret = visitor.visit_map(MapAccess::new(self));
572+
}
573+
let ret = ret?;
556574

557575
self.end_map()?;
558576

@@ -588,8 +606,11 @@ impl<'a, 'de> de::Deserializer<'de> for &'a mut Deserializer<'de> {
588606
b'"' => visitor.visit_enum(UnitVariantAccess::new(self)),
589607
// if it is a struct enum
590608
b'{' => {
591-
self.eat_char();
592-
visitor.visit_enum(StructVariantAccess::new(self))
609+
check_recursion! {
610+
self.eat_char();
611+
let value = visitor.visit_enum(StructVariantAccess::new(self));
612+
}
613+
value
593614
}
594615
_ => Err(Error::ExpectedSomeIdent),
595616
}
@@ -649,6 +670,20 @@ where
649670
from_slice(s.as_bytes())
650671
}
651672

673+
macro_rules! check_recursion {
674+
($this:ident $($body:tt)*) => {
675+
$this.remaining_depth -= 1;
676+
if $this.remaining_depth == 0 {
677+
return Err($crate::de::Error::RecursionLimitExceeded);
678+
}
679+
680+
$this $($body)*
681+
682+
$this.remaining_depth += 1;
683+
};
684+
}
685+
pub(crate) use check_recursion;
686+
652687
#[cfg(test)]
653688
mod tests {
654689
use super::from_str;

src/lib.rs

+25
Original file line numberDiff line numberDiff line change
@@ -227,4 +227,29 @@ mod test {
227227
item
228228
);
229229
}
230+
231+
#[test]
232+
fn no_stack_overflow() {
233+
const AMOUNT: usize = 2000;
234+
let mut json = String::from(r#"{"":"#);
235+
236+
#[derive(Debug, Deserialize, Serialize)]
237+
pub struct Person {
238+
name: String,
239+
age: u8,
240+
phones: Vec<String>,
241+
}
242+
243+
for _ in 0..AMOUNT {
244+
json.push('[');
245+
}
246+
for _ in 0..AMOUNT {
247+
json.push(']');
248+
}
249+
250+
json.push_str(r#"] }[[[[[[[[[[[[[[[[[[[[[ ""","age":35,"phones":["#);
251+
252+
let err = from_str::<Person>(&json).unwrap_err();
253+
assert_eq!(err, crate::de::Error::RecursionLimitExceeded);
254+
}
230255
}

0 commit comments

Comments
 (0)