Sprint 5 — Capabilities & IPC
The security model — unforgeable tokens and message passing.
🔲 Planned
Table of contents
Overview #
Sprint 5 implements the two defining features of MinimalOS: capability-based access control and IPC (Inter-Process Communication). Together, they replace traditional Unix permissions with a mathematically sound security model where every resource access requires an explicit, unforgeable token.
Capability System #
What is a Capability?
A capability is a kernel-managed token that represents the right to perform specific operations on a specific resource. Capabilities are:
- Unforgeable — only the kernel creates them; userspace cannot fabricate them
- Typed — each capability refers to a specific kind of resource
- Rights-bearing — capabilities carry a bitmask of permitted operations
- Transferable — processes can send capabilities to other processes via IPC
- Revocable — the creator can invalidate a capability
Capability Table
Each process has a capability table — an array of slots, each containing a capability or empty:
graph LR
subgraph CT["Process Capability Table"]
S0["Slot 0: IPC Endpoint\nserial_channel\nSend, Recv"]
S1["Slot 1: Memory\n0x1000-0x2000\nRead, Write"]
S2["Slot 2: Interrupt\nIRQ 4 COM1\nReceive"]
S3["Slot 3: empty"]
S4["Slot 4: Process\nPID 7\nSuspend, Kill"]
end
Syscalls reference capabilities by slot index: sys_ipc_send(slot=0, message).
Capability Types
| Type | Resource | Typical Rights |
|---|---|---|
| Memory | Physical page range | Read, Write, Execute, Grant |
| IPC Endpoint | Communication channel | Send, Receive, Grant |
| Interrupt | Hardware IRQ line | Receive, Mask |
| Process | Another process | Inspect, Suspend, Resume, Kill |
| Thread | A thread | Suspend, Resume, Set Priority |
Rights Bitmask
bitflags! {
pub struct CapRights: u32 {
const READ = 1 << 0; // Read data
const WRITE = 1 << 1; // Write / modify
const EXECUTE = 1 << 2; // Execute code
const GRANT = 1 << 3; // Transfer to another process
const REVOKE = 1 << 4; // Revoke derived capabilities
}
}
Capability Operations
| Syscall | Description |
|---|---|
sys_cap_create(type, resource, rights) | Kernel-only: create a new capability |
sys_cap_delete(slot) | Remove a capability from the table |
sys_cap_transfer(src_slot, dest_process, rights) | Send a capability (rights can only be reduced) |
sys_cap_revoke(slot) | Revoke all capabilities derived from this one |
sys_cap_inspect(slot) | Query type, resource, and rights |
Monotonic Rights Reduction
When transferring a capability, rights can only be reduced, never increased. This ensures that delegation cannot escalate privileges:
graph LR
A["Process A\nMemory 0x1000\nRead+Write+Grant"] -->|"transfer with reduced rights"| B["Process B\nMemory 0x1000\nRead only"]
IPC (Inter-Process Communication) #
Design Principles
- Synchronous — send blocks until the receiver accepts; receive blocks until a message arrives
- Capability-mediated — both sender and receiver must hold an IPC endpoint capability
- Zero-copy option — large data transfers use memory grant capabilities instead of copying bytes
- Small message optimization — short messages (≤ 64 bytes) are passed in registers, avoiding copies entirely
Message Format
pub struct IpcMessage {
// Inline data (passed in registers for small messages)
data: [u64; 8], // 64 bytes of inline data
data_len: usize, // Actual length used
// Optional capability transfers
caps: [CapSlot; 4], // Up to 4 capabilities transferred
cap_count: usize,
}
Communication Patterns
Send / Receive
Basic one-way message passing:
// Sender (process A)
sys_ipc_send(endpoint_slot, &message);
// Receiver (process B)
let msg = sys_ipc_recv(endpoint_slot);
Call / Reply (RPC)
Synchronous request-response, like a function call across processes:
// Client
let response = sys_ipc_call(server_endpoint, &request);
// Server
loop {
let (client_badge, request) = sys_ipc_recv(listen_endpoint);
let response = handle_request(request);
sys_ipc_reply(client_badge, &response);
}
The call operation atomically sends a message and blocks waiting for the reply, while temporarily granting the server a reply capability.
Notifications
Lightweight event signaling — non-blocking, no data transfer:
// Signal an event (non-blocking)
sys_ipc_notify(endpoint_slot, EVENT_DATA_READY);
// Wait for notifications (blocking)
let events = sys_ipc_wait(endpoint_slot);
Notifications are OR'd together if multiple arrive before the receiver checks — they never queue up or overflow.
Zero-Copy Memory Grants
For large data transfers (disk blocks, network packets), the sender shares physical pages directly:
- Sender creates a Memory capability for the relevant pages
- Sender transfers the capability via IPC to the receiver
- Receiver maps the pages into its own address space
- Both processes access the same physical memory — zero copies
Security Guarantees #
| Property | Mechanism |
|---|---|
| No ambient authority | All resources accessed via capability slots |
| Information hiding | Processes only see capabilities they were granted |
| Controlled delegation | Capabilities transfer with monotonically decreasing rights |
| Complete mediation | Every resource access goes through the kernel's capability check |
| Revocation | Creator can revoke derived capabilities instantly |
Dependencies #
- Requires: Sprint 4 (process/thread structures, scheduler)
- Enables: Sprint 6 (syscall dispatch needs capability validation), Sprint 7 (init process uses IPC for service registration)