//! Bounded proofs over every reachable state of a small frame table. //! Each harness starts from an arbitrary *well-formed* table (free count //! matches the number of empty slots) and checks one operation. use super::*; use types::address::{PhysicalAddress, VirtualAddress}; use types::id::{ProcessId, RegionId}; use types::mapping::PagePerms; const N: usize = 4; const BASE: usize = 0x8400_0000; fn any_perms() -> PagePerms { match kani::any::() % 3 { 0 => PagePerms::ReadOnly, 1 => PagePerms::ReadWrite, _ => PagePerms::ReadExecute, } } fn any_use() -> FrameUse { // Small id ranges keep owner collisions likely, which is where the // interesting cases are. let owner = ProcessId::from_raw(kani::any::() % 3); match kani::any::() % 3 { 0 => FrameUse::User { owner, page: kani::any::() % 4, perms: any_perms(), }, 1 => FrameUse::PageTable { owner }, _ => FrameUse::Region { region: RegionId::from_raw(kani::any::() % 2), }, } } fn any_slot() -> Option { if kani::any() { Some(any_use()) } else { None } } fn any_table() -> FrameTable { let frames = [any_slot(), any_slot(), any_slot(), any_slot()]; let free = frames.iter().filter(|f| f.is_none()).count(); FrameTable { base: PhysicalAddress::from_raw(BASE), total: N, frames, free, // Next-fit may resume anywhere, so the proofs start it anywhere. cursor: kani::any::() % (N + 1), } } fn well_formed(t: &FrameTable) -> bool { t.free == t.frames[..t.total].iter().filter(|f| f.is_none()).count() } fn any_pa() -> PhysicalAddress { // Anything from below the pool to past its end, aligned or not. PhysicalAddress::from_raw(BASE - 4096 + kani::any::() % ((N + 2) * 4096)) } #[kani::proof] #[kani::unwind(6)] fn alloc_only_hands_out_free_frames() { let mut t = any_table(); let before = t.frames; let usage = any_use(); match t.alloc(usage) { Some(pa) => { let idx = (pa.raw() - BASE) / PAGE_SIZE; assert!(idx < N); assert!(before[idx].is_none()); assert!(t.frames[idx] == Some(usage)); for i in 0..N { if i != idx { assert!(t.frames[i] == before[i]); } } } None => { assert!(before.iter().all(Option::is_some)); assert!(t.frames == before); } } assert!(well_formed(&t)); } #[kani::proof] #[kani::unwind(6)] fn free_releases_exactly_one_allocated_frame_or_nothing() { let mut t = any_table(); let before = t.frames; let pa = any_pa(); match t.free(pa) { Some(usage) => { let idx = (pa.raw() - BASE) / PAGE_SIZE; assert!(pa.raw() % PAGE_SIZE == 0); assert!(before[idx] == Some(usage)); assert!(t.frames[idx].is_none()); for i in 0..N { if i != idx { assert!(t.frames[i] == before[i]); } } } None => assert!(t.frames == before), } assert!(well_formed(&t)); } #[kani::proof] #[kani::unwind(6)] fn retag_never_allocates() { let mut t = any_table(); let before = t.frames; let pa = any_pa(); let usage = any_use(); let changed = t.retag(pa, usage); for i in 0..N { if before[i].is_none() { assert!(t.frames[i].is_none()); } } if !changed { assert!(t.frames == before); } assert!(well_formed(&t)); } #[kani::proof] #[kani::unwind(6)] fn free_owned_by_spares_everyone_else() { let mut t = any_table(); let before = t.frames; let owner = ProcessId::from_raw(kani::any::() % 3); t.free_owned_by(owner); for i in 0..N { let was_owned = matches!( before[i], Some(FrameUse::User { owner: o, .. } | FrameUse::PageTable { owner: o }) if o == owner ); if was_owned { assert!(t.frames[i].is_none()); } else { assert!(t.frames[i] == before[i]); } } assert!(well_formed(&t)); } #[kani::proof] #[kani::unwind(6)] fn find_user_only_reports_the_owners_mapping() { let t = any_table(); let owner = ProcessId::from_raw(kani::any::() % 3); let va = VirtualAddress::from_raw(kani::any::() % (5 * 4096)); if let Some((pa, perms)) = t.find_user(owner, va) { let idx = (pa.raw() - BASE) / PAGE_SIZE; assert!(idx < N); assert!(va.raw() % PAGE_SIZE == 0); assert!( t.frames[idx] == Some(FrameUse::User { owner, page: (va.raw() / PAGE_SIZE) as u32, perms, }) ); } } #[kani::proof] #[kani::unwind(6)] fn alloc_contiguous_only_claims_a_free_run() { let mut t = any_table(); let before = t.frames; let count = kani::any::() % (N + 2); let region = RegionId::from_raw(1); match t.alloc_contiguous(count, |_| FrameUse::Region { region }) { Some(pa) => { let start = (pa.raw() - BASE) / PAGE_SIZE; assert!(count > 0 && start + count <= N); for i in 0..N { if i >= start && i < start + count { assert!(before[i].is_none()); assert!(t.frames[i] == Some(FrameUse::Region { region })); } else { assert!(t.frames[i] == before[i]); } } } None => assert!(t.frames == before), } assert!(well_formed(&t)); }