feat(kernel): shared ABI decoder+proofs, no-panic gate, syscall storm, kani on shrunk kernel

This commit is contained in:
2026-10-26 23:44:09 +01:00
parent 154599e3af
commit 130a82f50d
41 changed files with 1583 additions and 404 deletions

View File

@@ -1,6 +1,7 @@
use crate::macros::bounded;
bounded!(CapSlot, u8, 0, 63);
// Kani runs the real kernel with tiny tables; see kernel/src/proofs.
bounded!(CapSlot, u8, 0, if cfg!(kani) { 3 } else { 63 });
bounded!(Priority, u8, 0, 31);
bounded!(PageCount, u16, 1, 1024);

View File

@@ -0,0 +1,62 @@
use crate::bounded::CapSlot;
use crate::syscall_abi::*;
fn any_slot() -> Option<CapSlot> {
if kani::any() {
CapSlot::new(kani::any::<u8>() % (CapSlot::MAX + 1))
} else {
None
}
}
#[kani::proof]
fn optional_slots_roundtrip() {
let s = any_slot();
assert!(reg_slot(slot_reg(s)) == Some(s));
}
#[kani::proof]
fn every_register_decodes_to_none_or_a_valid_slot() {
let r: usize = kani::any();
if let Some(Some(slot)) = reg_slot(r) {
assert!(slot.value() <= CapSlot::MAX);
assert!(slot.value() as usize == r);
}
}
#[kani::proof]
fn page_perms_roundtrip_and_reject_the_rest() {
let r: usize = kani::any();
match reg_perms(r) {
Some(p) => assert!(perms_reg(p) == r),
None => assert!(r > PERMS_READ_EXECUTE),
}
}
/// What the kernel writes for a receive is what libkos reads back.
#[kani::proof]
fn deliveries_roundtrip() {
let kind = match kani::any::<u8>() % 3 {
0 => DeliveryKind::Message,
1 => DeliveryKind::Call,
_ => DeliveryKind::Signal,
};
let d = Delivery {
kind,
badge: kani::any(),
words: kani::any(),
cap: any_slot(),
};
let regs = delivery_regs(&d);
assert!(regs[0] == 0);
assert!(regs_delivery(&regs) == Some(d));
}
#[kani::proof]
fn error_codes_roundtrip() {
let r: usize = kani::any();
if let Some(code) = ErrorCode::from_raw(r) {
assert!(code as usize == r);
assert!(r != 0, "0 means success");
}
}

View File

@@ -4,14 +4,14 @@ use crate::bounded::{CapSlot, PageCount, Priority};
fn capslot_always_valid_index() {
let value: u8 = kani::any();
if let Some(slot) = CapSlot::new(value) {
assert!(slot.value() <= 63);
assert!(slot.value() <= CapSlot::MAX);
}
}
#[kani::proof]
fn capslot_rejects_out_of_range() {
let value: u8 = kani::any();
kani::assume(value > 63);
kani::assume(value > CapSlot::MAX);
assert!(CapSlot::new(value).is_none());
}

View File

@@ -1,3 +1,4 @@
mod abi;
mod bounded;
mod generation;
mod permissions;

View File

@@ -1,5 +1,9 @@
//! Register-level syscall contract shared by the kernel and libkos.
//! See docs/ipc.md for the meaning of each call.
//! See docs/ipc.md for the meaning of each call. Both sides encode and
//! decode through the functions below, so they cannot drift apart.
use crate::bounded::CapSlot;
use crate::mapping::PagePerms;
pub const SYS_SEND: usize = 0;
pub const SYS_RECEIVE: usize = 1;
@@ -91,3 +95,115 @@ impl ErrorCode {
})
}
}
// ---- wire encoding ----
/// An optional capability slot as a register.
pub fn slot_reg(slot: Option<CapSlot>) -> usize {
slot.map_or(NONE, |s| s.value() as usize)
}
/// Decodes an optional capability slot: `Some(None)` for `NONE`, `None`
/// for a value that is neither `NONE` nor a valid slot.
pub fn reg_slot(reg: usize) -> Option<Option<CapSlot>> {
match reg {
NONE => Some(None),
r => Some(Some(CapSlot::new(u8::try_from(r).ok()?)?)),
}
}
pub fn perms_reg(perms: PagePerms) -> usize {
match perms {
PagePerms::ReadOnly => PERMS_READ_ONLY,
PagePerms::ReadWrite => PERMS_READ_WRITE,
PagePerms::ReadExecute => PERMS_READ_EXECUTE,
}
}
pub fn reg_perms(reg: usize) -> Option<PagePerms> {
match reg {
PERMS_READ_ONLY => Some(PagePerms::ReadOnly),
PERMS_READ_WRITE => Some(PagePerms::ReadWrite),
PERMS_READ_EXECUTE => Some(PagePerms::ReadExecute),
_ => None,
}
}
/// Argument registers `a0..a5` of a message-carrying call.
pub fn message_regs(target: usize, words: [usize; 4], cap: Option<CapSlot>) -> [usize; 6] {
[
target,
words[0],
words[1],
words[2],
words[3],
slot_reg(cap),
]
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DeliveryKind {
/// A `Send`; no reply expected.
Message,
/// A `Call`; the receiver's reply slot now names the caller.
Call,
/// Notification bits.
Signal,
}
/// What a receiving syscall hands back.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Delivery {
pub kind: DeliveryKind,
/// The sender's badge, or the signal bits.
pub badge: usize,
pub words: [usize; 4],
/// Where a transferred capability landed in the receiver's table.
pub cap: Option<CapSlot>,
}
impl Delivery {
pub fn signal(bits: usize) -> Self {
Self {
kind: DeliveryKind::Signal,
badge: bits,
words: [0; 4],
cap: None,
}
}
}
/// Result registers `a0..a7` for a successful receive.
pub fn delivery_regs(d: &Delivery) -> [usize; 8] {
let kind = match d.kind {
DeliveryKind::Message => KIND_MESSAGE,
DeliveryKind::Call => KIND_CALL,
DeliveryKind::Signal => KIND_SIGNAL,
};
[
0,
d.badge,
d.words[0],
d.words[1],
d.words[2],
d.words[3],
slot_reg(d.cap),
kind,
]
}
/// Inverse of `delivery_regs`; `None` if the registers are not a delivery.
pub fn regs_delivery(r: &[usize; 8]) -> Option<Delivery> {
let kind = match r[7] {
KIND_MESSAGE => DeliveryKind::Message,
KIND_CALL => DeliveryKind::Call,
KIND_SIGNAL => DeliveryKind::Signal,
_ => return None,
};
Some(Delivery {
kind,
badge: r[1],
words: [r[2], r[3], r[4], r[5]],
cap: reg_slot(r[6])?,
})
}