From ae6023081a82909127f365efa2ee550abe94b54e Mon Sep 17 00:00:00 2001 From: Gabriel Kaszewski Date: Sun, 18 Oct 2026 00:22:37 +0200 Subject: [PATCH] feat(kernel): add Delegate syscall for capability delegation --- crates/kernel/src/lib.rs | 15 ++++++++ crates/kernel/src/syscall.rs | 44 +++++++++++++++++++++ crates/kernel/src/tests/capability.rs | 55 +++++++++++++++++++++++++++ crates/kernel/src/tests/mod.rs | 1 + 4 files changed, 115 insertions(+) create mode 100644 crates/kernel/src/tests/capability.rs diff --git a/crates/kernel/src/lib.rs b/crates/kernel/src/lib.rs index 707377c..76237b6 100644 --- a/crates/kernel/src/lib.rs +++ b/crates/kernel/src/lib.rs @@ -193,4 +193,19 @@ impl Kernel

{ } self.scheduler.remove(tid); } + + #[cfg(test)] + pub fn create_endpoint_for_test(&mut self) -> types::id::EndpointId { + self.endpoints.create().expect("create endpoint") + } + + #[cfg(test)] + pub fn grant_cap_for_test( + &mut self, + pid: ProcessId, + resource: Resource, + permissions: PermissionSet, + ) -> CapSlot { + self.grant_cap(pid, resource, permissions).expect("grant cap") + } } diff --git a/crates/kernel/src/syscall.rs b/crates/kernel/src/syscall.rs index 678bf6d..08bd83b 100644 --- a/crates/kernel/src/syscall.rs +++ b/crates/kernel/src/syscall.rs @@ -21,6 +21,9 @@ impl Kernel

{ Syscall::SpawnThread { entry, stack } => self.handle_spawn_thread(pid, entry, stack), Syscall::CreateProcess { create_cap, image } => self.handle_create_process(pid, create_cap, image), Syscall::Kill { process } => self.handle_kill(pid, process), + Syscall::Delegate { slot, to_endpoint, permissions } => { + self.handle_delegate(pid, slot, to_endpoint, permissions) + } _ => SyscallOutcome::Complete(SyscallResult::Error( domain::error::KernelError::WouldBlock, )), @@ -321,4 +324,45 @@ impl Kernel

{ self.process_memory[target_pid.raw() as usize] = None; SyscallOutcome::Complete(SyscallResult::Ok) } + + fn handle_delegate( + &mut self, + caller_pid: types::id::ProcessId, + slot: types::bounded::CapSlot, + to_endpoint: types::bounded::CapSlot, + restrict: types::permission::PermissionSet, + ) -> SyscallOutcome { + let (resource, permissions) = match self.check_cap( + caller_pid, + slot, + types::permission::Permission::Delegate, + ) { + Ok(cap) => (cap.resource().clone(), *cap.permissions()), + Err(e) => return SyscallOutcome::Complete(SyscallResult::Error(e)), + }; + + let restricted = permissions.restrict_to(&restrict); + + let target_pid = match self.check_cap( + caller_pid, + to_endpoint, + types::permission::Permission::Execute, + ) { + Ok(cap) => match cap.resource() { + domain::resource::Resource::Process(pid) => *pid, + _ => return SyscallOutcome::Complete(SyscallResult::Error( + domain::error::KernelError::PermissionDenied { + slot: to_endpoint, + needed: types::permission::Permission::Execute, + }, + )), + }, + Err(e) => return SyscallOutcome::Complete(SyscallResult::Error(e)), + }; + + match self.grant_cap(target_pid, resource, restricted) { + Ok(_) => SyscallOutcome::Complete(SyscallResult::Ok), + Err(e) => SyscallOutcome::Complete(SyscallResult::Error(e)), + } + } } diff --git a/crates/kernel/src/tests/capability.rs b/crates/kernel/src/tests/capability.rs new file mode 100644 index 0000000..397bb8a --- /dev/null +++ b/crates/kernel/src/tests/capability.rs @@ -0,0 +1,55 @@ +use super::*; +use crate::{SyscallOutcome, SyscallResult}; +use domain::syscall::Syscall; +use types::bounded::CapSlot; +use types::permission::PermissionSet; +use types::address::DataRef; + +#[test] +fn delegate_without_delegate_permission_fails() { + let (mut k, _) = kernel_with_process(); + // slot 0 has ProcessCreate cap — no Delegate permission + let outcome = k.handle_syscall(Syscall::Delegate { + slot: CapSlot::new(0).unwrap(), + to_endpoint: CapSlot::new(1).unwrap(), + permissions: PermissionSet::empty(), + }); + assert!(matches!( + outcome, + SyscallOutcome::Complete(SyscallResult::Error(_)) + )); +} + +#[test] +fn delegate_with_valid_cap_succeeds() { + let (mut k, pid) = kernel_with_process(); + + // Create a second process to delegate to + let target_slot = match k.handle_syscall(Syscall::CreateProcess { + create_cap: CapSlot::new(0).unwrap(), + image: DataRef::empty(), + }) { + SyscallOutcome::Complete(SyscallResult::Slot(s)) => s, + _ => panic!("expected slot"), + }; + + // Grant a delegatable cap to the caller (endpoint cap with Delegate perm) + let ep_id = k.create_endpoint_for_test(); + let source_slot = k.grant_cap_for_test( + pid, + domain::resource::Resource::Endpoint(ep_id), + PermissionSet::empty() + .with(types::permission::Permission::Read) + .with(types::permission::Permission::Delegate), + ); + + let outcome = k.handle_syscall(Syscall::Delegate { + slot: source_slot, + to_endpoint: target_slot, // using process cap as target identifier + permissions: PermissionSet::empty().with(types::permission::Permission::Read), + }); + assert!(matches!( + outcome, + SyscallOutcome::Complete(SyscallResult::Ok) + )); +} diff --git a/crates/kernel/src/tests/mod.rs b/crates/kernel/src/tests/mod.rs index e0a1f1a..e7030e4 100644 --- a/crates/kernel/src/tests/mod.rs +++ b/crates/kernel/src/tests/mod.rs @@ -1,3 +1,4 @@ +mod capability; mod exit; mod lifecycle; mod memory;