use crate::{
cilassembly::{ChangeRefRc, CilAssembly},
metadata::{
security::SecurityAction,
tables::{CodedIndex, CodedIndexType, DeclSecurityRaw, TableDataOwned, TableId},
token::Token,
},
Error, Result,
};
pub struct DeclSecurityBuilder {
action: Option<u16>,
parent: Option<CodedIndex>,
permission_set: Option<Vec<u8>>,
}
impl Default for DeclSecurityBuilder {
fn default() -> Self {
Self::new()
}
}
impl DeclSecurityBuilder {
#[must_use]
pub fn new() -> Self {
Self {
action: None,
parent: None,
permission_set: None,
}
}
#[must_use]
pub fn action(mut self, action: SecurityAction) -> Self {
self.action = Some(action.into());
self
}
#[must_use]
pub fn action_raw(mut self, action: u16) -> Self {
self.action = Some(action);
self
}
#[must_use]
pub fn parent(mut self, parent: CodedIndex) -> Self {
self.parent = Some(parent);
self
}
#[must_use]
pub fn permission_set(mut self, permission_set: &[u8]) -> Self {
self.permission_set = Some(permission_set.to_vec());
self
}
#[must_use]
pub fn unrestricted_permission_set(mut self) -> Self {
let unrestricted_blob = vec![
0x01, 0x01, 0x00, 0xFF, ];
self.permission_set = Some(unrestricted_blob);
self
}
pub fn build(self, assembly: &mut CilAssembly) -> Result<ChangeRefRc> {
let action = self
.action
.ok_or_else(|| Error::ModificationInvalid("Security action is required".to_string()))?;
let parent = self
.parent
.ok_or_else(|| Error::ModificationInvalid("Security parent is required".to_string()))?;
let permission_set = self
.permission_set
.ok_or_else(|| Error::ModificationInvalid("Permission set is required".to_string()))?;
if permission_set.is_empty() {
return Err(Error::ModificationInvalid(
"Permission set cannot be empty".to_string(),
));
}
let valid_parent_tables = CodedIndexType::HasDeclSecurity.tables();
if !valid_parent_tables.contains(&parent.tag) {
return Err(Error::ModificationInvalid(format!(
"Parent must be a HasDeclSecurity coded index (TypeDef/MethodDef/Assembly), got {:?}",
parent.tag
)));
}
if action == 0 {
return Err(Error::ModificationInvalid(
"Security action cannot be 0".to_string(),
));
}
let permission_set_index = assembly.blob_add(&permission_set)?.placeholder();
let rid = assembly.next_rid(TableId::DeclSecurity)?;
let token_value = ((TableId::DeclSecurity as u32) << 24) | rid;
let token = Token::new(token_value);
let decl_security_raw = DeclSecurityRaw {
rid,
token,
offset: 0, action,
parent,
permission_set: permission_set_index,
};
assembly.table_row_add(
TableId::DeclSecurity,
TableDataOwned::DeclSecurity(decl_security_raw),
)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
cilassembly::{ChangeRefKind, CilAssembly},
metadata::{cilassemblyview::CilAssemblyView, security::SecurityAction},
};
use std::path::PathBuf;
#[test]
fn test_decl_security_builder_basic() {
let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/samples/WindowsBase.dll");
if let Ok(view) = CilAssemblyView::from_path(&path) {
let mut assembly = CilAssembly::new(view);
let method_ref =
CodedIndex::new(TableId::MethodDef, 1, CodedIndexType::HasDeclSecurity); let permission_blob = vec![0x01, 0x02, 0x03, 0x04];
let security_ref = DeclSecurityBuilder::new()
.action(SecurityAction::Demand)
.parent(method_ref)
.permission_set(&permission_blob)
.build(&mut assembly)
.unwrap();
assert_eq!(
security_ref.kind(),
ChangeRefKind::TableRow(TableId::DeclSecurity)
);
}
}
#[test]
fn test_decl_security_builder_different_actions() {
let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/samples/WindowsBase.dll");
if let Ok(view) = CilAssemblyView::from_path(&path) {
let mut assembly = CilAssembly::new(view);
let permission_blob = vec![0x01, 0x02, 0x03, 0x04];
let actions = [
SecurityAction::Demand,
SecurityAction::Assert,
SecurityAction::Deny,
SecurityAction::LinkDemand,
SecurityAction::InheritanceDemand,
SecurityAction::RequestMinimum,
SecurityAction::PermitOnly,
];
for (i, &action) in actions.iter().enumerate() {
let parent = CodedIndex::new(
TableId::TypeDef,
(i + 1) as u32,
CodedIndexType::HasDeclSecurity,
);
let security_ref = DeclSecurityBuilder::new()
.action(action)
.parent(parent)
.permission_set(&permission_blob)
.build(&mut assembly)
.unwrap();
assert_eq!(
security_ref.kind(),
ChangeRefKind::TableRow(TableId::DeclSecurity)
);
}
}
}
#[test]
fn test_decl_security_builder_different_parents() {
let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/samples/WindowsBase.dll");
if let Ok(view) = CilAssemblyView::from_path(&path) {
let mut assembly = CilAssembly::new(view);
let permission_blob = vec![0x01, 0x02, 0x03, 0x04];
let assembly_parent =
CodedIndex::new(TableId::Assembly, 1, CodedIndexType::HasDeclSecurity);
let type_parent = CodedIndex::new(TableId::TypeDef, 1, CodedIndexType::HasDeclSecurity);
let method_parent =
CodedIndex::new(TableId::MethodDef, 1, CodedIndexType::HasDeclSecurity);
let assembly_security_ref = DeclSecurityBuilder::new()
.action(SecurityAction::RequestMinimum)
.parent(assembly_parent)
.permission_set(&permission_blob)
.build(&mut assembly)
.unwrap();
let type_security_ref = DeclSecurityBuilder::new()
.action(SecurityAction::LinkDemand)
.parent(type_parent)
.permission_set(&permission_blob)
.build(&mut assembly)
.unwrap();
let method_security_ref = DeclSecurityBuilder::new()
.action(SecurityAction::Demand)
.parent(method_parent)
.permission_set(&permission_blob)
.build(&mut assembly)
.unwrap();
assert_eq!(
assembly_security_ref.kind(),
ChangeRefKind::TableRow(TableId::DeclSecurity)
);
assert_eq!(
type_security_ref.kind(),
ChangeRefKind::TableRow(TableId::DeclSecurity)
);
assert_eq!(
method_security_ref.kind(),
ChangeRefKind::TableRow(TableId::DeclSecurity)
);
assert!(!std::sync::Arc::ptr_eq(
&assembly_security_ref,
&type_security_ref
));
assert!(!std::sync::Arc::ptr_eq(
&assembly_security_ref,
&method_security_ref
));
assert!(!std::sync::Arc::ptr_eq(
&type_security_ref,
&method_security_ref
));
}
}
#[test]
fn test_decl_security_builder_raw_action() {
let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/samples/WindowsBase.dll");
if let Ok(view) = CilAssemblyView::from_path(&path) {
let mut assembly = CilAssembly::new(view);
let parent_ref =
CodedIndex::new(TableId::MethodDef, 1, CodedIndexType::HasDeclSecurity);
let permission_blob = vec![0x01, 0x02, 0x03, 0x04];
let security_ref = DeclSecurityBuilder::new()
.action_raw(0x0002) .parent(parent_ref)
.permission_set(&permission_blob)
.build(&mut assembly)
.unwrap();
assert_eq!(
security_ref.kind(),
ChangeRefKind::TableRow(TableId::DeclSecurity)
);
}
}
#[test]
fn test_decl_security_builder_unrestricted_permission() {
let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/samples/WindowsBase.dll");
if let Ok(view) = CilAssemblyView::from_path(&path) {
let mut assembly = CilAssembly::new(view);
let parent_ref = CodedIndex::new(TableId::TypeDef, 1, CodedIndexType::HasDeclSecurity);
let security_ref = DeclSecurityBuilder::new()
.action(SecurityAction::Assert)
.parent(parent_ref)
.unrestricted_permission_set()
.build(&mut assembly)
.unwrap();
assert_eq!(
security_ref.kind(),
ChangeRefKind::TableRow(TableId::DeclSecurity)
);
}
}
#[test]
fn test_decl_security_builder_missing_action() {
let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/samples/WindowsBase.dll");
if let Ok(view) = CilAssemblyView::from_path(&path) {
let mut assembly = CilAssembly::new(view);
let parent_ref =
CodedIndex::new(TableId::MethodDef, 1, CodedIndexType::HasDeclSecurity);
let permission_blob = vec![0x01, 0x02, 0x03, 0x04];
let result = DeclSecurityBuilder::new()
.parent(parent_ref)
.permission_set(&permission_blob)
.build(&mut assembly);
assert!(result.is_err());
}
}
#[test]
fn test_decl_security_builder_missing_parent() {
let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/samples/WindowsBase.dll");
if let Ok(view) = CilAssemblyView::from_path(&path) {
let mut assembly = CilAssembly::new(view);
let permission_blob = vec![0x01, 0x02, 0x03, 0x04];
let result = DeclSecurityBuilder::new()
.action(SecurityAction::Demand)
.permission_set(&permission_blob)
.build(&mut assembly);
assert!(result.is_err());
}
}
#[test]
fn test_decl_security_builder_missing_permission_set() {
let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/samples/WindowsBase.dll");
if let Ok(view) = CilAssemblyView::from_path(&path) {
let mut assembly = CilAssembly::new(view);
let parent_ref =
CodedIndex::new(TableId::MethodDef, 1, CodedIndexType::HasDeclSecurity);
let result = DeclSecurityBuilder::new()
.action(SecurityAction::Demand)
.parent(parent_ref)
.build(&mut assembly);
assert!(result.is_err());
}
}
#[test]
fn test_decl_security_builder_empty_permission_set() {
let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/samples/WindowsBase.dll");
if let Ok(view) = CilAssemblyView::from_path(&path) {
let mut assembly = CilAssembly::new(view);
let parent_ref =
CodedIndex::new(TableId::MethodDef, 1, CodedIndexType::HasDeclSecurity);
let empty_blob = vec![];
let result = DeclSecurityBuilder::new()
.action(SecurityAction::Demand)
.parent(parent_ref)
.permission_set(&empty_blob)
.build(&mut assembly);
assert!(result.is_err());
}
}
#[test]
fn test_decl_security_builder_invalid_parent_type() {
let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/samples/WindowsBase.dll");
if let Ok(view) = CilAssemblyView::from_path(&path) {
let mut assembly = CilAssembly::new(view);
let invalid_parent =
CodedIndex::new(TableId::Field, 1, CodedIndexType::HasDeclSecurity); let permission_blob = vec![0x01, 0x02, 0x03, 0x04];
let result = DeclSecurityBuilder::new()
.action(SecurityAction::Demand)
.parent(invalid_parent)
.permission_set(&permission_blob)
.build(&mut assembly);
assert!(result.is_err());
}
}
#[test]
fn test_decl_security_builder_zero_action() {
let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/samples/WindowsBase.dll");
if let Ok(view) = CilAssemblyView::from_path(&path) {
let mut assembly = CilAssembly::new(view);
let parent_ref =
CodedIndex::new(TableId::MethodDef, 1, CodedIndexType::HasDeclSecurity);
let permission_blob = vec![0x01, 0x02, 0x03, 0x04];
let result = DeclSecurityBuilder::new()
.action_raw(0) .parent(parent_ref)
.permission_set(&permission_blob)
.build(&mut assembly);
assert!(result.is_err());
}
}
#[test]
fn test_decl_security_builder_security_action_conversion() {
assert_eq!(SecurityAction::Demand, 0x0002.into());
assert_eq!(SecurityAction::Assert, 0x0003.into());
assert_eq!(SecurityAction::Deny, 0x0001.into());
assert_eq!(SecurityAction::from(0x0002), SecurityAction::Demand);
assert_eq!(SecurityAction::from(0x0003), SecurityAction::Assert);
assert_eq!(SecurityAction::from(0x0001), SecurityAction::Deny);
assert_eq!(
SecurityAction::from(0xFFFF),
SecurityAction::Unknown(0xFFFF)
); }
#[test]
fn test_decl_security_builder_multiple_declarations() {
let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/samples/WindowsBase.dll");
if let Ok(view) = CilAssemblyView::from_path(&path) {
let mut assembly = CilAssembly::new(view);
let method_ref =
CodedIndex::new(TableId::MethodDef, 1, CodedIndexType::HasDeclSecurity);
let permission_blob1 = vec![0x01, 0x02, 0x03, 0x04]; let permission_blob2 = vec![0x05, 0x06, 0x07, 0x08];
let demand_security_ref = DeclSecurityBuilder::new()
.action(SecurityAction::Demand)
.parent(method_ref.clone())
.permission_set(&permission_blob1)
.build(&mut assembly)
.unwrap();
let assert_security_ref = DeclSecurityBuilder::new()
.action(SecurityAction::Assert)
.parent(method_ref) .permission_set(&permission_blob2)
.build(&mut assembly)
.unwrap();
assert_eq!(
demand_security_ref.kind(),
ChangeRefKind::TableRow(TableId::DeclSecurity)
);
assert_eq!(
assert_security_ref.kind(),
ChangeRefKind::TableRow(TableId::DeclSecurity)
);
assert!(!std::sync::Arc::ptr_eq(
&demand_security_ref,
&assert_security_ref
));
}
}
#[test]
fn test_decl_security_builder_realistic_scenario() {
let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/samples/WindowsBase.dll");
if let Ok(view) = CilAssemblyView::from_path(&path) {
let mut assembly = CilAssembly::new(view);
let file_method =
CodedIndex::new(TableId::MethodDef, 1, CodedIndexType::HasDeclSecurity);
let file_io_permission = vec![
0x01, 0x01, 0x10, 0x20, 0x30, 0x40, 0x02, 0x00, 0x08, b'C', 0x00, b':', 0x00, b'\\', 0x00, b'*', 0x00, ];
let file_security_ref = DeclSecurityBuilder::new()
.action(SecurityAction::Demand)
.parent(file_method)
.permission_set(&file_io_permission)
.build(&mut assembly)
.unwrap();
let assembly_ref =
CodedIndex::new(TableId::Assembly, 1, CodedIndexType::HasDeclSecurity);
let assembly_security_ref = DeclSecurityBuilder::new()
.action(SecurityAction::RequestMinimum)
.parent(assembly_ref)
.unrestricted_permission_set() .build(&mut assembly)
.unwrap();
let privileged_method =
CodedIndex::new(TableId::MethodDef, 2, CodedIndexType::HasDeclSecurity);
let privilege_security_ref = DeclSecurityBuilder::new()
.action(SecurityAction::Assert)
.parent(privileged_method)
.unrestricted_permission_set()
.build(&mut assembly)
.unwrap();
assert_eq!(
file_security_ref.kind(),
ChangeRefKind::TableRow(TableId::DeclSecurity)
);
assert_eq!(
assembly_security_ref.kind(),
ChangeRefKind::TableRow(TableId::DeclSecurity)
);
assert_eq!(
privilege_security_ref.kind(),
ChangeRefKind::TableRow(TableId::DeclSecurity)
);
assert!(!std::sync::Arc::ptr_eq(
&file_security_ref,
&assembly_security_ref
));
assert!(!std::sync::Arc::ptr_eq(
&file_security_ref,
&privilege_security_ref
));
assert!(!std::sync::Arc::ptr_eq(
&assembly_security_ref,
&privilege_security_ref
));
}
}
}