use crate::{
file::parser::Parser,
metadata::security::{
security_classes, ArgumentType, ArgumentValue, NamedArgument, Permission,
PermissionSetFormat, SecurityPermissionFlags,
},
utils::EnumUtils,
Result,
};
use quick_xml::{
events::{attributes::Attributes, Event},
Reader,
};
use std::fmt;
const MAX_PERMISSIONS: u32 = 1024;
#[derive(Debug, Clone)]
pub struct PermissionSet {
format: PermissionSetFormat,
permissions: Vec<Permission>,
data: Vec<u8>,
}
impl PermissionSet {
pub fn new(data: &[u8]) -> Result<Self> {
if data.is_empty() {
return Err(malformed_error!("PermissionSet data is empty"));
}
let (format, permissions) = match data[0] {
0x2E => Self::parse_binary_format(data)?,
0x3C => Self::parse_xml_format(data)?,
_ => (PermissionSetFormat::Unknown, Vec::new()),
};
Ok(PermissionSet {
format,
permissions,
data: data.to_vec(),
})
}
fn parse_binary_format(data: &[u8]) -> Result<(PermissionSetFormat, Vec<Permission>)> {
let mut parser = Parser::new(data);
parser.advance().map_err(|e| {
malformed_error!("Failed to read format marker in permission set: {}", e)
})?;
let permission_count = parser.read_compressed_uint().map_err(|e| {
malformed_error!(
"Failed to read permission count at position {}: {}",
parser.pos(),
e
)
})?;
if permission_count > MAX_PERMISSIONS {
return Err(malformed_error!(
"Permission set has too many permissions: {} (max: {})",
permission_count,
MAX_PERMISSIONS
));
}
let mut permissions = Vec::with_capacity(permission_count as usize);
for perm_index in 0..permission_count {
let class_name_length = parser.read_compressed_uint().map_err(|e| {
malformed_error!(
"Failed to read class name length for permission {} at position {}: {}",
perm_index,
parser.pos(),
e
)
})? as usize;
let class_name = if class_name_length > 0 {
let name_bytes = parser.read_bytes(class_name_length).map_err(|_| {
malformed_error!(
"Permission {}: class name length {} exceeds available data at position {}",
perm_index,
class_name_length,
parser.pos()
)
})?;
String::from_utf8_lossy(name_bytes).to_string()
} else {
String::new()
};
let assembly_name = Self::resolve_assembly_name_from_class(&class_name);
let blob_length = parser.read_compressed_uint().map_err(|e| {
malformed_error!(
"Permission '{}': failed to read blob length at position {}: {}",
class_name,
parser.pos(),
e
)
})? as usize;
let mut named_arguments = Vec::new();
if blob_length > 0 {
let blob_end = parser.calc_end_position(blob_length).map_err(|_| {
malformed_error!(
"Permission '{}': blob length {} at position {} exceeds available data (total: {})",
class_name,
blob_length,
parser.pos(),
data.len()
)
})?;
let property_count = parser.read_compressed_uint().map_err(|e| {
malformed_error!(
"Permission '{}': failed to read property count at position {}: {}",
class_name,
parser.pos(),
e
)
})? as usize;
for prop_index in 0..property_count {
let _ = parser.read_le::<u8>().map_err(|e| {
malformed_error!(
"Permission '{}', property {}: failed to read field/property marker: {}",
class_name,
prop_index,
e
)
})?;
let prop_type = parser.read_le::<u8>().map_err(|e| {
malformed_error!(
"Permission '{}', property {}: failed to read property type: {}",
class_name,
prop_index,
e
)
})?;
let name_length = parser.read_compressed_uint().map_err(|e| {
malformed_error!(
"Permission '{}', property {}: failed to read name length: {}",
class_name,
prop_index,
e
)
})? as usize;
let prop_name = if name_length > 0 {
let name_bytes = parser.read_bytes(name_length).map_err(|_| {
malformed_error!(
"Permission '{}', property {}: name length {} exceeds available data",
class_name,
prop_index,
name_length
)
})?;
String::from_utf8_lossy(name_bytes).to_string()
} else {
String::new()
};
let (arg_type, value) =
Self::parse_argument_value(&mut parser, prop_type, &class_name, &prop_name)
.map_err(|e| {
malformed_error!(
"Permission '{}', property '{}': failed to parse argument value: {}",
class_name,
prop_name,
e
)
})?;
named_arguments.push(NamedArgument {
name: prop_name,
arg_type,
value,
});
}
if parser.pos() < blob_end {
parser.seek(blob_end)?;
}
}
permissions.push(Permission {
class_name,
assembly_name,
named_arguments,
});
}
Ok((PermissionSetFormat::BinaryLegacy, permissions))
}
fn parse_xml_format(data: &[u8]) -> Result<(PermissionSetFormat, Vec<Permission>)> {
if data.len() < 5 {
return Err(malformed_error!("XML data too short"));
}
let xml_start = b"<Perm";
if &data[0..5] != xml_start {
return Err(malformed_error!("Invalid XML permission set format"));
}
let mut reader = Reader::from_reader(std::io::Cursor::new(data));
reader.config_mut().trim_text(true);
let mut permissions = Vec::new();
let mut buf = Vec::new();
let mut in_permission_set = false;
loop {
match reader.read_event_into(&mut buf) {
Ok(Event::Start(ref e)) => match e.name().as_ref() {
b"PermissionSet" => {
in_permission_set = true;
}
b"IPermission" => {
if in_permission_set {
match Self::parse_permission_from_xml_attributes(e.attributes()) {
Ok(permission) => permissions.push(permission),
Err(e) => return Err(e),
}
}
}
_ => {}
},
Ok(Event::Empty(ref e)) => {
if e.name().as_ref() == b"IPermission" && in_permission_set {
match Self::parse_permission_from_xml_attributes(e.attributes()) {
Ok(permission) => permissions.push(permission),
Err(e) => return Err(e),
}
}
}
Ok(Event::End(ref e)) => {
if e.name().as_ref() == b"PermissionSet" {
in_permission_set = false;
}
}
Ok(Event::Eof) => break,
Err(e) => return Err(malformed_error!("XML parsing error: {}", e)),
_ => {}
}
buf.clear();
}
Ok((PermissionSetFormat::Xml, permissions))
}
fn parse_permission_from_xml_attributes(attributes: Attributes) -> Result<Permission> {
let mut class_name = String::new();
let mut _version = String::new();
let mut named_arguments = Vec::new();
for attr_result in attributes {
let attr = attr_result.map_err(|e| malformed_error!("Invalid XML attribute: {}", e))?;
let key = std::str::from_utf8(attr.key.as_ref())
.map_err(|_| malformed_error!("Invalid UTF-8 in XML attribute key"))?;
let value = std::str::from_utf8(&attr.value)
.map_err(|_| malformed_error!("Invalid UTF-8 in XML attribute value"))?;
match key {
"class" => class_name = value.to_string(),
"version" => _version = value.to_string(),
_ => {
let (arg_type, arg_value) = Self::infer_argument_type_from_xml_value(value);
named_arguments.push(NamedArgument {
name: key.to_string(),
arg_type,
value: arg_value,
});
}
}
}
if class_name.is_empty() {
return Err(malformed_error!(
"Missing class attribute in IPermission element"
));
}
let assembly_name = Self::resolve_assembly_name_from_class(&class_name);
Ok(Permission {
class_name,
assembly_name,
named_arguments,
})
}
fn infer_argument_type_from_xml_value(value: &str) -> (ArgumentType, ArgumentValue) {
if let Ok(bool_val) = value.to_lowercase().parse::<bool>() {
return (ArgumentType::Boolean, ArgumentValue::Boolean(bool_val));
}
if let Ok(int_val) = value.parse::<i32>() {
return (ArgumentType::Int32, ArgumentValue::Int32(int_val));
}
(
ArgumentType::String,
ArgumentValue::String(value.to_string()),
)
}
fn is_valid_permission_flags_enum(
enum_name: &str,
permission_class: &str,
property_name: &str,
) -> bool {
let valid_enum_names = [
"Flags", "UsageAllowed", "Access", "Control", "Rights", "Level", "Mode", "Window", "Clipboard", "AllFiles", "Resources", "Audio", "Image", "Video", "Zone", "Scope", ];
if !valid_enum_names.contains(&enum_name) {
return false;
}
let valid_flag_types = [
"System.Security.Permissions.SecurityPermissionFlag",
"System.Security.Permissions.ReflectionPermissionFlag",
"System.Security.Permissions.FileIOPermissionAccess",
"System.Security.Permissions.FileDialogPermissionAccess",
"System.Security.Permissions.EnvironmentPermissionAccess",
"System.Security.Permissions.RegistryPermissionAccess",
"System.Security.Permissions.UIPermissionWindow",
"System.Security.Permissions.UIPermissionClipboard",
"System.Security.Permissions.IsolatedStorageContainment",
"System.Security.Permissions.HostProtectionResource",
"System.Security.Permissions.DataProtectionPermissionFlags",
"System.Security.Permissions.KeyContainerPermissionFlags",
"System.Security.Permissions.StorePermissionFlags",
"System.Security.Permissions.TypeDescriptorPermissionFlags",
"System.Security.Permissions.MediaPermissionAudio",
"System.Security.Permissions.MediaPermissionImage",
"System.Security.Permissions.MediaPermissionVideo",
"System.Security.SecurityZone",
"System.Diagnostics.EventLogPermissionAccess",
"System.Diagnostics.PerformanceCounterPermissionAccess",
"System.DirectoryServices.DirectoryServicesPermissionAccess",
"System.Drawing.Printing.PrintingPermissionLevel",
"System.Net.NetworkAccess",
"System.Net.TransportType",
"System.Net.Mail.SmtpAccess",
"System.Net.NetworkInformation.NetworkInformationAccess",
"System.Net.PeerToPeer.PnrpScope",
"System.Web.AspNetHostingPermissionLevel",
];
let property_matches = valid_flag_types
.iter()
.any(|&valid_type| property_name.starts_with(valid_type));
if !property_matches {
return false;
}
let known_permission_classes = [
"System.Security.Permissions.SecurityPermission",
"System.Security.Permissions.ReflectionPermission",
"System.Security.Permissions.FileIOPermission",
"System.Security.Permissions.FileDialogPermission",
"System.Security.Permissions.EnvironmentPermission",
"System.Security.Permissions.RegistryPermission",
"System.Security.Permissions.UIPermission",
"System.Security.Permissions.IsolatedStoragePermission",
"System.Security.Permissions.IsolatedStorageFilePermission",
"System.Security.Permissions.HostProtection",
"System.Security.Permissions.DataProtectionPermission",
"System.Security.Permissions.KeyContainerPermission",
"System.Security.Permissions.StorePermission",
"System.Security.Permissions.TypeDescriptorPermission",
"System.Security.Permissions.MediaPermission",
"System.Security.Permissions.GacIdentityPermission",
"System.Security.Permissions.PrincipalPermission",
"System.Security.Permissions.PublisherIdentityPermission",
"System.Security.Permissions.StrongNameIdentityPermission",
"System.Security.Permissions.UrlIdentityPermission",
"System.Security.Permissions.ZoneIdentityPermission",
"System.Security.Permissions.PermissionSet",
"System.Configuration.ConfigurationPermission",
"System.Data.Common.DBDataPermission",
"System.Data.Odbc.OdbcPermission",
"System.Data.OleDb.OleDbPermission",
"System.Data.OracleClient.OraclePermission",
"System.Data.SqlClient.SqlClientPermission",
"System.Diagnostics.EventLogPermission",
"System.Diagnostics.PerformanceCounterPermission",
"System.DirectoryServices.DirectoryServicesPermission",
"System.Drawing.Printing.PrintingPermission",
"System.Net.DnsPermission",
"System.Net.SocketPermission",
"System.Net.WebPermission",
"System.Net.Mail.SmtpPermission",
"System.Net.NetworkInformation.NetworkInformationPermission",
"System.Net.PeerToPeer.PnrpPermission",
"System.Net.PeerToPeer.Collaboration.PeerCollaborationPermission",
"System.Web.AspNetHostingPermission",
];
known_permission_classes
.iter()
.any(|&known_class| permission_class.starts_with(known_class))
}
fn resolve_assembly_name_from_class(class_name: &str) -> String {
if let Some(comma_pos) = class_name.find(',') {
let after_comma = &class_name[comma_pos + 1..];
let assembly_part = after_comma.split(',').next().unwrap_or("").trim();
if !assembly_part.is_empty() {
return assembly_part.to_string();
}
}
if class_name.starts_with("System.Security.Permissions.")
|| class_name.starts_with("System.Security.Principal.")
{
"mscorlib".to_string()
} else if class_name.starts_with("System.Configuration.") {
"System.Configuration".to_string()
} else if class_name.starts_with("System.Data.") {
"System.Data".to_string()
} else if class_name.starts_with("System.Diagnostics.") {
"System".to_string() } else if class_name.starts_with("System.DirectoryServices.") {
"System.DirectoryServices".to_string()
} else if class_name.starts_with("System.Drawing.") {
"System.Drawing".to_string()
} else if class_name.starts_with("System.Net.PeerToPeer.") {
"System.Net".to_string()
} else if class_name.starts_with("System.Net.") {
"System".to_string()
} else if class_name.starts_with("System.Web.") {
"System.Web".to_string()
} else if class_name.starts_with("System.Windows.Forms.") {
"System.Windows.Forms".to_string()
} else if class_name.starts_with("System.Xml.") {
"System.Xml".to_string()
} else {
let parts: Vec<&str> = class_name.split('.').collect();
if parts.len() >= 2 {
format!("{}.{}", parts[0], parts[1])
} else {
"mscorlib".to_string() }
}
}
fn parse_argument_value(
parser: &mut Parser,
arg_type: u8,
permission_class: &str,
property_name: &str,
) -> Result<(ArgumentType, ArgumentValue)> {
match arg_type {
0x02 => {
let value = parser.read_le::<u8>()? != 0;
Ok((ArgumentType::Boolean, ArgumentValue::Boolean(value)))
}
0x03 => {
let value = parser.read_le::<u16>()?;
let ch = char::from_u32(u32::from(value)).unwrap_or('\u{FFFD}');
Ok((ArgumentType::Char, ArgumentValue::Char(ch)))
}
0x04 => {
let value = parser.read_le::<i8>()?;
Ok((ArgumentType::SByte, ArgumentValue::SByte(value)))
}
0x05 => {
let value = parser.read_le::<u8>()?;
Ok((ArgumentType::Byte, ArgumentValue::Byte(value)))
}
0x06 => {
let value = parser.read_le::<i16>()?;
Ok((ArgumentType::Int16, ArgumentValue::Int16(value)))
}
0x07 => {
let value = parser.read_le::<u16>()?;
Ok((ArgumentType::UInt16, ArgumentValue::UInt16(value)))
}
0x08 => {
let value = parser.read_le::<i32>()?;
Ok((ArgumentType::Int32, ArgumentValue::Int32(value)))
}
0x09 => {
let value = parser.read_le::<u32>()?;
Ok((ArgumentType::UInt32, ArgumentValue::UInt32(value)))
}
0x0A => {
let value = parser.read_le::<i64>()?;
Ok((ArgumentType::Int64, ArgumentValue::Int64(value)))
}
0x0B => {
let value = parser.read_le::<u64>()?;
Ok((ArgumentType::UInt64, ArgumentValue::UInt64(value)))
}
0x0C => {
let value = parser.read_le::<f32>()?;
Ok((ArgumentType::Single, ArgumentValue::Single(value)))
}
0x0D => {
let value = parser.read_le::<f64>()?;
Ok((ArgumentType::Double, ArgumentValue::Double(value)))
}
0x0E => {
let value = parser.read_compressed_string_utf8()?;
Ok((ArgumentType::String, ArgumentValue::String(value)))
}
0x50 => {
let type_name = parser.read_compressed_string_utf8()?;
Ok((ArgumentType::Type, ArgumentValue::Type(type_name)))
}
0x51 => {
let inner_type = parser.read_le::<u8>()?;
let (_inner_arg_type, inner_value) = Self::parse_argument_value(
parser,
inner_type,
permission_class,
property_name,
)?;
Ok((
ArgumentType::Object,
ArgumentValue::Object(Box::new(inner_value)),
))
}
0x55 => {
let type_name = parser.read_compressed_string_utf8()?;
if !Self::is_valid_permission_flags_enum(
&type_name,
permission_class,
property_name,
) {
return Err(malformed_error!(
"Invalid permission enum: type='{}', property='{}', class='{}'. \
PermissionSets should only contain 'Flags' properties with known .NET permission classes.",
type_name, property_name, permission_class
));
}
let enum_value = i64::from(parser.read_le::<i32>()?);
Ok((
ArgumentType::String,
ArgumentValue::String(EnumUtils::format_enum_value(&type_name, enum_value)),
))
}
_ => Ok((
ArgumentType::Unknown(arg_type),
ArgumentValue::Null, )),
}
}
#[must_use]
pub fn format(&self) -> &PermissionSetFormat {
&self.format
}
#[must_use]
pub fn permissions(&self) -> &[Permission] {
&self.permissions
}
#[must_use]
pub fn raw_data(&self) -> &[u8] {
&self.data
}
#[must_use]
pub fn contains_permission(&self, class_name: &str) -> bool {
self.permissions.iter().any(|p| p.class_name == class_name)
}
#[must_use]
pub fn get_permission(&self, class_name: &str) -> Option<&Permission> {
self.permissions.iter().find(|p| p.class_name == class_name)
}
#[must_use]
pub fn is_unrestricted(&self) -> bool {
self.permissions
.iter()
.any(super::permission::Permission::is_unrestricted)
}
#[must_use]
pub fn has_file_io(&self) -> bool {
self.contains_permission(security_classes::FILE_IO_PERMISSION)
}
#[must_use]
pub fn has_registry(&self) -> bool {
self.contains_permission(security_classes::REGISTRY_PERMISSION)
}
#[must_use]
pub fn has_reflection(&self) -> bool {
self.contains_permission(security_classes::REFLECTION_PERMISSION)
}
#[must_use]
pub fn has_environment(&self) -> bool {
self.contains_permission(security_classes::ENVIRONMENT_PERMISSION)
}
#[must_use]
pub fn has_security(&self) -> bool {
self.contains_permission(security_classes::SECURITY_PERMISSION)
}
#[must_use]
pub fn has_ui(&self) -> bool {
self.contains_permission(security_classes::UI_PERMISSION)
}
#[must_use]
pub fn has_dns(&self) -> bool {
self.contains_permission(security_classes::DNS_PERMISSION)
}
#[must_use]
pub fn has_socket(&self) -> bool {
self.contains_permission(security_classes::SOCKET_PERMISSION)
}
#[must_use]
pub fn has_web(&self) -> bool {
self.contains_permission(security_classes::WEB_PERMISSION)
}
#[must_use]
pub fn has_isolated_storage(&self) -> bool {
self.contains_permission(security_classes::STORAGE_PERMISSION)
}
#[must_use]
pub fn has_key_container(&self) -> bool {
self.contains_permission(security_classes::KEY_CONTAINER_PERMISSION)
}
#[must_use]
pub fn is_full_trust(&self) -> bool {
if let Some(permission) = self.get_permission(security_classes::SECURITY_PERMISSION) {
if permission.is_unrestricted() {
return true;
}
if let Some(flags) = permission.get_security_flags() {
if flags.is_all() {
return true;
}
if flags.contains(SecurityPermissionFlags::SECURITY_FLAG_SKIP_VERIFICATION)
&& flags.contains(SecurityPermissionFlags::SECURITY_FLAG_CONTROL_POLICY)
&& flags.contains(SecurityPermissionFlags::SECURITY_FLAG_CONTROL_EVIDENCE)
{
return true;
}
}
}
let critical_permissions_count = [
security_classes::SECURITY_PERMISSION,
security_classes::FILE_IO_PERMISSION,
security_classes::REFLECTION_PERMISSION,
security_classes::REGISTRY_PERMISSION,
]
.iter()
.filter(|class| {
if let Some(perm) = self.get_permission(class) {
perm.is_unrestricted()
} else {
false
}
})
.count();
critical_permissions_count >= 3
}
#[must_use]
pub fn get_all_file_read_paths(&self) -> Vec<String> {
self.collect_file_io_paths(Permission::get_file_read_paths)
}
#[must_use]
pub fn get_all_file_write_paths(&self) -> Vec<String> {
self.collect_file_io_paths(Permission::get_file_write_paths)
}
fn collect_file_io_paths<F>(&self, extractor: F) -> Vec<String>
where
F: Fn(&Permission) -> Option<Vec<String>>,
{
self.get_permission(security_classes::FILE_IO_PERMISSION)
.and_then(extractor)
.unwrap_or_default()
}
}
impl fmt::Display for PermissionSet {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.format == PermissionSetFormat::Xml {
write!(f, "{}", String::from_utf8_lossy(&self.data))
} else {
writeln!(f, "Permission Set ({:?}):", self.format)?;
for permission in &self.permissions {
writeln!(
f,
"\t - {}, Assembly: {}",
permission.class_name, permission.assembly_name
)?;
for arg in &permission.named_arguments {
writeln!(f, "\t * {} = {}", arg.name, arg.value)?;
}
}
Ok(())
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::metadata::security::{ArgumentType, ArgumentValue, NamedArgument, Permission};
#[test]
fn test_xml_format_detection() {
let xml = b"<PermissionSet class=\"System.Security.PermissionSet\">\
<IPermission class=\"System.Security.Permissions.SecurityPermission\"/>\
</PermissionSet>";
let permission_set = PermissionSet::new(xml).unwrap();
assert!(matches!(permission_set.format, PermissionSetFormat::Xml));
assert_eq!(permission_set.permissions.len(), 1);
assert_eq!(
permission_set.permissions[0].class_name,
"System.Security.Permissions.SecurityPermission"
);
}
#[test]
fn test_xml_format_with_attributes() {
let xml = br#"<PermissionSet class="System.Security.PermissionSet" version="1">
<IPermission class="System.Security.Permissions.SecurityPermission"
version="1"
Flags="SkipVerification"/>
<IPermission class="System.Security.Permissions.FileIOPermission"
version="1"
Read="C:\temp"
Unrestricted="false"/>
</PermissionSet>"#;
let permission_set = PermissionSet::new(xml).unwrap();
assert!(matches!(permission_set.format, PermissionSetFormat::Xml));
assert_eq!(permission_set.permissions.len(), 2);
let security_perm = &permission_set.permissions[0];
assert_eq!(
security_perm.class_name,
"System.Security.Permissions.SecurityPermission"
);
assert_eq!(security_perm.assembly_name, "mscorlib");
assert_eq!(security_perm.named_arguments.len(), 1);
assert_eq!(security_perm.named_arguments[0].name, "Flags");
assert!(matches!(
security_perm.named_arguments[0].value,
ArgumentValue::String(ref s) if s == "SkipVerification"
));
let fileio_perm = &permission_set.permissions[1];
assert_eq!(
fileio_perm.class_name,
"System.Security.Permissions.FileIOPermission"
);
assert_eq!(fileio_perm.assembly_name, "mscorlib");
assert_eq!(fileio_perm.named_arguments.len(), 2);
let read_arg = fileio_perm
.named_arguments
.iter()
.find(|arg| arg.name == "Read")
.unwrap();
assert!(matches!(
read_arg.value,
ArgumentValue::String(ref s) if s == "C:\\temp"
));
let unrestricted_arg = fileio_perm
.named_arguments
.iter()
.find(|arg| arg.name == "Unrestricted")
.unwrap();
assert!(matches!(
unrestricted_arg.value,
ArgumentValue::Boolean(false)
));
}
#[test]
fn test_xml_format_boolean_parsing() {
let xml = br#"<PermissionSet class="System.Security.PermissionSet">
<IPermission class="System.Security.Permissions.SecurityPermission"
Unrestricted="true"
Flag1="false"
Flag2="True"
Flag3="FALSE"/>
</PermissionSet>"#;
let permission_set = PermissionSet::new(xml).unwrap();
let permission = &permission_set.permissions[0];
assert_eq!(permission.named_arguments.len(), 4);
let unrestricted = permission
.named_arguments
.iter()
.find(|arg| arg.name == "Unrestricted")
.unwrap();
assert!(matches!(unrestricted.value, ArgumentValue::Boolean(true)));
let flag1 = permission
.named_arguments
.iter()
.find(|arg| arg.name == "Flag1")
.unwrap();
assert!(matches!(flag1.value, ArgumentValue::Boolean(false)));
let flag2 = permission
.named_arguments
.iter()
.find(|arg| arg.name == "Flag2")
.unwrap();
assert!(matches!(flag2.value, ArgumentValue::Boolean(true)));
let flag3 = permission
.named_arguments
.iter()
.find(|arg| arg.name == "Flag3")
.unwrap();
assert!(matches!(flag3.value, ArgumentValue::Boolean(false)));
}
#[test]
fn test_xml_format_integer_parsing() {
let xml = br#"<PermissionSet class="System.Security.PermissionSet">
<IPermission class="System.Security.Permissions.SecurityPermission"
Flags="7"
NegativeFlag="-1"
ZeroFlag="0"/>
</PermissionSet>"#;
let permission_set = PermissionSet::new(xml).unwrap();
let permission = &permission_set.permissions[0];
assert_eq!(permission.named_arguments.len(), 3);
let flags = permission
.named_arguments
.iter()
.find(|arg| arg.name == "Flags")
.unwrap();
assert!(matches!(flags.value, ArgumentValue::Int32(7)));
let negative = permission
.named_arguments
.iter()
.find(|arg| arg.name == "NegativeFlag")
.unwrap();
assert!(matches!(negative.value, ArgumentValue::Int32(-1)));
let zero = permission
.named_arguments
.iter()
.find(|arg| arg.name == "ZeroFlag")
.unwrap();
assert!(matches!(zero.value, ArgumentValue::Int32(0)));
}
#[test]
fn test_xml_format_assembly_name_resolution() {
let xml = br#"<PermissionSet class="System.Security.PermissionSet">
<IPermission class="System.Security.Permissions.SecurityPermission"/>
<IPermission class="System.Data.SqlClient.SqlPermission"/>
<IPermission class="System.Xml.XmlPermission"/>
<IPermission class="System.Web.AspNetHostingPermission"/>
<IPermission class="System.Drawing.Printing.PrintingPermission"/>
<IPermission class="System.Windows.Forms.FileDialogPermission"/>
<IPermission class="Custom.Unknown.Permission"/>
</PermissionSet>"#;
let permission_set = PermissionSet::new(xml).unwrap();
assert_eq!(permission_set.permissions.len(), 7);
assert_eq!(permission_set.permissions[0].assembly_name, "mscorlib");
assert_eq!(permission_set.permissions[1].assembly_name, "System.Data");
assert_eq!(permission_set.permissions[2].assembly_name, "System.Xml");
assert_eq!(permission_set.permissions[3].assembly_name, "System.Web");
assert_eq!(
permission_set.permissions[4].assembly_name,
"System.Drawing"
);
assert_eq!(
permission_set.permissions[5].assembly_name,
"System.Windows.Forms"
);
assert_eq!(
permission_set.permissions[6].assembly_name,
"Custom.Unknown"
);
}
#[test]
fn test_xml_format_empty_permission_set() {
let xml = br#"<PermissionSet class="System.Security.PermissionSet" version="1">
</PermissionSet>"#;
let permission_set = PermissionSet::new(xml).unwrap();
assert!(matches!(permission_set.format, PermissionSetFormat::Xml));
assert_eq!(permission_set.permissions.len(), 0);
}
#[test]
fn test_xml_format_missing_class_attribute() {
let xml = br#"<PermissionSet class="System.Security.PermissionSet">
<IPermission version="1" Flags="SkipVerification"/>
</PermissionSet>"#;
let result = PermissionSet::new(xml);
assert!(result.is_err());
let error_msg = format!("{}", result.unwrap_err());
assert!(error_msg.contains("Missing class attribute"));
}
#[test]
fn test_xml_format_malformed_xml() {
let xml = b"<PermissionSet><IPermission class=unclosed";
let result = PermissionSet::new(xml);
assert!(result.is_err());
let error_msg = format!("{}", result.unwrap_err());
assert!(error_msg.contains("XML parsing error"));
}
#[test]
fn test_xml_format_complex_permission_set() {
let xml = br#"<PermissionSet class="System.Security.PermissionSet" version="1" Unrestricted="false">
<IPermission class="System.Security.Permissions.SecurityPermission"
version="1"
Flags="Assertion, Execution, SkipVerification"/>
<IPermission class="System.Security.Permissions.FileIOPermission"
version="1"
Read="C:\Program Files;C:\Windows\System32"
Write="C:\temp;C:\logs"
Append="C:\logs"/>
<IPermission class="System.Security.Permissions.RegistryPermission"
version="1"
Read="HKEY_LOCAL_MACHINE\SOFTWARE"/>
<IPermission class="System.Security.Permissions.EnvironmentPermission"
version="1"
Read="PATH;TEMP;TMP"/>
</PermissionSet>"#;
let permission_set = PermissionSet::new(xml).unwrap();
assert_eq!(permission_set.permissions.len(), 4);
assert!(
permission_set.contains_permission("System.Security.Permissions.SecurityPermission")
);
assert!(permission_set.contains_permission("System.Security.Permissions.FileIOPermission"));
assert!(
permission_set.contains_permission("System.Security.Permissions.RegistryPermission")
);
assert!(
permission_set.contains_permission("System.Security.Permissions.EnvironmentPermission")
);
assert!(permission_set.has_file_io());
assert!(permission_set.has_registry());
assert!(permission_set.has_environment());
}
#[test]
fn test_binary_format() {
let binary = include_bytes!("../../../tests/samples/WB_DeclSecurity_1.bin");
let permission_set = PermissionSet::new(binary).unwrap();
assert!(matches!(
permission_set.format,
PermissionSetFormat::BinaryLegacy
));
assert_eq!(permission_set.permissions.len(), 1);
assert_eq!(
permission_set.permissions[0].class_name,
"System.Security.Permissions.SecurityPermissionAttribute, System.Runtime, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"
);
assert_eq!(permission_set.permissions[0].named_arguments.len(), 1);
assert_eq!(
permission_set.permissions[0].named_arguments[0].name,
"SkipVerification"
);
match &permission_set.permissions[0].named_arguments[0].value {
ArgumentValue::Boolean(value) => assert!(*value),
_ => panic!("Expected Boolean value"),
}
}
#[test]
fn test_empty_data() {
let result = PermissionSet::new(&[]);
assert!(result.is_err());
}
#[test]
fn test_unknown_format() {
let unknown = b"\xFF\x00\x01\x02";
let permission_set = PermissionSet::new(unknown).unwrap();
assert!(matches!(
permission_set.format,
PermissionSetFormat::Unknown
));
}
#[test]
fn test_binary_format_empty_permission_set() {
let data = b".\x00";
let permission_set = PermissionSet::new(data).unwrap();
assert!(matches!(
permission_set.format,
PermissionSetFormat::BinaryLegacy
));
assert_eq!(permission_set.permissions.len(), 0);
}
#[test]
fn test_binary_format_zero_length_class_name() {
let data = b".\x01\x00\x00"; let permission_set = PermissionSet::new(data).unwrap();
assert_eq!(permission_set.permissions.len(), 1);
assert_eq!(permission_set.permissions[0].class_name, "");
assert_eq!(permission_set.permissions[0].assembly_name, "mscorlib");
}
#[test]
fn test_binary_format_multiple_permissions() {
let mut data = vec![b'.', 0x02];
let class_name1 = b"System.Security.Permissions.SecurityPermission";
data.push(class_name1.len() as u8); data.extend_from_slice(class_name1); data.push(0x00);
let class_name2 = b"System.Security.Permissions.FileIOPermission";
data.push(class_name2.len() as u8); data.extend_from_slice(class_name2); data.push(0x00);
let permission_set = PermissionSet::new(&data).unwrap();
assert_eq!(permission_set.permissions.len(), 2);
assert_eq!(
permission_set.permissions[0].class_name,
"System.Security.Permissions.SecurityPermission"
);
assert_eq!(permission_set.permissions[0].assembly_name, "mscorlib");
assert_eq!(
permission_set.permissions[1].class_name,
"System.Security.Permissions.FileIOPermission"
);
assert_eq!(permission_set.permissions[1].assembly_name, "mscorlib");
}
#[test]
fn test_binary_format_different_assembly_names() {
let mut data = vec![b'.', 0x03];
let class_name1 = b"System.Data.SqlClient.SqlPermission";
data.push(class_name1.len() as u8);
data.extend_from_slice(class_name1);
data.push(0x00);
let class_name2 = b"System.Xml.XmlPermission";
data.push(class_name2.len() as u8);
data.extend_from_slice(class_name2);
data.push(0x00);
let class_name3 = b"System.Net.NetworkInformation.NetworkInformationPermission";
data.push(class_name3.len() as u8);
data.extend_from_slice(class_name3);
data.push(0x00);
let permission_set = PermissionSet::new(&data).unwrap();
assert_eq!(permission_set.permissions.len(), 3);
assert_eq!(permission_set.permissions[0].assembly_name, "System.Data");
assert_eq!(permission_set.permissions[1].assembly_name, "System.Xml");
assert_eq!(permission_set.permissions[2].assembly_name, "System");
}
#[test]
fn test_binary_format_with_properties() {
let mut data = vec![b'.', 0x01];
let class_name = b"System.Security.Permissions.SecurityPermission";
data.push(class_name.len() as u8);
data.extend_from_slice(class_name);
let blob_start = data.len() + 1; data.push(0x00);
data.push(0x02);
data.push(0x54); data.push(0x02); let prop_name1 = b"Unrestricted";
data.push(prop_name1.len() as u8);
data.extend_from_slice(prop_name1);
data.push(0x01);
data.push(0x54); data.push(0x08); let prop_name2 = b"Flags";
data.push(prop_name2.len() as u8);
data.extend_from_slice(prop_name2);
data.extend_from_slice(&7i32.to_le_bytes());
let blob_length = data.len() - blob_start;
data[blob_start - 1] = blob_length as u8;
let permission_set = PermissionSet::new(&data).unwrap();
assert_eq!(permission_set.permissions.len(), 1);
assert_eq!(permission_set.permissions[0].named_arguments.len(), 2);
assert_eq!(
permission_set.permissions[0].named_arguments[0].name,
"Unrestricted"
);
assert!(matches!(
permission_set.permissions[0].named_arguments[0].value,
ArgumentValue::Boolean(true)
));
assert_eq!(
permission_set.permissions[0].named_arguments[1].name,
"Flags"
);
assert!(matches!(
permission_set.permissions[0].named_arguments[1].value,
ArgumentValue::Int32(7)
));
}
#[test]
fn test_binary_format_string_property() {
let mut data = vec![b'.', 0x01];
let class_name = b"System.Security.Permissions.FileIOPermission";
data.push(class_name.len() as u8);
data.extend_from_slice(class_name);
let blob_start = data.len() + 1;
data.push(0x00);
data.push(0x01); data.push(0x54); data.push(0x0E); let prop_name = b"Read";
data.push(prop_name.len() as u8);
data.extend_from_slice(prop_name);
let string_value = b"C:\\temp";
data.push(string_value.len() as u8);
data.extend_from_slice(string_value);
let blob_length = data.len() - blob_start;
data[blob_start - 1] = blob_length as u8;
let permission_set = PermissionSet::new(&data).unwrap();
assert_eq!(permission_set.permissions[0].named_arguments.len(), 1);
assert_eq!(
permission_set.permissions[0].named_arguments[0].name,
"Read"
);
assert!(
matches!(permission_set.permissions[0].named_arguments[0].value, ArgumentValue::String(ref s) if s == "C:\\temp")
);
}
#[test]
fn test_binary_format_unknown_argument_type() {
let mut data = vec![b'.', 0x01];
let class_name = b"TestPermission";
data.push(class_name.len() as u8);
data.extend_from_slice(class_name);
let blob_start = data.len() + 1;
data.push(0x00);
data.push(0x01); data.push(0x54); data.push(0xFF); data.push(0x04); data.extend_from_slice(b"Test");
let blob_length = data.len() - blob_start;
data[blob_start - 1] = blob_length as u8;
let result = PermissionSet::new(&data);
assert!(result.is_ok());
let permission_set = result.unwrap();
assert_eq!(permission_set.permissions.len(), 1);
assert_eq!(permission_set.permissions[0].named_arguments.len(), 1);
assert!(matches!(
permission_set.permissions[0].named_arguments[0].arg_type,
ArgumentType::Unknown(0xFF)
));
assert!(matches!(
permission_set.permissions[0].named_arguments[0].value,
ArgumentValue::Null
));
}
#[test]
fn test_binary_format_out_of_bounds_class_name() {
let data = b".\x01\xFF"; let result = PermissionSet::new(data);
assert!(result.is_err());
}
#[test]
fn test_xml_format_too_short() {
let data = b"<Pe"; let result = PermissionSet::new(data);
assert!(result.is_err());
}
#[test]
fn test_xml_format_invalid_start() {
let data = b"<Test"; let result = PermissionSet::new(data);
assert!(result.is_err());
}
#[test]
fn test_permission_set_methods() {
let permissions = vec![
Permission {
class_name: security_classes::SECURITY_PERMISSION.to_string(),
assembly_name: "mscorlib".to_string(),
named_arguments: vec![NamedArgument {
name: "Unrestricted".to_string(),
arg_type: ArgumentType::Boolean,
value: ArgumentValue::Boolean(true),
}],
},
Permission {
class_name: security_classes::FILE_IO_PERMISSION.to_string(),
assembly_name: "mscorlib".to_string(),
named_arguments: vec![NamedArgument {
name: "Read".to_string(),
arg_type: ArgumentType::String,
value: ArgumentValue::String("C:\\temp".to_string()),
}],
},
];
let permission_set = PermissionSet {
format: PermissionSetFormat::BinaryLegacy,
permissions,
data: vec![1, 2, 3],
};
assert!(matches!(
permission_set.format(),
PermissionSetFormat::BinaryLegacy
));
assert_eq!(permission_set.permissions().len(), 2);
assert_eq!(permission_set.raw_data(), &[1, 2, 3]);
assert!(permission_set.contains_permission(security_classes::SECURITY_PERMISSION));
assert!(permission_set.contains_permission(security_classes::FILE_IO_PERMISSION));
assert!(!permission_set.contains_permission(security_classes::REGISTRY_PERMISSION));
assert!(permission_set
.get_permission(security_classes::SECURITY_PERMISSION)
.is_some());
assert!(permission_set
.get_permission(security_classes::REGISTRY_PERMISSION)
.is_none());
assert!(permission_set.is_unrestricted());
assert!(permission_set.has_file_io());
assert!(!permission_set.has_registry());
assert!(!permission_set.has_reflection());
assert!(!permission_set.has_environment());
}
#[test]
fn test_is_full_trust_unrestricted_security_permission() {
let permissions = vec![Permission {
class_name: security_classes::SECURITY_PERMISSION.to_string(),
assembly_name: "mscorlib".to_string(),
named_arguments: vec![NamedArgument {
name: "Unrestricted".to_string(),
arg_type: ArgumentType::Boolean,
value: ArgumentValue::Boolean(true),
}],
}];
let permission_set = PermissionSet {
format: PermissionSetFormat::BinaryLegacy,
permissions,
data: vec![],
};
assert!(permission_set.is_full_trust());
}
#[test]
fn test_is_full_trust_all_flags() {
let permissions = vec![Permission {
class_name: security_classes::SECURITY_PERMISSION.to_string(),
assembly_name: "mscorlib".to_string(),
named_arguments: vec![NamedArgument {
name: "Flags".to_string(),
arg_type: ArgumentType::String,
value: ArgumentValue::String("AllFlags".to_string()),
}],
}];
let permission_set = PermissionSet {
format: PermissionSetFormat::BinaryLegacy,
permissions,
data: vec![],
};
assert!(permission_set.is_full_trust());
}
#[test]
fn test_is_full_trust_critical_flags_combination() {
let permissions = vec![Permission {
class_name: security_classes::SECURITY_PERMISSION.to_string(),
assembly_name: "mscorlib".to_string(),
named_arguments: vec![NamedArgument {
name: "Flags".to_string(),
arg_type: ArgumentType::String,
value: ArgumentValue::String(
"SkipVerification, ControlPolicy, ControlEvidence".to_string(),
),
}],
}];
let permission_set = PermissionSet {
format: PermissionSetFormat::BinaryLegacy,
permissions,
data: vec![],
};
assert!(permission_set.is_full_trust());
}
#[test]
fn test_is_full_trust_multiple_unrestricted_permissions() {
let permissions = vec![
Permission {
class_name: security_classes::SECURITY_PERMISSION.to_string(),
assembly_name: "mscorlib".to_string(),
named_arguments: vec![NamedArgument {
name: "Unrestricted".to_string(),
arg_type: ArgumentType::Boolean,
value: ArgumentValue::Boolean(true),
}],
},
Permission {
class_name: security_classes::FILE_IO_PERMISSION.to_string(),
assembly_name: "mscorlib".to_string(),
named_arguments: vec![NamedArgument {
name: "Unrestricted".to_string(),
arg_type: ArgumentType::Boolean,
value: ArgumentValue::Boolean(true),
}],
},
Permission {
class_name: security_classes::REFLECTION_PERMISSION.to_string(),
assembly_name: "mscorlib".to_string(),
named_arguments: vec![NamedArgument {
name: "Unrestricted".to_string(),
arg_type: ArgumentType::Boolean,
value: ArgumentValue::Boolean(true),
}],
},
];
let permission_set = PermissionSet {
format: PermissionSetFormat::BinaryLegacy,
permissions,
data: vec![],
};
assert!(permission_set.is_full_trust());
}
#[test]
fn test_is_not_full_trust() {
let permissions = vec![Permission {
class_name: security_classes::FILE_IO_PERMISSION.to_string(),
assembly_name: "mscorlib".to_string(),
named_arguments: vec![NamedArgument {
name: "Read".to_string(),
arg_type: ArgumentType::String,
value: ArgumentValue::String("C:\\temp".to_string()),
}],
}];
let permission_set = PermissionSet {
format: PermissionSetFormat::BinaryLegacy,
permissions,
data: vec![],
};
assert!(!permission_set.is_full_trust());
}
#[test]
fn test_get_file_paths() {
let permissions = vec![Permission {
class_name: security_classes::FILE_IO_PERMISSION.to_string(),
assembly_name: "mscorlib".to_string(),
named_arguments: vec![
NamedArgument {
name: "Read".to_string(),
arg_type: ArgumentType::String,
value: ArgumentValue::String("C:\\read\\path".to_string()),
},
NamedArgument {
name: "Write".to_string(),
arg_type: ArgumentType::String,
value: ArgumentValue::String("C:\\write\\path".to_string()),
},
],
}];
let permission_set = PermissionSet {
format: PermissionSetFormat::BinaryLegacy,
permissions,
data: vec![],
};
let read_paths = permission_set.get_all_file_read_paths();
let write_paths = permission_set.get_all_file_write_paths();
assert_eq!(read_paths.len(), 1);
assert_eq!(read_paths[0], "C:\\read\\path");
assert_eq!(write_paths.len(), 1);
assert_eq!(write_paths[0], "C:\\write\\path");
}
#[test]
fn test_get_file_paths_no_fileio_permission() {
let permissions = vec![Permission {
class_name: security_classes::SECURITY_PERMISSION.to_string(),
assembly_name: "mscorlib".to_string(),
named_arguments: vec![],
}];
let permission_set = PermissionSet {
format: PermissionSetFormat::BinaryLegacy,
permissions,
data: vec![],
};
let read_paths = permission_set.get_all_file_read_paths();
let write_paths = permission_set.get_all_file_write_paths();
assert_eq!(read_paths.len(), 0);
assert_eq!(write_paths.len(), 0);
}
#[test]
fn test_display_format_binary() {
let permissions = vec![
Permission {
class_name: "TestPermission1".to_string(),
assembly_name: "TestAssembly".to_string(),
named_arguments: vec![NamedArgument {
name: "TestArg".to_string(),
arg_type: ArgumentType::String,
value: ArgumentValue::String("TestValue".to_string()),
}],
},
Permission {
class_name: "TestPermission2".to_string(),
assembly_name: "TestAssembly2".to_string(),
named_arguments: vec![],
},
];
let permission_set = PermissionSet {
format: PermissionSetFormat::BinaryLegacy,
permissions,
data: vec![],
};
let display_string = format!("{permission_set}");
assert!(display_string.contains("Permission Set (BinaryLegacy):"));
assert!(display_string.contains("TestPermission1, Assembly: TestAssembly"));
assert!(display_string.contains("TestPermission2, Assembly: TestAssembly2"));
assert!(display_string.contains("TestArg = \"TestValue\""));
}
#[test]
fn test_display_format_xml() {
let xml_data = b"<PermissionSet>test</PermissionSet>";
let permission_set = PermissionSet {
format: PermissionSetFormat::Xml,
permissions: vec![],
data: xml_data.to_vec(),
};
let display_string = format!("{permission_set}");
assert_eq!(display_string, "<PermissionSet>test</PermissionSet>");
}
#[test]
fn test_clone() {
let permissions = vec![Permission {
class_name: "TestPermission".to_string(),
assembly_name: "TestAssembly".to_string(),
named_arguments: vec![],
}];
let original = PermissionSet {
format: PermissionSetFormat::BinaryLegacy,
permissions,
data: vec![1, 2, 3],
};
let cloned = original.clone();
assert!(matches!(cloned.format, PermissionSetFormat::BinaryLegacy));
assert_eq!(cloned.permissions.len(), 1);
assert_eq!(cloned.permissions[0].class_name, "TestPermission");
assert_eq!(cloned.data, vec![1, 2, 3]);
}
#[test]
fn test_binary_format_zero_blob_length() {
let mut data = vec![b'.', 0x01];
let class_name = b"TestPermission";
data.push(class_name.len() as u8);
data.extend_from_slice(class_name);
data.push(0x00);
let permission_set = PermissionSet::new(&data).unwrap();
assert_eq!(permission_set.permissions.len(), 1);
assert_eq!(permission_set.permissions[0].named_arguments.len(), 0);
}
#[test]
fn test_binary_format_zero_property_name_length() {
let mut data = vec![b'.', 0x01];
let class_name = b"TestPermission";
data.push(class_name.len() as u8);
data.extend_from_slice(class_name);
let blob_start = data.len() + 1;
data.push(0x00);
data.push(0x01); data.push(0x54); data.push(0x02); data.push(0x00); data.push(0x01);
let blob_length = data.len() - blob_start;
data[blob_start - 1] = blob_length as u8;
let permission_set = PermissionSet::new(&data).unwrap();
assert_eq!(permission_set.permissions[0].named_arguments.len(), 1);
assert_eq!(permission_set.permissions[0].named_arguments[0].name, "");
}
#[test]
fn test_binary_format_blob_end_seeking() {
let mut data = vec![b'.', 0x01];
let class_name = b"TestPermission";
data.push(class_name.len() as u8);
data.extend_from_slice(class_name);
data.push(0x10); data.push(0x00);
data.extend(std::iter::repeat_n(0x00, 15));
let result = PermissionSet::new(&data);
assert!(result.is_err());
}
#[test]
fn test_binary_format_blob_bounds_checking() {
let mut data = vec![b'.', 0x01];
let class_name = b"TestPermission";
data.push(class_name.len() as u8);
data.extend_from_slice(class_name);
data.push(0x20); data.push(0x00);
let result = PermissionSet::new(&data);
assert!(result.is_err());
let error_msg = format!("{}", result.unwrap_err());
assert!(
error_msg.contains("blob length") && error_msg.contains("exceeds available data"),
"Expected error about blob bounds, got: {}",
error_msg
);
}
}