feat(kernel): derivation-tree revocation, region caps

- caps record parent (mint/delegate/ipc), CapRef w/ pid+slot gens
- Revoke = delete subtree, keep self; drop/death re-parents children
- CreateRegion/MapRegion; mapping lives only as long as its cap
- drop Permission::Revoke + object-wide revoke
- mint any cap (badge only ep/ntfn), process caps delegable
- kani: descendants == subtree; model-based fuzz vs spec
This commit is contained in:
2026-10-26 20:41:03 +01:00
parent 76f10ff1f1
commit 154599e3af
32 changed files with 1302 additions and 239 deletions

View File

@@ -42,10 +42,8 @@ impl<P: Platform> Kernel<P> {
Err(e) => return SyscallOutcome::Complete(SyscallResult::Error(e)),
};
let inserted = self
.processes
.get_mut(target_pid)
.and_then(|p| p.capabilities_mut().insert(copy));
let parent = self.cap_ref(caller_pid, slot);
let inserted = self.insert_derived(target_pid, copy, parent);
if let Err(e) = inserted {
return SyscallOutcome::Complete(SyscallResult::Error(e));
}
@@ -58,14 +56,10 @@ impl<P: Platform> Kernel<P> {
caller_pid: types::id::ProcessId,
slot: types::bounded::CapSlot,
) -> SyscallOutcome {
let resource = match self.check_cap(caller_pid, slot, types::permission::Permission::Revoke)
{
Ok(cap) => cap.resource().clone(),
Err(e) => return SyscallOutcome::Complete(SyscallResult::Error(e)),
};
self.revoke_resource(&resource);
SyscallOutcome::Complete(SyscallResult::Ok)
SyscallOutcome::Complete(match self.revoke_subtree(caller_pid, slot) {
Ok(()) => SyscallResult::Ok,
Err(e) => SyscallResult::Error(e),
})
}
pub(crate) fn handle_drop_cap(
@@ -73,11 +67,7 @@ impl<P: Platform> Kernel<P> {
caller_pid: types::id::ProcessId,
slot: types::bounded::CapSlot,
) -> SyscallOutcome {
let result = self
.processes
.get_mut(caller_pid)
.and_then(|p| p.capabilities_mut().remove(slot));
SyscallOutcome::Complete(match result {
SyscallOutcome::Complete(match self.delete_cap(caller_pid, slot) {
Ok(()) => SyscallResult::Ok,
Err(e) => SyscallResult::Error(e),
})

View File

@@ -150,12 +150,8 @@ impl<P: Platform> Kernel<P> {
.check_cap(from, slot, Permission::Delegate)
.ok()?
.clone();
self.processes
.get_mut(to)
.ok()?
.capabilities_mut()
.insert(copy)
.ok()
let parent = self.cap_ref(from, slot);
self.insert_derived(to, copy, parent).ok()
}
/// Hands `out` from `sender` to `receiver`. For a call, the receiver's
@@ -361,13 +357,13 @@ impl<P: Platform> Kernel<P> {
permissions: PermissionSet,
) -> Result<CapSlot, KernelError> {
let source = self.check_cap(pid, slot, Permission::Delegate)?;
if !matches!(
let badgeable = matches!(
source.resource(),
Resource::Endpoint(_) | Resource::Notification(_)
) {
return Err(KernelError::WrongObject);
}
if !source.badge().is_none() {
);
// Any capability can be copied with narrower rights; only those a
// receiver can observe the badge of take one, and only once.
if (!badgeable && !badge.is_none()) || !source.badge().is_none() {
return Err(KernelError::InvalidArgument);
}
let minted = Capability::new(
@@ -376,10 +372,8 @@ impl<P: Platform> Kernel<P> {
*source.generation(),
)
.with_badge(badge);
self.processes
.get_mut(pid)?
.capabilities_mut()
.insert(minted)
let parent = self.cap_ref(pid, slot);
self.insert_derived(pid, minted, parent)
}
pub(crate) fn handle_create_endpoint(&mut self, pid: ProcessId) -> SyscallOutcome {

View File

@@ -65,9 +65,12 @@ impl<P: Platform> Kernel<P> {
match self.grant_cap(
caller_pid,
domain::resource::Resource::Process(new_pid),
// The creator has full authority over what it creates,
// including handing that authority on.
types::permission::PermissionSet::empty()
.with(types::permission::Permission::Execute)
.with(types::permission::Permission::Read),
.with(types::permission::Permission::Read)
.with(types::permission::Permission::Delegate),
) {
Ok(slot) => SyscallOutcome::Complete(SyscallResult::Slot(slot)),
Err(e) => {

View File

@@ -75,4 +75,44 @@ impl<P: Platform> Kernel<P> {
.map(|()| SyscallResult::Ok),
)
}
pub(crate) fn create_region_cap(
&mut self,
pid: ProcessId,
pages: PageCount,
) -> Result<CapSlot, KernelError> {
let id = self.create_pool_region(pid, pages)?;
let all = types::permission::PermissionSet::empty()
.with(Permission::Read)
.with(Permission::Write)
.with(Permission::Execute)
.with(Permission::Delegate);
self.grant_cap(pid, Resource::Memory(id), all)
}
/// Maps the region behind `slot` into the caller. The capability must
/// allow every access `perms` grants; the mapping lasts as long as
/// the capability.
pub(crate) fn map_region_cap(
&mut self,
pid: ProcessId,
slot: CapSlot,
va: VirtualAddress,
perms: PagePerms,
) -> Result<(), KernelError> {
let cap = self.check_cap(pid, slot, Permission::Read)?;
let needed = match perms {
PagePerms::ReadOnly => Permission::Read,
PagePerms::ReadWrite => Permission::Write,
PagePerms::ReadExecute => Permission::Execute,
};
if !cap.permissions().allows(needed) {
return Err(KernelError::PermissionDenied { slot, needed });
}
let Resource::Memory(id) = *cap.resource() else {
return Err(KernelError::WrongObject);
};
let via = self.cap_ref(pid, slot);
self.map_region_via(id, pid, va, perms, via)
}
}

View File

@@ -116,6 +116,13 @@ impl<P: Platform> Kernel<P> {
Syscall::Kill { process } => self.handle_kill(pid, process),
Syscall::Revoke { slot } => self.handle_revoke(pid, slot),
Syscall::DropCap { slot } => self.handle_drop_cap(pid, slot),
Syscall::CreateRegion { pages } => {
complete(self.create_region_cap(pid, pages).map(SyscallResult::Slot))
}
Syscall::MapRegion { region, va, perms } => complete(
self.map_region_cap(pid, region, va, perms)
.map(|()| SyscallResult::Ok),
),
Syscall::Delegate {
slot,
to_endpoint,