From dafb3be3de1373465018912c0157bc0df12d0b83 Mon Sep 17 00:00:00 2001 From: "Dylan M. Taylor" Date: Mon, 9 Nov 2026 05:38:52 -0500 Subject: [PATCH] fix(xfa): one decoding allowance for all XFA packets (#6000) --- crates/xfa/src/packets.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/crates/xfa/src/packets.rs b/crates/xfa/src/packets.rs index f79b071c0e..4202ffbb55 100644 --- a/crates/xfa/src/packets.rs +++ b/crates/xfa/src/packets.rs @@ -7,6 +7,8 @@ use crate::XfaError; /// Most decoded packet bytes read in total. const MAX_XDP: usize = 64 << 20; +/// Most bytes all filter stages of all packets may decode together. +const MAX_DECODE_WORK: usize = 4 * MAX_XDP; /// What was found in the XFA entry. #[derive(Clone, Debug, Default, PartialEq)] @@ -116,13 +118,17 @@ pub fn read_packets(doc: &Document) -> Result, XfaError> { // A stream listed again adds nothing but another decode, which may cost far more than the // bytes it yields (a Flate stream of hex whitespace). let mut seen = std::collections::HashSet::new(); + // One decoding allowance for every packet, charged for what each filter stage produced (a + // stage a later one shrinks included): a fresh MAX_XDP per stream let many packets of + // compressed bulk that decode to little cost far more than the packets kept. + let mut budget = pdfcraft_cos::DecodeBudget::new(MAX_DECODE_WORK, 0); let mut read = |o: &Object| -> Result, XfaError> { if o.as_ref().is_some_and(|r| !seen.insert(r)) { return Ok(None); } let s = doc.resolve(o); let Object::Stream(s) = &*s else { return Ok(None) }; - let bytes = s.decoded_within(MAX_XDP)?; + let bytes = budget.decode_within(s, MAX_XDP)?; total = total.saturating_add(bytes.len()); if total > MAX_XDP { return Err(XfaError::TooLarge("the XFA packets are larger than 64 MB".into()));