Performance, security and provenance hardening (ann/io/migrate/agent) + two audit fixes #2
@@ -204,11 +204,25 @@ fn read_uint(data: &[u8], offset: usize, nbytes: usize) -> Result<u64, FormatErr
|
||||
})
|
||||
}
|
||||
|
||||
/// Maximum recursion depth for nested datatypes (Compound/Enumeration/
|
||||
/// VariableLength/Array). A crafted file can nest a message-size-capped
|
||||
/// (65535 byte) datatype message ~8000 levels deep, which would blow the
|
||||
/// stack — especially on the project's no_std/embedded targets where
|
||||
/// available stack is a few KB.
|
||||
const MAX_DATATYPE_DEPTH: u16 = 64;
|
||||
|
||||
impl Datatype {
|
||||
/// Parse a datatype message from raw bytes.
|
||||
///
|
||||
/// Returns `(Datatype, bytes_consumed)` for recursive parsing.
|
||||
pub fn parse(data: &[u8]) -> Result<(Datatype, usize), FormatError> {
|
||||
Self::parse_with_depth(data, 0)
|
||||
}
|
||||
|
||||
fn parse_with_depth(data: &[u8], depth: u16) -> Result<(Datatype, usize), FormatError> {
|
||||
if depth >= MAX_DATATYPE_DEPTH {
|
||||
return Err(FormatError::NestingDepthExceeded);
|
||||
}
|
||||
// Minimum header: 4 bytes (class_and_version + 3 bytes bit field) + 4 bytes size = 8
|
||||
ensure_len(data, 0, 8)?;
|
||||
|
||||
@@ -358,7 +372,7 @@ impl Datatype {
|
||||
pos += name_len;
|
||||
let byte_offset = read_uint(data, pos, ob)?;
|
||||
pos += ob;
|
||||
let (member_dt, consumed) = Datatype::parse(&data[pos..])?;
|
||||
let (member_dt, consumed) = Self::parse_with_depth(&data[pos..], depth + 1)?;
|
||||
pos += consumed;
|
||||
members.push(CompoundMember {
|
||||
name,
|
||||
@@ -384,7 +398,7 @@ impl Datatype {
|
||||
// dimensionality(1) + reserved(3) + dim_perm(4) + 4 dim slots(16) = 24
|
||||
ensure_len(data, pos, 24)?;
|
||||
pos += 24;
|
||||
let (member_dt, consumed) = Datatype::parse(&data[pos..])?;
|
||||
let (member_dt, consumed) = Self::parse_with_depth(&data[pos..], depth + 1)?;
|
||||
pos += consumed;
|
||||
members.push(CompoundMember {
|
||||
name,
|
||||
@@ -415,7 +429,7 @@ impl Datatype {
|
||||
// Enumeration
|
||||
let num_members = (bf0 as u16) | ((bf1 as u16) << 8);
|
||||
// Parse base type
|
||||
let (base_type, base_consumed) = Datatype::parse(&data[pos..])?;
|
||||
let (base_type, base_consumed) = Self::parse_with_depth(&data[pos..], depth + 1)?;
|
||||
pos += base_consumed;
|
||||
let base_size = base_type.type_size();
|
||||
let mut members = Vec::with_capacity(num_members as usize);
|
||||
@@ -468,7 +482,7 @@ impl Datatype {
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let (base_type, consumed) = Datatype::parse(&data[pos..])?;
|
||||
let (base_type, consumed) = Self::parse_with_depth(&data[pos..], depth + 1)?;
|
||||
pos += consumed;
|
||||
Ok((
|
||||
Datatype::VariableLength {
|
||||
@@ -494,7 +508,7 @@ impl Datatype {
|
||||
}
|
||||
// skip permutation indices
|
||||
pos += ndims * 4;
|
||||
let (base_type, consumed) = Datatype::parse(&data[pos..])?;
|
||||
let (base_type, consumed) = Self::parse_with_depth(&data[pos..], depth + 1)?;
|
||||
pos += consumed;
|
||||
Ok((
|
||||
Datatype::Array {
|
||||
@@ -515,7 +529,7 @@ impl Datatype {
|
||||
dimensions.push(LittleEndian::read_u32(&data[pos..pos + 4]));
|
||||
pos += 4;
|
||||
}
|
||||
let (base_type, consumed) = Datatype::parse(&data[pos..])?;
|
||||
let (base_type, consumed) = Self::parse_with_depth(&data[pos..], depth + 1)?;
|
||||
pos += consumed;
|
||||
Ok((
|
||||
Datatype::Array {
|
||||
@@ -545,7 +559,7 @@ impl Datatype {
|
||||
pos += name_len;
|
||||
let byte_offset = read_uint(data, pos, ob)?;
|
||||
pos += ob;
|
||||
let (member_dt, consumed) = Datatype::parse(&data[pos..])?;
|
||||
let (member_dt, consumed) = Self::parse_with_depth(&data[pos..], depth + 1)?;
|
||||
pos += consumed;
|
||||
members.push(CompoundMember {
|
||||
name,
|
||||
@@ -814,6 +828,39 @@ mod tests {
|
||||
buf
|
||||
}
|
||||
|
||||
/// A crafted datatype message nesting Variable-Length wrappers deeper
|
||||
/// than `MAX_DATATYPE_DEPTH` must return `NestingDepthExceeded`
|
||||
/// instead of overflowing the stack.
|
||||
#[test]
|
||||
fn nested_variable_length_exceeds_depth_limit() {
|
||||
// Each VL level is just an 8-byte header (class 9, vl_type=0 =>
|
||||
// sequence, no padding/charset fields) immediately followed by the
|
||||
// next level's bytes, terminated by a fixed-point base type.
|
||||
let levels = MAX_DATATYPE_DEPTH as usize + 10;
|
||||
let mut data = Vec::new();
|
||||
for _ in 0..levels {
|
||||
data.extend_from_slice(&build_dt_header(9, 3, [0, 0, 0], 0));
|
||||
}
|
||||
data.extend_from_slice(&build_fixed_point(4, false, false, 0, 32));
|
||||
|
||||
let result = Datatype::parse(&data);
|
||||
assert!(matches!(result, Err(FormatError::NestingDepthExceeded)));
|
||||
}
|
||||
|
||||
/// A datatype nested just within the depth limit must still parse fine.
|
||||
#[test]
|
||||
fn nested_variable_length_within_depth_limit_ok() {
|
||||
let levels = MAX_DATATYPE_DEPTH as usize - 1;
|
||||
let mut data = Vec::new();
|
||||
for _ in 0..levels {
|
||||
data.extend_from_slice(&build_dt_header(9, 3, [0, 0, 0], 0));
|
||||
}
|
||||
data.extend_from_slice(&build_fixed_point(4, false, false, 0, 32));
|
||||
|
||||
let result = Datatype::parse(&data);
|
||||
assert!(result.is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fixed_point_u8() {
|
||||
let data = build_fixed_point(1, false, false, 0, 8);
|
||||
|
||||
Reference in New Issue
Block a user