craft-cms: read mAB/mBA elements through one checked slice (#823)

This commit is contained in:
Dylan M. Taylor authored and GitHub committed 2026-11-11 14:41:43 -05:00
1 parent b0a2510aac
commit 725ce2bdf2
1 file changed
+27 -4
+27 -4
View File
@@ -736,24 +736,28 @@ fn parse_lut(d: &[u8], a2b: bool) -> Result<Lut, CmsError> {
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<Stage, CmsError> {
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<Stage, CmsError> {
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 {