mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-10-14 19:27:04 -04:00
render: don't let a hostile page-tree /Count abort inspection (#956)
* render: don't let a hostile page-tree /Count abort inspection Found by cargo xtask fuzz on Windows (seed 42): a 355-byte PDF whose /Type /Pages node carries /Count 4294967295 aborted pdfcraft-cli check-one with a 48 GiB allocation (4294967295 x 12 bytes) - an abort, which the engine's catch_unwind guard cannot catch. AGENTS.md §4 requires a malformed PDF to produce an actable error, never a crash. The lopdf Structure impl delegated get_pages to lopdf's own page-tree walk, which trusts /Count and pre-allocates by it. The trait's default walk (depth cap, seen-set, checked page numbering) handles the same input fine, so the override is simply dropped and the bounded walk counts the real pages. Comes with a synthetic regression test (/Count 4294967295, one real page); cargo test -p pdfcraft-render, clippy -D warnings, fmt --check and an end-to-end check-one run all pass. * Drop the duplicated use super::* in the structure tests --------- Co-authored-by: pechnikov2026-star <[email protected]> Co-authored-by: Brandon Thomas <[email protected]>
This commit is contained in:
1 file changed
+26
-3
@@ -115,9 +115,9 @@ impl Structure for lopdf::Document {
|
||||
fn encrypted(&self) -> bool {
|
||||
self.is_encrypted() || self.was_encrypted() || self.trailer.has(b"Encrypt")
|
||||
}
|
||||
fn get_pages(&self) -> BTreeMap<u32, ObjectId> {
|
||||
self.get_pages()
|
||||
}
|
||||
// `get_pages` is not overridden: lopdf's own page-tree walk trusts /Count and pre-allocates
|
||||
// by it, so a hostile /Count (a fuzz finding: 4294967295) aborted the process with a 48 GiB
|
||||
// allocation. The trait's bounded default walk counts the real pages instead.
|
||||
fn get_page_fonts(&self, page: ObjectId) -> Result<BTreeMap<Vec<u8>, &Dictionary>> {
|
||||
self.get_page_fonts(page)
|
||||
}
|
||||
@@ -207,6 +207,29 @@ fn convert(object: &CosObject, depth: usize) -> Result<Object> {
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
/// A hostile page-tree /Count must not abort the process: lopdf's own `get_pages`
|
||||
/// pre-allocates by it (fuzz finding: /Count 4294967295 -> a 48 GiB allocation),
|
||||
/// the bounded default walk counts the real pages instead.
|
||||
#[test]
|
||||
fn a_hostile_page_count_does_not_abort_inspection() {
|
||||
let pdf = b"%PDF-1.7
|
||||
1 0 obj
|
||||
<< /Type /Catalog /Pages 2 0 R >>
|
||||
endobj
|
||||
2 0 obj
|
||||
<< /Type /Pages /Kids [3 0 R] /Count 4294967295 >>
|
||||
endobj
|
||||
3 0 obj
|
||||
<< /Type /Page /Parent 2 0 R /MediaBox [0 0 200 200] >>
|
||||
endobj
|
||||
trailer
|
||||
<< /Size 4 /Root 1 0 R >>
|
||||
%%EOF";
|
||||
let doc = lopdf::Document::load_mem(&pdf[..]).unwrap();
|
||||
let pages = Structure::get_pages(&doc);
|
||||
assert_eq!(pages.len(), 1, "the walk counts the one real page, not /Count");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn inspection_loads_only_requested_objects_in_a_compressed_document() {
|
||||
let mut source = lopdf::Document::with_version("1.7");
|
||||
|
||||
Reference in new issue
Block a user