Skip to main content

concinnity_core/decode/
size.rs

1//! Checked arithmetic over sizes read out of an untrusted buffer. Decoders take
2//! dimensions from the payload itself, so every product and sum derived from
3//! them is attacker-reachable and has to be range-checked before it is used as
4//! a length.
5
6use alloc::format;
7use alloc::string::String;
8
9/// Product of `factors`, or an error naming `label` if it overflows `usize`.
10pub fn checked_product(label: &str, factors: &[usize]) -> Result<usize, String> {
11    let mut acc: usize = 1;
12    for f in factors {
13        acc = acc
14            .checked_mul(*f)
15            .ok_or_else(|| format!("{} size overflow in {:?}", label, factors))?;
16    }
17    Ok(acc)
18}
19
20// Sum of `terms`, or an error naming `label` if it overflows `usize`.
21#[cfg(test)]
22pub(crate) fn checked_sum(label: &str, terms: &[usize]) -> Result<usize, String> {
23    let mut acc: usize = 0;
24    for t in terms {
25        acc = acc
26            .checked_add(*t)
27            .ok_or_else(|| format!("{} offset overflow in {:?}", label, terms))?;
28    }
29    Ok(acc)
30}
31
32#[cfg(test)]
33mod tests {
34    use super::*;
35
36    #[test]
37    fn product_multiplies() {
38        assert_eq!(checked_product("t", &[2, 3, 4]).unwrap(), 24);
39    }
40
41    #[test]
42    fn empty_product_is_one() {
43        assert_eq!(checked_product("t", &[]).unwrap(), 1);
44    }
45
46    #[test]
47    fn product_zero_short_circuits_safely() {
48        assert_eq!(checked_product("t", &[0, usize::MAX]).unwrap(), 0);
49    }
50
51    #[test]
52    fn product_reports_overflow() {
53        let err = checked_product("atlas", &[usize::MAX, 2]).unwrap_err();
54        assert!(err.contains("atlas"), "{}", err);
55        assert!(err.contains("overflow"), "{}", err);
56    }
57
58    // The concrete shape that broke font decoding: a 32-bit dimension pair
59    // whose product exceeds usize once the bytes-per-texel factor is applied.
60    #[test]
61    fn product_reports_overflow_for_max_dimensions() {
62        let w = u32::MAX as usize;
63        assert!(checked_product("atlas", &[w, w, 4]).is_err());
64    }
65
66    #[test]
67    fn sum_adds() {
68        assert_eq!(checked_sum("t", &[1, 2, 3]).unwrap(), 6);
69    }
70
71    #[test]
72    fn empty_sum_is_zero() {
73        assert_eq!(checked_sum("t", &[]).unwrap(), 0);
74    }
75
76    #[test]
77    fn sum_reports_overflow() {
78        let err = checked_sum("payload", &[usize::MAX, 1]).unwrap_err();
79        assert!(err.contains("payload"), "{}", err);
80        assert!(err.contains("overflow"), "{}", err);
81    }
82}