security: bound vendored hayro ASCII85 output by the stream decode limit (#253)

* security: bound ASCII85 output by the stream decode limit (CodeRabbit)

* fix(ascii85): truncate terminal partial groups to decode limit
This commit is contained in:
Dylan M. Taylor authored and GitHub committed 2026-10-23 21:54:26 -04:00
1 parent 5354479ce3
commit d6b3fb9671
2 files changed
+27 -3

No files matched your search

+26 -2
View File
@@ -4,7 +4,9 @@ use crate::reader::Reader;
use crate::trivia::is_white_space_character;
use alloc::vec::Vec;
pub(crate) fn decode(data: &[u8]) -> Option<Vec<u8>> {
/// PdfCraft patch: `limit` is the most bytes produced (see `filter::decode_limit`); each `z` costs
/// one input byte and yields four, so a Flate stream of them would outgrow the budget.
pub(crate) fn decode(data: &[u8], limit: usize) -> Option<Vec<u8>> {
const POW_85: [u32; 5] = [52200625, 614125, 7225, 85, 1];
let mut reader = Reader::new(data);
@@ -55,13 +57,21 @@ pub(crate) fn decode(data: &[u8]) -> Option<Vec<u8>> {
Some(())
};
let mut decoded = Vec::with_capacity(data.len() * 4 / 5);
let mut decoded = Vec::with_capacity((data.len() * 4 / 5).min(limit));
let mut group = Vec::with_capacity(5);
loop {
if decoded.len() >= limit {
decoded.truncate(limit);
warn!("ascii85 stream stopped at its decode limit of {limit} bytes");
return Some(decoded);
}
let Some(b) = read_byte() else {
// Be lenient and accept what we have (see PDFBOX-5910).
flush_group(&mut group, &mut decoded)?;
decoded.truncate(limit);
return Some(decoded);
};
@@ -82,6 +92,7 @@ pub(crate) fn decode(data: &[u8]) -> Option<Vec<u8>> {
// Technically requires a '>', but there is a PDF where it isn't
// appended and decodes fine in other viewers.
flush_group(&mut group, &mut decoded)?;
decoded.truncate(limit);
return Some(decoded);
}
@@ -89,3 +100,16 @@ pub(crate) fn decode(data: &[u8]) -> Option<Vec<u8>> {
}
}
}
#[cfg(test)]
mod tests {
use super::decode;
#[test]
fn output_stops_at_the_limit() {
assert_eq!(decode(&[b'z'; 100], 10).unwrap().len(), 10);
assert_eq!(decode(b"zz~>", 100).unwrap(), [0; 8]);
assert_eq!(decode(b"!!!!~>", 2).unwrap().len(), 2);
assert_eq!(decode(b"!!!!", 2).unwrap().len(), 2);
}
}
+1 -1
View File
@@ -95,7 +95,7 @@ impl Filter {
Self::AsciiHexDecode => ascii_hex::decode(data)
.map(FilterResult::from_data)
.ok_or(DecodeFailure::StreamDecode),
Self::Ascii85Decode => ascii_85::decode(data)
Self::Ascii85Decode => ascii_85::decode(data, limit)
.map(FilterResult::from_data)
.ok_or(DecodeFailure::StreamDecode),
Self::RunLengthDecode => run_length::decode(data, limit)