mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-24 09:46:59 -06:00
Merge pull request #2244 from dotCooCoo/fix/pdf-nested-stream-checks
Importers: PDF streams written inside arrays and dictionaries are checked before they are decoded
This commit is contained in:
@@ -2897,6 +2897,20 @@ fn pdf_streams_the_reader_would_fail_on_are_refused() {
|
||||
format!("q 10 0 0 10 0 0 cm BI /W 2 /H 2 /CS /G /BPC 8 /F [/AHx /Fl] /DP [null {dp}] ID {data}> EI Q")
|
||||
};
|
||||
let form = |dict: &str, content: &str| pdf_with("/F Do", "<< /XObject << /F 5 0 R >> >>", &[packed(&format!("{FORM} {dict}"), content)]);
|
||||
// A calculator function with the deflated program `z`, to write inside another object. Its
|
||||
// data is in hexadecimal digits with spaces between them, so the object around it still reads.
|
||||
let calculator = |z: &[u8], dp: &str| {
|
||||
let data: String = z.iter().map(|b| format!("{:X} {:X} ", b >> 4, b & 15)).collect();
|
||||
let dict = format!("/FunctionType 4 /Domain [0 1] /Range [0 1 0 1 0 1] /Filter [/ASCIIHexDecode /FlateDecode] {dp}");
|
||||
format!("<< {dict} /Length {} >>\nstream\n{data}\nendstream", data.len())
|
||||
};
|
||||
let held = |object: String| pdf_with("0 0 5 5 re f", "<< >>", &[object.into_bytes()]);
|
||||
let shaded = |function: &str| {
|
||||
let shading = format!("<< /ShadingType 2 /ColorSpace /DeviceRGB /Coords [0 0 100 0] /Function {function} >>");
|
||||
pdf_with("/S sh", "<< /Shading << /S 5 0 R >> >>", &[shading.into_bytes()])
|
||||
};
|
||||
let spaces = calculator(&spaces_past_the_cap(), "");
|
||||
let program = deflated(b"{ dup 1 exch sub 0 }");
|
||||
let files = [
|
||||
("a form with Columns 0", form(zero, "0 0 5 5 re f")),
|
||||
("a form with a huge Columns", form(huge, "0 0 5 5 re f")),
|
||||
@@ -2905,6 +2919,10 @@ fn pdf_streams_the_reader_would_fail_on_are_refused() {
|
||||
("an inline image in a compressed form", form("", &inline("<< /Predictor 2 /Columns 0 >>"))),
|
||||
("an object stream with Columns 0", pdf_with_object_stream("[0 0 100 100]", zero)),
|
||||
("a form of spaces past the cap", pdf_with("/F Do", "<< /XObject << /F 5 0 R >> >>", &[packed_as_is(FORM, &spaces_past_the_cap())])),
|
||||
("a function of spaces past the cap in an array", held(format!("[{spaces}]"))),
|
||||
("a function of spaces past the cap in a dictionary", held(format!("<< /K {spaces} >>"))),
|
||||
("a function of spaces past the cap in a shading", shaded(&spaces)),
|
||||
("a function with Columns 0 in an array", held(format!("[{}]", calculator(&program, "/DecodeParms [null << /Predictor 2 /Columns 0 >>]")))),
|
||||
];
|
||||
on_a_small_stack(move || {
|
||||
for (what, bytes) in &files {
|
||||
@@ -2916,7 +2934,8 @@ fn pdf_streams_the_reader_would_fail_on_are_refused() {
|
||||
});
|
||||
// Within range, the same files open.
|
||||
let fine = "/DecodeParms << /Predictor 1 /Columns 0 >>";
|
||||
for bytes in [form(fine, "0 0 5 5 re f"), deep_pdf(&inline("<< /Predictor 1 >>"), "<< >>")] {
|
||||
let function = calculator(&program, "");
|
||||
for bytes in [form(fine, "0 0 5 5 re f"), deep_pdf(&inline("<< /Predictor 1 >>"), "<< >>"), held(format!("[{function}]")), shaded(&function)] {
|
||||
vectorcraft_engine::cmd::fileio::load("fine.pdf", &bytes).expect("opens");
|
||||
}
|
||||
}
|
||||
|
||||
+13
-16
@@ -117,37 +117,34 @@ fn procedures_too_deep(o: &Object<'_>) -> bool {
|
||||
/// The error of a file with a stream the reader would fail on ([`crate::streams`]).
|
||||
const STREAM_NOTE: &str = "a stream in it would decode to more than 256 MB, or its decode parameters can't be applied";
|
||||
|
||||
/// The error of a file [`nests_without_end`].
|
||||
/// The error of a file with functions or color spaces built on one another past [`MAX_LINKS`]
|
||||
/// (or on themselves), or a calculator function nested past [`MAX_PROCEDURES`].
|
||||
const NESTED_NOTE: &str = "its functions or colour spaces are built on one another without end";
|
||||
|
||||
/// Why `pdf` is refused, if it is: a stream the reader would fail on (its own checks come first:
|
||||
/// the calculator functions are decoded below), or [`nests_without_end`].
|
||||
/// Why `pdf` is refused, if it is: a stream the reader would fail on ([`STREAM_NOTE`]), or
|
||||
/// functions, color spaces or calculator procedures nested too deep ([`NESTED_NOTE`]). Every
|
||||
/// object is looked at, and every array, dictionary and stream written inside one, wherever it
|
||||
/// is used from: the interpreter builds functions and color spaces from resources, shadings,
|
||||
/// images, patterns, masks and graphics states alike.
|
||||
fn hostile(pdf: &Pdf) -> Option<&'static str> {
|
||||
if pdf.objects().into_iter().any(|o| matches!(&o, Object::Stream(s) if crate::streams::unsafe_stream(s))) {
|
||||
return Some(STREAM_NOTE);
|
||||
}
|
||||
nests_without_end(pdf).then_some(NESTED_NOTE)
|
||||
}
|
||||
|
||||
/// Does `pdf` hold functions or colour spaces built on one another past [`MAX_LINKS`] (or on
|
||||
/// themselves), or a calculator function nested past [`MAX_PROCEDURES`]? Every object is looked
|
||||
/// at, wherever it is used from: the interpreter builds them from resources, shadings, images,
|
||||
/// patterns, masks and graphics states alike.
|
||||
fn nests_without_end(pdf: &Pdf) -> bool {
|
||||
let xref = pdf.xref();
|
||||
let mut known = HashMap::new();
|
||||
// The objects of the file, then what each holds directly (64 deep at most: the reader's cap).
|
||||
let mut pending: Vec<Object<'_>> = pdf.objects().into_iter().collect();
|
||||
while let Some(o) = pending.pop() {
|
||||
// A stream's own checks come first: a calculator function is decoded below.
|
||||
if matches!(&o, Object::Stream(s) if crate::streams::unsafe_stream(s)) {
|
||||
return Some(STREAM_NOTE);
|
||||
}
|
||||
if procedures_too_deep(&o) || built_on(MaybeRef::NotRef(o.clone()), xref, &mut known, MAX_LINKS).is_none() {
|
||||
return true;
|
||||
return Some(NESTED_NOTE);
|
||||
}
|
||||
match &o {
|
||||
Object::Array(a) => pending.extend(a.raw_iter().filter_map(direct)),
|
||||
_ => pending.extend(dict_or_stream(&o).into_iter().flat_map(|(d, _)| values(d)).filter_map(direct)),
|
||||
}
|
||||
}
|
||||
false
|
||||
None
|
||||
}
|
||||
|
||||
/// Read a PDF, decrypting it with `password` (none: the empty user password). The permissions
|
||||
|
||||
Reference in New Issue
Block a user