feat: add Kani proofs for delegation non-escalation across types, domain, and kernel
This commit is contained in:
@@ -1,5 +1,8 @@
|
||||
#![no_std]
|
||||
|
||||
#[cfg(kani)]
|
||||
pub mod proofs;
|
||||
|
||||
pub mod capability;
|
||||
pub mod endpoint;
|
||||
pub mod error;
|
||||
|
||||
184
crates/domain/src/proofs.rs
Normal file
184
crates/domain/src/proofs.rs
Normal file
@@ -0,0 +1,184 @@
|
||||
#[cfg(kani)]
|
||||
mod kani_proofs {
|
||||
use crate::capability::{Capability, CapabilityTable};
|
||||
use crate::resource::Resource;
|
||||
use crate::thread::{BlockReason, Thread, ThreadState};
|
||||
use types::bounded::{CapSlot, Priority};
|
||||
use types::generation::Generation;
|
||||
use types::id::{CoreId, EndpointId, ProcessId, ThreadId};
|
||||
use types::permission::{Permission, PermissionSet};
|
||||
use types::time::TimeSlice;
|
||||
|
||||
// --- Capability non-escalation ---
|
||||
|
||||
#[kani::proof]
|
||||
fn delegation_via_cap_table_never_escalates() {
|
||||
let source_bits: u8 = kani::any();
|
||||
let restrict_bits: u8 = kani::any();
|
||||
let ep_id: u16 = kani::any();
|
||||
kani::assume(ep_id < 256);
|
||||
|
||||
let source_perms = PermissionSet::from_bits(source_bits);
|
||||
kani::assume(source_perms.allows(Permission::Delegate));
|
||||
|
||||
let resource = Resource::Endpoint(EndpointId::from_raw(ep_id));
|
||||
let source_cap = Capability::new(resource.clone(), source_perms, Generation::initial());
|
||||
|
||||
let mut source_table = CapabilityTable::new();
|
||||
let slot = source_table.insert(source_cap);
|
||||
kani::assume(slot.is_ok());
|
||||
let slot = slot.unwrap();
|
||||
|
||||
let cap = source_table.get(slot).unwrap();
|
||||
let original_perms = *cap.permissions();
|
||||
let delegated_perms = original_perms.restrict_to(&PermissionSet::from_bits(restrict_bits));
|
||||
|
||||
let mut target_table = CapabilityTable::new();
|
||||
let delegated_cap = Capability::new(cap.resource().clone(), delegated_perms, Generation::initial());
|
||||
let target_slot = target_table.insert(delegated_cap);
|
||||
kani::assume(target_slot.is_ok());
|
||||
|
||||
let result = target_table.get(target_slot.unwrap()).unwrap();
|
||||
assert!(result.permissions().is_subset_of(&original_perms));
|
||||
}
|
||||
|
||||
#[kani::proof]
|
||||
fn delegation_cannot_add_permissions_not_in_source() {
|
||||
let source_bits: u8 = kani::any();
|
||||
let restrict_bits: u8 = kani::any();
|
||||
|
||||
let source = PermissionSet::from_bits(source_bits);
|
||||
let restrict = PermissionSet::from_bits(restrict_bits);
|
||||
let result = source.restrict_to(&restrict);
|
||||
|
||||
if !source.allows(Permission::Read) {
|
||||
assert!(!result.allows(Permission::Read));
|
||||
}
|
||||
if !source.allows(Permission::Write) {
|
||||
assert!(!result.allows(Permission::Write));
|
||||
}
|
||||
if !source.allows(Permission::Execute) {
|
||||
assert!(!result.allows(Permission::Execute));
|
||||
}
|
||||
if !source.allows(Permission::Delegate) {
|
||||
assert!(!result.allows(Permission::Delegate));
|
||||
}
|
||||
if !source.allows(Permission::Revoke) {
|
||||
assert!(!result.allows(Permission::Revoke));
|
||||
}
|
||||
}
|
||||
|
||||
// --- Capability table integrity ---
|
||||
|
||||
#[kani::proof]
|
||||
fn cap_table_insert_get_roundtrip() {
|
||||
let bits: u8 = kani::any();
|
||||
let ep_id: u16 = kani::any();
|
||||
kani::assume(ep_id < 256);
|
||||
|
||||
let perms = PermissionSet::from_bits(bits);
|
||||
let resource = Resource::Endpoint(EndpointId::from_raw(ep_id));
|
||||
let cap = Capability::new(resource, perms, Generation::initial());
|
||||
|
||||
let mut table = CapabilityTable::new();
|
||||
let slot = table.insert(cap);
|
||||
kani::assume(slot.is_ok());
|
||||
let slot = slot.unwrap();
|
||||
|
||||
let retrieved = table.get(slot).unwrap();
|
||||
assert!(*retrieved.permissions() == perms);
|
||||
}
|
||||
|
||||
#[kani::proof]
|
||||
fn cap_table_remove_then_get_fails() {
|
||||
let bits: u8 = kani::any();
|
||||
|
||||
let cap = Capability::new(
|
||||
Resource::ProcessCreate,
|
||||
PermissionSet::from_bits(bits),
|
||||
Generation::initial(),
|
||||
);
|
||||
|
||||
let mut table = CapabilityTable::new();
|
||||
let slot = table.insert(cap);
|
||||
kani::assume(slot.is_ok());
|
||||
let slot = slot.unwrap();
|
||||
|
||||
table.remove(slot).unwrap();
|
||||
assert!(table.get(slot).is_err());
|
||||
}
|
||||
|
||||
// --- Thread state machine safety ---
|
||||
|
||||
#[kani::proof]
|
||||
fn dead_thread_rejects_all_transitions() {
|
||||
let mut thread = Thread::new(
|
||||
ThreadId::from_raw(0),
|
||||
ProcessId::from_raw(0),
|
||||
Priority::new(16).unwrap(),
|
||||
TimeSlice::new(100),
|
||||
None,
|
||||
);
|
||||
|
||||
thread.set_running(CoreId::from_raw(0)).unwrap();
|
||||
thread.exit().unwrap();
|
||||
|
||||
assert!(matches!(thread.state(), ThreadState::Dead));
|
||||
assert!(thread.set_running(CoreId::from_raw(0)).is_err());
|
||||
assert!(thread.block(BlockReason::WaitingAny).is_err());
|
||||
assert!(thread.unblock().is_err());
|
||||
assert!(thread.preempt().is_err());
|
||||
assert!(thread.exit().is_err());
|
||||
}
|
||||
|
||||
#[kani::proof]
|
||||
fn only_running_thread_can_block() {
|
||||
let prio_val: u8 = kani::any();
|
||||
kani::assume(prio_val <= 31);
|
||||
let prio = Priority::new(prio_val).unwrap();
|
||||
|
||||
let thread = Thread::new(
|
||||
ThreadId::from_raw(0),
|
||||
ProcessId::from_raw(0),
|
||||
prio,
|
||||
TimeSlice::new(100),
|
||||
None,
|
||||
);
|
||||
|
||||
assert!(matches!(thread.state(), ThreadState::Ready));
|
||||
assert!(thread.block(BlockReason::WaitingAny).is_err());
|
||||
}
|
||||
|
||||
#[kani::proof]
|
||||
fn blocked_thread_cannot_run_directly() {
|
||||
let mut thread = Thread::new(
|
||||
ThreadId::from_raw(0),
|
||||
ProcessId::from_raw(0),
|
||||
Priority::new(16).unwrap(),
|
||||
TimeSlice::new(100),
|
||||
None,
|
||||
);
|
||||
|
||||
thread.set_running(CoreId::from_raw(0)).unwrap();
|
||||
thread.block(BlockReason::WaitingAny).unwrap();
|
||||
|
||||
assert!(thread.set_running(CoreId::from_raw(0)).is_err());
|
||||
}
|
||||
|
||||
// --- Chained delegation ---
|
||||
|
||||
#[kani::proof]
|
||||
fn chained_delegation_never_escalates() {
|
||||
let original_bits: u8 = kani::any();
|
||||
let restrict1_bits: u8 = kani::any();
|
||||
let restrict2_bits: u8 = kani::any();
|
||||
|
||||
let original = PermissionSet::from_bits(original_bits);
|
||||
let after_first = original.restrict_to(&PermissionSet::from_bits(restrict1_bits));
|
||||
let after_second = after_first.restrict_to(&PermissionSet::from_bits(restrict2_bits));
|
||||
|
||||
assert!(after_first.is_subset_of(&original));
|
||||
assert!(after_second.is_subset_of(&after_first));
|
||||
assert!(after_second.is_subset_of(&original));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user