From bb03081a70c18770fff3fb43311fbf0cbd5f34de Mon Sep 17 00:00:00 2001 From: "Dylan M. Taylor" Date: Wed, 4 Nov 2026 03:33:13 +0000 Subject: [PATCH] EPS import: bound and charge the filter chain over a pending file --- crates/eps/src/import/data/ops.rs | 13 ++++++++++++- crates/eps/src/import/tests.rs | 8 ++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/crates/eps/src/import/data/ops.rs b/crates/eps/src/import/data/ops.rs index c5c588557..baca4d0ed 100644 --- a/crates/eps/src/import/data/ops.rs +++ b/crates/eps/src/import/data/ops.rs @@ -7,6 +7,8 @@ use super::{ /// Most bytes one filter or image reads. pub(super) const MAX_DATA: usize = 1 << 28; +/// The longest chain of filters over one pending file (real programs stack two or three). +const MAX_FILTER_CHAIN: usize = 64; /// Most pixels an image may have. const MAX_PIXELS: usize = 1 << 26; @@ -294,7 +296,16 @@ impl Interp<'_> { let pending = match &*f.borrow() { Stream::Current => Some((vec![], None)), Stream::Buf { .. } if running => Some((vec![], Some(f.clone()))), - Stream::Pending { chain, from } => Some((chain.clone(), from.clone())), + Stream::Pending { chain, from } => { + // Cloning the chain copies each filter's delimiter: bound and charge it. + if chain.len() >= MAX_FILTER_CHAIN { + return Err(PsError::Limit("too many filters over one file")); + } + let copied = chain.iter().map(|c| c.sub.1.len() + 1).sum::(); + self.alloc(copied)?; + self.spend((copied / 256) as u64)?; + Some((chain.clone(), from.clone())) + } Stream::Buf { .. } => None, }; match pending { diff --git a/crates/eps/src/import/tests.rs b/crates/eps/src/import/tests.rs index 52c098bb0..8f9cb5581 100644 --- a/crates/eps/src/import/tests.rs +++ b/crates/eps/src/import/tests.rs @@ -391,6 +391,14 @@ fn work_on_the_longest_dash_pattern_ends_soon() { } } +#[test] +fn a_chain_of_filters_over_the_program_is_bounded() { + let r = read_soon("0 0 10 10 rectfill currentfile 100 { 0 (x) /SubFileDecode filter } repeat pop 20 20 10 10 rectfill"); + assert!(r.warnings.iter().any(|w| w.contains("too many filters")), "{:?}", r.warnings); + let r = read_soon("0 0 10 10 rectfill currentfile 3 { 0 (x) /SubFileDecode filter } repeat pop 20 20 10 10 rectfill"); + assert!(r.warnings.is_empty(), "{:?}", r.warnings); +} + #[test] fn procedures_bind_def_loops_and_dictionaries() { let r = read(