lib

Core libraries for Radroots
git clone https://radroots.dev/git/lib.git
Log | Files | Refs | README

commit a3403d68996d1d688d8597c21f368bdf4d4a13d7
parent 0ffd27253cdac69d570a6ec7a1848acb3f9f54c5
Author: triesap <tyson@radroots.org>
Date:   Thu,  9 Jul 2026 04:41:44 +0000

mesh: reject noncanonical cbor widths

Diffstat:
Mcrates/mesh/src/cbor.rs | 30+++++++++++++++++++++++-------
Mcrates/mesh/tests/mesh.rs | 59+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
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',