feat(kernel): ipc cap transfer drops Delegate; one owner per irq line, freed when owner dies
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
use types::{
|
||||
badge::Badge,
|
||||
generation::Generation,
|
||||
id::{IrqId, NotificationId},
|
||||
id::{IrqId, NotificationId, ProcessId},
|
||||
};
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -17,6 +17,8 @@ pub struct IrqRoute {
|
||||
pub notification: NotificationId,
|
||||
pub generation: Generation,
|
||||
pub badge: Badge,
|
||||
/// The process that set the route, and so owns the line.
|
||||
pub owner: ProcessId,
|
||||
}
|
||||
|
||||
pub struct IrqRoutingTable {
|
||||
@@ -53,6 +55,15 @@ impl IrqRoutingTable {
|
||||
}
|
||||
}
|
||||
|
||||
/// Drops every route `owner` set, when it dies.
|
||||
pub fn clear_owned_by(&mut self, owner: ProcessId) {
|
||||
for slot in self.routes.iter_mut() {
|
||||
if slot.is_some_and(|r| r.owner == owner) {
|
||||
*slot = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The route for `irq`; `None` for unknown or unrouted lines.
|
||||
pub fn route(&self, irq: IrqId) -> Option<IrqRoute> {
|
||||
*self.routes.get(irq.raw() as usize)?
|
||||
|
||||
@@ -8,6 +8,7 @@ fn route(n: u16) -> IrqRoute {
|
||||
notification: NotificationId::from_raw(n),
|
||||
generation: Generation::initial(),
|
||||
badge: Badge::new(1 << n),
|
||||
owner: types::id::ProcessId::from_raw(0),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -46,10 +46,15 @@ impl<P: Platform> Kernel<P> {
|
||||
if !grant {
|
||||
return None;
|
||||
}
|
||||
let copy: Capability = self
|
||||
.check_cap(from, slot, Permission::Delegate)
|
||||
.ok()?
|
||||
.clone();
|
||||
// The receiver gets the capability but not the right to pass it
|
||||
// on; a sender that wants that uses Delegate with explicit rights.
|
||||
let source = self.check_cap(from, slot, Permission::Delegate).ok()?;
|
||||
let copy = Capability::new(
|
||||
*source.resource(),
|
||||
source.permissions().without(Permission::Delegate),
|
||||
*source.generation(),
|
||||
)
|
||||
.with_badge(source.badge());
|
||||
let parent = self.cap_ref(from, slot);
|
||||
self.insert_derived(to, copy, parent).ok()
|
||||
}
|
||||
|
||||
@@ -27,10 +27,14 @@ impl<P: Platform> Kernel<P> {
|
||||
let irq = self.interrupt_cap(pid, irq_slot)?;
|
||||
let (notification, badge, generation) =
|
||||
self.notification_cap(pid, ntfn_slot, Permission::Write)?;
|
||||
if self.irq_owner(irq).is_some_and(|owner| owner != pid) {
|
||||
return Err(KernelError::IrqAlreadyOwned(irq));
|
||||
}
|
||||
let route = domain::irq::IrqRoute {
|
||||
notification,
|
||||
generation,
|
||||
badge,
|
||||
owner: pid,
|
||||
};
|
||||
if self.irqs.set(irq, route) {
|
||||
Ok(())
|
||||
@@ -45,10 +49,22 @@ impl<P: Platform> Kernel<P> {
|
||||
slot: CapSlot,
|
||||
) -> Result<(), KernelError> {
|
||||
let irq = self.interrupt_cap(pid, slot)?;
|
||||
if self.irq_owner(irq).is_some_and(|owner| owner != pid) {
|
||||
return Err(KernelError::IrqAlreadyOwned(irq));
|
||||
}
|
||||
self.platform.unmask_irq(irq);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// The process that routed `irq`, while its route still leads to a
|
||||
/// live notification. One owner per line: other holders of the IRQ
|
||||
/// capability cannot take it over.
|
||||
fn irq_owner(&self, irq: IrqId) -> Option<ProcessId> {
|
||||
let route = self.irqs.route(irq)?;
|
||||
(self.notifications.generation(route.notification) == Some(route.generation))
|
||||
.then_some(route.owner)
|
||||
}
|
||||
|
||||
/// An interrupt fired: mask the line and signal its notification. The
|
||||
/// line stays masked until the handler acknowledges it.
|
||||
pub fn handle_interrupt(&mut self, irq: IrqId) {
|
||||
|
||||
@@ -26,6 +26,7 @@ impl<P: Platform> Kernel<P> {
|
||||
self.delete_all_caps(pid);
|
||||
self.destroy_endpoints_owned_by(pid);
|
||||
self.destroy_notifications_owned_by(pid);
|
||||
self.irqs.clear_owned_by(pid);
|
||||
self.teardown_memory(pid);
|
||||
|
||||
let _ = self.processes.destroy(pid);
|
||||
|
||||
@@ -12,7 +12,7 @@ use domain::thread::ThreadState;
|
||||
use types::address::DataRef;
|
||||
use types::badge::Badge;
|
||||
use types::bounded::CapSlot;
|
||||
use types::id::{EndpointId, IrqId, NotificationId, ThreadId};
|
||||
use types::id::{EndpointId, IrqId, NotificationId, ProcessId, ThreadId};
|
||||
use types::permission::{Permission, PermissionSet};
|
||||
|
||||
fn perms(ps: &[Permission]) -> PermissionSet {
|
||||
@@ -869,3 +869,129 @@ fn interrupts_beyond_the_routing_table_are_ignored() {
|
||||
let mut t = Ipc::new();
|
||||
t.k.handle_interrupt(IrqId::from_raw(1000));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_received_cap_cannot_be_passed_on() {
|
||||
let mut t = Ipc::new();
|
||||
let (a, b, ta, tb, a_send, b_recv) = (t.a, t.b, t.ta, t.tb, t.a_send, t.b_recv);
|
||||
let other = t.k.create_endpoint(a).unwrap();
|
||||
let gift =
|
||||
t.k.grant_cap_for_test(a, Resource::Endpoint(other), perms(SEND_GRANT));
|
||||
t.run(
|
||||
ta,
|
||||
Syscall::Send {
|
||||
endpoint: a_send,
|
||||
message: msg([0; 4]).with_cap(gift),
|
||||
},
|
||||
);
|
||||
let d = delivered(&t.run(tb, Syscall::Receive { endpoint: b_recv })).unwrap();
|
||||
let got = d.cap.expect("the cap arrives");
|
||||
let rights =
|
||||
*t.k.processes
|
||||
.get(b)
|
||||
.unwrap()
|
||||
.capabilities()
|
||||
.get(got)
|
||||
.unwrap()
|
||||
.permissions();
|
||||
assert!(
|
||||
rights.allows(Permission::Execute),
|
||||
"the right itself arrives"
|
||||
);
|
||||
assert!(
|
||||
!rights.allows(Permission::Delegate),
|
||||
"but not the right to pass it on"
|
||||
);
|
||||
let mint = t.run(
|
||||
tb,
|
||||
Syscall::Mint {
|
||||
slot: got,
|
||||
badge: Badge::new(1),
|
||||
permissions: perms(SEND),
|
||||
},
|
||||
);
|
||||
assert!(matches!(
|
||||
error(&mint),
|
||||
Some(KernelError::PermissionDenied { .. })
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn an_irq_has_one_owner_until_it_dies() {
|
||||
let mut t = Ipc::new();
|
||||
let (a, b, ta, tb) = (t.a, t.b, t.ta, t.tb);
|
||||
let irq = IrqId::from_raw(12);
|
||||
let route = |t: &mut Ipc, pid: ProcessId, tid: ThreadId| {
|
||||
let irq_cap =
|
||||
t.k.grant_cap_for_test(pid, Resource::Interrupt(irq), perms(&[Permission::Execute]));
|
||||
let n = notification(t, pid);
|
||||
let sig =
|
||||
t.k.grant_cap_for_test(pid, Resource::Notification(n), perms(&[Permission::Write]));
|
||||
(
|
||||
irq_cap,
|
||||
t.run(
|
||||
tid,
|
||||
Syscall::SetIrqNotification {
|
||||
irq: irq_cap,
|
||||
notification: sig,
|
||||
},
|
||||
),
|
||||
)
|
||||
};
|
||||
let (_, first) = route(&mut t, b, tb);
|
||||
assert!(error(&first).is_none());
|
||||
let (a_irq, stolen) = route(&mut t, a, ta);
|
||||
assert!(matches!(
|
||||
error(&stolen),
|
||||
Some(KernelError::IrqAlreadyOwned(_))
|
||||
));
|
||||
let ack = t.run(ta, Syscall::AcknowledgeInterrupt { irq: a_irq });
|
||||
assert!(
|
||||
matches!(error(&ack), Some(KernelError::IrqAlreadyOwned(_))),
|
||||
"nor unmask it"
|
||||
);
|
||||
let (_, again) = route(&mut t, b, tb);
|
||||
assert!(
|
||||
error(&again).is_none(),
|
||||
"the owner may re-route its own line"
|
||||
);
|
||||
|
||||
t.k.destroy_process(b);
|
||||
let (_, claimed) = route(&mut t, a, ta);
|
||||
assert!(error(&claimed).is_none(), "a dead owner's line is free");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_dead_owner_frees_its_line_even_if_the_notification_lives_on() {
|
||||
let mut t = Ipc::new();
|
||||
let (a, b, ta, tb) = (t.a, t.b, t.ta, t.tb);
|
||||
let irq = IrqId::from_raw(13);
|
||||
// B routes the line to a notification A owns, then dies.
|
||||
let b_irq =
|
||||
t.k.grant_cap_for_test(b, Resource::Interrupt(irq), perms(&[Permission::Execute]));
|
||||
let n = notification(&mut t, a);
|
||||
let b_sig =
|
||||
t.k.grant_cap_for_test(b, Resource::Notification(n), perms(&[Permission::Write]));
|
||||
let routed = t.run(
|
||||
tb,
|
||||
Syscall::SetIrqNotification {
|
||||
irq: b_irq,
|
||||
notification: b_sig,
|
||||
},
|
||||
);
|
||||
assert!(error(&routed).is_none());
|
||||
t.k.destroy_process(b);
|
||||
|
||||
let a_irq =
|
||||
t.k.grant_cap_for_test(a, Resource::Interrupt(irq), perms(&[Permission::Execute]));
|
||||
let a_sig =
|
||||
t.k.grant_cap_for_test(a, Resource::Notification(n), perms(&[Permission::Write]));
|
||||
let claimed = t.run(
|
||||
ta,
|
||||
Syscall::SetIrqNotification {
|
||||
irq: a_irq,
|
||||
notification: a_sig,
|
||||
},
|
||||
);
|
||||
assert!(error(&claimed).is_none());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user