use crate::{
metadata::streams::StreamHeader,
utils::{read_le, read_le_at},
Result,
};
use std::io::Write;
pub const CIL_HEADER_MAGIC: u32 = 0x424A_5342;
const MIN_ROOT_HEADER_SIZE: usize = 36;
const VERSION_LENGTH_OFFSET: usize = 12;
const VERSION_STRING_OFFSET: u16 = 16;
const FLAGS_FIELD_SIZE: usize = 2;
const STREAM_COUNT_FIELD_SIZE: usize = 2;
const MAX_VERSION_STRING_LENGTH: usize = 255;
const MAX_STREAM_COUNT: u16 = 6;
const MIN_STREAM_HEADER_SIZE: usize = 12;
const STREAM_HEADER_FIXED_SIZE: usize = 8;
const MAX_STREAM_NAME_LENGTH: usize = 32;
const FIELD_OFFSET_MAJOR_VERSION: usize = 4;
const FIELD_OFFSET_MINOR_VERSION: usize = 6;
const FIELD_OFFSET_RESERVED: usize = 8;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Root {
pub signature: u32,
pub major_version: u16,
pub minor_version: u16,
pub reserved: u32,
pub length: u32,
pub version: String,
pub flags: u16,
pub stream_number: u16,
pub stream_headers: Vec<StreamHeader>,
}
impl Root {
pub fn read(data: &[u8]) -> Result<Root> {
if data.len() < MIN_ROOT_HEADER_SIZE {
return Err(out_of_bounds_error!());
}
let signature = read_le::<u32>(data)?;
if signature != CIL_HEADER_MAGIC {
return Err(malformed_error!(
"Root: invalid signature 0x{:08X}, expected 0x{:08X} [ECMA-335 §II.24.2.1]",
signature,
CIL_HEADER_MAGIC
));
}
let version_string_length = read_le_at::<u32>(data, &mut { VERSION_LENGTH_OFFSET })?;
match version_string_length.checked_add(u32::from(VERSION_STRING_OFFSET)) {
Some(str_end) => {
let data_len = u32::try_from(data.len()).map_err(|_| {
malformed_error!("Root: data length too large [ECMA-335 §II.24.2.1]")
})?;
if str_end > data_len {
return Err(out_of_bounds_error!());
}
}
None => {
return Err(malformed_error!(
"Root: version string length {} causes integer overflow [ECMA-335 §II.24.2.1]",
version_string_length
))
}
}
let mut version_string: String = String::with_capacity(version_string_length as usize);
for counter in 0..version_string_length {
version_string.push(char::from(read_le_at::<u8>(
data,
&mut (usize::from(VERSION_STRING_OFFSET) + counter as usize),
)?));
}
if version_string.is_empty() {
return Err(malformed_error!(
"Root: version string cannot be empty [ECMA-335 §II.24.2.1]"
));
}
if !version_string.starts_with('v') {
return Err(malformed_error!(
"Root: version string '{}' must start with 'v' [ECMA-335 §II.24.2.1]",
version_string
));
}
if version_string.len() > MAX_VERSION_STRING_LENGTH {
return Err(malformed_error!(
"Root: version string length {} exceeds reasonable limit ({}) [ECMA-335 §II.24.2.1]",
version_string.len(),
MAX_VERSION_STRING_LENGTH
));
}
let mut stream_count_offset =
version_string.len() + usize::from(VERSION_STRING_OFFSET) + FLAGS_FIELD_SIZE;
let stream_count = read_le_at::<u16>(data, &mut stream_count_offset)?;
if stream_count == 0
|| stream_count > MAX_STREAM_COUNT
|| (stream_count as usize * MIN_STREAM_HEADER_SIZE) > data.len()
{
return Err(malformed_error!(
"Root: invalid stream count {} (must be 1-{}) [ECMA-335 §II.24.2.1]",
stream_count,
MAX_STREAM_COUNT
));
}
let mut streams = Vec::with_capacity(stream_count as usize);
let mut stream_offset = version_string.len()
+ usize::from(VERSION_STRING_OFFSET)
+ FLAGS_FIELD_SIZE
+ STREAM_COUNT_FIELD_SIZE;
let mut streams_seen = [false; MAX_STREAM_COUNT as usize];
for _i in 0..stream_count {
if stream_offset > data.len() {
return Err(out_of_bounds_error!());
}
let new_stream = StreamHeader::from(&data[stream_offset..])?;
if new_stream.offset as usize > data.len()
|| new_stream.size as usize > data.len()
|| new_stream.name.len() > MAX_STREAM_NAME_LENGTH
{
return Err(out_of_bounds_error!());
}
match u32::checked_add(new_stream.offset, new_stream.size) {
Some(range) => {
if range as usize > data.len() {
return Err(out_of_bounds_error!());
}
}
None => {
return Err(malformed_error!(
"Root: stream '{}' offset {} + size {} causes integer overflow [ECMA-335 §II.24.2.2]",
new_stream.name,
new_stream.offset,
new_stream.size
))
}
}
let stream_index = match new_stream.name.as_str() {
"#Strings" => 0,
"#US" => 1,
"#Blob" => 2,
"#GUID" => 3,
"#~" => 4,
"#-" => 5,
_ => unreachable!("StreamHeader::from() should have validated the name"),
};
if streams_seen[stream_index] {
return Err(malformed_error!(
"Root: duplicate stream '{}' found [ECMA-335 §II.24.2.2]",
new_stream.name
));
}
streams_seen[stream_index] = true;
let name_aligned = ((new_stream.name.len() + 1) + 3) & !3;
stream_offset += STREAM_HEADER_FIXED_SIZE + name_aligned;
streams.push(new_stream);
}
if streams.is_empty() {
return Err(malformed_error!(
"Root: no valid streams found [ECMA-335 §II.24.2.1]"
));
}
Ok(Root {
signature,
major_version: read_le::<u16>(&data[FIELD_OFFSET_MAJOR_VERSION..])?,
minor_version: read_le::<u16>(&data[FIELD_OFFSET_MINOR_VERSION..])?,
reserved: read_le::<u32>(&data[FIELD_OFFSET_RESERVED..])?,
length: u32::try_from(version_string.len()).map_err(|_| {
malformed_error!("Root: version string length too large [ECMA-335 §II.24.2.1]")
})?,
flags: read_le::<u16>(
&data[usize::from(VERSION_STRING_OFFSET) + version_string.len()..],
)?,
stream_number: u16::try_from(streams.len())
.map_err(|_| malformed_error!("Root: too many streams [ECMA-335 §II.24.2.1]"))?,
stream_headers: streams,
version: version_string,
})
}
pub fn validate_stream_layout(
&self,
meta_root_offset: usize,
total_metadata_size: u32,
) -> Result<()> {
let mut stream_ranges: Vec<(u32, u32, &str)> = Vec::new();
let metadata_end = meta_root_offset
.checked_add(total_metadata_size as usize)
.ok_or_else(|| {
malformed_error!(
"Metadata size causes overflow: {} + {}",
meta_root_offset,
total_metadata_size
)
})?;
for stream in &self.stream_headers {
let absolute_start = meta_root_offset
.checked_add(stream.offset as usize)
.ok_or_else(|| {
malformed_error!(
"Stream '{}' offset causes overflow: {} + {}",
stream.name,
meta_root_offset,
stream.offset
)
})?;
let absolute_end = absolute_start
.checked_add(stream.size as usize)
.ok_or_else(|| {
malformed_error!(
"Stream '{}' size causes overflow: {} + {}",
stream.name,
absolute_start,
stream.size
)
})?;
if absolute_end > metadata_end {
return Err(malformed_error!(
"Stream '{}' extends beyond metadata bounds (end {} > metadata end {})",
stream.name,
absolute_end,
metadata_end
));
}
stream_ranges.push((
u32::try_from(absolute_start).map_err(|_| {
malformed_error!(
"Stream '{}' start position {} exceeds u32 range",
stream.name,
absolute_start
)
})?,
u32::try_from(absolute_end).map_err(|_| {
malformed_error!(
"Stream '{}' end position {} exceeds u32 range",
stream.name,
absolute_end
)
})?,
&stream.name,
));
}
for (i, &(start1, end1, name1)) in stream_ranges.iter().enumerate() {
for &(start2, end2, name2) in stream_ranges.iter().skip(i + 1) {
if start1 < end2 && start2 < end1 {
return Err(malformed_error!(
"Stream '{}' ({}..{}) overlaps with stream '{}' ({}..{})",
name1,
start1,
end1,
name2,
start2,
end2
));
}
}
}
Ok(())
}
pub fn write_to<W: Write>(&self, writer: &mut W) -> Result<()> {
writer.write_all(&self.signature.to_le_bytes())?;
writer.write_all(&self.major_version.to_le_bytes())?;
writer.write_all(&self.minor_version.to_le_bytes())?;
writer.write_all(&self.reserved.to_le_bytes())?;
let version_bytes = self.version.as_bytes();
let padded_len = (version_bytes.len() + 3) & !3;
let padded_len_u32 = u32::try_from(padded_len).map_err(|_| {
malformed_error!(
"Version string padded length {} exceeds u32 range",
padded_len
)
})?;
writer.write_all(&padded_len_u32.to_le_bytes())?;
writer.write_all(version_bytes)?;
let padding = padded_len - version_bytes.len();
if padding > 0 {
writer.write_all(&vec![0u8; padding])?;
}
writer.write_all(&self.flags.to_le_bytes())?;
writer.write_all(&self.stream_number.to_le_bytes())?;
for stream in &self.stream_headers {
stream.write_to(writer)?;
}
Ok(())
}
#[must_use]
pub fn serialized_size(&self) -> usize {
let fixed_size = 20;
let version_padded = (self.version.len() + 3) & !3;
let streams_size: usize = self
.stream_headers
.iter()
.map(crate::StreamHeader::serialized_size)
.sum();
fixed_size + version_padded + streams_size
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn crafted() {
#[rustfmt::skip]
let header_bytes = [
0x42, 0x53, 0x4A, 0x42,
0x00, 0x20,
0x00, 0x30,
0x00, 0x00, 0x00, 0x40,
0x06, 0x00, 0x00, 0x00, b'v', b'4', b'.', b'0', b'.', b'0',
0x00, 0x60,
0x01, 0x00,
0x1, 0x00, 0x00, 0x00, 0x8, 0x00, 0x00, 0x00,
0x23, 0x7E, 0x00,
];
let parsed_header = Root::read(&header_bytes).unwrap();
assert_eq!(parsed_header.signature, CIL_HEADER_MAGIC);
assert_eq!(parsed_header.major_version, 0x2000);
assert_eq!(parsed_header.minor_version, 0x3000);
assert_eq!(parsed_header.reserved, 0x40000000);
assert_eq!(parsed_header.length, 6);
assert_eq!(parsed_header.version, "v4.0.0");
assert_eq!(parsed_header.flags, 0x6000);
assert_eq!(parsed_header.stream_number, 1);
assert_eq!(parsed_header.stream_headers.len(), 1);
assert_eq!(parsed_header.stream_headers[0].offset, 0x1);
assert_eq!(parsed_header.stream_headers[0].size, 0x8);
assert_eq!(parsed_header.stream_headers[0].name, "#~");
}
#[test]
fn duplicate_stream_names_should_fail() {
#[rustfmt::skip]
let mut header_bytes = vec![
0x42, 0x53, 0x4A, 0x42, 0x00, 0x20, 0x00, 0x30, 0x00, 0x00, 0x00, 0x40, 0x06, 0x00, 0x00, 0x00, b'v', b'4', b'.', b'0', b'.', b'0', 0x00, 0x60, 0x02, 0x00,
0x52, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x23, 0x7E, 0x00, 0x00,
0x5A, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x23, 0x7E, 0x00, 0x00, ];
header_bytes.resize(98, 0x00);
let result = Root::read(&header_bytes);
let err = result.expect_err("Expected error for duplicate stream names");
let error_string = err.to_string();
assert!(
error_string.contains("duplicate stream") && error_string.contains("#~"),
"Expected error about duplicate stream '#~', got: {}",
error_string
);
}
#[test]
fn test_write_to_basic() {
let root = Root {
signature: CIL_HEADER_MAGIC,
major_version: 1,
minor_version: 1,
reserved: 0,
length: 12, version: "v4.0.30319".to_string(),
flags: 0,
stream_number: 1,
stream_headers: vec![StreamHeader {
offset: 0x6C,
size: 0x1000,
name: "#~".to_string(),
}],
};
let mut buffer = Vec::new();
root.write_to(&mut buffer).unwrap();
assert_eq!(&buffer[0..4], &[0x42, 0x53, 0x4A, 0x42]);
assert_eq!(&buffer[4..6], &[0x01, 0x00]);
assert_eq!(&buffer[6..8], &[0x01, 0x00]);
assert_eq!(&buffer[8..12], &[0x00, 0x00, 0x00, 0x00]);
assert_eq!(&buffer[12..16], &[0x0C, 0x00, 0x00, 0x00]);
assert_eq!(&buffer[16..20], b"v4.0");
}
#[test]
fn test_serialized_size() {
let root = Root {
signature: CIL_HEADER_MAGIC,
major_version: 1,
minor_version: 1,
reserved: 0,
length: 12,
version: "v4.0.30319".to_string(), flags: 0,
stream_number: 1,
stream_headers: vec![StreamHeader {
offset: 0x6C,
size: 0x1000,
name: "#~".to_string(), }],
};
assert_eq!(root.serialized_size(), 44);
}
#[test]
fn test_serialized_size_multiple_streams() {
let root = Root {
signature: CIL_HEADER_MAGIC,
major_version: 1,
minor_version: 1,
reserved: 0,
length: 12,
version: "v4.0.30319".to_string(),
flags: 0,
stream_number: 3,
stream_headers: vec![
StreamHeader {
offset: 0x6C,
size: 0x1000,
name: "#~".to_string(),
}, StreamHeader {
offset: 0x106C,
size: 0x500,
name: "#Strings".to_string(),
}, StreamHeader {
offset: 0x156C,
size: 0x100,
name: "#Blob".to_string(),
}, ],
};
assert_eq!(root.serialized_size(), 80);
}
}