//! Bounded UEFI BootInfo V1 admission. Direct Image boots use x1=0. use turkos_bootinfo::{BOOT_INFO_MAP_CAPACITY, BOOT_INFO_PAGE_BYTES}; pub use turkos_bootinfo::{ BootInfoHandoffError, BootInfoHeaderError, BootInfoPointerError, BootInfoV1, }; #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum MapError { Length, Descriptor, Overlap, Ownership, } pub const EFI_CONVENTIONAL_MEMORY: u32 = 7; #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct EfiDescriptor { pub kind: u32, pub start: u64, pub end: u64, } pub fn parse_memory_descriptor(bytes: &[u8]) -> Result { if bytes.len() < 40 { return Err(MapError::Descriptor); } let kind = u32::from_le_bytes(bytes[0..4].try_into().map_err(|_| MapError::Descriptor)?); let start = u64::from_le_bytes(bytes[8..16].try_into().map_err(|_| MapError::Descriptor)?); let pages = u64::from_le_bytes(bytes[24..32].try_into().map_err(|_| MapError::Descriptor)?); let end = start .checked_add( pages .checked_mul(BOOT_INFO_PAGE_BYTES) .ok_or(MapError::Descriptor)?, ) .ok_or(MapError::Descriptor)?; if pages == 0 || start % BOOT_INFO_PAGE_BYTES != 0 { return Err(MapError::Descriptor); } Ok(EfiDescriptor { kind, start, end }) } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct MapSummary { pub descriptors: usize, pub loader_data_descriptors: usize, } pub fn validate_uefi_boot_info_pointer(address: u64) -> Result<(), BootInfoPointerError> { turkos_bootinfo::validate_boot_info_pointer(address) } pub fn validate_uefi_boot_info_header(info: &BootInfoV1) -> Result<(), BootInfoHeaderError> { info.validate_header() } /// Inspect the transferred bytes, not a firmware-owned static pointer. The /// loader's header and 64 KiB backing allocation must both appear as LoaderData. pub fn inspect_uefi_memory_map( info: &BootInfoV1, boot_info_address: u64, map: &[u8], ) -> Result { let stride = info.descriptor_bytes as usize; if stride < 40 || stride % 8 != 0 || map.len() != info.memory_map_bytes as usize || map.is_empty() || map.len() % stride != 0 || map.len() > BOOT_INFO_MAP_CAPACITY as usize { return Err(MapError::Length); } let info_end = boot_info_address .checked_add(BOOT_INFO_PAGE_BYTES) .ok_or(MapError::Descriptor)?; let map_end = info .memory_map_ptr .checked_add(BOOT_INFO_MAP_CAPACITY) .ok_or(MapError::Descriptor)?; let mut info_owned = false; let mut map_owned = false; let mut loader_data_descriptors = 0; for (index, bytes) in map.chunks_exact(stride).enumerate() { let descriptor = parse_memory_descriptor(bytes)?; // A conventional descriptor overlapping any reserved or MMIO range // could otherwise hand a firmware-owned page to the allocator. for prior in map[..index * stride].chunks_exact(stride) { let earlier = parse_memory_descriptor(prior)?; if descriptor.start < earlier.end && earlier.start < descriptor.end { return Err(MapError::Overlap); } } if descriptor.kind == 2 { // EFI_LOADER_DATA loader_data_descriptors += 1; info_owned |= descriptor.start <= boot_info_address && info_end <= descriptor.end; map_owned |= descriptor.start <= info.memory_map_ptr && map_end <= descriptor.end; } } if !info_owned || !map_owned { return Err(MapError::Ownership); } Ok(MapSummary { descriptors: map.len() / stride, loader_data_descriptors, }) } #[cfg(test)] mod tests { use super::*; #[test] fn k484_kernel_rejects_unsupported_boot_info_version_without_accepting_payload() { let valid = BootInfoV1::empty(); assert_eq!(validate_uefi_boot_info_header(&valid), Ok(())); let mut wrong = valid; wrong.version = 2; assert_eq!( validate_uefi_boot_info_header(&wrong), Err(BootInfoHeaderError::Version) ); wrong.version = 0; assert_eq!( validate_uefi_boot_info_header(&wrong), Err(BootInfoHeaderError::Version) ); wrong = valid; wrong.struct_bytes -= 8; assert_eq!( validate_uefi_boot_info_header(&wrong), Err(BootInfoHeaderError::Size) ); assert_eq!(validate_uefi_boot_info_header(&valid), Ok(())); } #[test] fn k503_kernel_numeric_pointer_gate_never_dereferences_address() { assert_eq!(validate_uefi_boot_info_pointer(0x4000_1000), Ok(())); assert_eq!( validate_uefi_boot_info_pointer(0), Err(BootInfoPointerError::Missing) ); assert_eq!( validate_uefi_boot_info_pointer(0x4000_1001), Err(BootInfoPointerError::Unaligned) ); assert_eq!( validate_uefi_boot_info_pointer(u64::MAX - 7), Err(BootInfoPointerError::AddressOverflow) ); } #[test] fn k511_k514_map_requires_both_loader_owned_allocations() { let mut info = BootInfoV1::empty(); info.memory_map_ptr = 0x4f00_0000; info.memory_map_bytes = 96; info.descriptor_bytes = 48; info.descriptor_version = 1; let mut map = [0u8; 96]; map[0..4].copy_from_slice(&2u32.to_le_bytes()); map[8..16].copy_from_slice(&0x4f00_0000u64.to_le_bytes()); map[24..32].copy_from_slice(&16u64.to_le_bytes()); map[48..52].copy_from_slice(&2u32.to_le_bytes()); map[56..64].copy_from_slice(&0x4f10_0000u64.to_le_bytes()); map[72..80].copy_from_slice(&1u64.to_le_bytes()); assert_eq!( inspect_uefi_memory_map(&info, 0x4f10_0000, &map), Ok(MapSummary { descriptors: 2, loader_data_descriptors: 2 }) ); map[48..52].copy_from_slice(&7u32.to_le_bytes()); assert_eq!( inspect_uefi_memory_map(&info, 0x4f10_0000, &map), Err(MapError::Ownership) ); map[48..52].copy_from_slice(&2u32.to_le_bytes()); map[72..80].copy_from_slice(&0u64.to_le_bytes()); assert_eq!( inspect_uefi_memory_map(&info, 0x4f10_0000, &map), Err(MapError::Descriptor) ); assert_eq!( inspect_uefi_memory_map(&info, 0x4f10_0000, &map[..95]), Err(MapError::Length) ); map[72..80].copy_from_slice(&1u64.to_le_bytes()); map[56..64].copy_from_slice(&0x4f00_1000u64.to_le_bytes()); assert_eq!( inspect_uefi_memory_map(&info, 0x4f10_0000, &map), Err(MapError::Overlap) ); } }