feat(kernel): implement generation-based capability revocation with proofs and tests
This commit is contained in:
@@ -58,6 +58,10 @@ impl MemoryRegion {
|
|||||||
pub fn generation(&self) -> &Generation {
|
pub fn generation(&self) -> &Generation {
|
||||||
&self.generation
|
&self.generation
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn revoke(&mut self) {
|
||||||
|
self.generation = self.generation.next();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct RegionTable {
|
pub struct RegionTable {
|
||||||
@@ -82,6 +86,10 @@ impl RegionTable {
|
|||||||
self.regions.get(id.raw() as usize).and_then(|s| s.as_ref())
|
self.regions.get(id.raw() as usize).and_then(|s| s.as_ref())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_mut(&mut self, id: RegionId) -> Option<&mut MemoryRegion> {
|
||||||
|
self.regions.get_mut(id.raw() as usize).and_then(|s| s.as_mut())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn remove(&mut self, id: RegionId) -> Option<MemoryRegion> {
|
pub fn remove(&mut self, id: RegionId) -> Option<MemoryRegion> {
|
||||||
self.regions.get_mut(id.raw() as usize).and_then(|s| s.take())
|
self.regions.get_mut(id.raw() as usize).and_then(|s| s.take())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -160,16 +160,53 @@ impl<P: Platform> Kernel<P> {
|
|||||||
if !cap.permissions().allows(needed) {
|
if !cap.permissions().allows(needed) {
|
||||||
return Err(KernelError::PermissionDenied { slot, needed });
|
return Err(KernelError::PermissionDenied { slot, needed });
|
||||||
}
|
}
|
||||||
|
let current_gen = self.resource_generation(cap.resource());
|
||||||
|
if !cap.generation().is_valid_against(¤t_gen) {
|
||||||
|
return Err(KernelError::RevokedCapability(slot));
|
||||||
|
}
|
||||||
Ok(cap)
|
Ok(cap)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn resource_generation(&self, resource: &Resource) -> Generation {
|
||||||
|
match resource {
|
||||||
|
Resource::Endpoint(id) => self
|
||||||
|
.endpoints
|
||||||
|
.get(*id)
|
||||||
|
.map(|ep| *ep.generation())
|
||||||
|
.unwrap_or_else(|| Generation::initial()),
|
||||||
|
Resource::Memory(id) => self
|
||||||
|
.regions
|
||||||
|
.get(*id)
|
||||||
|
.map(|r| *r.generation())
|
||||||
|
.unwrap_or_else(|| Generation::initial()),
|
||||||
|
_ => Generation::initial(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn revoke_resource(&mut self, resource: &Resource) {
|
||||||
|
match resource {
|
||||||
|
Resource::Endpoint(id) => {
|
||||||
|
if let Some(ep) = self.endpoints.get_mut(*id) {
|
||||||
|
ep.revoke();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Resource::Memory(id) => {
|
||||||
|
if let Some(r) = self.regions.get_mut(*id) {
|
||||||
|
r.revoke();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn grant_cap(
|
fn grant_cap(
|
||||||
&mut self,
|
&mut self,
|
||||||
to: ProcessId,
|
to: ProcessId,
|
||||||
resource: Resource,
|
resource: Resource,
|
||||||
permissions: PermissionSet,
|
permissions: PermissionSet,
|
||||||
) -> Result<CapSlot, KernelError> {
|
) -> Result<CapSlot, KernelError> {
|
||||||
let cap = Capability::new(resource, permissions, Generation::initial());
|
let generation = self.resource_generation(&resource);
|
||||||
|
let cap = Capability::new(resource, permissions, generation);
|
||||||
self.processes.get_mut(to)?.capabilities_mut().insert(cap)
|
self.processes.get_mut(to)?.capabilities_mut().insert(cap)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
use domain::capability::{Capability, CapabilityTable};
|
use domain::capability::{Capability, CapabilityTable};
|
||||||
|
use domain::endpoint::Endpoint;
|
||||||
use domain::resource::Resource;
|
use domain::resource::Resource;
|
||||||
use super::{arbitrary_endpoint_cap, insert_cap};
|
use super::{arbitrary_endpoint_cap, insert_cap};
|
||||||
use types::generation::Generation;
|
use types::generation::Generation;
|
||||||
use types::id::EndpointId;
|
use types::id::EndpointId;
|
||||||
use types::permission::PermissionSet;
|
use types::permission::{Permission, PermissionSet};
|
||||||
|
|
||||||
#[kani::proof]
|
#[kani::proof]
|
||||||
fn revoke_invalidates_cap_slot() {
|
fn revoke_invalidates_cap_slot() {
|
||||||
@@ -61,3 +62,77 @@ fn grant_cap_preserves_resource_identity() {
|
|||||||
_ => panic!("resource type changed"),
|
_ => panic!("resource type changed"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[kani::proof]
|
||||||
|
fn generation_revocation_invalidates_all_caps() {
|
||||||
|
let bits: u8 = kani::any();
|
||||||
|
let ep_id: u16 = kani::any();
|
||||||
|
kani::assume(ep_id < 256);
|
||||||
|
|
||||||
|
let mut endpoint = Endpoint::new(EndpointId::from_raw(ep_id));
|
||||||
|
let original_gen = *endpoint.generation();
|
||||||
|
|
||||||
|
let cap = Capability::new(
|
||||||
|
Resource::Endpoint(EndpointId::from_raw(ep_id)),
|
||||||
|
PermissionSet::from_bits(bits),
|
||||||
|
original_gen,
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut table = CapabilityTable::new();
|
||||||
|
let slot = insert_cap(&mut table, cap);
|
||||||
|
|
||||||
|
assert!(table.get(slot).unwrap().generation().is_valid_against(&original_gen));
|
||||||
|
|
||||||
|
endpoint.revoke();
|
||||||
|
let new_gen = *endpoint.generation();
|
||||||
|
|
||||||
|
assert!(!table.get(slot).unwrap().generation().is_valid_against(&new_gen));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[kani::proof]
|
||||||
|
fn cap_created_after_revoke_is_valid() {
|
||||||
|
let ep_id: u16 = kani::any();
|
||||||
|
kani::assume(ep_id < 256);
|
||||||
|
let bits: u8 = kani::any();
|
||||||
|
|
||||||
|
let mut endpoint = Endpoint::new(EndpointId::from_raw(ep_id));
|
||||||
|
endpoint.revoke();
|
||||||
|
let current_gen = *endpoint.generation();
|
||||||
|
|
||||||
|
let cap = Capability::new(
|
||||||
|
Resource::Endpoint(EndpointId::from_raw(ep_id)),
|
||||||
|
PermissionSet::from_bits(bits),
|
||||||
|
current_gen,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(cap.generation().is_valid_against(¤t_gen));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[kani::proof]
|
||||||
|
fn multiple_revocations_invalidate_all_prior_caps() {
|
||||||
|
let ep_id: u16 = kani::any();
|
||||||
|
kani::assume(ep_id < 256);
|
||||||
|
|
||||||
|
let mut endpoint = Endpoint::new(EndpointId::from_raw(ep_id));
|
||||||
|
|
||||||
|
let gen0 = *endpoint.generation();
|
||||||
|
let cap0 = Capability::new(
|
||||||
|
Resource::Endpoint(EndpointId::from_raw(ep_id)),
|
||||||
|
PermissionSet::empty().with(Permission::Read),
|
||||||
|
gen0,
|
||||||
|
);
|
||||||
|
|
||||||
|
endpoint.revoke();
|
||||||
|
let gen1 = *endpoint.generation();
|
||||||
|
let cap1 = Capability::new(
|
||||||
|
Resource::Endpoint(EndpointId::from_raw(ep_id)),
|
||||||
|
PermissionSet::empty().with(Permission::Read),
|
||||||
|
gen1,
|
||||||
|
);
|
||||||
|
|
||||||
|
endpoint.revoke();
|
||||||
|
let gen2 = *endpoint.generation();
|
||||||
|
|
||||||
|
assert!(!cap0.generation().is_valid_against(&gen2));
|
||||||
|
assert!(!cap1.generation().is_valid_against(&gen2));
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
use super::*;
|
use super::*;
|
||||||
use crate::{SyscallOutcome, SyscallResult};
|
use crate::{SyscallOutcome, SyscallResult};
|
||||||
|
use domain::resource::Resource;
|
||||||
use domain::syscall::Syscall;
|
use domain::syscall::Syscall;
|
||||||
use types::bounded::CapSlot;
|
use types::bounded::CapSlot;
|
||||||
use types::permission::PermissionSet;
|
use types::permission::{Permission, PermissionSet};
|
||||||
use types::address::DataRef;
|
use types::address::DataRef;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -53,3 +54,92 @@ fn delegate_with_valid_cap_succeeds() {
|
|||||||
SyscallOutcome::Complete(SyscallResult::Ok)
|
SyscallOutcome::Complete(SyscallResult::Ok)
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Generation-based revocation ---
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn revoke_resource_invalidates_all_caps() {
|
||||||
|
let (mut k, pid) = kernel_with_process();
|
||||||
|
let ep_id = k.create_endpoint_for_test();
|
||||||
|
|
||||||
|
let cap_slot = k.grant_cap_for_test(
|
||||||
|
pid,
|
||||||
|
Resource::Endpoint(ep_id),
|
||||||
|
PermissionSet::empty().with(Permission::Read),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Revoke the endpoint
|
||||||
|
k.revoke_resource(&Resource::Endpoint(ep_id));
|
||||||
|
|
||||||
|
// Cap is now invalid — check_cap should reject with RevokedCapability
|
||||||
|
let outcome = k.handle_syscall(Syscall::Receive { endpoint: cap_slot });
|
||||||
|
assert!(matches!(
|
||||||
|
outcome,
|
||||||
|
SyscallOutcome::Complete(SyscallResult::Error(domain::error::KernelError::RevokedCapability(_)))
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn new_cap_after_revoke_uses_current_generation() {
|
||||||
|
let (mut k, pid) = kernel_with_process();
|
||||||
|
let ep_id = k.create_endpoint_for_test();
|
||||||
|
|
||||||
|
// Grant, revoke, grant again
|
||||||
|
let old_slot = k.grant_cap_for_test(
|
||||||
|
pid,
|
||||||
|
Resource::Endpoint(ep_id),
|
||||||
|
PermissionSet::empty().with(Permission::Read),
|
||||||
|
);
|
||||||
|
|
||||||
|
k.revoke_resource(&Resource::Endpoint(ep_id));
|
||||||
|
|
||||||
|
// Old cap is dead
|
||||||
|
let outcome = k.handle_syscall(Syscall::Receive { endpoint: old_slot });
|
||||||
|
assert!(matches!(
|
||||||
|
outcome,
|
||||||
|
SyscallOutcome::Complete(SyscallResult::Error(domain::error::KernelError::RevokedCapability(_)))
|
||||||
|
));
|
||||||
|
|
||||||
|
// New cap with current generation works
|
||||||
|
let new_slot = k.grant_cap_for_test(
|
||||||
|
pid,
|
||||||
|
Resource::Endpoint(ep_id),
|
||||||
|
PermissionSet::empty().with(Permission::Read),
|
||||||
|
);
|
||||||
|
|
||||||
|
let outcome = k.handle_syscall(Syscall::Receive { endpoint: new_slot });
|
||||||
|
// Should block (no sender), not error — proving the cap is valid
|
||||||
|
assert!(matches!(outcome, SyscallOutcome::BlockCaller(_)));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn revoke_does_not_affect_other_resources() {
|
||||||
|
let (mut k, pid) = kernel_with_process();
|
||||||
|
let ep1 = k.create_endpoint_for_test();
|
||||||
|
let ep2 = k.create_endpoint_for_test();
|
||||||
|
|
||||||
|
let slot1 = k.grant_cap_for_test(
|
||||||
|
pid,
|
||||||
|
Resource::Endpoint(ep1),
|
||||||
|
PermissionSet::empty().with(Permission::Read),
|
||||||
|
);
|
||||||
|
let slot2 = k.grant_cap_for_test(
|
||||||
|
pid,
|
||||||
|
Resource::Endpoint(ep2),
|
||||||
|
PermissionSet::empty().with(Permission::Read),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Revoke only ep1
|
||||||
|
k.revoke_resource(&Resource::Endpoint(ep1));
|
||||||
|
|
||||||
|
// ep1 cap is revoked
|
||||||
|
let outcome = k.handle_syscall(Syscall::Receive { endpoint: slot1 });
|
||||||
|
assert!(matches!(
|
||||||
|
outcome,
|
||||||
|
SyscallOutcome::Complete(SyscallResult::Error(domain::error::KernelError::RevokedCapability(_)))
|
||||||
|
));
|
||||||
|
|
||||||
|
// ep2 cap still works
|
||||||
|
let outcome = k.handle_syscall(Syscall::Receive { endpoint: slot2 });
|
||||||
|
assert!(matches!(outcome, SyscallOutcome::BlockCaller(_)));
|
||||||
|
}
|
||||||
|
|||||||
@@ -13,4 +13,9 @@ impl Generation {
|
|||||||
pub fn is_valid_against(&self, current: &Generation) -> bool {
|
pub fn is_valid_against(&self, current: &Generation) -> bool {
|
||||||
self == current
|
self == current
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(kani)]
|
||||||
|
pub fn from_raw(value: u32) -> Self {
|
||||||
|
Self(value)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,4 +6,6 @@ pub mod generation;
|
|||||||
pub mod id;
|
pub mod id;
|
||||||
pub mod macros;
|
pub mod macros;
|
||||||
pub mod permission;
|
pub mod permission;
|
||||||
|
#[cfg(kani)]
|
||||||
|
pub mod proofs;
|
||||||
pub mod time;
|
pub mod time;
|
||||||
|
|||||||
@@ -40,4 +40,9 @@ impl PermissionSet {
|
|||||||
pub fn is_subset_of(&self, other: &PermissionSet) -> bool {
|
pub fn is_subset_of(&self, other: &PermissionSet) -> bool {
|
||||||
self.0 & other.0 == self.0
|
self.0 & other.0 == self.0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(kani)]
|
||||||
|
pub fn from_bits(bits: u8) -> Self {
|
||||||
|
PermissionSet(bits)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user