feat(kernel): implement generation-based capability revocation with proofs and tests

This commit is contained in:
2026-10-18 01:11:24 +02:00
parent 71518a93a4
commit 192631d506
7 changed files with 225 additions and 3 deletions

View File

@@ -160,16 +160,53 @@ impl<P: Platform> Kernel<P> {
if !cap.permissions().allows(needed) {
return Err(KernelError::PermissionDenied { slot, needed });
}
let current_gen = self.resource_generation(cap.resource());
if !cap.generation().is_valid_against(&current_gen) {
return Err(KernelError::RevokedCapability(slot));
}
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(
&mut self,
to: ProcessId,
resource: Resource,
permissions: PermissionSet,
) -> 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)
}
}

View File

@@ -1,9 +1,10 @@
use domain::capability::{Capability, CapabilityTable};
use domain::endpoint::Endpoint;
use domain::resource::Resource;
use super::{arbitrary_endpoint_cap, insert_cap};
use types::generation::Generation;
use types::id::EndpointId;
use types::permission::PermissionSet;
use types::permission::{Permission, PermissionSet};
#[kani::proof]
fn revoke_invalidates_cap_slot() {
@@ -61,3 +62,77 @@ fn grant_cap_preserves_resource_identity() {
_ => 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(&current_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));
}

View File

@@ -1,8 +1,9 @@
use super::*;
use crate::{SyscallOutcome, SyscallResult};
use domain::resource::Resource;
use domain::syscall::Syscall;
use types::bounded::CapSlot;
use types::permission::PermissionSet;
use types::permission::{Permission, PermissionSet};
use types::address::DataRef;
#[test]
@@ -53,3 +54,92 @@ fn delegate_with_valid_cap_succeeds() {
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(_)));
}