mirror of
https://github.com/storytold/craft-libs.git
synced 2026-11-07 12:13:20 +00:00
Merge pull request #531 from storytold/fix/sec-xref-entry-cap
hayro-syntax: cap a document's xref map at the PDF object limit
This commit is contained in:
Vendored
+1
-1
@@ -338,7 +338,7 @@
|
||||
"path": "src/xref.rs",
|
||||
"source_sha256": "51ffeeb7d4ac035bae2cd5ba9288c180088ae06cdcefe2b29dfeda560a793aa0",
|
||||
"source_git_blob": "20e33a410489d42e438a3f2b87d70c7bb8d66eae",
|
||||
"sha256": "98fb56eb5c2da8b0c40202ed30b2f464874c1143436e72eda4e49f2333dd7c7f"
|
||||
"sha256": "95261b80fd7bdc946de625325c6c6fc9fd7c0c5b3273ad62b8666dff215e5bd9"
|
||||
},
|
||||
{
|
||||
"path": "src/decode_limits_tests.rs",
|
||||
|
||||
Vendored
+69
-17
@@ -91,8 +91,9 @@ fn fallback_xref_map_inner<'a>(
|
||||
let mut cloned = r.clone();
|
||||
// Check that the object following it is actually valid before inserting it.
|
||||
cloned.skip_white_spaces_and_comments();
|
||||
if cloned.skip::<Object<'_>>(false).is_some() {
|
||||
xref_map.insert(obj_id, EntryType::Normal(cur_pos));
|
||||
if cloned.skip::<Object<'_>>(false).is_some()
|
||||
&& insert_entry(&mut xref_map, obj_id, EntryType::Normal(cur_pos)).is_some()
|
||||
{
|
||||
last_obj_num = Some(obj_id);
|
||||
dummy_ctx.set_obj_number(obj_id);
|
||||
}
|
||||
@@ -139,13 +140,10 @@ fn fallback_xref_map_inner<'a>(
|
||||
.get(&id)
|
||||
.is_none_or(|e| !matches!(e, &EntryType::Normal(_)))
|
||||
{
|
||||
xref_map.insert(
|
||||
id,
|
||||
EntryType::ObjStream(
|
||||
last_obj_num.obj_number as u32,
|
||||
idx as u32,
|
||||
),
|
||||
);
|
||||
let entry = EntryType::ObjStream(last_obj_num.obj_number as u32, idx as u32);
|
||||
if insert_entry(&mut xref_map, id, entry).is_none() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -671,6 +669,33 @@ enum EntryType {
|
||||
|
||||
type XrefMap = FxHashMap<ObjectIdentifier, EntryType>;
|
||||
|
||||
/// PdfCraft patch: the most entries one document's xref map may hold, the PDF 32000-1 (Annex C)
|
||||
/// limit on indirect objects. An xref-stream entry can be one decoded byte, so a stream at the
|
||||
/// decode limit could otherwise become hundreds of millions of map entries (gigabytes of map).
|
||||
const MAX_XREF_ENTRIES: usize = 8_388_607;
|
||||
|
||||
/// PdfCraft patch: insert an entry unless that would take `map` past [`MAX_XREF_ENTRIES`].
|
||||
fn insert_entry(map: &mut XrefMap, id: ObjectIdentifier, entry: EntryType) -> Option<()> {
|
||||
if map.len() >= MAX_XREF_ENTRIES && !map.contains_key(&id) {
|
||||
warn!("xref holds more than {MAX_XREF_ENTRIES} entries");
|
||||
|
||||
return None;
|
||||
}
|
||||
map.insert(id, entry);
|
||||
|
||||
Some(())
|
||||
}
|
||||
|
||||
/// PdfCraft patch: merge a section's entries into `map`, refusing a result past
|
||||
/// [`MAX_XREF_ENTRIES`].
|
||||
fn merge_entries(map: &mut XrefMap, own: XrefMap) -> Option<()> {
|
||||
for (id, entry) in own {
|
||||
insert_entry(map, id, entry)?;
|
||||
}
|
||||
|
||||
Some(())
|
||||
}
|
||||
|
||||
/// Representation of a proper xref table.
|
||||
#[derive(Debug)]
|
||||
struct MapRepr {
|
||||
@@ -877,10 +902,11 @@ fn populate_from_xref_table<'a>(
|
||||
// Specification says we should ignore any object number > SIZE, but probably
|
||||
// not important?
|
||||
if entry.used {
|
||||
own.insert(
|
||||
insert_entry(
|
||||
&mut own,
|
||||
ObjectIdentifier::new(obj_number as i32, entry.gen_number),
|
||||
EntryType::Normal(entry.offset),
|
||||
);
|
||||
)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -896,7 +922,7 @@ fn populate_from_xref_table<'a>(
|
||||
populate_xref_impl_inner(data, xref_stm as usize, insert_map, visited, limits)?;
|
||||
}
|
||||
|
||||
insert_map.extend(own);
|
||||
merge_entries(insert_map, own)?;
|
||||
|
||||
Some(trailer.data())
|
||||
}
|
||||
@@ -949,7 +975,7 @@ fn populate_from_xref_stream<'a>(
|
||||
let _ = populate_xref_impl_inner(data, prev, insert_map, visited, limits)?;
|
||||
}
|
||||
|
||||
insert_map.extend(own);
|
||||
merge_entries(insert_map, own)?;
|
||||
|
||||
Some(stream.dict().data())
|
||||
}
|
||||
@@ -1036,10 +1062,11 @@ fn xref_stream_subsection<'a>(
|
||||
0
|
||||
};
|
||||
|
||||
insert_map.insert(
|
||||
insert_entry(
|
||||
insert_map,
|
||||
ObjectIdentifier::new(obj_number, gen_number as i32),
|
||||
EntryType::Normal(offset as usize),
|
||||
);
|
||||
)?;
|
||||
}
|
||||
2 => {
|
||||
let obj_stream_number = {
|
||||
@@ -1054,10 +1081,11 @@ fn xref_stream_subsection<'a>(
|
||||
0
|
||||
};
|
||||
|
||||
insert_map.insert(
|
||||
insert_entry(
|
||||
insert_map,
|
||||
ObjectIdentifier::new(obj_number, gen_number),
|
||||
EntryType::ObjStream(obj_stream_number, index),
|
||||
);
|
||||
)?;
|
||||
}
|
||||
_ => {
|
||||
warn!("xref has unknown field type {f_type}");
|
||||
@@ -1216,6 +1244,30 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn xref_entries_stop_at_the_document_limit() {
|
||||
// One-byte entries (type 1, offset and generation zero): the cheapest a stream can encode.
|
||||
let data = vec![1u8; MAX_XREF_ENTRIES + 1];
|
||||
let mut map = XrefMap::default();
|
||||
let mut full = Reader::new(&data[..MAX_XREF_ENTRIES]);
|
||||
assert!(xref_stream_subsection(&mut full, 0, MAX_XREF_ENTRIES as u32, 1, 0, 0, &mut map).is_some());
|
||||
assert_eq!(map.len(), MAX_XREF_ENTRIES);
|
||||
|
||||
// A new object past the limit is refused, from a stream, a table section or a merge.
|
||||
let mut over = Reader::new(&data[..1]);
|
||||
assert!(xref_stream_subsection(&mut over, MAX_XREF_ENTRIES as u32, 1, 1, 0, 0, &mut map).is_none());
|
||||
let mut other = XrefMap::default();
|
||||
other.insert(ObjectIdentifier::new(i32::MAX, 0), EntryType::Normal(0));
|
||||
assert!(merge_entries(&mut map, other).is_none());
|
||||
assert_eq!(map.len(), MAX_XREF_ENTRIES);
|
||||
|
||||
// Replacing an existing object (an incremental update) still works at the limit.
|
||||
let mut update = XrefMap::default();
|
||||
update.insert(ObjectIdentifier::new(0, 0), EntryType::Normal(42));
|
||||
assert!(merge_entries(&mut map, update).is_some());
|
||||
assert_eq!(map.get(&ObjectIdentifier::new(0, 0)), Some(&EntryType::Normal(42)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn object_stream_offsets_are_parsed_once() {
|
||||
let data = Data::new(PdfData::from(Vec::new()));
|
||||
|
||||
Reference in New Issue
Block a user