use crate::{
metadata::{
cilassemblyview::CilAssemblyView,
tables::{ClassLayoutRaw, FieldLayoutRaw, FieldRaw, TypeDefRaw},
validation::{
context::{RawValidationContext, ValidationContext},
traits::RawValidator,
},
},
Result,
};
use rustc_hash::FxHashMap;
pub struct RawLayoutConstraintValidator;
impl RawLayoutConstraintValidator {
#[must_use]
pub fn new() -> Self {
Self
}
fn validate_field_layouts(assembly_view: &CilAssemblyView) -> Result<()> {
let tables = assembly_view
.tables()
.ok_or_else(|| malformed_error!("Assembly view does not contain metadata tables"))?;
if let Some(field_layout_table) = tables.table::<FieldLayoutRaw>() {
let mut field_offsets: FxHashMap<usize, Vec<(u32, u32)>> = FxHashMap::default();
for field_layout in field_layout_table {
if field_layout.field == 0 {
return Err(malformed_error!(
"FieldLayout RID {} has null field reference",
field_layout.rid
));
}
if field_layout.field_offset > 0x7FFF_FFFF {
return Err(malformed_error!(
"FieldLayout RID {} has invalid field offset {} exceeding maximum",
field_layout.rid,
field_layout.field_offset
));
}
if let Some(field_tbl) = tables.table::<FieldRaw>() {
if field_layout.field > field_tbl.row_count {
return Err(malformed_error!(
"FieldLayout RID {} references Field RID {} but table only has {} rows",
field_layout.rid,
field_layout.field,
field_tbl.row_count
));
}
}
field_offsets
.entry(field_layout.field_offset as usize)
.or_default()
.push((field_layout.rid, field_layout.field));
}
for (offset, fields) in field_offsets {
if fields.len() > 1000 {
return Err(malformed_error!(
"Suspiciously large field overlap at offset {}: {} field layouts share the same position (possible corruption)",
offset,
fields.len()
));
}
}
}
Ok(())
}
fn validate_class_layouts(assembly_view: &CilAssemblyView) -> Result<()> {
let tables = assembly_view
.tables()
.ok_or_else(|| malformed_error!("Assembly view does not contain metadata tables"))?;
if let Some(class_layout_table) = tables.table::<ClassLayoutRaw>() {
let typedef_table = tables.table::<TypeDefRaw>();
for class_layout in class_layout_table {
let packing_size = class_layout.packing_size;
if packing_size != 0 && !packing_size.is_power_of_two() {
return Err(malformed_error!(
"ClassLayout RID {} has invalid packing size {} - must be 0 or a power of 2",
class_layout.rid,
packing_size
));
}
if packing_size > 128 {
return Err(malformed_error!(
"ClassLayout RID {} has excessive packing size {} exceeding maximum of 128",
class_layout.rid,
packing_size
));
}
if class_layout.class_size > 0x7FFF_FFFF {
return Err(malformed_error!(
"ClassLayout RID {} has invalid class size {} exceeding maximum",
class_layout.rid,
class_layout.class_size
));
}
if class_layout.parent == 0 {
return Err(malformed_error!(
"ClassLayout RID {} has null parent reference",
class_layout.rid
));
}
if let Some(typedef_tbl) = typedef_table {
if class_layout.parent > typedef_tbl.row_count {
return Err(malformed_error!(
"ClassLayout RID {} references TypeDef RID {} but table only has {} rows",
class_layout.rid,
class_layout.parent,
typedef_tbl.row_count
));
}
}
}
}
Ok(())
}
fn validate_layout_consistency(assembly_view: &CilAssemblyView) -> Result<()> {
let tables = assembly_view
.tables()
.ok_or_else(|| malformed_error!("Assembly view does not contain metadata tables"))?;
if let (Some(class_layout_table), Some(field_layout_table), Some(typedef_table)) = (
tables.table::<ClassLayoutRaw>(),
tables.table::<FieldLayoutRaw>(),
tables.table::<TypeDefRaw>(),
) {
let mut class_layouts: FxHashMap<u32, u32> = FxHashMap::default();
for class_layout in class_layout_table {
class_layouts.insert(class_layout.parent, class_layout.rid);
}
for field_layout in field_layout_table {
if field_layout.field_offset == 0x7FFF_FFFF {
return Err(malformed_error!(
"FieldLayout RID {} has field offset at maximum boundary - potential overflow",
field_layout.rid
));
}
if let Some(field_table) = tables.table::<FieldRaw>() {
if field_layout.field > field_table.row_count {
continue;
}
let typedef_rows: Vec<_> = typedef_table.iter().collect();
let mut parent_typedef_rid = None;
for (index, typedef_entry) in typedef_rows.iter().enumerate() {
let start_field = typedef_entry.field_list;
let end_field = if index + 1 < typedef_rows.len() {
typedef_rows[index + 1].field_list
} else {
u32::MAX
};
if field_layout.field >= start_field && field_layout.field < end_field {
parent_typedef_rid = Some(typedef_entry.rid);
break;
}
}
if let Some(parent_rid) = parent_typedef_rid {
if let Some(&class_layout_rid) = class_layouts.get(&parent_rid) {
if let Some(parent_class_layout) = class_layout_table
.iter()
.find(|cl| cl.rid == class_layout_rid)
{
if parent_class_layout.class_size > 0
&& field_layout.field_offset > 1_048_576
{
return Err(malformed_error!(
"FieldLayout RID {} has unreasonably large field offset {} (possible corruption)",
field_layout.rid,
field_layout.field_offset
));
}
}
}
}
}
}
for class_layout in class_layout_table {
let typedef_found = typedef_table
.iter()
.any(|typedef| typedef.rid == class_layout.parent);
if !typedef_found {
return Err(malformed_error!(
"ClassLayout RID {} references non-existent TypeDef RID {}",
class_layout.rid,
class_layout.parent
));
}
}
}
Ok(())
}
fn validate_field_alignment(assembly_view: &CilAssemblyView) -> Result<()> {
let tables = assembly_view
.tables()
.ok_or_else(|| malformed_error!("Assembly view does not contain metadata tables"))?;
if let (Some(field_layout_table), Some(_field_table)) =
(tables.table::<FieldLayoutRaw>(), tables.table::<FieldRaw>())
{
for field_layout in field_layout_table {
let field_offset = field_layout.field_offset;
if (field_offset % 4 == 1 || field_offset % 4 == 3) && field_offset > 65536 {
return Err(malformed_error!(
"FieldLayout RID {} has unusual alignment at field offset {} - potential layout issue",
field_layout.rid,
field_offset
));
}
if field_offset > 16_777_216 {
return Err(malformed_error!(
"FieldLayout RID {} has extremely large field offset {} - possible corruption",
field_layout.rid,
field_offset
));
}
if field_offset == u32::MAX - 1 || field_offset == u32::MAX - 3 {
return Err(malformed_error!(
"FieldLayout RID {} has field offset {} near maximum boundary - overflow risk",
field_layout.rid,
field_offset
));
}
}
}
Ok(())
}
fn validate_value_type_layouts(assembly_view: &CilAssemblyView) -> Result<()> {
let tables = assembly_view
.tables()
.ok_or_else(|| malformed_error!("Assembly view does not contain metadata tables"))?;
if let (Some(class_layout_table), Some(typedef_table)) = (
tables.table::<ClassLayoutRaw>(),
tables.table::<TypeDefRaw>(),
) {
for class_layout in class_layout_table {
if let Some(typedef_entry) = typedef_table
.iter()
.find(|td| td.rid == class_layout.parent)
{
const SEALED_FLAG: u32 = 0x0100;
const SERIALIZABLE_FLAG: u32 = 0x2000;
let is_likely_value_type = (typedef_entry.flags & SEALED_FLAG) != 0;
if is_likely_value_type {
if class_layout.class_size > 1_048_576 {
return Err(malformed_error!(
"ClassLayout RID {} for potential value type has excessive size {} - may cause stack issues",
class_layout.rid,
class_layout.class_size
));
}
if class_layout.packing_size > 0
&& class_layout.class_size > 0
&& u32::from(class_layout.packing_size) > class_layout.class_size
{
return Err(malformed_error!(
"ClassLayout RID {} has packing size {} larger than class size {} - invalid layout",
class_layout.rid,
class_layout.packing_size,
class_layout.class_size
));
}
}
}
}
}
Ok(())
}
fn validate_sequential_layout(assembly_view: &CilAssemblyView) -> Result<()> {
let tables = assembly_view
.tables()
.ok_or_else(|| malformed_error!("Assembly view does not contain metadata tables"))?;
if let Some(field_layout_table) = tables.table::<FieldLayoutRaw>() {
let field_layouts: Vec<_> = field_layout_table.iter().collect();
let mut type_field_layouts: FxHashMap<u32, Vec<FieldLayoutRaw>> = FxHashMap::default();
for field_layout in field_layouts {
let estimated_parent = field_layout.field / 10; type_field_layouts
.entry(estimated_parent)
.or_default()
.push(field_layout.clone());
}
for (_parent_id, mut fields) in type_field_layouts {
if fields.len() > 1 {
fields.sort_by_key(|f| f.field_offset);
for window in fields.windows(2) {
let field1 = &window[0];
let field2 = &window[1];
let gap = field2.field_offset.saturating_sub(field1.field_offset);
if gap > 1_048_576 {
return Err(malformed_error!(
"Large gap {} between FieldLayout RID {} and {} - possible layout issue",
gap,
field1.rid,
field2.rid
));
}
if gap == 0 && field1.field_offset > 0 && field1.field_offset > 65536 {
return Err(malformed_error!(
"FieldLayout RID {} and {} overlap at large field offset {} - verify union layout",
field1.rid,
field2.rid,
field1.field_offset
));
}
}
}
}
}
Ok(())
}
}
impl RawValidator for RawLayoutConstraintValidator {
fn validate_raw(&self, context: &RawValidationContext) -> Result<()> {
let assembly_view = context.assembly_view();
Self::validate_field_layouts(assembly_view)?;
Self::validate_class_layouts(assembly_view)?;
Self::validate_layout_consistency(assembly_view)?;
Self::validate_field_alignment(assembly_view)?;
Self::validate_value_type_layouts(assembly_view)?;
Self::validate_sequential_layout(assembly_view)?;
Ok(())
}
fn name(&self) -> &'static str {
"RawLayoutConstraintValidator"
}
fn priority(&self) -> u32 {
120
}
fn should_run(&self, context: &RawValidationContext) -> bool {
context.config().enable_constraint_validation
}
}
impl Default for RawLayoutConstraintValidator {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
metadata::validation::ValidationConfig,
test::{
factories::validation::raw_constraints_layout::raw_layout_constraint_validator_file_factory,
validator_test,
},
};
#[test]
fn test_raw_layout_constraint_validator() -> Result<()> {
let validator = RawLayoutConstraintValidator::new();
let config = ValidationConfig {
enable_constraint_validation: true,
..Default::default()
};
validator_test(
raw_layout_constraint_validator_file_factory,
"RawLayoutConstraintValidator",
"Malformed",
config,
|context| validator.validate_raw(context),
)
}
}