feat(boot): gatekeeper gates boot (init→UI→gatekeeper→data); keystore disk + virtio-rng discovery; terminal READ_SECRET; storage SET_KEY; PageCount ≤8192, alloc cap gone; device window 0x01D0_0000; test-qemu types passcode over 2 boots
This commit is contained in:
@@ -1,8 +1,9 @@
|
||||
//! Finds the board's devices (`types::bootinfo`): disk, GPU, and keyboard
|
||||
//! (or the UART when there is none).
|
||||
|
||||
use hw_virtio::block::CONFIG_CAPACITY;
|
||||
use hw_virtio::mmio::{
|
||||
DEVICE_ID_BLOCK, DEVICE_ID_GPU, DEVICE_ID_INPUT, MAGIC, REG_DEVICE_ID, REG_MAGIC,
|
||||
DEVICE_ID_BLOCK, DEVICE_ID_GPU, DEVICE_ID_INPUT, DEVICE_ID_RNG, MAGIC, REG_DEVICE_ID, REG_MAGIC,
|
||||
};
|
||||
use libkos::device::{self, Device};
|
||||
use libkos::syscall;
|
||||
@@ -19,8 +20,14 @@ pub struct Devices {
|
||||
pub disk: Device,
|
||||
pub gpu: Device,
|
||||
pub input: Device,
|
||||
/// The gatekeeper's: a small disk of its own for the key slots.
|
||||
pub keystore: Device,
|
||||
pub random: Device,
|
||||
}
|
||||
|
||||
/// A block device this small (1 MiB) is the keystore, not the disk.
|
||||
const KEYSTORE_MAX_SECTORS: u64 = 2048;
|
||||
|
||||
fn slot(raw: u64) -> Option<CapSlot> {
|
||||
if raw == NO_SLOT {
|
||||
return None;
|
||||
@@ -28,21 +35,30 @@ fn slot(raw: u64) -> Option<CapSlot> {
|
||||
CapSlot::new(u8::try_from(raw).ok()?)
|
||||
}
|
||||
|
||||
/// The virtio device id behind `region`, 0 for an empty slot. Read through
|
||||
/// a copy of the capability, mapped only while looking and then dropped,
|
||||
/// which unmaps it.
|
||||
fn virtio_id(region: CapSlot) -> u32 {
|
||||
/// The virtio device id behind `region` (0 for an empty slot) and, for a
|
||||
/// block device, its size in sectors. Read through a copy of the
|
||||
/// capability, mapped only while looking and then dropped, which unmaps it.
|
||||
fn virtio_id(region: CapSlot) -> (u32, u64) {
|
||||
let perms = PermissionSet::empty().with(Permission::Read).with(Permission::Write);
|
||||
let Ok(copy) = syscall::mint(region, Badge::NONE, perms) else {
|
||||
return 0;
|
||||
return (0, 0);
|
||||
};
|
||||
let id = match device::map_mmio(copy, PROBE_VA) {
|
||||
// The registers stay mapped until the drop below.
|
||||
Ok(regs) => match regs.read::<u32>(REG_MAGIC) == MAGIC {
|
||||
true => regs.read(REG_DEVICE_ID),
|
||||
false => 0,
|
||||
true => {
|
||||
let id = regs.read(REG_DEVICE_ID);
|
||||
let size = match id {
|
||||
DEVICE_ID_BLOCK => {
|
||||
regs.read::<u32>(CONFIG_CAPACITY) as u64 | (regs.read::<u32>(CONFIG_CAPACITY + 4) as u64) << 32
|
||||
}
|
||||
_ => 0,
|
||||
};
|
||||
(id, size)
|
||||
}
|
||||
false => (0, 0),
|
||||
},
|
||||
Err(_) => 0,
|
||||
Err(_) => (0, 0),
|
||||
};
|
||||
syscall::drop_cap(copy);
|
||||
id
|
||||
@@ -74,7 +90,13 @@ pub fn console() -> Option<CapSlot> {
|
||||
/// Sorts the board's devices out.
|
||||
pub fn discover() -> Devices {
|
||||
let info = boot_info();
|
||||
let mut found = Devices { disk: Device::NONE, gpu: Device::NONE, input: Device::NONE };
|
||||
let mut found = Devices {
|
||||
disk: Device::NONE,
|
||||
gpu: Device::NONE,
|
||||
input: Device::NONE,
|
||||
keystore: Device::NONE,
|
||||
random: Device::NONE,
|
||||
};
|
||||
let mut uart = Device::NONE;
|
||||
for d in info.devices() {
|
||||
let Some(region) = slot(d.region_slot) else {
|
||||
@@ -84,9 +106,11 @@ pub fn discover() -> Devices {
|
||||
let place = match d.kind {
|
||||
KIND_UART_16550 => &mut uart,
|
||||
KIND_VIRTIO => match virtio_id(region) {
|
||||
DEVICE_ID_BLOCK => &mut found.disk,
|
||||
DEVICE_ID_GPU => &mut found.gpu,
|
||||
DEVICE_ID_INPUT => &mut found.input,
|
||||
(DEVICE_ID_BLOCK, size) if size <= KEYSTORE_MAX_SECTORS => &mut found.keystore,
|
||||
(DEVICE_ID_BLOCK, _) => &mut found.disk,
|
||||
(DEVICE_ID_GPU, _) => &mut found.gpu,
|
||||
(DEVICE_ID_INPUT, _) => &mut found.input,
|
||||
(DEVICE_ID_RNG, _) => &mut found.random,
|
||||
_ => continue,
|
||||
},
|
||||
_ => continue,
|
||||
|
||||
@@ -110,8 +110,12 @@ impl<'a> Launcher<'a> {
|
||||
if let Some(free) = self.served.iter_mut().find(|s| s.is_none()) {
|
||||
*free = Some((p.name, ep));
|
||||
}
|
||||
if let Some(device) = p.device {
|
||||
note = self.give(device, ep);
|
||||
for device in p.devices {
|
||||
note = self.give(*device, ep);
|
||||
}
|
||||
if p.unlocks {
|
||||
unlock(ep)?;
|
||||
note = ", the owner is in";
|
||||
}
|
||||
if console.is_some() {
|
||||
hand_over_console(ep);
|
||||
@@ -139,6 +143,8 @@ impl<'a> Launcher<'a> {
|
||||
let (dev, given, none): (BoardDevice, _, _) = match device {
|
||||
Device::Gpu => (self.devices.gpu, ", gpu handed over", ", no gpu"),
|
||||
Device::Input => (self.devices.input, ", input device handed over", ", no input device"),
|
||||
Device::Keystore => (self.devices.keystore, ", keystore handed over", ", no keystore"),
|
||||
Device::Random => (self.devices.random, ", randomness handed over", ", no randomness"),
|
||||
};
|
||||
match libkos::device::give_device(ep, dev).is_ok() && dev.mmio.is_some() {
|
||||
true => given,
|
||||
@@ -147,6 +153,23 @@ impl<'a> Launcher<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Waits for the gatekeeper behind `ep` to authenticate the owner, then
|
||||
/// passes the master key on to storage, which alone keeps it.
|
||||
fn unlock(ep: CapSlot) -> Result<(), &'static str> {
|
||||
use types::gatekeeper::{KEY, UNLOCK, reply};
|
||||
match syscall::request(ep, [UNLOCK, 0, 0, 0]) {
|
||||
Ok([reply::OK, ..]) => {}
|
||||
_ => return Err("the gatekeeper could not unlock"),
|
||||
}
|
||||
let mut key = syscall::request(ep, [KEY, 0, 0, 0]).map_err(|_| "the gatekeeper gave no key")?;
|
||||
let given = [0, 1].iter().all(|&half| {
|
||||
let words = [types::storage::op::SET_KEY, half, key[half * 2], key[half * 2 + 1]];
|
||||
syscall::request(crate::STORAGE_EP, words).is_ok_and(|r| r[0] == types::storage::reply::OK)
|
||||
});
|
||||
key.fill(0);
|
||||
if given { Ok(()) } else { Err("storage would not take the key") }
|
||||
}
|
||||
|
||||
/// From here on init prints through the log server, like everyone else,
|
||||
/// and holds no console of its own.
|
||||
fn hand_over_console(log: CapSlot) {
|
||||
|
||||
@@ -80,6 +80,11 @@ fn launch_all(launcher: &mut launch::Launcher, programs: &[manifest::Program]) {
|
||||
libkos::print!("init: loading {}... ", p.name);
|
||||
match launcher.launch(p) {
|
||||
Ok(note) => libkos::println!("ok{}", note),
|
||||
// Without the owner, nothing that touches user data may start.
|
||||
Err(why) if p.unlocks => {
|
||||
libkos::println!("STOP ({})", why);
|
||||
idle();
|
||||
}
|
||||
Err(why) => libkos::println!("SKIP ({})", why),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,8 @@ use types::budget::Limits;
|
||||
pub enum Device {
|
||||
Gpu,
|
||||
Input,
|
||||
Keystore,
|
||||
Random,
|
||||
}
|
||||
|
||||
/// A storage grant: a folder (or the whole disk, `b""`) and whether it may
|
||||
@@ -23,7 +25,11 @@ pub struct Program {
|
||||
pub limits: Limits,
|
||||
/// Serves an endpoint, which it finds in slot 0.
|
||||
pub serves: bool,
|
||||
pub device: Option<Device>,
|
||||
/// Handed over in this order, after it starts.
|
||||
pub devices: &'static [Device],
|
||||
/// Authenticates the owner: init waits for the master key before
|
||||
/// starting anything that touches user data.
|
||||
pub unlocks: bool,
|
||||
/// Earlier programs' endpoints it may call, in its next slots; each
|
||||
/// program's copy carries its own badge, so a server can tell them apart.
|
||||
pub uses: &'static [&'static str],
|
||||
@@ -51,7 +57,8 @@ const fn server(name: &'static str, file: &'static [u8], limits: Limits) -> Prog
|
||||
file,
|
||||
limits,
|
||||
serves: true,
|
||||
device: None,
|
||||
devices: &[],
|
||||
unlocks: false,
|
||||
uses: &[],
|
||||
storage: None,
|
||||
console: false,
|
||||
@@ -63,16 +70,26 @@ const fn server(name: &'static str, file: &'static [u8], limits: Limits) -> Prog
|
||||
/// Started on every boot. The log server comes first so the rest can print.
|
||||
pub const SERVERS: &[Program] = &[
|
||||
Program { console: true, ..server("log", b"bin/log", SMALL) },
|
||||
Program { device: Some(Device::Input), ..server("input", b"bin/input", Limits { regions: 4, ..SMALL }) },
|
||||
Program { devices: &[Device::Input], ..server("input", b"bin/input", Limits { regions: 4, ..SMALL }) },
|
||||
// The input server's only client: it passes input on to the app in focus.
|
||||
Program {
|
||||
device: Some(Device::Gpu),
|
||||
devices: &[Device::Gpu],
|
||||
uses: &["input"],
|
||||
// The framebuffer alone is ~1000 pages at 1280x800.
|
||||
..server("display", b"bin/display", Limits { pages: 1280, regions: 4, ..SMALL })
|
||||
},
|
||||
// Text on the display, lines from the keyboard (it takes input focus).
|
||||
Program { uses: &["display"], ..server("terminal", b"bin/terminal", SMALL) },
|
||||
// Nothing that touches user data starts before the owner is in. It
|
||||
// holds the screen (through the terminal), its keystore and randomness,
|
||||
// and exits once it has handed over the key.
|
||||
Program {
|
||||
uses: &["terminal"],
|
||||
devices: &[Device::Keystore, Device::Random],
|
||||
unlocks: true,
|
||||
// argon2's 16 MiB, its image and a little more.
|
||||
..server("gatekeeper", b"bin/gatekeeper", Limits { pages: 4096 + 160, regions: 6, ..SMALL })
|
||||
},
|
||||
// The only one that equips apps (docs/apps.md): it reaches every app's
|
||||
// files and the owner's decisions, and runs apps through init.
|
||||
Program {
|
||||
|
||||
Reference in New Issue
Block a user