mod io_block;
mod memory_block;
mod spin_locked_gcd;
pub use spin_locked_gcd::DescriptorFilter;
use goblin::pe::section_table;
use alloc::boxed::Box;
use core::{cell::Cell, ffi::c_void, ops::Range};
use patina::{
base::{align_down, align_up},
error::EfiError,
pi::{
dxe_services::{GcdIoType, GcdMemoryType, MemorySpaceDescriptor},
hob::{self, Hob, HobList, PhaseHandoffInformationTable},
},
};
use patina_internal_cpu::paging::{PatinaPageTable, create_cpu_paging};
use r_efi::efi;
#[cfg(feature = "compatibility_mode_allowed")]
use patina::base::{UEFI_PAGE_SIZE, align_range};
use crate::{GCD, gcd::spin_locked_gcd::PagingAllocator, pecoff};
pub use spin_locked_gcd::{AllocateType, MapChangeType, SpinLockedGcd};
pub(crate) struct MemoryProtectionPolicy {
memory_allocation_default_attributes: Cell<u64>,
}
impl MemoryProtectionPolicy {
pub(crate) const fn new() -> Self {
Self { memory_allocation_default_attributes: Cell::new(efi::MEMORY_XP) }
}
pub(crate) const fn apply_allocated_memory_protection_policy(&self, attributes: u64) -> u64 {
(attributes & efi::CACHE_ATTRIBUTE_MASK) | self.memory_allocation_default_attributes.get()
}
pub(crate) fn apply_resc_desc_hobs_protection_policy(attributes: u64, memory_type: GcdMemoryType) -> u64 {
let mut new_attributes = (attributes & efi::CACHE_ATTRIBUTE_MASK) | efi::MEMORY_XP;
if memory_type == GcdMemoryType::SystemMemory {
new_attributes |= efi::MEMORY_RP;
}
new_attributes
}
pub(crate) const fn apply_nx_to_uc_policy(attributes: u64) -> u64 {
let mut new_attributes = attributes;
if new_attributes & efi::MEMORY_UC == efi::MEMORY_UC {
new_attributes |= efi::MEMORY_XP;
}
new_attributes
}
pub(crate) const fn apply_memory_attributes_table_policy(attributes: u64, memory_type: efi::MemoryType) -> u64 {
const ALLOWED_MAT_ATTRIBUTES: u64 = efi::MEMORY_RO | efi::MEMORY_XP | efi::MEMORY_RUNTIME;
match attributes & (efi::MEMORY_RO | efi::MEMORY_XP) {
0 if memory_type == efi::RUNTIME_SERVICES_CODE => ALLOWED_MAT_ATTRIBUTES,
0 if memory_type == efi::RUNTIME_SERVICES_DATA => efi::MEMORY_RUNTIME | efi::MEMORY_XP,
_ => attributes & ALLOWED_MAT_ATTRIBUTES,
}
}
pub(crate) const fn apply_image_stack_guard_policy(attributes: u64) -> u64 {
attributes | efi::MEMORY_RP | efi::MEMORY_XP
}
pub(crate) const fn apply_image_protection_policy(
section_characteristics: u32,
descriptor: &MemorySpaceDescriptor,
) -> (u64, u64) {
let mut attributes = efi::MEMORY_XP;
if section_characteristics & pecoff::IMAGE_SCN_CNT_CODE == pecoff::IMAGE_SCN_CNT_CODE {
attributes = efi::MEMORY_RO;
}
if section_characteristics & section_table::IMAGE_SCN_MEM_WRITE == 0
&& ((section_characteristics & section_table::IMAGE_SCN_MEM_READ) == section_table::IMAGE_SCN_MEM_READ)
{
attributes |= efi::MEMORY_RO;
}
attributes |= descriptor.attributes & efi::CACHE_ATTRIBUTE_MASK;
let capabilities = attributes | descriptor.capabilities;
(attributes, capabilities)
}
pub(crate) fn apply_efi_memory_map_policy(
attributes: u64,
capabilities: u64,
gcd_memory_type: GcdMemoryType,
memory_type: efi::MemoryType,
) -> u64 {
let mut final_attributes =
capabilities & !(efi::MEMORY_ACCESS_MASK | efi::MEMORY_RUNTIME) | (attributes & efi::MEMORY_RUNTIME);
if gcd_memory_type == GcdMemoryType::Persistent {
final_attributes |= efi::MEMORY_NV;
}
if matches!(memory_type, efi::RUNTIME_SERVICES_CODE | efi::RUNTIME_SERVICES_DATA) {
final_attributes |= efi::MEMORY_RUNTIME;
}
final_attributes
}
pub(crate) const fn apply_add_memory_policy(capabilities: u64) -> (u64, u64) {
(capabilities | efi::MEMORY_ACCESS_MASK | efi::MEMORY_RUNTIME, efi::MEMORY_RP | efi::MEMORY_XP)
}
pub(crate) const fn apply_free_memory_policy(attributes: u64) -> u64 {
(attributes & efi::CACHE_ATTRIBUTE_MASK) | efi::MEMORY_RP | efi::MEMORY_XP
}
pub(crate) const fn apply_null_page_policy(attributes: u64) -> u64 {
(attributes & efi::CACHE_ATTRIBUTE_MASK) | efi::MEMORY_RP | efi::MEMORY_XP
}
#[cfg(not(feature = "compatibility_mode_allowed"))]
pub(crate) fn activate_compatibility_mode(
_gcd: &SpinLockedGcd,
_image_base_page: usize,
_image_num_pages: usize,
filename: &str,
) -> Result<(), EfiError> {
log::error!(
"Attempting to load {} that is not NX compatible. Compatibility mode is not allowed in this build, not loading image.",
filename
);
Err(EfiError::LoadError)
}
#[cfg(feature = "compatibility_mode_allowed")]
pub(crate) fn activate_compatibility_mode(
gcd: &SpinLockedGcd,
image_base_page: usize,
image_num_pages: usize,
filename: &str,
) -> Result<(), EfiError> {
use patina::log_debug_assert;
gcd.memory_protection_policy.memory_allocation_default_attributes.set(0);
const LEGACY_BIOS_WB_ADDRESS: usize = 0xA0000;
log::warn!("Attempting to load an application image that is not NX compatible. Activating compatibility mode.");
match gcd.get_existent_memory_descriptor_for_address(0) {
Ok(descriptor) if descriptor.memory_type == GcdMemoryType::SystemMemory => {
if let Err(e) = gcd.set_memory_space_attributes(
0,
UEFI_PAGE_SIZE,
descriptor.attributes & efi::CACHE_ATTRIBUTE_MASK,
) {
log_debug_assert!("Failed to map page 0 for compat mode. Status: {e:#x?}");
}
}
_ => {}
}
let mut address = UEFI_PAGE_SIZE; while address < LEGACY_BIOS_WB_ADDRESS {
let mut size = UEFI_PAGE_SIZE;
if let Ok(descriptor) = gcd.get_existent_memory_descriptor_for_address(address as efi::PhysicalAddress) {
if descriptor.memory_type == GcdMemoryType::SystemMemory {
size = match address + descriptor.length as usize {
end_addr if end_addr > LEGACY_BIOS_WB_ADDRESS => LEGACY_BIOS_WB_ADDRESS - address,
_ => descriptor.length as usize,
};
match gcd.set_memory_space_attributes(
address,
size,
descriptor.attributes & efi::CACHE_ATTRIBUTE_MASK,
) {
Ok(_) => {}
Err(e) => {
log_debug_assert!(
"Failed to map legacy bios region at {:#x?} of length {:#x?} with attributes {:#x?}. Status: {:#x?}",
address,
size,
descriptor.attributes & efi::CACHE_ATTRIBUTE_MASK,
e
);
}
}
}
}
address += size;
}
let mut loader_mem_ranges = crate::allocator::get_memory_ranges_for_memory_type(efi::LOADER_CODE);
loader_mem_ranges.extend(crate::allocator::get_memory_ranges_for_memory_type(efi::LOADER_DATA));
for range in loader_mem_ranges.iter() {
let mut addr = range.start;
while addr < range.end {
let mut len = UEFI_PAGE_SIZE as u64;
match gcd.get_existent_memory_descriptor_for_address(addr) {
Ok(descriptor) => {
let attributes = descriptor.attributes & !efi::MEMORY_XP;
len = match descriptor.base_address + descriptor.length {
end if end > range.end => range.end - addr,
_ => descriptor.length,
};
(addr, len) = match align_range(addr, len, UEFI_PAGE_SIZE as u64) {
Ok((aligned_addr, aligned_len)) => (aligned_addr, aligned_len),
Err(_) => {
log_debug_assert!(
"Failed to align address {addr:#x?} + {len:#x?} to page size, compatibility mode may fail",
);
addr += len;
continue;
}
};
if gcd.set_memory_space_attributes(addr as usize, len as usize, attributes).is_err() {
log_debug_assert!(
"Failed to set memory space attributes for range {addr:#x?} - {len:#x?}, compatibility mode may fail",
);
}
}
_ => {
log_debug_assert!(
"Failed to get memory space descriptor for range {:#x?} - {:#x?}, compatibility mode may fail",
range.start,
range.end,
);
}
}
addr += len;
}
}
crate::memory_attributes_protocol::uninstall_memory_attributes_protocol();
let stripped_attrs = gcd
.get_existent_memory_descriptor_for_address(image_base_page as u64)
.map(|desc| desc.attributes & efi::CACHE_ATTRIBUTE_MASK)
.unwrap_or(patina::base::DEFAULT_CACHE_ATTR);
if gcd
.set_memory_space_attributes(image_base_page, patina::uefi_pages_to_size!(image_num_pages), stripped_attrs)
.is_err()
{
log_debug_assert!("Failed to set GCD attributes for image {}", filename);
}
Ok(())
}
}
pub fn init_gcd(physical_hob_list: *const c_void) {
let mut free_memory_start: u64 = 0;
let mut free_memory_size: u64 = 0;
let mut memory_start: u64 = 0;
let mut memory_end: u64 = 0;
let mut free_memory_attributes: u64 = 0;
let mut free_memory_capabilities: u64 = 0;
let hob_list = Hob::Handoff(unsafe {
(physical_hob_list as *const PhaseHandoffInformationTable)
.as_ref::<'static>()
.expect("Physical hob list pointer is null, but it must exist and be valid.")
});
for hob in &hob_list {
match hob {
Hob::Handoff(handoff) => {
free_memory_start = align_up(handoff.free_memory_bottom, 0x1000).expect("Unaligned free memory bottom");
free_memory_size =
align_down(handoff.free_memory_top, 0x1000).expect("Unaligned free memory top") - free_memory_start;
memory_start = handoff.memory_bottom;
memory_end = handoff.memory_top;
}
Hob::Cpu(cpu) => {
GCD.init(cpu.size_of_memory_space as u32, cpu.size_of_io_space as u32);
}
Hob::ResourceDescriptorV2(_) | Hob::ResourceDescriptor(_) => {
debug_assert!(
free_memory_start != 0,
"The handoff HOB should come before any resource descriptor HOBs."
);
if free_memory_start != 0
&& free_memory_attributes == 0
&& let Some((res_desc, cache_attributes)) = parse_resource_descriptor_hob(&hob)
&& res_desc.resource_type == hob::EFI_RESOURCE_SYSTEM_MEMORY
&& res_desc.physical_start <= free_memory_start
&& res_desc.physical_start.saturating_add(res_desc.resource_length)
>= free_memory_start.saturating_add(free_memory_size)
{
free_memory_attributes = cache_attributes.unwrap_or(0);
free_memory_capabilities = spin_locked_gcd::get_capabilities(
GcdMemoryType::SystemMemory,
res_desc.resource_attribute as u64,
);
}
}
_ => (),
}
}
log::info!("memory_start: {memory_start:#x?}");
log::info!("memory_size: {:#x?}", memory_end - memory_start);
log::info!("free_memory_start: {free_memory_start:#x?}");
log::info!("free_memory_size: {free_memory_size:#x?}");
log::info!("physical_hob_list: {:#x?}", physical_hob_list as u64);
log::info!("free_memory_attributes: {free_memory_attributes:#x?}");
log::info!("free_memory_capabilities: {free_memory_capabilities:#x?}");
if free_memory_size == 0 {
panic!("PHIT HOB indicates no free memory available for DXE core to start. Free memory size = 0.");
}
if memory_end <= memory_start {
panic!("PHIT HOB indicates no memory available for DXE core to start. Memory end <= memory start.");
}
unsafe {
GCD.init_memory_blocks(
GcdMemoryType::SystemMemory,
free_memory_start as usize,
free_memory_size as usize,
free_memory_attributes,
efi::MEMORY_ACCESS_MASK | free_memory_capabilities,
)
.expect("Failed to add initial region to GCD.");
}
}
#[coverage(off)]
pub fn init_paging(hob_list: &HobList) {
let page_allocator = PagingAllocator::new(&GCD);
let page_table: Box<dyn PatinaPageTable> =
Box::new(create_cpu_paging(page_allocator).expect("Failed to create CPU page table"));
GCD.init_paging_with(hob_list, page_table);
}
pub fn add_hob_resource_descriptors_to_gcd(hob_list: &HobList) {
#[cfg(feature = "v1_resource_descriptor_support")]
{
log::debug!("v1_resource_descriptor_support feature is active (V1 ResourceDescriptor HOBs only)");
}
#[cfg(not(feature = "v1_resource_descriptor_support"))]
{
log::debug!("v1_resource_descriptor_support feature is NOT active (V2 ResourceDescriptor HOBs only)");
}
let phit = hob_list
.iter()
.find_map(|x| match x {
patina::pi::hob::Hob::Handoff(handoff) => Some(*handoff),
_ => None,
})
.expect("Failed to find PHIT Hob");
let free_memory_start = align_up(phit.free_memory_bottom, 0x1000).expect("Unaligned free memory bottom");
let free_memory_size =
align_down(phit.free_memory_top, 0x1000).expect("Unaligned free memory top") - free_memory_start;
for hob in hob_list.iter() {
let mut gcd_mem_type: GcdMemoryType = GcdMemoryType::NonExistent;
let mut resource_attributes: u32 = 0;
let (res_desc, cache_attributes) = match parse_resource_descriptor_hob(hob) {
Some((desc, Some(attrs))) => (desc, attrs),
Some((desc, None)) => (desc, 0u64),
None => continue, };
let mem_range = res_desc.physical_start
..res_desc.physical_start.checked_add(res_desc.resource_length).expect("Invalid resource descriptor hob");
match res_desc.resource_type {
hob::EFI_RESOURCE_SYSTEM_MEMORY => {
resource_attributes = res_desc.resource_attribute;
if resource_attributes & hob::MEMORY_ATTRIBUTE_MASK == hob::TESTED_MEMORY_ATTRIBUTES {
if resource_attributes & hob::EFI_RESOURCE_ATTRIBUTE_MORE_RELIABLE
== hob::EFI_RESOURCE_ATTRIBUTE_MORE_RELIABLE
{
gcd_mem_type = GcdMemoryType::MoreReliable;
} else {
gcd_mem_type = GcdMemoryType::SystemMemory;
}
}
if (resource_attributes & hob::MEMORY_ATTRIBUTE_MASK == (hob::INITIALIZED_MEMORY_ATTRIBUTES))
|| (resource_attributes & hob::MEMORY_ATTRIBUTE_MASK == (hob::PRESENT_MEMORY_ATTRIBUTES))
{
gcd_mem_type = GcdMemoryType::Reserved;
}
if resource_attributes & hob::EFI_RESOURCE_ATTRIBUTE_PERSISTENT
== hob::EFI_RESOURCE_ATTRIBUTE_PERSISTENT
{
gcd_mem_type = GcdMemoryType::Persistent;
}
}
hob::EFI_RESOURCE_MEMORY_MAPPED_IO | hob::EFI_RESOURCE_FIRMWARE_DEVICE => {
resource_attributes = res_desc.resource_attribute;
gcd_mem_type = GcdMemoryType::MemoryMappedIo;
}
hob::EFI_RESOURCE_MEMORY_MAPPED_IO_PORT | hob::EFI_RESOURCE_MEMORY_RESERVED => {
resource_attributes = res_desc.resource_attribute;
gcd_mem_type = GcdMemoryType::Reserved;
}
hob::EFI_RESOURCE_IO => {
log::info!(
"Mapping io range {:#x?} as {:?}",
res_desc.physical_start..res_desc.resource_length,
GcdIoType::Io
);
GCD.add_io_space(GcdIoType::Io, res_desc.physical_start as usize, res_desc.resource_length as usize)
.expect("Failed to add IO space to GCD");
}
hob::EFI_RESOURCE_IO_RESERVED => {
log::info!(
"Mapping io range {:#x?} as {:?}",
res_desc.physical_start..res_desc.resource_length,
GcdIoType::Reserved
);
GCD.add_io_space(
GcdIoType::Reserved,
res_desc.physical_start as usize,
res_desc.resource_length as usize,
)
.expect("Failed to add IO space to GCD");
}
_ => {
debug_assert!(false, "Unknown resource type in HOB");
}
};
if gcd_mem_type != GcdMemoryType::NonExistent {
debug_assert!(res_desc.attributes_valid());
}
if gcd_mem_type != GcdMemoryType::NonExistent {
let memory_attributes =
MemoryProtectionPolicy::apply_resc_desc_hobs_protection_policy(cache_attributes, gcd_mem_type);
for split_range in
remove_range_overlap(&mem_range, &(free_memory_start..(free_memory_start + free_memory_size)))
.into_iter()
.take_while(|r| r.is_some())
.flatten()
{
unsafe {
GCD.add_memory_space(
gcd_mem_type,
split_range.start as usize,
split_range.end.saturating_sub(split_range.start) as usize,
spin_locked_gcd::get_capabilities(gcd_mem_type, resource_attributes as u64),
)
.expect("Failed to add memory space to GCD");
}
log::info!(
"Mapping memory range {split_range:#x?} as {gcd_mem_type:?} with attributes {memory_attributes:#x?}",
);
match GCD.set_memory_space_attributes(
split_range.start as usize,
split_range.end.saturating_sub(split_range.start) as usize,
memory_attributes,
) {
Err(EfiError::NotReady) => (),
_ => {
debug_assert!(
false,
"GCD failed to set memory attributes {:#X} for base: {:#X}, length: {:#X}",
memory_attributes,
split_range.start,
split_range.end.saturating_sub(split_range.start),
);
}
}
}
}
}
}
fn remove_range_overlap<T: PartialOrd + Copy>(a: &Range<T>, b: &Range<T>) -> [Option<Range<T>>; 2] {
if a.start < b.end && a.end > b.start {
let first_range = if a.start < b.start { Some(a.start..b.start) } else { None };
let second_range = if a.end > b.end { Some(b.end..a.end) } else { None };
[first_range, second_range]
} else {
[Some(a.start..a.end), None]
}
}
#[cfg(not(feature = "v1_resource_descriptor_support"))]
fn parse_resource_descriptor_hob(hob: &Hob) -> Option<(hob::ResourceDescriptor, Option<u64>)> {
match hob {
Hob::ResourceDescriptorV2(v2_res_desc) => {
let attrs = if v2_res_desc.attributes != 0 { Some(v2_res_desc.attributes) } else { None };
Some((v2_res_desc.v1, attrs))
}
_ => None, }
}
#[cfg(feature = "v1_resource_descriptor_support")]
fn parse_resource_descriptor_hob(hob: &Hob) -> Option<(hob::ResourceDescriptor, Option<u64>)> {
match hob {
Hob::ResourceDescriptor(v1_res_desc) => {
Some((**v1_res_desc, None)) }
_ => None, }
}
#[cfg(test)]
#[coverage(off)]
mod tests {
use core::ffi::c_void;
use patina::pi::{
dxe_services::{GcdIoType, GcdMemoryType, IoSpaceDescriptor, MemorySpaceDescriptor},
hob::{HobList, PhaseHandoffInformationTable},
};
use crate::{
GCD,
gcd::init_gcd,
test_support::{self, build_test_hob_list},
};
use super::*;
const MEM_SIZE: u64 = 0x200000;
fn with_locked_state<F: Fn() + std::panic::RefUnwindSafe>(f: F) {
test_support::with_global_lock(|| {
test_support::init_test_logger();
unsafe {
GCD.reset();
}
f();
})
.unwrap();
}
fn init_gcd_should_init_gcd(physical_hob_list: *const c_void, mem_base: u64) {
let handoff = unsafe {
(physical_hob_list as *const PhaseHandoffInformationTable)
.as_ref::<'static>()
.expect("Physical hob list pointer is null, but it must exist and be valid.")
};
let free_memory_start = handoff.free_memory_bottom;
let free_memory_size = handoff.free_memory_top - handoff.free_memory_bottom;
init_gcd(physical_hob_list);
assert!(free_memory_start >= mem_base && free_memory_start < mem_base + MEM_SIZE);
assert!(free_memory_size <= 0x100000);
let mut descriptors: Vec<MemorySpaceDescriptor> = Vec::with_capacity(GCD.memory_descriptor_count() + 10);
GCD.get_memory_descriptors(&mut descriptors, DescriptorFilter::All).expect("get_memory_descriptors failed.");
assert!(
descriptors
.iter()
.any(|x| x.base_address == free_memory_start && x.memory_type == GcdMemoryType::SystemMemory)
)
}
fn add_resource_descriptors_should_add_resource_descriptors(hob_list: &HobList, mem_base: u64) {
add_hob_resource_descriptors_to_gcd(hob_list);
let mut descriptors: Vec<MemorySpaceDescriptor> = Vec::with_capacity(GCD.memory_descriptor_count() + 10);
GCD.get_memory_descriptors(&mut descriptors, DescriptorFilter::All).expect("get_memory_descriptors failed.");
descriptors
.iter()
.find(|x| x.base_address == mem_base + 0xE0000 && x.memory_type == GcdMemoryType::SystemMemory)
.unwrap();
descriptors
.iter()
.find(|x| x.base_address == mem_base + 0x190000 && x.memory_type == GcdMemoryType::Reserved)
.unwrap();
let mmio_3_4 = descriptors
.iter()
.find(|x| x.base_address == 0x10000000 && x.memory_type == GcdMemoryType::MemoryMappedIo)
.unwrap();
assert_eq!(mmio_3_4.length, 0x2000000);
descriptors.iter().find(|x| x.base_address == 0x12000000 && x.memory_type == GcdMemoryType::Reserved).unwrap();
let mut descriptors: Vec<IoSpaceDescriptor> = Vec::with_capacity(GCD.io_descriptor_count() + 10);
GCD.get_io_descriptors(&mut descriptors).expect("get_io_descriptors failed.");
descriptors.iter().find(|x| x.base_address == 0x0000 && x.io_type == GcdIoType::Reserved).unwrap();
descriptors.iter().find(|x| x.base_address == 0x1000 && x.io_type == GcdIoType::Io).unwrap();
}
#[test]
fn test_full_gcd_init() {
with_locked_state(|| {
let physical_hob_list = build_test_hob_list(MEM_SIZE);
init_gcd_should_init_gcd(physical_hob_list, physical_hob_list as u64);
let mut hob_list = HobList::default();
hob_list.discover_hobs(physical_hob_list);
add_resource_descriptors_should_add_resource_descriptors(&hob_list, physical_hob_list as u64);
});
}
#[test]
fn test_remove_range_overlap() {
let a = 10..20;
let b = 30..40;
let result = remove_range_overlap(&a, &b);
assert_eq!(result, [Some(10..20), None]);
let a = 5..20;
let b = 10..30;
let result = remove_range_overlap(&a, &b);
assert_eq!(result, [Some(5..10), None]);
let a = 20..40;
let b = 10..30;
let result = remove_range_overlap(&a, &b);
assert_eq!(result, [None, Some(30..40)]);
let a = 20..30;
let b = 10..40;
let result = remove_range_overlap(&a, &b);
assert_eq!(result, [None, None]);
}
#[test]
fn test_memory_protection_policy_apply_allocated_memory_protection_policy() {
let policy = MemoryProtectionPolicy::new();
let attributes = efi::MEMORY_WB;
let result = policy.apply_allocated_memory_protection_policy(attributes);
assert_eq!(result, efi::MEMORY_WB | efi::MEMORY_XP);
}
#[test]
fn test_memory_protection_policy_apply_resc_desc_hobs_protection_policy() {
let attributes = efi::MEMORY_WB;
let memory_type = GcdMemoryType::SystemMemory;
let result = MemoryProtectionPolicy::apply_resc_desc_hobs_protection_policy(attributes, memory_type);
assert_eq!(result, efi::MEMORY_WB | efi::MEMORY_XP | efi::MEMORY_RP);
let attributes = efi::MEMORY_UC;
let memory_type = GcdMemoryType::MemoryMappedIo;
let result = MemoryProtectionPolicy::apply_resc_desc_hobs_protection_policy(attributes, memory_type);
assert_eq!(result, efi::MEMORY_UC | efi::MEMORY_XP);
}
#[test]
fn test_memory_protection_policy_apply_nx_to_uc_policy() {
let attributes = efi::MEMORY_UC;
let result = MemoryProtectionPolicy::apply_nx_to_uc_policy(attributes);
assert_eq!(result, efi::MEMORY_UC | efi::MEMORY_XP);
let attributes = efi::MEMORY_WB;
let result = MemoryProtectionPolicy::apply_nx_to_uc_policy(attributes);
assert_eq!(result, efi::MEMORY_WB);
}
#[test]
fn test_memory_protection_policy_apply_memory_attributes_table_policy() {
let attributes = efi::MEMORY_WB;
let memory_type = efi::RUNTIME_SERVICES_CODE;
let result = MemoryProtectionPolicy::apply_memory_attributes_table_policy(attributes, memory_type);
assert_eq!(result, efi::MEMORY_RO | efi::MEMORY_XP | efi::MEMORY_RUNTIME);
let attributes = efi::MEMORY_WB;
let memory_type = efi::RUNTIME_SERVICES_DATA;
let result = MemoryProtectionPolicy::apply_memory_attributes_table_policy(attributes, memory_type);
assert_eq!(result, efi::MEMORY_RUNTIME | efi::MEMORY_XP);
let attributes = efi::MEMORY_RO | efi::MEMORY_XP | efi::MEMORY_RUNTIME | efi::MEMORY_WB;
let memory_type = efi::RUNTIME_SERVICES_CODE;
let result = MemoryProtectionPolicy::apply_memory_attributes_table_policy(attributes, memory_type);
assert_eq!(result, efi::MEMORY_RO | efi::MEMORY_XP | efi::MEMORY_RUNTIME);
}
#[test]
fn test_memory_protection_policy_apply_image_stack_guard_policy() {
let attributes = efi::MEMORY_WB;
let result = MemoryProtectionPolicy::apply_image_stack_guard_policy(attributes);
assert_eq!(result, efi::MEMORY_RP | efi::MEMORY_XP | efi::MEMORY_WB);
}
#[test]
fn test_memory_protection_policy_apply_image_protection_policy() {
let descriptor = MemorySpaceDescriptor {
base_address: 0,
length: 0x1000,
memory_type: GcdMemoryType::SystemMemory,
attributes: efi::MEMORY_WB,
capabilities: efi::MEMORY_WB | efi::MEMORY_ACCESS_MASK,
image_handle: std::ptr::null_mut(),
device_handle: std::ptr::null_mut(),
};
let section_characteristics = pecoff::IMAGE_SCN_CNT_CODE;
let (attributes, capabilities) =
MemoryProtectionPolicy::apply_image_protection_policy(section_characteristics, &descriptor);
assert_eq!(attributes, efi::MEMORY_RO | efi::MEMORY_WB);
assert_eq!(capabilities, efi::MEMORY_WB | efi::MEMORY_ACCESS_MASK);
let section_characteristics = section_table::IMAGE_SCN_MEM_READ;
let (attributes, _) =
MemoryProtectionPolicy::apply_image_protection_policy(section_characteristics, &descriptor);
assert_eq!(attributes, efi::MEMORY_RO | efi::MEMORY_WB | efi::MEMORY_XP);
let section_characteristics = section_table::IMAGE_SCN_MEM_WRITE;
let (attributes, _) =
MemoryProtectionPolicy::apply_image_protection_policy(section_characteristics, &descriptor);
assert_eq!(attributes, efi::MEMORY_XP | efi::MEMORY_WB);
}
#[test]
fn test_memory_protection_policy_apply_efi_memory_map_policy() {
let attributes = efi::MEMORY_RUNTIME | efi::MEMORY_WB;
let capabilities = efi::MEMORY_WB | efi::MEMORY_RO | efi::MEMORY_RUNTIME;
let gcd_memory_type = GcdMemoryType::Persistent;
let memory_type = efi::CONVENTIONAL_MEMORY;
let result =
MemoryProtectionPolicy::apply_efi_memory_map_policy(attributes, capabilities, gcd_memory_type, memory_type);
assert_eq!(result, efi::MEMORY_NV | efi::MEMORY_RUNTIME | efi::MEMORY_WB);
let attributes = efi::MEMORY_WB;
let gcd_memory_type = GcdMemoryType::SystemMemory;
let result =
MemoryProtectionPolicy::apply_efi_memory_map_policy(attributes, capabilities, gcd_memory_type, memory_type);
assert_eq!(result, efi::MEMORY_WB);
let attributes = efi::MEMORY_WB;
let capabilities = efi::MEMORY_WB | efi::MEMORY_RO | efi::MEMORY_RUNTIME;
let gcd_memory_type = GcdMemoryType::SystemMemory;
let memory_type = efi::RUNTIME_SERVICES_CODE;
let result =
MemoryProtectionPolicy::apply_efi_memory_map_policy(attributes, capabilities, gcd_memory_type, memory_type);
assert_eq!(result, efi::MEMORY_WB | efi::MEMORY_RUNTIME);
let memory_type = efi::RUNTIME_SERVICES_DATA;
let result =
MemoryProtectionPolicy::apply_efi_memory_map_policy(attributes, capabilities, gcd_memory_type, memory_type);
assert_eq!(result, efi::MEMORY_WB | efi::MEMORY_RUNTIME);
}
#[test]
fn test_memory_protection_policy_apply_add_memory_policy() {
let capabilities = efi::MEMORY_WB | efi::MEMORY_UC;
let (new_capabilities, attributes) = MemoryProtectionPolicy::apply_add_memory_policy(capabilities);
assert_eq!(new_capabilities, efi::MEMORY_ACCESS_MASK | efi::MEMORY_RUNTIME | efi::MEMORY_WB | efi::MEMORY_UC);
assert_eq!(attributes, efi::MEMORY_RP | efi::MEMORY_XP);
}
#[test]
fn test_memory_protection_policy_apply_free_memory_policy() {
let attributes = efi::MEMORY_WB | efi::MEMORY_RO | efi::MEMORY_RUNTIME;
let result = MemoryProtectionPolicy::apply_free_memory_policy(attributes);
assert_eq!(result, efi::MEMORY_RP | efi::MEMORY_XP | efi::MEMORY_WB);
}
#[test]
fn test_memory_protection_policy_apply_null_page_policy() {
let attributes = efi::MEMORY_WB | efi::MEMORY_RO;
let result = MemoryProtectionPolicy::apply_null_page_policy(attributes);
assert_eq!(result, efi::MEMORY_RP | efi::MEMORY_XP | efi::MEMORY_WB);
}
#[test]
#[cfg(not(feature = "compatibility_mode_allowed"))]
fn test_activate_compatibility_mode_not_allowed() {
let gcd: SpinLockedGcd = SpinLockedGcd::new(None);
let result = MemoryProtectionPolicy::activate_compatibility_mode(&gcd, 0x1000, 0x10, "test_image.efi");
assert_eq!(result, Err(EfiError::LoadError));
}
#[test]
#[cfg(feature = "compatibility_mode_allowed")]
fn test_activate_compatibility_mode_allowed() {
use crate::{
allocator,
test_support::{MockPageTable, MockPageTableWrapper},
};
use std::{cell::RefCell, rc::Rc};
with_locked_state(|| {
GCD.init(48, 16);
let mem = unsafe { crate::test_support::get_memory(spin_locked_gcd::MEMORY_BLOCK_SLICE_SIZE * 10) };
let address = align_up(mem.as_ptr() as usize, 0x1000).unwrap();
unsafe {
GCD.init_memory_blocks(
patina::pi::dxe_services::GcdMemoryType::SystemMemory,
address,
spin_locked_gcd::MEMORY_BLOCK_SLICE_SIZE * 10,
efi::MEMORY_WB,
efi::CACHE_ATTRIBUTE_MASK | efi::MEMORY_ACCESS_MASK,
)
.unwrap();
GCD.add_memory_space(
patina::pi::dxe_services::GcdMemoryType::SystemMemory,
0x0,
0xA0000,
efi::CACHE_ATTRIBUTE_MASK | efi::MEMORY_ACCESS_MASK,
)
.unwrap();
}
let mock_table = Rc::new(RefCell::new(MockPageTable::new()));
let mock_page_table = Box::new(MockPageTableWrapper::new(Rc::clone(&mock_table)));
GCD.add_test_page_table(mock_page_table);
let policy = &GCD.memory_protection_policy;
assert_eq!(policy.memory_allocation_default_attributes.get(), efi::MEMORY_XP);
let mut loader_code_mem = 0;
let mut loader_data_mem = 0;
allocator::core_allocate_pages(efi::ALLOCATE_ANY_PAGES, efi::LOADER_CODE, 2, &mut loader_code_mem, None)
.expect("Failed to allocate loader code memory");
allocator::core_allocate_pages(efi::ALLOCATE_ANY_PAGES, efi::LOADER_DATA, 2, &mut loader_data_mem, None)
.expect("Failed to allocate loader data memory");
let loader_code_ranges = allocator::get_memory_ranges_for_memory_type(efi::LOADER_CODE);
let loader_data_ranges = allocator::get_memory_ranges_for_memory_type(efi::LOADER_DATA);
for range in loader_code_ranges.iter().chain(loader_data_ranges.iter()) {
let mut addr = range.start;
while addr < range.end {
let mut len = 0x1000;
if let Ok(desc) = GCD.get_existent_memory_descriptor_for_address(addr) {
assert_eq!(desc.attributes & efi::MEMORY_XP, efi::MEMORY_XP);
len = desc.length;
}
addr += len;
}
}
let mut image_base_page = 0;
allocator::core_allocate_pages(
efi::ALLOCATE_ANY_PAGES,
efi::BOOT_SERVICES_DATA,
4,
&mut image_base_page,
None,
)
.expect("Failed to allocate loader code memory");
let image_num_pages = 4;
let filename = "legacy_app.efi";
let desc = GCD.get_existent_memory_descriptor_for_address(image_base_page).unwrap();
assert_eq!(desc.attributes & efi::MEMORY_XP, efi::MEMORY_XP);
let result = MemoryProtectionPolicy::activate_compatibility_mode(
&GCD,
image_base_page as usize,
image_num_pages,
filename,
);
assert!(result.is_ok());
assert_eq!(policy.memory_allocation_default_attributes.get(), 0);
let desc = GCD.get_existent_memory_descriptor_for_address(0).unwrap();
assert_eq!(desc.attributes & efi::CACHE_ATTRIBUTE_MASK, efi::MEMORY_WB);
let legacy_desc = GCD.get_existent_memory_descriptor_for_address(0xA0000);
if let Ok(desc) = legacy_desc
&& desc.memory_type == GcdMemoryType::SystemMemory
{
assert_eq!(desc.base_address, 0xA0000);
}
let loader_code_ranges = allocator::get_memory_ranges_for_memory_type(efi::LOADER_CODE);
let loader_data_ranges = allocator::get_memory_ranges_for_memory_type(efi::LOADER_DATA);
for range in loader_code_ranges.iter().chain(loader_data_ranges.iter()) {
let mut addr = range.start;
while addr < range.end {
let mut len = 0x1000;
if let Ok(desc) = GCD.get_existent_memory_descriptor_for_address(addr) {
assert_eq!(desc.attributes & efi::MEMORY_XP, 0);
len = desc.length;
}
addr += len;
}
}
let desc = GCD.get_existent_memory_descriptor_for_address(image_base_page).unwrap();
assert_eq!(desc.attributes & efi::MEMORY_XP, 0);
});
}
}