use alloc::{boxed::Box, collections::BTreeMap, vec, vec::Vec};
use core::{
convert::TryInto,
ffi::c_void,
mem::transmute,
ptr::{NonNull, null_mut},
slice,
slice::from_raw_parts,
};
use patina::{
base::{DEFAULT_CACHE_ATTR, UEFI_PAGE_SIZE, align_up},
component::service::memory::{AllocationOptions, MemoryManager, PageFree},
efi_types::EfiMemoryType,
error::EfiError,
guids,
performance::{
logging::{perf_image_start_begin, perf_image_start_end, perf_load_image_begin, perf_load_image_end},
measurement::create_performance_measurement,
},
pi::{
self,
fw_fs::FfsSectionRawType::PE32,
hob::{Hob, HobList},
},
uefi_size_to_pages,
};
use patina_internal_device_path::{DevicePathWalker, copy_device_path_to_boxed_slice, device_path_node_count};
use r_efi::efi;
use crate::{
GCD,
dxe_services::{self, core_set_memory_space_attributes},
events::EVENT_DB,
filesystems::SimpleFile,
gcd::MemoryProtectionPolicy,
memory_manager::CoreMemoryManager,
pecoff::{self, UefiPeInfo, relocation::RelocationBlock},
pi_dispatcher::debug_image_info_table::{DebugImageInfoData, ImageInfoType},
protocol_db,
protocols::{
PROTOCOL_DB, core_install_protocol_interface, core_locate_device_path, core_uninstall_protocol_interface,
},
runtime,
systemtables::EfiSystemTable,
tpl_mutex,
};
use efi::Guid;
use uefi_corosensei::{
Coroutine, CoroutineResult, Yielder,
stack::{MIN_STACK_SIZE, STACK_ALIGNMENT, Stack, StackPointer},
};
pub const EFI_IMAGE_SUBSYSTEM_EFI_APPLICATION: u16 = 10;
pub const EFI_IMAGE_SUBSYSTEM_EFI_BOOT_SERVICE_DRIVER: u16 = 11;
pub const EFI_IMAGE_SUBSYSTEM_EFI_RUNTIME_DRIVER: u16 = 12;
pub const ENTRY_POINT_STACK_SIZE: usize = 0x100000;
const _: () = assert!(STACK_ALIGNMENT < UEFI_PAGE_SIZE);
#[coverage(off)]
extern "efiapi" fn unimplemented_entry_point(
_handle: efi::Handle,
_system_table: *mut efi::SystemTable,
) -> efi::Status {
unimplemented!()
}
struct ImageStack {
stack: Box<[u8], PageFree>,
}
impl ImageStack {
fn new(size: usize) -> Result<Self, EfiError> {
let len = align_up(size.max(MIN_STACK_SIZE), STACK_ALIGNMENT)?;
let page_count = uefi_size_to_pages!(len) + 1;
let stack = CoreMemoryManager.allocate_pages(page_count, AllocationOptions::default())?.into_boxed_slice();
let base_address = stack.as_ptr() as efi::PhysicalAddress;
let mut attributes = match dxe_services::core_get_memory_space_descriptor(base_address) {
Ok(descriptor) => descriptor.attributes,
Err(_) => DEFAULT_CACHE_ATTR,
};
attributes = MemoryProtectionPolicy::apply_image_stack_guard_policy(attributes);
if let Err(err) =
dxe_services::core_set_memory_space_attributes(base_address, UEFI_PAGE_SIZE as u64, attributes)
{
log::error!("Failed to set memory space attributes for stack guard page: {err:?}");
}
Ok(ImageStack { stack })
}
#[allow(unused)]
fn guard(&self) -> &[u8] {
&self.stack[..UEFI_PAGE_SIZE]
}
fn body(&self) -> &[u8] {
&self.stack[UEFI_PAGE_SIZE..]
}
}
unsafe impl Stack for ImageStack {
fn base(&self) -> StackPointer {
self.limit().checked_add(self.body().len()).expect("Stack base address overflow.")
}
fn limit(&self) -> StackPointer {
StackPointer::new(self.body().as_ptr() as usize)
.expect("Stack pointer address was zero, but it should always be nonzero.")
}
}
struct PrivateImageData {
image_buffer: Buffer,
image_info: Box<efi::protocols::loaded_image::Protocol>,
hii_resource_section: Option<Box<[u8], PageFree>>,
entry_point: efi::ImageEntryPoint,
started: bool,
exit_data: Option<ExitData>,
image_device_path: Option<Box<[u8]>>,
pe_info: UefiPeInfo,
relocation_data: Vec<RelocationBlock>,
}
impl PrivateImageData {
fn new(mut image_info: efi::protocols::loaded_image::Protocol, pe_info: UefiPeInfo) -> Result<Self, EfiError> {
let image_size = usize::try_from(image_info.image_size).map_err(|_| EfiError::LoadError)?;
let section_alignment = usize::try_from(pe_info.section_alignment).map_err(|_| EfiError::LoadError)?;
let page_count = if section_alignment > UEFI_PAGE_SIZE {
image_size
.checked_add(section_alignment)
.map(|size| uefi_size_to_pages!(size))
.ok_or(EfiError::LoadError)?
} else {
uefi_size_to_pages!(image_size)
};
let options = AllocationOptions::new()
.with_memory_type(EfiMemoryType::from_efi(image_info.image_code_type)?)
.with_alignment(section_alignment);
let bytes = CoreMemoryManager.allocate_pages(page_count, options)?.into_boxed_slice::<u8>();
image_info.image_base = bytes.as_ptr() as *mut c_void;
let image_data = PrivateImageData {
image_buffer: Buffer::Owned(bytes),
image_info: Box::new(image_info),
hii_resource_section: None,
entry_point: unimplemented_entry_point,
started: false,
exit_data: None,
image_device_path: None,
pe_info,
relocation_data: Vec::new(),
};
Ok(image_data)
}
fn new_from_static_image(
image_info: efi::protocols::loaded_image::Protocol,
image_buffer: &'static [u8],
entry_point: efi::ImageEntryPoint,
pe_info: &UefiPeInfo,
) -> Self {
PrivateImageData {
image_buffer: Buffer::Borrowed(image_buffer),
image_info: Box::new(image_info),
hii_resource_section: None,
entry_point,
started: true,
exit_data: None,
image_device_path: None,
pe_info: pe_info.clone(),
relocation_data: Vec::new(),
}
}
fn load_resource_section(&mut self, image: &[u8]) -> Result<(), EfiError> {
let loaded_image = self.image_buffer.as_ref();
let result = pecoff::load_resource_section(&self.pe_info, image).map_err(|err| {
let pe_file_name = self.pe_info.filename_or("Unknown");
log::error!("core_load_pe_image failed: {pe_file_name} load_resource_section returned status: {err:?}");
EfiError::LoadError
})?;
let Some((resource_section_offset, resource_section_size)) = result else { return Ok(()) };
if resource_section_offset + resource_section_size > loaded_image.len() {
let pe_file_name = self.pe_info.filename_or("Unknown");
log::error!(
"HII Resource Section offset {:#X} and size {:#X} are out of bounds for image {pe_file_name}.",
resource_section_offset,
resource_section_size
);
debug_assert!(false);
return Err(EfiError::LoadError);
}
let resource_section = &loaded_image[resource_section_offset..resource_section_offset + resource_section_size];
let size = resource_section.len();
let alignment = usize::try_from(self.pe_info.section_alignment).map_err(|_| EfiError::LoadError)?;
let memory_type = EfiMemoryType::from_efi(self.image_info.image_code_type)?;
let page_count: usize =
if alignment > UEFI_PAGE_SIZE { uefi_size_to_pages!(size + alignment) } else { uefi_size_to_pages!(size) };
let options = AllocationOptions::new().with_memory_type(memory_type).with_alignment(alignment);
let mut bytes = CoreMemoryManager.allocate_pages(page_count, options)?.into_boxed_slice::<u8>();
bytes[..resource_section.len()].copy_from_slice(resource_section);
self.hii_resource_section = Some(bytes);
Ok(())
}
fn load_image(&mut self, image: &[u8]) -> Result<(), EfiError> {
let bytes = self.image_buffer.as_mut().ok_or(EfiError::LoadError)?;
if let Err(e) = pecoff::load_image(&self.pe_info, image, bytes) {
let file_name = self.pe_info.filename_or("Unknown");
log::error!("core_load_pe_image failed: {file_name} load_image returned status: {e:?}");
return Err(EfiError::LoadError);
}
Ok(())
}
fn relocate_image(&mut self) -> Result<(), EfiError> {
let image_buffer = self.image_buffer.as_mut().ok_or(EfiError::LoadError)?;
let physical_addr = self.image_info.image_base as usize;
self.relocation_data =
pecoff::relocate_image(&self.pe_info, physical_addr, image_buffer, &self.relocation_data).map_err(
|err| {
let pe_file_name = self.pe_info.filename_or("Unknown");
log::error!("core_load_pe_image failed: {pe_file_name} relocate_image returned status: {err:?}");
EfiError::LoadError
},
)?;
self.entry_point = unsafe {
transmute::<usize, extern "efiapi" fn(*mut c_void, *mut r_efi::system::SystemTable) -> efi::Status>(
physical_addr + self.pe_info.entry_point_offset,
)
};
Ok(())
}
fn install(&self) -> Result<efi::Handle, EfiError> {
let handle = core_install_protocol_interface(
None,
efi::protocols::loaded_image::PROTOCOL_GUID,
self.image_info.as_ref() as *const efi::protocols::loaded_image::Protocol as *mut c_void,
)?;
core_install_protocol_interface(
Some(handle),
efi::protocols::loaded_image_device_path::PROTOCOL_GUID,
self.get_file_path(),
)?;
if let Some(hii_section) = &self.hii_resource_section {
core_install_protocol_interface(
Some(handle),
efi::protocols::hii_package_list::PROTOCOL_GUID,
hii_section.as_ptr() as *mut c_void,
)?;
}
if self.pe_info.image_type == EFI_IMAGE_SUBSYSTEM_EFI_RUNTIME_DRIVER {
runtime::add_runtime_image(
self.image_info.image_base,
self.image_info.image_size,
&self.relocation_data,
handle,
)?;
}
Ok(handle)
}
fn uninstall(&self, handle: efi::Handle) -> Result<(), EfiError> {
let mut result = Ok(());
if let Err(err) = core_uninstall_protocol_interface(
handle,
efi::protocols::loaded_image::PROTOCOL_GUID,
self.image_info.as_ref() as *const efi::protocols::loaded_image::Protocol as *mut c_void,
) && !matches!(err, EfiError::NotFound | EfiError::InvalidParameter)
{
log::warn!("Failed to uninstall loaded image protocol for handle {handle:?}: {err:?}");
result = Err(err);
}
if let Err(err) = core_uninstall_protocol_interface(
handle,
efi::protocols::loaded_image_device_path::PROTOCOL_GUID,
self.get_file_path(),
) && !matches!(err, EfiError::NotFound | EfiError::InvalidParameter)
{
log::warn!("Failed to uninstall loaded image device path protocol for handle {handle:?}: {err:?}");
result = Err(err);
}
if let Some(hii_section) = &self.hii_resource_section
&& let Err(err) = core_uninstall_protocol_interface(
handle,
efi::protocols::hii_package_list::PROTOCOL_GUID,
hii_section.as_ptr() as *mut c_void,
)
&& !matches!(err, EfiError::NotFound | EfiError::InvalidParameter)
{
log::warn!("Failed to uninstall HII package list protocol for handle {handle:?}: {err:?}");
result = Err(err);
}
if self.pe_info.image_type == EFI_IMAGE_SUBSYSTEM_EFI_RUNTIME_DRIVER
&& let Err(err) = runtime::remove_runtime_image(handle)
&& err != EfiError::NotFound
{
log::warn!("Failed to remove runtime image for handle {handle:?}: {err:?}");
result = Err(err);
}
result
}
fn set_file_path(&mut self, file_path: NonNull<efi::protocols::device_path::Protocol>) -> Result<(), EfiError> {
let mut fp = file_path.as_ptr();
if let Ok(device_path) = PROTOCOL_DB
.get_interface_for_handle(self.image_info.device_handle, efi::protocols::device_path::PROTOCOL_GUID)
{
let (_, device_path_size) =
device_path_node_count(device_path as *mut efi::protocols::device_path::Protocol)?;
let split_idx =
device_path_size.saturating_sub(core::mem::size_of::<efi::protocols::device_path::Protocol>());
fp = unsafe {
file_path.cast::<u8>().add(split_idx).cast::<efi::protocols::device_path::Protocol>().as_ptr()
};
}
self.image_info.file_path =
Box::into_raw(copy_device_path_to_boxed_slice(fp)?) as *mut efi::protocols::device_path::Protocol;
self.image_device_path = Some(copy_device_path_to_boxed_slice(file_path.as_ptr())?);
Ok(())
}
fn get_file_path(&self) -> *mut c_void {
self.image_device_path.as_ref().map_or(core::ptr::null_mut(), |dp| dp.as_ptr() as *mut c_void)
}
fn activate_compatibility_mode(&self) -> Result<(), EfiError> {
let bytes = self.image_buffer.as_ref();
MemoryProtectionPolicy::activate_compatibility_mode(
&GCD,
bytes.as_ptr() as usize,
uefi_size_to_pages!(bytes.len()),
self.pe_info.filename_or("Unknown"),
)
}
fn apply_image_memory_protections(&self) -> Result<(), EfiError> {
for section in &self.pe_info.sections {
let section_base_addr = (self.image_info.image_base as u64) + (section.virtual_address as u64);
let desc = dxe_services::core_get_memory_space_descriptor(section_base_addr)?;
let (attributes, capabilities) =
MemoryProtectionPolicy::apply_image_protection_policy(section.characteristics, &desc);
let aligned_virtual_size =
if let Ok(virtual_size) = align_up(section.virtual_size, self.pe_info.section_alignment) {
virtual_size as u64
} else {
log::error!(
"Failed to align up section size {:#X} with alignment {:#X}",
section.virtual_size,
self.pe_info.section_alignment
);
debug_assert!(false);
return Err(EfiError::LoadError);
};
if let Err(status) =
dxe_services::core_set_memory_space_capabilities(section_base_addr, aligned_virtual_size, capabilities)
{
log::error!(
"Failed to set GCD capabilities for image section {section_base_addr:#X} with Status {status:#X?}",
);
}
log::info!(
"Applying image memory protections on {section_base_addr:#X} for len {aligned_virtual_size:#X} with attributes {attributes:#X}",
);
dxe_services::core_set_memory_space_attributes(section_base_addr, aligned_virtual_size, attributes)
.inspect_err(|status| {
log::error!(
"Failed to set GCD attributes for image section {section_base_addr:#X} with Status {status:#X?}",
);
})?;
}
Ok(())
}
}
struct ExitData(usize, *mut efi::Char16);
unsafe impl Sync for ExitData {}
unsafe impl Send for ExitData {}
pub(super) struct ImageData {
system_table: *mut efi::SystemTable,
private_image_data: BTreeMap<efi::Handle, PrivateImageData>,
current_running_image: Option<efi::Handle>,
image_start_contexts: Vec<*const Yielder<efi::Handle, efi::Status>>,
}
impl ImageData {
const fn new() -> Self {
ImageData {
system_table: core::ptr::null_mut(),
private_image_data: BTreeMap::new(),
current_running_image: None,
image_start_contexts: Vec::new(),
}
}
pub(super) const fn new_locked() -> tpl_mutex::TplMutex<Self> {
tpl_mutex::TplMutex::new(efi::TPL_NOTIFY, Self::new(), "ImageLock")
}
pub const fn set_system_table(&mut self, system_table: *mut efi::SystemTable) {
self.system_table = system_table;
}
pub(super) fn install_dxe_core_image(
&mut self,
hob_list: &HobList,
system_table: &mut EfiSystemTable,
debug_image_data: &mut DebugImageInfoData,
) {
let dxe_core_hob = hob_list
.iter()
.find_map(|hob| {
if let Hob::MemoryAllocationModule(module) = hob
&& module.module_name == guids::DXE_CORE
{
Some(module)
} else {
None
}
})
.expect("Did not find MemoryAllocationModule Hob for DxeCore. Use patina::guid::DXE_CORE as FFS GUID.");
let mut image_info = empty_image_info();
image_info.system_table = system_table as *mut _ as *mut efi::SystemTable;
image_info.image_base = dxe_core_hob.alloc_descriptor.memory_base_address as *mut c_void;
image_info.image_size = dxe_core_hob.alloc_descriptor.memory_length;
let entry_point = unsafe {
transmute::<u64, extern "efiapi" fn(*mut c_void, *mut r_efi::system::SystemTable) -> r_efi::base::Status>(
dxe_core_hob.entry_point,
)
};
let dxe_core_image_buffer = unsafe {
from_raw_parts(
dxe_core_hob.alloc_descriptor.memory_base_address as *const u8,
dxe_core_hob.alloc_descriptor.memory_length as usize,
)
};
let pe_info = UefiPeInfo::parse(dxe_core_image_buffer).expect("Failed to parse PE info for DXE Core");
let private_image_data =
PrivateImageData::new_from_static_image(image_info, dxe_core_image_buffer, entry_point, &pe_info);
let handle = core_install_protocol_interface(
Some(protocol_db::DXE_CORE_HANDLE),
efi::protocols::loaded_image::PROTOCOL_GUID,
private_image_data.image_info.as_ref() as *const efi::protocols::loaded_image::Protocol as *mut c_void,
)
.unwrap_or_else(|err| panic!("Failed to install dxe core image handle: {err:?}"));
assert_eq!(handle, protocol_db::DXE_CORE_HANDLE);
let protocol_ptr = NonNull::from(private_image_data.image_info.as_ref());
self.private_image_data.insert(handle, private_image_data);
debug_image_data.add_entry(ImageInfoType::Normal, protocol_ptr, handle);
}
fn validate_parent(parent: efi::Handle) -> Result<(), EfiError> {
PROTOCOL_DB.validate_handle(parent).inspect_err(|err| log::error!("Invalid parent handle {err:?}"))?;
PROTOCOL_DB.get_interface_for_handle(parent, efi::protocols::loaded_image::PROTOCOL_GUID).map_err(|err| {
log::error!("Failed to get loaded image interface on the parent handle: {err:?}");
EfiError::InvalidParameter
})?;
Ok(())
}
fn locate_image_metadata_by_buffer(
image: &[u8],
file_path: *mut efi::protocols::device_path::Protocol,
) -> (Vec<u8>, bool, *mut c_void, u32) {
if let Ok((_, device_handle)) = core_locate_device_path(efi::protocols::device_path::PROTOCOL_GUID, file_path) {
(image.to_vec(), false, device_handle, 0)
} else {
(image.to_vec(), false, protocol_db::INVALID_HANDLE, 0)
}
}
fn locate_image_metadata_by_file_path(
boot_policy: bool,
file_path: *mut efi::protocols::device_path::Protocol,
) -> Result<(Vec<u8>, bool, *mut c_void, u32), EfiError> {
if file_path.is_null() {
Err(EfiError::InvalidParameter)?;
}
if let Ok((buffer, device_handle)) = get_file_buffer_from_fw(file_path) {
return Ok((buffer, true, device_handle, 0));
}
if let Ok((buffer, device_handle)) = get_file_buffer_from_sfs(file_path) {
return Ok((buffer, false, device_handle, 0));
}
if !boot_policy
&& let Ok((buffer, device_handle)) =
get_file_buffer_from_load_protocol(efi::protocols::load_file2::PROTOCOL_GUID, false, file_path)
{
return Ok((buffer, false, device_handle, 0));
}
if let Ok((buffer, device_handle)) =
get_file_buffer_from_load_protocol(efi::protocols::load_file::PROTOCOL_GUID, boot_policy, file_path)
{
return Ok((buffer, false, device_handle, 0));
}
Err(EfiError::NotFound)
}
}
unsafe impl Sync for ImageData {}
unsafe impl Send for ImageData {}
impl<P: super::PlatformInfo> super::PiDispatcher<P> {
pub fn load_image(
&self,
boot_policy: bool,
parent_image_handle: efi::Handle,
file_path: *mut efi::protocols::device_path::Protocol,
image: Option<&[u8]>,
) -> Result<efi::Handle, ImageStatus> {
perf_load_image_begin(core::ptr::null_mut(), create_performance_measurement);
if image.is_none() && file_path.is_null() {
log::error!("failed to load image: image is none or device path is null.");
return Err(EfiError::InvalidParameter.into());
}
ImageData::validate_parent(parent_image_handle)?;
let (image_to_load, from_fv, device_handle, auth_status) = match image {
Some(buffer) => ImageData::locate_image_metadata_by_buffer(buffer, file_path),
None => ImageData::locate_image_metadata_by_file_path(boot_policy, file_path)?,
};
let security_status = authenticate_image(file_path, &image_to_load, boot_policy, from_fv, auth_status);
if let Err(err) = security_status
&& err != EfiError::SecurityViolation
{
if err == EfiError::AccessDenied {
return Err(ImageStatus::AccessDenied);
}
return Err(err.into());
}
let mut image_info = empty_image_info();
image_info.system_table = self.image_data.lock().system_table;
image_info.parent_handle = parent_image_handle;
image_info.device_handle = device_handle;
let mut private_info = core_load_pe_image(&image_to_load, image_info)?;
if let Some(fp) = NonNull::new(file_path) {
private_info.set_file_path(fp)?;
}
let handle = private_info.install().map_err(|_| EfiError::LoadError)?;
let mut private_image_data = self.image_data.lock();
private_image_data.private_image_data.insert(handle, private_info);
let private_info = private_image_data
.private_image_data
.get(&handle)
.expect("Image just inserted must exist in private image data map");
log::info!(
"Loaded image at {:#x?} Size={:#x?} EntryPoint={:#x?} {:}",
private_info.image_info.image_base,
private_info.image_info.image_size,
private_info.entry_point as usize,
private_info.pe_info.filename_or("<no PDB>"),
);
self.debug_image_data.write().add_entry(
ImageInfoType::Normal,
NonNull::from(private_info.image_info.as_ref()),
handle,
);
patina_debugger::notify_module_load(
private_info.pe_info.filename_or(""),
private_info.image_info.image_base as usize,
private_info.image_info.image_size as usize,
);
perf_load_image_end(handle, create_performance_measurement);
match security_status {
Err(EfiError::SecurityViolation) => Err(ImageStatus::SecurityViolation(handle)),
Err(_) => unreachable!(), _ => Ok(handle),
}
}
#[coverage(off)]
pub(super) extern "efiapi" fn load_image_efiapi(
boot_policy: efi::Boolean,
parent_image_handle: efi::Handle,
device_path: *mut efi::protocols::device_path::Protocol,
source_buffer: *mut c_void,
source_size: usize,
image_handle: *mut efi::Handle,
) -> efi::Status {
if image_handle.is_null() {
return efi::Status::INVALID_PARAMETER;
}
let image = if source_buffer.is_null() {
None
} else {
if source_size == 0 {
return efi::Status::LOAD_ERROR;
}
Some(unsafe { from_raw_parts(source_buffer as *const u8, source_size) })
};
let (handle, status) =
match Self::instance().load_image(boot_policy.into(), parent_image_handle, device_path, image) {
Ok(handle) => (handle, efi::Status::SUCCESS),
Err(ImageStatus::AccessDenied) => (null_mut(), efi::Status::ACCESS_DENIED),
Err(ImageStatus::SecurityViolation(handle)) => (handle, efi::Status::SECURITY_VIOLATION),
Err(ImageStatus::LoadError(err)) => return err.into(),
};
unsafe { image_handle.write_unaligned(handle) };
status
}
pub fn start_image(&'static self, image_handle: efi::Handle) -> Result<(), efi::Status> {
PROTOCOL_DB.validate_handle(image_handle)?;
if let Some(private_data) = self.image_data.lock().private_image_data.get_mut(&image_handle) {
if private_data.started {
Err(EfiError::InvalidParameter)?;
}
} else {
Err(EfiError::InvalidParameter)?;
}
let stack = ImageStack::new(ENTRY_POINT_STACK_SIZE)?;
perf_image_start_begin(image_handle, create_performance_measurement);
let mut coroutine = Coroutine::with_stack(stack, move |yielder, image_handle| {
let mut private_data = self.image_data.lock();
let status;
if let Some(private_info) = private_data.private_image_data.get_mut(&image_handle) {
private_info.started = true;
let entry_point = private_info.entry_point;
private_data.image_start_contexts.push(yielder as *const Yielder<_, _>);
let system_table = private_data.system_table;
drop(private_data);
status = entry_point(image_handle, system_table);
self.exit(image_handle, status, 0, core::ptr::null_mut());
} else {
status = efi::Status::NOT_FOUND;
}
status
});
let mut private_data = self.image_data.lock();
let previous_image = private_data.current_running_image;
private_data.current_running_image = Some(image_handle);
drop(private_data);
let status = match coroutine.resume(image_handle) {
CoroutineResult::Yield(status) => status,
CoroutineResult::Return(status) => status,
};
log::info!("start_image entrypoint exit with status: {status:x?}");
unsafe { coroutine.force_reset() };
self.image_data.lock().current_running_image = previous_image;
perf_image_start_end(image_handle, create_performance_measurement);
match status {
efi::Status::SUCCESS => Ok(()),
err => Err(err),
}
}
#[coverage(off)]
pub(super) extern "efiapi" fn start_image_efiapi(
image_handle: efi::Handle,
exit_data_size: *mut usize,
exit_data: *mut *mut efi::Char16,
) -> efi::Status {
let status = Self::instance().start_image(image_handle);
if !exit_data_size.is_null() && !exit_data.is_null() {
let private_data = Self::instance().image_data.lock();
if let Some(image_data) = private_data.private_image_data.get(&image_handle)
&& let Some(image_exit_data) = &image_data.exit_data
&& !exit_data_size.is_null()
&& !exit_data.is_null()
{
unsafe {
exit_data_size.write_unaligned(image_exit_data.0);
exit_data.write_unaligned(image_exit_data.1);
}
}
}
let image_type =
Self::instance().image_data.lock().private_image_data.get(&image_handle).map(|x| x.pe_info.image_type);
if status.is_err() || image_type == Some(EFI_IMAGE_SUBSYSTEM_EFI_APPLICATION) {
let _result = Self::instance().unload_image(image_handle, true);
}
match status {
Ok(()) => efi::Status::SUCCESS,
Err(err) => err,
}
}
pub fn unload_image(&self, image_handle: efi::Handle, force_unload: bool) -> Result<(), efi::Status> {
PROTOCOL_DB.validate_handle(image_handle)?;
let private_data = self.image_data.lock();
let private_image_data =
private_data.private_image_data.get(&image_handle).ok_or(efi::Status::INVALID_PARAMETER)?;
let unload_function = private_image_data.image_info.unload;
let started = private_image_data.started;
drop(private_data);
if started {
if let Some(function) = unload_function {
#[allow(unused_unsafe)]
unsafe {
let status = (function)(image_handle);
if status != efi::Status::SUCCESS {
Err(status)?;
}
}
} else if !force_unload {
Err(EfiError::Unsupported)?;
}
}
let handles = PROTOCOL_DB.locate_handles(None).unwrap_or_default();
if let Some(mut table) = self.debug_image_data.try_write() {
table.remove_entry(image_handle);
} else {
debug_assert!(
false,
"Failed to remove debug image info table entry during unload_image, re-entrant lock detected."
);
}
for handle in handles {
let protocols = match PROTOCOL_DB.get_protocols_on_handle(handle) {
Err(_) => continue,
Ok(protocols) => protocols,
};
for protocol in protocols {
let open_infos = match PROTOCOL_DB.get_open_protocol_information_by_protocol(handle, protocol) {
Err(_) => continue,
Ok(open_infos) => open_infos,
};
for open_info in open_infos {
if Some(image_handle) == open_info.agent_handle {
let _result = PROTOCOL_DB.remove_protocol_usage(
handle,
protocol,
open_info.agent_handle,
open_info.controller_handle,
Some(open_info.attributes),
);
}
}
}
}
let private_image_data = self.image_data.lock().private_image_data.remove(&image_handle).unwrap();
if private_image_data.uninstall(image_handle).is_err() {
self.image_data.lock().private_image_data.insert(image_handle, private_image_data);
}
Ok(())
}
#[coverage(off)]
pub(super) extern "efiapi" fn unload_image_efiapi(image_handle: efi::Handle) -> efi::Status {
match Self::instance().unload_image(image_handle, false) {
Ok(()) => efi::Status::SUCCESS,
Err(err) => err,
}
}
fn exit(
&self,
image_handle: efi::Handle,
status: efi::Status,
exit_data_size: usize,
exit_data: *mut efi::Char16,
) -> efi::Status {
let started = match self.image_data.lock().private_image_data.get(&image_handle) {
Some(image_data) => image_data.started,
None => return efi::Status::INVALID_PARAMETER,
};
if !started {
return match self.unload_image(image_handle, true) {
Ok(()) => efi::Status::SUCCESS,
Err(_err) => efi::Status::INVALID_PARAMETER,
};
}
let mut private_data = self.image_data.lock();
if Some(image_handle) != private_data.current_running_image {
return efi::Status::INVALID_PARAMETER;
}
if exit_data_size != 0
&& !exit_data.is_null()
&& let Some(image_data) = private_data.private_image_data.get_mut(&image_handle)
{
image_data.exit_data = Some(ExitData(exit_data_size, exit_data));
}
if let Some(yielder) = private_data.image_start_contexts.pop() {
let yielder = unsafe { &*yielder };
drop(private_data);
yielder.suspend(status);
}
efi::Status::ACCESS_DENIED
}
#[coverage(off)]
pub(super) extern "efiapi" fn exit_efiapi(
image_handle: efi::Handle,
status: efi::Status,
exit_data_size: usize,
exit_data: *mut efi::Char16,
) -> efi::Status {
Self::instance().exit(image_handle, status, exit_data_size, exit_data)
}
pub(super) extern "efiapi" fn runtime_image_protection_fixup_ebs(event: efi::Event, _context: *mut c_void) {
let mut private_data = Self::instance().image_data.lock();
for image in private_data.private_image_data.values_mut() {
let buffer = image.image_buffer.as_ref();
if image.pe_info.image_type == EFI_IMAGE_SUBSYSTEM_EFI_RUNTIME_DRIVER {
let cache_attrs =
dxe_services::core_get_memory_space_descriptor(buffer.as_ptr() as efi::PhysicalAddress)
.map(|desc| desc.attributes & efi::CACHE_ATTRIBUTE_MASK)
.unwrap_or(DEFAULT_CACHE_ATTR);
match core_set_memory_space_attributes(
buffer.as_ptr() as efi::PhysicalAddress,
buffer.len() as u64,
cache_attrs,
) {
Ok(_) => {
}
Err(status) => {
log::error!(
"Failed to set GCD attributes for runtime image {:#X?} with Status {:#X?}, may fail to relocate",
buffer.as_ptr() as efi::PhysicalAddress,
status
);
debug_assert!(false);
}
};
}
}
if let Err(status) = EVENT_DB.close_event(event) {
log::error!("Failed to close image EBS event with status {status:#X?}. This should be okay.");
}
}
}
fn empty_image_info() -> efi::protocols::loaded_image::Protocol {
efi::protocols::loaded_image::Protocol {
revision: efi::protocols::loaded_image::REVISION,
parent_handle: core::ptr::null_mut(),
system_table: core::ptr::null_mut(),
device_handle: core::ptr::null_mut(),
file_path: core::ptr::null_mut(),
reserved: core::ptr::null_mut(),
load_options_size: 0,
load_options: core::ptr::null_mut(),
image_base: core::ptr::null_mut(),
image_size: 0,
image_code_type: efi::BOOT_SERVICES_CODE,
image_data_type: efi::BOOT_SERVICES_DATA,
unload: None,
}
}
fn core_load_pe_image(
image: &[u8],
mut image_info: efi::protocols::loaded_image::Protocol,
) -> Result<PrivateImageData, EfiError> {
let pe_info = pecoff::UefiPeInfo::parse(image).map_err(|err| {
log::error!("core_load_pe_image failed: UefiPeInfo::parse returned {err:?}");
EfiError::Unsupported
})?;
let pe_file_name = pe_info.filename_or("Unknown");
let (code_type, data_type) = match pe_info.image_type {
EFI_IMAGE_SUBSYSTEM_EFI_APPLICATION => (efi::LOADER_CODE, efi::LOADER_DATA),
EFI_IMAGE_SUBSYSTEM_EFI_BOOT_SERVICE_DRIVER => (efi::BOOT_SERVICES_CODE, efi::BOOT_SERVICES_DATA),
EFI_IMAGE_SUBSYSTEM_EFI_RUNTIME_DRIVER => (efi::RUNTIME_SERVICES_CODE, efi::RUNTIME_SERVICES_DATA),
unsupported_type => {
log::error!("core_load_pe_image failed: {pe_file_name} unsupported image type: {unsupported_type:#x?}");
return Err(EfiError::Unsupported);
}
};
let alignment = pe_info.section_alignment as usize; let size = pe_info.size_of_image as usize;
if !alignment.is_multiple_of(UEFI_PAGE_SIZE) {
log::error!(
"core_load_pe_image failed: {pe_file_name} section alignment of {alignment:#x?} is not a multiple of page size {UEFI_PAGE_SIZE:#x?}"
);
return Err(EfiError::LoadError);
}
if !size.is_multiple_of(alignment) {
log::error!(
"core_load_pe_image failed: {pe_file_name} size of image is not a multiple of the section alignment"
);
return Err(EfiError::LoadError);
}
image_info.image_size = size as u64;
image_info.image_code_type = code_type;
image_info.image_data_type = data_type;
let mut private_info = PrivateImageData::new(image_info, pe_info)?;
private_info.load_image(image)?;
private_info.relocate_image()?;
private_info.load_resource_section(image)?;
if private_info.pe_info.image_type == EFI_IMAGE_SUBSYSTEM_EFI_RUNTIME_DRIVER && !private_info.pe_info.nx_compat {
private_info.activate_compatibility_mode()?;
} else {
private_info.apply_image_memory_protections()?;
}
Ok(private_info)
}
fn get_file_guid_from_device_path(path: *mut efi::protocols::device_path::Protocol) -> Result<Guid, EfiError> {
let mut walker = unsafe { DevicePathWalker::new(path) };
let file_path_node = walker.next().ok_or(EfiError::InvalidParameter)?;
if file_path_node.header().r#type != efi::protocols::device_path::TYPE_MEDIA
|| file_path_node.header().sub_type != efi::protocols::device_path::Media::SUBTYPE_PIWG_FIRMWARE_FILE
{
return Err(EfiError::InvalidParameter);
}
Ok(Guid::from_bytes(file_path_node.data().try_into().map_err(|_| EfiError::BadBufferSize)?))
}
fn get_file_buffer_from_fw(
file_path: *mut efi::protocols::device_path::Protocol,
) -> Result<(Vec<u8>, efi::Handle), EfiError> {
let (remaining_file_path, handle) =
core_locate_device_path(pi::protocols::firmware_volume::PROTOCOL_GUID, file_path)?;
let fv_name_guid = get_file_guid_from_device_path(remaining_file_path)?;
let fv_ptr = PROTOCOL_DB.get_interface_for_handle(handle, pi::protocols::firmware_volume::PROTOCOL_GUID)?
as *mut pi::protocols::firmware_volume::Protocol;
if fv_ptr.is_null() {
debug_assert!(!fv_ptr.is_null(), "ERROR: get_interface_for_handle returned NULL ptr for FirmwareVolume!");
return Err(EfiError::InvalidParameter);
}
let fw_vol = unsafe { fv_ptr.as_ref().unwrap() };
let mut buffer: *mut u8 = core::ptr::null_mut();
let buffer_ptr: *mut *mut c_void = &mut buffer as *mut _ as *mut *mut c_void;
let mut buffer_size = 0;
let mut authentication_status = 0;
let authentication_status_ptr = &mut authentication_status;
let status = (fw_vol.read_section)(
fw_vol,
&fv_name_guid,
PE32,
0, buffer_ptr,
core::ptr::addr_of_mut!(buffer_size),
authentication_status_ptr,
);
EfiError::status_to_result(status)?;
let section_slice = unsafe { slice::from_raw_parts(buffer, buffer_size) };
Ok((section_slice.to_vec(), handle))
}
fn get_file_buffer_from_sfs(
file_path: *mut efi::protocols::device_path::Protocol,
) -> Result<(Vec<u8>, efi::Handle), EfiError> {
let (remaining_file_path, handle) =
core_locate_device_path(efi::protocols::simple_file_system::PROTOCOL_GUID, file_path)?;
let mut file = SimpleFile::open_volume(handle)?;
for node in unsafe { DevicePathWalker::new(remaining_file_path) } {
match node.header().r#type {
efi::protocols::device_path::TYPE_MEDIA
if node.header().sub_type == efi::protocols::device_path::Media::SUBTYPE_FILE_PATH => {} efi::protocols::device_path::TYPE_END => break,
_ => Err(EfiError::Unsupported)?,
}
let filename: Vec<u16> = node
.data()
.chunks_exact(2)
.map(|x: &[u8]| {
if let Ok(x_bytes) = x.try_into() {
Ok(u16::from_le_bytes(x_bytes))
} else {
Err(EfiError::InvalidParameter)
}
})
.collect::<Result<Vec<_>, _>>()?;
file = file.open(filename, efi::protocols::file::MODE_READ, 0)?;
}
Ok((file.read()?, handle))
}
fn get_file_buffer_from_load_protocol(
protocol: efi::Guid,
boot_policy: bool,
file_path: *mut efi::protocols::device_path::Protocol,
) -> Result<(Vec<u8>, efi::Handle), EfiError> {
if !(protocol == efi::protocols::load_file::PROTOCOL_GUID || protocol == efi::protocols::load_file2::PROTOCOL_GUID)
{
Err(EfiError::InvalidParameter)?;
}
if protocol == efi::protocols::load_file2::PROTOCOL_GUID && boot_policy {
Err(EfiError::InvalidParameter)?;
}
let (remaining_file_path, handle) = core_locate_device_path(protocol, file_path)?;
let load_file = PROTOCOL_DB.get_interface_for_handle(handle, protocol)?;
let load_file =
unsafe { (load_file as *mut efi::protocols::load_file::Protocol).as_mut().ok_or(EfiError::Unsupported)? };
let mut buffer_size = 0;
let status = (load_file.load_file)(
load_file,
remaining_file_path,
boot_policy.into(),
core::ptr::addr_of_mut!(buffer_size),
core::ptr::null_mut(),
);
match status {
efi::Status::BUFFER_TOO_SMALL => (), efi::Status::SUCCESS => Err(EfiError::DeviceError)?, _ => EfiError::status_to_result(status)?, }
let mut file_buffer = vec![0u8; buffer_size];
let status = (load_file.load_file)(
load_file,
remaining_file_path,
boot_policy.into(),
core::ptr::addr_of_mut!(buffer_size),
file_buffer.as_mut_ptr() as *mut c_void,
);
EfiError::status_to_result(status).map(|_| (file_buffer, handle))
}
fn authenticate_image(
device_path: *mut efi::protocols::device_path::Protocol,
image: &[u8],
boot_policy: bool,
from_fv: bool,
authentication_status: u32,
) -> Result<(), EfiError> {
let security2_protocol = unsafe {
match PROTOCOL_DB.locate_protocol(pi::protocols::security2::PROTOCOL_GUID) {
Ok(protocol) => (protocol as *mut pi::protocols::security2::Protocol).as_ref(),
Err(_) => None,
}
};
let security_protocol = unsafe {
match PROTOCOL_DB.locate_protocol(pi::protocols::security::PROTOCOL_GUID) {
Ok(protocol) => (protocol as *mut pi::protocols::security::Protocol).as_ref(),
Err(_) => None,
}
};
let mut security_status = efi::Status::SUCCESS;
if let Some(security2) = security2_protocol {
security_status = (security2.file_authentication)(
security2 as *const _ as *mut pi::protocols::security2::Protocol,
device_path,
image.as_ptr() as *const _ as *mut c_void,
image.len(),
boot_policy,
);
if security_status == efi::Status::SUCCESS && from_fv {
let security = security_protocol.expect("Security Arch must be installed if Security2 Arch is installed");
security_status = (security.file_authentication_state)(
security as *const _ as *mut pi::protocols::security::Protocol,
authentication_status,
device_path,
);
}
} else if let Some(security) = security_protocol {
security_status = (security.file_authentication_state)(
security as *const _ as *mut pi::protocols::security::Protocol,
authentication_status,
device_path,
);
}
EfiError::status_to_result(security_status)
}
#[derive(Debug)]
pub enum ImageStatus {
LoadError(EfiError),
SecurityViolation(efi::Handle),
AccessDenied,
}
impl From<EfiError> for ImageStatus {
fn from(err: EfiError) -> Self {
ImageStatus::LoadError(err)
}
}
enum Buffer {
Owned(Box<[u8], PageFree>),
Borrowed(&'static [u8]),
}
impl Buffer {
fn as_ref(&self) -> &[u8] {
match self {
Buffer::Owned(boxed) => boxed.as_ref(),
Buffer::Borrowed(slice) => slice,
}
}
fn as_mut(&mut self) -> Option<&mut [u8]> {
match self {
Buffer::Owned(boxed) => Some(boxed.as_mut()),
Buffer::Borrowed(_) => None,
}
}
}
#[cfg(test)]
#[coverage(off)]
mod tests {
extern crate std;
use super::*;
use crate::{
Core, MockPlatformInfo,
pecoff::UefiPeInfo,
pi_dispatcher::PiDispatcher,
protocol_db::{self, DXE_CORE_HANDLE},
protocols::{PROTOCOL_DB, core_install_protocol_interface},
systemtables::{SYSTEM_TABLE, init_system_table},
test_collateral, test_support,
};
use core::{ffi::c_void, sync::atomic::AtomicBool};
use patina::{
error::EfiError,
guids,
pi::{
self,
hob::{HobList, MemoryAllocationModule, header::MemoryAllocation},
},
};
use patina_internal_device_path::device_path_node_count;
use r_efi::{
efi,
protocols::device_path::{End, Hardware, Media, TYPE_END, TYPE_HARDWARE, TYPE_MEDIA},
};
use std::{fs::File, io::Read, ptr::NonNull, slice::from_raw_parts};
fn with_locked_state<F: Fn() + std::panic::RefUnwindSafe>(f: F) {
test_support::with_global_lock(|| unsafe {
test_support::init_test_gcd(None);
test_support::init_test_protocol_db();
init_system_table();
f();
})
.unwrap();
}
#[test]
fn test_simple_init() {
with_locked_state(|| {
static IMAGE_DATA: ImageData = ImageData::new();
assert!(IMAGE_DATA.private_image_data.is_empty());
static IMAGE_DATA2: tpl_mutex::TplMutex<ImageData> = ImageData::new_locked();
assert!(IMAGE_DATA2.lock().private_image_data.is_empty());
});
}
#[test]
fn load_image_invalid_parameter() {
with_locked_state(|| {
static PI_DISPATCHER: PiDispatcher<MockPlatformInfo> =
PiDispatcher::<MockPlatformInfo>::new(patina_ffs_extractors::NullSectionExtractor);
PI_DISPATCHER.init(&create_dxe_core_hob(), SYSTEM_TABLE.lock().as_mut().unwrap());
let result = PI_DISPATCHER.load_image(false, protocol_db::DXE_CORE_HANDLE, core::ptr::null_mut(), None);
assert!(matches!(result, Err(ImageStatus::LoadError(EfiError::InvalidParameter))));
});
}
#[test]
fn load_image_should_load_the_image() {
with_locked_state(|| {
let mut test_file =
File::open(test_collateral!("test_image_msvc_hii.pe32")).expect("failed to open test file.");
let mut image: Vec<u8> = Vec::new();
test_file.read_to_end(&mut image).expect("failed to read test file");
static PI_DISPATCHER: PiDispatcher<MockPlatformInfo> =
PiDispatcher::<MockPlatformInfo>::new(patina_ffs_extractors::NullSectionExtractor);
PI_DISPATCHER.init(&create_dxe_core_hob(), SYSTEM_TABLE.lock().as_mut().unwrap());
let image_handle = PI_DISPATCHER
.load_image(false, protocol_db::DXE_CORE_HANDLE, core::ptr::null_mut(), Some(image.as_slice()))
.unwrap();
let private_data = PI_DISPATCHER.image_data.lock();
let image_data = private_data.private_image_data.get(&image_handle).unwrap();
let image_buf_len = image_data.image_buffer.as_ref().len() as usize;
assert_eq!(image_buf_len, image_data.image_info.image_size as usize);
assert_eq!(image_data.image_info.image_data_type, efi::BOOT_SERVICES_DATA);
assert_eq!(image_data.image_info.image_code_type, efi::BOOT_SERVICES_CODE);
assert_ne!(image_data.entry_point as usize, 0);
assert!(!image_data.relocation_data.is_empty());
assert!(image_data.hii_resource_section.is_some());
});
}
#[test]
fn load_image_should_pass_for_subsystem_efi_application() {
with_locked_state(|| {
let mut test_file =
File::open(test_collateral!("subsystem_efi_application.efi")).expect("failed to open test file.");
let mut image: Vec<u8> = Vec::new();
test_file.read_to_end(&mut image).expect("failed to read test file");
static PI_DISPATCHER: PiDispatcher<MockPlatformInfo> =
PiDispatcher::<MockPlatformInfo>::new(patina_ffs_extractors::NullSectionExtractor);
PI_DISPATCHER.init(&create_dxe_core_hob(), SYSTEM_TABLE.lock().as_mut().unwrap());
let status = PI_DISPATCHER.load_image(
false,
protocol_db::DXE_CORE_HANDLE,
core::ptr::null_mut(),
Some(image.as_slice()),
);
assert!(status.is_ok());
});
}
#[test]
fn load_image_should_pass_for_subsystem_efi_runtime_driver() {
with_locked_state(|| {
let mut test_file =
File::open(test_collateral!("subsystem_efi_runtime_driver.efi")).expect("failed to open test file.");
let mut image: Vec<u8> = Vec::new();
test_file.read_to_end(&mut image).expect("failed to read test file");
static PI_DISPATCHER: PiDispatcher<MockPlatformInfo> =
PiDispatcher::<MockPlatformInfo>::new(patina_ffs_extractors::NullSectionExtractor);
PI_DISPATCHER.init(&create_dxe_core_hob(), SYSTEM_TABLE.lock().as_mut().unwrap());
assert!(
PI_DISPATCHER
.load_image(false, protocol_db::DXE_CORE_HANDLE, core::ptr::null_mut(), Some(image.as_slice()),)
.is_ok()
);
});
}
#[test]
fn load_image_should_fail_for_windows_image() {
with_locked_state(|| {
let mut test_file =
File::open(test_collateral!("windows_console_app.exe")).expect("failed to open test file.");
let mut image: Vec<u8> = Vec::new();
test_file.read_to_end(&mut image).expect("failed to read test file");
static PI_DISPATCHER: PiDispatcher<MockPlatformInfo> =
PiDispatcher::<MockPlatformInfo>::new(patina_ffs_extractors::NullSectionExtractor);
PI_DISPATCHER.init(&create_dxe_core_hob(), SYSTEM_TABLE.lock().as_mut().unwrap());
let result = PI_DISPATCHER.load_image(
false,
protocol_db::DXE_CORE_HANDLE,
core::ptr::null_mut(),
Some(image.as_slice()),
);
assert!(matches!(result, Err(ImageStatus::LoadError(EfiError::Unsupported))));
});
}
#[test]
fn load_image_should_fail_for_section_alignment_not_multiple_of_uefi_page_size() {
with_locked_state(|| {
let mut test_file =
File::open(test_collateral!("section_alignment_200.efi")).expect("failed to open test file.");
let mut image: Vec<u8> = Vec::new();
test_file.read_to_end(&mut image).expect("failed to read test file");
static PI_DISPATCHER: PiDispatcher<MockPlatformInfo> =
PiDispatcher::<MockPlatformInfo>::new(patina_ffs_extractors::NullSectionExtractor);
PI_DISPATCHER.init(&create_dxe_core_hob(), SYSTEM_TABLE.lock().as_mut().unwrap());
let status = PI_DISPATCHER.load_image(
false,
protocol_db::DXE_CORE_HANDLE,
core::ptr::null_mut(),
Some(image.as_slice()),
);
assert!(matches!(status, Err(ImageStatus::LoadError(EfiError::LoadError))));
});
}
#[test]
fn load_image_should_fail_for_incorrect_size_of_image() {
with_locked_state(|| {
let mut test_file =
File::open(test_collateral!("invalid_size_of_image.efi")).expect("failed to open test file.");
let mut image: Vec<u8> = Vec::new();
test_file.read_to_end(&mut image).expect("failed to read test file");
static PI_DISPATCHER: PiDispatcher<MockPlatformInfo> =
PiDispatcher::<MockPlatformInfo>::new(patina_ffs_extractors::NullSectionExtractor);
PI_DISPATCHER.init(&create_dxe_core_hob(), SYSTEM_TABLE.lock().as_mut().unwrap());
let status = PI_DISPATCHER.load_image(
false,
protocol_db::DXE_CORE_HANDLE,
core::ptr::null_mut(),
Some(image.as_slice()),
);
assert!(matches!(status, Err(ImageStatus::LoadError(EfiError::LoadError))));
});
}
#[test]
fn load_image_should_fail_for_hii_section_has_invalid_directory_name_offset() {
with_locked_state(|| {
let mut test_file = File::open(test_collateral!("invalid_directory_name_offset_hii.pe32"))
.expect("failed to open test file.");
let mut image: Vec<u8> = Vec::new();
test_file.read_to_end(&mut image).expect("failed to read test file");
static PI_DISPATCHER: PiDispatcher<MockPlatformInfo> =
PiDispatcher::<MockPlatformInfo>::new(patina_ffs_extractors::NullSectionExtractor);
PI_DISPATCHER.init(&create_dxe_core_hob(), SYSTEM_TABLE.lock().as_mut().unwrap());
let status = PI_DISPATCHER.load_image(
false,
protocol_db::DXE_CORE_HANDLE,
core::ptr::null_mut(),
Some(image.as_slice()),
);
assert!(matches!(status, Err(ImageStatus::LoadError(EfiError::LoadError))));
});
}
#[test]
fn load_image_should_fail_for_invalid_relocation_directory_size() {
with_locked_state(|| {
let mut test_file = File::open(test_collateral!("invalid_relocation_directory_size.efi"))
.expect("failed to open test file.");
let mut image: Vec<u8> = Vec::new();
test_file.read_to_end(&mut image).expect("failed to read test file");
static PI_DISPATCHER: PiDispatcher<MockPlatformInfo> =
PiDispatcher::<MockPlatformInfo>::new(patina_ffs_extractors::NullSectionExtractor);
PI_DISPATCHER.init(&create_dxe_core_hob(), SYSTEM_TABLE.lock().as_mut().unwrap());
let status =
PI_DISPATCHER.load_image(false, protocol_db::DXE_CORE_HANDLE, core::ptr::null_mut(), Some(&image));
assert!(matches!(status, Err(ImageStatus::LoadError(EfiError::LoadError))));
});
}
#[test]
fn load_image_should_authenticate_the_image_with_security_arch() {
with_locked_state(|| {
let mut test_file =
File::open(test_collateral!("test_image_msvc_hii.pe32")).expect("failed to open test file.");
let mut image: Vec<u8> = Vec::new();
test_file.read_to_end(&mut image).expect("failed to read test file");
static PI_DISPATCHER: PiDispatcher<MockPlatformInfo> =
PiDispatcher::<MockPlatformInfo>::new(patina_ffs_extractors::NullSectionExtractor);
PI_DISPATCHER.init(&create_dxe_core_hob(), SYSTEM_TABLE.lock().as_mut().unwrap());
static SECURITY_CALL_EXECUTED: AtomicBool = AtomicBool::new(false);
extern "efiapi" fn mock_file_authentication_state(
this: *mut pi::protocols::security::Protocol,
authentication_status: u32,
file: *mut efi::protocols::device_path::Protocol,
) -> efi::Status {
assert!(!this.is_null());
assert_eq!(authentication_status, 0);
assert!(file.is_null()); SECURITY_CALL_EXECUTED.store(true, core::sync::atomic::Ordering::SeqCst);
efi::Status::SUCCESS
}
let security_protocol =
pi::protocols::security::Protocol { file_authentication_state: mock_file_authentication_state };
PROTOCOL_DB
.install_protocol_interface(
None,
pi::protocols::security::PROTOCOL_GUID,
&security_protocol as *const _ as *mut _,
)
.unwrap();
let image_handle = PI_DISPATCHER
.load_image(false, protocol_db::DXE_CORE_HANDLE, core::ptr::null_mut(), Some(&image))
.unwrap();
assert!(SECURITY_CALL_EXECUTED.load(core::sync::atomic::Ordering::SeqCst));
let private_data = PI_DISPATCHER.image_data.lock();
let image_data = private_data.private_image_data.get(&image_handle).unwrap();
let image_buf_len = image_data.image_buffer.as_ref().len();
assert_eq!(image_buf_len, image_data.image_info.image_size as usize);
assert_eq!(image_data.image_info.image_data_type, efi::BOOT_SERVICES_DATA);
assert_eq!(image_data.image_info.image_code_type, efi::BOOT_SERVICES_CODE);
assert_ne!(image_data.entry_point as usize, 0);
assert!(!image_data.relocation_data.is_empty());
assert!(image_data.hii_resource_section.is_some());
});
}
#[test]
fn load_image_should_authenticate_the_image_with_security2_arch() {
with_locked_state(|| {
let mut test_file =
File::open(test_collateral!("test_image_msvc_hii.pe32")).expect("failed to open test file.");
let mut image: Vec<u8> = Vec::new();
test_file.read_to_end(&mut image).expect("failed to read test file");
static PI_DISPATCHER: PiDispatcher<MockPlatformInfo> =
PiDispatcher::<MockPlatformInfo>::new(patina_ffs_extractors::NullSectionExtractor);
PI_DISPATCHER.init(&create_dxe_core_hob(), SYSTEM_TABLE.lock().as_mut().unwrap());
extern "efiapi" fn mock_file_authentication_state(
_this: *mut pi::protocols::security::Protocol,
_authentication_status: u32,
_file: *mut efi::protocols::device_path::Protocol,
) -> efi::Status {
unreachable!()
}
let security_protocol =
pi::protocols::security::Protocol { file_authentication_state: mock_file_authentication_state };
PROTOCOL_DB
.install_protocol_interface(
None,
pi::protocols::security::PROTOCOL_GUID,
&security_protocol as *const _ as *mut _,
)
.unwrap();
static SECURITY2_CALL_EXECUTED: AtomicBool = AtomicBool::new(false);
extern "efiapi" fn mock_file_authentication(
this: *mut pi::protocols::security2::Protocol,
file: *mut efi::protocols::device_path::Protocol,
file_buffer: *mut c_void,
file_size: usize,
boot_policy: bool,
) -> efi::Status {
assert!(!this.is_null());
assert!(file.is_null()); assert!(!file_buffer.is_null());
assert!(file_size > 0);
assert!(!boot_policy);
SECURITY2_CALL_EXECUTED.store(true, core::sync::atomic::Ordering::SeqCst);
efi::Status::SUCCESS
}
let security2_protocol =
pi::protocols::security2::Protocol { file_authentication: mock_file_authentication };
PROTOCOL_DB
.install_protocol_interface(
None,
pi::protocols::security2::PROTOCOL_GUID,
&security2_protocol as *const _ as *mut _,
)
.unwrap();
let image_handle = PI_DISPATCHER
.load_image(false, protocol_db::DXE_CORE_HANDLE, core::ptr::null_mut(), Some(&image))
.unwrap();
assert!(SECURITY2_CALL_EXECUTED.load(core::sync::atomic::Ordering::SeqCst));
let private_data = PI_DISPATCHER.image_data.lock();
let image_data = private_data.private_image_data.get(&image_handle).unwrap();
let image_buf_len = image_data.image_buffer.as_ref().len();
assert_eq!(image_buf_len, image_data.image_info.image_size as usize);
assert_eq!(image_data.image_info.image_data_type, efi::BOOT_SERVICES_DATA);
assert_eq!(image_data.image_info.image_code_type, efi::BOOT_SERVICES_CODE);
assert_ne!(image_data.entry_point as usize, 0);
assert!(!image_data.relocation_data.is_empty());
assert!(image_data.hii_resource_section.is_some());
});
}
#[test]
fn load_image_with_auth_err_security_violation_should_continue_to_load_image() {
with_locked_state(|| {
let mut test_file =
File::open(test_collateral!("test_image_msvc_hii.pe32")).expect("failed to open test file.");
let mut image: Vec<u8> = Vec::new();
test_file.read_to_end(&mut image).expect("failed to read test file");
static PI_DISPATCHER: PiDispatcher<MockPlatformInfo> =
PiDispatcher::<MockPlatformInfo>::new(patina_ffs_extractors::NullSectionExtractor);
PI_DISPATCHER.init(&create_dxe_core_hob(), SYSTEM_TABLE.lock().as_mut().unwrap());
extern "efiapi" fn mock_file_authentication(
_this: *mut pi::protocols::security2::Protocol,
_file: *mut efi::protocols::device_path::Protocol,
_file_buffer: *mut c_void,
_file_size: usize,
_boot_policy: bool,
) -> efi::Status {
efi::Status::SECURITY_VIOLATION
}
let security2_protocol =
pi::protocols::security2::Protocol { file_authentication: mock_file_authentication };
PROTOCOL_DB
.install_protocol_interface(
None,
pi::protocols::security2::PROTOCOL_GUID,
&security2_protocol as *const _ as *mut _,
)
.unwrap();
assert_eq!(PROTOCOL_DB.locate_handles(Some(efi::protocols::loaded_image::PROTOCOL_GUID)).unwrap().len(), 1);
assert_eq!(PI_DISPATCHER.image_data.lock().private_image_data.len(), 1);
let status =
PI_DISPATCHER.load_image(false, protocol_db::DXE_CORE_HANDLE, core::ptr::null_mut(), Some(&image));
let image_handle = match status {
Err(ImageStatus::SecurityViolation(h)) => h,
_ => panic!("Expected SecurityViolation error"),
};
assert!(!image_handle.is_null());
assert_eq!(PROTOCOL_DB.locate_handles(Some(efi::protocols::loaded_image::PROTOCOL_GUID)).unwrap().len(), 2);
assert_eq!(PI_DISPATCHER.image_data.lock().private_image_data.len(), 2);
});
}
#[test]
fn load_image_with_auth_err_access_denied_should_exit_early_and_not_load_image() {
with_locked_state(|| {
let mut test_file =
File::open(test_collateral!("test_image_msvc_hii.pe32")).expect("failed to open test file.");
let mut image: Vec<u8> = Vec::new();
test_file.read_to_end(&mut image).expect("failed to read test file");
static PI_DISPATCHER: PiDispatcher<MockPlatformInfo> =
PiDispatcher::<MockPlatformInfo>::new(patina_ffs_extractors::NullSectionExtractor);
PI_DISPATCHER.init(&create_dxe_core_hob(), SYSTEM_TABLE.lock().as_mut().unwrap());
extern "efiapi" fn mock_file_authentication(
_this: *mut pi::protocols::security2::Protocol,
_file: *mut efi::protocols::device_path::Protocol,
_file_buffer: *mut c_void,
_file_size: usize,
_boot_policy: bool,
) -> efi::Status {
efi::Status::ACCESS_DENIED
}
let security2_protocol =
pi::protocols::security2::Protocol { file_authentication: mock_file_authentication };
PROTOCOL_DB
.install_protocol_interface(
None,
pi::protocols::security2::PROTOCOL_GUID,
&security2_protocol as *const _ as *mut _,
)
.unwrap();
assert_eq!(PROTOCOL_DB.locate_handles(Some(efi::protocols::loaded_image::PROTOCOL_GUID)).unwrap().len(), 1);
assert_eq!(PI_DISPATCHER.image_data.lock().private_image_data.len(), 1);
let status =
PI_DISPATCHER.load_image(false, protocol_db::DXE_CORE_HANDLE, core::ptr::null_mut(), Some(&image));
assert!(matches!(status, Err(ImageStatus::AccessDenied)));
assert_eq!(PROTOCOL_DB.locate_handles(Some(efi::protocols::loaded_image::PROTOCOL_GUID)).unwrap().len(), 1);
assert_eq!(PI_DISPATCHER.image_data.lock().private_image_data.len(), 1);
});
}
#[test]
fn load_image_with_auth_err_unexpected_should_exit_early_and_not_load_image() {
with_locked_state(|| {
let mut test_file =
File::open(test_collateral!("test_image_msvc_hii.pe32")).expect("failed to open test file.");
let mut image: Vec<u8> = Vec::new();
test_file.read_to_end(&mut image).expect("failed to read test file");
static PI_DISPATCHER: PiDispatcher<MockPlatformInfo> =
PiDispatcher::<MockPlatformInfo>::new(patina_ffs_extractors::NullSectionExtractor);
PI_DISPATCHER.init(&create_dxe_core_hob(), SYSTEM_TABLE.lock().as_mut().unwrap());
extern "efiapi" fn mock_file_authentication(
_this: *mut pi::protocols::security2::Protocol,
_file: *mut efi::protocols::device_path::Protocol,
_file_buffer: *mut c_void,
_file_size: usize,
_boot_policy: bool,
) -> efi::Status {
efi::Status::INVALID_PARAMETER
}
let security2_protocol =
pi::protocols::security2::Protocol { file_authentication: mock_file_authentication };
PROTOCOL_DB
.install_protocol_interface(
None,
pi::protocols::security2::PROTOCOL_GUID,
&security2_protocol as *const _ as *mut _,
)
.unwrap();
assert_eq!(PROTOCOL_DB.locate_handles(Some(efi::protocols::loaded_image::PROTOCOL_GUID)).unwrap().len(), 1);
assert_eq!(PI_DISPATCHER.image_data.lock().private_image_data.len(), 1);
let status =
PI_DISPATCHER.load_image(false, protocol_db::DXE_CORE_HANDLE, core::ptr::null_mut(), Some(&image));
assert!(matches!(status, Err(ImageStatus::LoadError(EfiError::InvalidParameter))));
assert_eq!(PROTOCOL_DB.locate_handles(Some(efi::protocols::loaded_image::PROTOCOL_GUID)).unwrap().len(), 1);
assert_eq!(PI_DISPATCHER.image_data.lock().private_image_data.len(), 1);
});
}
#[test]
fn start_image_should_start_image() {
with_locked_state(|| {
let mut test_file =
File::open(test_collateral!("RustImageTestDxe.efi")).expect("failed to open test file.");
let mut image: Vec<u8> = Vec::new();
test_file.read_to_end(&mut image).expect("failed to read test file");
static PI_DISPATCHER: PiDispatcher<MockPlatformInfo> =
PiDispatcher::<MockPlatformInfo>::new(patina_ffs_extractors::NullSectionExtractor);
PI_DISPATCHER.init(&create_dxe_core_hob(), SYSTEM_TABLE.lock().as_mut().unwrap());
let image_handle = PI_DISPATCHER
.load_image(false, protocol_db::DXE_CORE_HANDLE, core::ptr::null_mut(), Some(&image))
.unwrap();
static ENTRY_POINT_RAN: AtomicBool = AtomicBool::new(false);
pub extern "efiapi" fn test_entry_point(
_image_handle: *mut core::ffi::c_void,
_system_table: *mut r_efi::system::SystemTable,
) -> efi::Status {
println!("test_entry_point executed.");
ENTRY_POINT_RAN.store(true, core::sync::atomic::Ordering::Relaxed);
efi::Status::SUCCESS
}
let mut private_data = PI_DISPATCHER.image_data.lock();
let image_data = private_data.private_image_data.get_mut(&image_handle).unwrap();
image_data.entry_point = test_entry_point;
drop(private_data);
PI_DISPATCHER.start_image(image_handle).unwrap();
assert!(ENTRY_POINT_RAN.load(core::sync::atomic::Ordering::Relaxed));
let mut private_data = PI_DISPATCHER.image_data.lock();
let image_data = private_data.private_image_data.get_mut(&image_handle).unwrap();
assert!(image_data.started);
drop(private_data);
});
}
#[test]
fn start_image_error_status_should_unload_image() {
with_locked_state(|| {
let mut test_file =
File::open(test_collateral!("RustImageTestDxe.efi")).expect("failed to open test file.");
let mut image: Vec<u8> = Vec::new();
test_file.read_to_end(&mut image).expect("failed to read test file");
static CORE: Core<MockPlatformInfo> =
Core::<MockPlatformInfo>::new(patina_ffs_extractors::NullSectionExtractor);
CORE.pi_dispatcher.init(&create_dxe_core_hob(), SYSTEM_TABLE.lock().as_mut().unwrap());
CORE.override_instance();
let image_handle = CORE
.pi_dispatcher
.load_image(false, protocol_db::DXE_CORE_HANDLE, core::ptr::null_mut(), Some(&image))
.unwrap();
static ENTRY_POINT_RAN: AtomicBool = AtomicBool::new(false);
extern "efiapi" fn test_entry_point(
_image_handle: *mut core::ffi::c_void,
_system_table: *mut r_efi::system::SystemTable,
) -> efi::Status {
log::info!("test_entry_point executed.");
ENTRY_POINT_RAN.store(true, core::sync::atomic::Ordering::Relaxed);
efi::Status::UNSUPPORTED
}
let mut private_data = CORE.pi_dispatcher.image_data.lock();
let image_data = private_data.private_image_data.get_mut(&image_handle).unwrap();
image_data.entry_point = test_entry_point;
drop(private_data);
let mut exit_data_size = 0;
let mut exit_data: *mut u16 = core::ptr::null_mut();
let status = PiDispatcher::<MockPlatformInfo>::start_image_efiapi(
image_handle,
core::ptr::addr_of_mut!(exit_data_size),
core::ptr::addr_of_mut!(exit_data),
);
assert_eq!(status, efi::Status::UNSUPPORTED);
assert!(ENTRY_POINT_RAN.load(core::sync::atomic::Ordering::Relaxed));
let private_data = CORE.pi_dispatcher.image_data.lock();
assert!(!private_data.private_image_data.contains_key(&image_handle));
drop(private_data);
});
}
#[test]
fn unload_non_started_image_should_unload_the_image() {
with_locked_state(|| {
let mut test_file =
File::open(test_collateral!("RustImageTestDxe.efi")).expect("failed to open test file.");
let mut image: Vec<u8> = Vec::new();
test_file.read_to_end(&mut image).expect("failed to read test file");
static PI_DISPATCHER: PiDispatcher<MockPlatformInfo> =
PiDispatcher::<MockPlatformInfo>::new(patina_ffs_extractors::NullSectionExtractor);
PI_DISPATCHER.init(&create_dxe_core_hob(), SYSTEM_TABLE.lock().as_mut().unwrap());
let image_handle = PI_DISPATCHER
.load_image(false, protocol_db::DXE_CORE_HANDLE, core::ptr::null_mut(), Some(&image))
.unwrap();
PI_DISPATCHER.unload_image(image_handle, false).unwrap();
let private_data = PI_DISPATCHER.image_data.lock();
assert!(!private_data.private_image_data.contains_key(&image_handle));
});
}
#[test]
fn locate_image_metadata_by_file_path_should_fail_if_no_file_support() {
with_locked_state(|| {
assert_eq!(
ImageData::locate_image_metadata_by_file_path(true, core::ptr::null_mut()),
Err(EfiError::InvalidParameter)
);
let mut device_path_bytes = [
efi::protocols::device_path::TYPE_MEDIA,
efi::protocols::device_path::Media::SUBTYPE_FILE_PATH,
0x8, 0x0, 0x41,
0x00, 0x00,
0x00, efi::protocols::device_path::Media::SUBTYPE_FILE_PATH,
0x8, 0x0, 0x42,
0x00, 0x00,
0x00, efi::protocols::device_path::Media::SUBTYPE_FILE_PATH,
0x8, 0x0, 0x43,
0x00, 0x00,
0x00, efi::protocols::device_path::TYPE_END,
efi::protocols::device_path::End::SUBTYPE_ENTIRE,
0x4, 0x00, ];
let device_path_ptr = device_path_bytes.as_mut_ptr() as *mut efi::protocols::device_path::Protocol;
assert_eq!(ImageData::locate_image_metadata_by_file_path(true, device_path_ptr), Err(EfiError::NotFound));
});
}
extern "efiapi" fn file_read(
_this: *mut efi::protocols::file::Protocol,
buffer_size: *mut usize,
buffer: *mut c_void,
) -> efi::Status {
let mut test_file = File::open(test_collateral!("RustImageTestDxe.efi")).expect("failed to open test file.");
unsafe {
let slice = core::slice::from_raw_parts_mut(buffer as *mut u8, *buffer_size);
let read_bytes = test_file.read(slice).unwrap();
buffer_size.write(read_bytes);
}
efi::Status::SUCCESS
}
extern "efiapi" fn file_close(_this: *mut efi::protocols::file::Protocol) -> efi::Status {
efi::Status::SUCCESS
}
extern "efiapi" fn file_info(
_this: *mut efi::protocols::file::Protocol,
_prot: *mut efi::Guid,
size: *mut usize,
buffer: *mut c_void,
) -> efi::Status {
let test_file = File::open(test_collateral!("RustImageTestDxe.efi")).expect("failed to open test file.");
let file_info = efi::protocols::file::Info {
size: core::mem::size_of::<efi::protocols::file::Info>() as u64,
file_size: test_file.metadata().unwrap().len(),
physical_size: test_file.metadata().unwrap().len(),
create_time: Default::default(),
last_access_time: Default::default(),
modification_time: Default::default(),
attribute: 0,
file_name: [0; 0],
};
let file_info_ptr = Box::into_raw(Box::new(file_info));
let mut status = efi::Status::SUCCESS;
unsafe {
if *size >= (*file_info_ptr).size.try_into().unwrap() {
core::ptr::copy(file_info_ptr, buffer as *mut efi::protocols::file::Info, 1);
} else {
status = efi::Status::BUFFER_TOO_SMALL;
}
size.write((*file_info_ptr).size.try_into().unwrap());
}
status
}
extern "efiapi" fn file_open(
_this: *mut efi::protocols::file::Protocol,
new_handle: *mut *mut efi::protocols::file::Protocol,
_filename: *mut efi::Char16,
_open_mode: u64,
_attributes: u64,
) -> efi::Status {
let file_ptr = get_file_protocol_mock();
unsafe {
new_handle.write(file_ptr);
}
efi::Status::SUCCESS
}
extern "efiapi" fn file_set_position(_this: *mut efi::protocols::file::Protocol, _pos: u64) -> efi::Status {
efi::Status::SUCCESS
}
extern "efiapi" fn unimplemented_extern() {
unimplemented!();
}
#[allow(clippy::undocumented_unsafe_blocks)]
fn get_file_protocol_mock() -> *mut efi::protocols::file::Protocol {
#[allow(clippy::missing_transmute_annotations)]
let file = efi::protocols::file::Protocol {
revision: efi::protocols::file::LATEST_REVISION,
open: file_open,
close: file_close,
delete: unsafe { core::mem::transmute(unimplemented_extern as extern "efiapi" fn()) },
read: file_read,
write: unsafe { core::mem::transmute(unimplemented_extern as extern "efiapi" fn()) },
get_position: unsafe { core::mem::transmute(unimplemented_extern as extern "efiapi" fn()) },
set_position: file_set_position,
get_info: file_info,
set_info: unsafe { core::mem::transmute(unimplemented_extern as extern "efiapi" fn()) },
flush: unsafe { core::mem::transmute(unimplemented_extern as extern "efiapi" fn()) },
open_ex: unsafe { core::mem::transmute(unimplemented_extern as extern "efiapi" fn()) },
read_ex: unsafe { core::mem::transmute(unimplemented_extern as extern "efiapi" fn()) },
write_ex: unsafe { core::mem::transmute(unimplemented_extern as extern "efiapi" fn()) },
flush_ex: unsafe { core::mem::transmute(unimplemented_extern as extern "efiapi" fn()) },
};
Box::into_raw(Box::new(file))
}
const ROOT_DEVICE_PATH_BYTES: [u8; 12] = [
efi::protocols::device_path::TYPE_MEDIA,
efi::protocols::device_path::Media::SUBTYPE_FILE_PATH,
0x8, 0x0, 0x41,
0x00, 0x00,
0x00, efi::protocols::device_path::TYPE_END,
efi::protocols::device_path::End::SUBTYPE_ENTIRE,
0x4, 0x00, ];
const FULL_DEVICE_PATH_BYTES: [u8; 28] = [
efi::protocols::device_path::TYPE_MEDIA,
efi::protocols::device_path::Media::SUBTYPE_FILE_PATH,
0x8, 0x0, 0x41,
0x00, 0x00,
0x00, efi::protocols::device_path::TYPE_MEDIA,
efi::protocols::device_path::Media::SUBTYPE_FILE_PATH,
0x8, 0x0, 0x42,
0x00, 0x00,
0x00, efi::protocols::device_path::TYPE_MEDIA,
efi::protocols::device_path::Media::SUBTYPE_FILE_PATH,
0x8, 0x0, 0x43,
0x00, 0x00,
0x00, efi::protocols::device_path::TYPE_END,
efi::protocols::device_path::End::SUBTYPE_ENTIRE,
0x4, 0x00, ];
#[test]
fn locate_image_metadata_by_file_path_should_work_over_sfs() {
with_locked_state(|| {
extern "efiapi" fn open_volume(
_this: *mut efi::protocols::simple_file_system::Protocol,
root: *mut *mut efi::protocols::file::Protocol,
) -> efi::Status {
let file_ptr = get_file_protocol_mock();
unsafe {
root.write(file_ptr);
}
efi::Status::SUCCESS
}
let protocol = efi::protocols::simple_file_system::Protocol {
revision: efi::protocols::simple_file_system::REVISION,
open_volume,
};
let protocol_ptr = Box::into_raw(Box::new(protocol));
let handle = core_install_protocol_interface(
None,
efi::protocols::simple_file_system::PROTOCOL_GUID,
protocol_ptr as *mut c_void,
)
.unwrap();
let root_device_path_ptr = Box::into_raw(Box::new(ROOT_DEVICE_PATH_BYTES)) as *mut u8
as *mut efi::protocols::device_path::Protocol;
core_install_protocol_interface(
Some(handle),
efi::protocols::device_path::PROTOCOL_GUID,
root_device_path_ptr as *mut c_void,
)
.unwrap();
let mut full_device_path_bytes = FULL_DEVICE_PATH_BYTES;
let device_path_ptr = full_device_path_bytes.as_mut_ptr() as *mut efi::protocols::device_path::Protocol;
let mut test_file =
File::open(test_collateral!("RustImageTestDxe.efi")).expect("failed to open test file.");
let mut image: Vec<u8> = Vec::new();
test_file.read_to_end(&mut image).expect("failed to read test file");
assert_eq!(
ImageData::locate_image_metadata_by_file_path(true, device_path_ptr),
Ok((image, false, handle, 0))
);
});
}
#[test]
fn locate_image_metadata_by_file_path_should_work_over_load_protocol() {
with_locked_state(|| {
extern "efiapi" fn load_file(
_this: *mut efi::protocols::load_file::Protocol,
_file_path: *mut efi::protocols::device_path::Protocol,
_boot_policy: efi::Boolean,
buffer_size: *mut usize,
buffer: *mut c_void,
) -> efi::Status {
let mut test_file =
File::open(test_collateral!("RustImageTestDxe.efi")).expect("failed to open test file.");
let status;
unsafe {
if *buffer_size < test_file.metadata().unwrap().len() as usize {
buffer_size.write(test_file.metadata().unwrap().len() as usize);
status = efi::Status::BUFFER_TOO_SMALL;
} else {
let slice = core::slice::from_raw_parts_mut(buffer as *mut u8, *buffer_size);
let read_bytes = test_file.read(slice).unwrap();
buffer_size.write(read_bytes);
status = efi::Status::SUCCESS;
}
}
status
}
let protocol = efi::protocols::load_file::Protocol { load_file };
let protocol_ptr = Box::into_raw(Box::new(protocol));
let handle = core_install_protocol_interface(
None,
efi::protocols::load_file::PROTOCOL_GUID,
protocol_ptr as *mut c_void,
)
.unwrap();
let root_device_path_ptr = Box::into_raw(Box::new(ROOT_DEVICE_PATH_BYTES)) as *mut u8
as *mut efi::protocols::device_path::Protocol;
core_install_protocol_interface(
Some(handle),
efi::protocols::device_path::PROTOCOL_GUID,
root_device_path_ptr as *mut c_void,
)
.unwrap();
let mut full_device_path_bytes = FULL_DEVICE_PATH_BYTES;
let device_path_ptr = full_device_path_bytes.as_mut_ptr() as *mut efi::protocols::device_path::Protocol;
let mut test_file =
File::open(test_collateral!("RustImageTestDxe.efi")).expect("failed to open test file.");
let mut image: Vec<u8> = Vec::new();
test_file.read_to_end(&mut image).expect("failed to read test file");
assert_eq!(
ImageData::locate_image_metadata_by_file_path(true, device_path_ptr),
Ok((image, false, handle, 0))
);
});
}
#[test]
fn load_image_should_succeed_with_proper_memory_protections() {
with_locked_state(|| {
let mut test_file =
File::open(test_collateral!("RustImageTestDxe.efi")).expect("failed to open test file.");
let mut image: Vec<u8> = Vec::new();
test_file.read_to_end(&mut image).expect("failed to read test file");
static PI_DISPATCHER: PiDispatcher<MockPlatformInfo> =
PiDispatcher::<MockPlatformInfo>::new(patina_ffs_extractors::NullSectionExtractor);
PI_DISPATCHER.init(&create_dxe_core_hob(), SYSTEM_TABLE.lock().as_mut().unwrap());
let image_handle = PI_DISPATCHER
.load_image(false, protocol_db::DXE_CORE_HANDLE, core::ptr::null_mut(), Some(&image))
.unwrap();
let private_data = PI_DISPATCHER.image_data.lock();
let image_data = private_data.private_image_data.get(&image_handle).unwrap();
assert_ne!(image_data.entry_point as usize, 0);
assert_eq!(image_data.image_info.image_code_type, efi::BOOT_SERVICES_CODE);
assert_eq!(image_data.image_info.image_data_type, efi::BOOT_SERVICES_DATA);
drop(private_data);
let image_info = empty_image_info();
let result = super::core_load_pe_image(&image, image_info);
assert!(result.is_ok());
let private_info = result.unwrap();
assert_ne!(private_info.entry_point as usize, 0);
});
}
#[test]
fn apply_memory_protections_should_fail_when_section_address_not_in_gcd() {
let result = test_support::with_global_lock(|| {
unsafe {
crate::GCD.reset();
crate::GCD.init(48, 16); }
let section = goblin::pe::section_table::SectionTable {
name: [0; 8],
real_name: None,
virtual_size: 0x1000,
virtual_address: 0x0, size_of_raw_data: 0x1000,
pointer_to_raw_data: 0,
pointer_to_relocations: 0,
pointer_to_linenumbers: 0,
number_of_relocations: 0,
number_of_linenumbers: 0,
characteristics: goblin::pe::section_table::IMAGE_SCN_CNT_CODE
| goblin::pe::section_table::IMAGE_SCN_MEM_READ
| goblin::pe::section_table::IMAGE_SCN_MEM_EXECUTE,
};
let pe_info = super::UefiPeInfo {
sections: vec![section],
section_alignment: 0x1000,
size_of_image: 0x2000,
..Default::default()
};
let mut image_info = empty_image_info();
image_info.image_base = 0x1000 as *mut c_void; image_info.image_size = 0x2000;
const LEN: usize = 0x2000;
let fake_buffer =
unsafe { alloc::alloc::alloc(alloc::alloc::Layout::from_size_align(LEN, 0x1000).unwrap()) };
let slice = unsafe { core::slice::from_raw_parts_mut(fake_buffer, LEN) };
let bytes = super::Buffer::Borrowed(slice);
extern "efiapi" fn dummy_entry(_: *mut c_void, _: *mut efi::SystemTable) -> efi::Status {
efi::Status::SUCCESS
}
let private_info = super::PrivateImageData {
image_buffer: bytes,
image_info: Box::new(image_info),
hii_resource_section: None,
entry_point: dummy_entry,
started: false,
exit_data: None,
image_device_path: None,
pe_info: pe_info.clone(),
relocation_data: Vec::new(),
};
let result = private_info.apply_image_memory_protections();
assert!(result.is_err(), "Protection should fail when section address is not in GCD");
assert_eq!(result.unwrap_err(), EfiError::NotFound, "Expected NotFound from GCD descriptor lookup");
});
#[cfg(debug_assertions)]
assert!(result.is_err(), "Expected panic from debug_assert! in debug build");
#[cfg(not(debug_assertions))]
assert!(result.is_ok(), "Expected successful test execution in release build");
}
#[test]
fn load_image_should_fail_with_section_alignment_overflow() {
let result = test_support::with_global_lock(|| {
unsafe {
test_support::init_test_gcd(None);
test_support::init_test_protocol_db();
init_system_table();
}
let mut test_file =
File::open(test_collateral!("RustImageTestDxe.efi")).expect("failed to open test file.");
let mut image: Vec<u8> = Vec::new();
test_file.read_to_end(&mut image).expect("failed to read test file");
let mut image_info = empty_image_info();
let mut pe_info = UefiPeInfo::parse(&image).unwrap();
let size = pe_info.size_of_image as usize;
image_info.image_size = size as u64;
image_info.image_code_type = efi::BOOT_SERVICES_CODE;
image_info.image_data_type = efi::BOOT_SERVICES_DATA;
pe_info.sections[0].virtual_size = u32::MAX - 0x800;
let mut private_info = PrivateImageData::new(image_info, pe_info).unwrap();
private_info.load_image(&image).unwrap();
private_info.relocate_image().unwrap();
private_info.load_resource_section(&image).unwrap();
let result = private_info.apply_image_memory_protections();
assert!(matches!(result, Err(EfiError::LoadError)), "Expected LoadError from section size overflow");
});
#[cfg(debug_assertions)]
assert!(result.is_err(), "Expected panic from debug_assert! in debug build");
#[cfg(not(debug_assertions))]
assert!(result.is_ok(), "Expected successful test execution in release build");
}
#[test]
fn load_image_should_fail_with_unaligned_section_address() {
test_support::with_global_lock(|| {
unsafe {
test_support::init_test_gcd(None);
test_support::init_test_protocol_db();
init_system_table();
}
let mut test_file =
File::open(test_collateral!("RustImageTestDxe.efi")).expect("failed to open test file.");
let mut image: Vec<u8> = Vec::new();
test_file.read_to_end(&mut image).expect("failed to read test file");
let pe_offset = 0x78;
let opt_header_size_offset = pe_offset + 4 + 16;
let opt_header_size =
u16::from_le_bytes([image[opt_header_size_offset], image[opt_header_size_offset + 1]]) as usize;
let section_table_offset = pe_offset + 4 + 20 + opt_header_size;
let virtual_address_offset = section_table_offset + 12;
let unaligned_value: u32 = 0x1001; image[virtual_address_offset..virtual_address_offset + 4].copy_from_slice(&unaligned_value.to_le_bytes());
let image_info = empty_image_info();
let result = super::core_load_pe_image(&image, image_info);
assert!(
matches!(result, Err(EfiError::InvalidParameter)),
"Expected InvalidParameter from unaligned section address"
);
})
.unwrap();
}
#[test]
fn test_stack_guard_sizes_are_calculated_correctly() {
test_support::with_global_lock(|| {
unsafe {
test_support::init_test_gcd(None);
test_support::init_test_protocol_db();
init_system_table();
}
const STACK_SIZE: usize = 0x10000;
let stack = super::ImageStack::new(STACK_SIZE).unwrap();
let guard_start = stack.stack.as_ptr() as usize;
let guard_end = guard_start + super::UEFI_PAGE_SIZE;
let stack_start = guard_end;
let stack_end = stack_start + STACK_SIZE;
assert_eq!(stack.guard().as_ptr() as usize, guard_start);
assert_eq!(stack.guard().len(), super::UEFI_PAGE_SIZE);
assert_eq!(stack.guard().as_ptr() as usize + stack.guard().len(), guard_end);
assert_eq!(stack.guard().as_ptr() as usize + stack.guard().len(), stack.body().as_ptr() as usize);
assert_eq!(stack.body().as_ptr() as usize, stack_start);
assert_eq!(stack.body().len(), STACK_SIZE);
assert_eq!(stack.body().as_ptr() as usize + stack.body().len(), stack_end);
})
.unwrap();
}
#[test]
fn test_custom_alignment_creates_proper_page_count() {
test_support::with_global_lock(|| {
unsafe {
test_support::init_test_gcd(None);
test_support::init_test_protocol_db();
init_system_table();
}
let mut test_file =
File::open(test_collateral!("RustImageTestDxe.efi")).expect("failed to open test file.");
let mut image: Vec<u8> = Vec::new();
test_file.read_to_end(&mut image).expect("failed to read test file");
let mut pe_info = UefiPeInfo::parse(&image).unwrap();
const CUSTOM_ALIGNMENT: u32 = super::UEFI_PAGE_SIZE as u32 * 4;
pe_info.section_alignment = CUSTOM_ALIGNMENT;
let mut protocol = super::empty_image_info();
protocol.image_size = pe_info.size_of_image as u64;
protocol.image_code_type = efi::BOOT_SERVICES_CODE;
protocol.image_data_type = efi::BOOT_SERVICES_DATA;
let image_info = PrivateImageData::new(protocol, pe_info).unwrap();
match image_info.image_buffer {
super::Buffer::Owned(buffer) => {
assert_eq!(buffer.as_ptr() as usize % CUSTOM_ALIGNMENT as usize, 0);
}
super::Buffer::Borrowed(_) => {
panic!("Expected owned buffer for loaded image");
}
}
})
.unwrap();
}
#[test]
fn test_cannot_load_image_on_foreign_image() {
test_support::with_global_lock(|| {
unsafe {
test_support::init_test_gcd(None);
test_support::init_test_protocol_db();
init_system_table();
}
let mut test_file =
File::open(test_collateral!("RustImageTestDxe.efi")).expect("failed to open test file.");
let mut image: Vec<u8> = Vec::new();
test_file.read_to_end(&mut image).expect("failed to read test file");
let mut protocol = super::empty_image_info();
protocol.image_size = image.len() as u64;
protocol.image_code_type = efi::BOOT_SERVICES_CODE;
protocol.image_data_type = efi::BOOT_SERVICES_DATA;
let pe_info = UefiPeInfo::parse(&image).unwrap();
let slice_ptr: &'static [u8] = unsafe { from_raw_parts(image.as_mut_ptr(), image.len()) };
let mut image_data = PrivateImageData::new_from_static_image(
protocol,
slice_ptr,
super::unimplemented_entry_point,
&pe_info,
);
assert!(image_data.load_image(&image).is_err_and(|err| err == EfiError::LoadError));
})
.unwrap();
}
#[test]
fn test_pecoff_load_error_is_propagaged() {
test_support::with_global_lock(|| {
unsafe {
test_support::init_test_gcd(None);
test_support::init_test_protocol_db();
init_system_table();
}
let mut test_file =
File::open(test_collateral!("RustImageTestDxe.efi")).expect("failed to open test file.");
let mut image: Vec<u8> = Vec::new();
test_file.read_to_end(&mut image).expect("failed to read test file");
let mut protocol = super::empty_image_info();
protocol.image_size = image.len() as u64;
protocol.image_code_type = efi::BOOT_SERVICES_CODE;
protocol.image_data_type = efi::BOOT_SERVICES_DATA;
let pe_info = UefiPeInfo::parse(&image).unwrap();
let mut image_data = PrivateImageData::new(protocol, pe_info).unwrap();
image[0] = 0x00;
assert!(image_data.load_image(&image).is_err_and(|err| err == EfiError::LoadError));
})
.unwrap();
}
#[test]
#[cfg(not(feature = "compatibility_mode_allowed"))]
fn test_activate_compatability_mode_should_fail_if_feature_not_set() {
test_support::with_global_lock(|| {
unsafe {
test_support::init_test_gcd(None);
test_support::init_test_protocol_db();
init_system_table();
}
let mut test_file =
File::open(test_collateral!("RustImageTestDxe.efi")).expect("failed to open test file.");
let mut image: Vec<u8> = Vec::new();
test_file.read_to_end(&mut image).expect("failed to read test file");
let mut protocol = super::empty_image_info();
protocol.image_size = image.len() as u64;
protocol.image_code_type = efi::BOOT_SERVICES_CODE;
protocol.image_data_type = efi::BOOT_SERVICES_DATA;
let pe_info = UefiPeInfo::parse(&image).unwrap();
let image_data = PrivateImageData::new(protocol, pe_info).unwrap();
assert!(image_data.activate_compatibility_mode().is_err_and(|err| err == EfiError::LoadError));
})
.unwrap();
}
#[test]
fn test_private_image_data_uninstall_succeeds_even_if_handle_is_stale() {
test_support::with_global_lock(|| {
unsafe {
test_support::init_test_gcd(None);
test_support::init_test_protocol_db();
init_system_table();
}
let mut test_file =
File::open(test_collateral!("RustImageTestDxe.efi")).expect("failed to open test file.");
let mut image: Vec<u8> = Vec::new();
test_file.read_to_end(&mut image).expect("failed to read test file");
let mut protocol = super::empty_image_info();
protocol.image_size = image.len() as u64;
protocol.image_code_type = efi::BOOT_SERVICES_CODE;
protocol.image_data_type = efi::BOOT_SERVICES_DATA;
let pe_info = UefiPeInfo::parse(&image).unwrap();
let image_data = PrivateImageData::new(protocol, pe_info).unwrap();
let handle = image_data.install().unwrap();
assert!(image_data.uninstall(handle).is_ok());
assert!(image_data.uninstall(handle).is_ok());
})
.unwrap();
}
#[test]
fn test_private_image_data_uninstall_succeeds_even_if_protocol_already_uninstalled() {
test_support::with_global_lock(|| {
unsafe {
test_support::init_test_gcd(None);
test_support::init_test_protocol_db();
init_system_table();
}
let mut test_file =
File::open(test_collateral!("RustImageTestDxe.efi")).expect("failed to open test file.");
let mut image: Vec<u8> = Vec::new();
test_file.read_to_end(&mut image).expect("failed to read test file");
let mut protocol = super::empty_image_info();
protocol.image_size = image.len() as u64;
protocol.image_code_type = efi::BOOT_SERVICES_CODE;
protocol.image_data_type = efi::BOOT_SERVICES_DATA;
let pe_info = UefiPeInfo::parse(&image).unwrap();
let image_data = PrivateImageData::new(protocol, pe_info).unwrap();
let handle = image_data.install().unwrap();
core_install_protocol_interface(
Some(handle),
efi::protocols::disk_io::PROTOCOL_GUID,
core::ptr::null_mut(),
)
.unwrap();
assert!(image_data.uninstall(handle).is_ok());
assert!(image_data.uninstall(handle).is_ok());
})
.unwrap();
}
#[test]
fn test_private_image_data_uninstall_succeeds_if_found() {
test_support::with_global_lock(|| {
unsafe {
test_support::init_test_gcd(None);
test_support::init_test_protocol_db();
init_system_table();
}
let mut test_file =
File::open(test_collateral!("RustImageTestDxe.efi")).expect("failed to open test file.");
let mut image: Vec<u8> = Vec::new();
test_file.read_to_end(&mut image).expect("failed to read test file");
let mut protocol = super::empty_image_info();
protocol.image_size = image.len() as u64;
protocol.image_code_type = efi::BOOT_SERVICES_CODE;
protocol.image_data_type = efi::BOOT_SERVICES_DATA;
let pe_info = UefiPeInfo::parse(&image).unwrap();
let image_data = PrivateImageData::new(protocol, pe_info).unwrap();
let handle = image_data.install().unwrap();
assert_eq!(image_data.uninstall(handle), Ok(()));
})
.unwrap();
}
fn node_from_str(node_str: &str) -> Option<Vec<u8>> {
let node_str = node_str.to_uppercase();
match node_str.as_str() {
s if s.starts_with("PCI(") => {
let inner = s.strip_prefix("PCI(")?.strip_suffix(")")?;
let mut parts = inner.split(',');
let device = parts.next()?.trim();
let function = parts.next()?.trim();
Some(vec![
TYPE_HARDWARE,
Hardware::SUBTYPE_PCI,
0x6, 0x0, u8::from_str_radix(function, 16).ok()?, u8::from_str_radix(device, 16).ok()?, ])
}
"END" => Some(vec![
TYPE_END,
End::SUBTYPE_ENTIRE,
0x4, 0x0, ]),
_ => None,
}
}
fn filepath_node_from_str(path: &str) -> Vec<u8> {
let path_bytes = path.as_bytes();
let path_len = path_bytes.len() + 2 + 4; let mut node = vec![
TYPE_MEDIA,
Media::SUBTYPE_FILE_PATH,
(path_len & 0xFF) as u8, ((path_len >> 8) & 0xFF) as u8, ];
node.extend_from_slice(path_bytes);
node.push(0); node.push(0); node
}
fn device_path_from_string(path: String) -> Box<[u8]> {
let path = path.replace("\\", "/").replace("0x", "");
let mut total = Vec::new();
let mut current_path = String::new();
for nodes in path.split('/') {
if let Some(node) = node_from_str(nodes) {
if !current_path.is_empty() {
let filepath_node = filepath_node_from_str(¤t_path);
total.extend_from_slice(&filepath_node);
current_path.clear();
}
total.extend_from_slice(&node);
}
else {
if !current_path.is_empty() {
current_path.push('/');
}
current_path.push_str(nodes);
}
}
if !current_path.is_empty() {
let filepath_node = filepath_node_from_str(¤t_path);
total.extend_from_slice(&filepath_node);
}
Box::from(total.as_slice())
}
#[test]
fn test_set_file_path_with_no_device_handle() {
test_support::with_global_lock(|| {
let child_device_path =
device_path_from_string(String::from("PCI(0,1C)/PCI(0,0)/EFI/BOOT/BOOT_X64.EFI/END"));
unsafe {
test_support::init_test_gcd(None);
test_support::init_test_protocol_db();
init_system_table();
}
let mut test_file =
File::open(test_collateral!("RustImageTestDxe.efi")).expect("failed to open test file.");
let mut image: Vec<u8> = Vec::new();
test_file.read_to_end(&mut image).expect("failed to read test file");
let mut protocol = super::empty_image_info();
protocol.image_size = image.len() as u64;
protocol.image_code_type = efi::BOOT_SERVICES_CODE;
protocol.image_data_type = efi::BOOT_SERVICES_DATA;
let pe_info = UefiPeInfo::parse(&image).unwrap();
let mut private_info = PrivateImageData::new(protocol, pe_info).unwrap();
private_info.image_info.device_handle = protocol_db::INVALID_HANDLE;
let nn = NonNull::new(child_device_path.as_ptr() as *mut efi::protocols::device_path::Protocol).unwrap();
private_info.set_file_path(nn).unwrap();
assert!(!private_info.image_info.file_path.is_null());
let (_, len) = device_path_node_count(private_info.image_info.file_path).unwrap();
let bytes = unsafe { core::slice::from_raw_parts(private_info.image_info.file_path as *const u8, len) };
assert_eq!(bytes, child_device_path.as_ref());
let (_, len) =
device_path_node_count(private_info.get_file_path() as *mut efi::protocols::device_path::Protocol)
.unwrap();
let bytes = unsafe { core::slice::from_raw_parts(private_info.get_file_path() as *const u8, len) };
assert_eq!(bytes, child_device_path.as_ref());
})
.unwrap();
}
#[test]
fn test_set_file_path_with_a_device_handle() {
test_support::with_global_lock(|| {
let parent_device_path = device_path_from_string(String::from("PCI(0,1C)/PCI(0,0)/END"));
let child_device_path =
device_path_from_string(String::from("PCI(0,1C)/PCI(0,0)/EFI/BOOT/BOOT_X64.EFI/END"));
let child_filename = device_path_from_string(String::from("EFI/BOOT/BOOT_X64.EFI/END"));
unsafe {
test_support::init_test_gcd(None);
test_support::init_test_protocol_db();
init_system_table();
}
let (parent_handle, _) = PROTOCOL_DB
.install_protocol_interface(
None,
efi::protocols::device_path::PROTOCOL_GUID,
parent_device_path.as_ptr() as *mut c_void,
)
.unwrap();
let mut test_file =
File::open(test_collateral!("RustImageTestDxe.efi")).expect("failed to open test file.");
let mut image: Vec<u8> = Vec::new();
test_file.read_to_end(&mut image).expect("failed to read test file");
let mut protocol = super::empty_image_info();
protocol.image_size = image.len() as u64;
protocol.image_code_type = efi::BOOT_SERVICES_CODE;
protocol.image_data_type = efi::BOOT_SERVICES_DATA;
let pe_info = UefiPeInfo::parse(&image).unwrap();
let mut private_info = PrivateImageData::new(protocol, pe_info).unwrap();
private_info.image_info.device_handle = parent_handle;
let nn = NonNull::new(child_device_path.as_ptr() as *mut efi::protocols::device_path::Protocol).unwrap();
private_info.set_file_path(nn).unwrap();
assert!(!private_info.image_info.file_path.is_null());
let (_, len) = device_path_node_count(private_info.image_info.file_path).unwrap();
let bytes = unsafe { core::slice::from_raw_parts(private_info.image_info.file_path as *const u8, len) };
assert_eq!(bytes, child_filename.as_ref());
let (_, len) =
device_path_node_count(private_info.get_file_path() as *mut efi::protocols::device_path::Protocol)
.unwrap();
let bytes = unsafe { core::slice::from_raw_parts(private_info.get_file_path() as *const u8, len) };
assert_eq!(bytes, child_device_path.as_ref());
})
.unwrap();
}
fn create_dxe_core_hob() -> HobList<'static> {
let mut test_file = File::open(test_collateral!("RustImageTestDxe.efi")).expect("failed to open test file.");
let mut image: Vec<u8> = Vec::new();
test_file.read_to_end(&mut image).expect("failed to read test file");
let image = Box::leak(image.into_boxed_slice());
extern "efiapi" fn entry_point(_: *mut c_void, _: *mut efi::SystemTable) -> efi::Status {
efi::Status::SUCCESS
}
let hob = patina::pi::hob::header::Hob {
r#type: patina::pi::hob::MEMORY_ALLOCATION,
length: core::mem::size_of::<MemoryAllocationModule>() as u16,
reserved: 0,
};
let ma_hob = MemoryAllocationModule {
header: hob,
alloc_descriptor: MemoryAllocation {
name: guids::DXE_CORE,
memory_base_address: image.as_ptr() as u64,
memory_length: image.len() as u64,
memory_type: efi::BOOT_SERVICES_CODE,
reserved: [0; 4],
},
module_name: guids::DXE_CORE,
entry_point: entry_point as *const () as u64,
};
let end_hob = patina::pi::hob::header::Hob {
r#type: patina::pi::hob::END_OF_HOB_LIST,
length: core::mem::size_of::<patina::pi::hob::header::Hob>() as u16,
reserved: 0,
};
let mut hobs = Vec::new();
hobs.extend_from_slice(unsafe {
core::slice::from_raw_parts(
&ma_hob as *const MemoryAllocationModule as *const u8,
core::mem::size_of::<MemoryAllocationModule>(),
)
});
hobs.extend_from_slice(unsafe {
core::slice::from_raw_parts(
&end_hob as *const patina::pi::hob::header::Hob as *const u8,
core::mem::size_of::<patina::pi::hob::header::Hob>(),
)
});
let mut hob_list = HobList::new();
hob_list.discover_hobs(hobs.as_ptr() as *mut c_void);
hob_list
}
#[test]
fn test_install_dxe_core_image() {
test_support::with_global_lock(|| {
unsafe {
test_support::init_test_gcd(None);
test_support::init_test_protocol_db();
init_system_table();
}
let mut test_file =
File::open(test_collateral!("RustImageTestDxe.efi")).expect("failed to open test file.");
let mut image: Vec<u8> = Vec::new();
test_file.read_to_end(&mut image).expect("failed to read test file");
static PI_DISPATCHER: PiDispatcher<MockPlatformInfo> =
PiDispatcher::<MockPlatformInfo>::new(patina_ffs_extractors::NullSectionExtractor);
PI_DISPATCHER.init(&create_dxe_core_hob(), SYSTEM_TABLE.lock().as_mut().unwrap());
assert!(PI_DISPATCHER.image_data.lock().private_image_data.contains_key(&DXE_CORE_HANDLE));
})
.unwrap();
}
}