mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-11-12 05:42:52 +00:00
fix(cos): cap the total entries a materialized xref-stream chain merges (#5082)
read_xref_chain_materialized capped each section's decode and the chain length but merged every row of every section into one map with no total. It now counts the rows each section merges and, past MAX_XREF_OBJECTS, returns a syntax error so the open path logs it and rebuilds from object headers, as it already does when a single section's stream decodes past that limit. The count is rows merged rather than map size, so relisted objects are bounded too, and it never exceeds what the compact reader counts, whose limit is about a quarter of this one. Fixes #5082 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
3932ee0639
commit
fde104475b
1 file changed
+14
@@ -343,6 +343,7 @@ impl Document {
|
||||
// At most MAX_XREF_OBJECTS entries are kept in all; a stream past that is read again.
|
||||
let mut hybrids: HashMap<i64, Option<BTreeMap<u32, XrefEntry>>> = HashMap::new();
|
||||
let (mut hybrid_rows, mut noted) = (0usize, HashSet::new());
|
||||
let mut merged_rows = 0usize;
|
||||
let mut next = Some(first);
|
||||
while let Some(off) = next.take() {
|
||||
if !visited.insert(off) || visited.len() > 10_000 {
|
||||
@@ -413,6 +414,19 @@ impl Document {
|
||||
hybrids.insert(x, read);
|
||||
}
|
||||
}
|
||||
// Each section's rows are capped, so the chain is too: at most MAX_XREF_OBJECTS rows
|
||||
// merged in all, or the file is rebuilt from object headers, as when one section's
|
||||
// stream decodes past that. This counts rows merged, not the map's size, since a chain
|
||||
// that relists the same objects costs as much to merge. A section's rows (its hybrid
|
||||
// stream's included) never outnumber the rows the compact reader counts for it, and
|
||||
// that reader stops at about a quarter of this cap: no chain within its limit is refused.
|
||||
let Some(total) = merged_rows.checked_add(sect.len()).filter(|total| *total <= MAX_XREF_OBJECTS) else {
|
||||
return Err(CosError::Syntax {
|
||||
offset: usize::try_from(off).unwrap_or(usize::MAX),
|
||||
detail: format!("cross-reference chain lists more than {MAX_XREF_OBJECTS} entries"),
|
||||
});
|
||||
};
|
||||
merged_rows = total;
|
||||
oddities.extend(odd.into_iter().map(|num| (off, num)));
|
||||
for (num, e) in sect {
|
||||
entries.entry(num).or_insert(e);
|
||||
|
||||
Reference in new issue
Block a user