feat(storage): scoped grants (subtree, ro/rw), narrow-only, release revokes all holders; IPC KEEP_DELEGATE opt-in; shell cd/pwd, programs get ro cwd

This commit is contained in:
2026-10-27 20:20:34 +01:00
parent c603a578c0
commit 1fc5a54f06
38 changed files with 1237 additions and 170 deletions

View File

@@ -15,6 +15,24 @@ fn optional_slots_roundtrip() {
assert!(reg_slot(slot_reg(s)) == Some(s));
}
#[kani::proof]
fn gives_roundtrip() {
let g = any_slot().map(|slot| Give {
slot,
keep_delegate: kani::any(),
});
assert!(reg_give(give_reg(g)) == Some(g));
}
/// The flag never turns one slot into another.
#[kani::proof]
fn a_give_register_names_the_slot_it_carries() {
let r: usize = kani::any();
if let Some(Some(g)) = reg_give(r) {
assert!(g.slot.value() as usize == r & !KEEP_DELEGATE);
}
}
#[kani::proof]
fn every_register_decodes_to_none_or_a_valid_slot() {
let r: usize = kani::any();

View File

@@ -27,6 +27,12 @@ pub mod op {
pub const MKDIR: usize = 9;
/// Carries the log endpoint; only from the unbadged holder (init).
pub const SET_LOG: usize = 10;
/// A capability to the path in the buffer, inside the caller's scope;
/// word 2 non-zero asks for write (only if the caller has it).
pub const GRANT: usize = 11;
/// Takes back the grant this is called through, and everything
/// granted from it, from every holder.
pub const RELEASE: usize = 12;
}
/// Reply tags (word 0).
@@ -42,4 +48,8 @@ pub mod reply {
pub const ERROR: usize = 8;
pub const READ_ONLY: usize = 9;
pub const NOT_ATTACHED: usize = 10;
/// Carries the new capability.
pub const GRANTED: usize = 11;
/// Outside the caller's scope, beyond its rights, or no grant at all.
pub const DENIED: usize = 12;
}

View File

@@ -52,6 +52,10 @@ pub const PERMS_READ_EXECUTE: usize = 2;
/// "No capability" in a capability argument or result register.
pub const NONE: usize = usize::MAX;
/// Set in a message's capability register: the receiver may pass the
/// capability on (it keeps `Delegate`). Without it the receiver may not.
pub const KEEP_DELEGATE: usize = 1 << 8;
/// What a receiving syscall got, returned in `a7`.
pub const KIND_MESSAGE: usize = 0;
pub const KIND_CALL: usize = 1;
@@ -115,6 +119,53 @@ pub fn reg_slot(reg: usize) -> Option<Option<CapSlot>> {
}
}
/// A capability sent with a message.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Give {
pub slot: CapSlot,
/// The receiver gets it with `Delegate`, so it can pass it on; only
/// if the sender's own copy has `Delegate` to give.
pub keep_delegate: bool,
}
impl Give {
pub const fn keeping_delegate(slot: CapSlot) -> Self {
Self {
slot,
keep_delegate: true,
}
}
}
impl From<CapSlot> for Give {
fn from(slot: CapSlot) -> Self {
Self {
slot,
keep_delegate: false,
}
}
}
pub fn give_reg(give: Option<Give>) -> usize {
match give {
None => NONE,
Some(g) => g.slot.value() as usize | if g.keep_delegate { KEEP_DELEGATE } else { 0 },
}
}
/// Decodes a message's capability register: `Some(None)` for `NONE`,
/// `None` for anything but a valid slot, optionally with `KEEP_DELEGATE`.
pub fn reg_give(reg: usize) -> Option<Option<Give>> {
if reg == NONE {
return Some(None);
}
let slot = reg_slot(reg & !KEEP_DELEGATE)??;
Some(Some(Give {
slot,
keep_delegate: reg & KEEP_DELEGATE != 0,
}))
}
pub fn perms_reg(perms: PagePerms) -> usize {
match perms {
PagePerms::ReadOnly => PERMS_READ_ONLY,
@@ -133,14 +184,14 @@ pub fn reg_perms(reg: usize) -> Option<PagePerms> {
}
/// Argument registers `a0..a5` of a message-carrying call.
pub fn message_regs(target: usize, words: [usize; 4], cap: Option<CapSlot>) -> [usize; 6] {
pub fn message_regs(target: usize, words: [usize; 4], cap: Option<Give>) -> [usize; 6] {
[
target,
words[0],
words[1],
words[2],
words[3],
slot_reg(cap),
give_reg(cap),
]
}