diff --git a/ciborium/src/de/mod.rs b/ciborium/src/de/mod.rs index f59a319..b68458d 100644 --- a/ciborium/src/de/mod.rs +++ b/ciborium/src/de/mod.rs @@ -831,3 +831,27 @@ where T::deserialize(&mut reader) } + +/// Deserializes as CBOR from a type with [`impl ciborium_io::Read`](ciborium_io::Read), with +/// a specified maximum recursion limit. Inputs that are nested beyond the specified limit +/// will result in [`Error::RecursionLimitExceeded`] . +/// +/// Set a high recursion limit at your own risk (of stack exhaustion)! +#[inline] +pub fn from_reader_with_recursion_limit( + reader: R, + recurse_limit: usize, +) -> Result> +where + R::Error: core::fmt::Debug, +{ + let mut scratch = [0; 4096]; + + let mut reader = Deserializer { + decoder: reader.into(), + scratch: &mut scratch, + recurse: recurse_limit, + }; + + T::deserialize(&mut reader) +} diff --git a/ciborium/tests/recursion.rs b/ciborium/tests/recursion.rs index a340b2d..cda1ce2 100644 --- a/ciborium/tests/recursion.rs +++ b/ciborium/tests/recursion.rs @@ -7,7 +7,7 @@ //! test each of these types here to ensure there is no stack overflow. use ciborium::{ - de::{from_reader, Error}, + de::{from_reader, from_reader_with_recursion_limit, Error}, value::Value, }; @@ -46,3 +46,47 @@ fn text() { e => panic!("incorrect error: {:?}", e), } } + +#[test] +fn array_limit() { + let bytes = [0x9f; 128 * 1024]; + for limit in 16..256 { + match from_reader_with_recursion_limit::(&bytes[..], limit).unwrap_err() { + Error::RecursionLimitExceeded => (), + e => panic!("incorrect error with limit {}: {:?}", limit, e), + } + // Data that is nested beyond the limit should fail with `RecursionLimitExceeded` + match from_reader_with_recursion_limit::(&bytes[..limit + 1], limit).unwrap_err() + { + Error::RecursionLimitExceeded => (), + e => panic!("incorrect error with limit {}: {:?}", limit, e), + } + // Data that is nested within the limit fails with a different error. + match from_reader_with_recursion_limit::(&bytes[..limit], limit).unwrap_err() { + Error::Io(..) => (), + e => panic!("incorrect error with limit {}: {:?}", limit, e), + } + } +} + +#[test] +fn map_limit() { + let bytes = [0xbf; 128 * 1024]; + for limit in 16..256 { + match from_reader_with_recursion_limit::(&bytes[..], limit).unwrap_err() { + Error::RecursionLimitExceeded => (), + e => panic!("incorrect error with limit {}: {:?}", limit, e), + } + // Data that is nested beyond the limit should fail with `RecursionLimitExceeded` + match from_reader_with_recursion_limit::(&bytes[..limit + 1], limit).unwrap_err() + { + Error::RecursionLimitExceeded => (), + e => panic!("incorrect error with limit {}: {:?}", limit, e), + } + // Data that is nested within the limit fails with a different error. + match from_reader_with_recursion_limit::(&bytes[..limit], limit).unwrap_err() { + Error::Io(..) => (), + e => panic!("incorrect error with limit {}: {:?}", limit, e), + } + } +}