fix(create): refuse an indexed colour space whose base is indexed before following it

Image export parsed an Indexed colour space's base before checking whether
that base was itself Indexed. A base that refers back to the same array
recursed until the stack overflowed. The base is now checked first; since
Indexed is the only colour space that recurses, this also ends any cycle.
This commit is contained in:
aiongg
2026-10-22 13:27:52 -04:00
committed by Dylan M. Taylor
parent 77480498b2
commit 4aad36cf19
2 changed files with 25 additions and 2 deletions
+9 -2
View File
@@ -383,10 +383,17 @@ fn space(doc: &Document, cs: Option<&Object>) -> Result<Space, String> {
Some(b"CalGray") => Ok(Space::Gray),
Some(b"CalRGB") => Ok(Space::Rgb),
Some(b"Indexed" | b"I") => {
let base = space(doc, a.get(1))?;
if matches!(base, Space::Indexed(..)) {
// Checked before recursing: Indexed is the only space that recurses, so refusing
// an Indexed base here also ends a cyclic base reference.
let nested = match a.get(1).map(|b| doc.resolve(b)).as_deref() {
Some(Object::Array(b)) => matches!(b.first().and_then(Object::as_name), Some(b"Indexed" | b"I")),
Some(Object::Name(n)) => matches!(n.as_slice(), b"Indexed" | b"I"),
_ => false,
};
if nested {
return Err("nested indexed colour spaces aren't valid".into());
}
let base = space(doc, a.get(1))?;
let hival = match a.get(2).map(|value| doc.resolve(value)).as_deref() {
Some(Object::Int(value)) if (0..=255).contains(value) => *value as u8,
_ => return Err("an indexed colour space with an invalid hival".into()),
+16
View File
@@ -1181,3 +1181,19 @@ fn a_jpeg_with_an_exif_orientation_becomes_an_upright_page() {
let img = image_of(&doc, 1);
assert_eq!((img.int(b"Width"), img.int(b"Height")), (Some(60), Some(20)));
}
#[test]
fn a_cyclic_indexed_colour_space_is_refused() {
let mut doc = from_images(&[("flat.png".into(), png_bytes(false))]).unwrap();
// 5 0 obj [/Indexed 5 0 R 255 <00>]: the base is the indexed space itself.
let cs = doc.add(Object::Null);
doc.set(cs, Object::Array(vec![Object::name("Indexed"), Object::Ref(cs), Object::Int(255), Object::String(PdfString::literal(vec![0]))]));
with_image(&mut doc, |_| {
let mut dict = image_dict(1, 1, "DeviceGray", 8);
dict.set(b"ColorSpace".to_vec(), Object::Ref(cs));
Stream::from_raw(dict, vec![0])
});
let out = extract_images(&doc, &[0], 0);
assert!(out.images.is_empty());
assert_eq!(out.skipped.len(), 1, "{:?}", out.skipped);
}