diff --git a/crates/cms/src/profile.rs b/crates/cms/src/profile.rs index b7e389b7b..10a068830 100644 --- a/crates/cms/src/profile.rs +++ b/crates/cms/src/profile.rs @@ -736,24 +736,28 @@ fn parse_lut(d: &[u8], a2b: bool) -> Result { return Err(CmsError::Invalid("bad lutAB dimensions".into())); } let (ob, om, omc, oc, oa) = (r.u32(12)? as usize, r.u32(16)? as usize, r.u32(20)? as usize, r.u32(24)? as usize, r.u32(28)? as usize); + // Offsets come straight from the file: each element is cut out with one checked slice + // and read with local offsets, so no `off + k` can wrap (wasm32, overflow checks off). let matrix = |off: usize| -> Result { + let e = Reader { b: r.slice(off, 48)? }; let mut m = vec![0.0; 9]; for (k, v) in m.iter_mut().enumerate() { - *v = r.s15f16(off + k * 4)?; + *v = e.s15f16(k * 4)?; } - let offset = vec![r.s15f16(off + 36)?, r.s15f16(off + 40)?, r.s15f16(off + 44)?]; + let offset = vec![e.s15f16(36)?, e.s15f16(40)?, e.s15f16(44)?]; Ok(Stage::Matrix { rows: 3, cols: 3, m, offset }) }; let clut = |off: usize, cin: usize, cout: usize| -> Result { + let h = Reader { b: r.slice(off, 20)? }; let mut grid = Vec::with_capacity(cin); for k in 0..cin { - let g = r.u8(off + k)? as usize; + let g = h.u8(k)? as usize; if g < 1 { return Err(CmsError::Invalid("zero grid points".into())); } grid.push(g); } - let prec = r.u8(off + 16)? as usize; + let prec = h.u8(16)? as usize; let nodes: u128 = grid.iter().map(|g| *g as u128).product(); if nodes * cout as u128 > MAX_CLUT_VALUES as u128 { return Err(CmsError::Invalid("CLUT too large".into())); @@ -887,6 +891,25 @@ mod tests { assert!(parse_text(&d).is_empty()); } + #[test] + fn mab_offsets_near_the_end_of_the_address_space_are_errors() { + // Offsets are file-controlled u32s: element reads must be checked slices, never `off + k`. + for tag in [b"mAB ", b"mBA "] { + for off in [u32::MAX, u32::MAX - 3, u32::MAX - 19, u32::MAX - 47] { + for (ob, om, omc, oc, oa) in [(off, 0, 0, 0, 0), (32, off, 32, 0, 0), (32, 32, 32, off, 32), (32, 0, 0, 32, off)] { + let mut e = tag.to_vec(); + e.extend([0u8; 4]); + e.extend([3, 3, 0, 0]); + for v in [ob, om, omc, oc, oa] { + e.extend(v.to_be_bytes()); + } + e.resize(256, 0); + assert!(parse_lut(&e, true).is_err() || parse_lut(&e, false).is_err()); + } + } + } + } + /// `p` with the content hash of `other`: the hash is a fixed-key 64-bit SipHash, so a /// crafted profile can have another's. Setting it stands in for finding one. fn with_hash_of(p: &Profile, other: &Profile) -> Profile {