feat(kernel): shared ABI decoder+proofs, no-panic gate, syscall storm, kani on shrunk kernel
This commit is contained in:
@@ -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);
|
||||
|
||||
|
||||
62
crates/types/src/proofs/abi.rs
Normal file
62
crates/types/src/proofs/abi.rs
Normal 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(®s) == 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");
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
mod abi;
|
||||
mod bounded;
|
||||
mod generation;
|
||||
mod permissions;
|
||||
|
||||
@@ -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])?,
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user