From 8c57a65ee0ae477dbb225c097cbe2e75537521b2 Mon Sep 17 00:00:00 2001 From: Don Omondi <282319807+mockydevs@users.noreply.github.com> Date: Fri, 6 Nov 2026 17:32:34 -0500 Subject: [PATCH] EPS import: SubFileDecode reads counts past the allocation cap The count of a SubFileDecode filter bounds what it passes through, not what it allocates, but the reader refused counts over 4 MiB as a rangecheck. Ghostscript's eps2write files read each content stream with currentfile Length () /SubFileDecode filter, so a page whose stream is longer than 4 MiB failed to open. --- crates/eps/src/import/data/ops.rs | 4 +++- crates/eps/src/import/tests.rs | 11 +++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/crates/eps/src/import/data/ops.rs b/crates/eps/src/import/data/ops.rs index baca4d0ed..1b78901b4 100644 --- a/crates/eps/src/import/data/ops.rs +++ b/crates/eps/src/import/data/ops.rs @@ -270,7 +270,9 @@ impl Interp<'_> { let name = self.pop()?.text().ok_or(PsError::Ps("typecheck", "filter".into()))?; let sub = if &*name == "SubFileDecode" { let eod = self.pop_str()?.to_vec(); - let count = self.pop_count()?; + // The count bounds what the filter reads, not what it allocates: a stream of several + // megabytes read through `currentfile N () /SubFileDecode filter` gives its length. + let count = usize::try_from(self.pop_int()?).map_err(|_| PsError::Ps("rangecheck", "a count".into()))?; (count, eod) } else { (0, vec![]) diff --git a/crates/eps/src/import/tests.rs b/crates/eps/src/import/tests.rs index fbb0eda72..852598e1b 100644 --- a/crates/eps/src/import/tests.rs +++ b/crates/eps/src/import/tests.rs @@ -1060,6 +1060,17 @@ fn an_executable_filter_runs_its_data_as_a_program() { check("(1 2 add) 0 () /SubFileDecode filter exec type /filetype eq"); } +/// `SubFileDecode`'s count is how many bytes (or end strings) to pass, not an allocation: a count +/// past the 4 MiB cap on allocations (a long content stream's `/Length`) is read, and only the data +/// there passes through. +#[test] +fn subfile_counts_past_the_allocation_cap_are_read() { + check("(1 2 add) 6000000 () /SubFileDecode filter cvx exec 3 eq"); + check("(1 2 add) 5000000 (x) /SubFileDecode filter cvx exec 3 eq"); + check("(abcd) 7000000 () /SubFileDecode filter 8 string readstring pop (abcd) eq"); + check("{ (1) -1 () /SubFileDecode filter } stopped"); +} + #[test] fn flushfile_skips_the_data_of_a_filter() { // Metadata that isn't PostScript, skipped up to its end marker.