use std::collections::BTreeMap; use turkos_os_page_tables::descriptor::*; struct Tables { entries: BTreeMap<(u64, usize), u64>, owned: Vec, pages: Vec, } impl Reader for Tables { fn validate_table(&self, address: u64) -> Result<(), Error> { if self.owned.contains(&address) { Ok(()) } else { Err(Error::Backing) } } fn validate_page(&self, address: u64) -> Result<(), Error> { if self.pages.contains(&address) { Ok(()) } else { Err(Error::Backing) } } fn entry(&self, address: u64, index: usize) -> Result { assert!(index < ENTRIES); Ok(self.entries.get(&(address, index)).copied().unwrap_or(0)) } } fn geometry() -> Geometry { Geometry::new(48, 40).unwrap() } fn rx() -> Permissions { Permissions { user: true, write: false, execute: true, } } #[test] fn four_level_indices_cover_each_bit_boundary() { let geometry = geometry(); for (level, shift) in [(0, 39), (1, 30), (2, 21), (3, 12)] { assert_eq!(geometry.index(511 << shift, level), Ok(511)); assert_eq!(geometry.index(1 << shift, level), Ok(1)); assert_eq!(geometry.index((1 << shift) - 1, level), Ok(0)); } assert_eq!( Geometry::new(39, 40).unwrap().index(0, 0), Err(Error::Level) ); assert_eq!(geometry.index(1 << 48, 0), Err(Error::VirtualAddress)); assert_eq!(geometry.index(0, 4), Err(Error::Level)); } #[test] fn known_virtual_address_walks_to_owned_physical_page() { let geometry = geometry(); let va = (7 << 39) | (13 << 30) | (29 << 21) | (31 << 12) | 0xabc; let mut tables = Tables { entries: BTreeMap::new(), owned: vec![0x40000000, 0x40001000, 0x40002000, 0x40003000], pages: vec![0x41000000], }; for level in 0..3 { tables.entries.insert( ( tables.owned[level], geometry.index(va, level as u8).unwrap(), ), table(geometry, level as u8, tables.owned[level + 1]).unwrap(), ); } tables.entries.insert( (tables.owned[3], geometry.index(va, 3).unwrap()), page(geometry, 0x41000000, Memory::Normal, rx()).unwrap(), ); assert_eq!( walk(geometry, tables.owned[0], va, &tables), Ok(Translation { physical: 0x41000abc, memory: Memory::Normal, permissions: rx() }) ); tables.pages.clear(); assert_eq!( walk(geometry, tables.owned[0], va, &tables), Err(Error::Backing) ); } #[test] fn three_level_walk_and_empty_guard_stop_without_backing() { let geometry = Geometry::new(39, 40).unwrap(); let tables = Tables { entries: BTreeMap::new(), owned: vec![0x40000000], pages: vec![], }; assert_eq!( walk(geometry, 0x40000000, 0x80001000, &tables), Err(Error::Unmapped) ); assert_eq!(geometry.start_level(), 1); } #[test] fn strict_table_bits_and_unsupported_extensions_are_refused() { let geometry = geometry(); let valid = table(geometry, 0, 0x40000000).unwrap(); for bit in [2, 6, 10, 48, 49, 50, 51, 52, 53, 54, 55, 59, 60, 61, 62, 63] { assert_eq!( decode(geometry, 0, valid | 1 << bit), Err(Error::UnsupportedBits(1 << bit)) ); } assert_eq!(decode(geometry, 0, 1), Err(Error::UnsupportedBlock)); assert_eq!(decode(geometry, 3, 1), Err(Error::InvalidDescriptor)); assert_eq!(decode(geometry, 3, 2), Err(Error::InvalidDescriptor)); } #[test] fn physical_width_and_page_alignment_never_truncate() { let geometry = geometry(); assert_eq!(table(geometry, 1, 0x40000001), Err(Error::Alignment)); assert_eq!(table(geometry, 1, 1 << 40), Err(Error::PhysicalAddress)); assert_eq!( decode(geometry, 1, (1 << 40) | 3), Err(Error::PhysicalAddress) ); assert_eq!( page(geometry, (1 << 40) - PAGE, Memory::Normal, rx()).unwrap() & (((1 << 48) - 1) & !(PAGE - 1)), (1 << 40) - PAGE ); for (va, pa) in [(38, 40), (52, 48), (48, 52), (48, 35)] { assert_eq!(Geometry::new(va, pa), Err(Error::Geometry)); } } #[test] fn normal_user_rx_and_rw_have_exact_direct_permissions() { let geometry = geometry(); let rw = Permissions { user: true, write: true, execute: false, }; let code = page(geometry, 0x40000000, Memory::Normal, rx()).unwrap(); let data = page(geometry, 0x40001000, Memory::Normal, rw).unwrap(); assert_eq!(code >> 6 & 3, 3); assert_eq!(data >> 6 & 3, 1); assert_eq!(code >> 53 & 3, 1); assert_eq!(data >> 53 & 3, 3); assert!(code & (1 << 11) != 0 && data & (1 << 11) != 0); assert_eq!( decode(geometry, 3, code), Ok(Descriptor::Page { address: 0x40000000, memory: Memory::Normal, permissions: rx() }) ); assert_eq!( decode(geometry, 3, data), Ok(Descriptor::Page { address: 0x40001000, memory: Memory::Normal, permissions: rw }) ); } #[test] fn rwx_and_kernel_execution_of_user_code_are_refused() { let geometry = geometry(); let rwx = Permissions { user: true, write: true, execute: true, }; assert_eq!( page(geometry, 0x40000000, Memory::Normal, rwx), Err(Error::Permissions) ); let code = page(geometry, 0x40000000, Memory::Normal, rx()).unwrap(); assert_eq!( decode(geometry, 3, code & !(1 << 7)), Err(Error::Permissions) ); assert_eq!( decode(geometry, 3, code & !(1 << 53)), Err(Error::Permissions) ); assert_eq!( decode(geometry, 3, code & !(1 << 11)), Err(Error::Permissions) ); } #[test] fn device_pages_are_kernel_only_and_never_executable() { let geometry = geometry(); let kernel = Permissions { user: false, write: true, execute: false, }; let device = page(geometry, 0x09000000, Memory::Device, kernel).unwrap(); assert_eq!(device >> 2 & 7, 1); assert_eq!(device >> 8 & 3, 0); assert_eq!(device >> 53 & 3, 3); assert_eq!( decode(geometry, 3, device), Ok(Descriptor::Page { address: 0x09000000, memory: Memory::Device, permissions: kernel }) ); assert_eq!( page( geometry, 0x09000000, Memory::Device, Permissions { user: true, ..kernel } ), Err(Error::DeviceUser) ); assert_eq!( page( geometry, 0x09000000, Memory::Device, Permissions { write: false, execute: true, ..kernel } ), Err(Error::DeviceUser) ); } #[test] fn missing_accessflag_and_unknown_mair_slot_are_refused() { let geometry = geometry(); let code = page(geometry, 0x40000000, Memory::Normal, rx()).unwrap(); assert_eq!( decode(geometry, 3, code & !(1 << 10)), Err(Error::AccessFlag) ); assert_eq!( decode(geometry, 3, code | (2 << 2)), Err(Error::UnsupportedBits(2 << 2)) ); assert_eq!( decode(geometry, 3, code & !(3 << 8)), Err(Error::Shareability) ); } #[test] fn owned_table_cycle_and_foreign_table_stop_the_walk() { let geometry = geometry(); let mut tables = Tables { entries: BTreeMap::new(), owned: vec![0x40000000], pages: vec![], }; tables .entries .insert((0x40000000, 0), table(geometry, 0, 0x40000000).unwrap()); assert_eq!(walk(geometry, 0x40000000, 0, &tables), Err(Error::Cycle)); tables .entries .insert((0x40000000, 0), table(geometry, 0, 0x40001000).unwrap()); assert_eq!(walk(geometry, 0x40000000, 0, &tables), Err(Error::Backing)); }