mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-14 19:26:56 -04:00
Fix #1715: ASCII85 decoding is bounded while it decodes
A `z` is four bytes for one character, so the text's length didn't bound the result, and the filter's and the interpreter's budgets only saw it once it was whole. The decoder now stops at 256 MB, the most one filter or data section may hold.
This commit is contained in:
1 parent
0cb55bc0aa
commit
277e893e82
2 files changed
+21
-3
No files matched your search
+16
-3
@@ -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<Vec<u8>> {
|
||||
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<Vec<u8>> {
|
||||
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<Vec<u8>> {
|
||||
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' => {
|
||||
|
||||
@@ -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()));
|
||||
|
||||
Reference in new issue
Block a user