OCIO: refuse a .csp LUT whose header claims an oversized table before reserving it (#2938)

This commit is contained in:
Dylan M. Taylor authored and GitHub committed 2026-11-03 20:23:08 -05:00
1 parent 6572514eb1
commit 58ca9c1c1b
2 files changed
+30 -7

No files matched your search

+22 -7
View File
@@ -57,7 +57,7 @@ pub fn parse_spi1d(text: &str) -> Option<Lut> {
}
}
// A 1D table is interpolated by index, so cap it like the lattices (2²⁴ entries).
if !(2..=1 << 24).contains(&len)
if !(2..=MAX_1D).contains(&len)
|| !matches!(comps, 1 | 3)
|| values.len() != len.checked_mul(comps)?
|| from[1] <= from[0]
@@ -79,7 +79,7 @@ pub fn parse_spi3d(text: &str) -> Option<Lut> {
lines.next()?;
let size: Vec<usize> = lines.next()?.split_whitespace().map(|t| t.parse().ok()).collect::<Option<_>>()?;
let n = *size.first()?;
if size.len() != 3 || size.iter().any(|s| *s != n) || !(2..=129).contains(&n) {
if size.len() != 3 || size.iter().any(|s| *s != n) || !(2..=MAX_LATTICE).contains(&n) {
return None;
}
let mut d3 = vec![[0.0f32; 3]; n * n * n];
@@ -154,6 +154,11 @@ pub fn parse_3dl(text: &str) -> Option<Lut> {
Some(Lut { n1: 0, d1: vec![], n3: n, d3, dmin: [0.0; 3], dmax: [1.0; 3] })
}
/// Largest lattice side the lattice readers accept (`.spi3d`, `.csp`).
const MAX_LATTICE: usize = 129;
/// Most entries of a 1D table (interpolated by index, so capped like the lattices: 2²⁴).
const MAX_1D: usize = 1 << 24;
/// Parse a cineSpace `.csp` LUT (1D or 3D, with its three pre-LUT shapers).
pub fn parse_csp(text: &str) -> Option<(Lut, [Vec<(f32, f32)>; 3])> {
let mut lines = text.lines().map(str::trim).filter(|l| !l.is_empty());
@@ -185,8 +190,15 @@ pub fn parse_csp(text: &str) -> Option<(Lut, [Vec<(f32, f32)>; 3])> {
if sz.len() != 3 || sz[0] != sz[1] || sz[1] != sz[2] {
return None;
}
let n = sz[0] as usize;
let mut d3 = Vec::with_capacity(n * n * n);
// The size comes from the file: an integer lattice size the other lattice readers accept
// (2..=129), checked before anything is allocated. Rows are read before they are stored,
// so a short file never reserves the table it claims.
let n = sz[0];
if n.fract() != 0.0 || !(2.0..=MAX_LATTICE as f32).contains(&n) {
return None;
}
let n = n as usize;
let mut d3 = Vec::new();
for _ in 0..n * n * n {
let v = nums(lines.next()?)?;
if v.len() != 3 {
@@ -194,10 +206,13 @@ pub fn parse_csp(text: &str) -> Option<(Lut, [Vec<(f32, f32)>; 3])> {
}
d3.push([v[0], v[1], v[2]]);
}
(n >= 2).then_some((Lut { n1: 0, d1: vec![], n3: n, d3, dmin: [0.0; 3], dmax: [1.0; 3] }, shaper))
Some((Lut { n1: 0, d1: vec![], n3: n, d3, dmin: [0.0; 3], dmax: [1.0; 3] }, shaper))
} else {
let n: usize = lines.next()?.parse().ok()?;
let mut d1 = Vec::with_capacity(n);
if !(2..=MAX_1D).contains(&n) {
return None;
}
let mut d1 = Vec::new();
for _ in 0..n {
let v = nums(lines.next()?)?;
if v.len() != 3 {
@@ -205,7 +220,7 @@ pub fn parse_csp(text: &str) -> Option<(Lut, [Vec<(f32, f32)>; 3])> {
}
d1.push([v[0], v[1], v[2]]);
}
(n >= 2).then_some((Lut { n1: n, d1, n3: 0, d3: vec![], dmin: [0.0; 3], dmax: [1.0; 3] }, shaper))
Some((Lut { n1: n, d1, n3: 0, d3: vec![], dmin: [0.0; 3], dmax: [1.0; 3] }, shaper))
}
}
+8
View File
@@ -179,6 +179,14 @@ fn lut_files_parse_and_interpolate() {
assert!((o[0] - 0.25).abs() < 1e-6 && (o[1] - 0.4).abs() < 1e-6, "{o:?}");
let inv = x.apply(o, 2, true, "");
assert!((inv[0] - 0.5).abs() < 1e-4, "{inv:?}");
// A .csp whose header claims a huge or fractional table is refused before anything is reserved.
let head = "CSPLUTV100\n{k}\n\n2\n0 1\n0 1\n2\n0 1\n0 1\n2\n0 1\n0 1\n\n";
for (k, size) in [("3D", "100000 100000 100000"), ("3D", "130 130 130"), ("3D", "2.5 2.5 2.5"), ("1D", "4000000000000")] {
let text = head.replace("{k}", k) + size + "\n0 0 0\n";
assert!(parse_file(&text, "csp").is_none(), "{k} {size}");
}
// A short file that claims a valid size is refused once its rows run out.
assert!(parse_file(&(head.replace("{k}", "3D") + "129 129 129\n0 0 0\n"), "csp").is_none());
// CDL collection: pick by id.
let ccc = r#"<ColorCorrectionCollection><ColorCorrection id="a"><SOPNode><Slope>2 2 2</Slope><Offset>0 0 0</Offset><Power>1 1 1</Power></SOPNode></ColorCorrection>
<ColorCorrection id='b'><SOPNode><Slope>0.5 0.5 0.5</Slope></SOPNode><SatNode><Saturation>1</Saturation></SatNode></ColorCorrection></ColorCorrectionCollection>"#;