diff --git a/crates/eps/src/ps.rs b/crates/eps/src/ps.rs index e00d813a..fb27425e 100644 --- a/crates/eps/src/ps.rs +++ b/crates/eps/src/ps.rs @@ -181,13 +181,26 @@ fn push85(s: &mut String, mut v: u32, n: usize) { } } -/// ASCII85 `text` (whitespace ignored, up to `~>`) decoded; `None` when it is malformed. +/// Most bytes [`ascii85_decode`] makes: what one filter or data section may hold. +pub(crate) const MAX_ASCII85: usize = 1 << 28; + +/// ASCII85 `text` (whitespace ignored, up to `~>`) decoded; `None` when it is malformed or longer +/// than [`MAX_ASCII85`]. pub(crate) fn ascii85_decode(text: &str) -> Option> { - let mut out = Vec::with_capacity(text.len() * 4 / 5); + ascii85_decode_within(text, MAX_ASCII85) +} + +/// [`ascii85_decode`] of at most `max` bytes: `z` is four bytes for one, so the length of the +/// text doesn't bound what it becomes (#1715). +pub(crate) fn ascii85_decode_within(text: &str, max: usize) -> Option> { + let mut out = Vec::with_capacity((text.len() / 5 * 4).min(max)); let mut group = [0u8; 5]; let mut n = 0; let mut chars = text.bytes().filter(|b| !b.is_ascii_whitespace()).peekable(); while let Some(b) = chars.next() { + if out.len() > max { + return None; + } match b { b'~' => { if chars.next() != Some(b'>') { @@ -201,7 +214,7 @@ pub(crate) fn ascii85_decode(text: &str) -> Option> { let v = value85(&padded)?; out.extend(v.to_be_bytes().iter().take(n - 1)); } - return Some(out); + return (out.len() <= max).then_some(out); } b'z' if n == 0 => out.extend([0; 4]), b'!'..=b'u' => { diff --git a/crates/eps/src/tests.rs b/crates/eps/src/tests.rs index b4e5fa1c..4cab4628 100644 --- a/crates/eps/src/tests.rs +++ b/crates/eps/src/tests.rs @@ -345,6 +345,11 @@ fn run_length_and_ascii85_round_trip() { assert_eq!(ps::ascii85(b"Man "), "9jqo^~>\n"); assert_eq!(ps::ascii85(&[0, 0, 0, 0]), "z~>\n"); assert!(ps::ascii85_decode("9jqo^").is_none(), "no end marker"); + // #1715: the result is bounded while it is made, not by the length of the text. + assert_eq!(ps::ascii85_decode_within("zz~>", 8), Some(vec![0; 8])); + assert!(ps::ascii85_decode_within("zzz~>", 8).is_none(), "three `z` are 12 bytes"); + assert!(ps::ascii85_decode_within("zz9jqo^~>", 8).is_none()); + assert!(ps::ascii85_decode_within(&format!("{}~>", "z".repeat(1 << 16)), 1 << 10).is_none()); assert!(ps::ascii85_decode("9jqo^v~>").is_none(), "not base 85"); assert_eq!(ps::num(1.0 / 3.0), "0.3333"); assert_eq!((ps::num(-0.00001), ps::num(f64::NAN), ps::num(2.5)), ("0".into(), "0".into(), "2.5".into()));