fix(cms): reject malformed hex data in .look LUTs (#8657)

Co-authored-by: Lucenx9 <185146821+Lucenx9@users.noreply.github.com>
This commit is contained in:
Simone
2026-10-23 17:40:16 -04:00
committed by GitHub
co-authored by Lucenx9
parent 7bc6fd8a4f
commit 4092fe79c9
+40 -1
View File
@@ -221,7 +221,14 @@ pub fn parse_look(text: &str) -> Result<LutFile, LutError> {
};
let size: usize = tag("size").and_then(|s| s.parse().ok()).ok_or_else(|| LutError("no <size> in .look".into()))?;
let (entries, _) = checked_data_len(size)?;
let hex: Vec<u8> = tag("data").ok_or_else(|| LutError("no <data> in .look".into()))?.bytes().filter(u8::is_ascii_hexdigit).collect();
let hex: Vec<u8> = tag("data").ok_or_else(|| LutError("no <data> in .look".into()))?.bytes().filter(|b| !b.is_ascii_whitespace()).collect();
if hex.iter().any(|b| !b.is_ascii_hexdigit()) {
return err("invalid hexadecimal <data> in .look");
}
// Each float needs eight hex digits; as_chunks would silently discard a partial value.
if !hex.len().is_multiple_of(8) {
return err("incomplete float in .look <data>");
}
let nib = |c: u8| (c as char).to_digit(16).unwrap_or(0) as u8;
let bytes: Vec<u8> = hex.as_chunks::<2>().0.iter().map(|p| nib(p[0]) << 4 | nib(p[1])).collect();
let floats: Vec<f32> = bytes.as_chunks::<4>().0.iter().map(|b| f32::from_le_bytes([b[0], b[1], b[2], b[3]])).collect();
@@ -456,6 +463,38 @@ DOMAIN_MAX 0.5 1 1
assert_eq!(parse("a.look", xml.as_bytes()).unwrap().data, id.data);
}
#[test]
fn look_rgb_and_rgba_allow_whitespace_in_hex_data() {
let id = LutFile::identity(2);
for channels in [3, 4] {
let mut hex = String::new();
for rgb in id.data.as_chunks::<3>().0 {
for value in rgb.iter().copied().chain((channels == 4).then_some(1.0)) {
for byte in value.to_le_bytes() {
hex.push_str(&format!("{byte:02x} \t\r\n"));
}
}
}
let xml = format!("<look><size>2</size><data>\"{hex}\"</data></look>");
assert_eq!(parse("valid.look", xml.as_bytes()).unwrap().data, id.data);
}
}
#[test]
fn look_rejects_partial_floats_and_invalid_hex() {
let id = LutFile::identity(2);
let hex: String = id.data.iter().flat_map(|v| v.to_le_bytes()).map(|b| format!("{b:02X}")).collect();
for suffix in ["0", "00", "0000", "000000", "G", "é"] {
let xml = format!("<look><size>2</size><data>{hex}{suffix}</data></look>");
assert!(parse("bad.look", xml.as_bytes()).is_err(), "accepted malformed suffix {suffix:?}");
}
let invalid = hex.replacen('0', "G", 1);
let xml = format!("<look><size>2</size><data>{invalid}</data></look>");
assert!(parse("bad.look", xml.as_bytes()).unwrap_err().0.contains("invalid hexadecimal"));
let xml = format!("<look><size>2</size><data>{hex}00</data></look>");
assert!(parse("bad.look", xml.as_bytes()).unwrap_err().0.contains("incomplete float"));
}
#[test]
fn look_3d_size_exponent_overflow_is_rejected() {
let text = format!("<look><size>{}</size><data>00</data></look>", usize::MAX);