Files
k-os/crates/domain/src/memory/frame/proofs.rs

198 lines
5.6 KiB
Rust

//! 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::<u8>() % 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::<u16>() % 3);
match kani::any::<u8>() % 3 {
0 => FrameUse::User {
owner,
page: kani::any::<u32>() % 4,
perms: any_perms(),
},
1 => FrameUse::PageTable { owner },
_ => FrameUse::Region {
region: RegionId::from_raw(kani::any::<u16>() % 2),
},
}
}
fn any_slot() -> Option<FrameUse> {
if kani::any() { Some(any_use()) } else { None }
}
fn any_table() -> FrameTable<N> {
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::<usize>() % (N + 1),
}
}
fn well_formed(t: &FrameTable<N>) -> 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::<usize>() % ((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::<u16>() % 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::<u16>() % 3);
let va = VirtualAddress::from_raw(kani::any::<usize>() % (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::<usize>() % (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));
}