commit a3403d68996d1d688d8597c21f368bdf4d4a13d7
parent 0ffd27253cdac69d570a6ec7a1848acb3f9f54c5
Author: triesap <tyson@radroots.org>
Date: Thu, 9 Jul 2026 04:41:44 +0000
mesh: reject noncanonical cbor widths
Diffstat:
2 files changed, 82 insertions(+), 7 deletions(-)
diff --git a/crates/mesh/src/cbor.rs b/crates/mesh/src/cbor.rs
@@ -149,22 +149,38 @@ impl<'a> Cursor<'a> {
}
match initial & 0x1f {
value @ 0..=23 => Ok(u64::from(value)),
- 24 => Ok(u64::from(self.read_byte()?)),
+ 24 => {
+ let value = u64::from(self.read_byte()?);
+ if value < 24 {
+ return Err(RadrootsMeshError::InvalidCbor);
+ }
+ Ok(value)
+ }
25 => {
let bytes = self.read_exact(2)?;
- Ok(u64::from(u16::from_be_bytes([bytes[0], bytes[1]])))
+ let value = u64::from(u16::from_be_bytes([bytes[0], bytes[1]]));
+ if value < 0x100 {
+ return Err(RadrootsMeshError::InvalidCbor);
+ }
+ Ok(value)
}
26 => {
let bytes = self.read_exact(4)?;
- Ok(u64::from(u32::from_be_bytes([
- bytes[0], bytes[1], bytes[2], bytes[3],
- ])))
+ let value = u64::from(u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]));
+ if value < 0x1_0000 {
+ return Err(RadrootsMeshError::InvalidCbor);
+ }
+ Ok(value)
}
27 => {
let bytes = self.read_exact(8)?;
- Ok(u64::from_be_bytes([
+ let value = u64::from_be_bytes([
bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7],
- ]))
+ ]);
+ if value < 0x1_0000_0000 {
+ return Err(RadrootsMeshError::InvalidCbor);
+ }
+ Ok(value)
}
_ => Err(RadrootsMeshError::InvalidCbor),
}
diff --git a/crates/mesh/tests/mesh.rs b/crates/mesh/tests/mesh.rs
@@ -13,6 +13,16 @@ fn default_frame() -> RadrootsMeshFrame {
)
}
+fn default_encoded_with_replacement(
+ start: usize,
+ end: usize,
+ replacement: impl IntoIterator<Item = u8>,
+) -> Vec<u8> {
+ let mut encoded = encode_mesh_frame_cbor(&default_frame()).expect("encode default");
+ encoded.splice(start..end, replacement);
+ encoded
+}
+
#[test]
fn default_frame_encodes_as_mesh_frame_v1_cddl_cbor() {
let frame = default_frame();
@@ -197,6 +207,55 @@ fn cbor_codec_covers_extended_integer_widths() {
}
#[test]
+fn decoder_rejects_noncanonical_cbor_widths() {
+ let cases = [
+ (
+ "over-wide map length",
+ default_encoded_with_replacement(0, 1, [0xb8, 0x07]),
+ ),
+ (
+ "over-wide key",
+ default_encoded_with_replacement(1, 2, [0x18, 0x00]),
+ ),
+ (
+ "over-wide version",
+ default_encoded_with_replacement(2, 3, [0x18, 0x01]),
+ ),
+ (
+ "over-wide frame type",
+ default_encoded_with_replacement(4, 5, [0x1a, 0x00, 0x00, 0x00, 0x00]),
+ ),
+ (
+ "over-wide text length",
+ default_encoded_with_replacement(6, 7, [0x78, 0x05]),
+ ),
+ (
+ "over-wide created-at",
+ default_encoded_with_replacement(24, 26, [0x19, 0x00, 0x2a]),
+ ),
+ (
+ "over-wide ttl",
+ default_encoded_with_replacement(
+ 27,
+ 30,
+ [0x1b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xea, 0x60],
+ ),
+ ),
+ (
+ "over-wide forbidden byte payload length",
+ default_encoded_with_replacement(31, 32, [0x58, 0x03, 1, 2, 3]),
+ ),
+ ];
+
+ for (label, encoded) in cases {
+ assert_eq!(
+ decode_mesh_frame_cbor(&encoded).expect_err(label),
+ RadrootsMeshError::InvalidCbor
+ );
+ }
+}
+
+#[test]
fn decoder_rejects_previous_five_field_frame_shape() {
let previous_shape = vec![
0xa5, 0x01, 0x01, 0x02, 0x65, b'l', b'o', b'c', b'a', b'l', 0x03, 0x71, b'p', b'a', b'y',