use crate::{
metadata::{
cilassemblyview::CilAssemblyView,
validation::{
context::{RawValidationContext, ValidationContext},
traits::RawValidator,
},
},
Result,
};
pub struct RawHeapValidator;
impl RawHeapValidator {
#[must_use]
pub fn new() -> Self {
Self
}
fn validate_string_heap(assembly_view: &CilAssemblyView) -> Result<()> {
let streams = assembly_view.streams();
let strings_stream = streams.iter().find(|s| s.name == "#Strings");
if let Some(stream) = strings_stream {
if stream.size > 0x7FFF_FFFF {
return Err(malformed_error!(
"String heap (#Strings) size {} exceeds maximum allowed size",
stream.size
));
}
if stream.size % 4 != 0 {
return Err(malformed_error!(
"String heap (#Strings) size {} is not 4-byte aligned as required by ECMA-335",
stream.size
));
}
if stream.offset > 0x7FFF_FFFF {
return Err(malformed_error!(
"String heap (#Strings) offset {} exceeds maximum allowed offset",
stream.offset
));
}
}
Self::validate_string_heap_content(assembly_view)?;
Ok(())
}
fn validate_string_heap_content(assembly_view: &CilAssemblyView) -> Result<()> {
if let Some(strings) = assembly_view.strings() {
for (offset, string_data) in strings.iter() {
if std::str::from_utf8(string_data.as_bytes()).is_err() {
return Err(malformed_error!(
"String heap contains invalid UTF-8 sequence at offset {}",
offset
));
}
}
}
Ok(())
}
fn validate_blob_heap(assembly_view: &CilAssemblyView) -> Result<()> {
let streams = assembly_view.streams();
let blob_stream = streams.iter().find(|s| s.name == "#Blob");
if let Some(stream) = blob_stream {
if stream.size > 0x7FFF_FFFF {
return Err(malformed_error!(
"Blob heap (#Blob) size {} exceeds maximum allowed size",
stream.size
));
}
if stream.size % 4 != 0 {
return Err(malformed_error!(
"Blob heap (#Blob) size {} is not 4-byte aligned as required by ECMA-335",
stream.size
));
}
if stream.offset > 0x7FFF_FFFF {
return Err(malformed_error!(
"Blob heap (#Blob) offset {} exceeds maximum allowed offset",
stream.offset
));
}
}
Self::validate_blob_heap_content(assembly_view)?;
Ok(())
}
fn validate_blob_heap_content(assembly_view: &CilAssemblyView) -> Result<()> {
if let Some(blobs) = assembly_view.blobs() {
for (offset, blob_data) in blobs.iter() {
if blob_data.len() > 0x1FFF_FFFF {
return Err(malformed_error!(
"Blob at offset {} has excessive size {} bytes (max: {})",
offset,
blob_data.len(),
0x1FFF_FFFF
));
}
}
}
Ok(())
}
fn validate_guid_heap(assembly_view: &CilAssemblyView) -> Result<()> {
let streams = assembly_view.streams();
let guid_stream = streams.iter().find(|s| s.name == "#GUID");
if let Some(stream) = guid_stream {
if stream.size > 0x7FFF_FFFF {
return Err(malformed_error!(
"GUID heap (#GUID) size {} exceeds maximum allowed size",
stream.size
));
}
if stream.size % 16 != 0 {
return Err(malformed_error!(
"GUID heap (#GUID) size {} is not a multiple of 16 bytes (GUID size)",
stream.size
));
}
if stream.size % 4 != 0 {
return Err(malformed_error!(
"GUID heap (#GUID) size {} is not 4-byte aligned as required by ECMA-335",
stream.size
));
}
if stream.offset > 0x7FFF_FFFF {
return Err(malformed_error!(
"GUID heap (#GUID) offset {} exceeds maximum allowed offset",
stream.offset
));
}
}
Self::validate_guid_heap_content(assembly_view)?;
Ok(())
}
fn validate_guid_heap_content(assembly_view: &CilAssemblyView) -> Result<()> {
if let Some(guids) = assembly_view.guids() {
let mut guid_count = 0;
for (one_based_index, guid_data) in guids.iter() {
guid_count += 1;
let guid_bytes = guid_data.to_bytes();
if guid_bytes.len() != 16 {
return Err(malformed_error!(
"GUID at index {} has invalid length {} (expected 16 bytes)",
one_based_index,
guid_bytes.len()
));
}
match guids.get(one_based_index) {
Ok(indexed_guid) => {
let indexed_bytes = indexed_guid.to_bytes();
if indexed_bytes != guid_bytes {
return Err(malformed_error!(
"GUID heap consistency error: iterator and indexed access return different data for index {}",
one_based_index
));
}
}
Err(_) => {
return Err(malformed_error!(
"GUID heap access error: cannot access GUID at 1-based index {}",
one_based_index
));
}
}
if guid_count > 65536 {
return Err(malformed_error!(
"GUID heap contains excessive number of entries (>65536), possible corruption"
));
}
}
let streams = assembly_view.streams();
if let Some(guid_stream) = streams.iter().find(|s| s.name == "#GUID") {
let expected_count = (guid_stream.size / 16) as usize;
if guid_count != expected_count {
return Err(malformed_error!(
"GUID heap count mismatch: found {} GUIDs but stream size {} indicates {} GUIDs",
guid_count,
guid_stream.size,
expected_count
));
}
}
}
Ok(())
}
fn validate_userstring_heap(assembly_view: &CilAssemblyView) -> Result<()> {
let streams = assembly_view.streams();
let us_stream = streams.iter().find(|s| s.name == "#US");
if let Some(stream) = us_stream {
if stream.size > 0x7FFF_FFFF {
return Err(malformed_error!(
"UserString heap (#US) size {} exceeds maximum allowed size",
stream.size
));
}
if stream.size % 4 != 0 {
return Err(malformed_error!(
"UserString heap (#US) size {} is not 4-byte aligned as required by ECMA-335",
stream.size
));
}
if stream.offset > 0x7FFF_FFFF {
return Err(malformed_error!(
"UserString heap (#US) offset {} exceeds maximum allowed offset",
stream.offset
));
}
}
Self::validate_userstring_heap_content(assembly_view)?;
Ok(())
}
fn validate_userstring_heap_content(assembly_view: &CilAssemblyView) -> Result<()> {
if let Some(userstrings) = assembly_view.userstrings() {
for (offset, userstring_data) in userstrings.iter().take(1000) {
let utf16_chars = userstring_data.as_slice();
if utf16_chars.len() > 0x1FFF_FFFF {
return Err(malformed_error!(
"UserString at offset {} has excessive length {} characters (max: {})",
offset,
utf16_chars.len(),
0x1FFF_FFFF
));
}
if String::from_utf16(utf16_chars).is_err() {
return Err(malformed_error!(
"UserString heap contains invalid UTF-16 sequence at offset {}",
offset
));
}
}
}
Ok(())
}
}
impl RawValidator for RawHeapValidator {
fn validate_raw(&self, context: &RawValidationContext) -> Result<()> {
let assembly_view = context.assembly_view();
Self::validate_string_heap(assembly_view)?;
Self::validate_blob_heap(assembly_view)?;
Self::validate_guid_heap(assembly_view)?;
Self::validate_userstring_heap(assembly_view)?;
Ok(())
}
fn name(&self) -> &'static str {
"RawHeapValidator"
}
fn priority(&self) -> u32 {
180
}
fn should_run(&self, context: &RawValidationContext) -> bool {
context.config().enable_structural_validation
}
}
impl Default for RawHeapValidator {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
metadata::validation::ValidationConfig,
test::{
factories::validation::raw_structure_heap::*, get_testfile_wb, validator_test,
TestAssembly,
},
Error, Result,
};
#[test]
fn test_raw_heap_validator() -> Result<()> {
let validator = RawHeapValidator::new();
let config = ValidationConfig {
enable_structural_validation: true,
..Default::default()
};
validator_test(
raw_heap_validator_file_factory,
"RawHeapValidator",
"Malformed",
config,
|context| validator.validate_raw(context),
)
}
#[test]
fn test_raw_heap_validator_configuration() -> Result<()> {
let validator = RawHeapValidator::new();
fn clean_only_factory() -> Result<Vec<TestAssembly>> {
let Some(clean_testfile) = get_testfile_wb() else {
return Err(Error::Other("WindowsBase.dll not available".to_string()));
};
Ok(vec![TestAssembly::new(&clean_testfile, true)])
}
let result_disabled = validator_test(
clean_only_factory,
"RawHeapValidator",
"Malformed",
ValidationConfig {
enable_structural_validation: false,
..Default::default()
},
|context| {
if validator.should_run(context) {
validator.validate_raw(context)
} else {
Ok(())
}
},
);
assert!(
result_disabled.is_ok(),
"Configuration test failed: validator should not run when disabled"
);
let result_enabled = validator_test(
clean_only_factory,
"RawHeapValidator",
"Malformed",
ValidationConfig {
enable_structural_validation: true,
..Default::default()
},
|context| validator.validate_raw(context),
);
assert!(
result_enabled.is_ok(),
"Configuration test failed: validator should run when enabled"
);
Ok(())
}
#[test]
fn test_raw_heap_validator_metadata() {
let validator = RawHeapValidator::new();
assert_eq!(validator.name(), "RawHeapValidator");
assert_eq!(validator.priority(), 180);
let _config_enabled = ValidationConfig {
enable_structural_validation: true,
..Default::default()
};
let _config_disabled = ValidationConfig {
enable_structural_validation: false,
..Default::default()
};
}
}